RemixletDocs

Sandbox APIs

rmx.* API reference

Every rmx call available to remixlet code, including signatures, capabilities, return values, limits, and worker security checks.

rmx is the sandbox API for extension services and runtime coordination. Calls that use browser privileges cross the sandbox host and run in the extension worker. Page access is provided separately by the dom.* API reference.

Security model#

Every worker call carries the remixlet id and a secret bridge token attached by the sandbox host. The token never enters remixlet code or the page. Before a call runs, the worker verifies:

  1. the bridge token belongs to the remixlet;
  2. the call came from a page covered by the remixlet's matches;
  3. neither the remixlet nor its site is paused;
  4. the manifest declares the required capability;
  5. the user approved that exact capability.

rmx.log is the only worker lane that bypasses the page and pause checks, so a paused or off-site caller can still record a diagnostic. It still requires a valid bridge token.

Capability denials reject with an Error and write the same message to the remixlet log. Argument validation failures reject without performing the operation.

Capability index#

APIRequired capabilityAuthority
rmx.prefixNoneThe filled mark prefix, for code written before the id slot.
rmx.keepNoneCoordinates sandbox callbacks with page mutation notices.
rmx.relay.onSee entrySubscribes to low-level relay messages already filtered for this sandbox.
rmx.navigation.onChangeNoneReports current-page URL changes.
rmx.log.warn, rmx.log.errorNoneWrites bounded diagnostic entries.
rmx.storage.*storageReads and writes this remixlet's extension-owned JSON store.
rmx.fetchfetch:<host-pattern>Sends credentialed HTTP requests to approved hosts.
rmx.network.onResponsenetwork:observe:<host-pattern>Reads textual responses the page receives from approved hosts.
rmx.notifications.*notificationsCreates and clears owned text notifications.
rmx.clipboard.writeTextclipboardWrites text to the clipboard.
rmx.menu.registermenuRegisters a command for the current tab and document.
rmx.schedule.*scheduleStores fixed scheduled actions and site-open hooks.

Runtime properties and callbacks#

rmx.prefix#

js
const prefix = rmx.prefix;

Read-only string in the form rmx-<remixlet-id>. Attributes and classes added to page-owned elements carry this prefix on the page. New code does not read it: write marks as rmx--<name> (data-rmx--mix, rmx--on) and the page agent fills the id in, so the same spelling works in code, in style.css, and in verification selectors. Code written before the id slot interpolates rmx.prefix and still runs. See ownership marks.

rmx.keep#

js
const stop = rmx.keep(label, {
  when,
  ensure,
  apply,
});

Registers a condition that the runtime maintains and returns a synchronous unsubscribe function.

FieldTypeBehavior
labelstringRequired, non-empty, trimmed to 120 characters for logging.
when() => value | Promise<value>Optional. A falsey result idles this keep.
ensure() => value | Promise<value>Required. A truthy result means no write is needed.
apply() => void | Promise<void>Required. Runs when when passes and ensure is false.

The runtime evaluates keeps at registration, after coalesced page mutations, after URL changes, and when the page removes a node listened to during the latest apply. Evaluations are serialized. A notice received during evaluation schedules one more pass.

After apply, the runtime runs ensure again. Three consecutive thrown callbacks or applies that leave ensure false halt the keep and write an error. At 25 successful reapplies within one minute, the runtime writes one informational entry and continues.

rmx.navigation.onChange#

js
const stop = rmx.navigation.onChange(({ url, previousUrl }) => {});

Registers a callback and returns a synchronous unsubscribe function. The callback may be asynchronous. It runs only when the runtime's last known URL changes.

The page agent detects Navigation API changes, popstate, and hashchange. The extension worker and page relay provide fallback hints. Each hint causes a comparison against the last known URL, so forged or duplicate events cannot invent a URL change.

rmx.relay.on#

js
const stop = rmx.relay.on(topic, (data) => {});

Registers a low-level relay callback and returns a synchronous unsubscribe function. The built-in relay emits network:response and navigation:change. Sequenced records buffered before registration are replayed oldest first. Unsequenced navigation hints are live only.

