Shared Storage API

Draft Community Group Report,

This version:
https://github.com/WICG/shared-storage
Issue Tracking:
GitHub
Inline In Spec
Editor:
(Google)

Abstract

Shared Storage is a storage API that is intentionally not partitioned by top-frame site (though still partitioned by context origin of course!). To limit cross-site reidentification of users, data in Shared Storage may only be read in a restricted environment that has carefully constructed output gates.

Status of this document

This specification was published by the Web Platform Incubator Community Group. It is not a W3C Standard nor is it on the W3C Standards Track. Please note that under the W3C Community Contributor License Agreement (CLA) there is a limited opt-out and other conditions apply. Learn more about W3C Community and Business Groups.

1. Introduction

This section is not normative.

In order to prevent cross-site user tracking, browsers are partitioning all forms of storage by top-frame site; see Client-Side Storage Partitioning. But, there are many legitimate use cases currently relying on unpartitioned storage.

This document introduces a new storage API that is intentionally not partitioned by top-frame site (though still partitioned by context origin), in order to serve a number of the use cases needing unpartitioned storage. To limit cross-site reidentification of users, data in Shared Storage may only be read in a restricted environment, called a worklet, and any output from the worklet is in the form of a fenced frame or a private aggregation report. Over time, there may be additional ouput gates included in the standard.

a.example randomly assigns users to groups in a way that is consistent cross-site.

Inside an a.example iframe:

function generateSeed() {}
await window.sharedStorage.worklet.addModule('experiment.js');

// Only write a cross-site seed to a.example’s storage if there isn’t one yet.
window.sharedStorage.set('seed', generateSeed(), { ignoreIfPresent: true });

let fencedFrameConfig = await window.sharedStorage.selectURL(
  'select-url-for-experiment',
  [
    {url: "blob:https://a.example/123…", reportingMetadata: {"click": "https://report.example/1..."}},
    {url: "blob:https://b.example/abc…", reportingMetadata: {"click": "https://report.example/a..."}},
    {url: "blob:https://c.example/789…"}
  ],
  { data: { name: 'experimentA' } }
);

// Assumes that the fenced frame 'my-fenced-frame' has already been attached.
document.getElementById('my-fenced-frame').config = fencedFrameConfig;

inside the experiment.js worklet script:

class SelectURLOperation {
  hash(experimentName, seed) {}

  async run(urls, data) {
    const seed = await this.sharedStorage.get('seed');
    return hash(data.name, seed) % urls.length;
  }
}
register('select-url-for-experiment', SelectURLOperation);

2. The SharedStorageWorklet Interface

The SharedStorageWorklet object allows developers to supply module scripts to process Shared Storage data and then output the result through one or more of the output gates. Currently there are two output gates, the private aggregation output gate and the URL-selection output gate.
[Exposed=(Window)]
interface SharedStorageWorklet : Worklet {
};

Each SharedStorageWorklet has an associated boolean addModule initiated, initialized to false.

Because adding multiple module scripts via addModule() for the same SharedStorageWorklet would give the caller the ability to store data from Shared Storage in global variables defined in the module scripts and then exfiltrate the data through later call(s) to addModule(), each SharedStorageWorklet can only call addModule() once. The addModule initiated boolean makes it possible to enforce this restriction.

When addModule() is called for a worklet, it will run check if addModule is allowed and update status, and if the result is false, abort the remaining steps in the addModule() call, as detailed in the § 2.1 Monkey Patch for Worklets.

To check if user preference setting allows access to shared storage from an environment settings object environment, run the following step:
  1. Using values available in environment as needed, perform an implementation-defined algorithm to return either true or false.

To determine whether shared storage is allowed, given an environment settings object environment, run these steps:
  1. If environment is not a secure context, then return false.

  2. Let origin be environment’s origin.

  3. If origin is an opaque origin, then return false.

  4. Let globalObject be the current realm's global object.

  5. Assert that globalObject is a Window or a SharedStorageWorkletGlobalScope.

  6. If globalObject is a Window and globalObject’s associated document is not allowed to use the "shared-storage" feature, return false.

  7. If the result of running obtain a site with origin is not enrolled, then return false.

  8. If the result of running check if user preference setting allows access to shared storage from environment is false, then return false.

  9. Return true.

To check if addModule is allowed and update status for a SharedStorageWorklet worklet, run the following steps:
  1. If the result of running determine whether shared storage is allowed on the relevant settings object of this is false, return false.

  2. If worklet’s addModule initiated is true, return false.

  3. Set worklet’s addModule initiated to true.

  4. Return true.

Moreover, each SharedStorageWorklet's list of global scopes, initially empty, can contain at most one instance of its worklet global scope type, the SharedStorageWorkletGlobalScope.

2.1. Monkey Patch for Worklets

This specification will make some modifications to the Worklet standard to accommodate the needs of Shared Storage.

In particular, the addModule() method steps for Worklet will need to be prepended with the following step:

  1. If Worklet has an associated boolean addModule initiated, and the result of running check if addModule is allowed and update status on Worklet is false, return a promise rejected with a TypeError.

And the penultimate step (i.e. the final indented step), currently "If pendingTasks is 0, then resolve promise.", should be updated to:

  1. If pendingTasks is 0, perform the following steps:

    1. If workletGlobalScope has an associated boolean addModule success, set workletGlobalScope’s addModule success to true.

    2. Resolve promise.

Add additional monkey patch pieces for out-of-process worklets.

2.2. The SharedStorageWorkletGlobalScope

The SharedStorageWorklet's worklet global scope type is SharedStorageWorkletGlobalScope.

[Exposed=SharedStorageWorklet, Global=SharedStorageWorklet]
interface SharedStorageWorkletGlobalScope : WorkletGlobalScope {
  undefined register(DOMString name,
                     SharedStorageOperationConstructor operationCtor);

  readonly attribute WorkletSharedStorage sharedStorage;
};

callback SharedStorageOperationConstructor =
  SharedStorageOperation(optional SharedStorageRunOperationMethodOptions options);

[Exposed=SharedStorageWorklet]
interface SharedStorageOperation {
};

dictionary SharedStorageRunOperationMethodOptions {
  object data;
  boolean resolveToConfig = false;
  boolean keepAlive = false;
};

Each SharedStorageWorkletGlobalScope has an associated environment settings object outside settings, which is the associated SharedStorageWorklet's relevant settings object.

Each SharedStorageWorkletGlobalScope has an associated boolean addModule success, which is initialized to false.

The SharedStorageWorkletGlobalScope's module map's module scripts should each define and register one or more SharedStorageOperations.

Each SharedStorageWorkletGlobalScope also has an associated operation map, which is a map, initially empty, of strings (denoting operation names) to functions.

Currently each SharedStorageOperation registered via register() must be one of the following two types:

The SharedStorageRunOperation is designed to work with output gates that do not need a return value, like the private aggregation service. A SharedStorageRunOperation performs an async operation and returns a promise that resolves to undefined.

A SharedStorageSelectURLOperation is an SharedStorageOperation that takes in a list of SharedStorageUrlWithMetadatas (i.e. dictionaries containing strings representing URLs each wrapped with optional metadata), performs an async operation, and then returns a promise to a long integer index specifying which of these URLs should be selected.

[Exposed=SharedStorageWorklet]
interface SharedStorageRunOperation : SharedStorageOperation {
  Promise<undefined> run(object data);
};

[Exposed=SharedStorageWorklet]
interface SharedStorageSelectURLOperation : SharedStorageOperation {
  Promise<long> run(object data,
                    FrozenArray<SharedStorageUrlWithMetadata> urls);
};

Each SharedStorageWorkletGlobalScope also has an associated WorkletSharedStorage instance, with the sharedStorage getter algorithm as described below.

2.3. SharedStorageWorkletGlobalScope algorithms

The register(name, operationCtor) method steps are:
  1. If name is missing or empty, throw a TypeError.

  2. Let operationMap be this SharedStorageWorkletGlobalScope's operation map.

  3. If operationMap contains an entry with key name, throw a TypeError.

  4. If operationCtor is missing, throw a TypeError.

  5. If the result of running IsConstructor() with operationCtor is false, throw a TypeError.

  6. Let prototype be the result of running operationCtor’s [[GetPrototypeOf]]() method.

  7. If prototype is not an object, throw a TypeError.

  8. Let run be the result of running GetMethod() with prototype and "run".

  9. If run is undefined, throw a TypeError.

  10. Set the value of operationMap[name] to run.

The sharedStorage getter steps are:
  1. If this’s addModule success is true, return this’s sharedStorage.

  2. Otherwise, throw a TypeError.

To check whether addModule is finished, the step is:
  1. Return the value of addModule success.

