Skip to main content
Info

In short: an extension is a small program written with web technology (HTML, CSS and JavaScript) plus a manifest.json that says what it is and what it may do. It runs inside the browser in a few separate places: a background service worker, content scripts inside pages, and its own pages such as a popup. Automation loads one by starting the browser a particular way. The seleniumbase-rs companion extension is one; the crx plugin is how a test loads a packed one.

What an extension is#

An extension is a folder (or a zip, packed as a .crx file) with a manifest.json and some files. The browser reads the manifest and wires the parts up. There is no installer and no native code: everything is web code, run by the browser with the permissions the manifest asks for.

Manifest V3#

Manifest V3 (MV3) is the current format for Chrome, Edge and other Chromium browsers. Firefox supports it too, with some differences. It replaced Manifest V2 with rules aimed at safety and at saving resources:

Manifest V2Manifest V3
A background page that stayed alive all the timeA service worker that the browser starts when an event arrives and stops when idle
Remote code and eval were possibleOnly code packaged in the extension runs; a strict content security policy (script-src 'self')
webRequest could block and rewrite any requestdeclarativeNetRequest: the extension hands the browser rules and the browser applies them
Host access granted at installHost access can be requested later and by the user (optional_host_permissions, activeTab)

The most important change for anyone writing one is the service worker: it can be stopped at any moment, so it must not keep state in memory. It keeps what it needs in chrome.storage and reads it again on the next event.

The parts#

The manifest

Names the extension, lists its permissions and points to everything else. The browser refuses to load an extension whose manifest is wrong.

The background service worker

Event-driven code that is not tied to any tab: it reacts to a click on the toolbar button, to a message, to a tab opening. It can use the extension APIs (chrome.tabs, chrome.scripting, chrome.storage) but has no page and no DOM. In Firefox the same job is done by a background script (background.scripts).

Content scripts

JavaScript that is injected into a web page, either declared in the manifest for sites that match, or injected on demand with chrome.scripting. A content script sees the page’s DOM, but it runs in an isolated world: it has its own JavaScript globals, so it cannot see the page’s variables, and the page cannot see its. They share the DOM and events, not the objects.

Extension pages

HTML pages that belong to the extension: the popup that opens from the toolbar button, the side panel (Chrome’s sidePanel, Firefox’s sidebar), an options page, a page in the DevTools window. They have a chrome-extension:// address (moz-extension:// in Firefox) and the extension’s permissions.

Permissions#

Everything an extension may do is declared. The browser shows the user the powerful ones when it is installed.

SettingWhat it grants
permissionsAccess to extension APIs: storage, tabs, scripting, sidePanel, activeTab
host_permissionsReading and changing pages on given sites; <all_urls> means every site
optional_host_permissionsThe same, but asked for later, at a moment the extension chooses
activeTabTemporary access to the one tab the user just invoked the extension on, until it navigates or closes
Warning

An extension with access to all sites can read every page you visit, including passwords as you type them. Load only extensions you trust, and give them the least access they need. A good one asks for activeTab first.

Messaging: how the parts talk#

The parts live in different places and cannot call each other directly. They send messages.

Messages between the parts of an extension. The content script shares the page’s DOM and nothing else.

A message is plain data (anything JSON can carry). The receiver should check what it gets, because a message may come from a page the extension does not control. For a long conversation, two sides can open a Port.

How automation loads an extension#

Start Chromium with --load-extension=/path/to/unpacked. This is the oldest way and still works in Chromium and in Chrome for Testing, but branded Chrome ignores --load-extension since version 137.

Some things to know when you do:

  • Headless. New headless Chrome supports extensions; the old headless mode did not (see browser architecture).
  • Incognito. An extension does not run in a private window unless it is allowed to; a fresh browser context is similar.
  • Timing. The service worker starts on its first event. A test should wait until the extension answers (the companion’s end-to-end tests wait for the background to reply) before it starts to depend on it.
  • Visibility. An extension changes the browser. Pages can probe for known extensions through the resources they expose, so an extension can show up in a fingerprint.

The companion extension#

The seleniumbase-rs companion extension is a Manifest V3 extension for Chrome, Edge and Firefox written in strict TypeScript and bundled with Bun. It is a good small example of everything above:

  • Its manifest asks for activeTab, scripting and storage, and for all sites only as an optional host permission, asked at the moment you press Start recording. It declares no static content_scripts and no static host permissions.
  • Its background is a service worker in Chrome and a background script in Firefox. It keeps its state in chrome.storage because the browser may stop it.
  • The recorder is a content script injected into the tab you point it at. It listens for trusted events only (isTrusted; see JavaScript, events and waiting), builds a selector for each (see selectors) and sends each step to the background. The panel (a popup, a side panel or a sidebar) shows the steps.
  • It exports a recording as a seleniumbase-rs pure CDP Rust test, a BaseCase test, or a Chrome DevTools Recorder JSON file.

Its messages use a small versioned format, { v: 1, type: "recorder/step", ... }, and a router that refuses a message from the wrong kind of sender. The full guide, with how to build and load it, is Browser extension.

The crx plugin in a test#

The crx plugin is a seleniumbase-rs plugin that loads packed extensions into a Chromium browser the plugin set starts. It works at launch only, and it does not check the signature in the header, so load only files you trust. Its unpacked folders (named with the prefix sbrs-crx-) are deleted when the browser closes.

src/main.rs

The library also bundles a few extensions of its own (src/utils/extensions), used by features such as ad blocking. See Plugins for how a plugin set is built, and the glossary entry crx.

Where to go next#