Portals

A Collection of Interesting Ideas,

This version:
https://kenjibaheux.github.io/portals/
Issue Tracking:
GitHub
Inline In Spec
Editors:
(Google)
(Google)

Abstract

This specification defines a mechanism that allows for rendering of, and seamless navigation to, embedded content.

1. Introduction

This section is non-normative.

This specification extends [HTML] to define a new kind of top-level browsing context, which can be embedded in another document, and a mechanism for replacing the contents of another top-level browsing context with the previously embedded context.

2. Concepts

A portal browsing context is a browsing context created according to the steps in this specification. A portal browsing context cannot have a parent browsing context.

A portal browsing context has a host which is embeds its rendered output and receives messages sent from the portal browsing context. This specification defines HTMLPortalElement, which hosts a portal browsing context inside an HTML document. A host defines steps to accept a message posted to the host.

This implies that every portal browsing context is a top-level browsing context. It is expected that a Web browser will not present a tab or window to display a portal browsing context, but rather that it will be presented only through a host element.

To activate a portal browsing context portalBrowsingContext in place of predecessorBrowsingContext with data serializeWithTransferResult, run the following steps in parallel:
  1. Let successorWindow be portalBrowsingContext’s associated WindowProxy's [[Window]] internal slot value.

  2. Update the user interface to replace predecessorBrowsingContext with portalBrowsingContext (e.g., by updating the tab/window contents and browser chrome).

    From this point onward, portalBrowsingContext is no longer a portal browsing context and has no host.

  3. Queue a task from the DOM manipulation task source to the event loop associated with successorWindow to run the following steps:

    1. Hide the portal host object of portalBrowsingContext.

    2. Let targetRealm be successorWindow’s realm.

    3. Let dataClone be StructuredDeserializeWithTransfer(serializeWithTransferResult, targetRealm).

      If this throws an exception, catch it, and let dataClone be null instead.

    4. Let event be the result of creating an event using PortalActivateEvent.

    5. Initialize event’s type attribute to portalactivate.

    6. Initialize event’s data attribute to dataClone.

    7. Set event’s predecessor browsing context to predecessorBrowsingContext.

    8. Dispatch event to successorWindow.

    9. If predecessorBrowsingContext is not a portal browsing context, close it. The user agent should not refuse to allow the document to be unloaded.

    10. Queue a task to complete portal activation.

In the case that structured deserialization throws, it may be useful to do something else to indicate it, rather than simply providing null data.
We need to specify how the session history of each browsing context is affected by activation, and supply non-normative text that explains how these histories are expected to be presented to the user.
To adopt the predecessor browsing context predecessorBrowsingContext in document, run the following steps:
  1. Let portalElement be the result of creating an element given document, portal, and the HTML namespace.

  2. Assert: portalElement is an HTMLPortalElement.

  3. Set the guest browsing context of portalElement to predecessorBrowsingContext. From this point onward, predecessorBrowsingContext is a portal browsing context, with portalElement as its host.

  4. Queue a task from the DOM manipulation task source to the event loop associated with predecessorBrowsingContext to run the following steps:

    1. Expose the portal host object of predecessorBrowsingContext.

  5. Return portalElement.

Since the task to expose the PortalHost is queued before the task to complete portal activation, from the same task source, it is exposed at the time the promise returned from activate() is resolved.

3. API

3.1. The portal element

A portal element for a portal browsing context to be embedded in an HTML document.

A portal element may have a guest browsing context, which is a portal browsing context. If so, the element is the host of its guest browsing context.

A portal is similar to an iframe, in that it allows another browsing context to be embedded. However, the portal browsing context hosted by a portal is part of a separate unit of related browsing contexts. The user agent is thus free to use a separate event loop for the browsing contexts, even if they are same origin-domain.

[HTMLConstructor]
interface HTMLPortalElement : HTMLElement {
    [CEReactions] attribute USVString src;
    [NewObject] Promise<void> activate(optional PortalActivateOptions options);
    void postMessage(any message, DOMString targetOrigin, optional sequence<object> transfer = []);
};