3. Shared Storage’s Backend

The Shared Storage API will integrate into the Storage API as below, via registering a new storage endpoint.

3.1. Monkey Patch for the Storage Model

This standard will add a new storage type "shared" to the Storage Model.

A user agent holds a shared storage shed for storage endpoints of type "shared".

This standard will also register a storage endpoint of type "shared" with storage identifier "sharedStorage" and quota 54 * 216 bytes (i.e. 39.0625 mebibytes).

This quota is calculated from the current implementation. Consider bringing the current implementation in line with the spec for storage endpoints "localStorage" and "sessionStorage", i.e. 5 * 220 bytes. For example, decreasing the per-origin entry limit from 10,000 to 1,280 would achieve this.

A shared storage shed is a map of origins to storage shelves. It is initially empty.

Note: Unlike storage sheds, whose keys are storage keys, shared storage sheds use origins as keys directly. Shared storage will be intentionally excluded from client-side storage partitioning.

For each storage shelf in a shared storage shed, the storage shelf's bucket map currently has only a single key of "default".

A user agent's shared storage shed holds all shared storage data.

To obtain a shared storage shelf, given a shared storage shed shed and an environment settings object environment, run these steps:
  1. If the result of running determine whether shared storage is allowed on environment is false, then return failure.

  2. Let origin be environment’s origin.

  3. If shed[origin] does not exist, then set shed[origin] to the result of running create a shared storage shelf with type "shared".

  4. Return shed[origin].

To create a shared storage shelf, run these steps:
  1. Let shelf be a new storage shelf.

  2. Set shelf’s bucket map["default"] to the result of running create a shared storage bucket.

  3. Return shelf.

A shared storage bucket is a storage bucket in one of a shared storage shed's shelves.

To create a shared storage bucket, run these steps:
  1. Let endpoint be the storage endpoint with storage identifier "sharedStorage".

  2. Let bucket be a new shared storage bucket.

  3. Set bucket’s bottle map["sharedStorage"] to a new storage bottle whose quota is endpoint’s quota.

  4. Return bucket.

Note: Currently, a shared storage bucket's bottle map has size 1, since there is only one storage endpoint registered with type "shared".

To obtain a shared storage bottle map, given an environment settings object environment, run these steps:
  1. Let shed be the user agent's shared storage shed.

  2. Let shelf be the result of running obtain a shared storage shelf with shed and environment.

  3. If shelf is failure, then return failure.

  4. Let bucket be shelf’s bucket map["default"].

  5. Let bottle be bucket’s bottle map["sharedStorage"].

  6. Let proxyMap be a new storage proxy map whose backing map is bottle’s map.

  7. Append proxyMap to bottle’s proxy map reference set.

  8. Return proxyMap.

3.2. The Shared Storage Database

A browsing context has an associated shared storage database, which provides methods to store, retrieve, delete, clear, and purge expired data, and additional methods as below. The data in the database take the form of entries.

Each shared storage database has a shared storage database queue, which is the result of starting a new parallel queue. This queue is used to run each of the shared storage database's methods when calls to them are initiated from that browsing context.

Each entry consists of a key and a value struct.

An entry's key is a string.

User agents may specify the maximum length of a key.

Since keys are used to organize and efficiently retrieve entries, keys must appear at most once in any given shared storage database.

An entry's value struct is a struct composed of string value and DOMHighResTimeStamp last updated (from the Unix Epoch).

User agents may specify the maximum length of a value.

User agents may specify a default entry lifetime, the default duration between when an entry is stored and when it expires. If the user agent specifies a default entry lifetime, then it should have a timer periodically purge expired entries from the database.

3.3. The Database Algorithms

To store an entry in the database, given a shared storage database queue queue, a storage proxy map databaseMap, an environment settings object environment, a key key, and a value value, run the following steps on queue:
  1. Let valueStruct be a new value struct.

  2. Set valueStruct’s value to value.

  3. Let currentTime be the environment’s current wall time.

  4. Set valueStruct’s last updated to currentTime.

  5. Set databaseMap[key] to valueStruct.

  6. If this throws an exception, catch it and return false.

    Note: Errors with storage proxy map databaseMap’s methods are possible depending on its implementation.

  7. Otherwise, return true.

To retrieve an entry from the database, given a shared storage database queue queue, a storage proxy map databaseMap, an environment settings object environment, and a key key, run the following steps on queue:
  1. If databaseMap does not contain key, return undefined.

  2. Let valueStruct be the result of running Get on databaseMap with key.

  3. If this throws an exception, catch it and return failure.

    Note: Errors with storage proxy map databaseMap’s methods are possible depending on its implementation.

  4. If the result of running determine whether an entry is expired with environment and valueStruct is true, return undefined.

  5. Return valueStruct’s value.

To delete an entry from the database, given a shared storage database queue queue, a storage proxy map databaseMap, and a key key, run the following steps on queue:
  1. Remove databaseMap[key].

  2. If this throws an exception, catch it and return false.

    Note: Errors with storage proxy map databaseMap’s methods are possible depending on its implementation.

  3. Return true.

To clear all entries in the database, given a shared storage database queue queue and a storage proxy map databaseMap, run the following steps on queue:
  1. Run Clear on databaseMap.

  2. If this throws an exception, catch it and return false.

    Note: Errors with storage proxy map databaseMap’s methods are possible depending on its implementation.

  3. Return true.

To retrieve all entries from the database, given a shared storage database queue queue and a storage proxy map databaseMap, run the following steps on queue:
  1. Let values be the result of running getting the values on databaseMap.

  2. If this throws an exception, catch it and return failure.

    Note: Errors with storage proxy map databaseMap’s methods are possible depending on its implementation.

  3. Return values.

To count entries in the database, given a shared storage database queue queue and a storage proxy map databaseMap, run the following steps on queue:
  1. Let size be databaseMap’s size.

  2. If this throws an exception, catch it and return failure.

    Note: Errors with storage proxy map databaseMap’s members are possible depending on its implementation.

  3. Return size.

To purge expired entries from the database, given a shared storage database queue queue, a storage proxy map databaseMap, and an environment settings object environment, run the following steps on queue:
  1. For each key key in databaseMap:

    1. Let valueStruct be the result of running Get on databaseMap with key.

    2. If this throws an exception, catch it and return false.

    3. If the result of running determine whether an entry is expired with environment and valueStruct is true, Remove databaseMap[key].

    4. If this throws an exception, catch it and return false.

  2. Return true.

To determine whether an entry is expired, given an environment settings object environment and a value struct valueStruct, run the following steps:

  1. Let lastUpdated be valueStruct’s last updated.

  2. Let lifetime be user agent's default entry lifetime.

  3. Let expiration be the sum of lastUpdated and lifetime.

  4. Let currentTime be the environment’s current wall time.

  5. If expiration is less than or equal to currentTime, return true.

  6. Otherwise, return false.

4. The SharedStorage Interface

The SharedStorage interface is the base for derived interfaces WindowSharedStorage and WorkletSharedStorage, which are exposed to the Window and the SharedStorageWorklet, respectively.

Methods that allow the setting and/or deleting of data are exposed to both the Window and the SharedStorageWorklet and hence are declared in the base SharedStorage interface, although their implementations may vary depending on their environment. This makes it possible to modify the data in Shared Storage from multiple contexts.

Meanwhile, methods for running SharedStorageOperations, along with the worklet attribute which is used to call addModule(), are declared in WindowSharedStorage and exposed to the Window only, as these are the means by which the Window interacts with the SharedStorageWorklet.

On the other hand, methods for getting data from the shared storage database are declared in WorkletSharedStorage and exposed to the SharedStorageWorklet only, in order to carefully control the flow of data read from the database.

[Exposed=(Window,SharedStorageWorklet)]
interface SharedStorage {
  Promise<any> set(DOMString key,
                   DOMString value,
                   optional SharedStorageSetMethodOptions options = {});
  Promise<any> append(DOMString key,
                      DOMString value);
  Promise<any> delete(DOMString key);
  Promise<any> clear();
};

dictionary SharedStorageSetMethodOptions {
  boolean ignoreIfPresent = false;
};

4.1. The WindowSharedStorage interface

The WindowSharedStorage interface is as follows.

typedef (USVString or FencedFrameConfig) SharedStorageResponse;

[Exposed=(Window)]
interface WindowSharedStorage : SharedStorage {
  Promise<any> run(DOMString name,
                   optional SharedStorageRunOperationMethodOptions options = {});
  Promise<SharedStorageResponse> selectURL(DOMString name,
                               FrozenArray<SharedStorageUrlWithMetadata> urls,
                               optional SharedStorageRunOperationMethodOptions options = {});

