Controlled Frame API

Draft Community Group Report,

This version:
https://wicg.github.io/controlled-frame/
Issue Tracking:
GitHub
Inline In Spec
Editors:
(Google LLC)
(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 traversables, 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

Categories:
Flow content.
Phrasing content.
Embedded content.
Interactive content.
Palpable content.
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
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();

    // Events:
    attribute EventHandler onconsolemessage;
    attribute EventHandler oncontentload;
    attribute EventHandler ondialog;
    attribute EventHandler onloadabort;
    attribute EventHandler onloadcommit;
    attribute EventHandler onloadstart;
    attribute EventHandler onloadstop;
    attribute EventHandler onnewwindow;
    attribute EventHandler onpermissionrequest;
    attribute EventHandler onsizechanged;
    attribute EventHandler onzoomchange;
};

The controlledframe element represents its embedded navigable.

Descendents of controlledframe elements represent nothing.

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 IDL attributes src and partition must reflect the respective content attributes of the same name.

Each controlledframe has an embedded navigable, which is either a traversable navigable with a non-null embedderParent, or null. It is initially null.

Note: The embedded navigable appears as a top-level traversable with a null parent. Content within the embedded navigable cannot detect that it is embedded.

Each controlledframe has a content script map, which is a map whose keys are strings and whose values are content script configs.

When a controlledframe element element is inserted into a document whose browsing context is non-null, run the following steps:
  1. If element’s src is not empty, then:

    1. Initialize a controlledframe given element.

When a controlledframe element element is removed from a document, run the following steps:
  1. TODO: destroy element’s embedded navigable.

To initialize a controlledframe element element, run the following steps:
  1. Assert that element’s embedded navigable is null.

  2. Let group be a new browsing context group.

  3. Let document be the second return value of creating a new browsing context and document given element’s node document, element, and group.

  4. Let documentState be a new document state, whose document is document.

  5. Let traversable be a new traversable navigable.

  6. Initialize the navigable traversable given documentState.

  7. Set traversable’s embedderParent to element.

  8. Set element’s embedded navigable to traversable.

  9. Let initialHistoryEntry be traversable’s active session history entry.

  10. Set initialHistoryEntry’s step to 0.

  11. Append initialHistoryEntry to traversable’s session history entries.

    These steps are needed to initialize History.length in the new navigable. This is an existing issue in the HTML Standard.

  12. Set element’s contentWindow to document’s WindowProxy.

  13. Navigate a controlledframe given element and element’s src.

To navigate a controlledframe element element given a USVString urlString, run the following steps:
  1. If urlString is not an absolute-URL string, return.

  2. Let url be the result of running the URL parser given urlString.

  3. Let historyHandling be "auto".

  4. If element’s embedded navigable’s active document is not completely loaded, then set historyHandling to "replace".

  5. Navigate element’s embedded navigable to url using element’s node document, with a NavigationHistoryBehavior of historyHandling.

The HTMLControlledFrameElement() constructor steps are:
  1. Let webRequest be this’s request.

  2. Set the eventName and webRequest fields of webRequest[onBeforeRequest] to "beforeRequest" and webRequest respectively.

  3. Set the eventName and webRequest fields of webRequest[onBeforeSendHeaders] to "beforeSendHeaders" and webRequest respectively.

  4. Set the eventName and webRequest fields of webRequest[onSendHeaders] to "sendHeaders" and webRequest respectively.

  5. Set the eventName and webRequest fields of webRequest[onHeadersReceived] to "headersReceived" and webRequest respectively.

  6. Set the eventName and webRequest fields of webRequest[onAuthRequired] to "authRequired" and webRequest respectively.

  7. Set the eventName and webRequest fields of webRequest[onBeforeRedirect] to "beforeRedirect" and webRequest respectively.

  8. Set the eventName and webRequest fields of webRequest[onResponseStarted] to "responseStarted" and webRequest respectively.

  9. Set the eventName and webRequest fields of webRequest[onCompleted] to "completed" and webRequest respectively.

  10. Set the eventName and webRequest fields of webRequest[onErrorOccurred] to "errorOccurred" and webRequest respectively.

3.1. Attributes

The partition attribute takes an identifier specifying where data related to the Controlled Frame’s instance should be stored. The identifier is composed of a string of alphanumeric characters. All data for the embedded navigable will be stored in a storage shelf keyed by this partition string along with the origin that created the data.

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

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. Embedded content should not be able to detect whether its storage is in-memory or persistent.

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

Note: The [STORAGE] specification is monkey patched below to partition storage based on the value of the partition attribute.

The partition IDL attribute setter steps are:
  1. If this’s embedded navigable is not null, then:

    1. Throw a "NotSupportedError" DOMException.

    2. Do not change the value of partition.

The src attribute reflects the Controlled Frame’s embedded navigable’s current session history entry’s URL.

The src IDL attribute setter steps are:
  1. If this is not in a document tree, then return.

  2. If this’s embedded navigable is null, then:

    1. Initialize a controlledframe given this.

  3. Otherwise:

    1. Navigate a controlledframe given this and this’s src.

3.2. 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 traverse an embedded navigable’s history, given a controlledframe controlledframe and an integer stepDelta, run the following steps:
  1. Let resultPromise be a new promise.

  2. Return resultPromise and run the remaining steps in parallel.

  3. If controlledframe’s embedded navigable is null, then resolve resultPromise with false.

  4. Let currentStep be controlledframe’s embedded navigable’s current session history step.

  5. Let step be the sum of currentStep and stepDelta.

  6. If step is negative, then resolve resultPromise with false.

  7. If step is greater than or equal to the size of the controlledframe’s embedded navigable’s session history entries, then resolve resultPromise with false.

  8. Let result be the result of applying the traverse history step given step, controlledframe’s embedded navigable, and a user navigation involvement of "browser UI".

  9. If result is not equal to "applied", resolve resultPromise with false.

  10. Otherwise, resolve resultPromise with true.

The canGoBack() method steps are:

We can’t actually synchronously access the embedded navigable’s history state. In the future we should update this method to return a Promise.

  1. If this’s embedded navigable is null, then return false.

  2. If this’s embedded navigable’s current session history step is greater than 0, return true.

  3. Return false.

The canGoForward() method steps are:
  1. If this’s embedded navigable is null, then return false.

  2. Let step be this’s embedded navigable’s current session history step.

  3. Let steps be the result of getting all used history steps given this’s embedded navigable.

  4. If step+1 is less than the size of steps, then return true.

  5. Return false.

The back() method steps are:
  1. Return the result of traversing an embedded navigable’s history given this and -1.

The forward() method steps are:
  1. Return the result of traversing an embedded navigable’s history given this and 1.

The go(relativeIndex) method steps are:
  1. Return the result of traversing an embedded navigable’s history given this and relativeIndex.

The reload() steps are:
  1. If this’s embedded navigable is null, return.

  2. Reload this’s embedded navigable given a user navigation involvement of "browser UI".

