RemixletDocs

Sandbox APIs

dom.* API reference

Every DOM call available to remixlet code, including signatures, return values, limits, and page-agent security checks.

dom is the sandbox API for reading and changing the current page. Every asynchronous call sends an operation to the page agent. The page agent owns the real document, resolves handles, applies limits, checks writes, and returns JSON data. No dom.* call needs a manifest capability.

Security model#

BoundaryEnforcement
Document accessRemixlet code has no document. It receives opaque Handle objects instead of DOM nodes.
ReadsThe page agent permits reads, caps their size, and returns serializable snapshots. Read calls do not perform network or extension actions.
WritesThe page agent checks tags, attributes, CSS, HTML, URLs, clicks, ownership marks, and tree size before touching the page.
IsolationEach remixlet has its own handle table, listeners, and observers. Dropping its sandbox removes all three.
Navigation and loadingDirect navigation, script injection, frame creation, form creation, and arbitrary resource URLs are blocked. Links, images, and clicks use the URL policy under Write security.
FailuresPolicy denials reject with refused: <reason> and log each distinct reason once. Invalid arguments and stale handles also reject.

Root handles#

PropertyTypeDescription
dom.documentHandleQuery root for the current document.
dom.bodyHandleHandle for document.body.
dom.headHandleHandle for document.head.
dom.windowWindowHandleEvent-only handle for approved window events.

dom.document, dom.body, and dom.head remain valid for the sandbox lifetime. dom.window supports only on and off.

Queries#

CallReturnsDescription and limits
await dom.query(selector)Handle | nullReturns the first document match. The selector may contain at most 4096 characters.
await dom.queryAll(selector, options?)Handle[]Returns document matches. options.limit lowers the default maximum of 500. options.info: true fills each handle's snapshot in the same round trip.
await dom.waitFor(selector, options?)Handle | nullReturns the first current or future match. Rechecks after page mutations, at most once per 50 ms. timeoutMs defaults to 10000 and may not exceed 120000.

Invalid selectors reject with invalid selector.

Creating page content#

dom.create#

js
const handle = await dom.create(tag, {
  text,
  html,
  attrs,
  classes,
  style,
  children,
});

Creates a detached element and returns its Handle. children accepts strings and nested { tag, ...options } objects. The page agent validates the complete tree before creating any node. A tree may contain at most 1000 nodes and may be at most 32 levels deep. Text and HTML fields may contain at most 256 KB each.

The returned tree is stamped with data-rmx-owner. Refused tags, attributes, styles, or URLs reject the whole call.

dom.clone#

js
const handle = await dom.clone(selector, { text, strip, keepIds });

Equivalent to dom.query(selector) followed by handle.clone(options). Returns null when the selector has no match.

The clone is detached and deep. Event listeners are not copied. strip removes matches before text edits the clone. A string text replaces all content; a { selector: text } map updates the first match for each selector. keepIds preserves id and for, which are removed by default. The page agent sanitizes the clone with the same rules as dom.create and logs removed content.

dom.addStyle#

js
const style = await dom.addStyle(css);

Adds an owned <style> element to the page head and returns its handle. CSS may contain at most 256 KB. The page agent rejects CSS that can load a resource or execute legacy behavior. See CSS.

Page facts and scrolling#

CallReturnsDescription
await dom.location(){ href, origin, pathname, search, hash, title }Reads the current values from the page agent.
dom.viewport(){ width, height, scrollX, scrollY }Returns the latest pushed viewport snapshot without a round trip.
await dom.viewportNow(){ width, height, scrollX, scrollY }Reads the current viewport from the page agent.
await dom.scrollTo(x?, y?, behavior?)voidSets the page window's scroll position. Also accepts { left, top, behavior }.
await dom.scrollBy(x?, y?, behavior?)voidMoves the page window's scroll position. Also accepts { left, top, behavior }.

behavior accepts auto, smooth, or instant. The runtime converts instant to auto before sending the call.

Mutation observation#

dom.observe#

js
const stop = dom.observe(callback, {
  root,
  childList,
  subtree,
  attributes,
  characterData,
});

Registers a page-agent observer and returns a synchronous unsubscribe function. root defaults to dom.document. All four mutation flags default to true. The callback receives { deliveries }, where deliveries is the number of raw MutationObserver deliveries folded into the notice. It does not receive mutation records.

The first notice after a quiet period arrives immediately. Later deliveries within 100 ms are coalesced. Each observer accepts at most 40 raw deliveries per second. A fast refill caused by callback writes is held to one trailing notice per second and logged as a feedback loop. Sustained page-driven changes remain coalesced and produce one informational log entry.

Handle properties#

PropertyTypeDescription
handle.idstringOpaque id minted by the page agent.
handle.snapshotElementInfo | undefinedLast value returned by info() or populated by queryAll(..., { info: true }). It is not live.

