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

Dave Tapuska
Dave Tapuska

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

Chrome 一直致力于提高导航速度。返回/前进缓存(Chrome 96 中已在桌面设备上推出)和推测规则(在 Chrome 103 中推出)等即时导航技术改善了返回和前进体验。在这篇博文中,我们将探讨为适应这些新工作流而对浏览器扩展程序 API 进行的更新。

了解网页类型

在引入往返缓存和预渲染之前,单个标签页只有一个活动页面。这始终是可见项。如果用户返回前一个页面,当前页面将被销毁(页面 B),历史记录中的前一个页面将被完全重建(页面 A)。扩展程序无需担心生命周期页面所在的部分,因为一个标签页只有一个页面,即活跃/可见状态。

逐出活跃页面
逐出活动页面。

使用往返缓存和预渲染时,标签页和页面之间将不再存在一对一关系。现在,每个标签页实际上会存储多个页面,并且页面会在状态之间转换,而不是被销毁和重建。

例如,一个页面一开始是预渲染(不可见)的页面,并在用户点击链接时转换为活动(可见)页面,然后在用户导航到另一个页面时将其存储在往返缓存(不可见)中,这一切都不会被销毁。在本文的后面部分,我们将介绍一些公开的新属性,这些属性可帮助扩展程序了解页面所处的状态。

网页类型
网页类型。

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

对于扩展程序开发者来说,会有哪些变化?

框架 ID == 0

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

如果扩展程序作者假设最外层帧的 frameId 为 0(之前的最佳做法),则可能会出现问题。由于一个标签页现在可以有多个最外层的帧(预渲染和缓存的网页),因此关于一个标签页只有一个最外层帧的假设是不正确的。frameId == 0 仍将继续表示活动页面的最外层帧,但同一标签页中其他页面的最外层帧将是非零值。为了解决此问题,我们添加了一个新字段 frameType。请参阅这篇博文的“如何确定某个帧是否为最外层的帧?”部分。

框架与文档的生命周期

扩展程序存在问题的另一个概念是帧的生命周期。框架托管一个文档(该文档与提交的网址关联)。文档可以更改(例如通过导航),但 frameId 不会,因此很难将特定文档中发生的情况与仅仅使用 frameIds 相关联。我们引入了 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,确定某个帧是否为活跃最外层的帧。例如: js 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 会发生变化。

当页面从往返缓存或预渲染转换为活动状态时,还会再有三个事件(但 DocumentLifecyle"active")。

onBeforeNavigate
onCommitted
onCompleted

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

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