The stop() steps are:
  1. If this’s embedded navigable is null, return.

  2. Stop loading this’s embedded navigable.

3.3. 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 {
  required DOMString name;
  InjectionItems js;
  InjectionItems css;
  required sequence<DOMString> matches;
  sequence<DOMString> excludeMatches;
  boolean allFrames;
  boolean matchAboutBlank;
  RunAt runAt;
};

A content script config is a struct with the following items:

pendingFetchCount

a long representing the number of pending script/style fetches.

js

a list of strings containing JavaScript that will be injected into a document.

css

a list of strings containing CSS that will be injected into a document.

matches

a list of strings containing patterns. Content will be injected into pages whose URL matches one of these patterns.

excludeMatches

a list of strings containing patterns. Content will not be injected into pages whose URL matches one of these patterns.

allFrames

a boolean indicating whether content should be injected into all frames in a page, or just the top-level frame.

matchAboutBlank

a boolean indicating whether content should be injected into about:blank pages.

runAt

a RunAt indicating when JavaScript content should be executed in a document’s lifecycle.

To fetch an injection item given a controlledframe controlledframe, a DOMString urlString, a boolean isCss, a long index, and an algorithm completionSteps, run the following steps:
  1. If urlString is not an valid URL string, then:

    1. Run completionSteps given 0 and false.

    2. Return.

  2. Let request be a new request with the following fields:

    URL

    The result of running the URL parser given urlString and controlledframe’s node document’s document base URL

    method

    "GET"

    destination

    "style" if isCss is true, "script" otherwise

    client

    controlledframe’s node document’s relevant settings object

    mode

    "cors"

  3. Fetch request, with processResponseConsumeBody set to the following steps given a response response and a null, failure, or byte sequence contents:

    1. If response’s status is not 200, or contents is null or failure, then run completionSteps given 0 and false.

    2. Otherwise, run completionSteps given index and contents.

To validate and resolve ContentScriptDetails given a controlledframe controlledframe and a ContentScriptDetails details, run the following steps:
  1. Let result be a new promise.

  2. If details["js"] and details["css"] are both defined, or both undefined, then reject result with a TypeError and abort these steps.

  3. If details["matches"] is empty, then reject result with a TypeError and abort these steps.

  4. Return result and run the remaining steps in parallel.

  5. Let config be a new content script config with the following values:

    pendingFetchCount

    0

    matches

    details["matches"]

    excludeMatches

    details["excludeMatches"] if defined, otherwise an empty list

    allFrames

    details["allFrames"] if defined, otherwise false

    matchAboutBlank

    details["matchAboutBlank"] if defined, otherwise false

    runAt

    details["runAt"] if defined, otherwise document-idle

  6. Let isCss be a boolean equal to true if details["css"] is defined, false otherwise.

  7. Let completionSteps be the following algorithm, which takes a long index and a string or boolean source:

    1. If source is not a string, then reject result with a TypeError and abort these steps.

    2. If isCss, then:

      1. Set config’s css[index] to source.

    3. Otherwise:

      1. Set config’s js[index] to source.

    4. Decrement config’s pendingFetchCount.

    5. If config’s pendingFetchCount is greater than 0, then return.

    6. Set controlledframe’s content script map[details[name]] to config.

    7. Resolve result.

  8. Let injectionItems be details["css"] if isCss is true, details["js"] otherwise.

  9. If injectionItems["code"] and injectionItems["files"] are both defined, or both undefined, reject result with a TypeError and abort these steps.

  10. If injectionItems["code"] is defined, then:

    1. Run completionSteps given 0 and injectionItems ["code"].

  11. Otherwise:

    1. If injectionItems["files"] is empty, then reject result with a TypeError and abort these steps.

    2. For each urlString of injectionItems ["files"]:

      1. Run fetch an injection item given controlledframe, urlString, isCss, config’s pendingFetchCount, and completionSteps.

      2. Increment config’s pendingFetchCount.

To determine if a content script config applies to a document given a content script config config, a URL url, and a boolean isTopLevel, run the following steps:
  1. If isTopLevel is false, and config’s allFrames is false, then return false.

  2. If the result of serializing url with exclude fragment equal to true is equal to "about:blank", and config’s matchAboutBlank is false, then return false.

  3. Let urlString be the result of serializing url.

  4. Let match be false.

  5. For each pattern of config’s matches:

    1. If urlString matches a URL pattern pattern, then set match to true.

  6. For each pattern of config’s excludeMatches:

    1. If urlString matches a URL pattern pattern, then set match to false.

  7. Return match.

To inject content scripts into a document given a Document document, and a RunAt currentPhase, run the following steps:
  1. Let embeddedNavigable be document’s node navigable’s traversable navigable.

  2. If embeddedNavigable is null or its embedderParent is null, then return.

  3. Let controlledframe be embeddedNavigable’s embedderParent.

  4. Let url be document’s URL.

  5. Let isTopLevel be true if document’s node navigable’s parent is null, false otherwise.

  6. For each config of controlledframe’s content script map:

    1. If the result of determining whether a content script config applies to a document given config, url, and isTopLevel equals false, then continue.

    2. If currentPhase is equal to document-start and config’s css is not empty, then:

      1. For each styleSource of config’s css:

        1. Run inject a stylesheet into a document given document and styleSource.

    3. Otherwise, if currentPhase equals config’s runAt, then:

      1. For each scriptSource of config’s js:

        1. Run inject a script into a document given document and scriptSource.

To inject a stylesheet into a document given a Document document and a string styleSource, run the following steps:
  1. Let styleSheet be a new CSS style sheet object.

  2. Synchronously replace the rules of a CSSStyleSheet given styleSheet and styleSource.

  3. Add a CSS style sheet given document and styleSheet.

The following algorithm executes script in a Document’s environment, but that isn’t the desired behavior. The goal, which cannot be specced with the current HTML specification infrastructure, is for this algorithm to execute script in an environment that is isolated from the Document’s environment with a different global object, but with shared access to the DOM. This execution environment is called an Isolated World in Blink, which uses it to execute content scripts in extensions. See this diagram for additional details about their execution model. Gecko uses a similar approach called Xray vision. This algorithm should eventually describe a speccable implementation of this isolation that can be implemented by all browsers.

To inject a script into a document given a Document document and a string scriptSource, run the following steps:
  1. Let script be the result of creating a classic script given scriptSource, document’s relevant settings object, document’s URL, and the default script fetch options.

  2. Return the result of running a classic script given script.

