Storage

现行标准 — 最后更新

参与:
GitHub whatwg/storage (新问题, 打开的问题)
在Matrix上聊天
提交记录:
GitHub whatwg/storage/commits
此提交的快照
@storagestandard
测试:
web-platform-tests storage/ (进行中的工作)
翻译 (非规范性):
日本語
简体中文

摘要

存储标准定义了持久存储和配额估算的API,以及平台存储架构。

1. 介绍

多年来,网页已经发展出各种可以用于存储的API,例如 IndexedDB、localStorageshowNotification()。存储标准通过定义以下内容来整合这些API:

传统上,当用户设备上的存储空间不足时,这些API存储的数据会丢失,用户无法干预。然而,持久化存储桶不能在未经用户同意的情况下被清除。这为网页带来了用户在本地平台上享受的数据保障。

使存储持久化的一种简单方法是调用persist()方法。它同时向终端用户请求权限,并在获得权限后将存储更改为持久化:

navigator.storage.persist().then(persisted => {
  if (persisted) {
    /* … */
  }
});

为了避免在没有预先通知的情况下向终端用户显示由用户代理驱动的对话框,可以编写稍微复杂一些的代码:

Promise.all([
  navigator.storage.persisted(),
  navigator.permissions.query({name: "persistent-storage"})
]).then(([persisted, permission]) => {
  if (!persisted && permission.state == "granted") {
    navigator.storage.persist().then( /* … */ );
  } else if (!persisted && permission.state == "prompt") {
    showPersistentStorageExplanation();
  }
});

estimate() 方法可以用来确定是否有足够的空间来存储应用程序的内容:

function retrieveNextChunk(nextChunkInfo) {
  return navigator.storage.estimate().then(info => {
    if (info.quota - info.usage > nextChunkInfo.size) {
      return fetch(nextChunkInfo.url);
    } else {
      throw new Error("insufficient space to store next chunk");
    }
  }).then( /* … */ );
}

2. 术语

本规范依赖于Infra标准。[INFRA]

本规范使用HTML、IDL和权限标准中的术语。[HTML] [WEBIDL] [PERMISSIONS]

3. 基础设施

用户代理具有各种类型的半持久性状态:

凭据

最终用户凭据,如通过HTML表单提交的用户名和密码

权限

各种功能的权限,如地理位置

网络

HTTP缓存、Cookies、身份验证条目、TLS客户端证书

存储
Indexed DB、Cache API、服务工作者注册、localStoragesessionStorage、应用缓存、通知等。

本标准主要关注存储。

4. 模型

定义本地或会话存储API的标准将定义存储端点并通过更改本标准来注册它们。这些标准将调用获取本地存储瓶映射获取会话存储瓶映射算法,这将为它们提供:

如果你正在为此类API定义标准,建议针对本标准提交一个issue以寻求帮助和审查。

存储模型图(在下文中描述)。

为了隔离这些数据,本标准定义了存储棚,它通过存储密钥存储架进行分段。存储架又由一个存储桶组成,未来可能会由多个存储桶组成,以允许不同的存储策略。最后,存储桶存储瓶组成,每个存储端点一个。

4.1. 存储端点

存储端点是一个本地会话存储API,它使用本标准定义的基础架构,尤其是存储瓶,来跟踪其存储需求。

存储端点有一个标识符,它是一个存储标识符

存储端点还具有类型,这是一个集合,包含存储类型

存储端点还具有一个配额,它是null或代表每个与此存储端点对应的配额(以字节为单位)的推荐数值。

存储标识符是一个ASCII字符串

存储类型为“local”或“session”。


注册的存储端点是一个集合,由下表定义的存储端点组成:

标识符 类型 配额
"caches" « "local" » null
"indexedDB" « "local" » null
"localStorage" « "local" » 5 × 220 (即5 Mebibytes)
"serviceWorkerRegistrations" « "local" » null
"sessionStorage" « "session" » 5 × 220 (即5 Mebibytes)

如前所述,标准可以将这些存储标识符获取本地存储瓶映射获取会话存储瓶映射一起使用。预计未来某些API将适用于这两种存储类型

4.2. 存储键

存储键是由元组组成的,包含一个(一个)。[HTML]

这预计会有所改变;请参见客户端存储分区

获取一个存储键,给定一个环境environment,执行以下步骤:

  1. key成为运行获取用于非存储目的的存储键environment后的结果。

  2. 如果key是一个不透明源,则返回失败。

  3. 如果用户禁用了存储,则返回失败。

  4. 返回key

获取用于非存储目的的存储键,给定一个环境environment,执行以下步骤:

  1. 如果environment环境设置对象,则originenvironment;否则为environment创建URL

  2. 返回一个包含origin元组

