diff --git a/index.bs b/index.bs index 189a2bc..a319aca 100644 --- a/index.bs +++ b/index.bs @@ -11,6 +11,24 @@ Editor: Ayu Ishii, Google https://www.google.com/, ayui@google.com Former Editor: Victor Costan !Participate: GitHub WICG/storage-buckets (new issue, open issues) Abstract: The Storage Buckets API provides a way for sites to organize locally stored data into groupings called "storage buckets". This allows the user agent or sites to manage and delete buckets independently rather than applying the same treatment to all the data from a single origin. +Markup Shorthands: css no, markdown yes + + +
+spec: storage; urlPrefix: https://storage.spec.whatwg.org/
+    type: dfn
+        text: bottle map; url: bottle-map
+        text: bucket map; url: bucket-map
+        text: bucket mode; url: bucket-mode
+        text: obtain a local storage shelf; url: obtain-a-local-storage-shelf
+        text: storage bottle; url: storage-bottle
+        text: storage bucket; url: storage-bucket
+        text: storage key; url: storage-key
+        text: storage quota; url: storage-quota
+        text: storage shelf; url: storage-shelf
+spec: html; urlPrefix: https://html.spec.whatwg.org/multipage/
+    type: dfn
+        text: parse a duration string; url: common-microsyntaxes.html#parse-a-duration-string
 

The {{StorageBucketManager}} interface

@@ -27,7 +45,7 @@ WorkerNavigator includes NavigatorStorageBuckets; Each [=environment settings object=] has an associated {{StorageBucketManager}} object. The storageBuckets -getter steps are to return [=this=]'s {{StorageBucketManager}} object. +getter steps are to return [=this=]'s [=/relevant settings object=]'s {{StorageBucketManager}} object. [Exposed=(Window,Worker), @@ -52,10 +70,196 @@ dictionary StorageBucketOptions {

Creating a bucket

+ +
+ +The open(|name|, |options|) method steps are: + +1. Let |environment| be [=/this=]'s [=/relevant settings object=]. + +1. Let |shelf| be the result of running [=obtain a local storage shelf=] given |environment|. + +1. If |shelf| is failure, then return [=a promise rejected with=] a {{TypeError}}. + +1. If the result of [=validate a bucket name=] with |name| is failure, then return [=a promise rejected with=] a {{TypeError}}. + +1. Let |r| be the result of running [=open a bucket=] with |shelf|, |name|, and |options|. + +1. If |r| is failure, then return [=a promise rejected with=] a {{TypeError}}. + +1. Return [=a promise resolved with=] with |r|. + +
+ +
+ +To open a bucket for a |shelf| given a bucket |name| and optional |options|, run the following steps: + +1. Let |expires| be undefined. + +1. If |options|["{{StorageBucketOptions/expires}}"] exists, then: + + 1. Let |expires| be |options|["{{StorageBucketOptions/expires}}"]. + + 1. If |expires| milliseconds after the [=Unix epoch=] is before the [=relevant settings object=]'s [=environment settings object/current wall time=], then return failure. + +1. Let |quota| be undefined. + +1. If |options|["{{StorageBucketOptions/quota}}"] exists, then: + + 1. Let |quota| be |options|["{{StorageBucketOptions/quota}}"]. + + 1. If |quota| is less than or equal to zero, then return failure. + +1. Let |persisted| be false. + +1. If |options|["{{StorageBucketOptions/persisted}}"] exists and is true, then: + + 1. Let |permission| be the result of [=/requesting permission to use=] `"persistent-storage"`. + + 1. If |permission| is "{{PermissionState/granted}}", then set |persisted| to true. + +1. Let |bucket| be the result of running [=get or expire a bucket=] with |shelf| and |name|. + +1. If |bucket| is null, then: + + 1. Let |bucket| be a new [=/storage bucket=] with name |name|. + + 1. Set |bucket|'s [=StorageBucket/durability value=] to |options|["{{StorageBucketOptions/durability}}"] if it exists. + + 1. Set |bucket|'s [=StorageBucket/quota value=] to |quota|. + + 1. Set |shelf|'s [=bucket map=][|name|] to |bucket|. + +1. If |persisted| is true, set |bucket|'s [=/bucket mode=] to `"persistent"`. + +1. Set |bucket|'s [=StorageBucket/expiration time|expiration=] to |expires| milliseconds after the [=Unix epoch=]. + +1. Let |storageBucket| be a new {{StorageBucket}}. + +1. Set |storageBucket|'s [=/storage bucket=] to |bucket|. + +1. Return |storageBucket|. + +
+ +
+ +To validate a bucket name given string |name|, run the following steps: + +1. If |name| contains any [=code point=] that is not [=ASCII lower alpha=], [=ASCII digit=], U+005F (_), or U+002D(-), then return failure. + +1. If |name| [=string/code point length=] is 0 or exceeds 64, then return failure. + +1. If |name| begins with U+005F (_) or U+002D(-), then return failure. + +1. Return. + +
+ +To get or expire a bucket on a |shelf| given string |name|, run the following steps: + +1. Let |bucket| be |shelf|'s [=bucket map=][|name|] if exists. Otherwise return null. + +1. If |bucket|'s [=StorageBucket/expiration time=] is non-null and before the [=relevant settings object=]'s [=environment settings object/current wall time=], then: + + 1. Set |bucket|'s [=storage bucket/removed=] to true. + + 1. Return null. + +1. Return |bucket|. + + + +When not null or undefined, {{StorageBucketOptions/durability}} is a hint to the user agent +specifying the desired {{StorageBucket/durability}}. The user agent MAY +create a new {{StorageBucket}} with this [=StorageBucket/durability value=]. The user agent +MUST NOT modify the [=StorageBucket/durability value=] of an existing bucket. +