The addContentScripts(contentScriptList) method steps are:
  1. If contentScriptList is empty, return a promise that is rejected with a TypeError.

  2. Let promises be an empty list.

  3. For each contentScript in contentScriptList:

    1. Let promise be the result of calling validate and resolve ContentScriptDetails given contentScript.

    2. Append promise to promises.

  4. Return the result of calling Promise.all given promises.

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

  2. Return result and run the remaining steps in parallel.

  3. If scriptNameList is undefined, then:

    1. Clear this’s content script map.

  4. Otherwise, for each name of scriptNameList:

    1. Remove this’s content script map[name].

  5. Resolve result.

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

  2. Return result and run the remaining steps in parallel.

  3. If this’s embedded navigable is null, reject result with a TypeError and abort these steps.

  4. If details["code"] and details"[file"] are either both defined or both undefined, reject result with a TypeError and abort these steps.

  5. Let executionSteps be the following algorithm that takes a long and a string or boolean scriptString:

    1. If scriptString is not a string, then reject result with a TypeError and abort these steps.

    2. Let document be this’s embedded navigable’s active document.

    3. Let status be the result of injecting a script into a document given document and scriptString.

    4. If status is a normal completion, then:

      1. Resolve result with status.[[Value]].

    5. Otherwise:

      1. Reject result with status.[[Value]].

  6. If details["code"] is defined, then run executionSteps given 0 and details["code"].

  7. Otherwise, fetch an injection item given this, details["file"], false, 0, and executionSteps.

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

  2. Return promise and run the remaining steps in parallel.

  3. If this’s embedded navigable is null, reject promise with a TypeError and abort these steps.

  4. If details["code"] and details["file"] are either both defined or both undefined, reject result with a TypeError and abort these steps.

  5. Let executionSteps be the following algorithm that takes a long and a string or boolean styleString:

    1. If styleString is not a string, then reject result with a TypeError and abort these steps.

    2. Let document be this’s embedded navigable’s active document.

    3. Inject a stylesheet into a document given document and styleString.

    4. Resolve promise.

  6. If details["code"] is defined, then run executionSteps given 0 and details["code"].

  7. Otherwise, fetch an injection item given this, details["file"], false, 0, and executionSteps.

3.4. 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. Return resultPromise and run the remaining steps in parallel.

  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. Return resultPromise and run the remaining steps in parallel.

  3. If this’s embedded navigable is null, then reject resultPromise with a TypeError and abort these steps.

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

  5. Then, resolve resultPromise with muteState.

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

  2. Return resultPromise and run the remaining steps in parallel.

  3. If this’s embedded navigable is null, then reject resultPromise with a TypeError and abort these steps.

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

  5. Then, resolve resultPromise with zoomFactor.

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

  2. Return resultPromise and run the remaining steps in parallel.

  3. If this’s embedded navigable is null, then reject resultPromise with a TypeError and abort these steps.

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

  5. Then, resolve resultPromise with muteState.

The setAudioMuted(mute) method steps are:
  1. If this’s embedded navigable is null, then throw a TypeError.

  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. Return resultPromise and run the remaining steps in parallel.

  3. If this’s embedded navigable is null, then reject resultPromise with a TypeError and abort these steps.

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

  5. Then, resolve resultPromise.

3.5. 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. Return resultPromise and run the remaining steps in parallel.

  3. If this’s embedded navigable is null, then reject resultPromise with a TypeError and abort these steps.

  4. Let optionsFormat be "JPEG" by default.

  5. Let optionsQuality be 100 by default.

  6. If options has field "format":

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

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

  8. If options has field "quality":

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

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

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

  11. Then, resolve resultPromise containing the image data.

The print() method steps are:
  1. If this’s embedded navigable is null, then throw a TypeError.

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

3.6. Events

HTMLControlledFrameElement implements EventTarget and supports the following event handlers (and their corresponding event handler event types).

Event handlers Event handler event types
onconsolemessage consolemessage
oncontentload contentload
ondialog dialog
onloadabort loadabort
onloadcommit loadcommit
onloadstart loadstart
onloadstop loadstop
onnewwindow newwindow
onpermissionrequest permissionrequest
onsizechanged sizechanged
onzoomchange zoomchange

The interactive events:

The UI-change events:

The navigation events:

3.6.1. consolemessage

[Exposed=Window, IsolatedContext]
interface ConsoleMessage {
  readonly attribute long level;
  readonly attribute DOMString message;
};

[Exposed=Window, IsolatedContext]
interface ConsoleMessageEvent : Event {
  constructor(DOMString type, optional ConsoleMessageEventInit eventInitDict = {});
  readonly attribute ConsoleMessage consoleMessage;
};

dictionary ConsoleMessageEventInit: EventInit {
  ConsoleMessage? consoleMessage;
};

To fire a ConsoleMessageEvent e given controlledframe element target and the Printer(logLevel, args[, options]) method arguments.

  1. Let consoleMessage be a new ConsoleMessage object.

  2. Set the following fields of consoleMessage:

    level

    logLevel.

    message

    The result of implementation-defined formatting to args.

  3. Set e’s consoleMessage to consoleMessage.

  4. Dispatch e at target.

TODO: monkey patch console Printer.

3.6.2. dialog

[Exposed=Window, IsolatedContext]
interface DialogController {
  undefined okay(optional DOMString response);
  undefined cancel();
};

[Exposed=Window, IsolatedContext]
interface DialogMessage {
  readonly attribute DOMString messageType;
  readonly attribute DOMString messageText;
  readonly attribute DialogController dialogController;
};

[Exposed=Window, IsolatedContext]
interface DialogEvent : Event {
  constructor(DOMString type, optional DialogEventInit eventInitDict = {});
  readonly attribute DialogMessage dialogMessage;
};

dictionary DialogEventInit: EventInit {
  DialogMessage? dialogMessage;
};

To fire a DialogEvent e given controlledframe element target, simple-dialogs type dialogType, and message message.

  1. Let dialogMessage be a new DialogMessage object.

  2. Set the following fields of dialogMessage:

    messageType

    dialogType, one of "alert", "confirm", or "prompt".

    messageText

    message.

  3. Set e’s dialogMessage to dialogMessage.

  4. Dispatch e at target.

TODO: monkey patch simple-dialogs.

3.6.3. newwindow

[Exposed=Window, IsolatedContext]
interface NewWindowController {
  undefined attach(HTMLControlledFrameElement controlledFrame);
  undefined discard();
};

[Exposed=Window, IsolatedContext]
interface NewWindow {
  readonly attribute NewWindowController window;
  readonly attribute USVString targetUrl;
  readonly attribute DOMString initialWidth;
  readonly attribute DOMString initialHeight;
  readonly attribute DOMString name;
  readonly attribute DOMString windowOpenDisposition;
};

[Exposed=Window, IsolatedContext]
interface NewWindowEvent : Event {
  constructor(DOMString type, optional NewWindowEventInit eventInitDict = {});
  readonly attribute NewWindow newWindow;
};

dictionary NewWindowEventInit: EventInit {
  NewWindow? newWindow;
};

TODO:.

3.6.4. permissionrequest

[Exposed=Window, IsolatedContext]
interface PermissionRequestControllerBase {
  undefined allow();
  undefined cancel();
};

