Lit Web Component function not firing from parent in Chrome Extension - google-chrome-extension

I am dynamically injecting a lit web component into the content script of a Chrome Extension. The component renders, but I'm unable to call a function within the component from the parent page that works outwith the extension environment (but not dynamically injected I might add). The code looks like this:
window.addEventListener('WebComponentsReady', () => {
const script = document.createElement('script');
script.setAttribute("type", "module");
script.setAttribute("src", 'https://localhost:8000/add-to-list.js');
script.onload = () => {
console.log('loaded');
};
const head = document.head || document.getElementsByTagName("head")[0] || document.documentElement;
head.insertBefore(script, head.lastChild);
var box = document.createElement('add-to-list');
box.className = 'comp';
box.id = 'comp'
document.body.appendChild(box);
setTimeout(function () {
// would be called somewhere else later, but error "productAdded is not a function"
document.getElementById('comp').productAdded('id', 'success');
},100);
});
I've injected a webcomponent polymer into the chrome extension, but that does not help "webcomponents-sd-ce.js". The snippet of the manifest is this:
"content_scripts": [
{
"matches": [ "<all_urls>"],
"js": [ "webcomponents-sd-ce.js" ]
},
{
"matches": [
"https://*/*",
"http://*/*"
],
"js": [
"src/inject/inject.js"
]
}
]
Any advice on why I can't call the function within the component?

Related

How do I stop MutationObserver from inserting my Chrome Extension multiple times on a SPA website?

For my Chrome Extension,
I'm searching multiple classes/id's to then include their text with some HTML I'm inserting into the website.
When I run the extension,
the HTML widget I'm inserting is inserted multiple times but should only be done once.
I realize MutationObserver is intended to check for every DOM change and seemingly inserts my widget every time that happens I presume. However, I'm not sure how best to approach making sure the widget is only inserted once.
I tried using observer.disconnect(); which helps on initial load to insert the widget once but then it won't re-insert the widget on return to the page – will only on refresh. Couldn't figure out a way to turn observe back on.
Not sure if disconnect and reconnect is even a smart path but it's what I came up with.
In the attached screenshot, you can see it keeps adding the widget with DOM changes.
How should I approach fixing this?
Thank you!
Link to download the extension
manifest.json
{
"manifest_version": 3,
"name": "Test SO Mutation Answer",
"description": "Example for StackOverflow",
"version": "0.0.1",
"host_permissions": ["<all_urls>"],
"permissions": ["storage", "activeTab", "scripting", "tabs", "webNavigation"],
"content_scripts": [
{
"matches": ["https://www.zillow.com/*"],
"js": ["ballpark.js"],
"css": ["main.css"]
}
],
"web_accessible_resources": [
{
"resources": ["/images/*"],
"matches": ["<all_urls>"]
}
]
}
ballpark.js
const observer = new MutationObserver(function() {
if (document.getElementsByClassName('ds-action-bar')[0]) {
if (document.querySelector('[data-testid="price"] span') && document.querySelector('.ds-expandable-card .eUxMDw')) {
console.log("All data found");
startWidget();
if (document.getElementById('ballpark-container')) {
console.log("Do nothing, BP exists");
} else {
console.log("Adding BP...");
insertWidget();
}
} else {
console.log("Cannot find all data");
}
} else {
offerPrice = undefined;
monthlyPITI = undefined;
}
})
const target = document.querySelector('body');
const config = { childList: true };
observer.observe(target, config);
var offerPrice;
var monthlyPITI;
function startWidget() {
getAskingPrice();
getExpenses();
}
// Get Price from Zillow
function getAskingPrice() {
var askingPrice = document.querySelector('[data-testid="price"] span');
if(askingPrice !== null) {
offerPrice = parseFloat(askingPrice.innerText.replace(/\$|,/g, ''));
console.log(offerPrice + " Offer Price");
} else {
console.log("Null Asking Price Div");
}
}
function getExpenses() {
var monthlyPITIZillow = document.querySelector('.ds-expandable-card .eUxMDw');
if (monthlyPITIZillow !== null) {
monthlyPITI = parseFloat(monthlyPITIZillow.innerText.replace(/\$|,/g, ''));
console.log(monthlyPITI + " PITI");
} else {
console.log("Null Monthly PITI Div");
}
}
// Find Zillow widget to insert the extension widget
function insertWidget() {
const select_div_for_bpd = document.querySelector('div.Spacer-c11n-8-65-2__sc-17suqs2-0');
if(select_div_for_bpd !== null) {
const ballpark_container = document.createElement("div");
ballpark_container.setAttribute("id", "ballpark-container");
select_div_for_bpd.appendChild(ballpark_container);
ballpark_container.innerHTML = `
<div class="ballpark-roi-container">
<div><h1>${offerPrice}</h1> Offer Price</div>
<div><h1>${monthlyPITI}</h1> Monthly PITI</div>
</div>
`;
} else {
console.log("Cannot insert your widget");
}
}

