|
| 1 | +<pre class=metadata> |
| 2 | +Group: WHATWG |
| 3 | +Date: 2019-02-18 |
| 4 | +H1: Storage |
| 5 | +Shortname: storage |
| 6 | +Text Macro: TWITTER storagestandard |
| 7 | +Abstract: The Storage Standard defines an API for persistent storage and quota estimates, as well as the platform storage architecture. |
| 8 | +Translation: ja https://triple-underscore.github.io/storage-ja.html |
| 9 | +</pre> |
| 10 | + |
| 11 | + |
| 12 | + |
| 13 | +<h2 id=introduction>Introduction</h2> |
| 14 | + |
| 15 | +Over the years the web has grown various APIs that can be used for storage, e.g., IndexedDB, |
| 16 | +<code>localStorage</code>, and <code>showNotification()</code>. The Storage Standard consolidates |
| 17 | +these APIs by defining: |
| 18 | + |
| 19 | +<ul class=brief> |
| 20 | + <li>A bucket, the primitive these APIs store their data in |
| 21 | + <li>A way of making that bucket persistent |
| 22 | + <li>A way of getting usage and quota estimates for an <a for=/>origin</a> |
| 23 | +</ul> |
| 24 | + |
| 25 | +<p>Traditionally, as the user runs out of storage space on their device, the data stored with these |
| 26 | +APIs gets lost without the user being able to intervene. However, persistent buckets cannot be |
| 27 | +cleared without consent by the user. This thus brings data guarantees users have enjoyed on native |
| 28 | +platforms to the web. |
| 29 | + |
| 30 | +<div class="example" id=example-3a7051a8> |
| 31 | + <p>A simple way to make storage persistent is through invoking the {{persist()}} method. It |
| 32 | + simultaneously requests the end user for permission and changes the storage to be persistent once |
| 33 | + granted:</p> |
| 34 | + |
| 35 | + <pre><code class="lang-javascript"> |
| 36 | +navigator.storage.persist().then(persisted => { |
| 37 | + if(persisted) { |
| 38 | + /* … */ |
| 39 | + } |
| 40 | +}) |
| 41 | +</code></pre> |
| 42 | + |
| 43 | + <p>To not show user-agent-driven dialogs to the end user unannounced slightly more involved code |
| 44 | + can be written:</p> |
| 45 | + |
| 46 | + <pre><code class="lang-javascript"> |
| 47 | +Promise.all([ |
| 48 | + navigator.storage.persisted(), |
| 49 | + navigator.permissions.query({name: "persistent-storage"}) |
| 50 | +]).then(([persisted, permission]) => { |
| 51 | + if(!persisted && permission.status == "granted") { |
| 52 | + navigator.storage.persist().then( /* … */ ) |
| 53 | + } else if(!persistent && permission.status == "prompt") { |
| 54 | + showPersistentStorageExplanation() |
| 55 | + } |
| 56 | +}) |
| 57 | +</code></pre> |
| 58 | + |
| 59 | + <p>The {{estimate()}} method can be used to determine whether there is enough space left to |
| 60 | + store content for an application: |
| 61 | + |
| 62 | + <pre><code class="lang-javascript"> |
| 63 | + function retrieveNextChunk(nextChunkInfo) { |
| 64 | + return navigator.storage.estimate().then(info => { |
| 65 | + if(info.quota - info.usage > nextChunkInfo.size) |
| 66 | + return fetch(nextChunkInfo.url) |
| 67 | + else throw new Error("insufficient space to store next chunk") |
| 68 | + }).then( /* … */ ) |
| 69 | + } |
| 70 | +</code></pre> |
| 71 | + |
| 72 | +</div> |
| 73 | + |
| 74 | + |
| 75 | + |
| 76 | +<h2 id=terminology>Terminology</h2> |
| 77 | + |
| 78 | +<p>This specification depends on the Infra Standard. [[!INFRA]] |
| 79 | + |
| 80 | +<p>This specification uses terminology from the DOM, HTML, IDL, Permissions API, and URL Standards. |
| 81 | +[[DOM]] [[HTML]] [[WEBIDL]] [[PERMISSIONS]] [[URL]] |
| 82 | + |
| 83 | +A <dfn>schemeless origin group</dfn> is a group of one of the following: |
| 84 | + |
| 85 | +<ul> |
| 86 | + <li>Identical <a lt="opaque origin">opaque origins</a>. |
| 87 | + <li><a lt="tuple origin">Tuple origins</a> whose <a for=origin>host</a> is identical and not a |
| 88 | + <a for=/>domain</a>. |
| 89 | + <li><a lt="tuple origin">Tuple origins</a> whose <a for=origin>host</a> is a <a for=/>domain</a> of |
| 90 | + which the <a href="https://publicsuffix.org/list/">registrable domain</a> is identical.</li> |
| 91 | +</ul> |
| 92 | + |
| 93 | +<p class="note">This definition will move to a more suitable location eventually. |
| 94 | + |
| 95 | + |
| 96 | + |
| 97 | +<h2 id=infrastructure>Infrastructure</h2> |
| 98 | + |
| 99 | +A user agent has various kinds of storage: |
| 100 | + |
| 101 | +<dl> |
| 102 | + <dt>Credentials |
| 103 | + <dd><p>End-user credentials, such as username and passwords submitted through HTML forms |
| 104 | + <dt>Permissions |
| 105 | + <dd><p>Permissions for various features, such as geolocation |
| 106 | + <dt>Network |
| 107 | + <dd><p>HTTP cache, cookies, authentication entries, TLS client certificates |
| 108 | + <dt>Site |
| 109 | + <dd>Indexed DB, Cache API, service worker registrations, <code>localStorage</code>, |
| 110 | + <code>history.pushState()</code>, application caches, notifications, etc. |
| 111 | +</dl> |
| 112 | + |
| 113 | +This specification primarily concerns itself with <dfn export id=site-storage>site storage</dfn>. |
| 114 | + |
| 115 | +<a>Site storage</a> consists of zero or more |
| 116 | +<dfn export id=site-storage-unit>site storage units</dfn>. |
| 117 | + |
| 118 | +Each <a for=/>origin</a> has an associated <a>site storage unit</a>. A <a>site storage unit</a> |
| 119 | +contains a single <dfn export id=bucket oldids=box>bucket</dfn>. [[HTML]] |
| 120 | + |
| 121 | + |
| 122 | +<h3 id=buckets oldids=boxes>Buckets</h3> |
| 123 | + |
| 124 | +A <a>bucket</a> has <dfn export for=bucket oldids=box-mode>mode</dfn> which is either |
| 125 | +"<code title>best-effort</code>" or "<code title>persistent</code>". A |
| 126 | +<dfn export oldids=persistent-box>persistent bucket</dfn> is a <a>bucket</a> whose |
| 127 | +<a for=bucket>mode</a> is "<code title>persistent</code>". A |
| 128 | +<dfn export oldids=non-persistent-box>non-persistent bucket</dfn> is a <a>bucket</a> whose |
| 129 | +<a for=bucket>mode</a> is <em>not</em> "<code title>persistent</code>". |
| 130 | + |
| 131 | +A bucket is considered to be an atomic unit. Whenever a <a>bucket</a> is cleared by the user agent, |
| 132 | +it must be cleared in its entirety. |
| 133 | + |
| 134 | + |
| 135 | + |
| 136 | +<h2 id=persistence>Persistence permission</h2> |
| 137 | + |
| 138 | +A <a>bucket</a> can only be turned into a <a>persistent bucket</a> if the user (or user agent |
| 139 | +on behalf of the user) has granted permission to use the {{"persistent-storage"}} feature. |
| 140 | + |
| 141 | +<p class="note">When granted to an <a for=/>origin</a>, the persistence permission can be used to |
| 142 | +protect storage from the user agent's clearing policies. The user agent cannot clear storage marked |
| 143 | +as persistent without involvement from the <a for=/>origin</a> or user. This makes it particularly |
| 144 | +useful for resources the user needs to have available while offline or resources the user creates |
| 145 | +locally. |
| 146 | + |
| 147 | +The <dfn for="PermissionName" enum-value>"<code>persistent-storage</code>"</dfn> |
| 148 | +<a>powerful feature</a>'s permission-related flags, algorithms, and types are defaulted, except for: |
| 149 | + |
| 150 | +<dl> |
| 151 | + <dt><a>permission state</a></dt> |
| 152 | + <dd>{{"persistent-storage"}}'s <a>permission state</a> must have the same value for all |
| 153 | + <a>environment settings objects</a> with a given <a for=/>origin</a>.</dd> |
| 154 | + |
| 155 | + <dt><a>permission revocation algorithm</a></dt> |
| 156 | + <dd algorithm="permission-revocation">If {{"persistent-storage"}}'s <a>permission state</a> is not |
| 157 | + {{"granted"}}, then set the current <a for=/>origin</a>’s <a>site storage unit</a>'s |
| 158 | + <a>bucket</a>'s <a for=bucket>mode</a> to "<code>best-effort</code>".</dd> |
| 159 | +</dl> |
| 160 | + |
| 161 | + |
| 162 | + |
| 163 | +<h2 id=usage-and-quota>Usage and quota</h2> |
| 164 | + |
| 165 | +The <dfn export>site storage usage</dfn> of an <a for=/>origin</a> <var>origin</var> is a rough |
| 166 | +estimate of the amount of bytes used in <var>origin</var>'s <a>site storage unit</a>. |
| 167 | + |
| 168 | +<p class=note>This cannot be an exact amount as user agents might, and are encouraged to, use |
| 169 | +deduplication, compression, and other techniques that obscure exactly how much bytes an |
| 170 | +<a for=/>origin</a> uses. |
| 171 | + |
| 172 | +The <dfn export>site storage quota</dfn> of an <a for=/>origin</a> <var>origin</var> is a |
| 173 | +conservative estimate of the amount of bytes available to <var>origin</var>'s |
| 174 | +<a>site storage unit</a>. This amount should be less than the total available storage space on the |
| 175 | +device to give users some wiggle room. |
| 176 | + |
| 177 | +<p class=note>User agents are strongly encouraged to provide "popular" <a for=/>origins</a> with a |
| 178 | +larger <a>site storage quota</a>. Factors such as navigation frequency, recency of visits, |
| 179 | +bookmarking, and <a href="#persistence">permission</a> for {{"persistent-storage"}} can be used as |
| 180 | +indications of "popularity". |
| 181 | + |
| 182 | + |
| 183 | + |
| 184 | +<h2 id=ui-guidelines>User Interface Guidelines</h2> |
| 185 | + |
| 186 | +User agents should not distinguish between network storage and <a>site storage</a> in their user |
| 187 | +interface. Instead user agents should offer users the ability to remove all storage for a given |
| 188 | +<a>schemeless origin group</a>. This ensures to some extent that network storage cannot be used to |
| 189 | +revive <a>site storage</a>. This also reduces the amount users need to know about the different ways |
| 190 | +in which a <a>schemeless origin group</a> can store data. |
| 191 | +<!-- To some extent, since HTTP ETag... And also, permissions/credentials, maybe? --> |
| 192 | + |
| 193 | +Credentials storage should be separated as it might contain data the user might not be able to |
| 194 | +revive, such as an autogenerated password. Since permissions storage is mostly simple booleans it |
| 195 | +too can be separated to avoid inconveniencing the user. Credentials and permissions are also |
| 196 | +somewhat easier to understand and differentiate for users from network storage and |
| 197 | +<a>site storage</a>. |
| 198 | + |
| 199 | + |
| 200 | +<h3 id=storage-pressure>Storage Pressure</h3> |
| 201 | + |
| 202 | +When the user agent notices it comes under storage pressure and it cannot free up sufficient space |
| 203 | +by clearing network storage and <a>non-persistent buckets</a> within <a>site storage</a>, then the |
| 204 | +user agent should alert the user and offer a way to clear <a>persistent buckets</a>. |
| 205 | + |
| 206 | + |
| 207 | + |
| 208 | +<h2 id=api>API</h2> |
| 209 | + |
| 210 | +<pre class=idl> |
| 211 | +[SecureContext] |
| 212 | +interface mixin NavigatorStorage { |
| 213 | + [SameObject] readonly attribute StorageManager storage; |
| 214 | +}; |
| 215 | +Navigator includes NavigatorStorage; |
| 216 | +WorkerNavigator includes NavigatorStorage; |
| 217 | +</pre> |
| 218 | + |
| 219 | +Each <a>environment settings object</a> has an associated {{StorageManager}} object. |
| 220 | +[[HTML]] |
| 221 | + |
| 222 | +The <dfn attribute for=NavigatorStorage><code>storage</code></dfn> attribute's getter must return |
| 223 | +<a>context object</a>'s <a>relevant settings object</a>'s {{StorageManager}} object. |
| 224 | + |
| 225 | +<pre class=idl> |
| 226 | +[SecureContext, |
| 227 | + Exposed=(Window,Worker)] |
| 228 | +interface StorageManager { |
| 229 | + Promise<boolean> persisted(); |
| 230 | + [Exposed=Window] Promise<boolean> persist(); |
| 231 | + |
| 232 | + Promise<StorageEstimate> estimate(); |
| 233 | +}; |
| 234 | + |
| 235 | +dictionary StorageEstimate { |
| 236 | + unsigned long long usage; |
| 237 | + unsigned long long quota; |
| 238 | +}; |
| 239 | +</pre> |
| 240 | + |
| 241 | +The <dfn method for=StorageManager><code>persisted()</code></dfn> method, when invoked, must run |
| 242 | +these steps: |
| 243 | + |
| 244 | +<ol> |
| 245 | + <li><p>Let <var>promise</var> be a new promise. |
| 246 | + |
| 247 | + <li><p>Let <var>origin</var> be <a>context object</a>'s <a>relevant settings object</a>'s |
| 248 | + <a for="environment settings object">origin</a>. |
| 249 | + |
| 250 | + <li><p>If <var>origin</var> is an <a>opaque origin</a>, then reject <var>promise</var> with a |
| 251 | + {{TypeError}}. |
| 252 | + |
| 253 | + <li> |
| 254 | + <p>Otherwise, run these steps <a>in parallel</a>: |
| 255 | + |
| 256 | + <ol> |
| 257 | + <li> |
| 258 | + <p>Let <var>persisted</var> be true if <var>origin</var>'s <a>site storage unit</a>'s |
| 259 | + <a>bucket</a> is a <a>persistent bucket</a>, and false otherwise. |
| 260 | + |
| 261 | + <p class=note>It will be false when there's an internal error. |
| 262 | + |
| 263 | + <li><p><a>Queue a task</a> to resolve <var>promise</var> with <var>persisted</var>. |
| 264 | + </ol> |
| 265 | + |
| 266 | + <li><p>Return <var>promise</var>. |
| 267 | +</ol> |
| 268 | + |
| 269 | +The <dfn method for=StorageManager><code>persist()</code></dfn> method, when invoked, must run these |
| 270 | +steps: |
| 271 | + |
| 272 | +<ol> |
| 273 | + <li><p>Let <var>promise</var> be a new promise. |
| 274 | + |
| 275 | + <li><p>Let <var>origin</var> be <a>context object</a>'s <a>relevant settings object</a>'s |
| 276 | + <a for="environment settings object">origin</a>. |
| 277 | + |
| 278 | + <li><p>If <var>origin</var> is an <a>opaque origin</a>, then reject <var>promise</var> with a |
| 279 | + {{TypeError}}. |
| 280 | + |
| 281 | + <li> |
| 282 | + <p>Otherwise, run these steps <a>in parallel</a>: |
| 283 | + |
| 284 | + <ol> |
| 285 | + <li> |
| 286 | + <p>Let <var>permission</var> be the result of <a>requesting permission to use</a> |
| 287 | + {{"persistent-storage"}}. |
| 288 | + |
| 289 | + <p class="note">User agents are encouraged to not let the user answer this question twice for |
| 290 | + the same <a for=/>origin</a> around the same time and this algorithm is not equipped to handle |
| 291 | + such a scenario. |
| 292 | + |
| 293 | + <li> |
| 294 | + <p>Let <var>persisted</var> be true, if <var>origin</var>'s <a>site storage unit</a>'s |
| 295 | + <a>bucket</a> is a <a>persistent bucket</a>, and false otherwise. |
| 296 | + |
| 297 | + <p class=note>It will be false when there's an internal error. |
| 298 | + |
| 299 | + <li> |
| 300 | + <p>If <var>persisted</var> is false and <var>permission</var> is {{"granted"}}, then: |
| 301 | + |
| 302 | + <ol> |
| 303 | + <li><p>Set <var>origin</var>'s <a>site storage unit</a>'s <a>bucket</a>'s <a>mode</a> to |
| 304 | + "<code>persistent</code>". |
| 305 | + |
| 306 | + <li><p>If there was no internal error, then set <var>persisted</var> to true. |
| 307 | + </ol> |
| 308 | + |
| 309 | + <li><p><a>Queue a task</a> to resolve <var>promise</var> with <var>persisted</var>. |
| 310 | + </ol> |
| 311 | + |
| 312 | + <li><p>Return <var>promise</var>. |
| 313 | +</ol> |
| 314 | + |
| 315 | +The <dfn method for=StorageManager><code>estimate()</code></dfn> method, when invoked, |
| 316 | +must run these steps: |
| 317 | + |
| 318 | +<ol> |
| 319 | + <li><p>Let <var>promise</var> be a new promise. |
| 320 | + |
| 321 | + <li><p>Let <var>origin</var> be <a>context object</a>'s <a>relevant settings object</a>'s |
| 322 | + <a for="environment settings object">origin</a>. |
| 323 | + |
| 324 | + <li><p>If <var>origin</var> is an <a>opaque origin</a>, then reject <var>promise</var> with a |
| 325 | + {{TypeError}}. |
| 326 | + |
| 327 | + <li> |
| 328 | + <p>Otherwise, run these steps <a>in parallel</a>: |
| 329 | + |
| 330 | + <ol> |
| 331 | + <li><p>Let <var>usage</var> be <a>site storage usage</a> for <var>origin</var>. |
| 332 | + |
| 333 | + <li><p>Let <var>quota</var> be <a>site storage quota</a> for <var>origin</var>. |
| 334 | + |
| 335 | + <li><p>Let <var>dictionary</var> be a new {{StorageEstimate}} dictionary whose {{usage}} member |
| 336 | + is <var>usage</var> and {{quota}} member is <var>quota</var>. |
| 337 | + |
| 338 | + <li> |
| 339 | + <p>If there was an internal error while obtaining <var>usage</var> and <var>quota</var>, then |
| 340 | + <a>queue a task</a> to reject <var>promise</var> with a {{TypeError}}. |
| 341 | + |
| 342 | + <p class=note>Internal errors are supposed to be extremely rare and indicate some kind of |
| 343 | + low-level platform or hardware fault. However, at the scale of the web with the diversity of |
| 344 | + implementation and platforms, the unexpected does occur. |
| 345 | + |
| 346 | + <li><p>Otherwise, <a>queue a task</a> to resolve <var>promise</var> with <var>dictionary</var>. |
| 347 | + </ol> |
| 348 | + |
| 349 | + <li><p>Return <var>promise</var>. |
| 350 | +</ol> |
| 351 | + |
| 352 | + |
| 353 | + |
| 354 | +<h2 class=no-num id="acks">Acknowledgments</h2> |
| 355 | + |
| 356 | +With that, many thanks to |
| 357 | +Adrian Bateman, |
| 358 | +Alex Russell, |
| 359 | +Aislinn Grigas, |
| 360 | +Ali Alabbas, |
| 361 | +Ben Kelly, |
| 362 | +Ben Turner, |
| 363 | +Dale Harvey, |
| 364 | +David Grogan, |
| 365 | +fantasai, |
| 366 | +Jake Archibald<!-- technically B.J. Archibald -->, |
| 367 | +Jeffrey Yasskin, |
| 368 | +Jinho Bang, |
| 369 | +Jonas Sicking, |
| 370 | +Joshua Bell, |
| 371 | +Kenji Baheux, |
| 372 | +Kinuko Yasuda, |
| 373 | +Luke Wagner, |
| 374 | +Michael Nordman, |
| 375 | +Mounir Lamouri, |
| 376 | +Shachar Zohar, |
| 377 | +黃強 (Shawn Huang), and |
| 378 | +簡冠庭 (Timothy Guan-tin Chien) |
| 379 | +for being awesome! |
| 380 | + |
| 381 | +This standard is written by |
| 382 | +<a lang=nl href=https://annevankesteren.nl/>Anne van Kesteren</a> |
| 383 | +(<a href=https://www.mozilla.org/>Mozilla</a>, |
| 384 | + |
0 commit comments