[Exposed=Window, IsolatedContext]
interface PermissionRequest {
  readonly attribute DOMString permission;
  readonly attribute PermissionRequestControllerBase request;
};

[Exposed=Window, IsolatedContext]
interface PermissionRequestEvent : Event {
  constructor(DOMString type, optional PermissionRequestEventInit eventInitDict = {});
  readonly attribute PermissionRequest permissionRequest;
};

dictionary PermissionRequestEventInit: EventInit {
  PermissionRequest? permissionRequest;
};

TODO: Different child interfaces of PermissionRequestControllerBase for different permissions

3.6.5. sizechanged

[Exposed=Window, IsolatedContext]
interface SizeChange {
  readonly attribute long oldWidth;
  readonly attribute long oldHeight;
  readonly attribute long newWidth;
  readonly attribute long newHeight;
};

[Exposed=Window, IsolatedContext]
interface SizeChangedEvent : Event {
  constructor(DOMString type, optional SizeChangedEventInit eventInitDict = {});
  readonly attribute SizeChange sizeChange;
};

dictionary SizeChangedEventInit: EventInit {
  SizeChange? sizeChange;
};

TODO:

3.6.6. zoomchange

[Exposed=Window, IsolatedContext]
interface ZoomChange {
  readonly attribute float oldZoomFactor;
  readonly attribute float newZoomFactor;
};

[Exposed=Window, IsolatedContext]
interface ZoomChangeEvent : Event {
  constructor(DOMString type, optional ZoomChangeEventInit eventInitDict = {});
  readonly attribute ZoomChange zoomChange;
};

dictionary ZoomChangeEventInit: EventInit {
  ZoomChange? zoomChange;
};

TODO:

3.6.7. contentload

[Exposed=Window, IsolatedContext]
interface ContentLoadEvent : Event {
  constructor(DOMString type, optional ContentLoadEventInit eventInitDict = {});
};

dictionary ContentLoadEventInit: EventInit {
};

TODO:

3.6.8. loadabort

[Exposed=Window, IsolatedContext]
interface LoadInfo {
  readonly attribute USVString url;
  readonly attribute boolean isTopLevel;
};

[Exposed=Window, IsolatedContext]
interface LoadAbortInfo : LoadInfo{
  readonly attribute long code;
  readonly attribute DOMString reason;
};

[Exposed=Window, IsolatedContext]
interface LoadAbortEvent : Event {
  constructor(DOMString type, optional LoadAbortEventInit eventInitDict = {});
  readonly attribute LoadAbortInfo loadAbortInfo;
};

dictionary LoadAbortEventInit: EventInit {
  LoadAbortInfo? loadAbortInfo;
};

TODO:

3.6.9. loadcommit

[Exposed=Window, IsolatedContext]
interface LoadCommitEvent : Event {
  constructor(DOMString type, optional LoadCommitEventInit eventInitDict = {});
  readonly attribute LoadInfo loadInfo;
};

dictionary LoadCommitEventInit: EventInit {
  LoadInfo? loadInfo;
};

TODO:

3.6.10. loadstart

[Exposed=Window, IsolatedContext]
interface LoadStartEvent : Event {
  constructor(DOMString type, optional LoadStartEventInit eventInitDict = {});
  readonly attribute LoadInfo loadInfo;
};

dictionary LoadStartEventInit: EventInit {
  LoadInfo? loadInfo;
};

TODO:

3.6.11. loadstop

[Exposed=Window, IsolatedContext]
interface LoadStopEvent : Event {
  constructor(DOMString type, optional LoadStopEventInit eventInitDict = {});
};

dictionary LoadStopEventInit: EventInit {
};

TODO:

3.7. Integration with other specifications

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

3.7.1. Monkey Patches

3.7.1.1. [HTML]

Each navigable has:

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

  1. Set navigable’s parent to parent.

  2. If parent is not null (navigable is not a top-level traversable), then:
    1. Let topLevelTraversable be the top-level traversable that navigable is a descendant of.
    2. Set navigable’s frameId to topLevelTraversable’s next frameId.
    3. Increment topLevelTraversable’s next frameId.

TODO: Monkeypatch in calls to the inject content scripts into a document algorithm.

3.7.1.2. [FETCH]

The determine the network partition key algorithm is monkey extended to require double-keying on network requests originating from a Controlled Frame’s embedded navigable.

To determine the network partition key, given an environment environment:
  1. Let topLevelSite be the result of obtaining a site, given topLevelOrigin.

  2. Let secondKey be null or an implementation-defined value.

  3. Let embedderParent be the result of getting an environment’s embedderParent given environment.
  4. If embedderParent is not null, then set secondKey to embedderParent’s partition.
  5. Return (topLevelSite, secondKey).

3.7.1.3. [STORAGE]

The obtain a storage key for non-storage purposes algorithm is extended to require double-keying on all storage belonging to a controlledframe’s embedded navigable.

To obtain a storage key for non-storage purposes, given an environment environment, run these steps:
  1. Let origin be environment’s origin if environment is an environment settings object; otherwise environment’s creation URL’s origin.

  2. Let embedderParent be the result of getting an environment’s embedderParent given environment.
  3. If embedderParent is not null, then return a tuple consisting of embedderParent’s partition and origin.
  4. Return a tuple consisting of origin.

To get an environment’s embedderParent given an environment environment, run the following steps:
  1. If environment is an environment settings object whose global object is a Window object, then:

    This algorithm doesn’t work for Shared or Service Workers because embedderParent is only defined on a navigable, and it’s not always possible to go from a non-Window environment to a navigable.

    1. Let navigable be environment’s global object’s navigable.

    2. Let top be the top-level traversable of navigable.

    3. If top’s embedderParent is not null, then return top’s embedderParent.

  2. Return null.

4. Web Request API

