API Implementation Guide
This document provides detailed implementation notes for each Chrome API namespace, organized by priority tier.
Implementation Status Legend
Section titled “Implementation Status Legend”[ ]Not started[~]Partially implemented (base class provides some behavior)[x]Fully implemented
Tier 1: Critical (Every Extension Needs These)
Section titled “Tier 1: Critical (Every Extension Needs These)”chrome.runtime
Section titled “chrome.runtime”Status: [~] (events wired up, methods are stubs)
The most important namespace. Provides extension identity, messaging, lifecycle events, and manifest access.
| Method / Property | Implementation Strategy | Needs Binding? |
|---|---|---|
runtime.id | Set from extension manifest/CRX ID at context creation | No |
runtime.getManifest() | Return parsed manifest object (stored in ExtensionRegistry) | No |
runtime.getURL(path) | Return /helium-ext/<extId>/<path> (Layer 1 URL resolution) | No |
runtime.sendMessage(msg, cb) | Route through SharedWorker (Layer 3 message passing) | No |
runtime.connect(extId?, info?) | Create port through SharedWorker (Layer 3) | No |
runtime.onMessage | Dispatch from SharedWorker messages (Layer 3) | No |
runtime.onConnect | Dispatch from SharedWorker port connections (Layer 3) | No |
runtime.onInstalled | Fired during extension load sequence | No |
runtime.onStartup | Fired on SharedWorker init (simulates browser startup) | No |
runtime.reload() | Destroy + recreate extension contexts | Yes (host does reload) |
runtime.openOptionsPage() | Open options page tab | Yes (host opens tab) |
runtime.getPlatformInfo() | Return hardcoded platform info (navigator.userAgent parsing) | No |
runtime.setUninstallURL(url) | Store URL, open on uninstall | No (stored in registry) |
runtime.getBackgroundPage() | MV2 only: return background iframe’s window | No |
runtime.getContexts() | MV3 only: query context registry in SharedWorker | No |
runtime.lastError | Set before callback invocation on error, cleared after | No |
runtime.onSuspend | MV2 event pages / MV3 worker lifecycle | No |
runtime.onSuspendCanceled | MV2 event pages | No |
runtime.onUpdateAvailable | Fired by update checker | No |
runtime.connectNative() | Not supportable in web context — throw meaningful error | No |
runtime.sendNativeMessage() | Not supportable — throw meaningful error | No |
MV2-specific methods (in MV2 package only):
runtime.getBackgroundPage()— Returns the background iframe’scontentWindowruntime.getVersion()— Returns manifest version string
MV3-specific methods (in MV3 package only):
runtime.getContexts(filter)— Queries the SharedWorker’s context registry
Implementation notes:
runtime.idmust be set correctly for each extension instanceruntime.lastErroris context-local (each context has its own)runtime.onInstalledfires with{ reason: 'install' | 'update' | 'chrome_update' }. For Helium, only ‘install’ and ‘update’ are relevant
chrome.storage
Section titled “chrome.storage”Status: [~] (StorageArea base class is fully implemented with in-memory store)
| Area | Quota | Implementation |
|---|---|---|
storage.local | 10MB (QUOTA_BYTES: 10485760) | IndexedDB per-extension + WAL |
storage.sync | 100KB total, 8KB/item, 512 items max | IndexedDB + WAL (no actual sync — there’s no Chrome account) |
storage.managed | Read-only | IndexedDB, populated by host app configuration |
storage.session | 10MB (MV3 only) | In-memory Map + optional durable-flush to IndexedDB on worker termination |
Transactional Storage Architecture
Section titled “Transactional Storage Architecture”All chrome.storage writes use a write-ahead log (WAL) and atomic batching to guarantee that no data is ever lost during worker terminations, crashes, page reloads, or unexpected shutdowns.
Write-Ahead Log (WAL)
Section titled “Write-Ahead Log (WAL)”Every set() / remove() / clear() operation first writes the intended mutation to a WAL object store before applying it to the data store. On next startup, Helium replays any incomplete WAL entries to recover from mid-write crashes.
interface WALEntry { walId: string; // UUID extensionId: string; areaName: 'local' | 'sync'; operation: 'set' | 'remove' | 'clear'; keys?: string[]; // Keys being modified data?: Record<string, any>; // New values (for 'set') timestamp: number; committed: boolean; // false until data store write completes}
class TransactionalStorageArea { async set(items: Record<string, any>): Promise<void> { const walId = crypto.randomUUID();
// 1. Write WAL entry (uncommitted) const walEntry: WALEntry = { walId, extensionId: this.extensionId, areaName: this.areaName, operation: 'set', keys: Object.keys(items), data: items, timestamp: Date.now(), committed: false, }; await this.walStore.put(walEntry);
// 2. Apply the mutation in a SINGLE IndexedDB transaction (atomic) const tx = this.db.transaction([this.areaName, 'wal'], 'readwrite'); const dataStore = tx.objectStore(this.areaName); const walStore = tx.objectStore('wal');
for (const [key, value] of Object.entries(items)) { dataStore.put({ key, value }); }
// 3. Mark WAL entry as committed within the SAME transaction walEntry.committed = true; walStore.put(walEntry);
await tx.complete;
// 4. Fire storage.onChanged to ALL contexts of this extension // Changes must propagate cross-context via the SharedWorker, // not just locally within the writing context. this.emitChanges(items); }
/** * Broadcast storage changes to all contexts of this extension * via the SharedWorker message backbone. */ private emitChanges(items: Record<string, any>): void { // Build Chrome-format change object: { key: { newValue, oldValue } } const changes: Record<string, { newValue?: any; oldValue?: any }> = {}; for (const [key, value] of Object.entries(items)) { changes[key] = { newValue: value }; // oldValue populated from cache }
// Post to SharedWorker for broadcast to all extension contexts this.routerPort.postMessage({ type: '__helium_storage_changed', extensionId: this.extensionId, areaName: this.areaName, changes, }); }
async remove(keys: string | string[]): Promise<void> { const keyList = Array.isArray(keys) ? keys : [keys]; const walId = crypto.randomUUID();
// Same WAL pattern: write intent → apply atomically → mark committed const walEntry: WALEntry = { walId, extensionId: this.extensionId, areaName: this.areaName, operation: 'remove', keys: keyList, timestamp: Date.now(), committed: false, }; await this.walStore.put(walEntry);
const tx = this.db.transaction([this.areaName, 'wal'], 'readwrite'); for (const key of keyList) { tx.objectStore(this.areaName).delete(key); } walEntry.committed = true; tx.objectStore('wal').put(walEntry); await tx.complete; }}WAL Recovery On Startup
Section titled “WAL Recovery On Startup”class StorageRecovery { async recover(extensionId: string): Promise<void> { const walEntries = await this.walStore.getAll(extensionId);
for (const entry of walEntries) { if (!entry.committed) { // Incomplete write — replay the operation console.warn(`[Helium] Recovering uncommitted WAL entry: ${entry.walId}`); if (entry.operation === 'set' && entry.data) { await this.applySet(entry.extensionId, entry.areaName, entry.data); } else if (entry.operation === 'remove' && entry.keys) { await this.applyRemove(entry.extensionId, entry.areaName, entry.keys); } else if (entry.operation === 'clear') { await this.applyClear(entry.extensionId, entry.areaName); } } // Clean up WAL entry after recovery await this.walStore.delete(entry.walId); } }}Versioned Snapshots (Disaster Recovery)
Section titled “Versioned Snapshots (Disaster Recovery)”Every 100 writes, Helium saves a point-in-time snapshot of the storage area. The last 3 snapshots are retained for disaster recovery:
class SnapshotManager { private writeCounter: Map<string, number> = new Map(); // extensionId:area → count private readonly SNAPSHOT_INTERVAL = 100; private readonly MAX_SNAPSHOTS = 3;
async maybeSnapshot(extensionId: string, areaName: string): Promise<void> { const key = `${extensionId}:${areaName}`; const count = (this.writeCounter.get(key) || 0) + 1; this.writeCounter.set(key, count);
if (count % this.SNAPSHOT_INTERVAL !== 0) return;
// Take snapshot const allData = await this.readAll(extensionId, areaName); const snapshot = { snapshotId: crypto.randomUUID(), extensionId, areaName, data: allData, timestamp: Date.now(), writeCount: count, };
// Store snapshot, evicting oldest if over limit await this.snapshotStore.put(snapshot); await this.evictOldSnapshots(extensionId, areaName); }
async restoreFromSnapshot( extensionId: string, areaName: string, snapshotId?: string ): Promise<void> { const snapshot = snapshotId ? await this.snapshotStore.get(snapshotId) : await this.getLatestSnapshot(extensionId, areaName);
if (!snapshot) { throw new Error('No snapshot available for recovery'); }
// Atomic restore: clear + write all in single transaction const tx = this.db.transaction([areaName], 'readwrite'); tx.objectStore(areaName).clear(); for (const [key, value] of Object.entries(snapshot.data)) { tx.objectStore(areaName).put({ key, value }); } await tx.complete;
console.log(`[Helium] Restored ${extensionId}/${areaName} from snapshot ${snapshot.snapshotId}`); }}Session Storage Durable-Flush
Section titled “Session Storage Durable-Flush”MV3 storage.session is normally in-memory only (cleared on worker restart). To prevent data loss during unexpected worker terminations, Helium provides an opt-in durable-flush mechanism:
class DurableSessionStorage extends SessionStorageArea { private durableFlushEnabled: boolean;
constructor(extensionId: string, config: HeliumConfig) { super(extensionId); this.durableFlushEnabled = config.sessionDurableFlush ?? false; }
/** * Called by the lifecycle manager BEFORE terminating the MV3 worker. * Flushes all in-memory session data to IndexedDB so it can be * restored when the worker respawns. */ async flushToDisk(): Promise<void> { if (!this.durableFlushEnabled) return;
const tx = this.db.transaction(['session_durable'], 'readwrite'); const store = tx.objectStore('session_durable'); store.clear(); // Fresh flush
for (const [key, value] of this.store.entries()) { store.put({ key, value }); } await tx.complete; }
/** * Called during worker respawn to restore session data. */ async restoreFromDisk(): Promise<void> { if (!this.durableFlushEnabled) return;
const entries = await this.db.getAll('session_durable'); for (const entry of entries) { this.store.set(entry.key, entry.value); } }}IndexedDB Schema (Updated)
Section titled “IndexedDB Schema (Updated)”Database: "helium-storage-<extensionId>"
Object store: "local" Key: string (storage key) Value: { key: string, value: any }
Object store: "sync" Key: string Value: { key: string, value: any }
Object store: "managed" Key: string Value: { key: string, value: any } (read-only, populated during extension install)
Object store: "wal" Key: walId (string) Value: WALEntry
Object store: "snapshots" Key: snapshotId (string) Index: [extensionId, areaName, timestamp] Value: { snapshotId, extensionId, areaName, data, timestamp, writeCount }
Object store: "session_durable" (opt-in) Key: string (storage key) Value: { key: string, value: any }Change event propagation remains the same: storage changes propagate across all contexts of the same extension via the SharedWorker.
chrome.tabs
Section titled “chrome.tabs”Status: [ ] (all stubs)
The second most important namespace. Almost all extensions query or manipulate tabs.
| Method | Binding Required | Notes |
|---|---|---|
tabs.create(props) | Yes | Host creates tab, returns TabInfo |
tabs.remove(tabIds) | Yes | Host closes tab(s) |
tabs.update(tabId, props) | Yes | Host updates tab URL, pinned state, etc. |
tabs.query(queryInfo) | Yes | Host returns matching tabs. Must filter by all query params: active, pinned, url, title, windowId, currentWindow, status, lastFocusedWindow, groupId |
tabs.get(tabId) | Yes | Host returns tab info |
tabs.getCurrent() | Partial | Returns info for the tab this context is running in (looked up from context registry) |
tabs.move(tabIds, props) | Yes | Host moves tab(s) |
tabs.reload(tabId?, props?) | Yes | Host reloads tab |
tabs.duplicate(tabId) | Yes | Host duplicates tab |
tabs.goBack(tabId?) | Yes | Host navigates back |
tabs.goForward(tabId?) | Yes | Host navigates forward |
tabs.group(options) | Yes | Host groups tab(s) |
tabs.ungroup(tabIds) | Yes | Host ungroups tab(s) |
tabs.highlight(info) | Yes | Host highlights tabs |
tabs.discard(tabId?) | Yes | Host discards tab (frees memory) |
tabs.captureVisibleTab(windowId?, opts?) | Yes | Host captures screenshot (returns data URL) |
tabs.detectLanguage(tabId?) | Yes | Host detects page language |
tabs.sendMessage(tabId, msg, opts?, cb?) | No | Routes through SharedWorker (Layer 3) |
tabs.connect(tabId, connectInfo?) | No | Creates port through SharedWorker (Layer 3) |
tabs.getZoom(tabId?) | Yes | Host returns zoom level |
tabs.setZoom(tabId?, factor) | Yes | Host sets zoom level |
tabs.getZoomSettings(tabId?) | Yes | Host returns zoom settings |
tabs.setZoomSettings(tabId?, settings) | Yes | Host sets zoom settings |
MV2-only methods (deprecated in MV3):
tabs.executeScript(tabId?, details, cb?)— Inject script. Binding + proxy integration.tabs.insertCSS(tabId?, details, cb?)— Inject CSS. Binding + proxy integration.tabs.removeCSS(tabId?, details, cb?)— Remove CSS.tabs.getAllInWindow(windowId?, cb?)— Deprecated, alias forquery({windowId}).tabs.getSelected(windowId?, cb?)— Deprecated, alias forquery({active: true, windowId}).tabs.sendRequest(tabId, request, cb?)— Deprecated, alias forsendMessage.
TabInfo object shape (returned by all tab methods):
interface TabInfo { id: number; index: number; windowId: number; active: boolean; pinned: boolean; highlighted: boolean; incognito: boolean; // Always false in Helium url?: string; // Only with "tabs" permission title?: string; // Only with "tabs" permission favIconUrl?: string; // Only with "tabs" permission pendingUrl?: string; // Only with "tabs" permission status?: 'loading' | 'complete' | 'unloaded'; discarded: boolean; autoDiscardable: boolean; mutedInfo?: { muted: boolean; reason?: string; extensionId?: string }; width?: number; height?: number; sessionId?: string; groupId: number; // -1 if not grouped lastAccessed?: number; audible?: boolean; openerTabId?: number;}Permission-based field filtering: When an extension without the tabs permission calls tabs.query(), sensitive fields (url, title, favIconUrl, pendingUrl) are stripped from the result. The activeTab permission grants these fields temporarily for the active tab when the user invokes the extension.
chrome.action (MV3) / chrome.browserAction (MV2)
Section titled “chrome.action (MV3) / chrome.browserAction (MV2)”Status: [ ] (all stubs)
Controls the extension’s toolbar icon, badge, and popup.
| Method | Binding Required | Notes |
|---|---|---|
action.setIcon(details) | Yes | Host updates icon in toolbar |
action.setBadgeText(details) | Yes | Host updates badge text |
action.setBadgeBackgroundColor(details) | Yes | Host updates badge color |
action.setBadgeTextColor(details) | Yes | Host updates badge text color |
action.setTitle(details) | Yes | Host updates tooltip |
action.setPopup(details) | Hybrid | Store popup path; host uses it when icon is clicked |
action.openPopup(opts?) | Yes | Host opens popup programmatically |
action.enable(tabId?) | Hybrid | Track enabled state; host reflects in UI |
action.disable(tabId?) | Hybrid | Track disabled state; host grays out icon |
action.getBadgeText(details) | No | Return from internal state |
action.getTitle(details) | No | Return from internal state |
action.getPopup(details) | No | Return from internal state |
action.getBadgeBackgroundColor(details) | No | Return from internal state |
action.getBadgeTextColor(details) | No | Return from internal state |
action.isEnabled(tabId?) | No | Return from internal state |
action.getUserSettings() | No | Return {isOnToolbar: true} (always) |
Per-tab state: Badge text, icon, title, popup, and enabled state can be set per-tab or globally. Internal state is stored as:
interface ActionState { global: { icon: string | Record<string, string>; badgeText: string; badgeBackgroundColor: [number, number, number, number]; badgeTextColor: [number, number, number, number]; title: string; popup: string; enabled: boolean; }; perTab: Map<number, Partial<typeof global>>;}When getting a value for a specific tab, check perTab.get(tabId) first, fall back to global.
chrome.alarms
Section titled “chrome.alarms”Status: [ ] (all stubs)
Self-contained timer system. No host binding needed.
| Method | Implementation |
|---|---|
alarms.create(name?, info) | setTimeout/setInterval + IndexedDB persistence |
alarms.get(name?, cb?) | Look up from internal alarm map |
alarms.getAll(cb?) | Return all alarms for this extension |
alarms.clear(name?, cb?) | Clear specific alarm |
alarms.clearAll(cb?) | Clear all alarms |
alarms.onAlarm | Fire when alarm triggers |
Implementation details:
- Minimum period: 1 minute (Chrome enforces this;
delayInMinutesminimum is also 1 for production, but Helium can optionally allow shorter for testing) - Alarms persist across background context restarts (store in IndexedDB, reload on context creation)
- Each extension has its own alarm namespace (alarm names are scoped to the extension)
interface AlarmInfo { name: string; scheduledTime: number; // Timestamp when next fire periodInMinutes?: number; // Repeat interval}
class AlarmManager { private alarms: Map<string, AlarmInfo> = new Map(); // key: `${extId}:${name}` private timers: Map<string, ReturnType<typeof setTimeout>> = new Map();
create(extensionId: string, name: string, info: { delayInMinutes?: number; when?: number; periodInMinutes?: number }): void { const key = `${extensionId}:${name}`;
// Clear existing alarm with same name this.clear(extensionId, name);
const now = Date.now(); let scheduledTime: number;
if (info.when) { scheduledTime = info.when; } else if (info.delayInMinutes) { scheduledTime = now + info.delayInMinutes * 60 * 1000; } else { scheduledTime = now + 60 * 1000; // default 1 minute }
const alarm: AlarmInfo = { name, scheduledTime, periodInMinutes: info.periodInMinutes, };
this.alarms.set(key, alarm); this.scheduleTimer(extensionId, name, alarm); this.persistAlarms(); // Write to IndexedDB }
private scheduleTimer(extensionId: string, name: string, alarm: AlarmInfo): void { const key = `${extensionId}:${name}`; const delay = Math.max(0, alarm.scheduledTime - Date.now());
const timer = setTimeout(() => { // Fire onAlarm event to the extension this.fireAlarm(extensionId, alarm);
// Reschedule if periodic if (alarm.periodInMinutes) { alarm.scheduledTime = Date.now() + alarm.periodInMinutes * 60 * 1000; this.scheduleTimer(extensionId, name, alarm); this.persistAlarms(); } else { this.alarms.delete(key); this.timers.delete(key); this.persistAlarms(); } }, delay);
this.timers.set(key, timer); }}chrome.i18n
Section titled “chrome.i18n”Status: [ ] (all stubs)
Internationalization. Self-contained, reads _locales/ files from the extension’s virtual filesystem.
| Method | Implementation |
|---|---|
i18n.getMessage(messageName, substitutions?) | Look up in _locales/<lang>/messages.json |
i18n.getUILanguage() | Return navigator.language |
i18n.getAcceptLanguages(cb?) | Return navigator.languages |
i18n.detectLanguage(text, cb?) | Use heuristics or return ‘und’ |
Message resolution order:
- Exact locale match (e.g.,
en_US) - Language match (e.g.,
en) - Default locale (from
manifest.default_locale) - Return empty string
Message format:
{ "appName": { "message": "My Extension", "description": "The name of the extension" }, "greeting": { "message": "Hello, $USER$!", "placeholders": { "user": { "content": "$1", "example": "World" } } }}chrome.permissions
Section titled “chrome.permissions”Status: [ ] (all stubs)
Runtime permission management. Self-contained.
| Method | Implementation |
|---|---|
permissions.getAll(cb?) | Return current permissions from PermissionResolver |
permissions.contains(perms, cb?) | Check if permissions are granted |
permissions.request(perms, cb?) | Prompt user (host UI) to grant optional permissions |
permissions.remove(perms, cb?) | Revoke optional permissions |
permissions.onAdded | Fire when permissions are added |
permissions.onRemoved | Fire when permissions are removed |
Notes:
permissions.request()can only be called from a user gesture context (popup click, etc.)- Only permissions listed in
optional_permissions/optional_host_permissionscan be requested permissions.remove()can only remove optional permissions, not required ones
Tier 2: Common Extensions
Section titled “Tier 2: Common Extensions”chrome.scripting (MV3 only)
Section titled “chrome.scripting (MV3 only)”| Method | Binding Required | Notes |
|---|---|---|
scripting.executeScript(injection) | Hybrid | Host identifies tab, Helium injects via SharedWorker → bootstrap |
scripting.insertCSS(injection) | Hybrid | Host identifies tab, Helium injects CSS via SharedWorker → bootstrap |
scripting.removeCSS(injection) | Hybrid | Host identifies tab, Helium removes CSS via SharedWorker → bootstrap |
scripting.registerContentScripts(scripts) | No | Store in registry, push to Reflux injection plugin |
scripting.unregisterContentScripts(filter?) | No | Remove from registry, push update to Reflux |
scripting.getRegisteredContentScripts(filter?) | No | Query registry |
scripting.updateContentScripts(scripts) | No | Update registry, push update to Reflux |
executeScript implementation: Must handle both func (function reference) and files (file paths) injection targets. For func, serialize the function to a string and inject. For files, read from extension virtual filesystem and inject. Dynamic injection goes through the SharedWorker to the target tab’s bootstrap, NOT through Reflux (since the page is already loaded).
chrome.webNavigation
Section titled “chrome.webNavigation”| Event | Trigger |
|---|---|
onBeforeNavigate | Host app reports tab about to navigate |
onCommitted | Host app reports navigation committed |
onDOMContentLoaded | Content script reports DOMContentLoaded |
onCompleted | Content script reports load complete |
onErrorOccurred | Host app reports navigation error |
onCreatedNavigationTarget | New tab created from link click in proxied page |
onReferenceFragmentUpdated | Hash change detected |
onTabReplaced | Tab replaced (prerender) |
onHistoryStateUpdated | pushState/replaceState detected |
chrome.cookies
Section titled “chrome.cookies”| Method | Implementation |
|---|---|
cookies.get(details) | Query IndexedDB cookie store |
cookies.getAll(details) | Query IndexedDB cookie store with filters |
cookies.set(details) | Write to IndexedDB cookie store |
cookies.remove(details) | Delete from IndexedDB cookie store |
cookies.getAllCookieStores() | Return [{id: "0", tabIds: [...]}] |
cookies.onChanged | Fire on set/remove |
Note: These are not the browser’s actual cookies. They are backed by an IndexedDB store that is synced with the BareMux worker’s cookie interceptor, which captures Set-Cookie headers from proxied responses and injects Cookie headers into proxied requests. This provides realistic cookie behavior for extensions. See PROXY-INTEGRATION.md.
chrome.contextMenus
Section titled “chrome.contextMenus”| Method | Implementation |
|---|---|
contextMenus.create(props, cb?) | Store menu item, notify host UI |
contextMenus.update(id, props, cb?) | Update stored item, notify host |
contextMenus.remove(id, cb?) | Remove stored item, notify host |
contextMenus.removeAll(cb?) | Remove all for this extension |
contextMenus.onClicked | Fire when user clicks menu item |
Context menu items are stored in-memory and rebuilt on extension restart. The host application receives the full menu tree and renders it in its context menu UI.
chrome.notifications
Section titled “chrome.notifications”| Method | Binding Required | Notes |
|---|---|---|
notifications.create(id?, opts) | Yes | Host shows notification |
notifications.update(id, opts) | Yes | Host updates notification |
notifications.clear(id) | Yes | Host dismisses notification |
notifications.getAll() | No | Return from internal state |
notifications.getPermissionLevel() | No | Return “granted” (always, since this is our controlled environment) |
notifications.onClicked | No | Host emits on user click |
notifications.onClosed | No | Host emits on dismiss |
notifications.onButtonClicked | No | Host emits on button click |
chrome.windows
Section titled “chrome.windows”| Method | Binding Required | Notes |
|---|---|---|
windows.create(createData?) | Yes | Host creates window |
windows.remove(windowId) | Yes | Host closes window |
windows.update(windowId, info) | Yes | Host updates window |
windows.get(windowId, info?) | Yes | Host returns window info |
windows.getAll(info?) | Yes | Host returns all windows |
windows.getCurrent(info?) | Yes | Host returns current window |
windows.getLastFocused(info?) | Yes | Host returns last focused |
windows.onCreated | No | Host emits |
windows.onRemoved | No | Host emits |
windows.onFocusChanged | No | Host emits |
windows.onBoundsChanged | No | Host emits |
Note: DaydreamX may operate as a single-window application. In that case, windows.getAll() always returns one window, and windows.create() could either create a new browser window (if the host supports it) or throw an error.
chrome.webRequest
Section titled “chrome.webRequest”| Event | MV2 Behavior | MV3 Behavior |
|---|---|---|
onBeforeRequest | Can block/redirect (with webRequestBlocking) | Observe only |
onBeforeSendHeaders | Can modify headers (with webRequestBlocking) | Observe only |
onSendHeaders | Observe only | Observe only |
onHeadersReceived | Can modify response headers | Observe only |
onAuthRequired | Can provide credentials | Observe only |
onResponseStarted | Observe only | Observe only |
onBeforeRedirect | Observe only | Observe only |
onCompleted | Observe only | Observe only |
onErrorOccurred | Observe only | Observe only |
Integration: All webRequest events are emitted by the BareMux worker’s network middleware. The BareMux worker communicates with extension contexts via a dedicated MessagePort bridge through the SharedWorker. See PROXY-INTEGRATION.md.
chrome.declarativeNetRequest (MV3 only)
Section titled “chrome.declarativeNetRequest (MV3 only)”| Method | Implementation |
|---|---|
declarativeNetRequest.updateDynamicRules(options) | Update rules in IndexedDB |
declarativeNetRequest.getDynamicRules(filter?) | Query rules from IndexedDB |
declarativeNetRequest.updateSessionRules(options) | Update in-memory rules |
declarativeNetRequest.getSessionRules(filter?) | Query in-memory rules |
declarativeNetRequest.updateEnabledRulesets(options) | Enable/disable static rulesets |
declarativeNetRequest.getEnabledRulesets() | Return enabled ruleset IDs |
declarativeNetRequest.getAvailableStaticRuleCount() | Return remaining quota |
declarativeNetRequest.isRegexSupported(regexOptions) | Test regex validity |
declarativeNetRequest.testMatchOutcome(request) | Test which rules would match |
declarativeNetRequest.onRuleMatchedDebug | Fire on rule match (debug only) |
Rule evaluation happens in the BareMux worker’s network middleware alongside webRequest, but rule management (CRUD) is in Helium core.
Tier 3: Specialized
Section titled “Tier 3: Specialized”chrome.bookmarks
Section titled “chrome.bookmarks”Fully host-bound. All CRUD operations delegate to DaydreamX’s bookmark manager. Events are emitted by the host.
chrome.history
Section titled “chrome.history”Fully host-bound. All query/modification operations delegate to DaydreamX’s history system.
chrome.downloads
Section titled “chrome.downloads”Fully host-bound. Download operations delegate to DaydreamX’s download manager.
chrome.sessions
Section titled “chrome.sessions”Fully host-bound. Session restore operations delegate to DaydreamX’s session manager.
chrome.management
Section titled “chrome.management”Hybrid:
management.getAll(),management.get(id),management.getSelf()— Read from ExtensionRegistry (self-contained)management.setEnabled(id, enabled),management.uninstall(id)— Modify ExtensionRegistry + host UI updatemanagement.onInstalled,management.onUninstalled,management.onEnabled,management.onDisabled— Emitted during extension lifecycle operations
chrome.proxy
Section titled “chrome.proxy”Host-bound. Proxy configuration delegates to the proxy layer (BareMux/Reflux transport settings).
chrome.identity
Section titled “chrome.identity”Partially implementable:
identity.getAuthToken()— Could integrate with an OAuth flow if the host provides itidentity.getProfileUserInfo()— Return profile info if the host provides itidentity.launchWebAuthFlow(details)— Open a popup/tab for OAuth redirect flow- Most identity methods will be stubs initially
chrome.declarativeContent
Section titled “chrome.declarativeContent”Self-contained using DeclarativeEvent base class. Evaluates page state rules against content script reports.
Tier 4: Niche
Section titled “Tier 4: Niche”Most Tier 4 APIs will remain as stubs initially. Implementation priority will be driven by specific extension compatibility requirements.
| Namespace | Feasibility | Notes |
|---|---|---|
tts / ttsEngine | Feasible | Use Web Speech API (speechSynthesis) |
offscreen | Feasible | Create hidden iframe (similar to MV2 background) |
sidePanel | Feasible | Host renders iframe in sidebar UI |
tabGroups | Feasible if host supports | Delegate to host’s tab group management |
idle | Feasible | Track user activity (mouse/keyboard events) |
power | Not feasible | Cannot prevent system sleep from web context |
debugger | Not feasible | Cannot attach Chrome DevTools Protocol from web context |
tabCapture / desktopCapture | Partial | Could use getDisplayMedia() but with limitations |
pageCapture | Not feasible | Cannot save MHTML from web context |
dns | Not feasible | Cannot do DNS lookups from web context |
topSites | Feasible | Return from host’s “most visited” data |
fontSettings | Partial | Can read some font info but cannot change browser fonts |
privacy | Partial | Some settings mappable to proxy config |
contentSettings | Partial | Some settings manageable via content script injection |
search | Feasible | Delegate to host’s search/omnibox |
readingList | Feasible if host supports | Delegate to host’s reading list |
browsingData | Partial | Can clear IndexedDB/cookies/cache within proxy scope |
printerProvider | Not feasible | No printing API access in web context |
Cross-Cutting Concerns
Section titled “Cross-Cutting Concerns”Promise/Callback Duality
Section titled “Promise/Callback Duality”Every method that accepts a callback must also return a Promise (for MV3 compatibility). The binding system wrapper handles this automatically (see API-BINDING.md).
chrome.runtime.lastError
Section titled “chrome.runtime.lastError”Must be set before any callback invocation where the operation failed. Must be cleared after the callback returns. Must be per-context (not global).
Extension-Scoped State
Section titled “Extension-Scoped State”All API state (alarms, storage, action state, context menus, etc.) is scoped to the extension. Two extensions cannot see each other’s alarms, storage keys, or context menus.
Tab ID Consistency
Section titled “Tab ID Consistency”Tab IDs must be consistent across all API calls and events. The SharedWorker’s Tab Registry is the source of truth. The host application must report tab creation/destruction to keep the registry in sync.