Controlled Frame API

Draft Community Group Report,

This version:
https://wicg.github.io/controlled-frame/
Issue Tracking:
GitHub
Editors:
(Google LLC)
(Google LLC)

Abstract

This document defines an API for embedding arbitrary web content only within the context of an Isolated Web Application (IWA). The embedded content is a new top-level browsing context within and controlled by the embedder.

Status of this document

This specification was published by the Web Platform Incubator Community Group. It is not a W3C Standard nor is it on the W3C Standards Track. Please note that under the W3C Community Contributor License Agreement (CLA) there is a limited opt-out and other conditions apply. Learn more about W3C Community and Business Groups.

1. Introduction

This specification describes a content embedding API that satisfies some critical use cases for IWAs that iframe does not support. This embedding environment should allow embedding all content without express permission from the embedded site, including content which iframe cannot embed, and provide embedding sites more control over that embedded content.

Since this is a particularly powerful API, its use and availability makes an app a target of various types of hacking. As a result, this API is limited to use in Isolated Web Applications (IWAs) which have addtional safeguards in place to protect users and developers. IWAs are not a normal web application and can exist only at a special 'isolated-app:' scheme. This means by design that this API will not be available to normal web pages.

Note: This API is not intended to be a replacement or substitute for iframe. All iframe use cases are still valid and should continue to use iframe, including IWAs where possible.

2. The Fenced Frame specification

For convenience, the Controlled Frame specification assumes that the Fenced Frame specification is in place. There are concepts introduced in the Fenced Frame specification, such as nested top-level traversibles, that are broadly useful to refer to in the context of Controlled Frame.

The Fenced Frame specification achieves defining these concepts via monkey patching some specifications, such as HTML. We will also require monkey patching specifications for some parts of this Controlled Frame specification.

3. The controlledframe element

Contexts in which this element can be used:
Where embedded content is expected.
Content model:
Nothing.
Content attributes:
Global attributes
src — Content source URL to embed
partition — Partition name to hold data related to this content
Properties
contentWindow — Embedded content window DOM accessor
contextMenus — Context menus accessor
request — WebRequest accessor
Accessibility considerations:

Screen readers should be able to traverse into the embedded content similar to how a reader can traverse into iframes and other related embedded content.

DOM interface:
[Exposed=Window, IsolatedContext]
interface HTMLControlledFrameElement : HTMLElement {
    [HTMLConstructor] constructor();

    [CEReactions] attribute USVString src;
    attribute DOMString partition;

    readonly attribute WindowProxy? contentWindow;
    readonly attribute ContextMenus contextMenus;
    readonly attribute WebRequest request;

    // Navigation methods.
    Promise<undefined> back();
    boolean canGoBack();
    boolean canGoForward();
    Promise<undefined> forward();
    Promise<undefined> go(long relativeIndex);
    undefined reload();
    undefined stop();

    // Scripting methods.
    Promise<undefined> addContentScripts(sequence<ContentScriptDetails> contentScriptList);
    Promise<any> executeScript(optional InjectDetails details = {});
    Promise<undefined> insertCSS(optional InjectDetails details = {});
    Promise<undefined> removeContentScripts(sequence<DOMString>? scriptNameList);

    // Configuration methods.
    Promise<undefined> clearData(
      optional ClearDataOptions options = {},
      optional ClearDataTypeSet types = {});
    Promise<boolean> getAudioState();
    Promise<long> getZoom();
    Promise<boolean> isAudioMuted();
    undefined setAudioMuted(boolean mute);
    Promise<undefined> setZoom(long zoomFactor);

    // Capture methods.
    Promise<undefined> captureVisibleRegion(optional ImageDetails options = {});
    undefined print();
};

The controlledframe element represents its embedded navigable which has a embedderParent that is valid and refers back to the embedder navigable.

The embedded navigable must appear as a top-level traversible. As part of the embedded navigable being a top-level traversible, the embedded navigable must have a null parent. Content within the embedded navigable cannot distinguish that it is embedded within another navigable.

The Controlled Frame element is exposed to any Document with the "controlled-frame" policy-controlled feature whose environment settings object is an isolated context.

The partition attribute takes an identifier specifying where data related to the Controlled Frame element’s instance should be stored. The identifier is composed of a string of alphanumeric digits. All data for the embedded navigable will be stored in this partition.

By default, all data stored will be held in an in-memory storage partition so that when the Controlled Frame element is destroyed the data is also destroyed. While the data is held in this partition, no data will persist from that Controlled Frame embedded navigable.

If the partition attribute identifier contains the prefix "persist:", the user agent will use a disk-based storage environment rather than an in-memory storage partition.

If multiple Controlled Frames share the same partition identifier, all of their embedded navigable instances will share the same storage partition.

To validate embedded content with an embedded navigable embeddedContent, run the following steps:
  1. If embeddedContent does not have an embedderParent, return false.

  2. If embeddedContent’s embedderParent is not valid, return false.

  3. If embeddedContent’s embedderParent is not the current navigable, return false.

  4. Return true.

3.1. Navigation methods

back()

Goes back one step in the overall session history entries list for the traversable navigable in the Controlled Frame.

If there is no previous page, does nothing.

canGoBack()

Returns true if the current current session history entry is not the first one in the navigation history entry list. This means that there is a previous session history entry for this navigable.

forward()

Goes forward one step in the overall session history entries list for the traversable navigable in the Controlled Frame.

If there is no next page, does nothing.

go()

Reloads the current page.

go(relativeIndex)

Goes back or forward relativeIndex number of steps in the overall session history entries list for the current traversable navigable.

A zero relative index will reload the current page.

If the relative index is out of range, does nothing.

reload()

Reloads the current page.

stop()

Cancels the document load.

To navigate embedded content to state, given an object state, run the following steps:
  1. If state is not an integer, return false.

  2. Attempt to navigate the embedded content by state history items. If an error is encountered, return false.

  3. Return true.

The back() method steps for an embeddedContent are:
  1. Let resultPromise be a new promise.

  2. If the result of running validate embedded content with an embedded navigable embeddedContent is false, reject resultPromise with a TypeError and abort these steps.

  3. If the result of running navigate embedded content to state with "-1" is false, reject resultPromise with a TypeError and abort these steps.

  4. Then, resolve resultPromise.

The canGoBack() method steps for an embeddedContent are:
  1. If the result of running validate embedded content with an embedded navigable embeddedContent is false, return false.

  2. If embedded content is already at its first history state, return false.

  3. Return true.

