Skip to content

API Implementation Guide

This document provides detailed implementation notes for each Chrome API namespace, organized by priority tier.

  • [ ] 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)”

Status: [~] (events wired up, methods are stubs)

The most important namespace. Provides extension identity, messaging, lifecycle events, and manifest access.

Method / PropertyImplementation StrategyNeeds Binding?
runtime.idSet from extension manifest/CRX ID at context creationNo
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.onMessageDispatch from SharedWorker messages (Layer 3)No
runtime.onConnectDispatch from SharedWorker port connections (Layer 3)No
runtime.onInstalledFired during extension load sequenceNo
runtime.onStartupFired on SharedWorker init (simulates browser startup)No
runtime.reload()Destroy + recreate extension contextsYes (host does reload)
runtime.openOptionsPage()Open options page tabYes (host opens tab)
runtime.getPlatformInfo()Return hardcoded platform info (navigator.userAgent parsing)No
runtime.setUninstallURL(url)Store URL, open on uninstallNo (stored in registry)
runtime.getBackgroundPage()MV2 only: return background iframe’s windowNo
runtime.getContexts()MV3 only: query context registry in SharedWorkerNo
runtime.lastErrorSet before callback invocation on error, cleared afterNo
runtime.onSuspendMV2 event pages / MV3 worker lifecycleNo
runtime.onSuspendCanceledMV2 event pagesNo
runtime.onUpdateAvailableFired by update checkerNo
runtime.connectNative()Not supportable in web context — throw meaningful errorNo
runtime.sendNativeMessage()Not supportable — throw meaningful errorNo

MV2-specific methods (in MV2 package only):

  • runtime.getBackgroundPage() — Returns the background iframe’s contentWindow
  • runtime.getVersion() — Returns manifest version string

MV3-specific methods (in MV3 package only):

  • runtime.getContexts(filter) — Queries the SharedWorker’s context registry

Implementation notes:

  • runtime.id must be set correctly for each extension instance
  • runtime.lastError is context-local (each context has its own)
  • runtime.onInstalled fires with { reason: 'install' | 'update' | 'chrome_update' }. For Helium, only ‘install’ and ‘update’ are relevant

Status: [~] (StorageArea base class is fully implemented with in-memory store)

