Skip to content

Commit

Permalink
Finally define storage infrastructure
Browse files Browse the repository at this point in the history
TODO

Closes #18.
  • Loading branch information
annevk committed May 2, 2020
1 parent 8e9ef53 commit 853158d
Showing 1 changed file with 244 additions and 50 deletions.
294 changes: 244 additions & 50 deletions storage.bs
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ function retrieveNextChunk(nextChunkInfo) {

<h2 id=infrastructure>Infrastructure</h2>

<p>A user agent has various kinds of semi-persistent state:
<p>A <a for=/>user agent</a> has various kinds of semi-persistent state:

<dl>
<dt>Credentials
Expand All @@ -94,37 +94,228 @@ function retrieveNextChunk(nextChunkInfo) {
<dd><p>Permissions for various features, such as geolocation
<dt>Network
<dd><p>HTTP cache, cookies, authentication entries, TLS client certificates
<dt>Storage
<dt id=site-storage>Storage
<dd>Indexed DB, Cache API, service worker registrations, <code>localStorage</code>,
<code>history.pushState()</code>, application caches, notifications, etc.
</dl>

<p>This standard primarily concerns itself with <dfn export id=site-storage>storage</dfn>.
<p>This standard primarily concerns itself with storage.

<p><a>Storage</a> consists of zero or more <dfn export id=site-storage-unit>storage units</dfn>.

<p>Each <a for=/>origin</a> has an associated <a>storage unit</a>. A <a>storage unit</a> contains a
single <dfn export id=bucket oldids=box>bucket</dfn>. [[HTML]]
<h3 id=storage-units>Storage units</h3>

<p>A <dfn>storage type</dfn> is "<code>storage</code>" or "<code>session-storage</code>".

<h3 id=buckets oldids=boxes>Buckets</h3>
<p>A <dfn>storage key</dfn> is an <a for=/>origin</a>. [[HTML]]

<p>A <a>bucket</a> has <dfn export for=bucket oldids=box-mode>mode</dfn> which is either
"<code title>best-effort</code>" or "<code title>persistent</code>". A
<dfn export oldids=persistent-box>persistent bucket</dfn> is a <a>bucket</a> whose
<a for=bucket>mode</a> is "<code title>persistent</code>". A
<dfn export oldids=non-persistent-box>non-persistent bucket</dfn> is a <a>bucket</a> whose
<a for=bucket>mode</a> is <em>not</em> "<code title>persistent</code>".
<p class=XXX>This is expected to change, see
<a href="https://privacycg.github.io/storage-partitioning/">Client-Side Storage Partitioning</a>.

<p>A <a>bucket</a> is considered to be an atomic unit. Whenever a <a>bucket</a> is cleared by the
user agent, it must be cleared in its entirety.
<p>A <dfn id=site-storage-unit>storage unit</dfn> is the least granular unit of storage, other than
the user agent. It holds a <dfn for="storage unit">map</dfn>, which is a <a for=/>map</a> of
<a for=/>ASCII strings</a> to <a>abstract storage buckets</a>.
<!-- Not sure if this needs to be exported, but it was already. -->

<p>A <dfn for=/>storage map</dfn> is a <a for=/>map</a> of <a>storage keys</a> to
<a>storage units</a>. It is initially empty.

<p>To <dfn>obtain a storage unit</dfn>, given a <a>storage map</a> <var>map</var>, an
<a>environment settings object</a> <var>environment</var>, and a <a>storage type</a>
<var>type</var>, run these steps:

<ol>
<li><p>Let <var>key</var> be <var>environment</var>'s
<a for="environment settings object">origin</a>.

<li><p>If <var>key</var> is an <a>opaque origin</a>, then return failure.

<li><p>If the user has disabled storage, then return failure.

<li>
<p>If <var>map</var>[<var>key</var>] does not <a for=map>exist</a>, then:

<ol>
<li><p>Let <var>unit</var> be a new <a>storage unit</a>.

<li>
<p>Set <var>unit</var>'s <a for="storage map">map</a>["<code>default</code>"] to the result of
<a>create a storage bucket</a> with <var>type</var>.

<p class="note">For now "<code>default</code>" is all that exists. See
<a href="https://github.com/whatwg/storage/issues/2">issue #2</a>.

<li><p>Set <var>map</var>[<var>key</var>] to <var>unit</var>.
</ol>

<li><p>Return <var>map</var>[<var>key</var>].
</ol>

<hr>

<p>A <a>user agent</a> holds a <dfn for="user agent">storage map</dfn>, which is a
<a for=/>storage map</a>.

<p>A browsing session holds a <dfn for="browsing session">storage map</dfn>, which is a
<a for=/>storage map</a>.

<p class=XXX>Browsing session is yet to be formally defined. For all intents and purposes it is a
<a>top-level browsing context</a>, except that it survives the <a>top-level browsing context</a>
being replaced due to a cross-origin opener policy.


<h3 id=storage-identifiers>Storage identifiers</h3>

<p>An <dfn>abstract storage identifier</dfn> is an <a for=/>ASCII string</a>.

<p>Each storage API has a <dfn>storage identifier</dfn>, which is an
<a>abstract storage identifier</a>. The <dfn>registered storage identifiers</dfm> are:

<ol>
<li>"<code>caches</code>"
<li>"<code>indexedDB</code>"
<li>"<code>localStorage</code>"
<li>"<code>serviceWorkerRegistrations</code>"
</ol>

<p>Each session storage API has a <dfn>session storage identifier</dfn>, which is an
<a>abstract storage identifier</a>. The <dfn>registered session storage identifiers</dfn> are
"<code>sessionStorage</code>".


<h3 id=buckets oldids=boxes>Storage buckets</h3>

<p>An <dfn>abstract storage bucket</dfn> is a place for storage and session storage APIs to store
data. Whenever an <a>abstract storage bucket</a> is cleared by the user agent, it must be cleared in
its entirety.
<!-- This is probably better defined in some kind of storage lifetime section. -->

<p>An <dfn for="abstract storage bucket,storage bucket,session storage bucket">area</dfn> is a part
of an <a>abstract storage bucket</a> carved out for a particular storage API or session storage API.
An <a for="abstract storage bucket">area</a> has a
<dfn for="abstract storage bucket/area,storage bucket/area,session storage bucket/area">map</dfn>,
which is an initially empty <a for=/>map</a>. An <a for="abstract storage bucket">area</a> also has
a
<dfn for="abstract storage bucket/area,storage bucket/area,session storage bucket/area">proxy map reference set</dfn>,
which is an initially empty <a for=/>set</a>.
<!-- The idea is that storage area's map holds the actual storage. It's in a map because those are
easy to work with. How the map is persisted is implementation-defined. How to make it
available across process boundaries is implementation-defined. This note also applies to
session buckets below. -->

<p>An <a>abstract storage bucket</a> has a
<dfn for="abstract storage bucket,storage bucket,session storage bucket">map</dfn> of
<a>abstract storage identifiers</a> to <a for="abstract storage bucket">areas</a>.

<hr>

<p>A <dfn id=bucket oldids=box>storage bucket</dfn> is an <a>abstract storage bucket</a> for storage
APIs.

<p>A <a>storage bucket</a> has a
<dfn for="storage bucket" id=bucket-mode oldids=box-mode>mode</dfn>, which is
"<code>best-effort</code>" or "<code>persistent</code>". It is initially "<code>best-effort</code>".

<hr>

<p>A <dfn>session storage bucket</dfn> is an <a>abstract storage bucket</a> for session storage
APIs.

<hr>

<p>To <dfn>create a storage bucket</dfn>, given a <a>storage type</a> <var>type</var>, run these
steps:

<ol>
<li><p>Let <var>bucket</var> be null.

<li>
<p>If <var>type</var> is "<code>storage</code>", then:

<ol>
<li><p>Set <var>bucket</var> to a new <a>storage bucket</a>.

<li><p>For each <var>identifier</var> of <a>registered storage identifiers</a>, set
<var>bucket</var>'s <a for="storage bucket">map</a>[<var>identifier</var>] to a new
<a for="storage bucket">area</a>.
</ol>

<li>
<p>Otherwise, if <var>type</var> is "<code>session-storage</code>":

<ol>
<li><p>Set <var>bucket</var> to a new <a>session storage bucket</a>.

<li><p>For each <var>identifier</var> of <a>registered session storage identifiers</a>, set
<var>bucket</var>'s <a for="session storage bucket">map</a>[<var>identifier</var>] to a new
<a for="session storage bucket">area</a>.
</ol>

<li><p>Assert: <var>bucket</var> is an <a>abstract storage bucket</a>.

<li><p>Return <var>bucket</var>.
</ol>


<h3 id=storage-proxy-maps>Storage proxy maps</h3>

<p>A <dfn>storage proxy map</dfn> is equivalent to a <a for=/>map</a>, except that all operations
are instead performed on its <dfn for="storage proxy map">backing map</dfn>.

<p class="note">This allows for the <a for="storage proxy map">backing map</a> to be replaced.

<hr>

<p>To <dfn>obtain a storage bucket area map</dfn>, given an <a for=/>environment settings object</a>
<var>environment</var>, <a>storage type</a> <var>type</var>, and <a>abstract storage identifier</a>
<var>identifier</var>, run these steps:</p>

<ol>
<li><p>Let <var>map</var> be null.

<li><p>If <var>type</var> is "<code>storage</code>", then set <var>map</var> to the user agent's
<a for="user agent">storage map</a>.

<li>
<p>Otherwise, if <var>type</var> is "<code>session-storage</code>", then set <var>map</var> to
<var>environment</var>'s <span class=XXX>browsing session</span>'s
<a for="browsing session">storage map</a>.

<p class="XXX">See
<a href="https://github.com/whatwg/html/issues/4782">whatwg/html issue #4782</a> and
<a href="https://github.com/whatwg/html/issues/5350">whatwg/html issue #5350</a> for defining
browsing session. It is roughly analogous to <a>top-level browsing context</a> except that it
cannot be replaced due to <code>Cross-Origin-Opener-Policy</code> or navigation.

<li><p>Assert: <var>map</var> is a <a for=/>storage map</a>.

<li><p>Let <var>unit</var> be the result of running <a>obtain a storage unit</a>, with
<var>map</var>, <var>environment</var>, and <var>type</var>.

<li><p>If <var>unit</var> is failure, then return failure.

<li><p>Let <var>bucket</var> be <var>unit</var>'s
<a for="storage unit">map</a>["<code>default</code>"].

<li><p>Let <var>area</var> be <var>bucket</var>'s
<a for="storage bucket">map</a>[<var>identifier</var>].

<li><p>Let <var>proxyMap</var> be a new <a>storage proxy map</a> whose
<a for="storage proxy map">backing map</a> is <var>area</var>'s
<a for="storage bucket/area">map</a>.

<li><p>Append a reference to <var>proxyMap</var> to <var>area</var>'s
<a for="storage bucket/area">proxy map reference set</a>.

<li><p>Return <var>proxyMap</var>.
</ol>



<h2 id=persistence>Persistence permission</h2>

<p>A <a>bucket</a> can only be turned into a <a>persistent bucket</a> if the user (or user agent
on behalf of the user) has granted permission to use the {{"persistent-storage"}} feature.
<p>A <a>storage bucket</a> can only have its <a for="storage bucket">mode</a> change to
"<code>persistent</code>" if the user (or user agent on behalf of the user) has granted permission
to use the {{"persistent-storage"}} feature.

<p class="note">When granted to an <a for=/>origin</a>, the persistence permission can be used to
protect storage from the user agent's clearing policies. The user agent cannot clear storage marked
Expand All @@ -142,29 +333,29 @@ locally.

<dt><a>permission revocation algorithm</a></dt>
<dd algorithm="permission-revocation">If {{"persistent-storage"}}'s <a>permission state</a> is not
{{"granted"}}, then set the current <a for=/>origin</a>’s <a>storage unit</a>'s <a>bucket</a>'s
<a for=bucket>mode</a> to "<code>best-effort</code>".</dd>
{{"granted"}}, then set the current <a for=/>origin</a>’s <a>storage unit</a>'s
<a>storage bucket</a>'s <a for="storage bucket">mode</a> to "<code>best-effort</code>".</dd>
<!-- XXX -->
</dl>



<h2 id=usage-and-quota>Usage and quota</h2>

<p>The <dfn export>storage usage</dfn> of an <a for=/>origin</a> <var>origin</var> is a rough
estimate of the amount of bytes used in <var>origin</var>'s <a>storage unit</a>.
<p>The <dfn export>storage usage</dfn> of a <a>storage unit</a> is a rough estimate of the amount
of bytes used by it.

<p class=note>This cannot be an exact amount as user agents might, and are encouraged to, use
deduplication, compression, and other techniques that obscure exactly how much bytes an
<a for=/>origin</a> uses.
deduplication, compression, and other techniques that obscure exactly how much bytes a
<a>storage unit</a> uses.

<p>The <dfn export>storage quota</dfn> of an <a for=/>origin</a> <var>origin</var> is a conservative
estimate of the amount of bytes available to <var>origin</var>'s <a>storage unit</a>. This amount
should be less than the total available storage space on the device to give users some wiggle room.
<p>The <dfn export>storage quota</dfn> of a <a>storage unit</a> is a conservative estimate of the
amount of bytes available to it. This amount should be less than the total available storage space
on the device to give users some wiggle room.

<p class=note>User agents are strongly encouraged to provide "popular" <a for=/>origins</a> with a
larger <a>storage quota</a>. Factors such as navigation frequency, recency of visits, bookmarking,
and <a href="#persistence">permission</a> for {{"persistent-storage"}} can be used as indications of
"popularity".
<p class=note>User agents are strongly encouraged to consider navigation frequency, recency of
visits, bookmarking, and <a href="#persistence">permission</a> for {{"persistent-storage"}} when
evaluating quotas.



Expand Down Expand Up @@ -229,19 +420,20 @@ these steps:
<ol>
<li><p>Let <var>promise</var> be a new promise.

<li><p>Let <var>origin</var> be <a>context object</a>'s <a>relevant settings object</a>'s
<a for="environment settings object">origin</a>.
<li><p>Let <var>unit</var> be the result of running <a>obtain a storage unit</a> with the user
agent's <a for="user agent">storage map</a>, <a>this</a>'s <a>relevant settings object</a>, and
"<code>storage</code>".

<li><p>If <var>origin</var> is an <a>opaque origin</a>, then reject <var>promise</var> with a
{{TypeError}}.
<li><p>If <var>unit</var> is a failure, then reject <var>promise</var> with a {{TypeError}}.

<li>
<p>Otherwise, run these steps <a>in parallel</a>:

<ol>
<li>
<p>Let <var>persisted</var> be true if <var>origin</var>'s <a>storage unit</a>'s <a>bucket</a>
is a <a>persistent bucket</a>, and false otherwise.
<p>Let <var>persisted</var> be true if <var>unit</var>'s
<a for="storage unit">map</a>["<code>default</code>"]'s <a for="storage bucket">mode</a> is
"<code>persistent</code>"; otherwise false.

<p class=note>It will be false when there's an internal error.

Expand All @@ -257,11 +449,11 @@ these steps:
<ol>
<li><p>Let <var>promise</var> be a new promise.

<li><p>Let <var>origin</var> be <a>context object</a>'s <a>relevant settings object</a>'s
<a for="environment settings object">origin</a>.
<li><p>Let <var>unit</var> be the result of running <a>obtain a storage unit</a> with the user
agent's <a for="user agent">storage map</a>, <a>this</a>'s <a>relevant settings object</a>, and
"<code>storage</code>".

<li><p>If <var>origin</var> is an <a>opaque origin</a>, then reject <var>promise</var> with a
{{TypeError}}.
<li><p>If <var>unit</var> is a failure, then reject <var>promise</var> with a {{TypeError}}.

<li>
<p>Otherwise, run these steps <a>in parallel</a>:
Expand All @@ -275,18 +467,20 @@ these steps:
the same <a for=/>origin</a> around the same time and this algorithm is not equipped to handle
such a scenario.

<li><p>Let <var>bucket</var> be <var>unit</var>'s
<a for="storage unit">map</a>["<code>default</code>"].

<li>
<p>Let <var>persisted</var> be true, if <var>origin</var>'s <a>storage unit</a>'s <a>bucket</a>
is a <a>persistent bucket</a>, and false otherwise.
<p>Let <var>persisted</var> be true if <var>bucket</var>'s <a for="storage bucket">mode</a> is
"<code>persistent</code>"; otherwise false.

<p class=note>It will be false when there's an internal error.

<li>
<p>If <var>persisted</var> is false and <var>permission</var> is {{"granted"}}, then:

<ol>
<li><p>Set <var>origin</var>'s <a>storage unit</a>'s <a>bucket</a>'s <a>mode</a> to
"<code>persistent</code>".
<li><p>Set <var>bucket</var>'s <a for="storage bucket">mode</a> to "<code>persistent</code>".

<li><p>If there was no internal error, then set <var>persisted</var> to true.
</ol>
Expand All @@ -303,19 +497,19 @@ these steps:
<ol>
<li><p>Let <var>promise</var> be a new promise.

<li><p>Let <var>origin</var> be <a>context object</a>'s <a>relevant settings object</a>'s
<a for="environment settings object">origin</a>.
<li><p>Let <var>unit</var> be the result of running <a>obtain a storage unit</a> with the user
agent's <a for="user agent">storage map</a>, <a>this</a>'s <a>relevant settings object</a>, and
"<code>storage</code>".

<li><p>If <var>origin</var> is an <a>opaque origin</a>, then reject <var>promise</var> with a
{{TypeError}}.
<li><p>If <var>unit</var> is a failure, then reject <var>promise</var> with a {{TypeError}}.

<li>
<p>Otherwise, run these steps <a>in parallel</a>:

<ol>
<li><p>Let <var>usage</var> be <a>storage usage</a> for <var>origin</var>.
<li><p>Let <var>usage</var> be <a>storage usage</a> for <var>unit</var>.

<li><p>Let <var>quota</var> be <a>storage quota</a> for <var>origin</var>.
<li><p>Let <var>quota</var> be <a>storage quota</a> for <var>unit</var>.

<li><p>Let <var>dictionary</var> be a new {{StorageEstimate}} dictionary whose {{usage}} member
is <var>usage</var> and {{quota}} member is <var>quota</var>.
Expand Down

0 comments on commit 853158d

Please sign in to comment.