The page agent attaches a sandbox to the relay only when that remixlet has an approved network:observe:<host-pattern> capability. It forwards response records only when their URL matches one of the approved patterns. Relay payloads are untrusted page data and carry no bridge token or extension authority. Use rmx.network.onResponse and rmx.navigation.onChange for their typed filtering.

rmx.log.warn and rmx.log.error#

js
rmx.log.warn(message);
rmx.log.error(message);

Writes a string to the per-remixlet log. Messages are capped at 500 characters. Each remixlet keeps at most 300 entries and 256 KB. All remixlet logs share a 3 MB cap. Eviction removes old log and info entries before warn and error entries.

The sandbox also captures console.log, console.info, console.warn, console.error, uncaught exceptions, unhandled rejections, bridge denials, and page-agent refusals. Console capture permits 100 lines per page load and 500 characters per line.

Storage#

Requires storage.

CallReturnsDescription
await rmx.storage.get(key)JSON value or undefinedReads one key.
await rmx.storage.set(key, value)voidStores a JSON-compatible value.
await rmx.storage.delete(key)voidRemoves one key.
rmx.storage.watch(key, callback)() => voidLong-polls for store revisions and calls back with the watched key's current value.

The worker namespaces storage by remixlet id. All tabs and matched hosts for the same remixlet share it. Pages cannot access it. Values may contain strings, numbers, booleans, null, arrays, and plain objects.

watch reacts to any revision of the remixlet's store, including a change to another key. The callback runs only when the revision changes. If the service worker stops, the sandbox retries the long-poll after one second. The unsubscribe function stops future polls.

Deleting a remixlet removes its store. Serialized writes cannot recreate storage after deletion begins.

Fetch#

Requires an approved fetch:host capability that covers the initial URL and every redirect.

js
const response = await rmx.fetch(url, {
  method,
  headers,
  body,
  timeoutMs,
});

The worker sends the request with the user's cookies for the target host. Page CORS rules do not apply.

InputRule
urlAbsolute HTTP or HTTPS URL without embedded credentials.
methodGET, POST, PUT, PATCH, DELETE, HEAD, or OPTIONS.
headersAt most 32 KB. Browser-owned headers including cookie, origin, referer, user-agent, host, and content-length are refused.
bodyString, at most 1 MB. Refused for GET and HEAD.
timeoutMsDefaults to 15000. Maximum 30000.
redirectsMaximum five. Every target must match an approved fetch: pattern. Cross-origin redirects drop authorization.
response bodyText, at most 2 MB. A larger response rejects.

An exact pattern covers one host. fetch:api.example.com does not cover www.example.com. fetch:*.example.com covers the apex and all subdomains.

The result is a frozen RmxResponse, not a browser Response:

MemberType
urlFinal URL string
statusNumber
statusTextString
oktrue for status 200 through 299
redirectedBoolean
headersHeaders
bodyResponse text
await text()Resolves to the response text
await json()Parses the response text as JSON

The worker removes set-cookie and set-cookie2 from returned headers.

Network response observation#

Requires an approved network:observe:host capability. The exact and wildcard host grammar matches fetch:.

js
const stop = rmx.network.onResponse(hostPattern, (entry) => {});

Registers a callback and returns a synchronous unsubscribe function. hostPattern may be an exact host, a *. host, an empty string for all approved hosts, or a URL substring that narrows the approved records.

Entry fieldTypeDescription
urlstringAbsolute request URL.
methodstringUppercase request method.
statusnumberResponse status.
contentTypestring | nullResponse content type.
bodystringTextual response body, capped at 512 KB.
truncatedbooleanWhether the body exceeded the per-response cap.
seqnumberIncreasing sequence number for this page.

The extension relay wraps page fetch and XMLHttpRequest at document start. It reads a clone and leaves the page's response unchanged. It records text, JSON, XML, and JavaScript response types. The ring holds at most 250 entries and 6 MB, evicting the oldest first. Buffered records are replayed before live records and deduplicated by seq.