  readonly attribute SharedStorageWorklet worklet;
};

4.1.1. Window Setter/Deleter Methods

The set(key, value, options) method step is:
  1. Return the result of running set a key-value pair on WindowSharedStorage, key, value, and options.

The append(key, value) method step is:
  1. Return the result of running append a key-value pair on WindowSharedStorage, key, and value.

The delete(key) method step is:
  1. Return the result of running delete a key on WindowSharedStorage and key.

The clear() method step is:
  1. Return the result of running clear all keys on WindowSharedStorage.

To set a key-value pair, given WindowSharedStorage sharedStorage, string key, string value, and SharedStorageSetMethodOptions options, perform the following steps:
  1. Let promise be a new promise.

  2. If key’s length exceeds the maximum length, return a promise rejected with a TypeError.

  3. If value’s length exceeds the maximum length, return a promise rejected with a TypeError.

  4. Let context be sharedStorage’s Window's browsing context.

  5. If context is null, return a promise rejected with a TypeError.

  6. Let environment be context’s active window's relevant settings object.

  7. Let databaseMap be the result of running obtain a shared storage bottle map for environment.

  8. If databaseMap is failure, then return a promise rejected with a TypeError.

  9. Let queue be context’s associated database's shared storage database queue.

  10. Let realm be the current realm.

  11. Enqueue the following steps on queue:

    1. If options["ignoreIfPresent"] is true and the result of running retrieve an entry from the database with queue, databaseMap, environment, and key is not undefined:

      1. Queue a global task on the DOM manipulation task source, given realm’s global object, to resolve promise with undefined.

      2. Abort these steps.

    2. Run store an entry in the database with queue, databaseMap, environment, key, and value.

    3. Queue a global task on the DOM manipulation task source, given realm’s global object, to resolve promise with undefined.

  12. Return promise.

To append a key-value pair, given WindowSharedStorage sharedStorage, string key, and string value, perform the following steps:
  1. Let promise be a new promise.

  2. If key’s length exceeds the maximum length, return a promise rejected with a TypeError.

  3. If value’s length exceeds the maximum length, return a promise rejected with a TypeError.

  4. Let context be sharedStorage’s Window's browsing context.

  5. If context is null, return a promise rejected with a TypeError.

  6. Let environment be context’s active window's relevant settings object.

  7. Let databaseMap be the result of running obtain a shared storage bottle map for environment.

  8. If databaseMap is failure, then return a promise rejected with a TypeError.

  9. Let queue be context’s associated database's shared storage database queue.

  10. Let realm be the current realm.

  11. Enqueue the following steps on queue:

    1. Let currentValue be the result of running retrieve an entry from the database with queue, databaseMap, environment, and key.

    2. If currentValue is failure:

      1. Queue a global task on the DOM manipulation task source, given realm’s global object, to resolve promise with undefined.

      2. Abort these steps.

    3. If currentValue is not undefined:

      1. Let list be a new list.

      2. Append currentValue to list.

      3. Append value to list.

      4. Set value to the result of running concatenate on list.

    4. Run store an entry in the database with queue, databaseMap, environment, key, and value.

    5. Queue a global task on the DOM manipulation task source, given realm’s global object, to resolve promise with undefined.

  12. Return promise.

To delete a key, given WindowSharedStorage sharedStorage and string key, perform the following steps:
  1. Let promise be a new promise.

  2. If key’s length exceeds the maximum length, return a promise rejected with a TypeError.

  3. Let context be sharedStorage’s Window's browsing context.

  4. If context is null, return a promise rejected with a TypeError.

  5. Let environment be context’s active window's relevant settings object.

  6. Let databaseMap be the result of running obtain a shared storage bottle map for environment.

  7. If databaseMap is failure, then return a promise rejected with a TypeError.

  8. Let queue be context’s associated database's shared storage database queue.

  9. Let realm be the current realm.

  10. Enqueue the following steps on queue:

    1. Run delete an entry from the database with queue, environment, and key.

    2. Queue a global task on the DOM manipulation task source, given realm’s global object, to resolve promise with undefined.

  11. Return promise.

To clear all keys, given WindowSharedStorage sharedStorage, perform the following steps:
  1. Let promise be a new promise.

  2. Let context be sharedStorage’s Window's browsing context.

  3. If context is null, return a promise rejected with a TypeError.

  4. Let environment be context’s active window's relevant settings object.

  5. Let databaseMap be the result of running obtain a shared storage bottle map for environment.

  6. If databaseMap is failure, then return a promise rejected with a TypeError.

  7. Let queue be context’s associated database's shared storage database queue.

  8. Let realm be the current realm.

  9. Enqueue the following steps on queue:

    1. Run clear all entries in the database with queue and environment.

    2. Queue a global task on the DOM manipulation task source, given realm’s global object, to resolve promise with undefined.

  10. Return promise.

4.1.2. SharedStorageUrlWithMetadata and Reporting

A SharedStorageUrlWithMetadata object is a dictionary containing a string representing a URL and, optionally, a reportingMetadata object.

dictionary SharedStorageUrlWithMetadata {
  required USVString url;
  object reportingMetadata;
};

If a SharedStorageUrlWithMetadata object contains a non-empty reportingMetadata object in the form of a dictionary whose keys are eventTypes and whose values are strings that parse to valid URLs, then these eventType-URL pairs will be registered for later access within any fenced frame that loads the SharedStorageResponse resulting from this selectURL() call.

Inside a fenced frame with eventType-URL pairs that have been registered through selectURL() with reportingMetadata objects, if fence.reportEvent() is called on a FenceEvent with a destination containing "shared-storage-select-url" and that FenceEvent's corresponding eventType is triggered, then the FenceEvent's eventData will be sent as a beacon to the registered URL for that eventType.

To validate reporting metadata, given an object reportingMetadata, run the following steps:
  1. If reportingMetadata is empty, return true.

  2. If reportingMetadata is not a dictionary, return false.

  3. For each eventType -> urlString of reportingMetadata, if the result of running get the canonical URL string if valid with urlString is undefined, return false.

  4. Return true.

To get the canonical URL string if valid, given a string urlString, run the following steps:
  1. Let url be the result of running a URL parser on urlString.

  2. If url is not a valid URL, return undefined.

  3. Otherwise, return the result of running a URL serializer on url.

To register reporting metadata, given an object reportingMetadata and a "fenced frame config struct" fencedFrameConfigStruct, run the following steps:
  1. If reportingMetadata is empty, return.

  2. Assert that reportingMetadata is a dictionary.

  3. Let reportingUrlMap be an empty map.

  4. For each eventType -> urlString of reportingMetadata:

    1. Let url be the result of running a URL parser on urlString.

    2. Assert that url is a valid URL.

    3. Set reportingUrlMap[eventType] to url.

  5. Store reportingUrlMap inside a "fenced frame reporter" class associated with fencedFrameConfigStruct. Both of these still need to be added to the draft Fenced Frame specification.

4.1.3. Entropy Budgets

Because bits of entropy can leak via selectURL(), the user agent will need to maintain budgets to limit these leaks.

If a user activates a fenced frame whose FencedFrameConfig was generated by selectURL() and thereby initiates a top-frame navigation, this will reveal to the landing page that its URL was selected, which is a leak in entropy bits of up to logarithm base 2 of the number of input URLs for the call to selectURL(). To mitigate this, a user agent will set a per-calling site navigation entropy allowance.

A calling site for selectURL() is the site resulting from running obtain a site with the origin of an environment that makes a selectURL() call.

A navigation entropy allowance is a maximum allowance of entropy bits that are permitted to leak via fenced frames initiating top-frame navigations during a given navigation budget epoch for a given calling calling site. This allowance is defined by the user agent and is calling site-agnostic.

A user agent will define a fixed predetermined duration navigation budget lifetime.

An navigation budget epoch is any interval of time whose duration is the navigation budget lifetime.

To keep track of how this navigation entropy allowance is used, the user agent uses a shared storage navigation budget table, which is a map of calling sites to navigation entropy ledgers.

An navigation entropy ledger is a list of bit debits.

A bit debit is a struct containing a double bits, indicating a value in entropy bits, along with a DOMHighResTimeStamp timestamp (from the Unix Epoch).

Bit debits whose timestamps precede the start of the current navigation budget epoch are said to be expired.

When a leak occurs, its value in entropy bits is calculated and stored for that calling site, along with the current time as a timestamp, together as a bit debit in the shared storage navigation budget table.

A calling site's remaining navigation budget is the navigation entropy allowance minus any bit debits whose timestamps are within the current navigation budget epoch.

selectURL()'s argument "urls" is its input URL list.

