Before I comment on the approaches you've listed, I want to ask why are you manually injecting content scripts?
The
static content script declarations included in manifest.json will always be injected into a website. If you need broad control over whether or not the scripts are injected, such as enabling or disabling integration with a specific site,
dynamic content script registrations allow you to add or remove registrations at runtime. These two are the most reliable ways to inject your scripts when a navigation occurs, thereby completely avoiding the need to manually detect and react to navigations.
With a single page app (SPA) like Google Docs, you may well also want to detect psudo-navigations – "navigations" inside a page that don't cause a frame to load. The web's Navigation API (
developer.chrome.com,
MDN), which shipped in Chrome 102, can be used to observe NavigationEvents in a page.
Okay, with that out of the way, I'm going to assume for the moment that for whatever reason you HAVE TO react at runtime from the background context and share some thoughts on the ideas you outlined.
Some other options I've considered:
- use `webNavigation.onCommitted` and tabs.onUpdated in tandem to ensure that a reload has in-fact occurred. But adding a new permission for this seems wrng.
WebNavigation is the more appropriate API for detecting navigation in a tab. Of the approach you've listed, I think this is best.
- Create a port with every content script being injected. When the page reloads, the port will be disconnected and we'll know that we should re-inject.
This approach could work, but you'll need to be cognizant of the extension service worker's lifetime. Chrome made some improvements to SW liftimes behavior near the beginning of the year; every event an extension receives will now extend the SW's lifetime, potentially indefinitely. That said, I'd still recommend keeping an eye out for unexpected behavior that could be attributed to the service worker terminating.
- Use `beforeunload` in the injected content script and post-back to the extension when the page is about to be discarded.
This approach has an inherent race condition. I think it should work but is not guaranteed to. I wouldn't go this route unless it was my only option, and I think you have much better options.
- Message the content script and check if its out there or just try to inject it anyways.
At first I was worried about the latency incurred by waiting for the check to time out. I had forgotten that the chrome.tabs.sendMesasge() call will asynchronously throw "Uncaught Error: Could not establish connection. Receiving end does not exist." if a content script hasn't registered a listener. If you get that error, you know your listenter is missing and can inject a script to register a listener and react to messages.
I actually rather like this approach. The downside to all of these, though, is that your content scripts will only be injected after the page has been initialized and has begun executing script. If you don't need to execute logic or shim page APIs before the page starts running script, that's not a deal breaker. Still, if you're adding UI to the page on load, that latency may make your UX feel a bit weird to the user, so it may be relevant.
Simeon - @dotproto