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

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

之前提供 frameId 的许多扩展程序 API 中添加了 DocumentLifecycle 类型。如果事件中存在 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 事件

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

onBeforeNavigate
onCommitted
onDOMContentLoaded
onCompleted

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

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

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

onBeforeNavigate
onCommitted
onCompleted

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

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