When a calling site has insufficient remaining navigation budget, selectURL() will return a SharedStorageResponse (i.e. either a FencedFrameConfig or an opaque URL) for the url in the SharedStorageUrlWithMetadata at the default index in its input URL list.

The default index for a call to selectURL() is implementation-defined in such a way that it is independent from the result of the associated SharedStorageSelectURLOperation's "run" method.

The default index could be defined to be 0.

In this case, whenever the SharedStorageSelectURLOperation's "run" method encounters an error, or whenever there is insufficient remaining navigation budget, the "run" method would return 0, and hence selectURL() would return a SharedStorageResponse for the first url in its input URL list.

The default index could be defined to be input URL list's size minus 1.

In this case, whenever the SharedStorageSelectURLOperation's "run" method encounters an error, or whenever there is insufficient remaining navigation budget, selectURL() would return a SharedStorageResponse for the last url in its input URL list.

To determine remaining navigation budget, given an environment settings object environment and a calling site site, run the following steps:
  1. If site is an opaque origin, return undefined.

  2. Let maxBits be the user agent's navigation entropy allowance.

  3. If the user agent's shared storage navigation budget table does not contain site, then return maxBits.

  4. Otherwise, let ledger be user agent's shared storage navigation budget table[site].

  5. Let debitSum be 0.

  6. For each item bitDebit in ledger, do the following steps:

    1. Let debit be bitDebit’s bits.

    2. If the result of running check whether a bit debit is expired with environment and bitDebit is false, then increment debitSum by debit.

  7. Return maxBits minus debitSum.

To check whether a bit debit is expired, given an environment settings object environment and a bit debit bitDebit, run the following steps:
  1. Let epochLength be the user agent's navigation budget lifetime.

  2. Let currentTime be environment’s current wall time.

  3. Let threshold be currentTime minus epochLength.

  4. If bitDebit’s timestamp is less than threshold, return true.

  5. Otherwise, return false.

A bit debit will need to be charged to the shared storage navigation budget table for each top-frame navigation initiated by a fenced frame whose FencedFrameConfig was generated via selectURL(), as this can leak cross-site data. Since the bits to charge is calculated during the call to selectURL() but only actually recorded in the shared storage navigation budget table if and when the resulting fenced frame initiates a top-frame navigation, the bits must be stored as a pending shared storage budget debit in the corresponding FencedFrameConfig until this time.

Between beginning navigation and ending navigation, a user agent will perform the charge shared storage navigation budget algorithm.

The "fenced frame config struct" and its boolean has navigated have not yet been added to the draft Fenced Frame specification. Some form of them will be added, although their names are subject to bikeshedding. Fix the names when they are added.

To charge shared storage navigation budget during a navigation with navigable navigable and Document sourceDocument, run the following steps:
  1. If navigable is not a traversable navigable, return.

  2. Let node be sourceDocument’s node navigable.

  3. While node is not null:

    1. Let site be the result of running obtain a site with node’s active document's origin.

    2. If node has a "fenced frame config struct" and site is not an opaque origin, perform the following steps:

      1. Let pendingBits be node’s "fenced frame config struct"'s pending shared storage budget debit.

      2. If pendingBits is greater than 0 and if "fenced frame config struct"'s has navigated is false, run the following steps:

        1. Let ledger be user agent's shared storage navigation budget table[site].

        2. Let bitDebit be a new bit debit.

        3. Set bitDebit’s bits to pendingBits.

        4. Let currentTime be the current wall time.

        5. Set bitDebit’s timestamp to currentTime.

        6. Append bitDebit to ledger.

        7. Set node’s "fenced frame config struct"'s has navigated to true.

    3. Set node to node’s parent.

4.1.3.2. Reporting Entropy Budget

Likewise, each time a call to fence.reportEvent() from a fenced frame originating via selectURL() whose destination contains "shared-storage-select-url" and whose eventType is triggered, there is a leak of up to logarithm base 2 of the number of main input URLs entropy bits. The user agent will need to set a per-page load reporting entropy allowance to restrict the information leaked, with page load referring to a top-level traversable's (i.e. primary main frame’s) lifecycle.

A reporting entropy allowance is a maximum allowance of entropy bits that are permitted to leak via fence.reportEvent() during a given page load. This allowance is defined by the user agent.

Each top-level traversable will have a new double shared storage reporting budget associated to it which will be initialized with the value of user agent's reporting entropy allowance upon top-level traversable's creation.

When fence.reportEvent() is called with a destination containing "shared-storage-select-url", it will be necessary to charge shared storage reporting budget as below.

To determine reporting budget to charge, given a Document sourceDocument, run the following steps:
  1. Let debitSum be 0.

  2. Let node be sourceDocument’s node navigable.

  3. While node is not null:

    1. If node has a "fenced frame config struct":

      1. Let pendingBits be node’s "fenced frame config struct"'s pending shared storage budget debit.

      2. If pendingBits is greater than 0 and if "fenced frame config struct"'s has reported is false, increment debitSum by pendingBits

    2. Set node to node’s parent.

  4. Return debitSum.

The "fenced frame config struct" and its boolean has reported have not yet been added to the draft Fenced Frame specification. Some form of them will be added, although their names are subject to bikeshedding. Fix the names when they are added.

To charge shared storage reporting budget given a Document sourceDocument, run the following steps:
  1. Let toCharge be the result of running determine reporting budget to charge with sourceDocument.

  2. Let node be sourceDocument’s node navigable.

  3. Let topNode be the result of running get the top-level traversable for node.

  4. If topNode’s shared storage reporting budget is less than toCharge, return false.

  5. While node is not null:

    1. If node has a "fenced frame config struct" and if node’s "fenced frame config struct"'s pending shared storage budget debit is greater than 0, set node’s "fenced frame config struct"'s has reported to true.

    2. Set node to node’s parent.

  6. Decrement topNode’s shared storage reporting budget by toCharge.

  7. Return true.

A user agent may wish to set a timer to periodically purge expired bit debits from all navigation entropy ledgers, as the expired bit debits will no longer be needed.

To purge expired bit debits from all navigation entropy ledgers, run the following steps:
  1. For each origin -> ledger of user agent's shared storage navigation budget table:

    1. For each bitDebit in ledger, if the result of running check whether a bit debit is expired with bitDebit is true, remove bitDebit from ledger.

4.1.4. Run Operation Methods

The run(name, options) method steps are:
  1. Let promise be a new promise.

  2. Let worklet be WindowSharedStorage's worklet.

  3. If worklet’s list of global scopes is empty, then return a promise rejected with a TypeError.

  4. Assert that worklet’s list of global scopes contains a single SharedStorageWorkletGlobalScope.

  5. If the result of running check whether addModule is finished for worklet’s SharedStorageWorkletGlobalScope is false, return a promise rejected with a TypeError.

  6. Let realm be the current realm.

  7. Let outsideSettings be worklet's relevant settings object.

  8. If the result of running determine whether shared storage is allowed on outsideSettings is false, return a promise rejected with a TypeError.

  9. Let agent be the result of obtaining a worklet agent given outsideSettings.

  10. Run the following steps in agent:

    1. Queue a global task on the DOM manipulation task source, given realm’s global object, to resolve promise with undefined.

    2. If worklet's module map is not empty:

      1. Let operationMap be this SharedStorageWorkletGlobalScope's operation map.

      2. If operationMap contains name:

        1. Let operation be operationMap[name].

        2. If options contains data:

          1. Let argumentsList be a new list.

          2. Append data to argumentsList.

          3. Call operation with argumentsList.

        3. Otherwise, call operation without any arguments list.

    3. If options["keepAlive"] is false:

      1. Wait for operation to finish running, if applicable.

      2. Run terminate a worklet global scope with SharedStorageWorkletGlobalScope.

  11. Return promise.

To get the select-url result index, given worklet worklet, DOMString operationName, list urlList, and SharedStorageRunOperationMethodOptions options:
  1. Let promise be a new promise.

  2. Let context be WindowSharedStorage's Window's browsing context.

  3. If context is null, return a promise rejected with a TypeError.

  4. Let environment be context’s active window's relevant settings object.

  5. If environment’s associated document is not fully active, return a promise rejected with a TypeError.

  6. Let realm be the current realm.

  7. Let outsideSettings be worklet’s relevant settings object.

  8. Let agent be the result of obtaining a worklet agent given outsideSettings.

  9. Run the following steps in agent:

    1. Let index be default index.

    2. If worklet's module map is not empty:

      1. Let operationMap be the associated SharedStorageWorkletGlobalScope's operation map.

      2. If operationMap contains operationName:

        1. Let operation be operationMap[operationName].

        2. Let argumentsList be a new list with a single entry containing urlList.

        3. If options contains data, append data to argumentsList.

        4. Let operationResult be the result of running Call on operation with argumentsList.

        5. If operationResult has any error(s), then queue a global task on the DOM manipulation task source, given realm’s global object, to reject promise with a TypeError.

        6. Otherwise:

          1. Set index to the result of casting operationResult to an unsigned long.

          2. If this throws an exception:

            1. Catch it and queue a global task on the DOM manipulation task source, given realm’s global object, to reject promise with a TypeError.

            2. Abort these steps.

          3. Otherwise, if index is greater than urlList’s size:

            1. Queue a global task on the DOM manipulation task source, given realm’s global object, to reject promise with a TypeError.

            2. Abort these steps.

          4. Queue a global task on the DOM manipulation task source, given realm’s global object, to resolve promise with index.

  10. Return promise.

