1. Introduction
This section is non-normative.
This specification defines a mechanism through which a script can delegate its ability to call a restricted API to another browsing context it trusts. The focus here is a dynamic delegation mechanism that exposes the delegated capability to the target browsing context in a time-constrained manner.
1.1. What is capability delegation?
Many capabilities in the Web are usable from JS in restricted manners. For example:
-
Most browsers allow popups (through
Window.open()
only if the user has either interacted with the page recently or allowed the browser to open popups from the page’s origin. -
A sandboxed iframe cannot make itself full-screen (though
requestFullscreen()
without a specific sandbox attribute or a user interaction within the frame.
Capability delegation means allowing a frame to dynamically relinquish its
ability to call a restricted API and transfer the ability to another (sub)frame
it can trust. The word "dynamic" here means the effect of the delegation lasts
for a limited time as defined by the capability being delegated. This is
different from static (load-time) exposure of a capability to a browsing
context through iframe allow
attribute where the capability becomes exposed to a subframe in a
time-unconstrained manner.
1.2. Initiating a delegation vs using a capability
Capability delegation needs two distinct steps to be effective. The first step is "initiation" where one browsing context notifies another browsing context about a specific capability being delegated. After initiation, the second (i.e. the receiver) browsing context would "use" the delegated capability, which typically means calling a capability-defined method. While the capability delegation specification here does not define the API interface used in the second step, it redefines the API’s internal behavior.
Because of this, this specification consists of two distinct parts: defining an API for the initiation step, and then defining delegated behavior for one specific "user" API. For the second part, this specification focuses on behavior changes needed in Payment Request API, which would serve as a guide for similar changes in any other APIs that would utilize capability delegation in future.
1.3. Transient availability
Both the steps mentioned above are time-constrained in nature:
-
The initiation step is activation consuming, so the step is allowed only after a recent user activation. Moreover, the consumption of user activation here guarantees that the delegation mechanism can’t be used more than once per user activation. This prevents malicous uses of capability delegation, like repeated delegation attempts to multiple frames to effectively bypass the user activation restriction for the delegated API.
-
After a successful completion of the initiation step, the delegated API becomes available for use in the target browsing context for a few seconds only. The exact time limit here depends on how a delegated API defines the delegated behavior in its own specification. For an API that does not define its own time limit, the default limit will be the same as user activation expiry.
2. Examples
show()
from a subframe after a mouse click, it will post a message to the subframe with an additional
option to specify the delegated capability:
window. onclick= () => { targetWindow. postMessage( 'a_message' , { delegate: "payment" }); };
Upon receiving the message, the subframe would be able to use show()
even though the frame hasn’t received a user
activation:
window. onmessage= () => { const payRequest= new PaymentRequest(...); const payResponse= await payRequest. show(); ... }
3. Initiating capability delegation
When a browsing context wants to delegate a capability to another browsing
context, it posts a message to the second browsing context with an extra WindowPostMessageOptions
called delegate
specifying the capability. The
value of this option MUST be a feature-identifier. The option MUST
be ignored if the value does not correspond to any features supported by the
user
agent.
3.1. Monkey-patch to HTML spec
The WindowPostMessageOptions
IDL definition will include an additional field as follows:
DOMString? delegate;
In the algorithm for window post message, the following step:
Let transfer be options["transfer"].
will be followed by two additional steps as follows:
-
Let delegate be options["delegate"].
-
If delegate is not null, then:
-
If the user agent does not support delegating the feature indicated by delegate, then throw a "NotSupportedError" DOMException.
-
If targetWindow’s associated Document is not allowed-to-use the feature indicated by delegate, then throw a a "NotAllowedError" DOMException.
-
If targetOrigin is a single U+002A ASTERISK character (*), then throw a a "NotAllowedError" DOMException.
The default value of targetOrigin is "/", restricting the message to same-origin targets. The additional requirement to use a string other than "*" means that cross-origin messages has to specify the specific origin for which they are intended. -
Let source be incumbentSettings’s global object.
-
If source does not have transient activation, then throw a "NotAllowedError" DOMException.
-
Consume user activation in source.
-
4. Tracking delegated capability
Capabilities delegated to a browsing context will be tracked using a map
named Window
.DELEGATED_CAPABILITY_TIMESTAMPS. Each time a capability is
delegated to a Window
, an entry will be added in DELEGATED_CAPABILITY_TIMESTAMPS with a key equal to the feature-identifier representing the
capability, and a value equal to current DOMHighResTimeStamp
. If the map
already has an entry for the same key, the existing value will be updated to
current DOMHighResTimeStamp
.
4.1. Monkey-patch to HTML spec
Right before the algorithm for window post message, a new paragraph will be inserted, as follow:
For the purpose of tracking capabilities delegated to a browsing context, each
Window
has a map called DELEGATED_CAPABILITY_TIMESTAMPS from feature-identifier toDOMHighResTimeStamp
. The map is initialized with an empty map.
In the algorithm for window post message, two additional sub-steps will be added to current Step 8. The first additional sub-step will be inserted after the following sub-step:
Queue a global task ...
Let origin be the serialization of incumbentSettings’s origin.
as follows:
-
Queue a global task ... (unchanged)
-
Let delegate be options["delegate"].
-
The second additional sub-step will be inserted after the following sub-step:
Queue a global task ...
Let newPorts be a new frozen array consisting of ...
as follows:
-
Queue a global task ... (unchanged)
-
Let newPorts be a new frozen array consisting of ... (unchanged except for numbering)
-
If delegate is not null, AND the user agent supports delegating delegate, then set DELEGATED_CAPABILITY_TIMESTAMPS[delegate] to current high resolution time.
-
5. Defining delegated capability behavior
Any capability that defines a delegated behavior uses the corresponding entry in Window
.DELEGATED_CAPABILITY_TIMESTAMPS in a manner appropriate for the
capability. Below is the spec change needed for one particular capability.
5.1. Monkey-patch to Payment Request spec
In the algorithm for show()
, the following steps will be
replaced to implement the delegated behavior:
The two steps:
If the relevant global object of request does not have transient activation:
Return a promise rejected with a "SecurityError" DOMException.
Consume user activation of the relevant global object.
will be replaced by the following three steps:
-
If the relevant global object of request does not have transient activation, AND the timestamp DELEGATED_CAPABILITY_TIMESTAMPS["payment"] in the relevant global object is either undefined or expired:
-
Return a promise rejected with a "SecurityError" DOMException.
-
-
If the relevant global object of request does not have transient activation, then clear the map entry DELEGATED_CAPABILITY_TIMESTAMPS["payment"].
-
Otherwise, consume user activation of the relevant global object.
5.2. Monkey-patch to Fullscreen spec
In the algorithm for requestFullscreen()
, the following changes will be
done to implement the delegated behavior:
The last condition in Step 5:
If any of the following conditions are false, then set error to true: ...
This’s relevant global object has transient activation or the algorithm is triggered by a user generated orientation change.
will be replaced by:
-
If any of the following conditions are false, then set error to true: ... (unchanged)
-
This’s relevant global object has transient activation, or the timestamp DELEGATED_CAPABILITY_TIMESTAMPS["fullscreen"] in this’s relevant global object is neither undefined nor expired, or the algorithm is triggered by a user generated orientation change.
-
Right before the Step 10:
Let fullscreenElements be an ordered set initially consisting of this.
the following new step will be inserted:
-
If this’s relevant global object does not have transient activation, then clear the map entry DELEGATED_CAPABILITY_TIMESTAMPS["fullscreen"] in this’s relevant global object.
-
Let fullscreenElements be an ordered set initially consisting of this. (unchanged except for numbering)
5.3. Monkey-patch to [SCREEN-CAPTURE] spec
In the algorithm for getDisplayMedia()
, the following changes will be
done to implement the delegated behavior:
The condition in Step 3:
If the relevant global object of this does not have transient activation, return a promise rejected with a DOMException object whose name attribute has the value InvalidStateError.
will be replaced by:
-
If the relevant global object of this does not have transient activation AND the timestamp DELEGATED_CAPABILITY_TIMESTAMPS["display-capture"] in this’s relevant global object is either undefined or expired, return a promise rejected with a DOMException object whose
name
attribute has the valueInvalidStateError
.