要确定A存储键是否等于B存储键,请执行以下步骤:

  1. 如果AB不是同源,则返回false。

  2. 返回true。

4.3. 存储棚

存储棚是一个映射,将存储键映射到存储架。最初它是空的。


用户代理持有一个存储棚,这是一个存储棚。用户代理的存储棚持有所有的本地存储数据。

可遍历的导航对象持有一个存储棚,这是一个存储棚可遍历的导航对象存储棚持有所有的会话存储数据。

遗留克隆一个可遍历存储棚,给定一个可遍历的导航对象A和一个可遍历的导航对象B,执行以下步骤:

  1. 对每个keyshelfA存储棚中的项:

    1. newShelf成为运行创建一个存储架并使用"session"后的结果。

    2. newShelf桶映射["default"]的瓶映射["sessionStorage"]的映射设置为shelf桶映射["default"]的瓶映射["sessionStorage"]的映射克隆

    3. B存储棚[key]设置为newShelf

这被认为是遗留的,因为其益处(如果有的话)并不超过实现的复杂性。因此,它不会被扩展或在HTML之外使用。[HTML]

4.4. 存储架

存储架存储棚中的每个存储键存在。它持有一个桶映射,这是一个将字符串映射到存储桶映射

目前,"default"是桶映射中唯一存在的。参见issue #2。当第一次获取一个存储架时,将为其赋予

获取一个存储架,给定一个存储棚shed、一个环境设置对象environment和一个存储类型type,运行以下步骤:

  1. key设为通过environment运行获取存储键的结果。

  2. 如果key失败,则返回失败。

  3. 如果shed[key]不存在存在,则将shed[key]设置为运行创建存储架并使用type的结果。

  4. 返回shed[key]。

获取一个本地存储架,给定一个环境设置对象environment,返回通过用户代理的存储棚environment和"local"运行获取存储架的结果。

创建一个存储架,给定一个存储类型type,运行以下步骤:

  1. shelf设为一个新的存储架

  2. shelf桶映射["default"]设置为通过type运行创建一个存储桶的结果。

  3. 返回shelf

4.5. 存储桶

存储桶存储端点存储数据的地方。

存储桶有一个瓶映射,将存储标识符映射到存储瓶


本地存储桶是用于本地存储API的存储桶

本地存储桶有一个模式,其值为"best-effort"或"persistent"。最初值为"best-effort"。


会话存储桶会话存储API的存储桶


创建一个存储桶,给定一个存储类型type,运行以下步骤:

  1. bucket设为null。

  2. 如果type为"local",则将bucket设为一个新的本地存储桶

  3. 否则:

    1. 断言:type为"session"。

    2. bucket设为一个新的会话存储桶

  4. 对于 endpoint中的每一个注册的存储端点,若其类型 包含 type,则将bucket瓶映射[endpoint标识符]设为一个新的存储瓶,其配额endpoint配额

  5. 返回bucket

4.6. 存储瓶

存储瓶存储桶的一部分,为单个存储端点划分出来。存储瓶有一个映射,最初为空映射存储瓶还有一个代理映射引用集,最初为空集合存储瓶还有一个配额,其值为null或表示它可以容纳的总字节数的保守估计。null表示没有限制。它仍然受其所包含的存储架存储配额的约束。

存储瓶映射是存储实际数据的位置。用户代理应存储这些数据,并以代理甚至代理集群边界的方式在实现定义的方式中跨越这些边界存储和访问这些内容,使本标准及使用本标准的标准能够访问这些内容。


获取存储瓶映射,给定一个存储类型type环境设置对象environment存储标识符identifier,运行以下步骤:

  1. shed设为null。

  2. 如果type为"local",则将shed设为用户代理的存储棚

  3. 否则:

    1. 断言:type为"session"。

    2. shed设为environment全局对象关联Document节点可导航可遍历导航存储棚

  4. shelf成为运行获取存储架的结果,传递shedenvironmenttype

  5. 如果shelf失败,则返回失败。

  6. bucket成为shelf桶映射["default"]。

  7. bottle成为bucket瓶映射[identifier]。

  8. proxyMap成为一个新的存储代理映射,其支持映射bottle映射

  9. proxyMap附加到bottle代理映射引用集中。

  10. 返回proxyMap

获取本地存储瓶映射,给定环境设置对象environment存储标识符identifier,返回运行获取存储瓶映射的结果,传递"local"、environmentidentifier

获取会话存储瓶映射,给定环境设置对象environment存储标识符identifier,返回运行获取存储瓶映射的结果,传递"session"、environmentidentifier

4.7. 存储代理映射

存储代理映射等同于映射,只是所有操作都改为在其支持映射上执行。

这允许替换支持映射。这对于问题#4和可能的存储访问API是必要的。