dictionary PortalActivateOptions {
    any data = null;
    sequence<object> transfer = [];
};
This should also include a PostMessageOptions dictionary overload, to reflect the changes due to the user activation proposal.

The following tasks may be queued by this standard. Unless otherwise specified, each task must be queued from the DOM manipulation task source.

The activate(options) method must run these steps:
  1. Let portalBrowsingContext be the guest browsing context of the context object.

    If no such context exists, throw an "InvalidStateError" DOMException.

  2. Let predecessorBrowsingContext be the browsing context of the context object's document.

    If no such context exists, throw an "InvalidStateError" DOMException.

  3. Let serializeWithTransferResult be StructuredSerializeWithTransfer(options["data"], options["transfer"]). Rethrow any exceptions.

  4. Let promise be a new promise.

  5. Run the steps to activate portalBrowsingContext in place of predecessorBrowsingContext with data serializeWithTransferResult.

    To complete portal activation, run these steps:

    1. Resolve promise with undefined.

  6. Return promise.

The postMessage(message, targetOrigin, transfer) method must run these steps:
  1. Let portalBrowsingContext be the guest browsing context of the context object.

    If no such context exists, throw an "InvalidStateError" DOMException.

  2. Let settings be the relevant settings object of the context object.

  3. Let origin be the serialization of settings’s origin.

  4. If targetOrigin is a single U+002F SOLIDUS character (/), then set targetOrigin to the origin of settings.

  5. Let serializeWithTransferResult be StructuredSerializeWithTransfer(message, transfer). Rethrow any exceptions.

  6. Queue a task from the posted message task source to the event loop of portalBrowsingContext to run the following steps:

    1. If targetOrigin is not a single literal U+002A ASTERISK character (*) and the origin of portalBrowsingContext’s active document is not same origin with targetOrigin, then abort these steps.

    2. Let targetWindow be portalBrowsingContext’s associated WindowProxy's [[Window]] internal slot value.

    3. Let portalHost be the targetWindow’s portal host object.

    4. Let targetRealm be the targetWindow’s realm.

    5. Let deserializeRecord be StructuredDeserializeWithTransfer(serializeWithTransferResult, targetRealm).

      If this throws an exception, catch it, fire an event named messageerror at portalHost using MessageEvent with the origin attribute initialized to origin and the source attribute initialized to portalHost, then abort these steps.

    6. Let messageClone be deserializeRecord.[[Deserialized]].

    7. Let newPorts be a new frozen array consisting of all MessagePort objects in deserializeRecord.[[TransferredValues]], if any, maintaining their relative order.

    8. Fire an event named message at portalHost using MessageEvent, with the origin attribute initialized to origin, the source attribute initialized to portalHost, the data attribute initialized to messageClone, and the ports attribute initialized to newPorts.

To accept a message posted to the host for a portal element with serializeWithTransferResult, origin and targetOrigin, queue a task from the posted message task source to the event loop associated with the element’s document's browsing context to run the following steps:
  1. Let settings be the relevant settings object of the context object.

  2. If targetOrigin is not a single literal U+002A ASTERISK character (*) and settings’s origin is not same origin with targetOrigin, then abort these steps.

  3. Let targetRealm be settings’s realm.

  4. Let deserializeRecord be StructuredDeserializeWithTransfer(serializeWithTransferResult, targetRealm).

    If this throws an exception, catch it, fire an event named messageerror at the context object using MessageEvent with the origin attribute initialized to origin and the source attribute initialized to the context object,

  5. Let messageClone be deserializeRecord.[[Deserialized]].

  6. Let newPorts be a new frozen array consisting of all MessagePort objects in deserializeRecord.[[TransferredValues]], if any, maintaining their relative order.

  7. Fire an event named message at the context object using MessageEvent, with the origin attribute initialized to origin, the source attribute initialized to the context object, the data attribute initialized to messageClone, and the ports attribute initialized to newPorts.

