Message passing: Background script to closing tab - javascript

I'm a javascript novice writing my first Chrome extension and I'm stuck on message passing. My extension includes a background script, a browser action icon, and a content script. The content script (a timer that measures the number of second spent in tab) needs to update the background script and change the icon when the tab is closed, but since the content script cannot access the tab onRemove event, it needs to listen for a message from the background script.
Here's how I've set up the background script listener to listen for closed tabs:
// Listen for closed tabs.
chrome.tabs.onRemoved.addListener(function(tab) {
send_closed_message();
});
function send_closed_message () {
chrome.tabs.getSelected(null, function(tab) {
console.log("sending tab_closed to tab " + tab.id);
chrome.tabs.sendRequest(tab.id, {close: true},
function(response) {
console.log(response.timer_stop); });
});
}
And here's the function in the content script that listens for this message.
function main () {
console.log("main()");
timer = new Timer();
timer.start();
// Listen for window focus
window.addEventListener('focus', function() { timer.start(); } );
// Listen for window blur
window.addEventListener('blur', function() { timer.stop(); } );
// Listen for tab close
chrome.extension.onRequest.addListener(
function(request, sender, sendResponse) {
console.log(sender.tab ?
"from a content script:" + sender.tab.url:
"from the extension");
if (request.close === true) {
sendResponse({timer_stop: "stopped"});
timer.stop();
}
});
}
I do not see a response from the content script in the background script console. Since I'm closing the tab, I can't view the content script console. Where is the message going wrong? Is there a better way to debug this? Is there another way the content script could listen for when its tab closes?

I believe the onRemoved on background happens when the tab is already being closed, so probably there is no enough time for the context to receive your message and replay back to the background script.
Maybe is a good idea for the context to keep sending messagens to background every second with its count value. And when the background receives the onRemoved event it simply uses the last received value as the live time of that page.

Related

Chrome Extension - ContextMenu listener not working after few minutes

I'm trying to create a Chrome Extension which can dynamically create ContextMenu based on storage info. Each created menu has its owned key and will do some actions then send message to Content.js to show info box on page if it is clicked.
The menu will be updated if user select some options in Options page. The Options.js will send out a message to background.js, background.js update the storage then refresh menu.
The menu behavior works fine after extension was loaded. But no longer work after few minutes. If I open a console of service worker then it can stay longer, but it eventually no response when i click menu button. Further more, if i trigger the menu updating from Option page, the button back to live. But still failed after few minutes.
I made some research and found out seems background.js will be terminated very soon after it was loaded. So i doubt that cause this issue. But how com the added menu listener not working after that? The listener should already be added, isn't it?
Background.js
function eventListener(message, sender, sendResponse) {
if (message.event === "updateMenu") {
updateMenu();
sendResponse({ result: "update menu completed" });
}
}
function updateMenu() {
chrome.storage.local.get("currencyMappings", function (result) {
prepareContextMenuBySetting(result.currencyMappings);
});
}
function prepareContextMenuBySetting(mappings) {
chrome.contextMenus.removeAll();
chrome.contextMenus.create(
{ id: "rootMenu", title: "%s", contexts: ["selection"] },
function () {
if (chrome.extension.lastError) {
console.log("Got expected error: " + chrome.extension.lastError.message);
}
});
mappings.forEach(map => {
currencies = map.split("|");
chrome.contextMenus.create(
{ id: `${currencies[0]}to${currencies[1]}`, title: `${currencies[0]} => ${currencies[1]}`, type: "normal", parentId: "rootMenu", contexts: ["selection"] });
})
chrome.contextMenus.onClicked.addListener(
(info, tab) => {
let rate = null;
rateKey = info.menuItemId.split("to");
makeExchange(rateKey, info)
}
);
}
Update:
Log will display when the function is working. But there was no any log in console when the button action is not working. Event both Server Worker and page console.
I figure out the possible reason. According to https://developer.chrome.com/docs/extensions/mv2/background_pages/#listeners
adding listener in listener function may not working.
The root cause is that the backgound.js will be terminated in short time. Only keep the root listener. After backgound.js is terminated, the listener in my menu button not working anymore.
Further more, the background.js actually back to live if the root listener get called.
Ex.
chrome.runtime.onMessage.addListener(eventListener);
i added a console.log() at the top of background.js and monitor the console. After the Service Worker display unavailable, which means the backgroud.js is shut down, then I fire a message to background.js. The console.log() will be triggerd.
The solution of my issue is moving adding listener of menu to root level.
chrome.contextMenus.onClicked.addListener(
(info, tab) => {
console.log("item clicked");
let rateKey = info.menuItemId.split("to");
makeExchange(rateKey, info)
}
);
Then once background.js is wake up, the button listener will be set.

How can I get different badge value for every tab on chrome?