chrome extension to read post request data

I am trying to read json of post request using Chrome Extension. So i have created a extension with following code:-
menifest.json file:-
{
"name": "numera name",
"version": "1.0",
"description": "numera",
"manifest_version": 2,
"content_scripts": [
{
"matches": ["<all_urls>"],
"run_at": "document_start",
"js": ["contentScript.js"]
}
],
"permissions": [
"activeTab",
"tabs"
]
}
contentScript.js
function interceptData() {
var xhrOverrideScript = document.createElement('script');
xhrOverrideScript.type = 'text/javascript';
xhrOverrideScript.innerHTML = '
(function() {
var XHR = XMLHttpRequest.prototype;
var send = XHR.send;
var open = XHR.open;
XHR.open = function(method, url) {
this.url = url; // the request url
return open.apply(this, arguments);
}
XHR.send = function() {
this.addEventListener('load', function() {
if (this.url.includes('WaitForResponsesData')) {
var dataDOMElement = document.createElement('div');
dataDOMElement.id = '__interceptedData';
dataDOMElement.innerText = this.response;
console.log(this.response);
dataDOMElement.style.height = 0;
//dataDOMElement.style.overflow = 'hidden';
document.body.appendChild(dataDOMElement);
}
});
return send.apply(this, arguments);
};
})();'
document.head.prepend(xhrOverrideScript);
}
function checkForDOM() {
if (document.body && document.head) {
interceptData();
} else {
requestIdleCallback(checkForDOM);
}
}
requestIdleCallback(checkForDOM);
function scrapeData() {
var responseContainingEle = document.getElementById('__interceptedData');
if (responseContainingEle) {
var response = JSON.parse(responseContainingEle.innerHTML);
} else {
requestIdleCallback(scrapeData);
}
}
requestIdleCallback(scrapeData);
But I am not sure why this code is not working i have added console log and alert as well not no luck,
Also i have tried to search __interceptedData ID in page HTML but that is also not there.
I followed this URL https://medium.com/better-programming/chrome-extension-intercepting-and-reading-the-body-of-http-requests-dd9ebdf2348b
please help me out from this