The selectURL(name, urls, options) method steps are:
  1. Let resultPromise be a new promise.

  2. Let context be WindowSharedStorage's Window's browsing context.

  3. If context is null, return a promise rejected with a TypeError.

  4. Let environment be context’s active window's relevant settings object.

  5. If the result of running determine whether shared storage is allowed on environment is false, return a promise rejected with a TypeError.

  6. Let document be context’s active document.

  7. If document is not allowed to use the "shared-storage-select-url" feature, return a promise rejected with a TypeError.

  8. Let worklet be WindowSharedStorage's worklet.

  9. If worklet’s list of global scopes is empty, then return a promise rejected with a TypeError.

  10. Assert that worklet’s list of global scopes contains a single SharedStorageWorkletGlobalScope.

  11. If the result of running check whether addModule is finished for worklet’s SharedStorageWorkletGlobalScope is false, return a promise rejected with a TypeError.

  12. If urls is empty or exceeds the maximum allowed length, return a promise rejected with a TypeError.

  13. Let urlList be an empty list.

  14. For each urlWithMetadata in urls:

    1. If urlWithMetadata has no field "url", return a promise rejected with a TypeError.

    2. Otherwise, let urlString be urlWithMetadata["url"].

    3. Let serializedUrl be the result of running get the canonical URL string if valid with urlString.

    4. If serializedUrl is undefined, return a promise rejected with a TypeError.

    5. Otherwise, append serializedUrl to urlList.

    6. If urlWithMetadata has field "reportingMetadata":

      1. Let reportingMetadata be urlWithMetadata["reportingMetadata"].

      2. If the result of running validate reporting metadata with reportingMetadata is false, reject resultPromise with a TypeError and abort these steps.

  15. Let fencedFrameConfigStruct be a "fenced frame config struct". Add correct struct name as well as linking when Fenced Frame API updates their draft spec to include it.

    The "fenced frame config struct" and the following "obtain a FencedFrameConfig from a fenced frame config struct" algorithm have not yet been added to the draft Fenced Frame specification. Some form of them will be added, although their names are subject to bikeshedding.

  16. If options["resolveToConfig"] is true, resolve resultPromise with the result of running "obtain a FencedFrameConfig from a fenced frame config struct" with fencedFrameConfigStruct. Add correct struct and algorithms names as well as linking when Fenced Frame API updates their draft spec to include it.

  17. Othewise, resolve resultPromise to fencedFrameConfigStruct’s "urn uuid". Add correct struct name and urn:uuid name as well as linking when Fenced Frame API updates their draft spec to include it.

  18. Let indexPromise be the result of running get the select-url result index, given worklet, name, urlList, and options.

  19. Upon fulfillment of indexPromise, perform the following steps:

    1. Let resultIndex be the numerical value of indexPromise.

    2. Let site be the result of running obtain a site with document’s origin.

    3. Let remainingBudget be the result of running determine remaining navigation budget with environment and site.

    4. Assert that remainingBudget is not undefined.

    5. Let pendingBits be the logarithm base 2 of urlList’s size.

    6. If pendingBits is greather than remainingBudget, set resultIndex to default index.

    7. Set fencedFrameConfigStruct’s pending shared storage budget debit to pendingBits.

    8. Set fencedFrameConfigStruct’s url to urlList[resultIndex].

    9. Let resultURLWithMetadata be urls[resultIndex].

    10. If resultURLWithMetadata has field "reportingMetadata", run register reporting metadata with resultURLWithMetadata["reportingMetadata"].

    11. If options["keepAlive"] is false, run terminate a worklet global scope with the associated SharedStorageWorkletGlobalScope.

  20. Upon rejection of indexPromise, perform the following steps:

    1. Set fencedFrameConfigStruct’s url to urlList[default index].

    2. If options["keepAlive"] is false, run terminate a worklet global scope with the associated SharedStorageWorkletGlobalScope.

  21. Return resultPromise.

4.2. Extension to the Window interface

Each Window object has an associated WindowSharedStorage instance sharedStorage, which is created alongside the .Window if Shared Storage is enabled, with the getter below.

partial interface Window {
  [SecureContext] readonly attribute WindowSharedStorage? sharedStorage;
};
The sharedStorage getter steps are:
  1. If this is fully active, return this’s sharedStorage.

  2. Otherwise, return null.

4.3. The WorkletSharedStorage interface

The WorkletSharedStorage interface is as follows.

[Exposed=(SharedStorageWorklet)]
interface WorkletSharedStorage : SharedStorage {
  Promise<DOMString> get(DOMString key);
  Promise<unsigned long> length();
  Promise<double> remainingBudget();

  async iterable<DOMString, DOMString>;
};

4.3.1. Worklet Setter/Deleter Methods

The set(key, value, options) method steps are:
  1. Let promise be a new promise.

  2. If the result of running check whether addModule is finished for WorkletSharedStorage's associated SharedStorageWorkletGlobalScope is false, return a promise rejected with a TypeError.

  3. If key’s length exceeds the maximum length, return a promise rejected with a TypeError.

  4. If value’s length exceeds the maximum length, return a promise rejected with a TypeError.

  5. Let context be WorkletSharedStorage's SharedStorageWorkletGlobalScope's outside settings's target browsing context.

  6. If context is null, return a promise rejected with a TypeError.

  7. If context’s active window's associated document is not fully active, return a promise rejected with a TypeError.

  8. Let environment be context’s active window's relevant settings object.

  9. Let databaseMap be the result of running obtain a shared storage bottle map for environment.

  10. If databaseMap is failure, then return a promise rejected with a TypeError.

  11. Let queue be context’s associated database's shared storage database queue.

  12. Let realm be the current realm.

  13. Enqueue the following steps on queue:

    1. If options["ignoreIfPresent"] is true:

      1. Let currentValue be the result of running retrieve an entry from the database with queue, databaseMap, environment, and key.

      2. If currentValue is failure:

        1. Queue a global task on the DOM manipulation task source, given realm’s global object, to reject promise with a TypeError.

        2. Abort these steps.

      3. If currentValue is not undefined:

        1. Queue a global task on the DOM manipulation task source, given realm’s global object, to resolve promise with undefined.

        2. Abort these steps.

    2. If the result of running store an entry in the database with queue, databaseMap, environment, key, and value is false, queue a global task on the DOM manipulation task source, given realm’s global object, to reject promise with a TypeError.

    3. Otherwise, queue a global task on the DOM manipulation task source, given realm’s global object, to resolve promise with undefined.

  14. Return promise.

The append(key, value) method steps are:
  1. Let promise be a new promise.

  2. If the result of running check whether addModule is finished for WorkletSharedStorage's associated SharedStorageWorkletGlobalScope is false, return a promise rejected with a TypeError.

  3. If key’s length exceeds the maximum length, return a promise rejected with a TypeError.

  4. If value’s length exceeds the maximum length, return a promise rejected with a TypeError.

  5. Let context be WorkletSharedStorage's SharedStorageWorkletGlobalScope's outside settings's target browsing context.

  6. If context is null, return a promise rejected with a TypeError.

  7. If context’s active window's associated document is not fully active, return a promise rejected with a TypeError.

  8. Let environment be context’s active window's relevant settings object.

  9. Let databaseMap be the result of running obtain a shared storage bottle map for environment.

  10. If databaseMap is failure, then return a promise rejected with a TypeError.

  11. Let queue be context’s associated database's shared storage database queue.

  12. Let realm be the current realm.

  13. Enqueue the following steps on queue:

    1. Let currentValue be the result of running retrieve an entry from the database with queue, databaseMap, environment, and key.

    2. If currentValue is failure:

      1. Queue a global task on the DOM manipulation task source, given realm’s global object, to reject promise with a TypeError.

      2. Abort these steps.

    3. If currentValue is not undefined:

      1. Let list be a new list.

      2. Append currentValue to list.

      3. Append value to list.

      4. Set value to the result of running concatenate on list.

    4. If the result of running store an entry in the database with queue, databaseMap, environment, key, and value is false, queue a global task on the DOM manipulation task source, given realm’s global object, to reject promise with a TypeError.

    5. Otherwise, queue a global task on the DOM manipulation task source, given realm’s global object, to resolve promise with undefined.

  14. Return promise.