4.8. 存储任务源

存储任务源是用于与存储端点相关的所有任务任务源。特别是那些与存储端点配额相关的任务。

排队存储任务,给定一个全局对象global和一系列步骤steps,在存储任务源上使用globalsteps排队一个全局任务

5. 持久化权限

只有当用户(或用户代理代表用户)授予使用"persistent-storage"强大功能的权限时,本地存储桶才能将其模式更改为"persistent"。

当授予给时,持久化权限可用于保护存储免受用户代理的清理策略影响。用户代理在没有源或用户参与的情况下不能清理标记为持久化的存储。这对于用户需要离线使用的资源或用户在本地创建的资源特别有用。

"persistent-storage"强大功能的权限相关算法和类型默认是一样的,除了以下几点:

权限状态

"persistent-storage"的权限状态必须对具有相同的所有环境设置对象具有相同的值。

权限撤销算法
  1. 如果使用"persistent-storage"时获取当前权限状态的结果是"granted",则返回。

  2. 运行获取本地存储架,使用当前设置对象的结果为shelf

  3. shelf桶映射["default"]的模式设置为"best-effort"。

6. 使用量和配额

存储使用量存储架使用的大致字节数的实现定义的粗略估计。

这不能是一个精确的数字,因为用户代理可能会(并且鼓励这样做)使用去重、压缩和其他技术,这些技术会模糊存储架实际使用的字节数。

(这是一个追踪矢量。) 存储配额存储架能容纳的总字节数的实现定义的保守估计。这一数值应小于设备上的总存储空间。它不能是设备上可用存储空间的函数。

强烈建议用户代理在确定配额时考虑导航频率、最近的访问、书签和持久存储”权限

直接或间接透露可用存储空间可能导致指纹识别并泄露超出涉及的范围的信息。

7. 管理

每当用户代理清除存储桶时,必须将其完全清除。用户代理应避免在能够访问它们的脚本正在运行时清除存储桶,除非用户另有指示。

如果删除存储桶导致其包含的存储架桶映射为空,则从其包含的存储棚移除存储架及相应的存储键

7.1. 存储压力

当用户代理面临存储压力时,应清除网络状态和模式为“尽力而为”的本地存储桶,理想情况下,优先以最不影响用户的方式进行清除。

如果用户代理持续面临存储压力,则用户代理应通知用户并提供一种清除剩余本地存储桶(即模式为“持久”的那些)的方式。

会话存储桶必须在可遍历导航关闭时清除。

如果用户代理允许恢复可遍历导航,例如通过重新打开可遍历导航或在重新启动用户代理后继续使用它们,那么清除过程必然涉及更复杂的一组启发式方法。

7.2. 用户界面指南

用户代理应为用户提供清除单个网站的网络状态和存储的能力。用户代理在其用户界面中不应区分网络状态和存储。这确保了网络状态不能用于恢复存储,并减少了用户需要注意的概念数量。

凭据应分开处理,因为它们包含用户可能无法恢复的数据,例如自动生成的密码。权限也最好分开处理,以避免给用户带来不便。

8. API

[SecureContext]
interface mixin NavigatorStorage {
  [SameObject] readonly attribute StorageManager storage;
};
Navigator includes NavigatorStorage;
WorkerNavigator includes NavigatorStorage;

每个环境设置对象都有一个关联的StorageManager对象。[HTML]

storage的getter步骤是返回this相关设置对象StorageManager对象。

[SecureContext,
 Exposed=(Window,Worker)]
interface StorageManager {
  Promise<boolean> persisted();
  [Exposed=Window] Promise<boolean> persist();

  Promise<StorageEstimate> estimate();
};

dictionary StorageEstimate {
  unsigned long long usage;
  unsigned long long quota;
};

persisted()方法的步骤为:

  1. promise一个新的 promise

  2. globalthis相关全局对象

  3. shelf为运行获取本地存储架this相关设置对象的结果。

  4. 如果shelf失败,则拒绝promise,并返回一个TypeError

  5. 否则,并行执行以下步骤:

    1. 如果shelfbucket map["default"]的模式为"persistent",则将persisted设为true;否则设为false。

      当存在内部错误时,它将为false。

    2. 排队存储任务global一起解析promise,并将persisted作为返回值。

  6. 返回promise

persist()方法的步骤为:

  1. promise一个新的 promise

  2. globalthis相关全局对象

  3. shelf为运行获取本地存储架this相关设置对象的结果。

  4. 如果shelf失败,则拒绝promise,并返回一个TypeError

  5. 否则,并行执行以下步骤:

    1. permission请求使用权限"persistent-storage"的结果。

      鼓励用户代理不要让用户在同一时间为相同的来源回答两次问题,并且该算法无法处理这种情况。

    2. bucketshelfbucket map["default"]。

    3. 如果bucket模式为"persistent",则将persisted设为true;否则设为false。

      当存在内部错误时,它将为false。

    4. 如果persisted为false且permission为"granted",则:

      1. bucket模式设为"persistent"。

      2. 如果没有内部错误,则将persisted设为true。

    5. 排队存储任务global一起解析promise,并将persisted作为返回值。

  6. 返回promise

