capability-delegation

Capability Delegation

Transferring the ability to use restricted APIs to another window.

(Draft specification)

Author

Mustaq Ahmed (mustaq@chromium.org, github.com/mustaqahmed)

Participate

Introduction

“Capability delegation” means allowing a frame to relinquish its ability to call a restricted API and transfer the ability to another (sub)frame it trusts. The focus here is a dynamic delegation mechanism which exposes the capability to the target frame in a time-constrained manner (unlike <iframe allow=...> attribute which is not time-constrained).

The API proposed here is based on postMessage(), where the sender frame uses a new PostMessageOptions member to specify the capability it wants to delegate.

Motivating use-cases

Here are some practical scenarios that are enabled by the Capability Delegation API.

Secure PaymentRequest processing in a subframe

Many merchant websites perform payment processing through a Payment Service Provider (PSP) site (e.g. Stripe) to comply with security and regulatory complexities around card payments. When the end-user clicks on the “Pay” button on the merchant website, the merchant website sends a message to a cross-origin iframe from the PSP website to initiate payment processing, and then the iframe uses the Payment Request API to complete the task.

But sites are only allowed to call the Payment Request API after transient user activation (a recent click or other interaction) to prevent malicious attempts like unattended or repeated payment requests. Since the user probably clicked on the main site, and not the PSP iframe, this would prevent the PSP from using the Payment Request API at all. Browsers today support such payment processing by ignoring the user activation requirement altogether (see crbug.com/1114218)!

Capability Delegation API provides a way to support this use-case while letting the browser enforce the user activation requirement, as follows:

// Top-frame (merchant website) code
checkout_button.onclick = () => {
    targetWindow.postMessage("process_payment", {targetOrigin: "https://example.com",
                                                 delegate: "payment"
                                                });
};

// Sub-frame (PSP website) code
window.onmessage = () => {
    const payment_request = new PaymentRequest(...);
    const payment_response = await payment_request.show();
    ...
}

Allowing fullscreen from opener Window click

This is a work-in-progress in Chrome.

Consider a presentation/slide website where the main “control panel” window has spawned a few presentation windows, and the user wants to selectively make one presentation window fullscreen by clicking on the appropriate button on the main window (a feature request from a developer). Clicking on the “control panel” button does not make the user activation available to the presentation window, so this does not work today.

The Web does not support this use-case today but Capability Delegation API provides a solution:

// Main window ("control panel") code
let win1 = open("presentation1.html");
let win2 = open("presentation2.html");

button1.onclick = () => win1.postMessage("msg", {targetOrigin: "https://example.com",
                                                 delegate: "fullscreen"});
button2.onclick = () => win2.postMessage("msg", {targetOrigin: "https://example.com",
                                                 delegate: "fullscreen"});

// Sub-frame ("presentation window") code
window.onmessage = () => document.body.requestFullscreen();

Allowing display capture from cross-origin iframe click

Consider a web app in which you want to add video-conferencing capabilities. You turn to a third party solution that can be embedded in a cross-origin iframe. There’s a lot of logic behind the scenes, but UX-wise, maybe you work out a scheme where it’s mostly the video which is user-facing in the video-conferencing iframe, and the user-facing controls - mute, leave, share-screen - are all part of the web app, and receive its specific UX styling. When those buttons are pressed, some messages are exchanged between the web app and the embedded video-conferencing solution.

To let the third-party iframe to prompt the user to share a tab, a window, or a screen, the top frame would delegate the mediaDevices.getDisplayMedia() permission to the iframe as follows:

// In the top frame, user clicks the "Share My Screen" button.
button.onclick = () =>
  frames[0].postMessage("msg", { delegate: "display-capture" });
// In the cross-origin video-conferencing iframe, prompt the user
// to share a tab, a window, or a screen.
window.onmessage = () => navigator.mediaDevices.getDisplayMedia();

Other similar scenarios

Non-goals

Using capability delegation

Developers would use Capability Delegation by just initiating the delegation appropriately, as shown in the example code snippets above. In short, when a browsing context wants to delegate a capability to another browsing context, it sends a postMessage() to the second browsing context with an extra WindowPostMessageOptions member called delegate specifying the capability.

After a successful delegation, the “user API” (the restricted API being delegated) just works when called at the right moment. The general idea is calling the restricted API in a MessageEvent handler or soon afterwards. In the examples above, the restricted APIs are payment_request.show(), element.requestFullscreen(), and mediaDevices.getDisplayMedia() respectively.

Demo

Considered alternatives

Delegating user activation instead of a specific capability

It may appear that we can delegate user activation to solve the same use-cases and thus avoid specifying a feature in the postMessage() call. We attempted this direction in the past from a few different perspectives, and decided not to pursue this. In particular, user activation controls many Web APIs, so delegating user activation for any of the mentioned use-cases is impossible without causing problems with unrelated APIs. See the TAG discussion with one past attempt.

Using a delegation-specific method instead of postMessage()

Instead of piggy-backing the delegation request as a PostMessageOptions entry, we considered adding a new delegation-specific interface on the Window object. While the latter may look cleaner from a developer’s perspective, to support cross-origin communication this solution would require adding the new method on the WindowProxy wrapper, which HTML’s editor strongly disliked.

Stakeholder feedback/opposition

We will track the overall status through this Chrome Status entry.

Acknowledgements

Many thanks for valuable feedback and advice from: