My goal is to inject frontend code in a tab if its url matches my url. For that I did:
background.js:
chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
if (changeInfo?.url?.includes("https://www.google.com/search?")) {
chrome.scripting.executeScript(
{
target: { tabId: tabId },
files: ['injection.js'],
}
);
}
})
It works but many people also do through content.js:
background.js:
chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
if (changeInfo?.url?.includes("https://www.google.com/search?")) {
await chrome.tabs.sendMessage(tabId, { isActive: true });
}
})
now we can add a listener in content.js.
My question is what are the difference? Which approach is better?
The difference is that injection is a more expensive operation.
However, both approaches are bad for this task because they require waking up and initializing a service worker each time there's any navigation in any tab for any site, not just the one you want, if that occurs in more than 30 seconds after the last one (this is the idle timeout of the service worker to be terminated).
A better approach is chrome.webNavigation.onHistoryStateUpdated and onReferenceFragmentUpdated events with a url filter:
chrome.webNavigation.onHistoryStateUpdated.addListener(info => { /* your listener */ }, {
url: [{pathPrefix: "https://www.google.com/search?"}],
});
An even better approach, which is 1000 times less expensive, is doing everything inside a content script via MutationObserver or navigation.onnavigate (Chrome-only at the time, soon to be implemented in Firefox):
let lastUrl = location.href;
new MutationObserver(() => {
const url = location.href;
if (url !== lastUrl) onUrlChange(lastUrl = url);
// alternatively, you can detect the changes in the actual DOM instead of the URL
}).observe(document, {subtree: true, childList: true});
function onUrlChange() {
console.log('URL changed!', location.href);
}
P.S. You need to re-inject files from content_scripts after reloading/installing the extension in Chrome.