9.1 KiB
Content Scripts
When creating content script entrypoints, they are automatically included in the manifest.json along with any CSS files they import.
Filenames
Definition
export default defineContentScript({
// Set manifest options
matches: ['*://google.com/*', '*://duckduckgo.com/*'],
excludeMatches: undefined | [],
includeGlobs: undefined | [],
excludeGlobs: undefined | [],
allFrames: undefined | [],
runAt: undefined | 'document_start' | 'document_end' | 'document_idle',
matchAboutBlank: undefined | true | false,
matchOriginAsFallback: undefined | true | false,
world: undefined | 'ISOLATED' | 'MAIN',
// Set include/exclude if the background should be removed from some builds
include: undefined | string[],
exclude: undefined | string[],
// Configure how CSS is injected onto the page
cssInjectionMode: undefined | "manifest" | "manual" | "ui",
main(ctx) {
// Executed when content script is loaded
},
});
All manifest options default to
undefined.
When defining multiple content scripts, content script entrypoints that have the same set of options will be merged into a single content_script item in the manifest.
Context
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 extension API.
WXT provides a utility for managing this process: ContentScriptContext. An instance of this class is provided to you automatically inside the main function of your content script.
export default defineContentScript({
// ...
main(ctx: ContentScriptContext) {
// Add custom listeners for stopping work
ctx.onInvalidated(() => {
// ...
});
// Stop fetch requests
fetch('...url', { signal: ctx.signal });
// Timeout utilities
ctx.setTimeout(() => {
// ...
}, 5e3);
ctx.setInterval(() => {
// ...
}, 60e3);
},
});
The class extends AbortController and provides other utilities for stopping a content script's logic once it becomes invalidated.
:::tip
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 the console in production. :::
CSS
To include CSS with your content script, import the CSS file at the top of your entrypoint.
<srcDir>
└─ entrypoints/
└─ overlay.content/
├─ index.ts
└─ style.css
// entrypoints/overlay.content/index.ts
import './style.css';
export default defineContentScript({
matches: ['*://google.com/*', '*://duckduckgo.com/*'],
main(ctx) {
// ...
},
});
Any styles imported in your content script will be added to that content script's css array in your manifest.json:
// .output/chrome-mv3/manifest.json
{
"content_scripts": [
{
"matches": ["*://google.com/*", "*://duckduckgo.com/*"],
"js": ["content-scripts/overlay.js"],
"css": ["content-scripts/overlay.css"]
}
]
}
To disable this behavior, set cssInjectionMode to "manual" or "ui".
export default defineContentScript({
matches: ['*://google.com/*', '*://duckduckgo.com/*'],
cssInjectionMode: 'manual',
main(ctx) {
// ...
},
});
UI
WXT provides a utility function, createContentScriptUi to simplify mounting a UI from a content script. Internally, it uses the ShadowRoot API to isolate your CSS from the webpages.
createContentScriptUi requires a ContentScriptContext so that when the context is invalidated, the UI is automatically removed from the webpage.
:::details When to use createContentScriptUi
You should only use createContentScriptUi if you want your UI's styles isolated from the webpages. If you want to create a more "integrated" UI that uses the page's styles, you can just use the regular JS API's to append your UI to the page.
const ui = document.createElement('div');
const anchor = document.querySelector('#anchor-selector');
anchor.append(ui);
You can try out createContentScriptIframe as an alternative solution
:::
Usage
To use createContentScriptUi, follow these steps:
- Import your CSS file at the top of your content script
- Set
cssInjectionMode: "ui"insidedefineContentScript - Call
createContentScriptUi - Call
mountto add the UI to the webpage
Here's a basic example:
// entrypoints/ui.content/index.ts
import './style.css';
export default defineContentScript({
// ...
cssInjectionMode: 'ui',
async main(ctx) {
const ui = await createContentScriptUi(ctx, {
name: 'example-ui',
type: 'inline',
anchor: '#some-element',
append: 'after',
mount(container) {
// Mount UI inside `container`...
},
});
// You must call `mount` to add the UI to the page.
ui.mount();
},
});
If you're using a frontend framework, you'll also need to include an onRemoved callback:
:::code-group
import { createApp } from 'vue';
createContentScriptUi(ctx, {
// ...
mount(container) {
// Create a new app and mount it inside the container
const app = createApp(...);
app.mount(container);
return app;
},
onRemove(app) {
// When the UI is removed from the DOM, call unmount to stop the app
app.unmount();
},
});
import ReactDOM from 'react-dom/client';
createContentScriptUi(ctx, {
// ...
mount(container) {
// Create a root using the container and render your app
const root = ReactDOM.createRoot(container);
root.render(...);
return root;
},
onRemove(root) {
// When the UI is removed from the DOM, call unmount to stop the app
root.unmount();
},
});
import App from './App.svelte';
createContentScriptUi(ctx, {
// ...
mount(container) {
// Mount your app component inside the container
return new App({
target: container,
});
},
onRemove(app) {
// When the UI is removed from the DOM, call $destroy to stop the app
app.$destroy();
},
});
import { render } from 'solid-js/web';
createContentScriptUi(ctx, {
// ...
mount(container) {
// Render your app component into the container
return render(() => ..., container)
},
onRemove(unmount) {
// When the UI is removed from the DOM, call unmount to stop the app
unmount();
},
});
:::
anchor
The anchor dictates where the UI will be mounted.
append
Customize where the UI get's appended to the DOM, relative to the anchor element.
type
There are 3 types of UI's you can mount.
inline: Shows up inline based on theanchorandappendoptionsoverlay: Shows up inline, but styled to be 0px by 0px, with overflow visible. This causes the UI to overlay on top of the webpage's contentmodal: A fullscreen overlay that covers the entire screen, regardless of where it's anchored.
TODO: Add visualization of the different UI types.
Overlay alignment
Because the overlay UI type results in a 0px by 0px container being added to the webpage, the alignment option allows you to configure which corner of your UI is aligned with the 0x0 element.
TODO: Add visualization of the different alignments.
IFrame
WXT provides a utility function, createContentScriptIframe to simplify mounting a UI from a content script. It creates an iframe to an unlisted HTML page. Unlike createContentScriptUi, this API support HMR.
createContentScriptIframe requires a ContentScriptContext so that when the context is invalidated, the UI is automatically removed from the webpage.
Usage
To use createContentScriptIframe, follow these steps:
- Create an unlisted HTML page that will be loaded into your iframe
- Add unlisted page to the manifest's
web_accessible_resouces - Call
createContentScriptUi - Call
mountto add the UI to the webpage
Here's a basic example:
export default defineContentScript({
// ...
async main(ctx) {
const ui = await createContentScriptIframe(ctx, {
page: '/your-unlisted-page.html',
type: 'inline',
anchor: '#some-element',
append: 'after',
});
// You must call `mount` to add the UI to the page.
ui.mount();
},
});
The options, other than page, are the same as createContentScriptUi.
page
The HTML page you want to load inside the iframe. This string will be passed into browser.runtime.getURL to resolve the full path of to your HTML page.