If you're only interested in distinguishing new tabs from tabs that are old and/or non-injectable, you can let the content scripts add the IDs of their tabs to session storage. Later on, you can look up the ID of any tab in session storage.
Tab IDs are only valid during the current session. When you store tab IDs in sessions storage, they are gone when you start a new session, which is what you want.
Content scripts don't know the ID of the tab they're running in, so they can't store it by calling chrome.storage.session.set() directly. However, content scripts can send a message to the service worker. The service worker receives information about the sender's tab along with the message.
The proof of concept below doesn't try to determine if a tab is injectable or not.
- You can either do this by checking the tab's URL, e.g. if it starts with "chrome://" or "chrome-extension://". But I don't know if you can determine all non-injectable tabs like this, e.g. those whose URL is forbidden by the runtime_blocked_hosts policy.
- Or you can inject an empty content script into the tab and check for errors. This requires the "scripting" permission, plus "activeTab" or the right host permissions, but lets you determine all non-injectable tabs.
When you click the action, the extension shows a notification. It tells you if the active tab is old, or if it's new and/or non-injectable.
manifest.json
{
"manifest_version": 3,
"name": "Tabs with vs without Content Scripts",
"version": "1.0",
"action": {
},
"background": {
"service_worker": "background.js"
},
"content_scripts": [
{
"matches": ["*://*/*"],
"js": ["content_script.js"]
}
],
"permissions": [
"notifications",
"storage"
]
}
background.js
async function action_on_clicked(tab) {
let { [tab.id.toString()]: tab_id_stored } = await chrome.storage.session.get(tab.id.toString());
let message;
if (tab_id_stored === undefined) {
/* Old or non-injectable tab */
message = "Old or non-injectable tab";
}
else {
message = "New tab";
}
chrome.notifications.create(
{
iconUrl: "/icon_128.png",
message,
title: "Tabs with vs without Content Scripts",
type: "basic",
},
notificationId => {
if (chrome.runtime.lastError === undefined) {
console.log(notificationId);
}
else {
console.error(chrome.runtime.lastError);
}
}
);
}
function runtime_on_message(message, sender, sendResponse) {
if (message == "store_tab_id") {
if (sender.tab) {
chrome.storage.session.set({ [sender.tab.id.toString()]: true })
.then(() => {
sendResponse("tab id stored");
})
.catch(error => {
sendResponse(error);
});
return true;
}
else {
sendResponse("sender.tab is undefined");
}
}
else {
sendResponse("unknown message");
}
}
chrome.action.onClicked.addListener(action_on_clicked);
chrome.runtime.onMessage.addListener(runtime_on_message);
content_script.js
(async () => {
let response = await chrome.runtime.sendMessage("store_tab_id");
console.log("response", response);
})();