Permissions and configuration
The manifest that the build makes
wxt.config.ts makes a manifest for each browser from one configuration. This is the Chrome MV3
manifest:
{ "name": "__MSG_extensionName__", "description": "__MSG_extensionDescription__", "default_locale": "en", "version": "0.1.0", "permissions": ["storage", "scripting", "tabs", "sidePanel"], "host_permissions": ["*://*.patreon.com/*"], "optional_host_permissions": ["*://*.patreonusercontent.com/*", "*://*.cpatreon.net/*"], "action": { "default_title": "__MSG_actionTitle__" }, "commands": { "_execute_action": { "suggested_key": { "default": "Ctrl+Shift+Y" } } }, "content_security_policy": { "extension_pages": "script-src 'self'; object-src 'self';" }, "side_panel": { "default_path": "sidepanel.html" }, "content_scripts": [{ "matches": ["*://*.patreon.com/*"] }]}Each __MSG_*__ value is a reference into public/_locales/. The browser puts the text of the
user in its place when it reads the manifest. See localization.
WXT changes some keys for the Firefox MV2 build. It writes sidebar_action in place of
side_panel, and it moves the host permissions into permissions. WXT does not change two other
keys, thus wxt.config.ts writes them: optional_permissions in place of
optional_host_permissions, and _execute_sidebar_action in place of _execute_action.
_execute_sidebar_action is the reserved command that opens the sidebar. The earlier
_execute_browser_action fired the toolbar button, which is a different surface, and no code
listened to it.
Two keys need more than the manifest factory:
sidebar_action.default_title. WXT writes the wholesidebar_actionkey after the factory gives its result, and it takes the title from the<title>text ofentrypoints/sidepanel/index.html. A__MSG_*__reference in the factory has no effect. Thebuild:manifestGeneratedhook inwxt.config.tswrites the reference again after the build makes the manifest, thus the German sidebar header has German text.background.persistent. WXT writesbackgroundfrom the options of the background entry point, not from the factory.defineBackground({ persistent: false, main() {...} })inentrypoints/background.tsgives Firefox an event page. MV3 does not read the value. Without it, MV2 makes a full background page that stays in memory for the whole session. The design of the service worker expects the opposite.
Configuration decisions
- Host permission: variant A (decided 2026-08-31). The manifest declares
host_permissionsfor patreon.com, and the content script is static. Gopher is a viewer for patreon.com, thus the permission message names the origin that the user expects. The media CDNs keep optional host permissions (see media, CDN, and CSP). - A side panel and no popup. The manifest does not declare
default_popup. If it declares a popup, a click on the icon opens the popup and not the side panel. TheonInstalledhandler callssidePanel.setPanelBehavior({ openPanelOnActionClick: true }). - CSP. The manifest keeps the default
script-src 'self'of MV3. There is noframe-src, because Gopher frames no content. A third-party player opens in the tab of the provider (see the panel). - Keyboard.
Ctrl+Shift+Yopens the panel. On macOS the keys areCmd+Shift+Y. - Firefox. The value of
browser_specific_settings.gecko.idisgopher@michi.onl. This ID must not change. The ID is the origin of the extension, and the origin is the key of the IndexedDB database. If the ID changes, the browser deletes the cache. The manifest also declaresdata_collection_permissions: { required: ['none'] }for AMO. modulePreloadis off in the Vite configuration. A<link rel="modulepreload">tag does not operate in an extension origin. Chrome writes the message “cross-world extension resource mismatch” and shows an error mark on the extensions page.- No cookie code. There is no
cookiespermission, no call tochrome.cookies, and no OAuth. The runner uses the session of the page because it has the same origin (ADR-002). - No analytics software. Such software makes the statement “collects no data” false. Note
that
setUninstallURLsends a request when the user removes the extension, also when it has no parameters. Do not use it. - Language.
default_localeisen, and the manifest gives five strings as__MSG_*__references. Withoutdefault_localethe browser refuses to load an extension that uses such a reference. Read localization. homepage_urlgiveshttps://gopher.michi.onl, the address of this site’s privacy policy. It also replaces the URL for removal that Gopher does not set.setUninstallURL()sends a request when a user removes the extension, and that would make the non-goal “no server” false (see principles).- The toolbar button has a listener.
setPanelBehavior()opens the panel on Chrome. Firefox has nosidePanel, thus the worker also listens toaction.onClickedandbrowserAction.onClickedand callssidebarAction.toggle(). The listener is also the path for Chrome ifsetPanelBehavior()does not succeed. The manifest declares nodefault_popup, because a popup wins over the side panel. - The content security policy names
img-srcandmedia-src. Only the CDN hosts of Patreon,'self',blob:, anddata:can give a picture or a media file to a panel page. This makes the host list inlib/safe-url.tsstructural (see media, CDN, and CSP).default-srcstays unset:hls.jsreads video on a web worker that no rule names, and a limit there needs an examination with a real stream.