To close a portal element, run the following steps:
  1. If the context object has a guest browsing context, then close it. The user agent should not refuse to allow the document to be unloaded.

  2. Clear the context object's guest browsing context.

To set the source URL of a portal element, run the following steps:
  1. Assert: the context object has a guest browsing context.

  2. If the context object has no src attribute specified, or its value is the empty string, then close the context object and abort these steps.

  3. Parse the value of the src attribute. If that is not successful, then close the context object and abort these steps. Otherwise, let url be the resulting URL record.

  4. Let resource be a new request whose URL is url and whose referrer policy is "no-referrer".

  5. Navigate the guest browsing context to resource.

Is no-referrer the right referrer policy here, or should we send the document’s URL?

Whenever a portal element’s src attribute is set, run the following steps:

  1. If the context object does not have a guest browsing context, abort these steps.

  2. Set the source URL of the context object.

Whenever a portal element is inserted, run the following steps:

  1. If the context object has a guest browsing context or is not connected, abort these steps.

  2. Create a new browsing context with noopener, and let newBrowsingContext be the result.

  3. Set the context object's guest browsing context to newBrowsingContext. newBrowsingContext is a portal browsing context from this point onward.

  4. Expose the portal host object of newBrowsingContext.

  5. Set the source URL of the context object.

Whenever a portal element is removed, run the following steps:

  1. If the context object does not have a guest browsing context, abort these steps.

  2. Close the context object.

It might be convenient to not immediately detach the portal element, but instead to do so in a microtask. This would allow developers to reinsert the portal element without losing its browsing context.

The following events are dispatched on HTMLPortalElement objects:

Event name Interface Dispatched when
message MessageEvent A message is received by the object, and deserialization does not throw an exception.
messageerror MessageEvent A message is received by the object, but deserialization throws an exception.

3.2. The PortalHost interface

The portal host object of a browsing context is a PortalHost. It may be either exposed or hidden; by default it is hidden.

The portal host object can be used to communicate with the host of a portal browsing context. Its operations throw if used while it’s context is not a portal browsing context (i.e. there is no host).
interface PortalHost : EventTarget {
    void postMessage(any message, DOMString targetOrigin, optional sequence<object> transfer = []);
};
This should also include a PostMessageOptions dictionary overload, to reflect the changes due to the user activation proposal.
The postMessage(message, targetOrigin, transfer) method must run these steps:
  1. If the context object's associated browsing context is not a portal browsing context, throw an "InvalidStateError" DOMException.

  2. Let settings be the relevant settings object of the context object.

  3. Let origin be the serialization of settings’s origin.

  4. If targetOrigin is a single U+002F SOLIDUS character (/), then set targetOrigin to the origin of settings.

  5. Let serializeWithTransferResult be StructuredSerializeWithTransfer(message, transfer). Rethrow any exceptions.

  6. Run the steps to accept a message posted to the host for the host associated with the context object with serializeWithTransferResult, origin and targetOrigin.

The following events are dispatched on PortalHost objects:

Event name Interface Dispatched when
message MessageEvent A message is received by the object, and deserialization does not throw an exception.
messageerror MessageEvent A message is received by the object, but deserialization throws an exception.

3.3. The PortalActivateEvent interface

interface PortalActivateEvent : Event {
    readonly attribute any data;
    HTMLPortalElement adoptPredecessor(Document document);
};

A PortalActivateEvent has an associated predecessor browsing context, which is a top-level browsing context.

The adoptPredecessor(document) method must run these steps:
  1. Let predecessorBrowsingContext be the context object's predecessor browsing context.

  2. Run the steps to adopt the predecessor browsing context predecessorBrowsingContext in document, and return the result.

3.4. Miscellaneous extensions

The MessageEventSource union is extended to include the new interfaces which can produce MessageEvent events.

typedef (WindowProxy or MessagePort or ServiceWorker or HTMLPortalElement or PortalHost) MessageEventSource;

