diff --git a/docs/api.md b/docs/api.md index ce969b39a..e6e95bd5d 100644 --- a/docs/api.md +++ b/docs/api.md @@ -1,5 +1,5 @@ # API Reference :::warning 🚧 Under construction -This documentation does not exist yet. +This documentation does not exist yet. All APIs are documented with JSDoc, so for now, you can view the documentation in your editor. ::: diff --git a/docs/guide/auto-imports.md b/docs/guide/auto-imports.md index d472e7393..0cb8c74e3 100644 --- a/docs/guide/auto-imports.md +++ b/docs/guide/auto-imports.md @@ -13,6 +13,7 @@ Some WXT APIs can be used without importing them: - [`browser`](/config.md#browser) from `wxt/browser`, a small wrapper around `webextension-polyfill` - [`defineContentScript`](/config.md#defiencontentscript) from `wxt/client` - [`defineBackground`](/config.md#definebackgroundscript) from `wxt/client` +- [`createContentScriptUi`](/config.md#createcontentscriptui) from `wxt/client` And more. All [`wxt/client`](/config.md#wxtclient) APIs can be used without imports. diff --git a/docs/guide/content-scripts.md b/docs/guide/content-scripts.md index b14802bb0..b4c0997bb 100644 --- a/docs/guide/content-scripts.md +++ b/docs/guide/content-scripts.md @@ -47,6 +47,43 @@ export default defineContentScript({ 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. + +```ts +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`](https://developer.mozilla.org/en-US/docs/Web/API/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. @@ -66,6 +103,7 @@ import './style.css'; export default defineContentScript({ matches: ['*://google.com/*', '*://duckduckgo.com/*'], + main(ctx) { // ... }, @@ -87,39 +125,168 @@ Any styles imported in your content script will be added to that content script' } ``` -## Context +To disable this behavior, set `cssInjectionMode` to `"manual"` or `"ui"`. -Old content scripts are not automatically stopped when an extension updates and restarts. 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. Since it's not connected to the latest version of your extension, the browser decides to throw an error. +```ts +export default defineContentScript({ + matches: ['*://google.com/*', '*://duckduckgo.com/*'], + cssInjectionMode: 'manual', -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. + 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. + +```ts +const ui = document.createElement('div'); +const anchor = document.querySelector('#anchor-selector'); +anchor.append(ui); +``` + +::: + +### Usage + +To use `createContentScriptUi`, follow these steps: + +1. Import your CSS file at the top of your content script +2. Set `cssInjectionMode: "ui"` inside `defineContentScript` +3. Call `createContentScriptUi` +4. Call `ui.mount()` to add the UI to the webpage + +Here's a basic example: ```ts +// entrypoints/ui.content/index.ts +import './style.css'; + export default defineContentScript({ // ... - main(ctx: ContentScriptContext) { - // Add custom listeners for stopping work - ctx.onInvalidated(() => { - // ... + 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`... + }, }); - // Stop fetch requests - fetch('...url', { signal: ctx.signal }); + // Yoy must call `mount` to add the UI to the page. + ui.mount(); + }, +}); +``` - // Timeout utilities - ctx.setTimeout(() => { - // ... - }, 5e3); - ctx.setInterval(() => { - // ... - }, 60e3); +If you're using a frontend framework, you'll also need to include an `onRemoved` callback: + +:::code-group + +```ts [Vue] +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(); }, }); ``` -The class extends [`AbortController`](https://developer.mozilla.org/en-US/docs/Web/API/AbortController) and provides other utilities for stopping a content script's logic once it becomes invalidated. +```ts [React] +import ReactDOM from 'react-dom/client'; -:::tip -When working with content scripts, **you should always use the `ctx` object to stop any async or future work.** +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(); + }, +}); +``` + +```ts [Svelte] +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(); + }, +}); +``` + +```ts [Solid] +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(); + }, +}); +``` -This prevents old content scripts from interfering with new content scripts, and prevents error messages from the console in production. ::: + +### `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 the `anchor` and `append` options +- `overlay`: 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 content +- `modal`: 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.