This API is based on the WebRequest API, which is available through the <webview> element in Chrome Apps. It is exposed to Controlled Frame with minimum modification to make efficient use of prior art. As a result, this API does not fully adhere to Web Platform best practices, particularly around naming conventions and event handling. If there is future cross-browser interest in Controlled Frame, this API should be revisited to align better with other Web APIs.

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

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. In parallel, clear the HTTP cache associated with the partition used by environments embedded within this HTMLControlledFrameElement.

    Note: The behavior of event handlers registered through the WebRequest API will be reflected in the HTTP cache, which will no longer be valid if the behavior of the event handlers changed. The purpose of this method is to invalidate any cache entries that were affected by WebRequest event handlers.

  2. Invoke callback when the HTTP cache has been cleared.

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 process beforeSendHeaders events given a request request, run the following steps:
  1. Let handlers be the result of calling lookup registered WebRequest handler configs given "beforeSendHeaders", and request.

  2. For each handler in handlers:

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

    2. If handler’s requestsHeaders is true, then set details’s requestHeaders to the result of calling convert a header list to an HttpHeader sequence given request’s header list, handler’s requestsAllHeaders, and isRequest equal to true.

    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", "requestHeaders" », then throw a TypeError.

      3. Return result.

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

    5. Return null.

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

  2. For each handler in handlers:

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

    2. If handler’s requestsHeaders is true, then set details’s requestHeaders to the result of calling convert a header list to an HttpHeader sequence given request’s header list, handler’s requestsAllHeaders, and isRequest equal to true.

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

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

  2. For each handler in handlers:

    1. Let details be the result of calling create a WebRequestResponseEventDetails object given request and response.

    2. If handler’s requestsHeaders is true, then set details’s responseHeaders to the result of calling convert a header list to an HttpHeader sequence given response’s header list, handler’s requestsAllHeaders, and isRequest equal to false.

    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", "redirectUrl", "responseHeaders" », then throw a TypeError.

      3. Return result.

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

    5. Return null.

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

  2. Let challenger be an AuthChallenger with host and port equal to the host and port of response’s URL.

  3. If response’s status is 401, then:

    1. Let isProxy be false.

    2. Let scheme be the scheme from response’s WWW-Authenticate header, parsed as defined by the [HTML] specification, or null if the header is missing or invalid.

    3. Let realm be the realm from response’s WWW-Authenticate header, parsed as defined by the [HTML] specification.

  4. Otherwise:

    1. Let isProxy be true.

    2. Let scheme be the scheme from response’s Proxy-Authenticate header, parsed as defined by the [HTML] specification, or null if the header is missing or invalid.

    3. Let realm be the realm from response’s Proxy-Authenticate header, parsed as defined by the [HTML] specification.

  5. If scheme is null, then return.

  6. For each handler in handlers:

    1. Let details be the result of calling create a WebRequestResponseEventDetails object given request and response.

    2. If handler’s requestsHeaders is true, then set details’s responseHeaders to the result of calling convert a header list to an HttpHeader sequence given response’s header list, handler’s requestsAllHeaders, and isRequest equal to false.

      1. Set details challenger, isProxy, scheme, and realm fields equal to challenger, isProxy, scheme, and realm respectively.

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

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

      2. Return the result of calling process an authRequired response given result.

    4. Otherwise, if handler’s asyncBlocking flat is true, then:

      1. Let result be null.

      2. Let callback be a function that takes a BlockingResponse blockingResponse argument and when executed calls process an authRequired response with blockingResponse and sets result equal to its return value.

      3. Let result be the result of calling handler[handler] given details and callback.

      4. Pause these steps until callback has been invoked.

      5. Return result.

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

    6. Return null.

To process an authRequired response given a BlockingResponse blockingResponse, run the following steps:
  1. If any key in blockingResponse is not contained in the set « "cancel", "authCredentials" », or if blockingResponse ["authCredentials"] does not contain two keys equal to "username" and "password", then throw a TypeError.

  2. Return blockingResponse.

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

  2. For each handler in handlers:

    1. Let details be the result of calling create a WebRequestResponseWithIpEventDetails object given request and response.

    2. If handler’s requestsHeaders is true, then set details’s responseHeaders to the result of calling convert a header list to an HttpHeader sequence given response’s header list, handler’s requestsAllHeaders, and isRequest equal to false.

    3. Let internalResponse be response, if response is not a filtered response; otherwise response’s internal response.

    4. Set details’s redirectUrl to internalResponse’s location URL given request’s current URL’s fragment.

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

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

  2. For each handler in handlers:

    1. Let details be the result of calling create a WebRequestResponseWithIpEventDetails object given request and response.

    2. If handler’s requestsHeaders is true, then set details’s responseHeaders to the result of calling convert a header list to an HttpHeader sequence given response’s header list, handler’s requestsAllHeaders, and isRequest equal to false.

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

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

  2. For each handler in handlers:

    1. Let details be the result of calling create a WebRequestResponseWithIpEventDetails object given request and response.

    2. If handler’s requestsHeaders is true, then set details’s responseHeaders to the result of calling convert a header list to an HttpHeader sequence given response’s header list, handler’s requestsAllHeaders, and isRequest equal to false.

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

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

  2. For each handler in handlers:

    1. Let details be the result of calling create a WebRequestResponseWithIpEventDetails object given request and response.

    2. If handler’s requestsHeaders is true, then set details’s responseHeaders to the result of calling convert a header list to an HttpHeader sequence given response’s header list, handler’s requestsAllHeaders, and isRequest equal to false.

    3. Set handler’s error field to an implementation-defined error message describing the error in response.

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

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 create a WebRequestResponseEventDetails object given a request request, a response response, a boolean requestsHeaders, and a boolean requestsAllHeaders, run the following steps:
  1. Let details be the result of creating a WebRequestEventDetails object given request.

  2. Update the following fields in details:

    statusCode

    response’s status.

    statusLine

    response’s status message.

  3. If requestsHeaders is true, then set details’s responseHeaders to the result of calling convert a header list to an HttpHeader sequence given response’s header list, requestsAllHeaders, and isRequest equal to false.

  4. Return details.

To create a WebRequestResponseWithIpEventDetails object given a request request, a response response, a boolean requestsHeaders, and a boolean requestsAllHeaders, run the following steps:
  1. Let details be the result of creating a WebRequestResponseEventDetails object given request, response, requestsHeaders, and requestsAllHeaders.

  2. If response’s cache state is "local", then set details’s fromCache to true.

  3. Set details’s ip to the ip address from which response was received if the request involved a network request.

    The [FETCH] spec currently doesn’t specify storing the remote IP address used when sending request.

  4. Return details.

To convert a header list to an HttpHeader sequence given a header list fetchHeaders, a boolean requestsAllHeaders, and a boolean isRequest, run the following steps:
  1. Let headers be a an empty list of HttpHeader objects.

  2. For each fetchHeader in fetchHeaders:

    1. If each of the following conditions are true, then continue:

    2. If each of the following conditions are true, then continue:

    3. Let header be a new HttpHeader.

    4. Set header["name"] to the isomorphic decoding of fetchHeader[0].

    5. Let value be the isomorphic decoding of fetchHeader[1] .

    6. If value is a scalar value string, then set header ["value"] to value.

    7. Otherwise, set header["binaryValue"] to fetchHeader[1].

    8. Append header to headers.

  3. Return headers.

To convert an HttpHeader sequence to a header list given a HttpHeader headers, run the following steps:
  1. Let headerList be a an empty header list.

  2. For each header in headers:

    1. If any of the following conditions are true, throw a TypeError:

      • header["name"] is not a string.

      • header["value"] is not a string or header["binaryValue"] is not an list of Numbers.

    2. Let value be header["value"] if it exists, or the isomorphic decoding of header["binaryValue"] otherwise.

    3. Append a new tuple with values « header["name"], value » to headerList.

  3. Return headerList.

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. If client’s corresponding global object is not a Window, return an empty list.

  4. Let navigable be the navigable that client’s global object is within.

  5. If navigable’s embedderParent is null, return an empty list.

  6. Let controlledFrame be the HTMLControlledFrameElement corresponding to navigable’s embedderParent.

  7. Let valid handlers be an empty list.

  8. Let handlerMap be controlledFrame’s request’s handler map.

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

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