estimate()方法的步骤为:

  1. promise一个新的 promise

  2. globalthis相关全局对象

  3. shelf为运行获取本地存储架this相关设置对象的结果。

  4. 如果shelf失败,则拒绝promise,并返回一个TypeError

  5. 否则,并行执行以下步骤:

    1. usageshelf存储使用量

    2. quotashelf存储配额

    3. dictionary为一个新的StorageEstimate字典,其中usage成员为usagequota成员为quota

    4. 如果在获取usagequota时出现内部错误,则排队存储任务global一起拒绝promise,并返回一个TypeError

      内部错误应该极其罕见,并表明某种低级平台或硬件故障。然而,在网络的规模和实现的多样性及平台的多样性下,意外确实会发生。

    5. 否则,排队存储任务global一起解析promise,并将dictionary作为返回值。

  6. 返回promise

致谢

由此,特别感谢Adrian Bateman,Aislinn Grigas,Alex Russell,Ali Alabbas,Andrew Sutherland,Andrew Williams,Austin Sullivan,Ben Kelly,Ben Turner,Dale Harvey,David Grogan,Domenic Denicola,fantasai,Jake Archibald,Jeffrey Yasskin,Jesse Mykolyn,Jinho Bang,Jonas Sicking,Joshua Bell,Kenji Baheux,Kinuko Yasuda,Luke Wagner,Michael Nordman,Mike Taylor,Mounir Lamouri,Shachar Zohar,黃強 (Shawn Huang),簡冠庭 (Timothy Guan-tin Chien),以及Victor Costan,感谢你们的出色贡献!

本标准由Anne van KesterenAppleannevk@annevk.nl)撰写。

知识产权

版权 © WHATWG(Apple,Google,Mozilla,Microsoft)。本作品采用知识共享署名 4.0 国际许可协议进行许可。对于纳入源代码的部分,此类部分在源代码中根据BSD 3-Clause 许可证进行许可。

这是现行标准。有兴趣获取专利审查版本的人士应查看现行标准审查草案

索引

本规范定义的术语

引用定义的术语

参考文献

规范性引用

[ECMASCRIPT]
ECMAScript语言规范。URL: https://tc39.es/ecma262/multipage/
[HTML]
Anne van Kesteren等。HTML标准。现行标准。URL: https://html.spec.whatwg.org/multipage/
[INFRA]
Anne van Kesteren; Domenic Denicola。Infra标准。现行标准。URL: https://infra.spec.whatwg.org/
[PERMISSIONS]
Marcos Caceres; Mike Taylor。权限。URL: https://w3c.github.io/permissions/
[URL]
Anne van Kesteren。URL标准。现行标准。 URL: https://url.spec.whatwg.org/
[WEBIDL]
Edgar Chen; Timothy Gu。Web IDL标准。现行标准。 URL: https://webidl.spec.whatwg.org/

IDL索引

[SecureContext]
interface mixin NavigatorStorage {
  [SameObject] readonly attribute StorageManager storage;
};
Navigator includes NavigatorStorage;
WorkerNavigator includes NavigatorStorage;

[SecureContext,
 Exposed=(Window,Worker)]
interface StorageManager {
  Promise<boolean> persisted();
  [Exposed=Window] Promise<boolean> persist();

  Promise<StorageEstimate> estimate();
};

dictionary StorageEstimate {
  unsigned long long usage;
  unsigned long long quota;
};

MDN

Navigator/storage

In all current engines.

Firefox57+Safari15.2+Chrome55+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebView?Samsung Internet?Opera Mobile?

WorkerNavigator/storage

In all current engines.

Firefox57+Safari15.2+Chrome55+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebView?Samsung Internet?Opera Mobile?
MDN

StorageManager/estimate

In all current engines.

Firefox57+Safari17+Chrome61+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebView?Samsung Internet?Opera Mobile?
MDN

StorageManager/persist

In all current engines.

Firefox57+Safari15.2+Chrome55+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebView?Samsung Internet?Opera Mobile?
MDN

StorageManager/persisted

In all current engines.

Firefox57+Safari15.2+Chrome55+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebView?Samsung Internet?Opera Mobile?
MDN

StorageManager

In all current engines.

Firefox57+Safari15.2+Chrome55+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebView?Samsung Internet?Opera Mobile?