Pass parameter in javascript function inside chrome.tabs.executeScript - javascript

I'm bulilding a chrome extension.
In this part of code I try to find paragraphs in loaded page, for each paragraph in divs with id load and detail, I search if value of findCheckedFact is inside that paragraph if yes I cut I want to put that text inside a span with a class red.
findCheckedFact is a string. Here in +'console.log("Print:", str_p, " Fact:", findCheckedFact);' the text inside paragraph is defined but the parameter findCheckedFact is not defined, so I cant pass it?
This function I tried calling findTextInsideParagraphs("test");.
function findTextInsideParagraphs(findCheckedFact){
chrome.tabs.executeScript({file: "js/jquery-1.12.0.min.js"}, function() {
chrome.tabs.executeScript({"code":
'(function(findCheckedFact){'
+'$("#lead, #detail").find("p").each(function() {'
+'var str_p = $(this).text();'
+'console.log("Print:", str_p, " Fact:", findCheckedFact);'
+'if (str_p.indexOf( findCheckedFact) >= 0) {'
+'console.log("Yes kest");'
+'$(this).html($(this).html().replace(findCheckedFact, "<span class=\'red\'> $& </span>"));'
+'}'
+'});'
+'}(' + JSON.stringify("Fact: " , findCheckedFact) + '));'
});
});
}
This function I tried calling findTextInsideParagraphs("test");
Inside manifest.json I did add everythin possible to make it work:
"content_scripts": [
{
"matches": [
"<all_urls>",
"http://*/*",
"https://*/*"
],
"css": [
"custom-css.css"
],
"js": [
"js/jquery-1.12.0.min.js"
],
"run_at": "document_start"
}],
"background": {
"persistent": false,
"scripts": [
"js/jquery-1.12.0.min.js",
"background.js",
"popup.js",
"blic-fact-checker.js"
],
"css":["custom-css.css"]
},
"permissions": [
"background",
"notifications",
"contextMenus",
"storage",
"tabs",
"activeTab",
"http://localhost:5000/*",
"chrome-extension://genmdmadineagnhncmefadakpchajbkj/blic-fact-checker.js",
"chrome-devtools://devtools/bundled/inspector.html?&remoteBase=https://chrome-devtools-frontend.appspot.com/serve_file/#202161/&dockSide=undocked",
"http://*/*",
"https://*/*",
"<all_urls>"
],
Can somebody help me with this,really I can't find what's going on ?

