Handling Extension Updates
When releasing an update to your extension, there's a couple of things you need to keep in mind:
Content Script Cleanup
Old content scripts are not automatically stopped when an extension updates and reloads. Often, this leads to "Invalidated context" errors in production when a content script from an old version of your extension tries to use a web extension API (ie, the browser
or chrome
globals).
WXT provides a utility for handling this process: ContentScriptContext
. An instance of this class is provided to you automatically inside the main
function of each content script.
When your extension updates or reloads, the context will become invalidated, and will trigger any ctx.onInvalidated
listeners you add:
export default defineContentScript({
main(ctx) {
ctx.onInvalidated(() => {
// Do something
});
},
)
The ctx
also provides other convenient APIs for stopping your content script without manually calling onInvalidated
to add a listener:
- Setting timers:ts
ctx.setTimeout(() => { ... }, ...); ctx.setInterval(() => { ... }, ...); ctx.requestAnimationFrame(() => { ... });
- Adding DOM events:ts
ctx.addEventListener(window, "mousemove", (event) => { ... });
- Implements
AbortController
for canceling standard APIs:tsfetch('...', { signal: ctx.signal, });
Other WXT APIs require a ctx
object so they can clean themselves up. For example, createIntegratedUi
, createShadowRootUi
, and createIframeUi
automatically unmount and stop a UI when the script is invalidated.
WARNING
When working with content scripts, you should always use the ctx
object to stop any async or future work.
This prevents old content scripts from interfering with new content scripts, and prevents error messages from being logged to the console in production.
Testing Permission Changes
When permissions
/host_permissions
change during an update, depending on what exactly changed, Chrome will disable your extension until the user accepts the new permissions.
It is possible to test this before you release an update, but it's not a simple process:
- Get 2 ZIPs of your extension, both generated by
wxt zip
. The first contains a previous version of your extension, the second contains the latest version. Make sure the second ZIP's version is higher than the first's. - Unzip the two ZIP files somewhere next to each other that's easy to locate.
- In Chrome, open
chrome://extensions
and make sure developer mode is enabled - Pack the first extension into a CRX, generating a new private key:
- Click "Pack Extension" in the top left
- For "Extension root directory", enter the path to the first extracted zip directory. The directory should contain a
manifest.json
file - Leave "Private key file" blank
- Click "Pack Extension". This will generate a
.crx
and.pem
file
- Pack the second extension into a CRX, reusing the private key generated by the previous step
- Click "Pack Extension" in the top left
- For "Extension root directory", enter the path to the second extracted zip directory.
- For "Private key file", enter the path to the generated
.pem
private key file - Click "Pack Extension". This will generate a second
.crx
file.
- Install the first CRX file by dragging and dropping it onto the
chrome://extensions
page - Install the second CRX file by dragging and dropping it onthe the
chrome://extensions
page
If a new permission must be accepted, you'll be prompted to accept it after dropping the second CRX file onto the page.
:::Info Note Note: Chrome no longer allows self-signed CRX extensions to run, but that's OK for this test case. We're still prompted to accept new permissions, even if we can't interact with the installed extension.
To validate this, you can create a third ZIP file with a rare permission like geolocation
in the manifest, that's guarenteed to reprompt permissions when added. :::
Update Event
You can setup a callback that runs after your extension updates like so:
browser.runtime.onInstalled.addListener(({ reason }) => {
if (reason === 'update') {
// Do something
}
});
If the logic is simple, write a unit test to cover this logic. If you feel the need to manually test this callback, you can either:
- In dev mode, remove the
if
statement and reload the extension fromchrome://extensions
- Build two ZIPs with the same runtime ID and actually update the extension
The first approach is very straightforward. The second is more complicated...
Here are the steps:
So the steps:
- Checkout an old commit.
- Add a
key
to themanifest
in yourwxt.config.ts
. - Run
wxt zip
to create the first ZIP. - Stash or reset changes and checkout your latest code.
- Add back the same
key
to your manifest. - Make sure the extension's version is higher than the first zip. It can be any version that's higher, since you won't be releasing this version.
- Run
wxt zip
to create the second ZIP. - In a fresh chrome profile, go to
chrome://extensions
, enable dev mode, and drag and drop the first zip onto the page to install it. - In the extension, play around and setup your test case.
- Back on
chrome://extensions
, drag and drop your second zip onto the page.
If you setup the key
correctly, it will cause the extension to act like it was updated instead of installing a second version of your extension.