Deleting a bucket

+ +
+ +The delete(|name|) method steps are: + +1. Let |environment| be [=/this=]'s [=/relevant settings object=]. + +1. Let |shelf| be the result of running [=obtain a local storage shelf=] given |environment|. + +1. If |shelf| is failure, then return [=a promise rejected with=] a {{TypeError}}. + +1. Let |p| be [=a new promise=]. + +1. Run the following steps [=in parallel=]: + + 1. If the result of [=validate a bucket name=] with |name| is failure, then [=/reject=] |p| with an {{InvalidCharacterError}} and abort these steps. + + 1. Let |r| be the result of running [=remove a bucket=] with |shelf| and |name|. + + 1. If |r| is failure, then [=/reject=] |p| with {{Type Error}}. + + 1. Otherwise, [=/resolve=] |p|. + +1. Return |p|. + +
+ +
+ +To remove a bucket on a |shelf| given a bucket |name|, run the following steps: + +1. Let |bucket| be |shelf|’s [=bucket map=][|name|] if exists. Otherwise return. + +1. Remove [=map/key=] |name| in |shelf|'s [=bucket map=]. + +1. Set |bucket|'s [=storage bucket/removed=] to true. + +1. Return. + + + +Issue: [[IndexedDB-3]] needs to define how deletion occurs when data is evicted by quota. + +Issue: [[FS]] needs to define how deletion occurs for Origin Private File System when data is evicted by quota. + +Issue: [[Service-Workers]] needs to define how deletion occurs for CacheStorage and Service Workers when data is evicted by quota. + +
+

Enumerating buckets

+
+ +The keys() method steps are: + +1. Let |environment| be [=/this=]'s [=/relevant settings object=]. + +1. Let |shelf| be the result of running [=obtain a local storage shelf=]. + +1. If |shelf| is failure, then return [=a promise rejected with=] a {{TypeError}}. + +1. Let |p| be [=a new promise=]. + +1. Let |keys| be a new [=/list=]. + +1. For each |key| in |shelf|'s [=bucket map=], run the following steps: + + 1. Let |bucket| be the result of running [=get or expire a bucket=] with |shelf| and |key|. + + 1. If |bucket| is non-null, [=list/append=] |key| to |keys|. + +1. [=Queue a task=] to [=/resolve=] |p| with |keys|. + +1. Return |p|. + +
+

The {{StorageBucket}} interface

+ [Exposed=(Window,Worker), SecureContext] @@ -78,4 +282,328 @@ interface StorageBucket { }; +A {{StorageBucket}} has an associated [=/storage bucket=]. + + +A [=/storage bucket=] has an associated removed flag, which is a boolean, initially false. Set as true when a [=/storage bucket=] is deleted. + + + +

Persistence