First, read this explanation of different kinds of javascript code in Chrome extensions
As you see, your code in executeScript is a content script, and it has no direct access to the code in background and other proper extension pages. You have 3 main options:
1) send your FindCheckedFact to the content script with sendMessage or port.postMessage. Of course, in your content script you have to establish listeners for them, and put the actual code in these listeners.
2) save your variable in local or sync storage, putting your executeScript in the callback, and read them from your content script. If you need to re-read it sometimes, set a listener for storage change in the content script.
These solutions work both with a content script in a .js file, and an inline content script. But, since you choose inlining, and you have only to send a value one way, you can
3) just stringify your FindCheckedFact (with JSON.stringify or toString), and insert its value with +:
chrome.tabs.executeScript({"code":
'(function('+strFindCheckedFact+'){'
etc.

Related

Is message passing between multiple content scripts possible without using the background script?

When migrating a Chrome Extension to Manifest v3 we are getting rid of the background script and are instead using service workers.
The problem is that we previously sent messages from multiple content scripts to another content script through the background script, and this is no longer possible because in Manifest v3 the background script will become inactive after a while.
Is it possible to send messages between multiple content scripts without using the background script?
This is an example of how the content scripts are setup, sender.js is available in multiple iframes while receiver.js only is present in the top document.
"content_scripts": [
{
"js": ["receiver.js"],
"all_frames": false,
"matches": ["<all_urls>"]
},
{
"js": ["sender.js"],
"all_frames": true,
"matches": ["<all_urls>"],
"run_at": "document_start"
}
]
You can send messages from contents scripts to other content scripts with chrome.storage.local, no service worker required.
Proof of concept:
The counter has two purposes:
Allows content scripts to distinguish their own messages from other content scripts' messages.
Guarantees that chrome.storage.onChanged always fires, even if the data hasn't changed.
You need to execute the entire code inside the async function every time you send a message.
You could also use random numbers instead of a counter.
Advantage: You don't have to read the old counter value from storage.
Disadvantage: You need to make sure there are no collisions.
I don't know if the "event doesn't wake up the service worker" bug can occur in content scripts.
Instead of chrome.storage.local, you could use chrome.storage.session, but you'd need to set the access level: Storage areas > storage.session
manifest.json
{
"manifest_version": 3,
"name": "Content Script Messaging with Storage",
"version": "1.0",
"action": {
},
"content_scripts": [
{
"matches": ["*://*/*"],
"js": ["content_script.js"]
}
],
"permissions": [
"storage"
]
}
content_script.js
let counter = -1;
function storage_on_changed(changes, areaName) {
if (changes?.message?.newValue?.counter == counter) {
console.log("The message came from this content script", changes);
}
else {
console.log("The message came from another content script", changes);
}
}
chrome.storage.onChanged.addListener(storage_on_changed);
(async () => {
let { message } = await chrome.storage.local.get("message");
if (message === undefined) {
counter = 0;
}
else {
counter = message.counter + 1;
}
await chrome.storage.local.set({ message: {counter, data: document.URL} });
})();

Chrome Extension postMessage from background script to content script every time in change crome tabs

Good day.
I have a problem sending messages from background script to content script.
I try to send messages every time a user switches between browser tabs.
I get this error.
Unchecked runtime.lastError: Could not establish connection. Receiving end does not exist.
Below is the code for the extension itself.
Background Script:
chrome.tabs.onActivated.addListener(function changeTab(activeInfo) {
var port = chrome.tabs.connect(activeInfo.tabId);
console.log(port);
port.postMessage({tabId: activeInfo.tabId});
});
Content Script:
chrome.runtime.onConnect.addListener(function(port) {
console.log(11111111);
port.onMessage.addListener(function(msg) {
console.log(22222222);
if(msg == undefined || Object.keys(msg).length == 0) return;
if(msg.checkExtensionRBPopup)
port.postMessage({active: window.localStorage.getItem('extension_rb_popup')});
});
});
Manifest:
{
"manifest_version": 2,
"name": "Rebatemango Extension",
"description": "Rebatemango",
"version": "1.0",
"browser_action": {
"default_popup": "popup.html"
},
"content_scripts": [
{
"matches": [
"<all_urls>"
],
"js": [
"content.js",
]
}
],
"permissions": [
"background",
"tabs",
"activeTab",
"declarativeContent",
"storage",
"clipboardWrite",
"cookies",
"tabCapture",
"displaySource",
"webNavigation"
],
"background": {
"scripts": [
"js/jquery.js",
"js/libs.js",
"background.js"
],
"persistent": false
},
"icons": {
"16": "images/mango.png",
"48": "images/mango.png",
"128": "images/mango.png"
}
}
Please tell me what am I doing wrong?
How to fix or track this error?
Your content script is using port.postMessage but there is no listener on the other side of the port in the background script, which is why the error is shown.
Solution:
Use simple messaging. Don't use ports, it's not needed here and your implementation is causing a memory leak since it creates a new port each time without releasing it. The simple messaging will handle this automatically.
chrome.tabs.sendMessage(activeInfo.tabId, {foo: 1}, response => {
if (chrome.runtime.lastError) return;
console.log(response);
});
The content script listener:
chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
console.log(msg);
sendResponse({bar: 2});
});
Other causes.
The error message may also appear if there is no content script running at the exact moment the message was sent. It may happen because by default content scripts run after DOMContentLoaded event in the web page. Or maybe the tab contained an unsupported URL like chrome:// or chrome-extension:// from an extension page or https://chrome.google.com/webstore/ or any URL forbidden by the runtime_blocked_hosts policy. For the first problem you can add "run_at": "document_start" to your content script declaration in manifest.json. For the other cases there's nothing you can do except for suppressing and ignoring the error message.
Also the background script sends {tabId: activeInfo.tabId} but onMessage listener in the content script doesn't read it. Instead it reads msg.checkExtensionRBPopup which doesn't make any sense in case it's not a copypaste artifact of posting the question.

Can I use an external array to match URLs for content script injection?

I'm working on a chrome extension where I'd like to inject a content script into a list of urls. Usually I'd use the regular syntax:
{
"name": "My extension",
...
"content_scripts": [
{
"matches": ["http://www.google.com/*"],
"css": ["mystyles.css"],
"js": ["jquery.js", "myscript.js"]
}
],
...
}
But for the match patterns I'd like to pull the array from a server. Is there a way to programmatically set the "matches" array (from the background.js file for example)?
As far as I know, you cannot modify your manifest.json file from within the extension. What you can do is programmatically inject your content scripts from the background page when the tab's URL matches one of the URLs you've got from the server.
Note that you will need tabs and <all_urls> permissions.
background.js
var list_of_URLs; //you populate this array using AJAX, for instance.
populate_list_of_URLs();
chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, tab){
if (list_of_URLs.indexOf(tab.url) != -1){
chrome.tabs.executeScript(tabId,{file:"jquery.js"},function(){
chrome.tabs.executeScript(tabId,{file:"myscript.js"});
});
}
});