The delete(key) method steps are:
  1. Let promise be a new promise.

  2. If the result of running check whether addModule is finished for WorkletSharedStorage's associated SharedStorageWorkletGlobalScope is false, return a promise rejected with a TypeError.

  3. If key’s length exceeds the maximum length, return a promise rejected with a TypeError.

  4. Let context be WorkletSharedStorage's SharedStorageWorkletGlobalScope's outside settings's target browsing context.

  5. If context is null, return a promise rejected with a TypeError.

  6. If context’s active window's associated document is not fully active, return a promise rejected with a TypeError.

  7. Let environment be context’s active window's relevant settings object.

  8. Let databaseMap be the result of running obtain a shared storage bottle map for environment.

  9. If databaseMap is failure, then return a promise rejected with a TypeError.

  10. Let queue be context’s associated database's shared storage database queue.

  11. Let realm be the current realm.

  12. Enqueue the following steps on queue:

    1. If the result of running delete an entry from the database with queue, environment, and key is false, queue a global task on the DOM manipulation task source, given realm’s global object, to reject promise with a TypeError.

    2. Otherwise, queue a global task on the DOM manipulation task source, given realm’s global object, to resolve promise with undefined.

  13. Return promise.

The clear() method steps are:
  1. Let promise be a new promise.

  2. If the result of running check whether addModule is finished for WorkletSharedStorage's associated SharedStorageWorkletGlobalScope is false, return a promise rejected with a TypeError.

  3. Let context be WorkletSharedStorage's SharedStorageWorkletGlobalScope's outside settings's target browsing context.

  4. If context is null, return a promise rejected with a TypeError.

  5. If context’s active window's associated document is not fully active, return a promise rejected with a TypeError.

  6. Let environment be context’s active window's relevant settings object.

  7. Let databaseMap be the result of running obtain a shared storage bottle map for environment.

  8. If databaseMap is failure, then return a promise rejected with a TypeError.

  9. Let queue be context’s associated database's shared storage database queue.

  10. Let realm be the current realm.

  11. Enqueue the following steps on queue:

    1. If the result of running clear all entries in the database with queue and environment is false, queue a global task on the DOM manipulation task source, given realm’s global object, to reject promise with a TypeError.

    2. Otherwise, queue a global task on the DOM manipulation task source, given realm’s global object, to resolve promise with undefined.

  12. Return promise.

4.3.2. Getter Methods

The get(key) method steps are:
  1. Let promise be a new promise.

  2. If the result of running check whether addModule is finished for WorkletSharedStorage's associated SharedStorageWorkletGlobalScope is false, return a promise rejected with a TypeError.

  3. If key’s length exceeds the maximum length, return a promise rejected with a TypeError.

  4. Let context be WorkletSharedStorage's SharedStorageWorkletGlobalScope's outside settings's target browsing context.

  5. If context is null, return a promise rejected with a TypeError.

  6. If context’s active window's associated document is not fully active, return a promise rejected with a TypeError.

  7. Let environment be context’s active window's relevant settings object.

  8. Let databaseMap be the result of running obtain a shared storage bottle map for environment.

  9. If databaseMap is failure, then return a promise rejected with a TypeError.

  10. Let queue be context’s associated database's shared storage database queue.

  11. Let realm be the current realm.

  12. Enqueue the following steps on queue:

    1. Let value be the result of running retrieve an entry from the database with queue, databaseMap, environment, and key.

    2. If value is failure, queue a global task on the DOM manipulation task source, given realm’s global object, to reject promise with a TypeError.

    3. Otherwise, if value is undefined, queue a global task on the DOM manipulation task source, given realm’s global object, to resolve promise with undefined.

    4. Otherwise, queue a global task on the DOM manipulation task source, given realm’s global object, to resolve promise with value.

  13. Return promise.

The length() method steps are:
  1. Let promise be a new promise.

  2. If the result of running check whether addModule is finished for WorkletSharedStorage's associated SharedStorageWorkletGlobalScope is false, return a promise rejected with a TypeError.

  3. Let context be WorkletSharedStorage's SharedStorageWorkletGlobalScope's outside settings's target browsing context.

  4. If context is null, return a promise rejected with a TypeError.

  5. If context’s active window's associated document is not fully active, return a promise rejected with a TypeError.

  6. Let environment be context’s active window's relevant settings object.

  7. Let databaseMap be the result of running obtain a shared storage bottle map for environment.

  8. If databaseMap is failure, then return a promise rejected with a TypeError.

  9. Let queue be context’s associated database's shared storage database queue.

  10. Let realm be the current realm.

  11. Enqueue the following steps on queue:

    1. Let numEntries be the result of running count entries in the database with queue and environment.

    2. If numEntries is failure, queue a global task on the DOM manipulation task source, given realm’s global object, to reject promise with a TypeError.

    3. Otherwise, queue a global task on the DOM manipulation task source, given realm’s global object, to resolve promise with numEntries.

  12. Return promise.

The remainingBudget() method steps are:
  1. Let promise be a new promise.

  2. If the result of running check whether addModule is finished for WorkletSharedStorage's associated SharedStorageWorkletGlobalScope is false, return a promise rejected with a TypeError.

  3. Let context be WorkletSharedStorage's SharedStorageWorkletGlobalScope's outside settings's target browsing context.

  4. If context is null, return a promise rejected with a TypeError.

  5. If context’s active window's associated document is not fully active, return a promise rejected with a TypeError.

  6. Let environment be context’s active window's relevant settings object.

  7. If the result of running determine whether shared storage is allowed on environment is false, return a promise rejected with a TypeError.

  8. Let site be the result of running obtain a site with context’s active document's origin.

  9. Assert that site is not an opaque origin.

  10. Let queue be context’s associated database's shared storage database queue.

  11. Let realm be the current realm.

  12. Enqueue the following steps on queue:

    1. Let remainingBudget be the result of running determine remaining navigation budget with site.

    2. Assert that remainingBudget is not undefined.

    3. Resolve queue a global task on the DOM manipulation task source, given realm’s global object, to resolve promise with remainingBudget.

  13. Return promise.

4.3.3. Iteration

Each WorkletSharedStorage async iterator instance has a queue pending entries of entries, initially empty.

Each WorkletSharedStorage async iterator instance also has a boolean error, initially false.

The asynchronous iterator initialization steps and get the next iteration result algorithms defined below correspond to those referred to as the asynchronous iterator initialization steps and get the next iteration result algorithms in the Web IDL Standard.

The asynchronous iterator initialization steps for a WorkletSharedStorage async iterator iterator are:
  1. Let promise be a new promise.

  2. If the result of running check whether addModule is finished for WorkletSharedStorage's associated SharedStorageWorkletGlobalScope is false, return a promise rejected with a TypeError.

  3. Let context be WorkletSharedStorage's SharedStorageWorkletGlobalScope's outside settings's target browsing context.

  4. If context is null, return a promise rejected with a TypeError.

  5. If context’s active window's associated document is not fully active, return a promise rejected with a TypeError.

  6. Let environment be context’s active window's relevant settings object.

  7. Let databaseMap be the result of running obtain a shared storage bottle map for environment.

  8. If databaseMap is failure, then return a promise rejected with a TypeError.

  9. Let queue be context’s associated database's shared storage database queue.

  10. Let realm be the current realm.

  11. Enqueue the following steps on queue:

    1. Let entries be the result of running retrieve all entries from the database with queue and environment.

    2. If entries is failure, queue a global task on the DOM manipulation task source, given realm’s global object, to reject promise with a TypeError.

    3. Otherwise, queue a global task on the DOM manipulation task source, given realm’s global object, to resolve promise with entries.

  12. Upon fulfillment of promise, run the following:

    1. Let promiseEntries be the value of promise.

    2. For each entry entry in promiseEntries, enqueue entry in iterator’s pending entries.

  13. Upon rejection of promise, set iterator’s error to true.

Toget the next iteration result, given a WorkletSharedStorage's async iterator iterator, run the following steps:
  1. Let promise be a new promise.

  2. Enqueue the following steps:

    1. If iterator’s error is true, return a promise rejected with a TypeError.

    2. If iterator’s pending entries is empty:

      1. Create an object doneObject.

      2. Queue a global task on the DOM manipulation task source, given realm’s global object, to resolve promise with doneObject.

      3. Abort these steps.

    3. Otherwise, let entry be the result of dequeueing from iterator’s pending entries.

    4. Queue a global task on the DOM manipulation task source, given realm’s global object, to resolve promise with entry.

  3. Return promise.

