Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions docs/guide/essentials/content-scripts.md
Original file line number Diff line number Diff line change
Expand Up @@ -591,6 +591,29 @@ For MV3, `injectScript` is synchronous and the injected script will be evaluated
However for MV2, `injectScript` has to `fetch` the script's text content and create an inline `<script>` block. This means for MV2, your script is injected asynchronously and it will not be evaluated at the same time as your content script's `run_at`.
:::

The `script` element can be modified just before it is added to the DOM by using the `modifyScript` option. This can be used to e.g. modify `script.async`/`script.defer`, add event listeners to the element, or pass data to the script via `script.dataset`. An example:

```ts
// entrypoints/example.content.ts
export default defineContentScript({
matches: ['*://*/*'],
async main() {
await injectScript('/example-main-world.js', {
modifyScript(script) {
script.dataset['greeting'] = 'Hello there';
},
});
},
});
```

```ts
// entrypoints/example-main-world.ts
export default defineUnlistedScript(() => {
console.log(document.currentScript?.dataset['greeting']);
});
```

## Mounting UI to dynamic element

In many cases, you may need to mount a UI to a DOM element that does not exist at the time the web page is initially loaded. To handle this, use the `autoMount` API to automatically mount the UI when the target element appears dynamically and unmount it when the element disappears. In WXT, the `anchor` option is used to target the element, enabling automatic mounting and unmounting based on its appearance and removal.
Expand Down
40 changes: 38 additions & 2 deletions packages/wxt/src/utils/inject-script.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,39 @@ export async function injectScript(
script.src = url;
}

const loadedPromise = makeLoadedPromise(script);

await options?.modifyScript?.(script);

(document.head ?? document.documentElement).append(script);

if (!options?.keepInDom) {
script.onload = () => script.remove();
script.remove();
}

(document.head ?? document.documentElement).append(script);
await loadedPromise;
}

function makeLoadedPromise(script: HTMLScriptElement): Promise<void> {
return new Promise((resolve, reject) => {
const onload = () => {
resolve();
cleanup();
};

const onerror = () => {
reject(new Error(`Failed to load script: ${script.src}`));
cleanup();
};

const cleanup = () => {
script.removeEventListener('load', onload);
script.removeEventListener('error', onerror);
};

script.addEventListener('load', onload);
script.addEventListener('error', onerror);
});
}

export interface InjectScriptOptions {
Expand All @@ -45,4 +73,12 @@ export interface InjectScriptOptions {
* injected. To disable this behavior, set this flag to true.
*/
keepInDom?: boolean;
/**
* Modify the script element just before it is added to the DOM.
*
* It can be used to e.g. modify `script.async`/`script.defer`, add event
* listeners to the element, or pass data to the script via `script.dataset`
* (which can be accessed by the script via `document.currentScript`).
*/
modifyScript?: (script: HTMLScriptElement) => Promise<void> | void;
}