The canGoForward() method steps for an embeddedContent are:
  1. If the result of running validate embedded content with an embedded navigable embeddedContent is false, return false.

  2. If embedded content is already at its last history state, return false.

  3. Return true.

The forward() method steps for an embeddedContent are:
  1. Let resultPromise be a new promise.

  2. If the result of running validate embedded content with an embedded navigable embeddedContent is false, reject resultPromise with a TypeError and abort these steps.

  3. If the result of running navigate embedded content to state with "1" is false, reject resultPromise with a TypeError and abort these steps.

  4. Then, resolve resultPromise.

The go(relativeIndex) method steps for an embeddedContent are:
  1. Let resultPromise be a new promise.

  2. If the result of running validate embedded content with an embedded navigable embeddedContent is false, reject resultPromise with a TypeError and abort these steps.

  3. If the result of running navigate embedded content to state with relativeIndex is false, reject resultPromise with a TypeError and abort these steps.

  4. Then, resolve resultPromise.

The reload() method steps for an embeddedContent are:
  1. If the result of running validate embedded content with an embedded navigable embeddedContent is false, return false.

  2. Reload embedded content for its top-level page.

  3. Return true.

The stop() method steps for an embeddedContent are:
  1. If the result of running validate embedded content with an embedded navigable embeddedContent is false, return false.

  2. If embedded content is not currently loading a navigation, abort these steps.

  3. Stop the current navigation in embedded content.

  4. Return true.

3.2. Scripting methods

// One of |code| or |file| must be specified but not both.
dictionary InjectDetails {
  DOMString code;
  DOMString file;
};

dictionary InjectionItems {
  DOMString code;
  sequence<DOMString> files;
};

enum RunAt {
  "document_start",
  "document_end",
  "document_idle",
};

dictionary ContentScriptDetails {
  boolean all_frames;
  InjectionItems css;
  sequence<DOMString> exclude_globs;
  sequence<DOMString> exclude_matches;
  sequence<DOMString> include_globs;
  InjectionItems js;
  boolean match_about_blank;
  required sequence<DOMString> matches;
  required DOMString name;
  RunAt run_at;
};

In fetch an injection item, there are references to "associate" some code with each load. This specific functionality is referring to an element of the user agent’s page load process that is considered to be unspecified. This section will elaborate:

When a page refers to some inline CSS or JS code, this will be injected into the appropriate CSS or JS subsystem at the correct point during the page load process.

If a page refers to some external file at a URL, then the user agent will fetch that URL. If the fetch returns successfully, the user agent will now have a body of either CSS or JS code. The user agent can then inject that code into the appropriate CSS or JS subsystem at the correct point during the page load process.

The way that fetch an injection item works is analogous to this process, except assume that the page never referred to the external file yet the content specified at that file URL was still injected into the page during page load.

Since there is no reference within the page to that external file URL, the user agent will not have a "correct point" at which to inject the content into the appropriate CSS or JS subsystem. Instead the content will be injected at the end of the page load process, as if the external file URL reference existed at the end of the page body.

To fetch an injection item, given an embeddedContent, an object injectionItem and a type, run the following steps:
  1. If type is neither "css" or "js", return false.

  2. If injectionItem is not a dictionary, return false.

  3. If injectionItem is empty, return false.

  4. If injectionItem has both fields "code" and "file" or neither field:

    1. Return false.

  5. If injectionItem has field "code":

    1. Let injectionCode be injectionItem["code"].

  6. If injectionItem has field "file":

    1. If injectionItem["file"] is not a valid URL, return false.

    2. Fetch the content from URL injectionItem["file"] and store it as injectionCode. If an error is encountered, return false.

  7. If type is "css":

    1. Update the CSS loader for the embeddedContent navigable’s source URL to associate injectionCode with each load.

  8. If type is "js":

    1. Update the JS loader for the embeddedContent navigable’s source URL to associate injectionCode with each load.

  9. Return true.

The addContentScripts(contentScriptList) method steps for an embeddedContent are:
  1. Let resultPromise be a new promise.

  2. If the result of running validate embedded content with an embedded navigable embeddedContent is false, reject resultPromise with a TypeError and abort these steps.

  3. For each contentScriptDetail in contentScriptList:

    1. If contentScriptDetail has field "css":

      1. For each cssInjectionItem in contentScriptDetail["css"]:

        1. If the result of running fetch an injection item with embeddedContent, cssInjectionItem and type "css" is false, reject resultPromise with a TypeError and abort these steps.

    2. If contentScriptDetail has field "js":

      1. For each jsInjectionItem in contentScriptDetail["js"]:

        1. If the result of running fetch an injection item with embeddedContent, jsInjectionItem and type "js" is false, reject resultPromise with a TypeError and abort these steps.

  4. Otherwise, resolve resultPromise.

The executeScript(optional details) method steps are:
  1. Let resultPromise be a new promise.

  2. If the result of running validate embedded content is false, reject resultPromise with a TypeError and abort these steps.

  3. If details has field "code":

    1. Let detailsCode be details["code"].

  4. If details has field "file":

    1. Let detailsFile be details["file"].

  5. If both detailsCode and detailsFile are set, reject resultPromise with a TypeError and abort these steps.

  6. If detailsFile is set:

    1. Fetch file located at detailsFile.

    2. If file is not fetched successfully, reject resultPromise with a TypeError and abort these steps.

    3. Store the result of fetching file in detailsCode.

  7. In the embedded content, execute the script detailsCode.

  8. Then, resolve resultPromise.

The insertCSS(optional details) method steps are:
  1. Let resultPromise be a new promise.

  2. If the result of running validate embedded content is false, reject resultPromise with a TypeError and abort these steps.

  3. If details has field "code":

    1. Let detailsCode be details["code"].

  4. If details has field "file":

    1. Let detailsFile be details["file"].

  5. If both detailsCode and detailsFile are set, reject resultPromise with a TypeError and abort these steps. an error.

  6. If detailsFile is set:

    1. Fetch file located at detailsFile.

    2. If file is not fetched successfully, reject resultPromise with a TypeError and abort these steps.

    3. Store the result of fetching file in detailsCode.

  7. In the embedded content, insert the stylesheet content detailsCode.

  8. Resolve the promise successfully.

  9. Then, resolve resultPromise.