The page agent receives approved host patterns from stored grants, not from the callback argument. It filters every record before sending it to the sandbox. A network:observe: grant does not permit rmx.fetch or admit new URLs through dom.*.

Notifications#

Requires notifications.

CallReturnsDescription and limits
await rmx.notifications.show(title, message)Notification idCreates a text notification. Title maximum: 120 characters. Message maximum: 1000 characters. Both must be non-empty strings.
await rmx.notifications.clear(id)booleanClears a notification created by this remixlet.

Notification ids are unguessable handles. The worker verifies ownership before clearing one. The API has no icon, button, or URL fields.

Clipboard#

Requires clipboard.

js
await rmx.clipboard.writeText(text);

Writes a string of at most 1 MB of UTF-8 to the clipboard. The API has no clipboard read operation.

Requires menu.

js
const registrationId = await rmx.menu.register(id, label, callback);

Registers a command in the Remixlet toolbar popup for the current tab and document. id may contain at most 128 characters. label may contain at most 160. Registering the same id again replaces the earlier command.

The worker stores command metadata and pending invocation ids. The callback remains inside the sandbox. A long-poll delivers clicks to the current document and acknowledges them after the callback settles. The command disappears when the document, tab, remixlet, grant, or matching active site no longer qualifies.

Schedules#

Requires schedule.

Timed schedules#

CallReturnsDescription
await rmx.schedule.register(definition)Stored scheduleRegisters { id, at, action } or { id, every, action }.
await rmx.schedule.at(id, when, action)Stored scheduleRegisters a one-shot schedule. when is epoch milliseconds or an ISO date string.
await rmx.schedule.every(id, minutes, action)Stored scheduleRegisters a repeating schedule. Minimum interval is 0.5 minutes on Chrome and 1 minute on Firefox.
await rmx.schedule.remove(id)booleanRemoves an owned timed schedule.
await rmx.schedule.list(){ timed, onSiteOpen }Lists timed definitions and registered site-open hook names.
await rmx.schedule.clear()voidRemoves timed schedules, site-open hooks, queued hooks, session markers, and browser alarms.

Ids and hook names may contain letters, digits, _, ., :, and -, must start with a letter or digit, and may contain at most 64 characters.

The worker stores data, not remixlet code. Each schedule uses one fixed action:

ActionEffect and security
{ type: "notify", title, message }Shows an owned text notification with the notification length limits.
{ type: "open", url }Focuses an existing exact-URL tab or opens one. The URL must be absolute HTTP or HTTPS and covered by the remixlet's matches.
{ type: "hook", name }Queues a hook for the next matching page sandbox.

register also accepts name as an alias for id, plus one shorthand action field: notify: { title, message }, open: url, open: { url }, or hook: name. Supplying action together with a shorthand action rejects as ambiguous.

Timers can fire without an open matching tab. One-shot schedules are removed after firing. Missed timers run once after browser startup; repeating schedules then move to their next future time. Pausing the remixlet stops its schedules until it resumes.

Site-open and hook delivery#

CallReturnsDescription
await rmx.schedule.onSiteOpen(name)nameRegisters a hook once per browser session when a matching site first opens.
await rmx.schedule.onSiteOpen(name, callback)() => voidRegisters the hook and a callback, then delivers matching queued hooks.
await rmx.schedule.removeOnSiteOpen(name)booleanRemoves the registration and queued instances of that hook.
await rmx.schedule.consumeHooks()string[]Atomically consumes all queued hook names for this remixlet. Only a matching page may consume them.
rmx.schedule.onHook(name, callback)() => voidRegisters a callback and delivers queued instances of that name.

Each hook callback receives { name }. A callback registered with onHook is delivered each matching queued instance once for that callback. The returned function unsubscribes it.

Compatibility#

dom.* and rmx.* share a versioned, add-only contract. Within one bridge version, methods and fields may be added but existing behavior is not renamed, removed, or changed.

The extension stamps the current bridge version into remixlet.json under builtWith.bridge. It parks a stored remixlet as needing repair when the stamp falls outside the installed extension's supported range. The current bridge version is 2.