存储策略更新

网站可以使用各种存储 API(如 localStorage、IndexedDB、File System 等)在用户的设备上存储数据。这是一项重要功能,通过提供本地资源,使网站能够在离线时保持功能并具有良好的性能。然而,存储容量并非无限。实际上,它通常比设备的磁盘空间小得多。当容量已满时,可能会发生存储操作失败或数据被逐出等问题。作为一名 Web 开发者,了解存储策略有助于避免这些意外结果。本文将讨论包括 Safari 在内的 WebKit 应用中的最新存储策略。

范围

网站数据有很多类型,本文讨论的策略主要涉及由存储 API 创建的类型:localStorage、Cache API、IndexedDB、Service Worker 和 File System。其他类型如 cookie 和 HTTP 缓存目前不受以下策略的约束——例如,它们不受配额限制。

存储配额

存储配额定义了可以存储多少数据。WebKit 中有两种类型的配额:源站配额和总配额。

源站配额

源站配额是单个源站的存储限制。如果达到限制,需要空间的存储操作将失败,并抛出 QuotaExceededError 异常。自 Safari 17.0 以及适用于 iOS 17、iPadOS 17 和 macOS Sonoma 的 WebKit 应用开始生效

  • 对于浏览器应用,源站配额最高可达总磁盘空间的 60%。
  • 对于其他应用,源站配额最高可达总磁盘空间的 15%。

如果一个应用可以设置为默认浏览器(参见 Apple 开发者文档),则它是一个浏览器应用。通过这项更改,Safari 17.0 不再就网站请求使用更多空间向用户发出提示。

跨源框架使用与嵌入它的框架不同的存储分区以防止跟踪,因此它具有不同的配额。该配额目前为主框架源站配额的 10%。

总配额

总配额是所有源站的存储限制。达到该限制可能导致数据逐出,从而释放应用使用的存储空间。自 Safari 17.0 以及适用于 iOS 17、iPadOS 17 和 macOS Sonoma 的 WebKit 应用开始生效

  • 对于浏览器应用,总配额最高可达总磁盘空间的 80%。
  • 对于其他应用,总配额最高可达总磁盘空间的 20%。

当 Web 应用作为独立应用运行(在 iOS 上作为主屏幕 Web 应用,或在 macOS 上添加到 Dock 的 Web 应用)时,其源站配额和总配额与在浏览器应用中打开时相同。

存储逐出

逐出是指非用户或网站主动发起的数据自动删除。它可能在以下几种情况下发生:超出总配额、系统存储空间不足,或用户长时间未与该网站互动时(参见 智能跟踪预防)。

WebKit 通常基于源站逐出数据:一个源站的数据将作为一个整体被删除。要删除的源站顺序是根据最近最少使用策略决定的。最后使用时间是指最后一次用户互动的时间,或最后一次存储操作的时间。

如果一个源站在逐出时有活跃页面,或者其存储处于持久模式,则可能会被排除在逐出之外。默认情况下,所有源站都使用尽力模式,这意味着它们的持久性不作保证,数据可能会被逐出。源站可以使用下面介绍的 Storage API 请求持久模式。

Storage API

不同的浏览器可能有不同的存储策略,而 Storage API 提供了一种标准方式,供网站获取有关当前存储策略的信息。自 Safari 17.0 以及适用于 iOS 17、iPadOS 17 和 macOS Sonoma 的 WebKit 应用开始,Storage API 已得到全面支持。

源站可以使用 StorageManager.estimate() 获取估计的使用量和配额值。使用量表示已使用了多少空间,配额即为源站配额。

if (navigator.storage && navigator.storage.estimate) {
    const storageEstimate = await navigator.storage.estimate();
    const availableSpace = storageEstimate.quota > storageEstimate.usage ? storageEstimate.quota - storageEstimate.usage : 0;
}

请注意,配额是可存储量的上限——不能保证网站能够存储那么多数据,因此必须对 QuotaExceededError 进行错误处理。此外,为了降低暴露使用量和配额带来的指纹识别风险,配额可能会根据现有使用量和网站访问频率等因素发生变化。

源站可以使用 StorageManager.persisted() 检查存储是否处于持久模式,并使用 StorageManager.persist() 请求将模式更改为持久。WebKit 目前根据网站是否作为主屏幕 Web 应用打开等启发式方法来批准请求。

if (navgator.storage && navigator.storage.persisted) {
    const persistent = await navigator.storage.persisted();
    if (!persistent && navigator.storage.persist)
        const result = await navigator.storage.persist();
}

如果您的网站或 Web 应用使用了存储 API 并期望存储大量数据,您应该采用 Storage API 以充分利用有限的空间。如果您有任何反馈或遇到问题,请在 bugs.webkit.org 的“Website Storage”组件下提交问题。