The removeContentScripts(scriptNameList) method steps are:
  1. Let resultPromise be a new promise.

  2. If the result of running validate embedded content is false, reject resultPromise with a TypeError and abort these steps.

  3. For each item of type DOMString in scriptNameList named scriptName:

    1. Find the loader for scriptName. If it doesn’t exist, then skip it.

    2. Attempt to remove the loader associated with scriptName. If an error is encountered, reject resultPromise with a TypeError and abort these steps.

  4. Then, resolve resultPromise.

3.3. Configuration methods

dictionary ClearDataOptions {
  long since;
};

dictionary ClearDataTypeSet {
  boolean appcache;
  boolean cache;
  boolean cookies;
  boolean fileSystems;
  boolean indexedDB;
  boolean localStorage;
  boolean persistentCookies;
  boolean sessionCookies;
  boolean webSQL;
};
The clearData(optional options, optional types) method steps are:
  1. Let resultPromise be a new promise.

  2. If the result of running validate embedded content is false, reject resultPromise with a TypeError and abort these steps.

  3. Let clearSince be 0. This will represent to clear all items.

  4. If options["since"] is set:

    1. Let clearSince be options["since"].

  5. If types["appcache"] is set:

    1. Clear the partition’s appcache items with a matching clearSince.

  6. If types["cache"] is set:

    1. Clear the partition’s cache items with a matching clearSince.

  7. If types["cookies"] is set:

    1. Clear the partition’s cookie items with a matching clearSince.

  8. If types["fileSystems"] is set:

    1. Clear the partition’s fileSystem items with a matching clearSince.

  9. If types["indexedDB"] is set:

    1. Clear the partition’s indexedDB items with a matching clearSince.

  10. If types["localStorage"] is set:

    1. Clear the partition’s localStorage items with a matching clearSince.

  11. If types["persistentCookies"] is set:

    1. Clear the partition’s persistentCookies items with a matching clearSince.

  12. If types["sessionCookies"] is set:

    1. Clear the partition’s sessionCookies items with a matching clearSince.

  13. If types["webSQL"] is set:

    1. Clear the partition’s webSQL items with a matching clearSince.

  14. Then, resolve resultPromise.

The getAudioState() method steps are:
  1. Let resultPromise be a new promise.

  2. If the result of running validate embedded content is false, reject resultPromise with a TypeError and abort these steps.

  3. Let muteState be the current audio mute state of embedded content.

  4. Then, resolve resultPromise with muteState.

The getZoom() method steps are:
  1. Let resultPromise be a new promise.

  2. If the result of running validate embedded content is false, reject resultPromise with a TypeError and abort these steps.

  3. Let zoomFactor be the current zoom setting for the embedded content.

  4. Then, resolve resultPromise with zoomFactor.

The isAudioMuted() method steps are:
  1. Let resultPromise be a new promise.

  2. If the result of running validate embedded content is false, reject resultPromise with a TypeError and abort these steps.

  3. Let muteState be the current audio mute state for embedded content.

  4. Then, resolve resultPromise with muteState.

The setAudioMuted(mute) method steps are:
  1. If the result of running validate embedded content is false, return false.

  2. Change the audio mute state for embedded content to match mute state.

The setZoom(zoomFactor) method steps are:
  1. Let resultPromise be a new promise.

  2. If the result of running validate embedded content is false, reject resultPromise with a TypeError and abort these steps.

  3. Change the zoom for the embedded content to be zoomFactor.

  4. Then, resolve resultPromise.

3.4. Capture methods

// One of |code| or |file| must be specified but not both.
dictionary ImageDetails {
  DOMString format;
  DOMString quality;
};
The captureVisibleRegion(optional options) method steps are:
  1. Let resultPromise be a new promise.

  2. If the result of running validate embedded content is false, reject resultPromise with a TypeError and abort these steps.

  3. Let optionsFormat be "JPEG" by default.

  4. Let optionsQuality be 100 by default.

  5. If options has field "format":

    1. Let optionsFormat be options["format"].

  6. If optionsFormat is an unrecognized format, reject resultPromise with a TypeError and abort these steps.

  7. If options has field "quality":

    1. Let optionsQuality be options["quality"].

  8. If optionsQuality is not an integer and is not between 0 and 100 inclusive, reject resultPromise with a TypeError and abort these steps.

  9. Create an image in format optionsFormat at quality optionsQuality showing the visible region of embedded content.

  10. Then, resolve resultPromise containing the image data.

The print() method steps are:
  1. If the result of running validate embedded content is false, abort these steps.

  2. Initiate the browser print page feature for embedded content.

3.5. Event listener API

3.6. Integration with other specifications

This specification will make some modifications to specifications to accommodate the needs of Controlled Frame.

3.6.1. Monkey Patch to HTML

Each navigable has:

The initialize the navigable algorithm given a navigable navigable and an optional navigable-or-null parent (default null) is monkeypatched as follows:

  1. Set navigable’s parent to parent.

  2. If this navigable has been created via a Controlled Frame embedder, then set embedderParent to the embedder navigable. Otherwise, set embedderParent to null.
  3. If parent is not null (navigable is not a top-level traversable), then:
    1. Set navigable’s frameId to parent’s next frameId.
    2. Increment parent’s next frameId.

4. Web Request API

enum ResourceType {
  "main_frame",
  "sub_frame",
  "stylesheet",
  "script",
  "image",
  "font",
  "object",
  "xmlhttprequest",
  "ping",
  "csp_report",
  "media",
  "websocket",
  "webbundle",
  "other",
};

callback interface WebRequestEventListener {
  BlockingResponse? handleEvent(WebRequestEventDetails details);
};

dictionary RequestFilter {
  sequence<ResourceType> types;
  sequence<USVString> urls;
};

enum ExtraInfoSpec {
  "asyncBlocking",
  "blocking",
  "extraHeaders",
  "requestHeaders",
  "responseHeaders",
};

[Exposed=Window, IsolatedContext]
interface WebRequestEvent {
  undefined addListener(WebRequestEventListener listener,
                        optional RequestFilter filter = {},
                        optional sequence<ExtraInfoSpec> extraInfoSpec);
  boolean hasListener(WebRequestEventListener listener);
  boolean hasListeners();
  undefined removeListener(WebRequestEventListener listener);
};

dictionary WebRequestAuthCredentials {
  required DOMString username;
  required DOMString password;
};

dictionary BlockingResponse {
  WebRequestAuthCredentials authCredentials;
  boolean cancel;
  USVString redirectUrl;
  sequence<HttpHeader> requestHeaders;
  sequence<HttpHeader> responseHeaders;
};

