使用 Content Indexing API 将支持离线访问的网页编入索引

使 Service Worker 能够告知浏览器哪些网页可以离线工作

什么是 Content Indexing API?

使用渐进式 Web 应用,意味着无论网络连接的当前状态如何,都可以访问用户关心的信息(图片、视频、文章等)。当用户直接与 PWA 交互时,Service WorkerCache Storage APIIndexedDB 等技术为您提供了存储和传送数据的基础组件。但是,构建高质量、离线优先的 PWA 只是一个环节。人们如果不知道 Web 应用的内容在离线状态下可用,他们就无法充分利用您为实现该功能所付出的工作量。

这是一个发现问题;PWA 如何让用户知道其支持离线访问的内容,以便他们发现和查看可用内容?Content Indexing API 可以解决这个问题。此解决方案中面向开发者的部分是对 Service Worker 的扩展,它允许开发者将支持离线访问的网页的网址和元数据添加到由浏览器维护的本地索引。此增强功能适用于 Chrome 84 及更高版本。

当索引被您的 PWA 以及任何其他已安装的 PWA 中的内容填充后,浏览器就会显示,如下所示。

Chrome 新标签页上“下载内容”菜单项的屏幕截图。
首先,在 Chrome 新标签页上选择下载菜单项。
已添加到索引中的媒体和报道。
添加到索引中的媒体和文章将显示在为您推荐的文章部分中。

此外,Chrome 还可以在检测到用户离线时主动推荐内容。

Content Indexing API 不是缓存内容的替代方式。它是一种提供 Service Worker 已缓存页面的相关元数据的方式,以便浏览器可以在用户想要查看页面时呈现这些页面。Content Indexing API 有助于发现缓存的网页。

查看实际案例

了解 Content Indexing API 的最佳方法是试用示例应用。

  1. 确保您使用的是受支持的浏览器和平台。目前,仅限于 Android 设备上的 Chrome 84 或更高版本。转到 about://version 查看您当前运行的 Chrome 版本。
  2. 访问 https://contentindex.dev
  3. 点击列表中的一项或多项内容旁边的 + 按钮。
  4. (可选)停用设备的 Wi-Fi 和移动数据网络连接,或启用飞行模式,以模拟让浏览器离线的情况。
  5. 从 Chrome 菜单中选择下载内容,然后切换到为您推荐的文章标签页。
  6. 浏览您之前保存的内容。

您可以在 GitHub 上查看示例应用的源代码

另一个示例应用(剪贴簿 PWA)说明了如何将 Content Indexing API 与 Web Share Target API 搭配使用。上述代码演示了一种技术,可保证 Content Indexing API 与使用 Cache Storage API 的 Web 应用存储的项保持同步。

使用 API

要使用该 API,您的应用必须具有可离线导航的 Service Worker 和网址。如果您的 Web 应用目前没有 Service Worker,Workbox 库可简化创建 Service Worker。

哪些类型的网址可以编入索引,并且支持离线访问?

该 API 支持将与 HTML 文档对应的网址编入索引。例如,缓存的媒体文件的网址不能直接编入索引。相反,您需要为显示媒体的网页提供一个网址,并且可以离线使用。

推荐的模式是创建一个可接受底层媒体网址作为查询参数的“查看器”HTML 网页,然后显示文件的内容,并可能在网页上显示额外的控件或内容。

Web 应用只能向内容索引添加位于当前 Service Worker 范围内的网址。换句话说,Web 应用无法将属于完全不同网域的网址添加到内容索引中。

概览

Content Indexing API 支持三种操作:添加、列出和移除元数据。这些方法通过添加到 ServiceWorkerRegistration 接口的新属性 index 公开。

将内容编入索引的第一步是获取对当前 ServiceWorkerRegistration 的引用。使用 navigator.serviceWorker.ready 是最直接的方法:

const registration = await navigator.serviceWorker.ready;

// Remember to feature-detect before using the API:
if ('index' in registration) {
  // Your Content Indexing API code goes here!
}

如果您是从 Service Worker 内(而不是在网页内)调用 Content Indexing API,则可以通过 registration 直接引用 ServiceWorkerRegistration。它将已定义ServiceWorkerGlobalScope.

添加到索引