The main fetch algorithm is monkey patched as follows:

  1. Let request be fetchParams’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"].

The fetch response handover algorithm is monkey patched as follows:

  1. If response is not a network error, then call process responseStarted events given fetchParams’s request and response.
  2. Let timingInfo be fetchParams’s timing info.

  1. If fetchParams’s process response consume body is non-null, then:

  2. If response is not a network error, then call process completed events given fetchParams’s request and response.
  3. Otherwise, call process errorOccurred events given fetchParams’s request and response.

The HTTP-network-or-cache fetch algorithm is monkey patched as follows:

  1. Let the revalidatingFlag be unset.

  2. Set webRequestResult to the result of calling process beforeSendHeaders events given request.
  3. If webRequestResult is not null, then:
    1. If webRequestResult["cancel"] is true, then set response to a network error.
    2. Otherwise, if webRequestResult ["requestHeaders"] is a non-empty list, then set request’s header list to the result of converting an HttpHeader sequence to a header list given webRequestResult ["requestHeaders"].
  4. If response is not null, run these steps, but abort when fetchParams is canceled:

  5. If aborted, then return the appropriate network error for fetchParams.

  6. If response is null, then:

    1. If httpRequest’s cache mode is "only-if-cached", then return a network error.

    2. Call process sendHeaders events given request.
    3. Let forwardResponse be the result of running HTTP-network fetch given httpFetchParams, includeCredentials, and isNewConnectionFetch.

    4. Let webRequestResult be the result of calling process headersReceived events given request and forwardResponse if forwardResponse is not a network error, null otherwise.
    5. 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 forwardResponse to the result of creating a redirect response given webRequestResult["redirectUrl"].
      3. Otherwise, if webRequestResult ["responseHeaders"] is a non-empty list, then set forwardResponse’s header list to the result of converting an HttpHeader sequence to a header list given webRequestResult ["responseHeaders"].

  1. If response’s status is 401, httpRequest’s response tainting is not "cors", includeCredentials is true, and request’s window is an environment settings object, then:

    1. If request’s use-URL-credentials flag is unset or isAuthenticationFetch is true, then:

      1. If fetchParams is canceled, then return the appropriate network error for fetchParams.

      2. Let username and password be null.

      3. Let webRequestResult be the result of calling process authRequired events given request and response.
      4. If webRequestResult is not null, then:
        1. If webRequestResult["cancel"] is true, then set response to a network error.
        2. Otherwise, if webRequestResult["authCredentials"] is an object, then:
          1. Set username to webRequestResult["authCredentials"]["username"].

          2. Set password to webRequestResult["authCredentials"]["password"].

      5. If username and password are null, then set them to the result of prompting the end user for a username and password, respectively, in request’s window.
  2. If response’s status is 407, then:

    1. Let webRequestResult be the result of calling process authRequired events given request and response.
    2. If webRequestResult is not null, then:
      1. If webRequestResult["cancel"] is true, then set response to a network error.
      2. Otherwise, if webRequestResult["authCredentials"] is an object, then:
        1. Store webRequestResult["authCredentials"] as a proxy-authentication entry.

    3. Otherwise, prompt the end user as appropriate ...

The HTTP-redirect fetch algorithm is monkey patched as follows:

  1. Call process beforeRedirect events given request and response.
  2. Let request be fetchParams’s request.

5. Context Menus API

This API is based on the ContextMenus API, which is available through the <webview> element in Chrome Apps. It is exposed to Controlled Frame with minimum modification to make efficient use of prior art. As a result, this API does not fully adhere to Web Platform best practices, particularly around naming conventions and event handling. If there is future cross-browser interest in Controlled Frame, this API should be revisited to align better with other Web APIs.

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 ContextMenusCallback = undefined (optional OnClickData info);

[Exposed=Window, IsolatedContext]
interface ContextMenusEvent {
    undefined addListener(ContextMenusCallback callback);
    undefined removeListener(ContextMenusCallback callback);
    boolean hasListener(ContextMenusCallback callback);
    boolean hasListeners();
    undefined dispatch(any data);
};

[Exposed=Window, IsolatedContext]
interface OnClickedEvent : ContextMenusEvent {};

[Exposed=Window, IsolatedContext]
interface OnShowEvent : ContextMenusEvent {};

dictionary ContextMenusProperties {
    boolean checked;
    sequence<ContextType> context;
    sequence<USVString> documentUrlPatterns;
    boolean enabled;
    (DOMString or long) parentId;
    sequence<USVString> targetUrlPatterns;
    DOMString title;
    ItemType type;
    ContextMenusCallback onclick;
};

dictionary ContextMenusCreateProperties : ContextMenusProperties {
    required DOMString id;
};

[Exposed=Window, IsolatedContext]
interface ContextMenus {
    attribute OnClickedEvent onClicked;
    attribute OnShowEvent onShow;

    Promise<undefined> create(ContextMenusCreateProperties properties);
    Promise<undefined> remove(DOMString id);
    Promise<undefined> removeAll();
    Promise<undefined> update(DOMString id, optional ContextMenusProperties properties = {});
};

Each controlledframe has a contextMenus member, which is a ContextMenus that manages a context menu map whose keys are strings and whose values are ContextMenusProperties.

Note: Each entry of the context menu map represents a context menu item. These items can show up when the user opens the context menu on the embedded document.

The create(properties) method steps are:

  1. Let p be a new promise.

  2. Run the following steps in parallel:

    1. Let contextMenusMap be this’s context menu map.

    2. Let id be the properties["menuItemId"].

    3. If contextMenusMap[id] exists, reject p with a TypeError and abort these steps.

    4. Set contextMenusMap[id] to properties.

    5. Resolve p with undefined.

The remove(id) method steps are:

  1. Let p be a new promise.

  2. Run the following steps in parallel:

    1. Let contextMenusMap be this’s context menu map.

    2. Remove contextMenusMap[id].

    3. Resolve p with undefined.

The removeAll() method steps are:

  1. Let p be a new promise.

  2. Run the following steps in parallel:

    1. Let contextMenusMap be this’s context menu map.

    2. Clear contextMenusMap.

    3. Resolve p with undefined.

The update(id, properties) method steps are:

  1. Let p be a new promise.

  2. Run the following steps in parallel:

    1. Let contextMenusMap be this’s context menu map.

    2. If contextMenusMap[id] does not exist, reject p with a TypeError and abort these steps.

    3. Set contextMenusMap[id] to properties.

    4. Resolve p with undefined.

The values of the context menu map, which are ContextMenusProperties objects, control the conditions under which the context menu items are displayed, as well as the features and behaviors of the items.

This section is non-normative.

ContextMenusProperties has the following fields:

context

A list of different ContextType, used to control the conditions under which the context menu item is displayed.