enum DocumentLifecycle {
  "prerender",
  "active",
  "cached",
  "pending_deletion",
};

enum FrameType {
  "outermost_frame",
  "fenced_frame",
  "sub_frame",
};

dictionary WebRequestEventDetails {
  DOMString documentId;
  DocumentLifecycle documentLifecycle;
  required long frameId;
  FrameType frameType;
  USVString initiator;
  required DOMString method;
  DOMString parentDocumentId;
  required long parentFrameId;
  required DOMString requestId;
  required long timeStamp;
  required ResourceType type;
  required USVString url;
};

dictionary UploadData {
  ArrayBuffer bytes;
  DOMString file;
};
dictionary RequestBody {
  DOMString error;
  any formData;
  sequence<UploadData> raw;
};

dictionary WebRequestBeforeRequestDetails : WebRequestEventDetails {
  RequestBody requestBody;
};

dictionary HttpHeader {
  required DOMString name;
  DOMString value;
  sequence<byte> binaryValue;
};

dictionary WebRequestBeforeSendHeadersDetails : WebRequestEventDetails {
  sequence<HttpHeader> requestHeaders;
};

dictionary WebRequestSendHeadersDetails : WebRequestEventDetails {
  sequence<HttpHeader> requestHeaders;
};

dictionary WebRequestResponseEventDetails : WebRequestEventDetails {
  required long statusCode;
  required DOMString statusLine;
  sequence<HttpHeader> responseHeaders;
};

dictionary WebRequestHeadersReceivedDetails : WebRequestResponseEventDetails {};

dictionary AuthChallenger {
  DOMString host;
  long port;
};
dictionary WebRequestAuthRequiredDetails : WebRequestResponseEventDetails {
  required AuthChallenger challenger;
  required boolean isProxy;
  required DOMString scheme;
  DOMString realm;
};

dictionary WebRequestResponseWithIpEventDetails : WebRequestResponseEventDetails {
  required boolean fromCache;
  DOMString ip;
};

dictionary WebRequestBeforeRedirectDetails : WebRequestResponseWithIpEventDetails {
  required USVString redirectUrl;
};

dictionary WebRequestResponseStartedDetails : WebRequestResponseWithIpEventDetails {};

dictionary WebRequestCompletedDetails : WebRequestResponseWithIpEventDetails {};

// TODO: is this the right base type?
dictionary WebRequestErrorOccurredDetails : WebRequestEventDetails {
  required DOMString error;
  required boolean fromCache;
  DOMString ip;
};

callback HandlerBehaviorChangedCallback = undefined ();

[Exposed=Window, IsolatedContext]
interface WebRequest {
  readonly attribute WebRequestEvent onBeforeRequest;
  readonly attribute WebRequestEvent onBeforeSendHeaders;
  readonly attribute WebRequestEvent onSendHeaders;
  readonly attribute WebRequestEvent onHeadersReceived;
  readonly attribute WebRequestEvent onAuthRequired;
  readonly attribute WebRequestEvent onBeforeRedirect;
  readonly attribute WebRequestEvent onResponseStarted;
  readonly attribute WebRequestEvent onCompleted;
  readonly attribute WebRequestEvent onErrorOccurred;

  undefined handlerBehaviorChanged(optional HandlerBehaviorChangedCallback callback);
};

Each WebRequest has a handler map, which is a map whose keys are strings and whose values are lists of WebRequest handler configs.

Each WebRequestEvent has:

The addListener(listener, filter, extraInfoSpec) method steps are:
  1. Call removeListener with listener.

  2. Let eventName be this’s eventName.

  3. Let handlerConfig be a new WebRequest handler config with the following items:

    handler

    listener

    filter

    filter

  4. Let specSet be a set containing the items in extraInfoSpec.

  5. If eventName equals "beforeRequest", then:

    1. If specSet is not a subset of the set « "blocking", "requestBody", "extraHeaders" », then throw a TypeError.

    2. If specSet contains "blocking", then set handlerConfig’s blocking to true.

    3. If specSet contains "requestBody", then set handlerConfig’s requestsBody to true.

    4. If specSet contains "extraHeaders", then set handlerConfig’s requestsAllHeaders to true.

  6. Otherwise, if eventName equals "beforeSendHeaders", then:

    1. If specSet is not a subset of the set « "blocking", "requestHeaders", "extraHeaders" », then throw a TypeError.

    2. If specSet contains "blocking", then set handlerConfig’s blocking to true.

    3. If specSet contains "requestHeaders", then set handlerConfig’s requestsHeaders to true.

    4. If specSet contains "extraHeaders", then set handlerConfig’s requestsAllHeaders to true.

  7. Otherwise, if eventName equals "sendHeaders", then:

    1. If specSet is not a subset of the set « "requestHeaders", "extraHeaders" », then throw a TypeError.

    2. If specSet contains "requestHeaders", then set handlerConfig’s requestsHeaders to true.

    3. If specSet contains "extraHeaders", then set handlerConfig’s requestsAllHeaders to true.

  8. Otherwise, if eventName equals "headersReceived", then:

    1. If specSet is not a subset of the set « "blocking", "responseHeaders", "extraHeaders" », then throw a TypeError.

    2. If specSet contains "blocking", then set handlerConfig’s blocking to true.

    3. If specSet contains "responseHeaders", then set handlerConfig’s requestsHeaders to true.

    4. If specSet contains "extraHeaders", then set handlerConfig’s requestsAllHeaders to true.

  9. Otherwise, if eventName equals "authRequired", then:

    1. If specSet is not a subset of the set « "asyncBlocking", "blocking", "responseHeaders", "extraHeaders" », then throw a TypeError.

    2. If specSet contains "asyncBlocking", then set handlerConfig asyncBlocking to true.

    3. If specSet contains "blocking", then set handlerConfig’s blocking to true.

    4. If specSet contains "responseHeaders", then set handlerConfig’s requestsHeaders to true.

    5. If specSet contains "extraHeaders", then set handlerConfig’s requestsAllHeaders to true.

  10. Otherwise, if eventName equals "beforeRedirect", then:

    1. If specSet is not a subset of the set « "responseHeaders", "extraHeaders" », then throw a TypeError.

    2. If specSet contains "responseHeaders", then set handlerConfig’s requestsHeaders to true.

    3. If specSet contains "extraHeaders", then set handlerConfig’s requestsAllHeaders to true.

  11. Otherwise, if eventName equals "responseStarted", then:

    1. If specSet is not a subset of the set « "responseHeaders", "extraHeaders" », then throw a TypeError.

    2. If specSet contains "responseHeaders", then set handlerConfig’s requestsHeaders to true.

    3. If specSet contains "extraHeaders", then set handlerConfig’s requestsAllHeaders to true.

  12. Otherwise, if eventName equals "completed", then:

    1. If specSet is not a subset of the set « "responseHeaders", "extraHeaders" », then throw a TypeError.

    2. If specSet contains "responseHeaders", then set handlerConfig’s requestsHeaders to true.

    3. If specSet contains "extraHeaders", then set handlerConfig’s requestsAllHeaders to true.

  13. Otherwise, if eventName equals "errorOccurred", then:

    1. If specSet is not a subset of the set « "extraHeaders" », then throw a TypeError.

    2. If specSet contains "extraHeaders", then set handlerConfig’s requestsAllHeaders to true.

  14. Otherwise, throw a TypeError.

  15. Let handlerMap be the handler map of this’s webRequest.

  16. Append handlerConfig to handlerMap[eventName].