How can I run jQuery if($(this) statements to send browser.runtime.sendMessage?

How can I use jQuery if($(this) to update my extensions badge? I've tried the code below and it doesn't work.
jQuery("a[onclick*='ga']").removeAttr('onclick');
if($(this).attr('onclick') == 'ga'){
var add = browser.runtime.sendMessage(message, 'Removed += 1')
}
It should add +1 for each element matching a[onclick='ga'] because that's what it's going to block.
The required scripts for the extension:
manifest.json
{
"manifest_version": 2,
"name": "Anti Click Tracking",
"version": "2.2.4",
"description": "Remove Known HTML <a> and <link> Click Tracking Attributes.",
"background": {
"scripts": ["background.js"]
},
"content_scripts": [
{
"js": [
"/jquery/jquery-3.4.0.min.js", "remove.js"
],
"matches": [
"<all_urls>"
]
}
],
"permissions": ["<all_urls>", "webRequest", "webRequestBlocking"],
}
background.js
chrome.runtime.onMessage.addListener(function(message, sender, sendResponse) {
if (!message) return
if (message <= 0) return
const text = message.toString()
chrome.tabs.get(sender.tab.id, function(tab) {
// Pre-rendered tab has been nuked
if (chrome.runtime.lastError) return
// Tab is visible
if (tab.index >= 0) {
chrome.browserAction.setBadgeText({
tabId: tab.id,
text: text
})
return
}
// Invisible pre-rendered tab
const tabId = sender.tab.id
chrome.webNavigation.onCommitted.addListener(function update(details) {
if (details.tabId == tabId) {
chrome.browserAction.setBadgeText({
tabId: tabId,
text: text
})
chrome.webNavigation.onCommitted.removeListener(update)
}
})
})
})
remove.js
// Remove ping attribute from <a> (somewhat based on PingBlock)
window.onload = function() {
let Removed = 0
const anchorElements = document.getElementsByTagName('A')
for (element of anchorElements) {
if (!element.getAttribute('ping')) continue
console.log("Removed ping: " + element.getAttribute('ping'))
element.removeAttribute('ping')
Removed += 1
chrome.extension.sendMessage(Removed)
}
}
// Remove Link Tracking
link();
function link(){
jQuery("a[onclick*='ga']").removeAttr('onclick');
jQuery("a[onclick*='_gaq.push']").removeAttr('onclick');
jQuery("link[rel*='pingback']").removeAttr('rel');
}
All this code together only updates the badge when the ping attribute is blocked. I need it to also update the badge when jQuery removes the selected attributes. Is it possible to use the Removed += 1 like I did under the first part for jQuery or will I have to change it to something else?

How to get user selection in the Microsoft Word Online documents

I'm creating a Chrome extension that should interact with the user selection in the Microsoft Word Online documents - add new highlighting instead of the natural selection highlighting and then remove it.
The problem is that I'm not able to get the user selection: the response for the window.getSelection() returns the result like the selection is empty.
Here are files from my extension:
manifest.json
{
"manifest_version": 2,
"name": "The extension name",
"version": "1.0",
"description": "This extension description",
"browser_action": {
"default_icon": "icon.png",
"default_popup": "popup.html"
},
"icons": {
"128": "icon.png"
},
"content_scripts": [{
"matches": ["<all_urls>"],
"js": ["content_script.js"],
"run_at": "document_end",
"all_frames": true
}],
"permissions": ["activeTab"]
}
popup.html
<!doctype html>
<html>
<head>
<script src="popup.js"></script>
</head>
<body>
<div id="wrapper">
<form id="settings" name="settings">
<div id="apply" class="form-row">
<input type="submit" name="apply" value="Apply"/>
</div>
</form>
</div>
</body>
</html>
popup.js
document.addEventListener('DOMContentLoaded', function() {
document.getElementById("settings").addEventListener("submit", function (e) {
e.preventDefault();
chrome.tabs.executeScript(null, {file: 'toolbar.js'}, function() {
chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
chrome.tabs.sendMessage(tabs[0].id, {});
});
);
}, false);
});
toolbar.js
function showToolbar() {
var dom_body = document.getElementsByTagName('body')[0];
var tb_wrapper = document.createElement('div');
tb_wrapper.id = "toolbar_wrapper";
var tb_toolbar_play = document.createElement('button');
tb_toolbar_play.id = "toolbar_play";
tb_toolbar_play.title = "Play";
tb_toolbar_play.value = "Play";
tb_wrapper.appendChild(tb_toolbar_play);
dom_body.appendChild(tb_wrapper);
}
showToolbar();
content_script.js
function playButtonOnClickEventListener(request, sender, sendResponse) {
var toolbar = document.getElementById("toolbar_wrapper");
if (toolbar !== null) {
var toolbar_play_button = document.getElementById("toolbar_play");
toolbar_play_button.addEventListener("click", function (e) {
var selection = window.getSelection();
console.log(selection);
});
}
sendResponse({data: "response", success: true});
}
chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
playButtonOnClickEventListener(request, sender, sendResponse);
});
So, what I want to see in the Chrome Developer tools after the console.log(selection) executes:
What I actually get:
P.S. The extension works perfectly with Google Docs.
I'm not sure how the Word Online editor is built or why you can't use window.getSelection(), but one thing I noticed immediately is that when you select text in it, the editor gives that text a Selected class. So maybe you could take advantage of that somehow? Override the style of the relevant elements?
Possibly you could use a MutationObserver and look for this class name being added to elements. You would have to do some experimenting to see if this works for your needs, and obviously if Microsoft change the class name or behaviour, your extension breaks.
Try selecting some text and putting
document.getElementsByClassName("Selected")[0].innerHTML
in the console.
Here's the solution of the issue. Hope it can be useful for somebody once.
Currently I see the whole picture differently, so I changed the number of files and its content.
manifest.json
{
"manifest_version": 2,
"version": "1.1.1",
"name": "Test name",
"description": "Test description",
"browser_action": {
"default_icon": "icon.png"
},
"icons": {
"128": "icon.png"
},
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["content_script.js"],
"run_at": "document_end",
"all_frames": true
}
],
"permissions": ["activeTab"]
}
content_script.js
//Word Online pages contain several different iframes, so, in order
//to find the one where the text that we are going to work with
//is placed, we have to use the Chrome Runtime API and its
//"sendMessage" method that works in every frame.
//https://developer.chrome.com/apps/runtime#method-sendMessage
chrome.runtime.sendMessage("", function() {
if (checkFrameLocation()) {
showToolbar();
}
});
function checkFrameLocation() {
//So as I work with documents in OneDrive (onedrive.live.com)
//and in Sharepoint (<USER_NAME>-my.shrepoint.com), I found out
//that I need iframes with these URLs only.
var trusted_urls = [
"https://euc-word-edit.officeapps.live.com",
"https://word-edit.officeapps.live.com",
"nl1-word-edit.officeapps.live.com"
];
var result = false;
for (var i = 0; i < trusted_urls.length; i++) {
if (window.location.href.indexOf(trusted_urls[i]) > -1) {
result = true;
}
}
return result;
}
function showToolbar() {
var dom_body = document.body;
var tb_wrapper = document.createElement('div');
tb_wrapper.id = "toolbar_wrapper";
tb_wrapper.style.position = "absolute";
tb_wrapper.style.top = "200px";
tb_wrapper.style.left = "200px";
tb_wrapper.style.backgroundColor = "#ffffff";
tb_wrapper.style.height = "30px";
tb_wrapper.style.width = "50px";
var tb_play = document.createElement('button');
tb_play.id = "toolbar_play_button";
tb_play.title = "Play";
tb_play.innerHTML = "►";
tb_play.style.height = "100%";
tb_play.style.width = "100%";
tb_play.style.lineHeight = "12px";
tb_play.addEventListener("click", function() {
playButtonOnClickEventListener();
});
tb_wrapper.appendChild(tb_play);
dom_body.appendChild(tb_wrapper);
}
function playButtonOnClickEventListener() {
//Now we can use the window selection object
var window_selection = window.getSelection();
console.log(window_selection);
//Also, Word Online adds special class name to every selected element
//in the document. So, this is another way to get the selected text.
var elements_selection = document.getElementsByClassName("Selected");
console.log(elements_selection);
}
And here's the proof screenshot:
There are results of accessing the selected text in the Word Online document