AreaQuotaImplementation
storage.local10MB (QUOTA_BYTES: 10485760)IndexedDB per-extension + WAL
storage.sync100KB total, 8KB/item, 512 items maxIndexedDB + WAL (no actual sync — there’s no Chrome account)
storage.managedRead-onlyIndexedDB, populated by host app configuration
storage.session10MB (MV3 only)In-memory Map + optional durable-flush to IndexedDB on worker termination

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.

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;
}
}
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);
}
}
}

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}`);
}
}

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);
}
}
}
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.

Status: [ ] (all stubs)

The second most important namespace. Almost all extensions query or manipulate tabs.

MethodBinding RequiredNotes
tabs.create(props)YesHost creates tab, returns TabInfo
tabs.remove(tabIds)YesHost closes tab(s)
tabs.update(tabId, props)YesHost updates tab URL, pinned state, etc.
tabs.query(queryInfo)YesHost returns matching tabs. Must filter by all query params: active, pinned, url, title, windowId, currentWindow, status, lastFocusedWindow, groupId
tabs.get(tabId)YesHost returns tab info
tabs.getCurrent()PartialReturns info for the tab this context is running in (looked up from context registry)
tabs.move(tabIds, props)YesHost moves tab(s)
tabs.reload(tabId?, props?)YesHost reloads tab
tabs.duplicate(tabId)YesHost duplicates tab
tabs.goBack(tabId?)YesHost navigates back
tabs.goForward(tabId?)YesHost navigates forward
tabs.group(options)YesHost groups tab(s)
tabs.ungroup(tabIds)YesHost ungroups tab(s)
tabs.highlight(info)YesHost highlights tabs
tabs.discard(tabId?)YesHost discards tab (frees memory)
tabs.captureVisibleTab(windowId?, opts?)YesHost captures screenshot (returns data URL)
tabs.detectLanguage(tabId?)YesHost detects page language
tabs.sendMessage(tabId, msg, opts?, cb?)NoRoutes through SharedWorker (Layer 3)
tabs.connect(tabId, connectInfo?)NoCreates port through SharedWorker (Layer 3)
tabs.getZoom(tabId?)YesHost returns zoom level
tabs.setZoom(tabId?, factor)YesHost sets zoom level
tabs.getZoomSettings(tabId?)YesHost returns zoom settings
tabs.setZoomSettings(tabId?, settings)YesHost 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 for query({windowId}).
  • tabs.getSelected(windowId?, cb?) — Deprecated, alias for query({active: true, windowId}).
  • tabs.sendRequest(tabId, request, cb?) — Deprecated, alias for sendMessage.

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.

MethodBinding RequiredNotes
action.setIcon(details)YesHost updates icon in toolbar
action.setBadgeText(details)YesHost updates badge text
action.setBadgeBackgroundColor(details)YesHost updates badge color
action.setBadgeTextColor(details)YesHost updates badge text color
action.setTitle(details)YesHost updates tooltip
action.setPopup(details)HybridStore popup path; host uses it when icon is clicked
action.openPopup(opts?)YesHost opens popup programmatically
action.enable(tabId?)HybridTrack enabled state; host reflects in UI
action.disable(tabId?)HybridTrack disabled state; host grays out icon
action.getBadgeText(details)NoReturn from internal state
action.getTitle(details)NoReturn from internal state
action.getPopup(details)NoReturn from internal state
action.getBadgeBackgroundColor(details)NoReturn from internal state
action.getBadgeTextColor(details)NoReturn from internal state
action.isEnabled(tabId?)NoReturn from internal state
action.getUserSettings()NoReturn {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.

Status: [ ] (all stubs)

Self-contained timer system. No host binding needed.

MethodImplementation
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.onAlarmFire when alarm triggers

Implementation details:

  • Minimum period: 1 minute (Chrome enforces this; delayInMinutes minimum 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);
}
}

Status: [ ] (all stubs)

Internationalization. Self-contained, reads _locales/ files from the extension’s virtual filesystem.

MethodImplementation
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:

  1. Exact locale match (e.g., en_US)
  2. Language match (e.g., en)
  3. Default locale (from manifest.default_locale)
  4. 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"
}
}
}
}

Status: [ ] (all stubs)

Runtime permission management. Self-contained.

MethodImplementation
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.onAddedFire when permissions are added
permissions.onRemovedFire 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_permissions can be requested
  • permissions.remove() can only remove optional permissions, not required ones
MethodBinding RequiredNotes
scripting.executeScript(injection)HybridHost identifies tab, Helium injects via SharedWorker → bootstrap
scripting.insertCSS(injection)HybridHost identifies tab, Helium injects CSS via SharedWorker → bootstrap
scripting.removeCSS(injection)HybridHost identifies tab, Helium removes CSS via SharedWorker → bootstrap
scripting.registerContentScripts(scripts)NoStore in registry, push to Reflux injection plugin
scripting.unregisterContentScripts(filter?)NoRemove from registry, push update to Reflux
scripting.getRegisteredContentScripts(filter?)NoQuery registry
scripting.updateContentScripts(scripts)NoUpdate 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).

EventTrigger
onBeforeNavigateHost app reports tab about to navigate
onCommittedHost app reports navigation committed
onDOMContentLoadedContent script reports DOMContentLoaded
onCompletedContent script reports load complete
onErrorOccurredHost app reports navigation error
onCreatedNavigationTargetNew tab created from link click in proxied page
onReferenceFragmentUpdatedHash change detected
onTabReplacedTab replaced (prerender)
onHistoryStateUpdatedpushState/replaceState detected
MethodImplementation
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.onChangedFire 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.

MethodImplementation
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.onClickedFire 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.

MethodBinding RequiredNotes
notifications.create(id?, opts)YesHost shows notification
notifications.update(id, opts)YesHost updates notification
notifications.clear(id)YesHost dismisses notification
notifications.getAll()NoReturn from internal state
notifications.getPermissionLevel()NoReturn “granted” (always, since this is our controlled environment)
notifications.onClickedNoHost emits on user click
notifications.onClosedNoHost emits on dismiss
notifications.onButtonClickedNoHost emits on button click
MethodBinding RequiredNotes
windows.create(createData?)YesHost creates window
windows.remove(windowId)YesHost closes window
windows.update(windowId, info)YesHost updates window
windows.get(windowId, info?)YesHost returns window info
windows.getAll(info?)YesHost returns all windows
windows.getCurrent(info?)YesHost returns current window
windows.getLastFocused(info?)YesHost returns last focused
windows.onCreatedNoHost emits
windows.onRemovedNoHost emits
windows.onFocusChangedNoHost emits
windows.onBoundsChangedNoHost 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.

EventMV2 BehaviorMV3 Behavior
onBeforeRequestCan block/redirect (with webRequestBlocking)Observe only
onBeforeSendHeadersCan modify headers (with webRequestBlocking)Observe only
onSendHeadersObserve onlyObserve only
onHeadersReceivedCan modify response headersObserve only
onAuthRequiredCan provide credentialsObserve only
onResponseStartedObserve onlyObserve only
onBeforeRedirectObserve onlyObserve only
onCompletedObserve onlyObserve only
onErrorOccurredObserve onlyObserve 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.

MethodImplementation
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.onRuleMatchedDebugFire 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.

Fully host-bound. All CRUD operations delegate to DaydreamX’s bookmark manager. Events are emitted by the host.

Fully host-bound. All query/modification operations delegate to DaydreamX’s history system.

Fully host-bound. Download operations delegate to DaydreamX’s download manager.

Fully host-bound. Session restore operations delegate to DaydreamX’s session manager.

Hybrid:

  • management.getAll(), management.get(id), management.getSelf() — Read from ExtensionRegistry (self-contained)
  • management.setEnabled(id, enabled), management.uninstall(id) — Modify ExtensionRegistry + host UI update
  • management.onInstalled, management.onUninstalled, management.onEnabled, management.onDisabled — Emitted during extension lifecycle operations

Host-bound. Proxy configuration delegates to the proxy layer (BareMux/Reflux transport settings).

Partially implementable:

  • identity.getAuthToken() — Could integrate with an OAuth flow if the host provides it
  • identity.getProfileUserInfo() — Return profile info if the host provides it
  • identity.launchWebAuthFlow(details) — Open a popup/tab for OAuth redirect flow
  • Most identity methods will be stubs initially

Self-contained using DeclarativeEvent base class. Evaluates page state rules against content script reports.

Most Tier 4 APIs will remain as stubs initially. Implementation priority will be driven by specific extension compatibility requirements.

NamespaceFeasibilityNotes
tts / ttsEngineFeasibleUse Web Speech API (speechSynthesis)
offscreenFeasibleCreate hidden iframe (similar to MV2 background)
sidePanelFeasibleHost renders iframe in sidebar UI
tabGroupsFeasible if host supportsDelegate to host’s tab group management
idleFeasibleTrack user activity (mouse/keyboard events)
powerNot feasibleCannot prevent system sleep from web context
debuggerNot feasibleCannot attach Chrome DevTools Protocol from web context
tabCapture / desktopCapturePartialCould use getDisplayMedia() but with limitations
pageCaptureNot feasibleCannot save MHTML from web context
dnsNot feasibleCannot do DNS lookups from web context
topSitesFeasibleReturn from host’s “most visited” data
fontSettingsPartialCan read some font info but cannot change browser fonts
privacyPartialSome settings mappable to proxy config
contentSettingsPartialSome settings manageable via content script injection
searchFeasibleDelegate to host’s search/omnibox
readingListFeasible if host supportsDelegate to host’s reading list
browsingDataPartialCan clear IndexedDB/cookies/cache within proxy scope
printerProviderNot feasibleNo printing API access in web context

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).

Must be set before any callback invocation where the operation failed. Must be cleared after the callback returns. Must be per-context (not global).

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 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.