5. Triggering Operations Via HTTP Response Header

While setter and deleter operations (e.g.. set(), append(), delete(), clear()) can be initiated via the above APIs for Window or SharedStorageWorkletGlobalScope, setter/deleter operations can alternatively be triggered via HTTP response header.

This will require monkey patches to the HTML and Fetch specifications.

6. HTML Monkey Patches

6.1. sharedStorageWritable & sharedstoragewritable Attributes

Define the following interface mixin and include it in the IDL interfaces for HTMLIFrameElement and HTMLImageElement:

interface mixin HTMLSharedStorageWritableElementUtils {
  [CEReactions, SecureContext] attribute boolean sharedStorageWritable;
};

HTMLIFrameElement includes HTMLSharedStorageWritableElementUtils;
HTMLImageElement includes HTMLSharedStorageWritableElementUtils;

Add the following boolean content attributes:

iframe

sharedstoragewritable

img

sharedstoragewritable

The IDL attribute sharedStorageWritable must reflect the respective content attribute of the same name.

6.2. HTML Algorithm Modifications

6.2.1. Modification to Update the image data Algorithm

Modify update the image data as follows:

After the step

Set request’s priority to the current state...

add the step

  1. If the element has the sharedstoragewritable attribute present, set request’s shared storage writable to true.

6.2.2. Modification to Create navigation params by fetching Algorithm

Modify create navigation params by fetching as follows:

After the step

Let request be a new request, with ...

add the step

  1. If navigable’s container is an iframe element, and if it has a sharedstoragewritable content attribute, then set request’s shared storage writable to true.

7. Fetch Monkey Patches

7.1. sharedStorageWritable Key

A request has an associated boolean shared storage writable. Unless stated otherwise it is false.

The RequestInit dictionary contains a sharedStorageWritable key:

partial dictionary RequestInit {
  boolean sharedStorageWritable;
};

7.2. Fetch Algorithm Modifications

7.2.1. Modification to the Request Constructor Algorithm

Modify the new Request(input, init) constructor as follows:

Before the step

Set this’s request to request.

add the step

  1. If init["sharedStorageWritable"] exists, then set request’s shared storage writable to it.

7.2.2. Modification to HTTP network or cache fetch Algorithm

Modify the HTTP network or cache fetch algorithm as follows:

Before the step

Modify httpRequest’s header list per HTTP. ...

add the step

  1. Append or modify a Sec-Shared-Storage-Writable request header for httpRequest.

7.2.3. Modification to HTTP fetch Algorithm

Modify the HTTP fetch algorithm as follows:

Before the step

If internalResponse’s status is a redirect status: ...

add the step

  1. Handle a Shared-Storage-Write response, given response internalResponse and request request as input.

7.3. Shared Storage HTTP Headers

7.3.1. `Sec-Shared-Storage-Writable` Request Header

This specification defines a Sec-Shared-Storage-Writable HTTP request header.

The `Sec-Shared-Storage-Writable` request header is a Structured Header whose value must be a Boolean.

When a request sets `Sec-Shared-Storage-Writable` to true its response will be able to write to shared storage.

7.3.2. `Shared-Storage-Write` Response Header

This specification defines a Shared-Storage-Write HTTP response header.

The `Shared-Storage-Write` response header is a Structured Header whose value must be a List. The following list members are defined. Tokens and Strings holding the same sequence of characters are considered equivalent. Byte Sequences representing UTF-8 encoded bytes are also allowed, in order to provide functionality for writing and deleting non-ASCII Unicode keys and values via HTTP headers.

Unknown list members, including types that are neither strings nor Byte Sequences, are skipped, and the rest of the list is processed as if they weren’t present. Members are also skipped if they’re missing required parameters or those parameters have unexpected types.

Note: We allow Byte Sequences in order to accommodate Unicode keys and values. Any Byte Sequence will be assumed to be UTF-8 encoded and will fail to parse otherwise.

Add examples.

7.4. Shared Storage Fetch-Related Algorithms

To determine whether a request can currently use shared storage, given a request request, perform the following steps:
  1. Let window to request’s window.

  2. If window is not an environment settings object whose global object is a Window, return false.

  3. If the result of running determine whether shared storage is allowed for window is false, return false.

  4. Let document be window’s global object's associated document.

  5. Return the result of running Is feature enabled in document for origin? on "shared-storage", document, and request’s current URL's origin.

The determine whether a request can currently use shared storage algorithm needs to take into account "opt-in features", as articulated in https://github.com/w3c/webappsec-permissions-policy/pull/499.

To append or modify a Sec-Shared-Storage-Writable request header, given a request request, perform the following steps:
  1. If request’s shared storage writable is not true, then return.

    Note: On a redirect, it is possible for request’s shared storage writable to be true, but for the redirect not to have permission to use shared storage, making the result of running determine whether a request can currently use shared storage on request false.

  2. If the result of running determine whether a request can currently use shared storage on request is false, delete `Sec-Shared-Storage-Writable` from request’s header list.

  3. Otherwise, set a structured field value (`Sec-Shared-Storage-Writable`, true) in request’s header list.

To handle a Shared-Storage-Write response, given a response response and a request request, perform the following steps:
  1. Let sharedStorageWritable the result of running get a structured field value algorithm given `Sec-Shared-Storage-Writable`, "item", and request’s header list as input.

  2. If sharedStorageWritable is null, or sharedStorageWritable is not a Boolean, or the value of sharedStorageWritable is false, return.

  3. Let window to request’s window.

  4. Assert that window is an environment settings object whose global object is a Window.

  5. Let sharedStorage be window’s global object's sharedStorage.

  6. If sharedStorage is null, then return.

  7. Let list be response’s header list.

  8. Let operationsToParse be the result of running get a structured field value algorithm given `Shared-Storage-Write`, "list", and list as input.

  9. If operationsToParse is null or empty, then return.

  10. For each tuple (item, parameters) in operationsToParse, perform the following steps:

    1. If item is an Inner List, continue.

    2. Assert that item is an Bare Item.

    3. Let operationString be the result of running get the string value for item.

    4. If operationString is failure, continue.

    5. Switch on operationString:

      If operationString is "clear":
      Perform the following steps:
      1. Run clear all keys on sharedStorage.

      2. Continue.

      If operationString is "delete":
      Perform the following steps:
      1. Let key be the result of running obtain a string-like parameter value with parameters and "key".

      2. If key is not null, run delete a key on sharedStorage with key.

      3. Continue.

      If operationString is "append":
      Perform the following steps:
      1. Let key be the result of running obtain a string-like parameter value with parameters and "key".

      2. If key is null, continue.

      3. Let value be the result of running obtain a string-like parameter value with parameters and "value".

      4. If value is not null, run append a key-value pair on sharedStorage with key and value.

      5. Continue.

      If operationString is "set":
      Perform the following steps:
      1. Let key be the result of running obtain a string-like parameter value with parameters and "key".

      2. If key is null, continue.

      3. Let value be the result of running obtain a string-like parameter value with parameters and "value".

      4. If value is null, continue.

      5. Let options be a new SharedStorageSetMethodOptions.

      6. If the result of running obtain a boolean parameter value with parameters and "ignore_if_present" is true, set options["ignoreIfPresent"] to true..

      7. Run set a key-value pair on sharedStorage with key, value, and options.

      8. Continue.

      If operationString is anything else:
      Continue.
To check if string-like for a Bare Item item, do the following:
  1. If the item is a String, Token, or Byte Sequence, return true.

  2. Otherwise, return false.

To get the string value for a Bare Item item, do the following:
  1. If the result of running check if string-like on item is false, return failure.

  2. Switch on the type of item:

    If item is a Token:
    If item is a String:
    Perform the following steps:
    1. Assert that item is an ASCII string.

    2. Return item.

    If item is a Byte Sequence:
    Perform the following steps:
    1. Let fromUTF8 be the result of running UTF-8 decode on item.

    2. If fromUTF8 is an error, return null.

    3. Return fromUTF8.

To obtain a string-like parameter value, given a parameters map parameters and a Token paramKey, perform the following steps:
  1. If parameters does not contain paramKey, return null.

  2. If the result of check if string-like for parameters[paramKey] is false, return null.

  3. Return the result of running get the string value for parameters[paramKey].

To obtain a boolean parameter value, given a parameters map parameters and a Token paramKey, perform the following steps:
  1. If parameters does not contain paramKey, return null.

  2. If parameters[paramKey] is not a Boolean, return null.

  3. Return parameters[paramKey].