documentUrlPatterns

A list of strings that are match patterns, used to control the conditions under which the context menu item is displayed.

targetUrlPatterns

A list of strings that are match patterns, used to control the conditions under which the context menu item is displayed.

parentId

The ID of the parent context menu item. The context menu item may appear under a sub-menu of the parent.

title

The title of the menu item.

type

The type of the menu item.

onclick

A ContextMenusCallback that is invoked when the context menu item is clicked.

checked

Whether the context menu item is initially checked, if the type is checkbox.

enabled

Whether the context menu item is enabled.

When a context menu is opened at HTMLElement element inside the embedded document, to return a boolean value for whether the context menu item associated with a ContextMenusProperties properties will be shown:

  1. Let documentUrl be the URL of the document that element belongs to.

  2. Let targetUrl be an empty string.

    1. If element is an instance of HTMLImageElement, then set targetUrl to the src attribute of element.

    2. If element is an instance of HTMLVideoElement, then set targetUrl to the src attribute of element.

    3. If element is an instance of HTMLAudioElement, then set targetUrl to the src attribute of element.

    4. If element is an instance of HTMLAnchorElement, then set targetUrl to the href attribute of element.

  3. If properties["context"] is not empty:

    1. Let result be a boolean value that is initially false.

    2. For each context in properties["context"]:

      1. If element matches context, set result to true and break;

      There is no standardized specifications for matching a HTMLElement against a ContextType.

    3. If result is false, return false.

  4. If properties["documentUrlPatterns"] is not empty:

    1. Let result be a boolean value that is initially false.

    2. For each urlPattern in properties["documentUrlPatterns"]:

      1. If documentUrl matches a URL pattern, given urlPattern, set result to true and break;

    3. If result is false, return false.

  5. If properties["targetUrlPatterns"] is not empty:

    1. Let result be a boolean value that is initially false.

    2. For each urlPattern in properties["targetUrlPatterns"]:

      1. If targetUrl matches a URL pattern, given urlPattern, set result to true and break;

    3. If result is false, return false.

Note: Different user agents may implement additional conditions.

  1. Return true.

ContextMenusEvent represents a target to which an event can be dispatched when a context menu interaction has occured.

Each ContextMenusEvent has an event listener list which is a list of ContextMenusCallbacks. It is initially an empty list.

The addListener(callback) method steps are:

  1. Let callbackList be the event listener list of the ContextMenusEvent.

  2. If callbackList[callback] exists, return.

  3. Append callback to callbackList.

The removeListener(callback) method steps are:

  1. Let callbackList be the event listener list of the ContextMenusEvent.

  2. If callbackList[callback] exists, remove callback from callbackList.

The hasListener(callback) method steps are:

  1. Let callbackList be the event listener list of the ContextMenusEvent.

  2. If callbackList[callback] exists, return true.

  3. Otherwise, return false.

The hasListeners() method steps are:

  1. Let callbackList be the event listener list of the ContextMenusEvent.

  2. If callbackList is empty, return false.

  3. Otherwise, return true.

The dispatch(data) method steps are:

  1. Let callbackList be the event listener list of the ContextMenusEvent.

  2. For each callback in callbackList:

    1. Invoke callback with data.

ContextMenus has the attribute onShow, which is a ContextMenusEvent that is associated with contextmenu events.

To dispatched an onShow event:

  1. Invoke onShow’s dispatch(data) with no argument.

ContextMenus has the attribute onClicked, which is a ContextMenusEvent that is associated with click events on a context menu item.

To dispatch an onClicked event, given element as the HTMLElement inside the embedded document where the context menu is opened at, id as the key of the context menu map’s entry that is associated with the context menu item.

  1. Let info be a new OnClickData.

    1. Update the following fields of info:

      editable

      Whether element is editable. what is "editable"?

      frameId

      The frameId of the navigable container from which the contextmenu event is dispatched.

      frameUrl

      The URL of the navigable container from which the contextmenu event is dispatched.

      menuItemId

      id.

      pageUrl

      The URL of the embedded navigable.

    2. Update the following optional fields of info:

      Note: Different user agents may set these fields differently.

      checked

      Whether the context menu item is checked.

      linkUrl

      If element is an instance of HTMLAnchorElement, element’s href attribute.

      mediaType

      One of "image", "video", or "audio", if any, based on element.

      parentMenuId

      The ID of the parent, if a parent exists.

      selectionText

      The text for the context selection, if any.

      srcUrl

      The "src" attribute of element, if it exists.

      wasChecked

      The state of a checkbox or radio item before it was clicked.

  2. Invoke onShow’s dispatch(data) with info.

  3. Let properties be the result of getting the value of the entry in context menu map given the key id.

  4. If the onclick field of properties is not null, invoke onclick with info.

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

