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 and per-browser manifest overlays.


Build targets

BrowserBuild commandOutput directory
Chromepnpm build:chromedist/chrome/
Firefoxpnpm build:firefoxdist/firefox/
Safaripnpm build:safaridist/safari/

pnpm build is an alias for pnpm build:chrome.


Browser API polyfill

webextension-polyfill normalises Chromium's callback-based chrome.* API into the same Promise-based browser.* surface used natively by Firefox and Safari. It is imported as the first statement in every Vite entry point:

ts
// src/background/service-worker.ts (and all other entry points)
import 'webextension-polyfill';

This ensures globalThis.browser is available before any extension code runs.

In unit/integration tests, webextension-polyfill is stubbed — the browser.* global is provided by test/mocks/browser-apis.ts via test/setup/unit.ts instead.


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 (Firefox 121+)
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 structure

text
manifests/
├── base.json              ← shared: permissions, host_permissions, content_scripts, action
├── chrome/manifest.json   ← adds: "background": { "service_worker": "..." }
├── firefox/manifest.json  ← adds: "background": { "scripts": [...] }, gecko settings
└── safari/manifest.json   ← adds: "background": { "service_worker": "..." }

At build time, scripts/merge-manifest.ts merges base.json with the browser overlay and writes the result to dist/<browser>/manifest.json.


Dev workflow

Chrome

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

Firefox

sh
pnpm build:watch:firefox
# Then either:
web-ext run --source-dir dist/firefox/ --firefox=nightly
# Or: about:debugging → Load Temporary Add-on → dist/firefox/manifest.json

Safari

sh
pnpm build:safari
xcrun safari-web-extension-converter dist/safari \
  --project-location ./safari-xcode \
  --app-name skeeditor \
  --bundle-identifier dev.selfagency.skeeditor \
  --swift
# Open the Xcode project, build, and enable in Safari → Settings → Extensions

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
Firefox121MV3 non-persistent background support
Safari17 (macOS 14/Sonoma)Baseline WebExtensions MV3

Released under the MIT License.