Chrome 扩展程序:扩展 API 以支持即时导航

Dave Tapuska
Dave Tapuska

要点:Extensions API 已更新为支持返回/前进缓存,并预加载导航。有关详情,请参阅下文。

Chrome 一直在努力提升导航速度。往返缓存(在 Chrome 96 中发布于桌面设备上)和推测规则(在 Chrome 103 中发布)等即时导航技术可同时改进返回和前进体验。在本文中,我们将探讨我们对浏览器扩展程序 API 所做的更新,以适应这些新的工作流程。

了解网页类型

在引入“前进/后退”缓存和预渲染之前,单个标签页只有一个活动页面。这始终是可见的选项。如果用户返回上一页,系统会销毁当前页面(页面 B),并完全重建历史记录中的上一页(页面 A)。扩展程序无需担心网页处于生命周期的哪个部分,因为标签页只有一个状态,即活跃/可见状态。

驱逐活动页面
驱逐活动页面。

使用前后浏览缓存和预渲染后,标签页和网页之间不再是一对一的关系。现在,每个标签页实际上都存储多个页面,并且页面在状态之间进行转换,而不是被销毁和重构。

例如,某个网页在生命周期的开始阶段可能是预渲染(不可见)网页,在用户点击链接时转换为活跃(可见)网页,然后在用户导航到其他网页时存储在返回/前进缓存(不可见)中,而整个过程中该网页都不会被销毁。在本文的后面部分,我们将介绍公开的新属性,以帮助扩展程序了解网页所处的状态。

页面类型
页面类型。

请注意,一个标签页可以包含一系列预渲染的网页(而不仅仅是一个网页)、一个活动(可见)网页,以及一系列往返缓存的网页。

扩展程序开发者将面临哪些变化?

FrameId == 0

在 Chromium 中,我们将最顶部的/主框架称为最外层框架。

如果扩展程序作者假定最外层帧的 frameId 为 0(以前的最佳实践),则可能会遇到问题。由于标签页现在可以有多个最外层框架(预渲染的页面和缓存的页面),因此假设标签页只有一个最外层框架是不正确的。frameId == 0 仍会继续表示活动页面的最外层框架,但同一标签页中其他页面的最外层框架将不为零。添加了新字段 frameType 来解决此问题。请参阅本帖子的“如何确定某个帧是否为最外层帧?”部分。

帧生命周期与文档生命周期

扩展程序中存在另一个问题概念,即帧的生命周期。帧托管文档(与提交的网址相关联)。文档可能会发生变化(例如通过导航),但 frameId 不会,因此很难仅通过 frameId 将特定文档中发生的事情相关联。我们引入了 documentId 这一概念,它是每个文档的唯一标识符。如果导航到某个框架并打开新文档,标识符将会更改。此字段对于确定网页何时更改其生命周期状态(在预渲染/活跃/缓存之间)非常有用,因为它保持不变。

网页导航事件

chrome.webNavigation 命名空间中的事件可以在同一页面上多次触发,具体取决于其所处的生命周期。请参阅“如何确定页面处于哪个生命周期?”“如何确定页面转换的时间?”部分。

如何判断网页处于哪个生命周期阶段?

DocumentLifecycle 类型已添加到之前提供 frameId 的多个扩展程序 API。如果事件中存在 DocumentLifecycle 类型(例如 onCommitted),则其值为事件生成时的状态。您可以随时通过 WebNavigation getFrame()getAllFrames() 方法查询信息,但最好使用事件中的值。如果您使用这两种方法中的任一方法,请注意,在事件生成到这两种方法返回的 promise 解析完毕之间,帧的状态可能会发生变化。

DocumentLifecycle 具有以下值:

  • "prerender”:目前未向用户显示,但可能准备向用户显示。
  • "active":目前向用户显示。
  • "cached":存储在往返缓存中。
  • "pending_deletion":文件正在销毁。

如何确定某个帧是否为最外层帧?

以前,扩展程序可能会检查 frameId == 0,以确定发生的事件是否针对最外层框架。由于标签页中包含多个网页,因此我们现在有多个最外层框架,因此 frameId 的定义存在问题。您绝不会收到与“前进/返回”缓存帧相关的事件。不过,对于预渲染帧,最外层帧的 frameId 将不为零。因此,使用 frameId == 0 作为确定它是否为最外层帧的信号是不正确的。

为此,我们引入了名为 FrameType 的新类型,这样现在就可以轻松确定帧是否确实是最外层帧。FrameType 具有以下值:

  • "outermost_frame":通常称为最顶层帧。请注意,这些类型有多种。例如,如果您有预渲染且缓存的网页,则每个网页都有一个最外层框架,可以称为其最顶层框架。
  • "fenced_frame":留待日后使用。
  • "sub_frame":通常是 iframe。

我们可以将 DocumentLifecycleFrameType 结合使用,并确定某个帧是否为最外层的有效帧。例如:tab.documentLifecycle === “active” && frameType === “outermost_frame”

如何解决相框的使用时间问题?

如上所述,帧会托管文档,并且帧可能会导航到新文档,但 frameId 不会更改。当您收到仅包含 frameId 的事件时,就会出现问题。如果您查询帧的网址,可能会发现该网址与事件发生时不同,这称为使用时间问题。

为解决此问题,我们引入了 documentId(和 parentDocumentId)。现在,如果提供了 documentIdwebNavigation.getFrame() 方法会使 frameId 成为可选项。每当导航到某个帧时,documentId 都会发生变化。

如何确定页面转换的时间?

有明确的信号可用于确定页面何时在状态之间转换。

我们来看看 WebNavigation 事件

对于任何网页的首次导航,您会按下列顺序看到四个事件。请注意,这 4 个事件可能会在 DocumentLifecycle 状态为 "prerender""active" 时发生。

onBeforeNavigate
onCommitted
onDOMContentLoaded
onCompleted

下图展示了预渲染页面变为活动页面时 documentId 会更改为 "xyz"

当预渲染页面变为活动页面时,documentId 会发生变化
当预渲染页面成为活跃页面时,documentId 会发生变化。

当网页从往返缓存或预渲染状态转换为活动状态时,系统会再触发 3 个事件(但 DocumentLifecyle"active")。

onBeforeNavigate
onCommitted
onCompleted

documentId 将保持与原始事件相同。如上图所示,当 documentId == xyz 激活时,就会发生这种情况。请注意,由于页面已加载,因此除了 onDOMContentLoaded 事件外,系统会触发相同的导航事件。

如果您有任何意见或疑问,欢迎随时在 chromium-extensions 群组中提问。