The hasListener(listener) method steps are:
  1. Let handlerMap be the handler map of this’s webRequest.

  2. Let event name be this’s eventName.

  3. For each handlerConfig in handlerMap[event name]:

    1. If handlerConfig’s handler equals listener, return true.

  4. Return false.

The hasListeners() method steps are:
  1. Let handlerMap be the handler map of this’s webRequest.

  2. Let event name be this’s eventName.

  3. If handlerMap[event name] is empty, return false, otherwise return true.

The removeListener(listener) method steps are:
  1. Let handlerMap be the handler map of this’s webRequest.

  2. Let event name be this’s eventName.

  3. Let handlerConfigs be handlerMap[event name].

  4. Remove all items from handlerConfigs whose handler equals listener.

The handlerBehaviorChanged(callback) method steps are:
  1. callback TODO

To process beforeRequest events given a request request, run the following steps:
  1. Let handlers be the result of calling lookup registered WebRequest handler configs given "beforeRequest", and request.

  2. For each handler in handlers:

    1. Let details be the result of calling create a WebRequestEventDetails object given request.

    2. If request’s body is not null, then:

      1. Let requestBody be a new RequestBody.

      2. Let body be request’s body.

        TODO: serialize stream if present.

      3. Switch on body’s source:

        byte sequence

        Append a new UploadData with bytes equal to the serialized body’s source to requestBody’s raw.

        Blob

        Append a new UploadData with bytes equal to the serialized body’s source to requestBody’s raw.

        FormData
        1. Let formData be a new object.

        2. For each entry in body’s source’s entry list:

          1. Switch on entry[1]:

          File

          Append a new UploadData with file equal to entry[1]'s name to requestBody’s raw.

          USVString
          1. If formData[entry[0]] does not exist, then set formData[entry[0]] equal to an empty list.

          2. Append entry[1] to formData[entry[0]].

        3. Set details’s formData to formData.

      4. Set details’s requestBody to requestBody.

    3. If handler’s blocking flag is true, then:

      1. Let result be the result of calling handler[handler] given details.

      2. If any key in result is not contained in the set « "cancel", "redirect" », then throw a TypeError.

      3. Return result.

    4. Call handler[handler] given details in parallel.

    5. Return null.

To create a WebRequestEventDetails object given a request request, run the following steps:
  1. Let environmentSettingsObject be request’s client.

  2. Let crossOriginIsolatedCapability be environmentSettingsObject’s cross-origin isolated capability, or false if environmentSettingsObject is null.

  3. Let details be a new WebRequestEventDetails with the following fields:

    timeStamp

    The coarsened shared current time given crossOriginIsolatedCapability.

    url

    The last element of request’s URL list.

    method

    request’s method.

    initiator

    The serialization of an origin given request’s origin.

    Note: An opaque origin will result in initiator being set to the string "null".

    type

    The result of calling get a request’s ResourceType given request.

    requestId

    request’s requestId.

    frameId

    -1

    parentFrameId

    -1

  4. If environmentSettingsObject’s global object is a Window object, then:

    1. Let window be environmentSettingsObject’s global object.

    2. Update the following fields of details:

      documentId

      environmentSettingsObject’s id.

      documentLifecycle

      It’s unclear if DocumentLifecycle values map to existing concepts.

      frameId

      The frameId of window’s navigable.

      frameType

      "fenced_frame" if window’s fence is non-null, "outermost_frame" if window’s top equals window, "sub_frame" otherwise.

      1. If window’s parent is not equal to window, then update the following values of details:

        parentDocumentId

        The id of window’s parent’s active document’s relevant global object.

        parentFrameId

        The frameId of window’s parent’s navigable.

  5. Return details.

To get a request’s ResourceType given a request request, run the following steps:
  1. Return request’s initiator type.

    TODO: initiator type doesn’t exactly map to ResourceType. Define this mapping.

To create a redirect response given a string redirectUrl, return a response with the following fields:
status

301

header list

« ("Location", redirectUrl) »

4.1. WebRequest handler config

A WebRequest handler config is a struct with the following items:

handler

a WebRequestEventListener

filter

a RequestFilter

requestsBody

a boolean

requestsHeaders

a boolean

requestsAllHeaders

a boolean

blocking

a boolean

asyncBlocking

a boolean

A string url matches a URL pattern string url pattern if the following steps return true:
  1. url url pattern TODO: Spec this, ideally referring to a not-yet-defined Match pattern spec.

To lookup registered WebRequest handler configs given a string event name, and a request request, run the following steps:
  1. Let client be request’s client.

  2. If client is null, return an empty list.

  3. Let controlled frame be the HTMLControlledFrameElement client is embedded within, or null.

    The ESO to CF mapping needs to be formalized.

  4. If controlled frame is null, return an empty list.

  5. Let valid handlers be an empty list.

  6. Let handlerMap be controlled frame’s request’s handler map.

  7. For each handlerConfig in handlerMap[event name]:

    1. Let filter be handlerConfig’s filter

    2. Let types be filter[types].

    3. If types is not empty and types does not contain the result of calling get a request’s ResourceType given request, then continue.

    4. Let urls be filter[urls].

    5. Let is valid url be true if urls is empty, false otherwise.

    6. For each url pattern in urls:

      1. If request’s URL matches a URL pattern given url pattern, set is valid url to true.

    7. Append handlerConfig to valid handlers.

  8. Return valid handlers.

