UA 小部件

介绍

用户代理小部件 (UA 小部件) 旨在替代我们在 Web 内容中使用 XBL 绑定。这些小部件在扩展的主体每个来源沙箱中运行 JavaScript。它们在称为 UA 小部件影子根的特殊、封闭的影子根(网页无法访问)内插入自己的 DOM。

UA 小部件生命周期

UA 小部件通常在元素附加到文档时构造,并在元素从树中删除时销毁。但是,为了提高速度,对每个小部件进行了专门化。

当元素附加到树时,会分派一个仅限 Chrome 的 UAWidgetSetupOrChange 事件,并由框架脚本(即 UAWidgetsChild)捕获。

然后,UAWidgetsChild 获取该来源的沙箱(根据需要延迟创建),根据需要加载脚本,并通过调用带有 DOM 创建的 UA 小部件影子根的引用的 JS 构造函数来初始化实例。我们将在后面的部分讨论沙箱。

构造实例后,立即调用 onsetup 方法。构造函数的调用不得抛出异常,否则 UAWidgetsChild 会感到困惑,因为它不会返回小部件的实例,但小部件已经半初始化。如果 onsetup 方法调用抛出异常,UAWidgetsChild 仍然能够保存小部件的引用并在稍后调用拆卸方法。

当元素从树中删除时,会分派 UAWidgetTeardown,以便 UAWidgetsChild 可以销毁小部件(如果存在)。如果是这样,UAWidgetsChild 会在小部件上调用 teardown() 方法,从而导致小部件自毁。

违反直觉的是,当文档卸载时,元素不被视为“从树中删除”。这被认为是安全的,因为小部件触及的任何内容都应在文档卸载时重置或清理。请不要通过让拆卸操作切换任何浏览器状态来违反此假设。

当 UA 小部件初始化时,它应该在传递的 UA 小部件影子根内创建自己的 DOM,包括加载样式表的 <link> 元素,添加事件侦听器等。当销毁(即调用拆卸方法)时,它应该执行相反的操作。

**专门化**: 对于视频控件,如果不需要控件(即当 <video><audio> 元素没有设置“controls”属性时),我们不想执行此操作,因此我们在 BindToTree 方法中放弃了从 HTMLMediaElement 分派事件。相反,另一个 UAWidgetSetupOrChange 事件将在属性设置为 true 时导致沙箱和小部件实例构造。如果小部件已初始化,相同的事件还负责触发 UA 小部件上的 onchange() 方法。

同样,仅当 <input>type 属性为 datetime 时,才会加载日期时间框小部件。

专门化不适用于 UA 小部件影子根的生命周期。始终构造它,以防止 Web 内容中的 DOM 元素的子元素接收布局帧。

UA 小部件影子根

UA 小部件影子根是一个封闭的影子根,启用了 UA 小部件标志。作为一个封闭的影子根,其他脚本可能无法访问它。它附加在主机元素上,规范不允许将影子根附加到该元素上。

UA 小部件标志启用了下一节中介绍的安全功能。

**旁注**: XML 美化打印也会将其转换后的内容隐藏在 UA 小部件影子 DOM 内,即使没有 JavaScript 要运行。设置此项是为了利用相同安全功能和行为。

JavaScript 沙箱

为 UA 小部件创建的沙箱是每个来源的,并设置为扩展的主体。这允许脚本访问 Web 内容无法访问的其他 DOM API,同时将其主体绑定到文档来源。它们根据需要创建,支持 UA 小部件的生命周期,如前所述。这些沙箱全局变量不与任何窗口对象关联,因为它们在所有相同来源的文档之间共享。UA 小部件脚本的工作是保存和管理小部件正在初始化的窗口和文档对象的引用,方法是通过访问传递的 UA 小部件影子根实例来访问它们。

虽然封闭的影子根在技术上可以防止内容访问内容,但我们希望提供更强的保证以防止意外泄漏对 UA 小部件影子树的引用到内容脚本中。通过在 UA 小部件范围内设置反射器(而不是正常范围),可以限制对 UA 小部件 DOM 的访问。为此,我们避免任何脚本(包括 UA 小部件脚本)在附加到影子 DOM 之前获取任何创建的 DOM 元素的引用。一旦元素位于影子 DOM 中,绑定机制将在访问时将其反射器置于所需的范围内。

为了避免在 DOM 插入之前创建反射器,可用的 DOM 接口是有限的。例如,脚本必须调用 UA 小部件影子根实例上可用的 createElementAndAppendChildAt(),而不是 createElement()appendChild(),以避免接收对 DOM 元素的引用,从而在元素与 UA 小部件影子树正确关联之前,在错误的范围内触发其反射器的创建。要了解差异,请在树内 WebIDL 文件中搜索 Func="IsChromeOrUAWidget"Func="IsNotUAWidget"

其他需要注意的事项

作为 Web 平台实现的一部分,务必确保小部件的 Web 可观察特性正确反映 Web 上的脚本所期望的内容。

  • 不要在 UA 小部件影子根主机元素上分派不符合规范的事件,因为 Web 内容脚本中的事件侦听器可以访问它们。

  • 构造函数和 onsetup 返回时,小部件的布局和尺寸应该已经准备就绪,因为它们可以在内容脚本获取主机元素的引用(即当 appendChild() 返回时)后立即检测到。为了简化此操作,我们在 UA 小部件影子 DOM 内同步加载 <link> 元素以加载 Chrome 样式表。

  • 影子 DOM 中不应该有任何空格节点,因为 UA 小部件可以放置在 white-space: pre 内。请参阅 错误 1502205

  • CSP 将阻止影子 DOM 中的内联样式。<link> 是加载样式的唯一安全方法。