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

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

如何判断网页所处的生命周期?

许多扩展程序 API 中都添加了 DocumentLifecycle 类型,而这些 API 以前提供 frameId。如果事件(例如 onCommitted)中存在 DocumentLifecycle 类型,则该类型的值即为生成事件时所处的状态。您可以随时通过 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 网上论坛中提出。