+ +Issue: Merge with [[Storage#buckets]] which already defines [=bucket mode=]. + +
+ +The persist() method steps are: + +1. Let |bucket| be [=this=]'s [=/storage bucket=]. + +1. Let |environment| be [=/this=]'s [=/relevant settings object=]. + +1. Let |p| be [=a new promise=]. + +1. Run the following steps [=in parallel=]: + + 1. If |bucket|'s [=storage bucket/removed=] flag is true, [=reject=] |p| with {{InvalidStateError}}. + + 1. If |bucket|'s [=bucket mode=] is `"persistent"`, [=/resolve=] |p| with true. + + 1. Otherwise, + + 1. Let |permission| be the result of [=getting the current permission state=] with `"persistent-storage"` and |environment|. + + 1. If |permission| is "{{PermissionState/granted}}", then set |bucket|'s [=bucket mode=] to `"persistent"` and [=queue a task=] to [=/resolve=] |p| with true. + + 1. Otherwise, [=/resolve=] |p| with false. + +1. Return |p|. + +
+ +
+ +The persisted() method steps are: + +1. Let |p| be [=a new promise=]. + +1. Let |bucket| be [=this=]'s [=/storage bucket=]. + +1. If |bucket|'s [=storage bucket/removed=] flag is true, then [=queue a task=] to [=reject=] |p| with an {{InvalidStateError}}. + +1. Otherwise, + + 1. If |bucket|'s [=bucket mode=] is `"persistent"`, then [=queue a task=] to [=/resolve=] |p| with true. + + 1. Otherwise, [=queue a task=] to [=/resolve=] |p| with false. + +1. Return |p|. + +
+ +

Quota

+ +A [=/storage bucket=] has a quota value, a number-or-null, initially null. +Specifies the upper limit of usage in bytes which can be used by the bucket. The user agent MAY further +limit the realized storage space. + +The storage usage of a [=/storage bucket=] is an [=implementation-defined=] rough estimate +of the number of bytes used by all of its [=/storage bottle=]s. + +
+ +The estimate() method steps are: + +1. Let |environment| be [=/this=]'s [=/relevant settings object=]. + +1. Let |shelf| be the result of running [=obtain a local storage shelf=] with |environment|. + +1. If |shelf| is failure, then return [=a promise rejected with=] a {{TypeError}}. + +1. Let |bucket| be [=this=]'s [=/storage bucket=]. + +1. If |bucket|'s [=storage bucket/removed=] flag is true, then return [=a promise rejected with=] an {{InvalidStateError}}. + +1. Let |p| be [=a new promise=]. + +1. Otherwise, run the following steps [=in parallel=]: + + 1. Let |quota| be [=/storage quota=] for |shelf|. + + 1. Set |quota| to |bucket|'s [=StorageBucket/quota value=] if it is non-null. + + 1. Let |usage| be [=storage bucket/storage usage=] for |bucket|. + + 1. Let |dictionary| be a new {{StorageEstimate}} dictionary whose {{StorageEstimate/usage}} member is |usage| and {{StorageEstimate/quota}} member is |quota|. + + 1. [=/Resolve=] |p| with |dictionary|. + +1. Return |p|. + +
+ + + +

Durability

+ +A [=/storage bucket=] has a durability value, a {{StorageBucketDurability}}. +The user agent MAY initialize this value to "{{StorageBucketDurability/strict}}" or +"{{StorageBucketDurability/relaxed}}". The user agent SHOULD ignore the [=StorageBucket/durability value=] +for operations that otherwise specify durability behavior. + +
+ +The durability() method steps are: + +1. Let |p| be [=a new promise=]. + +1. Let |bucket| be [=this=]'s [=/storage bucket=]. + +1. If |bucket|'s [=storage bucket/removed=] flag is true, [=queue a task=] to [=reject=] |p| with an {{InvalidStateError}}. + +1. Otherwise, [=queue a task=] to [=/resolve=] |p| with |bucket|'s [=StorageBucket/durability value=]. + +1. Return |p|. + +
+ + + +

Expiration

+ +A [=/storage bucket=] has an expiration time, which is either null or a [=moment=] on the [=wall clock=], initially null. +Specifies the upper limit of a bucket lifetime. + +The [=get or expire a bucket=] algorithm removes expired buckets when {{StorageBucketManager/keys()}} or {{StorageBucketManager/open()}} is called. +User agents MAY clear buckets whose [=/bucket mode=] is `"best-effort"` before their +[=StorageBucket/expiration time=] when faced with storage pressure. +User agents MAY remove any buckets before {{StorageBucketManager/open()}} or {{StorageBucketManager/keys()}} is called when the expiration is reached regardless of the [=/bucket mode=] + +
+ +The setExpires(|expires|) method steps are: + +1. Let |p| be [=a new promise=]. + +1. Let |bucket| be [=this=]'s [=/storage bucket=]. + +1. If |bucket|'s [=storage bucket/removed=] flag is true, [=queue a task=] to [=reject=] |p| with an {{InvalidStateError}}. + +1. Otherwise, set |bucket|'s [=StorageBucket/expiration time=] to |expires| milliseconds after the [=Unix epoch=] and [=queue a task=] to [=/resolve=] |p|. + +1. Return |p|. + +
+ +
+ +The expires() method steps are: + +1. Let |p| be [=a new promise=]. + +1. Let |bucket| be [=this=]'s [=/storage bucket=]. + +1. If |bucket|'s [=storage bucket/removed=] flag is true, [=queue a task=] to [=reject=] |p| with an {{InvalidStateError}}. + +1. Otherwise, [=queue a task=] to [=/resolve=] |p| with |bucket|'s [=StorageBucket/expiration time=]. + +1. Return |p|. + +
+ +

Using storage endpoints

+ +Storage endpoints, i.e. storage bottles, can be accessed as described below. + +

Using Indexed Database

+ +Issue: {{IDBFactory}} methods need to take a storage bottle map rather than a storageKey. + +
+ +A {{StorageBucket}} has an {{IDBFactory}} object, initially null. The indexedDB getter steps are: + +1. If [=this=]'s [=StorageBucket/indexedDB=] is null, run the following steps: + + 1. Let |bucket| be [=this=]'s [=/storage bucket=]. + + 1. Let |bottle map| be the result of [=obtain a local storage bottle map=] with |bucket| and `"indexedDB"`. + + 1. Let |indexedDB| be an {{IDBFactory}} object. + + 1. Set the [=bottle map=] for |indexedDB| to |bottle map|. + + 1. Set [=this=]'s [=StorageBucket/indexedDB=] to |indexedDB|. + +1. Return [=this=]'s [=StorageBucket/indexedDB=]. + +
+ +
+ +The user agent MUST consider the [=StorageBucket/durability value=] when evaluating the durability hint |durability| +of an {{IDBTransaction}} |transaction|. To calculate the effective durability hint for +|transaction| with associated |bucket|: + +1. If |durability| is not "{{IDBTransactionDurability/default}}", then return |durability|. + +1. If |bucket|'s [=StorageBucket/durability value=] is "{{StorageBucketDurability/strict}}", then return "{{IDBTransactionDurability/strict}}". + +1. Return "{{IDBTransactionDurability/relaxed}}". + +
+ +

Using CacheStorage

+ +
+ +A {{StorageBucket}} has a {{CacheStorage}} object, initially null. The caches getter steps are: + +1. If [=this=]'s [=StorageBucket/caches=] is null, run the following steps: + + 1. Let |bucket| be [=this=]'s [=/storage bucket=]. + + 1. Let |bottle map| be the result of [=obtain a local storage bottle map=] with |bucket| and `"cacheStorage"`. + + 1. Let |cacheStorage| be a {{CacheStorage}} object. + + 1. Set the relevant name to cache map for |cacheStorage| to |bottle map|. + + 1. Set [=this=]'s [=StorageBucket/caches=] to |caches|. + +1. Return [=this=]'s [=StorageBucket/caches=]. + +
+ +

Using an Origin Private File System

+ +Issue: [[Storage]] needs to define helpers to retrieve the bottle map for a given (non-default) bucket. + +Issue: [[FS]] needs to define a helper to retrieve an OPFS given a bottle map. + +
+ +The getDirectory() steps are: + +1. Let |map| be the result of [=obtain a local storage bottle map=] with [=this=]'s [=/storage bucket=] and `"fileSystem"`. + +1. Return the result of {{StorageManager/getDirectory}} with |map|. + +
+ + + +

Clear Site Data integration

+ +Issue: Update [[clear-site-data#header]]. + +: "`storage:bucket-name`" + + :: If the type string starts with "`storage:`" then the remaining characters after the + `:` will be taken to refer to a specific [=storage bucket=] in the [=origin=] of a + particular response's URL. + +Issue: add the steps below to the algorithm in [[clear-site-data#parsing]]. + +
+ +To parse a Clear-Site-Data header with buckets, execute the following steps: + +1. For each |type| in |header|, execute the following steps: + + 1. If |type| does not [=string/start with=] `"storage:"`, abort these steps. + + 1. Let |bucket name| be the [=code-unit-substring-by-positions|code unit substring=] from 8 to |end| of |type|. + + 1. If the result of [=validate a bucket name=] with |bucket name| is failure, then abort these steps. + + 1. Append a [=tuple=] consisting of (`"storage-bucket"`, |bucket name|) to |types|. + +
+ +Issue: add the steps below to the algorithm in [[clear-site-data#clear-response]]. + +
+ +To clear data with buckets, execute the following steps: + +1. Let |shelf| be the result of running [=obtain a local storage shelf=] given |environment|. + +1. If |shelf| is failure, then [=exception/throw=] a {{TypeError}} and abort these steps. + +1. For each |type| in |types|, execute the following steps: + + 1. If |type| is not a [=tuple=] or |type|[0] is not `"storage-bucket"`, abort these steps. + + 1. Let |bucket| be |shelf|'s [=bucket map=][|bucket name|] if one exists. Otherwise abort these steps + + 1. Remove |bucket|. + +
+

Security and privacy considerations