The page agent keeps at most 20000 handles per sandbox. Call release() when a large batch is no longer needed. The sandbox permits at most 5000 unanswered DOM calls.

Handle queries and traversal#

CallReturnsDescription
await handle.query(selector)Handle | nullReturns the first descendant match.
await handle.queryAll(selector, options?)Handle[]Returns descendant matches with the same options and 500-handle maximum as dom.queryAll.
await handle.matches(selector)booleanTests the element against a selector.
await handle.closest(selector)Handle | nullReturns the nearest matching ancestor, including the element itself.
await handle.parent()Handle | nullReturns the parent element.
await handle.children()Handle[]Returns up to 500 child elements.
await handle.first()Handle | nullReturns the first child element.
await handle.last()Handle | nullReturns the last child element.
await handle.next()Handle | nullReturns the next element sibling.
await handle.prev()Handle | nullReturns the previous element sibling.
await handle.contains(other)booleanTests whether this handle contains another handle.
await handle.shadow()Handle | nullReturns an open shadow root as a query handle. Closed shadow roots return null.
await handle.isConnected()booleanReports whether the node is still in the document.

Handle reads#

CallReturnsDescription and limits
await handle.info()ElementInfoReturns { handle, tag, id, className, text, attrs, dataset, value?, checked?, rect, visible } and updates snapshot. text is trimmed to 2000 characters. Password values are replaced with an empty string.
await handle.text()stringReturns textContent, capped at 64 KB.
await handle.innerText()stringReturns rendered text, capped at 64 KB.
await handle.html()stringReturns innerHTML, capped at 64 KB. Password value attributes are replaced with an empty string.
await handle.attr(name)string | nullReturns an attribute. A password field's value attribute returns an empty string.
await handle.data(name)string | nullReads a dataset key. data("itemId") reads data-item-id.
await handle.value()stringReturns a form control value, or an empty string for other elements and password fields.
await handle.computed(prop)stringReturns one computed CSS value.
await handle.computed(props)Record<string, string>Returns up to 100 computed CSS values.
await handle.rect()RectReturns { x, y, width, height, top, left, bottom, right }.
await handle.visible()booleanReturns true for a non-zero laid-out element that is not display: none or visibility: hidden.

Handle content and attributes#

CallReturnsEffect and security
await handle.setText(text)voidSets textContent. Limited to 256 KB. Allowed on page and owned elements.
await handle.setHTML(html)voidReplaces children with inertly parsed, sanitized markup. Limited to 256 KB.
await handle.setAttr(name, value)voidSets an attribute. Page-owned elements accept only this remixlet's marks. URL attributes use the URL policy.
await handle.removeAttr(name)voidRemoves an attribute. Page-owned elements accept only this remixlet's marks.
await handle.setData(name, value)voidSets a dataset key through setAttr.
await handle.hide()voidSets hidden. Refused on page-owned elements because hidden is not an ownership mark.
await handle.show()voidRemoves hidden. Refused on page-owned elements unless the element is owned.
await handle.setDisabled(disabled)voidAdds or removes disabled. Refused on page-owned elements unless the element is owned.
await handle.addClass(...names)string[]Adds classes and returns the resulting class list. Page-owned elements accept only prefixed classes.
await handle.removeClass(...names)string[]Removes classes and returns the resulting class list. Page-owned elements accept only prefixed classes.
await handle.toggleClass(...names)string[]Toggles classes and returns the resulting class list. A final boolean forces add or remove.
await handle.style(props)voidSets inline properties. Empty values remove properties. Camel-case and kebab-case names are accepted. A trailing !important is preserved. CSS security rules apply.
await handle.setValue(value)voidSets a form value, or a checkbox/radio from a boolean, then dispatches input and change. Allowed on page and owned controls.

Handle placement and actions#

CallReturnsEffect and security
await handle.clone(options?)HandleReturns a detached, sanitized deep copy. See dom.clone.
await parent.append(child)voidAppends a handled node. Browser-invalid placements reject.
await parent.prepend(child)voidPrepends a handled node. Browser-invalid placements reject.
await reference.before(node)voidInserts a handled node before this node.
await reference.after(node)voidInserts a handled node after this node.
await handle.remove()voidRemoves the node. Allowed for page and owned elements.
await handle.click()voidDispatches the element's click behavior after checking links, labels, controls, and form actions against the URL policy.
await handle.focus()voidFocuses the element.
await handle.blur()voidBlurs the element.
await handle.scrollIntoView(block?)voidScrolls the element into view. block accepts start, center, end, or nearest.
await handle.scrollTo(x?, y?, behavior?)voidSets the element's scroll position.
await handle.scrollBy(x?, y?, behavior?)voidMoves the element's scroll position.
await handle.release()voidRemoves the id from the sandbox's handle table.

