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

让服务工作线程能够告知浏览器哪些网页在离线状态下可正常运行

使用渐进式 Web 应用意味着,无论网络连接的当前状态如何,用户都可以访问他们关注的信息(图片、视频、文章等)。服务工作线程Cache Storage APIIndexedDB 等技术为您提供了在用户直接与 PWA 互动时存储和传送数据的构建块。但是,构建离线优先的高质量 PWA 只是整个过程的一部分。如果用户不知道 Web 应用的内容在离线状态下也能使用,就无法充分利用您为实现该功能所做的努力。

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

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

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

此外,当 Chrome 检测到用户处于离线状态时,还可以主动推荐内容。

Content Indexing API 不是缓存内容的替代方法。它是一种提供有关服务工作器已缓存的网页的元数据的方法,以便浏览器在用户可能想要查看这些网页时显示这些网页。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 上查看示例应用的源代码

另一个示例应用(Scrapbook PWA)演示了如何将 Content Indexing API 与 Web Share Target API 搭配使用。此代码演示了一种技术,可利用 Cache Storage API 使 Content Indexing API 与 Web 应用存储的项目保持同步。

使用此 API

如需使用此 API,您的应用必须具有服务工件和可在线下浏览的网址。如果您的 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!
}

如果您是在服务工件(而不是网页)内调用 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 事件。

  • 如果您在服务工件内(可能在其他事件处理程序内)调用 add(),该请求不会触发服务工件的 fetch 处理程序。系统会直接提取图标,而无需任何服务工件参与。如果您的图标依赖于 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 事件。虽然处理此事件并非强制性要求,但它为您的服务工件提供了一个机会,可以“清理”用户已表明不再使用的本地缓存媒体文件等内容。

您无需在 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 实现中的 bug?

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

打算使用该 API?

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

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

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

主打图片:Unsplash 上的 Maksym Kaharlytskyi 提供。