使用 add() 方法将网址及其关联的元数据编入索引。何时将项添加到索引由您自行决定。您可能需要将内容添加到索引来响应输入,例如点击“离线保存”按钮。或者,您也可以在每次通过定期后台同步等机制更新缓存数据时,自动添加项。

await registration.index.add({
  // Required; set to something unique within your web app.
  id: 'article-123',

  // Required; url needs to be an offline-capable HTML page.
  url: '/articles/123',

  // Required; used in user-visible lists of content.
  title: 'Article title',

  // Required; used in user-visible lists of content.
  description: 'Amazing article about things!',

  // Required; used in user-visible lists of content.
  icons: [{
    src: '/img/article-123.png',
    sizes: '64x64',
    type: 'image/png',
  }],

  // Optional; valid categories are currently:
  // 'homepage', 'article', 'video', 'audio', or '' (default).
  category: 'article',
});

添加条目只会影响内容索引,而不会向缓存中添加任何内容。

极端情况:如果您的图标依赖于 fetch 处理程序,则从 window 上下文调用 add()

当您调用 add() 时,Chrome 将请求每个图标的网址,以确保它拥有该图标的副本,以便在显示已编入索引的内容列表时使用。

  • 如果您从 window 上下文(换句话说,从您的网页调用)调用 add(),则此请求将在您的 Service Worker 上触发 fetch 事件。

  • 如果您在 Service Worker 内(可能在另一个事件处理脚本内)调用 add(),该请求将不会触发 Service Worker 的 fetch 处理程序。图标将被直接提取,而不涉及任何 Service Worker。如果您的图标依赖于 fetch 处理程序,请谨记这一点,这可能是因为图标只存在于本地缓存中,而不存在于网络中。如果支持,请确保仅从 window 上下文调用 add()

列出索引的内容

getAll() 方法会返回一个包含已编入索引的条目的可迭代列表及其元数据的 promise。返回的条目将包含使用 add() 保存的所有数据。

const entries = await registration.index.getAll();
for (const entry of entries) {
  // entry.id, entry.launchUrl, etc. are all exposed.
}

从索引中移除项

如需从索引中移除项,请使用要移除的项的 id 调用 delete()

await registration.index.delete('article-123');

调用 delete() 只会影响索引。它不会从缓存中删除任何内容。

处理用户删除事件

当浏览器显示已编入索引的内容时,可能会包含带删除菜单项的专属界面,让用户有机会指明他们已看过之前编入索引的内容。Chrome 80 中的删除界面如下所示:

“删除”菜单项。

当用户选择该菜单项时,您的 Web 应用的 Service Worker 将收到 contentdelete 事件。虽然处理此事件是可选操作,但通过它可以让 Service Worker “清理”已被他人标为已完成的内容(如本地缓存的媒体文件)。

您无需在 contentdelete 处理程序内调用 registration.index.delete();如果事件已触发,则浏览器已经执行了相关的索引删除操作。

self.addEventListener('contentdelete', (event) => {
  // event.id will correspond to the id value used
  // when the indexed content was added.
  // Use that value to determine what content, if any,
  // to delete from wherever your app stores it—usually
  // the Cache Storage API or perhaps IndexedDB.
});

有关 API 设计的反馈

这个 API 是否存在异常之处或无法按预期运行?或者说,你是否还有遗漏部分需要来实现?

Content Indexing API 铺垫消息 GitHub 代码库中提交问题,或对现有问题提出您的想法。

实施方面有问题?

您是否发现了 Chrome 实现方面的错误?

https://new.crbug.com 上提交 bug。请添加尽可能多的详细信息、简单的重现说明,并将组件设为 Blink>ContentIndexing

打算使用该 API?

打算在您的 Web 应用中使用 Content Indexing API?您公开提供的支持有助于 Chrome 确定功能的优先级,并向其他浏览器供应商显示支持这些功能的重要性。

  • 您可以使用 # 标签 #ContentIndexingAPI 发送一条推文,并详细说明您在什么位置、如何使用该推文。

内容索引编制对安全和隐私有何影响?

查看根据您根据 W3C 的安全和隐私权调查问卷提供的答案。如果您还有其他问题,请通过项目的 GitHub 代码库发起讨论。

主打图片由 Maksym Kaharlytskyi 在 Unsplash 上使用了。