I'm trying to do something like adblock does. Adblock counts number of "ads" and update badge value. For now, I tried to do something with 'background pages', but they are run only one time and badge value is the same for all tabs. I can't use browser action popup.html, because it triggers only after the click.
So I need something which takes current tab, is able to read current DOM of tab and after all update badge value. But also after I click on different tab I need to compute new badge value.
Thanks in advance
The badge text is stored for each tab independently provided you specify tabId parameter, you don't have to update it manually after the user switches tabs if you already have set the value.
So if your extension processes the pages immediately after loading, call chrome.browserAction.setBadgeText once. You can do it e.g. by sending a message from your content script to your background/event page which will invoke setBadgeText with the sender tab's id (this parameter is what makes the text unique to a tab).
content script:
chrome.runtime.sendMessage({badgeText: "123"});
background/event script:
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
if (message.badgeText != null) {
chrome.browserAction.setBadgeText({
tabId: sender.tab.id,
text: message.badgeText,
}, () => chrome.runtime.lastError); // ignore errors due to closed/prerendered tabs
}
});
You can listen to the Chrome tab events in your background/event page. The following code helped me to solve the same problem:
// fires when tab is updated
chrome.tabs.onUpdated.addListener(updateBadge);
// fires when active tab changes
chrome.tabs.onActivated.addListener(updateBadge);
function updateBadge() {
// get active tab on current window
chrome.tabs.query({active: true, currentWindow: true}, function(arrayOfTabs) {
// the return value is an array
var activeTab = arrayOfTabs[0];
if (!activeTab) return;
// compute number for badge for current tab's url
var count = getCount(activeTab.url);
chrome.browserAction.setBadgeText({
text: count.toString()
});
});
}
function getCount(currentUrl) {
// your logic, e.g., return 42
}
You can write an "onActiveChanged" listener and call your updateBadge function and pass the tabId. Hope it helped.
chrome.tabs.onActiveChanged.addListener(function (tabId, changeInfo, tab) {
updateBadge(tabId);
});
function updateBadge(tabId) {
...
}

Chrome content script run in all pages in a given tab

I have a Chrome extension (content script) with a popup window. When the user clicks a "Start" button in the popup window, I'd like a new tab to open to a url (say www.test.com), and for the content script to be injected into that tab. Not just executed once, but injected so that it will work on (www.test.com/*) on that same tab. Not in other tabs - just that one.
Here's what I have now:
chrome.tabs.create({
'url': 'http://test.com/shop/new'
}, function(tab) {
chrome.tabs.executeScript(tab.id, {
'file': 'script.js'
});
});
But, chrome.tabs.executeScript is being used, which only executes the script once. The script redirects the page to 'http://test.com/shop/new/xxx', but since the script is only executed once, it stops working when the page changes. Again - how can I make it so that the script is injected into all 'http://test.com/shop/*' pages in that tab?
A good idea is to make a script that is always injected into http://test.com/shop/* (via manifest):
"content_scripts" : [
{
matches: ["http://test.com/shop/*"],
js: ["script.js"]
}
],
Then, in the script, ask the background page if it should be active for this ID:
// script.js
chrome.runtime.sendMessage({shouldIRun : true}, function(response){
if(response) {
// Actually do stuff
}
});
And in the background script, keep a record of tabs that you want it to apply to:
// Background script
var activeTabs = {}; // Slightly more difficult with event pages
// At some point when you enable it, e.g. in a browserAction.onClicked listener
activeTabs[tabId] = true;
chrome.runtime.onMessage.addListener(function(message, sender, sendResponse) {
if(message.shouldIRun) {
// Double negation to ensure true/false
sendResponse(!!activeTabs[sender.tab.id]);
}
});
// It's a good idea to clear the stray entries
chrome.tabs.onRemoved.addListener(function(tabId, removeInfo) {
delete activeTabs[tabId];
});
// Sometimes this can also happen
chrome.tabs.onReplaced.addListener(function(addedTabId, removedTabId) {
if(!!activeTabs[removedTabId]) activeTabs[addedTabId] = true;
delete activeTabs[removedTabId];
});

Can't shoot a response every time extension button is clicked

So I'm just trying to get a response every time the extension button is clicked. So like with AdBlock how this comes down
But instead I'm just trying to do a console.log() every time the button is clicked without any visible popups.
I've tried this so far
chrome.extension.onMessage.addListener(
function(request, sender, sendResponse) {
switch (request.directive) {
case "popup-click":
// execute the content script
chrome.tabs.executeScript(null, { // defaults to the current tab
file: "real.js", // script to inject into page and run in sandbox
allFrames: true // This injects script into iframes in the page and doesn't work before 4.0.266.0.
});
sendResponse({}); // sending back empty response to sender
break;
default:
// helps debug when request directive doesn't match
alert("Unmatched request of '" + request + "' from script to background.js from " + sender);
}
}
);
Then my real.js
console.log("Yo");
But sadly I only get a Yo when it launches. Any ideas?
If you don't have a popup (nothing shows when you click the button), then there is an event that will fire when the button is clicked:
chrome.browserAction.onClicked
Fired when a browser action icon is clicked. This event will not fire if the browser action has a popup.
To use:
// In your background script
chrome.browserAction.onClicked.addListener( function() {
// Do stuff
});
If, however, you do have a popup, then, as the docs mention, this event will not fire. Then your code is more appropriate: you just need to send a message from the popup and catch it in the background script whenever it is opened. See a full example in this answer.

Messaging between content script and popup script in Chrome extension

I'm trying to create a popup chrome extension that shows information about the DOM in the current page, which seems to require messaging. I've been able to send messages to the background, but I need the data to be specific to the current page, as the background is identical to all popups/pages.
In popup.js, I send a message when the DOM is loaded (should trigger when popup is clicked?)
document.addEventListener('DOMContentLoaded', function() {
chrome.runtime.sendMessage({method: "getTableData"}, function response() {
});
});
I also have a listener in the contentscript.js (and background.js for testing)
chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
if(request.method == "getTableData") {
console.log("table request found!");
}
});
Then, when I activate the popup, the background console outputs table request found!, while
the console for the current page doesn't.
Thanks.
You need to use chrome.tabs.sendMessage instead of chrome.runtime.sendMessage to send a message to a content script.

Categories