4.2. Monkey Patches

4.2.1. Fetch

A request has an associated requestId, which is an opaque string, randomly assigned at the request’s creation.

Open questions:

  1. How does WR interact with HSTS? When is the https upgrade applied? If HSTS is pre-onbeforerequest then we need to move the event later

  2. How does WR interact with preload?

  3. Does WR intercept worker scripts or requests from workers?

The main fetch algorithm is monkeypatched as follows:

  1. Let request be fetchParam’s request.

  2. Let response be null.

  3. Let webRequestResult be the result of calling process beforeRequest events given request.
  4. If webRequestResult is not null, then:
    1. If webRequestResult["cancel"] is true, then set response to a network error.
    2. Otherwise, if webRequestResult ["redirectUrl"] is not an empty string, then set response to the result of creating a redirect response given webRequestResult["redirectUrl"].

5. Context Menus API

enum ContextType {
    "all",
    "page",
    "frame",
    "selection",
    "link",
    "editable",
    "image",
    "video",
    "audio",
};

enum ItemType {
    "normal",
    "checkbox",
    "radio",
    "separator",
};

dictionary OnClickData {
    boolean checked;
    required boolean editable;
    long frameId;
    USVString frameUrl;
    USVString linkUrl;
    DOMString mediaType;
    required (DOMString or long) menuItemId;
    USVString pageUrl;
    (DOMString or long) parentMenuId;
    DOMString selectionText;
    USVString srcUrl;
    boolean wasChecked;
};

callback ContextMenusEventListener = undefined (OnClickData data);

dictionary ContextMenusProperties {
    boolean checked;
    sequence<ContextType> context;
    DOMString documentUrlPatterns;
    boolean enabled;
    DOMString parentId;
    DOMString targetUrlPatterns;
    DOMString title;
    ItemType type;
    ContextMenusEventListener onclick;
};

dictionary ContextMenusCreateProperties : ContextMenusProperties {
    DOMString id;
};

callback ContextMenusCallback = undefined ();

[Exposed=Window, IsolatedContext]
interface ContextMenus {
    // TODO: Define the `onShow` property.

    // Returns the ID of the newly created menu item.
    (DOMString or long) create(
        ContextMenusCreateProperties properties,
        ContextMenusCallback? callback);

    undefined remove(
        (DOMString or long) menuItemId,
        ContextMenusCallback? callback);
    undefined removeAll(ContextMenusCallback? callback);
    undefined update(
        (DOMString or long) id,
        ContextMenusProperties properties,
        ContextMenusCallback? callback);
};

6. Usage Overview

Lorem ipsum. Insert basic info and example here.

7. Motivating Applications

This section is non-normative.

7.1. Latency-sensitive applications in virtualized sessions

In virtualized environments, users typically have a local thin client that renders a full virtual desktop. The actual desktop execution environment will be running on a remote virtualization server. If the user’s browser navigates to a latency-sensitive application (such as a video app), the rendered content will have additional latency ("lag") that makes the experience difficult or impossible for the user. This also applies for applications that record the user, such as video conferencing applications. In these latency-sensitive applications, the virtual desktop application can render the latency-sensitive content locally and overlay it on top of the rendered remote content to reduce this latency. This use case is also known as "browser content redirection."

7.2. Embedding third party web content without restriction

In a kiosk environment, applications must load content from third parties and display that content on screens within their applications. A teacher may trigger the navigation event, or it may be configured by an administrator such as a shopping mall manager. The content may prohibit embedding by iframe through the use of X-Frame-Options and CSP. An controlled frame, however, should be able to load all content, even content that prohibits embedding by iframe.

7.3. Remote display and manipulation of web content

In a kiosk environment, applications must ensure that content continues to display on screens and may need to interrupt content with their own supplied behaviors. This behavior should work without local attendance by an administrator, and ideally can be managed remotely over the network. If content were to crash, for example, these applications should observe and respond to the crash by reloading the content in a fresh embedded view.

7.4. Clearing user content after each session

In some environments, someone only uses a single device for a brief time to complete their task, like ordering in a restaurant. When their task is complete, the embedder application should be able to clear all of the local user data associated with the task and then restart the embedded instance.

7.5. Monitor for idle sessions

While users interact with embedded content, the user may not explicitly end their session. This content may assume the user is present when they have actually finished or departed without completing the task. Embedder applications want to detect when users idle over their case’s threshold and begin a fresh session.

7.6. Arbitrarily blocking navigations

While displaying embedded web content that’s not authored by the embedder, pages may link to third party web content that’s disallowed. Allowing the embedder to edit elements in embedded content through arbitrary script injection into the web content can ensure navigation cannot occur to blocked pages. The embedder can also use the Controlled Frame API to capture navigation events and ensure that only pages to approved sites can be loaded within that controlled frame.

8. Security, Privacy, and Accessibility Considerations

This section is non-normative.

8.1. Security

Controlled Frame is based upon [Isolated-Web-Apps] (IWA) and integrates with core security specs

Since Controlled Frame is a particularly powerful API, using it or even having it available makes an app a target of various types of hacking. As a result, this API is limited to use in IWA which have additional safeguards in place to protect application developers and users. The Isolated Web App explainer has this to say:

"A user agent may also force an application to adopt this threat model if the developer needs access to APIs which would make the application an appealing target for XSS or server-side attacks."

Controlled Frame makes just such an appealing target, and to expose this with caution we’re opting into IWA to guard against certain attacks. Generally, IWAs provide strong security assurances that each of the resources in an application are secure both at rest and in-transit. You can read more about IWAs security and permissions in the IWA explainer and the IWAs [High-Watermark-Permissions] explainer.

Controlled Frame integrates with [Permissions-Policy] and [Permissions]. You can read more about Permissions Policy §  12. Privacy and Security and Permissions § E Security considerations (note the entry is currently sparse).

Attacking web sites could display content that doesn’t otherwise allow itself to be embedded and trick users on non-IWAs.

Planned mitigation:

An IWA may embed another IWA (or itself) via Controlled Frame to manipulate our IWA policies somehow (e.g. an Controlled Frame embedded IWA may detect it’s being embedded due to the absence of the "controlled-frame" policy-controlled feature).

Planned mitigation:

Controlled Frame could gain access to the powerful <controlledframe> element.

