跳转到内容

存储中的平台

Safari 对脚本可写存储的 7 天上限

发布于 更新于

智能跟踪防护(ITP)会在用户连续七个 Safari 使用日没有与某站点交互之后,删除该站点全部由脚本写入的存储。计数只看使用 Safari 的天数,不按日历算。主屏幕 Web App 在 Safari 之外运行,使用自己的计数器,所以同一个站点可能在标签页里丢失数据,在已安装应用里却仍然保留。

这项上限分两步到位,真正波及 PWA 离线数据的是第二步。

ITP 2.1 于 2019 年 2 月 21 日随 iOS 12.2 与 Safari 12.1 的测试版公布,将持久化客户端 Cookie 的有效期上限设为七天。这包括所有用 document.cookie 创建的持久 Cookie。WebKit 解释,跟踪方已经开始往第一方 Cookie 存储中写入标识符;在第一方上下文运行的跟踪脚本可以相互读写数据。

2020 年 3 月 24 日那篇介绍 iOS 与 iPadOS 13.4、macOS 上 Safari 13.1 中 ITP 的文章,把同一限制扩展到其余由脚本写入的存储,原因是第三方脚本已转向 LocalStorage 这类第一方存储,而后者根本没有过期机制。它对触发条件的措辞很精确:在连续七个 Safari 使用日没有与站点发生用户交互之后,删除该站点全部由脚本写入的存储。不打开 Safari,使用日就不增加;与站点交互则会重置计数。

WebKit 列出了受影响的脚本可写存储形式,并排除了若干遗留的网站数据类型:

存储形式 PWA 会失去什么
Indexed DB 结构化的应用数据,包括离线队列与已同步的记录。
LocalStorage 放在同步存储里的偏好、特性开关与会话线索。
SessionStorage 被删除源的按标签页状态。
Media keys 为加密媒体保存的密钥。
Service Worker 注册与缓存 注册本身连同它的 Cache Storage 内容。下一次访问没有既有控制者;只有页面再次调用 register() 并创建新注册后,新 Worker 才进入安装阶段。

这次清理会影响离线功能。如果应用外壳放在 Cache Storage,记录放在 IndexedDB,两者就会连同 Service Worker 注册一起删除。

WebKit 在 2020 年那篇文章里同时交代了已安装应用。七天上限的触发条件是连续七个 Safari 使用日没有与站点发生用户交互,而这说的是 Safari 里的情形;添加到主屏幕的 Web App 不属于 Safari,因而有自己的使用天数计数器,该计数与 Web App 的实际使用相符,打开它就会重置计时。WebKit 表示,不预期删除这类 Web App 的第一方数据,并请开发者在遇到这种情况时按严重缺陷上报,因为删除 Web App 中第一方的数据并非智能跟踪防护的本意。

七天上限只是存储逐出的一种原因。按 WebKit 2023 年的存储策略,超过总配额、系统存储紧张或用户已有一段时间未与站点交互,都可能触发删除,无需用户或站点发起。最后一种就是 ITP 的清理路径。

默认情况下,所有源都使用尽力而为模式。同一篇文章说明,清理时仍有活动页面,或者存储已切换到持久化模式的源,可能免于逐出。

持久化模式可以从脚本申请。该文章说明,源可以用 StorageManager.persisted() 查询模式、用 StorageManager.persist() 请求切换;自 Safari 17.0 以及 iOS 17、iPadOS 17 与 macOS Sonoma 上的 WebKit 应用起,Storage API 获得完整支持;WebKit 依据「站点是否作为主屏幕 Web App 打开」这类启发式规则决定是否授予请求。文章还提到,独立运行的 Web App(iOS 上的主屏幕 Web App,或 macOS 上添加到程序坞的 Web App)与在浏览器应用中打开时拥有相同的源配额与总配额,而 Cookie 与 HTTP 缓存不受这套管辖存储 API 的配额策略约束。

Service Worker 注册缺失时可以重新注册;申请持久化存储也可能让源免于逐出,具体条件见 WebKit 的说明。

WebKit 说明七天清理包含 Service Worker 注册和缓存。注册缺失并不能说明它为何不存在,因此下面的示例把这种状态作为重新注册并获取引导数据的条件,而不把它当成 ITP 清理发生过的证据。

async function reseedFromServer() {
const response = await fetch('/api/bootstrap');
localStorage.setItem('bootstrap', await response.text());
}
async function restoreMissingRegistration() {
if (!('serviceWorker' in navigator)) {
return 'unsupported';
}
const registration = await navigator.serviceWorker.getRegistration();
if (!registration) {
await navigator.serviceWorker.register('/sw.js');
await reseedFromServer();
return 'missing-registration-reseeded';
}
return 'intact';
}
restoreMissingRegistration().then((state) => console.log('存储状态:', state));

该分支处理文档记录的删除机制,但不把注册缺失当作其原因的证据。WebKit 的文章描述的是客户端网站数据删除,而不是服务端账号删除。

从已安装的应用申请持久化模式

链接到“从已安装的应用申请持久化模式”一节

WebKit 的启发式规则偏向从主屏幕打开的 Web App,因此在应用安装后申请持久化,比首次在标签页访问时申请更合适。函数会返回申请结果,调用后要检查是否获准。

async function ensurePersistentStorage() {
if (!('storage' in navigator) || !navigator.storage.persist) {
return false; // 没有 Storage API:按会被删除来规划
}
if (await navigator.storage.persisted()) {
return true;
}
return navigator.storage.persist(); // 可能解析为 false,两种结果都要处理
}

返回 false 表示源仍处于尽力而为模式,数据必须能从服务端重建。返回 true 也不等于豁免。2023 年那篇文章的措辞是:源的存储处于持久化模式时「可能」被排除在逐出之外,这是一项条件而不是保证;该文讲的是存储策略,并没有对七天清理作出任何承诺,所以上一个示例里的恢复路径无论如何都值得保留。

本次修订把第一方 Cookie 表述限定为跟踪脚本,不再把 Service Worker 注册缺失视为 ITP 清理的证据,并删除无来源支持的系统设置测试步骤与账号丢失结论。

规范

规范状态
无。