1. 引言
目前,用户并不容易发现高质量的离线可用 Web 内容。用户必须知道哪些网站可离线工作, 或者需要安装 PWA,才能在离线时浏览内容。这并不是良好的用户体验,因为没有入口点可用于发现 可用内容。为了解决这一点,本规范涵盖了一种新的 API,允许开发者将其特定内容告知浏览器。
内容索引允许网站向浏览器注册其离线可用内容。然后浏览器可以改进网站的离线能力, 并向用户提供可在离线时浏览的内容。此数据也可用于改进设备端搜索和增强浏览历史。
使用该 API 可帮助用户更轻松地发现以下示例用例中的内容:
-
新闻网站在后台预取最新文章。
-
内容流媒体应用向浏览器注册已下载内容。
1.1. 示例
function deleteArticleResources( id) { return Promise. all([ caches. open( 'offline-articles' ) . then( articlesCache=> articlesCache. delete ( `/article/ ${ id} ` )), // 如果该函数是因 `contentdelete` 事件而被调用的, // 则这是一个 no-op。 self. registration. index. delete ( id), ]); } self. addEventListener( 'activate' , event=> { // 当 service worker 被激活时,移除旧内容。 event. waitUntil( async function () { const descriptions= await self. registration. index. getAll(); const oldDescriptions= descriptions. filter( description=> shouldRemoveOldArticle( description)); await Promise. all( oldDescriptions. map( description=> deleteArticleResources( description. id))); }()); }); self. addEventListener( 'push' , event=> { const payload= event. data. json(); // 获取并存储文章,然后注册它。 event. waitUntil( async function () { const articlesCache= await caches. open( 'offline-articles' ); await articlesCache. add( `/article/ ${ payload. id} ` ); await self. registration. index. add({ id: payload. id, title: payload. title, description: payload. description, category: 'article' , icons: payload. icons, url: `/article/ ${ payload. id} ` , }); // 如果紧急,则显示通知。 }()); }); self. addEventListener( 'contentdelete' , event=> { // 在用户删除后清除底层内容。 event. waitUntil( deleteArticleResources( event. id)); });
在上面的示例中,shouldRemoveOldArticle 是开发者定义的函数。
2. 隐私考量
触发 contentdelete 事件可能会在用户离开页面后泄露用户的 IP 地址。利用这一点
可用于跟踪位置历史。用户代理应通过限制事件的持续时间来限制跟踪。
在触发 contentdelete 事件时,用户代理应阻止网站添加新内容。
这可防止垃圾网站重新添加相同内容,并防止用户看到其刚刚删除的内容。
显示所有已注册内容可能会导致 恶意网站用其内容向用户发送垃圾信息,以最大化曝光。强烈鼓励用户代理不要浮现所有内容, 而是基于一组用户代理定义的信号选择适当的内容来显示, 这些信号旨在改善用户体验。
3. 基础设施
3.1. 对 service worker registration 的扩展
一个 service worker registration 还具有:
-
一个 条目编辑队列(一个 并行队列),初始为启动一个新的并行队列的结果。
3.2. 内容索引条目
一个内容索引 条目由以下部分组成:
-
一个描述(一个
ContentDescription)。 -
一个启动 URL(一个 URL)。
-
一个service worker registration(一个 service worker registration)。
3.2.1. 显示
用户代理可在任何时候显示一个 内容索引 条目(entry),只要 entry 存在于 一个 service worker registration 的内容索引条目中。
注: 用户代理应限制浮现的内容,以避免向用户显示过多条目。
-
UI 必须醒目地显示 entry 的 service worker registration 的 作用域 URL 的 源。
-
UI 可显示 entry 的描述的
description。 -
UI 可将 entry 的任何图标显示为 图像。
-
UI 应提供一种方式,让用户删除 UI 所暴露的底层 entry,在这种情况下, 对 entry 运行删除一个内容索引条目。
-
UI 必须提供一种方式,让用户激活它(例如通过点击),在这种情况下, 对 entry 运行激活一个内容索引条目。
3.2.2. 取消显示
4. 算法
4.1. 删除一个内容 索引条目
-
令 contentIndexEntries 为 entry 的 service worker registration 的内容索引条目。
-
将以下步骤入队到 entry 的 service worker registration 的条目编辑队列:
-
取消显示 entry。
-
移除 contentIndexEntries[id]。
-
为 entry 触发一个 content delete 事件。
-
4.2. 激活一个 内容索引条目
-
令 activeWorker 为 entry 的 service worker registration 的active worker。
-
如果 activeWorker 为 null,则中止这些步骤。
-
令 newContext 为一个新的顶级浏览上下文。
-
使用用户交互任务源,将运行以下步骤的任务排队到 newContext 的
Window对象的环境设置对象的负责事件循环:-
HandleNavigate:以exceptions enabled 和 replacement enabled,将 newContext 导航到 entry 的启动 URL。
-
如果标记为 HandleNavigate 的步骤中调用的算法步骤抛出异常,则中止 这些步骤。
-
令 frameType 为 "`top-level`"。
-
令 visibilityState 为 newContext 的活动文档的
visibilityState属性值。 -
令 focusState 为以 newContext 的活动文档作为参数运行has focus steps的结果。
-
令 ancestorOriginsList 为 newContext 的活动文档的相关全局对象的
Location对象的祖先源列表的 关联列表。 -
使用 DOM manipulation task source,将运行以下步骤的任务排队到 serviceWorkerEventLoop:
-
创建新的 Browsing Context 是这里正确的做法吗? (issue)
4.3. 触发一个 content delete 事件
ContentIndexEvent
触发一个功能性事件,名称为
"contentdelete",并带有以下属性:
5. API
5.1.
对 ServiceWorkerGlobalScope
的扩展
partial interface ServiceWorkerGlobalScope {attribute EventHandler ; };oncontentdelete
5.1.1. 事件
以下是必须由所有实现
ServiceWorker
接口的对象作为事件处理器 IDL 属性支持的事件处理器(及其对应的事件处理器事件类型):
| 事件处理器事件类型 | 事件处理器 | 接口 |
|---|---|---|
contentdelete
| oncontentdelete
| ContentIndexEvent
|
5.2. 对 ServiceWorkerRegistration
的扩展
partial interface ServiceWorkerRegistration { [SameObject ]readonly attribute ContentIndex index ; };
一个 ServiceWorkerRegistration
具有一个内容索引(一个 ContentIndex),
初始为一个新的
ContentIndex,
其service worker registration 是上下文
对象的service worker registration。
5.3. ContentIndex
仅在一个当前引擎中。
Opera无Edge无
Edge (Legacy)无IE无
Firefox for Android无iOS Safari无Chrome for Android84+Android WebView84+Samsung Internet无Opera Mobile60+
enum {ContentCategory ,"" ,"homepage" ,"article" ,"video" , };"audio" dictionary {ContentDescription required DOMString ;id required DOMString ;title required DOMString ;description ContentCategory = "";category sequence <ImageResource >= [];icons required USVString ; }; [url Exposed =(Window ,Worker )]interface {ContentIndex Promise <undefined >add (ContentDescription );description Promise <undefined >delete (DOMString );id Promise <sequence <ContentDescription >>getAll (); };
仅在一个当前引擎中。
Opera无Edge无
Edge (Legacy)无IE无
Firefox for Android无iOS Safari无Chrome for Android84+Android WebView84+Samsung Internet无Opera Mobile60+
一个 ContentIndex
具有一个service worker registration(一个 service worker registration)。
5.3.1.
add()
仅在一个当前引擎中。
Opera无Edge无
Edge (Legacy)无IE无
Firefox for Android无iOS Safari无Chrome for Android84+Android WebView84+Samsung Internet无Opera Mobile60+
add(description) 方法在被调用时,必须
返回一个新的 promise promise,并并行运行
这些步骤:
-
令 registration 为上下文 对象的service worker registration。
-
如果 registration 的active worker 为 null,则用
TypeError拒绝 promise 并中止这些步骤。 -
如果 description 的
id、title、description或url中的任何一个是空字符串,则用TypeError拒绝 promise 并中止这些步骤。 -
令 launchURL 为用上下文对象的相关设置对象的API 基准 URL 对 description 的
url进行解析的结果。注: 之后可能会引入一个具有更窄作用域的新的 service worker registration。
-
令 matchedRegistration 为以 launchURL 作为参数运行 Match Service Worker Registration 算法的结果。
-
如果 matchedRegistration 不等于 registration,则用
TypeError拒绝 promise 并中止 这些步骤。 -
如果 registration 的active worker 的扩展事件集合不包含一个
FetchEvent, 则用TypeError拒绝 promise 并中止这些步骤。 -
令 icons 为空列表。
-
可选地,用户代理可从 description 的
icons中选择要使用的图标。在这种情况下,在成功解析后,对 description 的icons的选定图标中的图像资源(resource)逐个 运行以下步骤: -
令 entry 为一个新的内容索引条目,其具有:
- 描述
-
description。
- 启动 URL
-
launchURL
- service worker registration
-
registration。
- 图标
-
icons
-
令 id 为 description 的
id。 -
令 contentIndexEntries 为 registration 的内容索引 条目。
注: 添加具有已有 ID 的描述会覆盖先前的值。
5.3.2.
delete()
仅在一个当前引擎中。
Opera无Edge无
Edge (Legacy)无IE无
Firefox for Android无iOS Safari无Chrome for Android84+Android WebView84+Samsung Internet无Opera Mobile60+
5.3.3.
getAll()
5.4. ContentIndexEvent
仅在一个当前引擎中。
Opera无Edge无
Edge (Legacy)无IE无
Firefox for Android无iOS Safari无Chrome for Android84+Android WebView84+Samsung Internet无Opera Mobile60+
dictionary :ContentIndexEventInit ExtendableEventInit {required DOMString ; }; [id Exposed =ServiceWorker ]interface :ContentIndexEvent ExtendableEvent {(constructor DOMString ,type ContentIndexEventInit );init readonly attribute DOMString ; };id