An IWA that’s not expected to use Controlled Frame may attempt to embed content.

Planned mitigation:

An IWA may attempt to embed content from non-https schemes, such as 'http:' or 'isolated-app:'

Planned mitigation:

Malicious Controlled Frame could access the embedder’s running process (eg. Spectre attack)

Planned mitigation:

Controlled Frame for a given "https origin" could interact or interfere with the user’s own storage data for that https origin

Planned mitigation:

Malicious Controlled Frame could overwrite embedder’s stored data

Planned mitigation:

Malicious Controlled Frame may detect it is embedded and attempt to attack the embedder application

Planned mitigation:

Ideas:

User may not be able to verify the origin of the page being viewed in the Controlled Frame

Ideas:

Controlled Frame may exploit vulnerabilities in out-of-date browser engine

Already addressed with:

8.2. Privacy

Controlled Frame integrates with Permissions Policy and Permissions. You can read more about Permissions Policy §  12. Privacy and Security. You can read more about Permissions § E Security considerations.

For Controlled Frame specifically, we’ve identified the following privacy considerations:

8.3. Accessibility

For Controlled Frame, we’ve identified the following accessibility considerations:

9. Acknowledgements

The following people contributed to the development of this document.

Conformance

Document conventions

Conformance requirements are expressed with a combination of descriptive assertions and RFC 2119 terminology. The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in the normative parts of this document are to be interpreted as described in RFC 2119. However, for readability, these words do not appear in all uppercase letters in this specification.

All of the text of this specification is normative except sections explicitly marked as non-normative, examples, and notes. [RFC2119]

Examples in this specification are introduced with the words “for example” or are set apart from the normative text with class="example", like this:

This is an example of an informative example.

Informative notes begin with the word “Note” and are set apart from the normative text with class="note", like this:

Note, this is an informative note.

Index

Terms defined by this specification

Terms defined by reference

References

Normative References

[DOM]
Anne van Kesteren. DOM Standard. Living Standard. URL: https://dom.spec.whatwg.org/
[FENCED-FRAME]
Fenced Frame. Draft Community Group Report. URL: https://wicg.github.io/fenced-frame/
[FETCH]
Anne van Kesteren. Fetch Standard. Living Standard. URL: https://fetch.spec.whatwg.org/
[FileAPI]
Marijn Kruisselbrink. File API. URL: https://w3c.github.io/FileAPI/
[HR-TIME-3]
Yoav Weiss. High Resolution Time. URL: https://w3c.github.io/hr-time/
[HTML]
Anne van Kesteren; et al. HTML Standard. Living Standard. URL: https://html.spec.whatwg.org/multipage/
[INFRA]
Anne van Kesteren; Domenic Denicola. Infra Standard. Living Standard. URL: https://infra.spec.whatwg.org/
[ISOLATED-CONTEXTS]
Isolated Contexts. Draft Community Group Report. URL: https://wicg.github.io/isolated-web-apps/isolated-contexts.html
[Permissions]
Marcos Caceres; Mike Taylor. Permissions. URL: https://w3c.github.io/permissions/
[Permissions-Policy]
Ian Clelland. Permissions Policy. URL: https://w3c.github.io/webappsec-permissions-policy/
[RFC2119]
S. Bradner. Key words for use in RFCs to Indicate Requirement Levels. March 1997. Best Current Practice. URL: https://datatracker.ietf.org/doc/html/rfc2119
[WEBIDL]
Edgar Chen; Timothy Gu. Web IDL Standard. Living Standard. URL: https://webidl.spec.whatwg.org/
[XHR]
Anne van Kesteren. XMLHttpRequest Standard. Living Standard. URL: https://xhr.spec.whatwg.org/

Informative References

[High-Watermark-Permissions]
Robbie McElrath. Isolated Web Apps High Watermark Permissions Explainer. URL: https://github.com/WICG/isolated-web-apps/blob/main/Permissions.md
[Isolated-Web-Apps]
Reilly Grant. Isolated Web Apps Explainer. URL: https://github.com/WICG/isolated-web-apps/blob/main/README.md

IDL Index

[Exposed=Window, IsolatedContext]
interface HTMLControlledFrameElement : HTMLElement {
    [HTMLConstructor] constructor();

    [CEReactions] attribute USVString src;
    attribute DOMString partition;

    readonly attribute WindowProxy? contentWindow;
    readonly attribute ContextMenus contextMenus;
    readonly attribute WebRequest request;

    // Navigation methods.
    Promise<undefined> back();
    boolean canGoBack();
    boolean canGoForward();
    Promise<undefined> forward();
    Promise<undefined> go(long relativeIndex);
    undefined reload();
    undefined stop();

    // Scripting methods.
    Promise<undefined> addContentScripts(sequence<ContentScriptDetails> contentScriptList);
    Promise<any> executeScript(optional InjectDetails details = {});
    Promise<undefined> insertCSS(optional InjectDetails details = {});
    Promise<undefined> removeContentScripts(sequence<DOMString>? scriptNameList);

    // Configuration methods.
    Promise<undefined> clearData(
      optional ClearDataOptions options = {},
      optional ClearDataTypeSet types = {});
    Promise<boolean> getAudioState();
    Promise<long> getZoom();
    Promise<boolean> isAudioMuted();
    undefined setAudioMuted(boolean mute);
    Promise<undefined> setZoom(long zoomFactor);

    // Capture methods.
    Promise<undefined> captureVisibleRegion(optional ImageDetails options = {});
    undefined print();
};

// One of |code| or |file| must be specified but not both.
dictionary InjectDetails {
  DOMString code;
  DOMString file;
};

dictionary InjectionItems {
  DOMString code;
  sequence<DOMString> files;
};

enum RunAt {
  "document_start",
  "document_end",
  "document_idle",
};

dictionary ContentScriptDetails {
  boolean all_frames;
  InjectionItems css;
  sequence<DOMString> exclude_globs;
  sequence<DOMString> exclude_matches;
  sequence<DOMString> include_globs;
  InjectionItems js;
  boolean match_about_blank;
  required sequence<DOMString> matches;
  required DOMString name;
  RunAt run_at;
};

dictionary ClearDataOptions {
  long since;
};

dictionary ClearDataTypeSet {
  boolean appcache;
  boolean cache;
  boolean cookies;
  boolean fileSystems;
  boolean indexedDB;
  boolean localStorage;
  boolean persistentCookies;
  boolean sessionCookies;
  boolean webSQL;
};