Mismatched anonymous define() in Chrome extension content script

I'm trying to build a Chrome extension with TypeScript.
The setup is quite simple:
In manifest.json
{
"permissions": [
"webRequest",
"webRequestBlocking",
"tabs",
"storage",
"http://*/",
"https://*/*"
],
"content_scripts": [
{
"matches": [ "http://*/*", "https://*/*" ],
"js": [ "scripts/require.js", "scripts/require-cs.js",
"scripts/main.js", "scripts/contentscript.js" ],
"run_at": "document_end",
"all_frames": true
}],
}
In model.ts:
export class WebPage
{
private id: number;
private processed: boolean;
get Id() { return this.id; }
set Id(value: number) { this.id = value };
get Processed() { return this.processed; }
set Processed(value: boolean) { this.processed = value };
constructor(id: number)
{
this.id = id;
this.processed = false;
}
}
When compiled the resulting JavaScript starts with:
define(["require", "exports"], function (require, exports) {
var WebPage = (function ()
{
//Code omitted to keep the SO question short
}});
In main.ts:
(function ()
{
console.log("Executing main.js");
requirejs.config(
{
baseUrl: "scripts", paths: { "model" : "model" }
});
})();
In contentscript.ts:
import model = require("model");
console.log("Processing page");
var page = new model.WebPage(1);
page.Processed = true;
console.log("Done processing page");
When compiled the resulting JavaScript looks like this:
define(["require", "exports", "model"], function (require, exports, model) {
console.log("Processing page");
var page = new model.WebPage(1);
page.Processed = true;
console.log("Done processing page");
});
And finally in require-cs.js:
console.log("Executing requirejs-cs.js");
require.load = function (context, moduleName, url) {
console.log("require.load called");
var xhr = new XMLHttpRequest();
xhr.open("GET", chrome.extension.getURL(url) + '?r=' + (new Date()).getTime(), true);
xhr.onreadystatechange = function (e) {
if (xhr.readyState === 4 && xhr.status === 200) {
console.log("evaluating" + url)
eval(xhr.responseText);
context.completeLoad(moduleName);
}
};
xhr.send(null);
};
Which is what I found in all the other questions related to my issue.
All of this results in the following output when loading a page:
Uncaught Error: Mismatched anonymous define() module: function (require, exports, model) {
console.log("Processing page");
var page = new model.WebPage(1);
page.Processed = true;
console.log("Done processing page");
}
http://requirejs.org/docs/errors.html#mismatch
I've read those docs, I went through a lot of similar questions on SO,
but I haven't found anything that works for me yet.
Most questions deal with JavaScript specifically,
perhaps there is something missing on the TypeScript side of things?
Note: The TypeScript compiler is configured to use AMD.
The docs for the error message state:
Be sure to load all scripts that call define() via the RequireJS API. Do not manually code script tags in HTML to load scripts that have define() calls in them.
As described in this question, it seems that if you use import in a type script, it will be turned into a module when compiled using AMD. So by including "scripts/contentscript.js" as a content script you are trying to load a module script without using the RequireJS API. You can try removing contentscript.js from the content_scripts entry in the manifest and adding the following to main.js:
requirejs(["contentscript"], function() {});

Resources