8. Permissions Policy Integration

This specification defines a policy-controlled feature identified by the string "shared-storage," along with a second policy-controlled feature identified by "shared-storage-select-url".

"shared-storage" gates access to Shared Storage in general, whereas "shared-storage-select-url" adds an exra permission layer to selectURL(). For each of these, the default allowlist is *.

9. Clear Site Data Integration

Add details for Clear Site Data integration.

10. Privacy Considerations

The Shared Storage API attempts to provide the ability to use cross-site data for a range of use cases in a way that better protects user privacy than the use of third-party cookies. Shared Storage’s main privacy safeguard is that read access of the data stored in its storage may only occur within an embedder’s SharedStorageWorklet. Well-defined limits restrict output of data from the SharedStorageWorklet to a minimum.

In particular, an embedder can select a URL from a short list of URLs based on data in their shared storage and then display the result in a fenced frame. The embedder will not be able to know which URL was chosen except through specifc mechanisms that will be better-mitigated in the longer term. Currently, a few bits of entropy can leak each time that the user clicks on the fenced frame to initiate a top-frame navigation and/or the fenced frame calls the fence.reportEvent() API.

An embedder is also able to send aggregatable reports through the Private Aggregation Service, which adds noise in order to achieve differential privacy, uses a time delay to send reports, imposes limits on the number of reports sent, and processes the reports into aggregate data so that individual privacy is protected.

Conformance

Document conventions

Conformance requirements are expressed with a combination of descriptive assertions and RFC 2119 terminology. The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in the normative parts of this document are to be interpreted as described in RFC 2119. However, for readability, these words do not appear in all uppercase letters in this specification.

All of the text of this specification is normative except sections explicitly marked as non-normative, examples, and notes. [RFC2119]

Examples in this specification are introduced with the words “for example” or are set apart from the normative text with class="example", like this:

This is an example of an informative example.

Informative notes begin with the word “Note” and are set apart from the normative text with class="note", like this:

Note, this is an informative note.

Index

Terms defined by this specification

Terms defined by reference

References

Normative References

[BEACON]
Ilya Grigorik; Alois Reitbauer. Beacon. URL: https://w3c.github.io/beacon/
[CSS-SYNTAX-3]
Tab Atkins Jr.; Simon Sapin. CSS Syntax Module Level 3. URL: https://drafts.csswg.org/css-syntax/
[DOM]
Anne van Kesteren. DOM Standard. Living Standard. URL: https://dom.spec.whatwg.org/
[ENCODING]
Anne van Kesteren. Encoding Standard. Living Standard. URL: https://encoding.spec.whatwg.org/
[FENCED-FRAME]
Fenced Frame. Draft Community Group Report. URL: https://wicg.github.io/fenced-frame/
[FETCH]
Anne van Kesteren. Fetch Standard. Living Standard. URL: https://fetch.spec.whatwg.org/
[FileAPI]
Marijn Kruisselbrink. File API. URL: https://w3c.github.io/FileAPI/
[HR-TIME-3]
Yoav Weiss. High Resolution Time. URL: https://w3c.github.io/hr-time/
[HTML]
Anne van Kesteren; et al. HTML Standard. Living Standard. URL: https://html.spec.whatwg.org/multipage/
[I18N-GLOSSARY]
Richard Ishida; Addison Phillips. Internationalization Glossary. URL: https://w3c.github.io/i18n-glossary/
[INFRA]
Anne van Kesteren; Domenic Denicola. Infra Standard. Living Standard. URL: https://infra.spec.whatwg.org/
[PERMISSIONS-POLICY-1]
Ian Clelland. Permissions Policy. URL: https://w3c.github.io/webappsec-permissions-policy/
[PUB-MANIFEST]
Matt Garrish; Ivan Herman. Publication Manifest. URL: https://w3c.github.io/pub-manifest/
[RFC2119]
S. Bradner. Key words for use in RFCs to Indicate Requirement Levels. March 1997. Best Current Practice. URL: https://datatracker.ietf.org/doc/html/rfc2119
[RFC8941]
M. Nottingham; P-H. Kamp. Structured Field Values for HTTP. February 2021. Proposed Standard. URL: https://www.rfc-editor.org/rfc/rfc8941
[STORAGE]
Anne van Kesteren. Storage Standard. Living Standard. URL: https://storage.spec.whatwg.org/
[URL]
Anne van Kesteren. URL Standard. Living Standard. URL: https://url.spec.whatwg.org/
[WEBIDL]
Edgar Chen; Timothy Gu. Web IDL Standard. Living Standard. URL: https://webidl.spec.whatwg.org/
[WEBRTC-IDENTITY]
Cullen Jennings; Martin Thomson. Identity for WebRTC 1.0. URL: https://w3c.github.io/webrtc-identity/

Informative References

[SERVICE-WORKERS]
Jake Archibald; Marijn Kruisselbrink. Service Workers. URL: https://w3c.github.io/ServiceWorker/

IDL Index

[Exposed=(Window)]
interface SharedStorageWorklet : Worklet {
};

[Exposed=SharedStorageWorklet, Global=SharedStorageWorklet]
interface SharedStorageWorkletGlobalScope : WorkletGlobalScope {
  undefined register(DOMString name,
                     SharedStorageOperationConstructor operationCtor);

  readonly attribute WorkletSharedStorage sharedStorage;
};

callback SharedStorageOperationConstructor =
  SharedStorageOperation(optional SharedStorageRunOperationMethodOptions options);

[Exposed=SharedStorageWorklet]
interface SharedStorageOperation {
};

dictionary SharedStorageRunOperationMethodOptions {
  object data;
  boolean resolveToConfig = false;
  boolean keepAlive = false;
};

[Exposed=SharedStorageWorklet]
interface SharedStorageRunOperation : SharedStorageOperation {
  Promise<undefined> run(object data);
};

[Exposed=SharedStorageWorklet]
interface SharedStorageSelectURLOperation : SharedStorageOperation {
  Promise<long> run(object data,
                    FrozenArray<SharedStorageUrlWithMetadata> urls);
};

[Exposed=(Window,SharedStorageWorklet)]
interface SharedStorage {
  Promise<any> set(DOMString key,
                   DOMString value,
                   optional SharedStorageSetMethodOptions options = {});
  Promise<any> append(DOMString key,
                      DOMString value);
  Promise<any> delete(DOMString key);
  Promise<any> clear();
};

dictionary SharedStorageSetMethodOptions {
  boolean ignoreIfPresent = false;
};

typedef (USVString or FencedFrameConfig) SharedStorageResponse;

[Exposed=(Window)]
interface WindowSharedStorage : SharedStorage {
  Promise<any> run(DOMString name,
                   optional SharedStorageRunOperationMethodOptions options = {});
  Promise<SharedStorageResponse> selectURL(DOMString name,
                               FrozenArray<SharedStorageUrlWithMetadata> urls,
                               optional SharedStorageRunOperationMethodOptions options = {});

  readonly attribute SharedStorageWorklet worklet;
};

dictionary SharedStorageUrlWithMetadata {
  required USVString url;
  object reportingMetadata;
};

partial interface Window {
  [SecureContext] readonly attribute WindowSharedStorage? sharedStorage;
};

[Exposed=(SharedStorageWorklet)]
interface WorkletSharedStorage : SharedStorage {
  Promise<DOMString> get(DOMString key);
  Promise<unsigned long> length();
  Promise<double> remainingBudget();

  async iterable<DOMString, DOMString>;
};

interface mixin HTMLSharedStorageWritableElementUtils {
  [CEReactions, SecureContext] attribute boolean sharedStorageWritable;
};

HTMLIFrameElement includes HTMLSharedStorageWritableElementUtils;
HTMLImageElement includes HTMLSharedStorageWritableElementUtils;

partial dictionary RequestInit {
  boolean sharedStorageWritable;
};

Issues Index

The "fenced frame config struct" and its boolean has navigated have not yet been added to the draft Fenced Frame specification. Some form of them will be added, although their names are subject to bikeshedding. Fix the names when they are added.
The "fenced frame config struct" and its boolean has reported have not yet been added to the draft Fenced Frame specification. Some form of them will be added, although their names are subject to bikeshedding. Fix the names when they are added.
The "fenced frame config struct" and the following "obtain a FencedFrameConfig from a fenced frame config struct" algorithm have not yet been added to the draft Fenced Frame specification. Some form of them will be added, although their names are subject to bikeshedding.
The determine whether a request can currently use shared storage algorithm needs to take into account "opt-in features", as articulated in https://github.com/w3c/webappsec-permissions-policy/pull/499.