Chrome extension fires multiple times on one page - javascript

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.

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.

XSS "Blocked a frame with origin from accessing a cross-origin frame" error in content script for a Chrome extension

I've had this extension in the Google Chrome store for a while now. After doing a maintenance update I noticed that the following line from the content.js (content script):
//Get top document URL (that is the same for all IFRAMEs)
var strTopURL = window.top.document.URL;
is now throwing the following exception when the loaded page has an IFRAME in it:
Blocked a frame with origin "https://www.youtube.com" from accessing a
cross-origin frame.
Like I said, it used to be the way to obtain the top document URL for your extension (from the content script). So what's the accepted way to do it now?
PS. Again, I'm talking about a Google Chrome extension (and not just a regular JS on the page.)
EDIT: This script is running under the content_scripts in the manifest.json that is defined as such:
"content_scripts": [
{
"run_at": "document_end",
"all_frames" : true,
"match_about_blank": true,
"matches": ["http://*/*", "https://*/*"],
"js": ["content.js"]
}
],
The content script should ask your background script to do it via messaging:
chrome.runtime.sendMessage('getTopUrl', url => {
// use the URL here inside the callback or store in a global variable
// to use in another event callback that will be triggered in the future
console.log(url);
});
// can't use it right here - because the callback runs asynchronously
The background script should be declared in manifest.json:
"background": {
"scripts": ["background.js"],
"persistent": false
},
You'll also need need specific URL permissions in manifest.json or allow all URLs:
"permissions": ["<all_urls>"]
And the listener in the background script:
chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
if (msg === 'getTopUrl') {
chrome.tabs.get(sender.tab.id, tab => sendResponse(tab.url));
// keep the message channel open for the asynchronous callback above
return true;
}
});

Irregular message detection

I'm working on a simple extension that, based on certain user actions in browser, sends messages to a popup script which then in turn calls functions in a background script. I'm new to developing Chrome extensions so bear with me.
Currently, I have a setup that detects user actions in-browser with a content script, sends a message to a popup script, and that calls a function in the detected background page (or so I believe, I haven't gotten alerts or logs to display anywhere from the background.js).
My question is: why aren't messages being detected when sent from the background script, and is the function in my background script being called at all?
manifest.json
{
...
"browser_action": {
"default_icon": "gamify.png",
"default_popup": "user_stats.html"
},
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["jquery.min.js", "contentscript.js"],
"run_at": "document_end"
}
],
"background": {
"scripts": ["background.js"],
"persistent": false
},
"permissions": [
"storage"
]
}
contentscript.js
$(document).ready(function() {
$("#page-container").click(function() {
chrome.runtime.sendMessage({
action: "Load"
});
});
});
//Popup script
$(document).ready(function() {
var bg = chrome.extension.getBackgroundPage();
chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
var action = request.action;
if (action == "Load") {
bg.initialize();
}
});
});
background.js
function initialize() {
chrome.runtime.sendMessage({
action: "Start"
});
chrome.storage.sync.get("initialized", function(data) {
alert("BS: Get initialized: " data);
//Do stuff here
});
}
Why are you doing this in a roundabout way?
Popup page only exists as long as it's shown; if the popup is not open, nothing will listen to your message and it will be lost. Since it is very fragile, it's not a good candidate for message routing.
So, step 1: remove the message routing from the popup. Ideologically, the background page is "always there", and handles most of the operations. You can control the display of the popup by, say, listening to chrome.storage.onChanged, or just different messages that make sense only to the popup.
However, you also have declared that the background page has "persistent" : false, i.e. it's an Event page. That means it's not always always there.
This will, by the way, cause chrome.extension.getBackgroundPage to fail from time to time if the page is unloaded.
You have two options:
Remove "persistent": false. Event pages are harder to deal with, so if you're new, you might want to skip it.
Read the Event page documentation carefully. It lists limitations you have to deal with.

History search failing to execute in Chrome extension

I am in the process of writing an extension for Chrome to display the users 3 most visited sites. (Yes, I am aware that the "New Tab" page already does this) However, whenever I try to query the users history then it seems like the entire script shuts down.
My manifest files does contain:
{
"name": "Most Visited Sites Test",
"description": "Show your most visited sites",
"version": "1.0",
"background_page": "background.html",
"app": {
"launch": {
"web_url": "http://localhost/*"
}
},
"permissions": [
"tabs",
"history",
"unlimitedStorage",
"notifications"
],
"icons": {"128": "icon.png" },
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["contentscript.js"]
}
]
}
So I believe this ought to give my background page the ability to use the history. However, my background page contains:
function onRequest(request, sender, sendResponse)
{
alert("Call 1");
var oneWeekAgo = //Code for getting last weeks date;
chrome.history.search({
'text': '',
'startTime': oneWeekAgo
},
function(historyItems)
{
// Do stuff...
});
alert("Call 2");
};
The request is sent from my contentscript.js
chrome.extension.sendRequest("foo");
When run, "Call 1" is shown but then nothing is done with the history and "Call 2" is never shown. What might be causing this? I apologize if this is a simple problem but this is my first attempt at a legitimate Chrome extension.
Opening console to see if there any errors is the first thing I always do (go to the Extensions tab and click on "background.html").
Your history call is correct, so maybe your last week calculation isn't? This is what works for me:
chrome.history.search({text: "", startTime:(new Date()).getTime()-7*24*3600*1000}, function(items) {
console.log(items);
});

Categories