使用 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 搭配使用。该代码演示了一种方法,用于让 Content Indexing API 与使用 Cache Storage API 存储的 Web 应用项保持同步。

使用此 API

如需使用此 API,您的应用必须具有服务工件和可在线下浏览的网址。如果您的 Web 应用目前没有服务工作器,Workbox 库可以简化服务工作器的创建。

哪些类型的网址可以作为离线功能编入索引?

该 API 支持索引编制与 HTML 文档对应的网址。例如,缓存的媒体文件的网址无法直接编入索引。您需要提供一个可离线显示媒体的网页的网址。

建议的模式是创建一个“查看器”HTML 页面,该页面可以接受底层媒体网址作为查询参数,然后显示文件内容,页面上可能还会显示其他控件或内容。

Web 应用只能向内容索引添加当前服务工件的作用域下的网址。换句话说,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 提供。