A PortalHost is exposed at times when the window may be in a portal browsing context.

partial interface Window {
    readonly attribute PortalHost? portalHost;
};
The portalHost attribute must run the following steps:
  1. Let windowProxy be the context object's WindowProxy object.

  2. If there is no browsing context with windowProxy as its WindowProxy object, then return null.

  3. Let context be that browsing context.

  4. Let portalHostObject be the portal host object of context.

  5. If portalHostObject is hidden, then return null.

  6. Return portalHostObject.

The following events are dispatched on Window objects:

Event name Interface Dispatched when
portalactivate PortalActivateEvent The window is associated with a new top-level browsing context due to activation of its portal browsing context.

4. Security Considerations

We should explicitly cover how this specification interacts with [CSP], [RFC7034] and other specifications that confine the behavior of frames.

Conformance

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

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

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

This is an example of an informative example.

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

Note, this is an informative note.

Index

Terms defined by this specification

Terms defined by reference

References

Normative References

[DOM]
Anne van Kesteren. DOM Standard. Living Standard. URL: https://dom.spec.whatwg.org/
[ECMASCRIPT]
ECMAScript Language Specification. URL: https://tc39.github.io/ecma262/
[FETCH]
Anne van Kesteren. Fetch Standard. Living Standard. URL: https://fetch.spec.whatwg.org/
[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/
[REFERRER-POLICY]
Jochen Eisinger; Emily Stark. Referrer Policy. 26 January 2017. CR. URL: https://www.w3.org/TR/referrer-policy/
[RFC2119]
S. Bradner. Key words for use in RFCs to Indicate Requirement Levels. March 1997. Best Current Practice. URL: https://tools.ietf.org/html/rfc2119
[SERVICE-WORKERS-2]
Service Workers URL: https://w3c.github.io/ServiceWorker/
[WebIDL]
Cameron McCormack; Boris Zbarsky; Tobie Langel. Web IDL. 15 December 2016. ED. URL: https://heycam.github.io/webidl/

Informative References

[CSP]
Mike West. Content Security Policy Level 3. 13 September 2016. WD. URL: https://www.w3.org/TR/CSP3/
[RFC7034]
D. Ross; T. Gondrom. HTTP Header Field X-Frame-Options. October 2013. Informational. URL: https://tools.ietf.org/html/rfc7034

IDL Index

[HTMLConstructor]
interface HTMLPortalElement : HTMLElement {
    [CEReactions] attribute USVString src;
    [NewObject] Promise<void> activate(optional PortalActivateOptions options);
    void postMessage(any message, DOMString targetOrigin, optional sequence<object> transfer = []);
};

dictionary PortalActivateOptions {
    any data = null;
    sequence<object> transfer = [];
};

interface PortalHost : EventTarget {
    void postMessage(any message, DOMString targetOrigin, optional sequence<object> transfer = []);
};

interface PortalActivateEvent : Event {
    readonly attribute any data;
    HTMLPortalElement adoptPredecessor(Document document);
};

typedef (WindowProxy or MessagePort or ServiceWorker or HTMLPortalElement or PortalHost) MessageEventSource;

partial interface Window {
    readonly attribute PortalHost? portalHost;
};

Issues Index

In the case that structured deserialization throws, it may be useful to do something else to indicate it, rather than simply providing null data.
We need to specify how the session history of each browsing context is affected by activation, and supply non-normative text that explains how these histories are expected to be presented to the user.
This should also include a PostMessageOptions dictionary overload, to reflect the changes due to the user activation proposal.
Is no-referrer the right referrer policy here, or should we send the document’s URL?
It might be convenient to not immediately detach the portal element, but instead to do so in a microtask. This would allow developers to reinsert the portal element without losing its browsing context.
This should also include a PostMessageOptions dictionary overload, to reflect the changes due to the user activation proposal.
We should explicitly cover how this specification interacts with [CSP], [RFC7034] and other specifications that confine the behavior of frames.