[go: up one dir, main page]

Storage

现行标准 — 最后更新

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

摘要

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

1. 介绍

多年来,网页已经发展出各种可以用于存储的API,例如 IndexedDB、localStorage和showNotification()。存储标准通过定义以下内容来整合这些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、Service Worker注册、localStorage、sessionStorage、应用缓存、通知等。

本标准主要关注存储。

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]

一个存储键的相等性, 给定存储键 A 和B, 如果A 等于 B,则返回 true;否则返回 false。

预计会发生变化;请参阅客户端存储分区。

要获取存储密钥,给定一个环境 environment:

  1. 令 key 为使用 environment 运行获取非存储目的的存储密钥的结果。

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

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

  4. 返回 key。

要获取非存储目的的存储密钥,给定一个环境 environment:

  1. 如果 environment 是一个环境设置对象,则令 origin 为 environment 的源;否则为 environment 的创建 URL 的源。

  2. 返回一个由 origin 组成的元组。

要确定一个存储 键 A 是否等于 存储键 B:

  1. 如果A的源与 同源, B的源不同源,则返回 false。

  2. 返回 true。

4.3. 存储棚

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


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

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

要旧版克隆可遍历存储棚, 给定一个可遍历导航对象 A 和一个可遍历导航对象 B,请执行以下步骤:

  1. 对每个key → shelf在A的存储棚中的项:

    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 的存储桶。

一个本地存储桶有一个模式,它是 “尽力而为”或“持久”。它最初是“尽力而为”。


会话存储桶是会话存储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成为运行获取存储架的结果,传递shed、environment和type。

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

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

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

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

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

  10. 返回proxyMap。

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

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

4.7. 存储代理映射

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

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

4.8. 存储任务源

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

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

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. 令global为this的相关全局对象。

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

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

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

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

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

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

  6. 返回promise。

persist()方法的步骤为:

  1. 令promise为一个新的 promise。

  2. 令global为this的相关全局对象。

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

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

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

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

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

    2. 令bucket为shelf的bucket 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. 令global为this的相关全局对象。

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

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

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

    1. 令usage为shelf的存储使用量。

    2. 令quota为shelf的存储配额。

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

    4. 如果在获取usage和quota时出现内部错误,则排队存储任务与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 Kesteren(Apple,annevk@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?