Skip to content

Cross-Browser Platform ​

Skeeditor targets Chrome, Firefox, and Safari using a shared src/ codebase. Browser-specific differences are isolated in src/platform/<browser>/ shims. The manifest is generated per-browser by WXT from wxt.config.ts — no separate overlay files are required.


Build targets ​

BrowserBuild commandOutput directory
Chrometask build:chromedist/chrome/
Firefoxtask build:firefoxdist/firefox/
Safaritask build:safaridist/safari/

task build is an alias for Chrome; task build:all builds every browser target.


Browser API ​

All extension contexts import browser from wxt/browser:

ts
import { browser } from 'wxt/browser';

WXT re-exports webextension-polyfill under this import path and automatically injects the polyfill for Chromium at build time. No manual polyfill setup is needed — you don't need to import it as the first statement, add a separate polyfill-only content script, or worry about manifest script ordering.

Entrypoints live under src/entrypoints/ and are discovered by WXT convention:

Entrypoint file/dirContext
src/entrypoints/background.tsService worker
src/entrypoints/content.tsContent script
src/entrypoints/popup/Action popup
src/entrypoints/options/Options page

Shared logic lives under src/shared/; platform-specific shims live under src/platform/<browser>/.

In unit/integration tests, wxt/browser is aliased to test/mocks/wxt-browser.ts, which proxies through globalThis.browser populated by test/mocks/browser-apis.ts.


Platform detection (src/platform/detect.ts) ​

Use feature detection, never navigator.userAgent. The detectPlatform() function uses API presence as the signal:

SignalBrowser
browser.runtime.getBrowserInfo is a functionFirefox
globalThis.safari?.extension is definedSafari
NeitherChrome
ts
import { platform } from '@src/platform';

if (platform.isFirefox) {
  // Firefox-specific path
}

Known API differences ​

Background execution model ​

BrowserManifest keyNotes
Chrome"service_worker": "…"Non-persistent, wakes on events
Firefox"scripts": ["…"]Non-persistent background script
Safari"service_worker": "…"Non-persistent, mirrors Chrome

Never store in-memory state between background wake cycles. Use browser.storage.local for any data that must survive the background being unloaded.

browser.identity ​

Not available on Firefox or Safari. Skeeditor uses browser.tabs.create for the OAuth redirect tab — this works cross-browser.

Side panel / sidebar ​

  • Chrome 114+: browser.sidePanel (not currently used by Skeeditor)
  • Firefox: browser.sidebarAction (different API, Firefox-only)
  • Safari: no equivalent

webRequest blocking mode ​

Replaced by declarativeNetRequest in Manifest V3 on Chrome and Safari. Firefox MV3 still supports webRequest blocking, but skeeditor does not use either API.

Safari limitations ​

  • Minimum version: macOS 14+ (Sonoma), Safari 17+
  • The extension must ship as a macOS app wrapper (Xcode project). The build:safari script handles this via xcrun safari-web-extension-converter.
  • Check Apple's Safari release notes before using any new WebExtension API.

Manifest ​

The manifest is generated by WXT from the manifest factory function in wxt.config.ts. Browser-specific fields are expressed with conditional spreads:

ts
manifest: ctx => ({
  permissions: ['storage', 'tabs', 'alarms'],
  host_permissions: [
    'https://bsky.app/*',
    'https://*.bsky.network/*',
    'https://docs.skeeditor.link/*',
    'https://slingshot.microcosm.blue/*',
  ],
  ...(ctx.browser === 'firefox' && {
    browser_specific_settings: {
      gecko: {
        id: '[email protected]',
        strict_min_version: '140.0',
        data_collection_permissions: {
          required: ['authenticationInfo', 'personalCommunications'],
        },
      },
      gecko_android: {
        strict_min_version: '142.0',
      },
    },
  }),
});

WXT writes the final manifest.json to dist/<browser>/manifest.json during each build. There is no manifests/ directory or scripts/merge-manifest.ts — those belong to the previous (pre-WXT) build system.


Dev workflow ​

Chrome ​

sh
task build:watch:chrome
# In Chrome: chrome://extensions → Developer mode → Load unpacked → dist/chrome/

Firefox ​

sh
task build:watch:firefox
# Then either:
task webext:run:firefox
# Or: about:debugging → Load Temporary Add-on → dist/firefox/manifest.json

Safari ​

sh
task build:safari
task build:safari:swift
# Open the Xcode project under xcode/, build, and enable in Safari → Settings → Extensions

To run the converter manually instead:

sh
xcrun safari-web-extension-converter dist/safari \
  --project-location ./xcode \
  --app-name skeeditor \
  --bundle-identifier agency.self.skeeditor \
  --swift

To allow unsigned extensions during development: Safari → Settings → Advanced → Show features for web developers → Developer → Allow unsigned extensions.


Minimum supported versions ​

BrowserMinimum versionKey requirement
Chrome120MV3 service worker stability
Firefox125MV3 + Intl.Segmenter support
Safari17 (macOS 14/Sonoma)Baseline WebExtensions MV3

Privacy Policy · Released under the MIT License.