How to Inject a Global with Web Extensions in Manifest V3
For those of you not familiar with the world of web extension development, a storm is brewing with Chrome. Google will stop support for manifest version 2, which is what the vast majority of web extensions use. Manifest version 3 sees many changes but the largest change is moving from persistent background scripts to service workers. This...is...a...massive...change.
Changes from manifest version 2 to version 3 include:
- Going from persistent background script to a service worker that can die after 5 minutes
- No use of
<iframe>
elements or other DOM APIs from the service worker - All APIs have become Promise-based
- Restrictions on content from a CSP perspective
One function that web extensions often employ is executing scripts upon each new page load. For a web extension like MetaMask, we need to provide a global window.ethereum
for dApps to use. So how do we do that with manifest version 3?
As of Chrome v102, developers can define a world
property with a value of isolated
or main
(in the page) for content scripts. While developers should define content_scripts
in the extension's manifest.json
file, the main
value really only works (due to a Chrome bug) when you programmatically define it from the service worker:
await chrome.scripting.registerContentScripts([ { id: 'inpage', matches: ['http://*/*', 'https://*/*'], js: ['in-page.js'], runAt: 'document_start', world: 'MAIN', }, ]);
In the example above, in-page.js
is injected and executed within the main content tab every time a new page is loaded. This in-page.js
file sets window.ethereum
for all dApps to use. If the world
is undefined
or isolated
, the script would still execute but would do so in an isolated environment.
Manifest version 3 work is quite the slog so please hug your closest extension developer. There are many huge structural changes and navigating those changes is a brutal push!