[CONSOLE]
Dominic Farolino; Robert Kowalski; Terin Stock. Console Standard. Living Standard. URL: https://console.spec.whatwg.org/
[CSS-SYNTAX-3]
Tab Atkins Jr.; Simon Sapin. CSS Syntax Module Level 3. URL: https://drafts.csswg.org/css-syntax/
[CSSOM-1]
Daniel Glazman; Emilio Cobos Álvarez. CSS Object Model (CSSOM). URL: https://drafts.csswg.org/cssom/
[DOM]
Anne van Kesteren. DOM Standard. Living Standard. URL: https://dom.spec.whatwg.org/
[ECMASCRIPT]
ECMAScript Language Specification. URL: https://tc39.es/ecma262/multipage/
[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
[MANIFEST-APP-INFO]
Aaron Gustafson. Web App Manifest - Application Information. URL: https://w3c.github.io/manifest-app-info/
[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
[STORAGE]
Anne van Kesteren. Storage Standard. Living Standard. URL: https://storage.spec.whatwg.org/
[UIEVENTS]
Gary Kacmarcik; Travis Leithead. UI Events. URL: https://w3c.github.io/uievents/
[URL]
Anne van Kesteren. URL Standard. Living Standard. URL: https://url.spec.whatwg.org/
[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();

    // Events:
    attribute EventHandler onconsolemessage;
    attribute EventHandler oncontentload;
    attribute EventHandler ondialog;
    attribute EventHandler onloadabort;
    attribute EventHandler onloadcommit;
    attribute EventHandler onloadstart;
    attribute EventHandler onloadstop;
    attribute EventHandler onnewwindow;
    attribute EventHandler onpermissionrequest;
    attribute EventHandler onsizechanged;
    attribute EventHandler onzoomchange;
};

// 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 {
  required DOMString name;
  InjectionItems js;
  InjectionItems css;
  required sequence<DOMString> matches;
  sequence<DOMString> excludeMatches;
  boolean allFrames;
  boolean matchAboutBlank;
  RunAt runAt;
};

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

[Exposed=Window, IsolatedContext]
interface ConsoleMessage {
  readonly attribute long level;
  readonly attribute DOMString message;
};

[Exposed=Window, IsolatedContext]
interface ConsoleMessageEvent : Event {
  constructor(DOMString type, optional ConsoleMessageEventInit eventInitDict = {});
  readonly attribute ConsoleMessage consoleMessage;
};

dictionary ConsoleMessageEventInit: EventInit {
  ConsoleMessage? consoleMessage;
};


[Exposed=Window, IsolatedContext]
interface DialogController {
  undefined okay(optional DOMString response);
  undefined cancel();
};

[Exposed=Window, IsolatedContext]
interface DialogMessage {
  readonly attribute DOMString messageType;
  readonly attribute DOMString messageText;
  readonly attribute DialogController dialogController;
};

[Exposed=Window, IsolatedContext]
interface DialogEvent : Event {
  constructor(DOMString type, optional DialogEventInit eventInitDict = {});
  readonly attribute DialogMessage dialogMessage;
};

dictionary DialogEventInit: EventInit {
  DialogMessage? dialogMessage;
};


[Exposed=Window, IsolatedContext]
interface NewWindowController {
  undefined attach(HTMLControlledFrameElement controlledFrame);
  undefined discard();
};

[Exposed=Window, IsolatedContext]
interface NewWindow {
  readonly attribute NewWindowController window;
  readonly attribute USVString targetUrl;
  readonly attribute DOMString initialWidth;
  readonly attribute DOMString initialHeight;
  readonly attribute DOMString name;
  readonly attribute DOMString windowOpenDisposition;
};

[Exposed=Window, IsolatedContext]
interface NewWindowEvent : Event {
  constructor(DOMString type, optional NewWindowEventInit eventInitDict = {});
  readonly attribute NewWindow newWindow;
};

dictionary NewWindowEventInit: EventInit {
  NewWindow? newWindow;
};


[Exposed=Window, IsolatedContext]
interface PermissionRequestControllerBase {
  undefined allow();
  undefined cancel();
};

[Exposed=Window, IsolatedContext]
interface PermissionRequest {
  readonly attribute DOMString permission;
  readonly attribute PermissionRequestControllerBase request;
};

[Exposed=Window, IsolatedContext]
interface PermissionRequestEvent : Event {
  constructor(DOMString type, optional PermissionRequestEventInit eventInitDict = {});
  readonly attribute PermissionRequest permissionRequest;
};

dictionary PermissionRequestEventInit: EventInit {
  PermissionRequest? permissionRequest;
};


[Exposed=Window, IsolatedContext]
interface SizeChange {
  readonly attribute long oldWidth;
  readonly attribute long oldHeight;
  readonly attribute long newWidth;
  readonly attribute long newHeight;
};

[Exposed=Window, IsolatedContext]
interface SizeChangedEvent : Event {
  constructor(DOMString type, optional SizeChangedEventInit eventInitDict = {});
  readonly attribute SizeChange sizeChange;
};

dictionary SizeChangedEventInit: EventInit {
  SizeChange? sizeChange;
};


[Exposed=Window, IsolatedContext]
interface ZoomChange {
  readonly attribute float oldZoomFactor;
  readonly attribute float newZoomFactor;
};

[Exposed=Window, IsolatedContext]
interface ZoomChangeEvent : Event {
  constructor(DOMString type, optional ZoomChangeEventInit eventInitDict = {});
  readonly attribute ZoomChange zoomChange;
};

dictionary ZoomChangeEventInit: EventInit {
  ZoomChange? zoomChange;
};


[Exposed=Window, IsolatedContext]
interface ContentLoadEvent : Event {
  constructor(DOMString type, optional ContentLoadEventInit eventInitDict = {});
};

dictionary ContentLoadEventInit: EventInit {
};


[Exposed=Window, IsolatedContext]
interface LoadInfo {
  readonly attribute USVString url;
  readonly attribute boolean isTopLevel;
};

[Exposed=Window, IsolatedContext]
interface LoadAbortInfo : LoadInfo{
  readonly attribute long code;
  readonly attribute DOMString reason;
};

[Exposed=Window, IsolatedContext]
interface LoadAbortEvent : Event {
  constructor(DOMString type, optional LoadAbortEventInit eventInitDict = {});
  readonly attribute LoadAbortInfo loadAbortInfo;
};

dictionary LoadAbortEventInit: EventInit {
  LoadAbortInfo? loadAbortInfo;
};


[Exposed=Window, IsolatedContext]
interface LoadCommitEvent : Event {
  constructor(DOMString type, optional LoadCommitEventInit eventInitDict = {});
  readonly attribute LoadInfo loadInfo;
};

dictionary LoadCommitEventInit: EventInit {
  LoadInfo? loadInfo;
};


[Exposed=Window, IsolatedContext]
interface LoadStartEvent : Event {
  constructor(DOMString type, optional LoadStartEventInit eventInitDict = {});
  readonly attribute LoadInfo loadInfo;
};

dictionary LoadStartEventInit: EventInit {
  LoadInfo? loadInfo;
};


[Exposed=Window, IsolatedContext]
interface LoadStopEvent : Event {
  constructor(DOMString type, optional LoadStopEventInit eventInitDict = {});
};

dictionary LoadStopEventInit: EventInit {
};


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

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 ContextMenusCallback = undefined (optional OnClickData info);

[Exposed=Window, IsolatedContext]
interface ContextMenusEvent {
    undefined addListener(ContextMenusCallback callback);
    undefined removeListener(ContextMenusCallback callback);
    boolean hasListener(ContextMenusCallback callback);
    boolean hasListeners();
    undefined dispatch(any data);
};

[Exposed=Window, IsolatedContext]
interface OnClickedEvent : ContextMenusEvent {};

[Exposed=Window, IsolatedContext]
interface OnShowEvent : ContextMenusEvent {};

dictionary ContextMenusProperties {
    boolean checked;
    sequence<ContextType> context;
    sequence<USVString> documentUrlPatterns;
    boolean enabled;
    (DOMString or long) parentId;
    sequence<USVString> targetUrlPatterns;
    DOMString title;
    ItemType type;
    ContextMenusCallback onclick;
};

dictionary ContextMenusCreateProperties : ContextMenusProperties {
    required DOMString id;
};

[Exposed=Window, IsolatedContext]
interface ContextMenus {
    attribute OnClickedEvent onClicked;
    attribute OnShowEvent onShow;

    Promise<undefined> create(ContextMenusCreateProperties properties);
    Promise<undefined> remove(DOMString id);
    Promise<undefined> removeAll();
    Promise<undefined> update(DOMString id, optional ContextMenusProperties properties = {});
};

Issues Index

These steps are needed to initialize History.length in the new navigable. This is an existing issue in the HTML Standard.
We can’t actually synchronously access the embedded navigable’s history state. In the future we should update this method to return a Promise.
This algorithm doesn’t work for Shared or Service Workers because embedderParent is only defined on a navigable, and it’s not always possible to go from a non-Window environment to a navigable.