1. 引言
本节为非规范性内容。
本规范定义了一种机制,使脚本能够将调用受限 API 的能力委托给另一个它信任的浏览上下文。这里关注的是一种动态委托机制,它在有限时间内向目标浏览上下文提供所委托的能力。
1.1. 什么是能力委托?
网页中的许多能力只能以受限方式从 JS 使用。例如:
-
大多数浏览器仅在用户最近与页面交互过,或允许浏览器打开来自该页面源的弹出窗口时,才允许通过
Window.open()打开弹出窗口。 -
沙盒中的 iframe 若没有特定的沙盒属性,且用户未在框架内交互,就不能通过
requestFullscreen()使自身进入全屏。
能力委托允许框架动态放弃其调用受限 API 的能力,并将该能力转移给另一个可信的(子)框架。这里的“动态”意味着委托效果仅在所委托能力定义的有限时间内持续。这与通过 浏览上下文所用的 iframe
的 allow
属性静态地(在加载时)暴露能力不同;后一种方式会不受时间限制地向子框架暴露能力。
1.2. 发起委托与使用能力
能力委托需要两个不同的步骤才能生效。第一步是“发起”:一个浏览上下文通知另一个浏览上下文,某项能力正在委托给它。发起之后,第二个(即接收方)浏览上下文会“使用”所委托的能力,通常是调用由该能力定义的方法。本能力委托规范不定义第二步所用的 API 接口,但会重新定义该 API 的内部行为。
因此,本规范分为两个不同的部分:先定义发起步骤的 API,再为一个具体的“使用方”API 定义委托行为。第二部分重点说明支付请求 API所需的行为变更,为将来利用能力委托的其他 API 提供类似变更的指南。
1.3. 短暂可用性
上述两个步骤本质上都受时间限制:
-
发起步骤会消耗激活,因此只有在最近发生用户激活之后才允许执行。此外,这里的用户激活消耗确保每次用户激活最多只能使用一次委托机制。这可以防止恶意使用能力委托,例如反复向多个框架尝试委托,从而实际上绕过所委托 API 的用户激活限制。
-
成功完成发起步骤后,所委托的 API 仅在目标浏览上下文中可用数秒。确切时限取决于该 API 的规范如何定义委托行为。如果 API 未定义自己的时限,则默认时限与用户激活的过期时限相同。
2. 示例
show(),它会向子框架发送消息,并附带一个指定委托能力的选项:
window. onclick= () => { targetWindow. postMessage( 'a_message' , { delegate: "payment" }); };
收到消息后,子框架即使没有获得用户激活,也能够使用 show():
window. onmessage= () => { const payRequest= new PaymentRequest(...); const payResponse= await payRequest. show(); ... }
3. 发起能力委托
当一个浏览上下文希望将能力委托给另一个浏览上下文时,它向第二个浏览上下文发送消息,并附加一个 WindowPostMessageOptions,名为
delegate,用于指定能力。该选项的值必须是功能标识符。如果该值不对应任何用户代理支持的功能,则必须忽略此选项。
3.1. 对 HTML 规范的临时修补
WindowPostMessageOptions
的 IDL 定义将增加如下字段:
DOMString? delegate;
在窗口发送消息的算法中,以下步骤:
令 transfer 为 options["transfer"]。
之后将增加以下两个步骤:
-
令 delegate 为 options["delegate"]。
-
如果 delegate 不为 null,则:
-
如果用户代理不支持委托 delegate 指示的功能,则抛出 "NotSupportedError" DOMException。
-
如果 targetWindow 的关联 Document未被允许使用由 delegate 指示的功能,则抛出 "NotAllowedError" DOMException。
-
如果 targetOrigin 为单个 U+002A 星号字符(*),则抛出 "NotAllowedError" DOMException。
选项 targetOrigin 的默认值为 "/",将消息限制在同源目标内。另行要求使用 "*" 之外的字符串,意味着跨源消息必须指定其预期接收方的具体源。 -
令 source 为 incumbentSettings 的全局对象。
-
如果 source 没有瞬时激活,则抛出 "NotAllowedError" DOMException。
-
消耗用户激活,所针对的是 source。
-
4. 跟踪委托的能力
委托给浏览上下文的能力将使用名为 Window.DELEGATED_CAPABILITY_TIMESTAMPS 的映射来跟踪。每次将能力委托给 Window
时,都在 DELEGATED_CAPABILITY_TIMESTAMPS 中添加一个条目,其键等于表示该能力的功能标识符,值等于当前的 DOMHighResTimeStamp。如果映射已包含同键条目,则将已有值更新为当前的
DOMHighResTimeStamp。
4.1. 对 HTML 规范的临时修补
在窗口发送消息算法的紧前方插入如下新段落:
为了跟踪委托给浏览上下文的能力,每个
Window都有一个映射,名为 DELEGATED_CAPABILITY_TIMESTAMPS,将功能标识符映射到DOMHighResTimeStamp。该映射初始化为空映射。
在窗口发送消息算法中,为当前第 8 步添加两个子步骤。第一个新增子步骤插入以下子步骤之后:
将一个全局任务加入队列……
令 origin 为 incumbentSettings 的源的序列化结果。
如下:
-
将一个全局任务加入队列……(不变)
-
令 delegate 为 options["delegate"]。
-
第二个新增子步骤插入以下子步骤之后:
将一个全局任务加入队列……
令 newPorts 为由……组成的新冻结数组。
如下:
-
将一个全局任务加入队列……(不变)
-
令 newPorts 为由……组成的新冻结数组。(除编号外不变)
-
如果 delegate 不为 null,并且用户代理支持委托 delegate,则将 DELEGATED_CAPABILITY_TIMESTAMPS[delegate] 设为当前高精度时间。
-
5. 定义委托能力的行为
任何定义了委托行为的能力,都以适合该能力的方式使用 Window.DELEGATED_CAPABILITY_TIMESTAMPS
中的对应条目。以下列出某项具体能力所需的规范变更。
5.1. 对支付请求规范的临时修补
在 show()
的算法中,将替换以下步骤以实现委托行为:
以下两个步骤:
如果 request 的相关全局对象没有瞬时激活:
返回以 "SecurityError" DOMException 拒绝的 promise。
消耗相关全局对象的用户激活。
将被替换为以下三个步骤:
-
如果 request 的相关全局对象没有瞬时激活,并且该相关全局对象中的时间戳 DELEGATED_CAPABILITY_TIMESTAMPS["payment"] 为 undefined 或已过期:
-
返回以 "SecurityError" DOMException 拒绝的 promise。
-
-
如果 request 的相关全局对象没有瞬时激活,则清除映射条目 DELEGATED_CAPABILITY_TIMESTAMPS["payment"]。
-
否则,消耗用户激活,所针对的是相关全局对象。
5.2. 对全屏规范的临时修补
在 requestFullscreen()
的算法中,进行以下修改以实现委托行为:
第 5 步的最后一个条件:
如果以下任一条件为 false,则将 error 设为 true:……
this 的相关全局对象具有瞬时激活,或者该算法由用户产生的方向变化触发。
将被替换为:
-
如果以下任一条件为 false,则将 error 设为 true:……(不变)
-
this 的相关全局对象具有瞬时激活,或者该对象中的时间戳 DELEGATED_CAPABILITY_TIMESTAMPS["fullscreen"] 既不是 undefined 也未过期,或者该算法由用户产生的方向变化触发。
-
在第 10 步的紧前方:
令 fullscreenElements 为初始仅包含 this 的有序集合。
将插入以下新步骤:
-
如果 this 的相关全局对象没有瞬时激活,则清除该对象中的映射条目 DELEGATED_CAPABILITY_TIMESTAMPS["fullscreen"]。
-
令 fullscreenElements 为初始仅包含 this 的有序集合。(除编号外不变)
5.3. 临时修补:[SCREEN-CAPTURE] 规范
在 getDisplayMedia()
的算法中,进行以下修改以实现委托行为:
第 3 步的条件:
如果 this 的相关全局对象没有瞬时激活,则返回一个 promise,其以 name 属性值为 InvalidStateError 的 DOMException 对象拒绝。
将被替换为:
-
如果 this 的相关全局对象没有瞬时激活,并且时间戳 DELEGATED_CAPABILITY_TIMESTAMPS["display-capture"] 在 this 的相关全局对象中为 undefined 或已过期,则返回一个已拒绝的 promise,拒绝原因是一个 DOMException 对象,其
name属性值为InvalidStateError。