Events#

handle.on#

js
const stop = handle.on(type, callback, {
  selector,
  preventDefault,
  stopPropagation,
  once,
  capture,
  passive,
});

Registers an event listener and returns a synchronous unsubscribe function. selector delegates from the current handle and makes the matched descendant the event's target.

The callback receives a plain object with type, target, currentTarget, key, code, modifier keys, button, clientX, clientY, form value and checked, and defaultPrevented. target and currentTarget are handles. Fields that do not apply to the event are omitted. Events from password fields omit key and code and replace value with an empty string.

Callbacks run in the sandbox after a message round trip. To cancel the page event synchronously, set preventDefault or stopPropagation in the registration options. passive: true overrides preventDefault.

If the page removes a listened node, the page agent removes its listeners and marks the handle stale. Every later call except isConnected() and release() rejects. A listener registered during rmx.keep(...).apply() causes that keep to reapply once so it can bind to the replacement node.

dom.window.on and dom.window.off#

js
const stop = dom.window.on(type, callback, options);
dom.window.off(type, callback);

Supports scroll, resize, keydown, keyup, focus, blur, and visibilitychange. Re-registering the same type and callback returns the existing subscription. The sandbox's window.addEventListener and window.removeEventListener facades route to these methods.

Write security#

Ownership marks#

The page agent stamps elements created, cloned, or inserted through sanitized HTML with data-rmx-owner. Remixlet code cannot set or remove that attribute.

Owned elements accept ordinary attributes and classes. Page-owned elements accept only this remixlet's marks, written with the id slot:

KindYou writeThe page carries
Attributedata-rmx--mixdata-rmx-<id>--mix
Classrmx--onrmx-<id>--on

The double hyphen is the slot for the remixlet id. The page agent fills it in every name, selector, attribute value, markup string, and stylesheet the remixlet hands it, the extension fills it in style.css on injection, and reads (info(), html(), class lists) show the slot form again, so a name is spelled one way everywhere. Names that spell the id in full (data-${rmx.prefix}-<name>, ${rmx.prefix}-<name>) still pass for code written before the slot.

Content writes, values, inline styles, placement, removal, and approved clicks do not use the mark rule.

Tags and attributes#

Creation rejects unlisted tags and tags that can load, execute, submit, or embed content. This includes form, iframe, frame, object, embed, script, base, meta, link, style, template, media source tags, math, and noscript.

Attribute writes reject event handlers and attributes that can load, execute, submit, or redirect. This includes on*, action, srcdoc, srcset, formaction, ping, poster, target, http-equiv, style, is, form, rel, and download. href on links and src on images use the URL policy instead.

setHTML, create({ html }), and clone parse markup in an inert document. The sanitizer removes comments, refused attributes, and refused tags with their subtrees before importing the result into the page.

URLs#

The page agent resolves link href and image src values to absolute URLs. It permits:

  • the current page origin;
  • a host covered by the remixlet's matches;
  • a host covered by an approved fetch: capability;
  • an exact URL the page already loads;
  • data:image URLs.

It rejects javascript:, blob:, and hosts granted only through network:observe:. Stored attributes contain the resolved absolute URL.

click() applies the same rule to the element, an enclosing link, the control activated through a label, and a submitted form or formaction target.

CSS#

Inline styles and dom.addStyle reject CSS containing url(, image-set(, @import, src(, expression(, or behavior: in any spelling accepted by CSS.

Sandbox browser facades#

Remixlet files have no live browser globals. The sandbox provides read-only facades:

FacadeAvailable members
locationhref, origin, protocol, host, hostname, port, pathname, search, hash, toString(), valueOf(), toJSON()
navigatorlanguage, languages, userAgent, platform, hardwareConcurrency, onLine
windowTimers, requestAnimationFrame, queueMicrotask, structuredClone, JavaScript built-ins, console, the facades above, viewport fields, approved event methods, scrolling, dom, and rmx

Writes to these facades reject. location.assign, location.replace, and location.reload reject. Page-shaped globals such as document, fetch, XMLHttpRequest, MutationObserver, localStorage, WebSocket, Image, Worker, alert, and open are blocked with an error that names the corresponding sandbox API where one exists.

Limits#

ResourceLimit
Handles per sandbox20000
Calls in flight5000
Selector4096 characters
queryAll and children500 handles
waitFor10 seconds by default, 120 seconds maximum
info().text2000 characters
text(), innerText(), html()64 KB
Text, HTML, attribute, and stylesheet writes256 KB
Created or cloned tree1000 nodes, 32 levels
computed()100 properties
Observer40 raw deliveries per second, notices coalesced over 100 ms

dom.* shares the rmx.* bridge compatibility version. The extension does not run a remixlet whose stored version falls outside its supported range. See rmx.* API compatibility.