// One of |code| or |file| must be specified but not both.
dictionary ImageDetails {
  DOMString format;
  DOMString quality;
};

enum ResourceType {
  "main_frame",
  "sub_frame",
  "stylesheet",
  "script",
  "image",
  "font",
  "object",
  "xmlhttprequest",
  "ping",
  "csp_report",
  "media",
  "websocket",
  "webbundle",
  "other",
};

callback interface WebRequestEventListener {
  BlockingResponse? handleEvent(WebRequestEventDetails details);
};

dictionary RequestFilter {
  sequence<ResourceType> types;
  sequence<USVString> urls;
};

enum ExtraInfoSpec {
  "asyncBlocking",
  "blocking",
  "extraHeaders",
  "requestHeaders",
  "responseHeaders",
};

[Exposed=Window, IsolatedContext]
interface WebRequestEvent {
  undefined addListener(WebRequestEventListener listener,
                        optional RequestFilter filter = {},
                        optional sequence<ExtraInfoSpec> extraInfoSpec);
  boolean hasListener(WebRequestEventListener listener);
  boolean hasListeners();
  undefined removeListener(WebRequestEventListener listener);
};

dictionary WebRequestAuthCredentials {
  required DOMString username;
  required DOMString password;
};

dictionary BlockingResponse {
  WebRequestAuthCredentials authCredentials;
  boolean cancel;
  USVString redirectUrl;
  sequence<HttpHeader> requestHeaders;
  sequence<HttpHeader> responseHeaders;
};

enum DocumentLifecycle {
  "prerender",
  "active",
  "cached",
  "pending_deletion",
};

enum FrameType {
  "outermost_frame",
  "fenced_frame",
  "sub_frame",
};

dictionary WebRequestEventDetails {
  DOMString documentId;
  DocumentLifecycle documentLifecycle;
  required long frameId;
  FrameType frameType;
  USVString initiator;
  required DOMString method;
  DOMString parentDocumentId;
  required long parentFrameId;
  required DOMString requestId;
  required long timeStamp;
  required ResourceType type;
  required USVString url;
};

dictionary UploadData {
  ArrayBuffer bytes;
  DOMString file;
};
dictionary RequestBody {
  DOMString error;
  any formData;
  sequence<UploadData> raw;
};

dictionary WebRequestBeforeRequestDetails : WebRequestEventDetails {
  RequestBody requestBody;
};

dictionary HttpHeader {
  required DOMString name;
  DOMString value;
  sequence<byte> binaryValue;
};

dictionary WebRequestBeforeSendHeadersDetails : WebRequestEventDetails {
  sequence<HttpHeader> requestHeaders;
};

dictionary WebRequestSendHeadersDetails : WebRequestEventDetails {
  sequence<HttpHeader> requestHeaders;
};

dictionary WebRequestResponseEventDetails : WebRequestEventDetails {
  required long statusCode;
  required DOMString statusLine;
  sequence<HttpHeader> responseHeaders;
};

dictionary WebRequestHeadersReceivedDetails : WebRequestResponseEventDetails {};

dictionary AuthChallenger {
  DOMString host;
  long port;
};
dictionary WebRequestAuthRequiredDetails : WebRequestResponseEventDetails {
  required AuthChallenger challenger;
  required boolean isProxy;
  required DOMString scheme;
  DOMString realm;
};

dictionary WebRequestResponseWithIpEventDetails : WebRequestResponseEventDetails {
  required boolean fromCache;
  DOMString ip;
};

dictionary WebRequestBeforeRedirectDetails : WebRequestResponseWithIpEventDetails {
  required USVString redirectUrl;
};

dictionary WebRequestResponseStartedDetails : WebRequestResponseWithIpEventDetails {};

dictionary WebRequestCompletedDetails : WebRequestResponseWithIpEventDetails {};

// TODO: is this the right base type?
dictionary WebRequestErrorOccurredDetails : WebRequestEventDetails {
  required DOMString error;
  required boolean fromCache;
  DOMString ip;
};

callback HandlerBehaviorChangedCallback = undefined ();

[Exposed=Window, IsolatedContext]
interface WebRequest {
  readonly attribute WebRequestEvent onBeforeRequest;
  readonly attribute WebRequestEvent onBeforeSendHeaders;
  readonly attribute WebRequestEvent onSendHeaders;
  readonly attribute WebRequestEvent onHeadersReceived;
  readonly attribute WebRequestEvent onAuthRequired;
  readonly attribute WebRequestEvent onBeforeRedirect;
  readonly attribute WebRequestEvent onResponseStarted;
  readonly attribute WebRequestEvent onCompleted;
  readonly attribute WebRequestEvent onErrorOccurred;

  undefined handlerBehaviorChanged(optional HandlerBehaviorChangedCallback callback);
};

enum ContextType {
    "all",
    "page",
    "frame",
    "selection",
    "link",
    "editable",
    "image",
    "video",
    "audio",
};

enum ItemType {
    "normal",
    "checkbox",
    "radio",
    "separator",
};

dictionary OnClickData {
    boolean checked;
    required boolean editable;
    long frameId;
    USVString frameUrl;
    USVString linkUrl;
    DOMString mediaType;
    required (DOMString or long) menuItemId;
    USVString pageUrl;
    (DOMString or long) parentMenuId;
    DOMString selectionText;
    USVString srcUrl;
    boolean wasChecked;
};

callback ContextMenusEventListener = undefined (OnClickData data);

dictionary ContextMenusProperties {
    boolean checked;
    sequence<ContextType> context;
    DOMString documentUrlPatterns;
    boolean enabled;
    DOMString parentId;
    DOMString targetUrlPatterns;
    DOMString title;
    ItemType type;
    ContextMenusEventListener onclick;
};

dictionary ContextMenusCreateProperties : ContextMenusProperties {
    DOMString id;
};

callback ContextMenusCallback = undefined ();

[Exposed=Window, IsolatedContext]
interface ContextMenus {
    // TODO: Define the `onShow` property.

    // Returns the ID of the newly created menu item.
    (DOMString or long) create(
        ContextMenusCreateProperties properties,
        ContextMenusCallback? callback);

    undefined remove(
        (DOMString or long) menuItemId,
        ContextMenusCallback? callback);
    undefined removeAll(ContextMenusCallback? callback);
    undefined update(
        (DOMString or long) id,
        ContextMenusProperties properties,
        ContextMenusCallback? callback);
};