Chrome extension fires multiple times on one page

I'm trying to wrap my head around how these extensions work. Right now I'm sending a message from content script to the background. I tried both single messages and a long-lived connection. For each page, the console registers multiple logs...and I have no idea why. The scripts are simple:
content.js
chrome.runtime.sendMessage({hi: "hi"}, function(response) {});
//or
//var port = chrome.runtime.connect({name: "test"});
//port.postMessage({hi: "hi"});
background.js
chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
console.log(sender.tab);
console.log(request.hi);
});
//OR
/*chrome.runtime.onConnect.addListener(function(port) {
port.onMessage.addListener(function(msg) {
console.log(msg);
});
});*/
manifest.json
"background": {
"scripts": ["background.js"],
"persistent": false
},
"content_scripts": [{
"matches": ["http://*/*", "https://*/*"],
"js": ["content.js"],
"run_at": "document_end",
"all_frames": true
}],
"permissions": [
"tabs",
"<all_urls>"
]
In each case, like I said, I get three logs in the background page. Why? I thought it might be due to the number of tabs I have open but the logs show the sender information and it says it comes from the same tab.
MORE: When I refresh this question, I only get one log entry (one entry per block, as in the first comment). When I refresh google results or the Google extensions API page, I get multiple logs.

Can I share code between different parts of Chrome Extension?

Let's say, I have a function:
var rand = function(n) {
return Math.floor(Math.random() * n);
}
Can I use this function in both Content Script and Background Script without copypaste?
Thank you.
Yes.
You could have an external JS file which is loaded as part of the background and the content script (like any normal JS file). Just add it to the background and content script file arrays in the manifest and it will be loaded for you.
For example if our shared function reside in sharedFunctions.js, the content script using them is in mainContentScript.js and the background code in mainBackground.js (all in the js subfolder) we can do something like this in the manifest:
"background": {
"scripts": [ "js/sharedFunctions.js", "js/mainBackground.js" ]
},
"content_scripts": [
{
"matches": ["*://*/*"],
"js": ["js/sharedFunctions.js", "js/mainContentScript.js"]
}
]
Note: Make sure to load it in the right order, before other files using it.
Or you can also add it as a script tag (with a relative URL) in the background.html and in the manifest only add it to the content script's JS array. So the manifest will look like:
"background": {
"page": "background.html"
},
"content_scripts": [
{
"matches": ["*://*/*"],
"js": ["js/sharedFunctions.js", "js/mainContentScript.js"]
}
]
and the background.html file will have this script tag:
<script type="text/javascript" src="js/sharedFunctions.js"></script>
Edit: Also, For sharing with other scopes in other parts of the extension (unfortunately not available in the content script).
You can also have the functions you want to share reside in the background script and reach them via chrome.runtime.getBackgroundPage() which you can read more about here:
https://developer.chrome.com/extensions/runtime#method-getBackgroundPage
So, let's say you have your rand() function declared as a global variable in the background script (which means it's a property on the background's window object), you can do something of this sort at the beginning the script in the other scope (this can be a popup context like a browserAction window):
var background, newRandomNumber;
chrome.runtime.getBackgroundPage(function(backgroundWindow){
background = backgroundWindow;
newRandomNumber = background.rand();
})
This way you can also use the variable background to access any property or method set on the background's window object. Be mindful, that this function runs asynchrounusly, meaning that only after the callback is called will the variables background and newRandomNumber will be defined.
Yes, you can, by putting it in a separate JS file and loading it in both.
Say, you have a file utils.js that contain all such functions.
Then you can load the background page like this:
"background": {
"scripts": [ "utils.js", "background.js" ]
},
And the content script like this:
"content_scripts": [
{
"matches": ["..."],
"js": ["utils.js", "content.js"]
}
],
Or, if you're using programmatic injection, chain the calls like this:
chrome.tabs.executeScript(tabId, {file: "utils.js"}, function(){
chrome.tabs.executeScript(tabId, {file: "content.js"}, yourActualCallback);
});

Categories