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(); ... }
WindowClient with {delegate: "popup"}:
// sw.js (Service Worker): self. addEventListener( 'notificationclick' , ( event) => { event. waitUntil(( async () => { const clients= await self. clients. matchAll({ type: 'window' }); const client= clients. find( c=> c. visibilityState=== 'visible' ) || clients[ 0 ]; if ( client) { await client. focus(); client. postMessage( { action: 'preview_email' , emailId: event. notification. data. emailId}, { delegate: 'popup' } ); } })()); });
Upon receiving the message, the client window can call
window.open() even without
transient user activation:
// main.js (Client Window): navigator. serviceWorker. addEventListener( 'message' , ( event) => { if ( event. data. action=== 'preview_email' ) { window. open( '/preview?id=' + event. data. emailId, 'preview_win' , 'width=600,height=500' ); } });
3. Initiating capability delegation
When a browsing context or service worker wants to delegate a capability to another browsing context, it posts a message with an extra option called delegate specifying the capability.
For a browsing context, the message is sent using WindowPostMessageOptions, and the value
of this option MUST be a feature-identifier.
For a service worker, the message is sent to a same-origin WindowClient using ClientPostMessageOptions,
and the value of this option MUST be "popup".
The option MUST be ignored or rejected if the value does not correspond to any capability supported
for delegation 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 delegate is not a feature-identifier, or the user agent does not support delegating the feature indicated by delegate via
Window.postMessage(), 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.
-
3.2. Monkey-patch to Notifications spec
Each NotificationEvent has an associated active transient capability token for "popup", which is initially unset.
In the algorithm for fire a service worker notification event, the following step will be added after Step 2:
Initialize event’s type attribute to e.
-
If e is
"notificationclick", then:-
Set event’s active transient capability token for
"popup". -
The token expires and is unset when event is no longer active (i.e., after its synchronous dispatch completes and upon the settlement of all promises passed to
event.waitUntil()), or when 5 seconds have elapsed from the creation of event, whichever occurs first.
-
When an active transient capability token is consumed by an algorithm (such as in Client.postMessage()),
it is unset.
3.3. Monkey-patch to Service Workers spec
The ClientPostMessageOptions dictionary will be added to the Service Workers specification,
replacing the StructuredSerializeOptions parameter in Client.postMessage():
dictionary :ClientPostMessageOptions StructuredSerializeOptions {DOMString ?; };delegate partial interface Client {undefined (postMessage any ,message optional ClientPostMessageOptions = {}); };options
In the algorithm for Client.postMessage(), before serializing message, the following steps will be added to validate and transfer the capability:
-
Let client be this
Clientobject. -
Let delegate be options["delegate"].
-
If delegate is not null, then:
-
If delegate is not
"popup", or the user agent does not support delegating delegate, then throw a "NotSupportedError" DOMException. -
If client is not a
WindowClient, then throw a "NotSupportedError" DOMException. -
If the current execution context is not associated with an active
NotificationEventthat has an active transient capability token for delegate, then throw a "NotAllowedError" DOMException. -
Consume the active transient capability token for delegate from the
NotificationEvent.
-
-
In the task queued on the destination client’s responsible event loop to dispatch the
messageevent, immediately before firing themessageevent (after deserialization succeeds):-
If delegate is not null, set DELEGATED_CAPABILITY_TIMESTAMPS[delegate] on the target
Windowto current high resolution time.
-
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 capability identifier
(either a feature-identifier or
"popup"), 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
Windowhas a map called DELEGATED_CAPABILITY_TIMESTAMPS from feature-identifier or DOMString 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 delegate is a feature-identifier supported for delegation by the user agent, 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 are the spec changes needed for capabilities that define delegated behavior.
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
nameattribute has the valueInvalidStateError.
5.4. Monkey-patch to HTML spec for window open
In Step 5 of the algorithm for the rules for choosing a
navigable
(invoked by the window open
steps
when chosen is null and a new top-level traversable is being requested),
the following changes will be made to implement the delegated behavior for "popup":
-
Popup Allowed Check: In the first condition of Step 5 (evaluating whether the user agent’s popup blocker blocks creating a new top-level traversable), allow creation (do not block) if currentNavigable’s active window (the relevant global object) has transient activation, OR if the timestamp DELEGATED_CAPABILITY_TIMESTAMPS["popup"] in currentNavigable’s active window is present and not expired (where an entry for
"popup"is considered expired if the current high resolution time minus the timestamp is greater than 1 second, i.e., 1000 milliseconds). Subsequent sandboxing flag checks in Step 5 remain unchanged. -
Unified Consumption: In the branch of Step 5 that creates a new top-level traversable:
-
If currentNavigable’s active window has transient activation, then consume user activation of currentNavigable’s active window.
-
Clear the map entry DELEGATED_CAPABILITY_TIMESTAMPS["popup"] in currentNavigable’s active window.
-