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-level traversable 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-level traversable 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-level traversable 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 output 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.
typedef (USVString or FencedFrameConfig) SharedStorageResponse;
[Exposed=(Window)]
interface SharedStorageWorklet : Worklet {
  Promise<SharedStorageResponse> selectURL(DOMString name,
                               sequence<SharedStorageUrlWithMetadata> urls,
                               optional SharedStorageRunOperationMethodOptions options = {});
  Promise<any> run(DOMString name,
                   optional SharedStorageRunOperationMethodOptions options = {});
};

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

Each SharedStorageWorklet has an associated USVString data origin, initialized to "context-origin".

Each SharedStorageWorklet has an associated boolean has cross-origin data origin, 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 state, and if the result is "DisallowedDueToNonPreferenceError", or if the result is "DisallowedDueToPreferenceError" and the worklet’s has cross-origin data origin is false, it will cause addModule() to fail, as detailed in the § 2.2.6 Monkey Patch for addModule().

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

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

  2. If allowedInOpaqueOriginContext is false and environment’s origin is an opaque origin, then return false.

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

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

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

  6. If globalObject is a Window, and if the result of running Is feature enabled in document for origin? on "shared-storage", globalObject’s associated document, and origin returns false, then return false.

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

  8. Return true.

Here are the scenarios where the algorithms determine whether shared storage is allowed by context and check if user preference setting allows access to shared storage are used:
To check if addModule is allowed and update state given a SharedStorageWorklet worklet and a URL moduleURLRecord, run the following steps:
  1. If worklet’s addModule initiated is true, return "DisallowedDueToNonPreferenceError".

  2. Set worklet’s addModule initiated to true.

  3. Let workletDataOrigin be the current settings object's origin.

  4. If worklet’s data origin is "script-origin", set workletDataOrigin to moduleURLRecord’s origin.

  5. Otherwise, if worklet’s data origin is not "context-origin":

    1. Let customOriginUrl be the result of running a URL parser on worklet’s data origin.

    2. If customOriginUrl is not a valid URL, return "DisallowedDueToNonPreferenceError".

    3. Set workletDataOrigin to customOriginUrl’s origin.

  6. Let hasCrossOriginDataOrigin be false.

  7. If workletDataOrigin and the current settings object's origin are not same origin, then set hasCrossOriginDataOrigin to true.

  8. Let allowedInOpaqueOriginContext be hasCrossOriginDataOrigin.

  9. If the result of running determine whether shared storage is allowed by context given the current settings object, workletDataOrigin, and allowedInOpaqueOriginContext is false, return "DisallowedDueToNonPreferenceError".

  10. Set worklet’s has cross-origin data origin to hasCrossOriginDataOrigin.

  11. If the result of running check if user preference setting allows access to shared storage given the current settings object and workletDataOrigin is false, return "DisallowedDueToPreferenceError".

  12. Return "Allowed".

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. Run Operation Methods on SharedStorageWorklet

To get the select-url result index, given SharedStorageWorklet worklet, DOMString operationName, list of strings urlList, an origin workletDataOrigin, a navigable navigable, SharedStorageRunOperationMethodOptions options, a pre-specified report parameters or null preSpecifiedParams and an aggregation coordinator or null aggregationCoordinator, run the following steps. This algorithm will return a tuple consisting of a promise that resolves into an unsigned long whose value is the index of the URL selected from urlList and a boolean indicating whether the top-level traversable’s budgets should be charged.
  1. Let promise be a new promise.

  2. Let window be worklet’s relevant settings object.

  3. Assert: window is a Window.

  4. If window’s browsing context is null, then return the tuple of a (promise rejected with a TypeError, true).

  5. If window’s associated document is not fully active, return the tuple of a (promise rejected with a TypeError, true).

  6. Assert: worklet’s global scopes's size is 1.

  7. Let globalScope be worklet’s global scopes[0].

  8. Let moduleMapKeyTuples be the result of running get the keys on globalScope’s relevant settings object's module map.

  9. Assert: moduleMapKeyTuples has size 1.

  10. Let moduleURLRecord be moduleMapKeyTuples[0][0].

  11. Let savedQueryName be options["savedQuery"].

  12. If savedQueryName is a string that is not the empty string, then:

    1. Let callbackTask be the result of running obtain a callback to process the saved index result, given window, urlList, and promise.

    2. Let savedIndex be the result of running get the index for a saved query on navigable, workletDataOrigin, moduleURLRecord, operationName, savedQueryName, and callbackTask.

    3. If savedIndex is "pending callback", then return the tuple (promise, false).

      Note: callbackTask is now stored to be run when a previously obtained worklet agent completes its operation to select the index for this query. When the steps of callbackTask are run, promise will be resolved.

    4. If savedIndex is an unsigned long, then:

      1. Queue a global task on the DOM manipulation task source, given window, to run the steps of callbackTask, given savedIndex.

        Note: Running the steps of callbackTask will resolve promise.

      2. Return the tuple (promise, false).

    5. Assert that savedIndex is "pending current operation".

  13. Queue a task on globalScope’s worklet event loop to perform the following steps:

    1. Let operationMap be globalScope’s operation map.

    2. If operationMap does not contain operationName, then queue a global task on the DOM manipulation task source, given window, to reject promise with a TypeError, and abort these steps.

      Note: This could happen if register() was never called with operationName.

    3. Assert: operationMap[operationName]'s associated realm is this's relevant realm.

    4. Let operation be operationMap[operationName], converted to RunFunctionForSharedStorageSelectURLOperation.

    5. Let privateAggregationCompletionTask be the result of setting up the Private Aggregation scopes given workletDataOrigin, preSpecifiedParams and aggregationCoordinator.

    6. Let argumentsList be the list « urlList ».

    7. If options["data"] exists, append it to argumentsList.

    8. Let indexPromise be the result of invoking operation with argumentsList.

    9. React to indexPromise:

      If it was fulfilled with value index:
      1. If index is greater than urlList’s size, then:

        1. If savedQueryName is a string that is not the empty string, then run store the index for a saved query with window, navigable, workletDataOrigin, moduleURLRecord, operationName, savedQueryName, and the default selectURL index.

        2. Queue a global task on the DOM manipulation task source, given window, to reject promise with a TypeError, and abort these steps.

        Note: The result index is beyond the input urls' size. This violates the selectURL() protocol, and we don’t know which url should be selected.

        Otherwise:

        1. If savedQueryName is a string that is not the empty string, then run store the index for a saved query with window, navigable, workletDataOrigin, moduleURLRecord, operationName, savedQueryName, and index.

        2. Queue a global task on the DOM manipulation task source, given window, to resolve promise with index.

        3. Run privateAggregationCompletionTask.

      If it was rejected:
      1. If savedQueryName is a string that is not the empty string, then run store the index for a saved query with window, navigable, workletDataOrigin, moduleURLRecord, operationName, savedQueryName, and the default selectURL index.

      2. Queue a global task on the DOM manipulation task source, given window, to reject promise with a TypeError.

        Note: This indicates that either operationCtor’s run() method encounters an error (where operationCtor is the parameter in register()), or the result index is a non-integer value, which violates the selectURL() protocol, and we don’t know which url should be selected.

      3. Run privateAggregationCompletionTask.

  14. Return the tuple (promise, true).

To handle the result of selecting an index, given a SharedStorageWorklet worklet, an environment settings object environment, a Document document, a sequence of SharedStorageUrlWithMetadata urls, a list of strings urlList, a navigable navigable, a SharedStorageRunOperationMethodOptions options, a fenced frame config mapping fencedFrameConfigMapping, a urn uuid urn, a boolean shouldChargeTopLevelBudgets, a boolean shouldUseDefaultIndex, and an unsigned long resultIndex, perform the following steps:
  1. Let site be the result of running obtain a site with document’s origin.

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

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

  4. If shouldChargeTopLevelBudgets is true:

    1. Let pageBudgetResult be the result of running charge shared storage top-level traversable budgets with navigable, site, and pendingBits.

    2. If pageBudgetResult is false, set shouldUseDefaultIndex to true.

  5. If pendingBits is greater than remainingBudget, set shouldUseDefaultIndex to true.

  6. If shouldUseDefaultIndex is true, set resultIndex to the default selectURL index.

  7. Let finalConfig be a new fenced frame config.

  8. Set finalConfig’s mapped url to urlList[resultIndex].

  9. Set finalConfig’s a "pending shared storage budget debit" field to pendingBits.

  10. Finalize a pending config on fencedFrameConfigMapping with urn and finalConfig.

  11. Let resultURLWithMetadata be urls[resultIndex].

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

  13. If options["keepAlive"] is false, run terminate a worklet global scope with worklet.

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

  2. If this's addModule initiated is false, then return a promise rejected with a TypeError.

  3. Let window be this's relevant settings object.

  4. Assert: window is a Window.

  5. Let context be window’s browsing context.

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

  7. Let preSpecifiedParams be the result of obtaining the pre-specified report parameters given options and context.

  8. If preSpecifiedParams is a DOMException, return a promise rejected with preSpecifiedParams.

  9. Let aggregationCoordinator be the result of obtaining the aggregation coordinator given options.

  10. If aggregationCoordinator is a DOMException, return a promise rejected with aggregationCoordinator.

  11. Let document be context’s active document.

  12. If this's global scopes is empty, then return a promise rejected with a TypeError.

    Note: This can happen if selectURL() is called before addModule().

  13. Assert: this's global scopes's size is 1.

  14. Let globalScope be this's global scopes[0].

  15. Let workletDataOrigin be globalScope’s realm's settings object's origin.

  16. If the result of running Is feature enabled in document for origin? on "shared-storage-select-url", document, and workletDataOrigin returns false, return a promise rejected with a TypeError.

  17. If the result of running check whether addModule is finished for globalScope is false, return a promise rejected with a TypeError.

  18. If urls is empty or if urls’s size is greater than 8, return a promise rejected with a TypeError.

    Note: 8 is chosen here so that each call of selectURL() can leak at most log2(8) = 3 bits of information when the result fenced frame is clicked. It’s not a lot of information per-call.

  19. Let urlList be an empty list.

  20. 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.

  21. Let navigable be window’s associated document's node navigable.

  22. Let fencedFrameConfigMapping be navigable’s traversable navigable's fenced frame config mapping.

  23. Let pendingConfig be a new fenced frame config.

  24. Let urn be the result of running store a pending config on fencedFrameConfigMapping with pendingConfig.

  25. If urn is failure, then return a promise rejected with a TypeError.

  26. Let environment be window’s relevant settings object.

  27. Let allowedInOpaqueOriginContext be this's has cross-origin data origin.

  28. If the result of running determine whether shared storage is allowed by context given environment, workletDataOrigin, and allowedInOpaqueOriginContext is false, return a promise rejected with a TypeError.

  29. If the result of running check if user preference setting allows access to shared storage given environment and workletDataOrigin is false:

    1. If this's has cross-origin data origin is false, return a promise rejected with a TypeError.

  30. If options["resolveToConfig"] is true, resolve resultPromise with pendingConfig.

  31. Otherwise, resolve resultPromise with urn.

  32. Let (indexPromise, shouldChargeTopLevelBudgets) be the result of running get the select-url result index, given this, name, urlList, workletDataOrigin, navigable, options, preSpecifiedParams and aggregationCoordinator.

  33. Upon fulfillment of indexPromise with resultIndex, run handle the result of selecting an index given worklet, environment, document, urls, urlList, navigable, options, fencedFrameConfigMapping, urn, shouldChargeTopLevelBudgets, false, and resultIndex.

  34. Upon rejection of indexPromise, run handle the result of selecting an index given worklet, environment, document, urls, urlList, navigable, options, fencedFrameConfigMapping, urn, shouldChargeTopLevelBudgets, true, and the default selectURL index.

  35. Return resultPromise.

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

  2. If this's addModule initiated is false, then return a promise rejected with a TypeError.

  3. Let window be this's relevant settings object.

  4. Assert: window is a Window.

  5. Let context be window’s browsing context.

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

  7. Let preSpecifiedParams be the result of obtaining the pre-specified report parameters given options and context.

  8. If preSpecifiedParams is a DOMException, return a promise rejected with preSpecifiedParams.

  9. Let aggregationCoordinator be the result of obtaining the aggregation coordinator given options.

  10. If aggregationCoordinator is a DOMException, return a promise rejected with aggregationCoordinator.

  11. If this's global scopes is empty, then return a promise rejected with a TypeError.

    Note: This can happen if run() is called before addModule().

  12. Assert: this's global scopes's size is 1.

  13. Let globalScope be this's global scopes[0].

  14. If the result of running check whether addModule is finished for globalScope is false, return a promise rejected with a TypeError.

  15. Let workletDataOrigin be globalScope’s realm's settings object's origin.

  16. Let allowedInOpaqueOriginContext be this's has cross-origin data origin.

  17. If the result of running determine whether shared storage is allowed by context given window, workletDataOrigin, and allowedInOpaqueOriginContext is false, reject promise with a TypeError.

  18. If the result of running check if user preference setting allows access to shared storage given window and workletDataOrigin is false:

    1. If this's has cross-origin data origin is false, reject promise with a TypeError.

    2. Else, resolve promise with undefined.

    3. Return promise.

  19. Return promise, and immediately obtaining a worklet agent given window and run the rest of these steps in that agent:

    Note: The promise’s resolution should be before and not depend on the execution inside SharedStorageWorkletGlobalScope. This is because shared storage is a type of unpartitioned storage, and a SharedStorageWorkletGlobalScope can have access to cross-site data, which shouldn’t be leaked via run() (via its success/error result).

    1. Queue a global task on the DOM manipulation task source, given window, to resolve promise with undefined.

    2. If globalScope’s relevant settings object's module map is not empty:

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

      2. If operationMap contains name:

        1. Assert: operationMap[name]'s associated realm is this's relevant realm.

        2. Let operation be operationMap[name], converted to Function.

        3. Let privateAggregationCompletionTask be the result of setting up the Private Aggregation scopes given workletDataOrigin, preSpecifiedParams and aggregationCoordinator.

        4. Let argumentsList be a new list.

        5. If options["data"] exists, append it to argumentsList.

        6. Invoke operation with argumentsList and "report".

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

        8. Run privateAggregationCompletionTask.

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

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

      2. Run terminate a worklet global scope with this.

To obtain the aggregation coordinator given a SharedStorageRunOperationMethodOptions options, perform the following steps. They return an aggregation coordinator, null or a DOMException:
  1. If options["privateAggregationConfig"] does not exist, return null.

  2. If options["privateAggregationConfig"]["aggregationCoordinatorOrigin"] does not exist, return null.

  3. Return the result of obtaining the Private Aggregation coordinator given options["privateAggregationConfig"]["aggregationCoordinatorOrigin"].

To obtain the pre-specified report parameters given a SharedStorageRunOperationMethodOptions options and a browsing context context, perform the following steps. They return a pre-specified report parameters, null, or a DOMException:
  1. If options["privateAggregationConfig"] does not exist, return null.

  2. Let privateAggregationConfig be options["privateAggregationConfig"].

  3. Let contextId be null.

  4. If privateAggregationConfig["contextId"] exists, set contextId to privateAggregationConfig["contextId"].

  5. If contextId’s length is greater than 64, return a new DOMException with name "DataError".

  6. Let filteringIdMaxBytes be the default filtering ID max bytes.

  7. If privateAggregationConfig["filteringIdMaxBytes"] exists, set filteringIdMaxBytes to privateAggregationConfig["filteringIdMaxBytes"].

  8. If filteringIdMaxBytes is not contained in the valid filtering ID max bytes range, return a new DOMException with name "DataError".

  9. If context’s fenced frame config instance is not null:

    1. If filteringIdMaxBytes is not the default filtering ID max bytes or contextId is not null, return a new DOMException with name "DataError".

  10. Return a new pre-specified report parameters with the items:

    context ID

    contextId

    filtering ID max bytes

    filteringIdMaxBytes

To set up the Private Aggregation scopes given an origin workletDataOrigin, a pre-specified report parameters or null preSpecifiedParams and an aggregation coordinator or null aggregationCoordinator, perform the following steps. They return an algorithm.

Note: The returned algorithm should be run when the associated operation is complete.

  1. Let batchingScope be a new batching scope.

  2. Let debugScope be a new debug scope.

  3. Let privateAggregationTimeout be null.

  4. Let hasRunPrivateAggregationCompletionTask be false.

  5. Let privateAggregationCompletionTask be an algorithm to perform the following steps:

    1. If hasRunPrivateAggregationCompletionTask, return.

    2. Set hasRunPrivateAggregationCompletionTask to true.

    3. Mark a debug scope complete given debugScope.

    4. Process contributions for a batching scope given batchingScope, workletDataOrigin, "shared-storage" and privateAggregationTimeout.

  6. If aggregationCoordinator is not null, set the aggregation coordinator for a batching scope given aggregationCoordinator and batchingScope.

  7. If preSpecifiedParams is not null:

    1. Let isDeterministicReport be the result of determining if a report should be sent deterministically given preSpecifiedParams.

    2. If isDeterministicReport:

      1. Set privateAggregationTimeout to the current wall time plus the deterministic operation timeout duration.

    3. Set the pre-specified report parameters for a batching scope given preSpecifiedParams and batchingScope.

    4. If isDeterministicReport, run the following steps in parallel:

      1. Wait until privateAggregationTimeout.

      2. Run privateAggregationCompletionTask.

  8. Return privateAggregationCompletionTask.

The deterministic operation timeout duration is an implementation-defined non-negative duration that controls how long a Shared Storage operation may make Private Aggregation contributions if it is triggering a deterministic report and, equivalently, when that report should be sent after the operation begins.

2.2. Monkey Patch for Worklets

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

2.2.1. Monkey Patch for set up a worklet environment settings object

The set up a worklet environment settings object algorithm will need to include an additional parameter: Worklet worklet. The step that defines the settingsObject’s origin should be modified as follows:

  1. Let settingsObject be a new environment settings object whose algorithms are defined as follows:

    ......

    The origin

    1. Let workletGlobalScope be the global object of realmExecutionContext’s Realm component.

    2. If workletGlobalScope is not SharedStorageWorkletGlobalScope, return origin.

    3. Assert that worklet is a SharedStorageWorklet.

    4. If worklet’s data origin is "context-origin", return outsideSettings’s origin.

    5. Otherwise, if data origin is "script-origin":

      1. Let pendingAddedModules be a clone of worklet’s added modules list.

      2. Assert: pendingAddedModules’s size is 1.

      3. Let moduleURL be pendingAddedModules[0].

      4. Return moduleURL’s origin.

    6. Otherwise, let customOriginUrl be the result of running a URL parser on data origin.

    7. Assert customOriginUrl is a valid URL.

    8. Return customOriginUrl’s origin.

    ......

2.2.2. Monkey Patch for create a worklet global scope

The create a worklet global scope algorithm will need to be modified to pass in the worklet parameter:

  1. Let insideSettings be the result of setting up a worklet environment settings object given realmExecutionContext, outsideSettings, and worklet.

2.2.3. Monkey Patch for fetch a worklet script graph

The algorithm fetch a worklet script graph calls into the fetch a worklet/module worker script graph algorithm, which takes in an algorithm parameter processCustomFetchResponse. The definition of that processCustomFetchResponse parameter will need to include the following step before the step "5. Fetch request, ...":

  1. If fetchClient’s global object is SharedStorageWorkletGlobalScope:

    1. Set request’s redirect mode to "error".

      Note: For shared storage, redirects are disallowed for the module script request. With this restriction, it’s possible to define and to use the algorithm that gets the realm’s settings object's origin (as described in § 2.2.1 Monkey Patch for set up a worklet environment settings object) as soon as the SharedStorageWorkletGlobalScope is created, as the origin won’t change. This restriction may be removed in a future iteration of the design. If redirects become allowed, presumably, the algorithm that gets the realm’s settings object's origin should be updated to return the final request’s URL's origin after receiving the final request’s response, and the user preference checkings shall only be done after that point.

    2. If fetchClient’s origin and settingsObject’s origin are not same origin:

      1. Let dataOriginValue be the serialization of settingsObject’s origin.

      2. Assert that dataOriginValue is not null.

      3. Append the header (`Sec-Shared-Storage-Data-Origin`, dataOriginValue) to request’s header list.

2.2.4. The `Shared-Storage-Cross-Origin-Worklet-Allowed` HTTP response header

The `Shared-Storage-Cross-Origin-Worklet-Allowed` HTTP response header, along with the traditional CORS headers, can be used to grant a cross-origin site the permission to create a worklet from the module script’s URL's origin, and to run subsequent operations on the worklet using the module script’s URL's origin as the data partition origin for accessing shared storage data, i.e. the origin set in § 2.2.1 Monkey Patch for set up a worklet environment settings object, which becomes the origin used in all SharedStorage calls to obtain a shared storage bottle map.

Worklets that load cross-origin scripts rely on CORS as a baseline permission mechanism to indicate trusted external origins. However, CORS alone is insufficient for creation of a worklet with cross-origin script whose data partition origin is the script origin. Unlike simple resource sharing, worklets allow the creator site to execute JavaScript within the context of the target origin. To ensure security, an additional response header, `Shared-Storage-Cross-Origin-Worklet-Allowed`, is required from the script origin.

2.2.5. Monkey Patch for HTTP fetch

Steps will need to be added to the HTTP fetch algorithm.

Note: It is the responsibility of the site serving the module script to carefully consider the security implications: when the module script’s URL's origin and the worklet’s creator Window origin are not same origin, by sending permissive CORS headers the `Shared-Storage-Cross-Origin-Worklet-Allowed` header on the module script response, the server will be granting the worklet’s creation and subsequent operations on the worklet, while allowing the worklet to use the worklet’s script’s origin as the origin for accessing the shared storage data, i.e. the data partition origin. For example, the worklet’s creator Window could poison and use up the worklet origin’s site's remaining navigation budget by calling selectURL() or run(), where the worklet origin is the global scope’s realm's settings object's origin.

2.2.6. Monkey Patch for addModule()

The addModule() method steps for Worklet will need to include the following step before the step "Let promise be a new promise":

  1. If this is of type SharedStorageWorklet:

    1. Let addModuleAllowedResult be the result of running check if addModule is allowed and update state given this and moduleURLRecord.

    2. If addModuleAllowedResult is "DisallowedDueToNonPreferenceError":

      1. Return a promise rejected with a TypeError.

    3. Else if addModuleAllowedResult is "DisallowedDueToPreferenceError":

      1. If this’s has cross-origin data origin is false, then return a promise rejected with a TypeError.

    4. Else:

      1. Assert: addModuleAllowedResult is "Allowed".

On user preferences error, addModule() will be aborted at an early stage. However, the error will only be exposed to the caller for a same-origin worklet (i.e. where the initiator document’s origin is same-origin with the module script’s origin). For a cross-origin worklet, the error will be hidden. This is to prevent a caller from knowing which origins the user has disabled shared storage for via preferences (if a per-origin preference exists for that browser vendor).

A caller may still use timing attacks to know this information, but this is a minor security/privacy issue, as in reality very few users would set such preferences, and doing a wide search would incur a significant performance cost spinning up the worklets.

This rationale also applies to the handling for user preferences error for selectURL() and run().

After the step "Let addedSuccessfully be false", we need to include the following step:

  1. If this is of type SharedStorageWorklet, has cross-origin data origin is true, and data origin is not "script-origin":

    1. Assert pendingTasks is 1.

    2. Set pendingTasks to 2.

    3. Queue a global task on the networking task source given workletGlobalScope to perform the following steps:

      1. Let customOriginUrl be the result of running a URL parser on data origin.

      2. Assert customOriginUrl is a valid URL.

      3. Set customOriginUrl’s path to ≪".well-known", "shared-storage", "trusted-origins"≫.

      4. Let request be a new request whose URL is customOriginUrl, mode is "cors", referrer is "client", destination is "json", initiator type is "script", and client is outsideSettings.

      5. Fetch request with processResponseConsumeBody set to the following algorithm, given response response and null, failure or a byte sequence bodyBytes:

        1. If any of the following are true:

          • bodyBytes is null or failure; or

          • response’s status is not an ok status,

          then:

          1. Set pendingTasks to −1.

          2. Reject promise with an "TypeError" DOMException.

          3. Abort these steps.

        2. Let mimeType be the result of extracting a MIME type from response’s header list.

        3. If mimeType is not a JSON MIME type, then:

          1. Set pendingTasks to −1.

          2. Reject promise with an "TypeError" DOMException.

          3. Abort these steps.

        4. Let sourceText be the result of UTF-8 decoding bodyBytes.

        5. Let parsed be the result of parsing a JSON string to an Infra value given sourceText.

        6. If parsed is not a list or if parsed is empty, then:

          1. Set pendingTasks to −1.

          2. Reject promise with an "TypeError" DOMException.

          3. Abort these steps.

        7. Let doesMatch be false.

        8. For each item of parsed:

          1. If item is not an ordered map, or if item does not contain scriptOrigin, or if item does not contain contextOrigin:

            1. Set pendingTasks to −1.

            2. Reject promise with an "TypeError" DOMException.

            3. Abort these steps.

          2. Let doesMatch be the result of running check for script and context origin match on item[scriptOrigin], moduleURLRecord’s origin, item[contextOrigin], and outsideSettings’s origin.

          3. If doesMatch is true:

            1. Queue a global task on the networking task source given this’s relevant global object to perform the following steps:

              1. If pendingTasks is not −1, then:

                1. Set pendingTasks to pendingTasks − 1.

                2. 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.

            2. Break.

        9. If doesMatch is false, then:

          1. Set pendingTasks to −1.

          2. Reject promise with an "TypeError" DOMException.

Note: If the worklet data origin is different from the current context and the script origin, an additional check is performed. This involves fetching a configuration file from the worklet data origin to verify that the current context is allowed to load the worklet with the script and perform operations.

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.

Just before the final step, currently "Return promise.", add the following step:

  1. If this is a SharedStorageWorklet, upon fulfillment of promise or upon rejection of promise, run the following steps:

    1. Let globalScopes be this’s global scopes.

    2. Assert: globalScopessize equals 1.

    3. Let privateAggregationObj be globalScopes[0]'s privateAggregation.

    4. Set privateAggregationObj’s allowed to use to the result of determining whether this's relevant global object's associated document is allowed to use the "private-aggregation" policy-controlled feature.

      Consider adding an early return here if the permissions policy check is made first.

    5. Set privateAggregationObj’s scoping details to a new scoping details with the items:

      get batching scope steps

      An algorithm that returns the batching scope that is scheduled to be passed to process contributions for a batching scope when the call currently executing in scope returns.

      get debug scope steps

      An algorithm that returns the debug scope that is scheduled to be passed to mark a debug scope complete when the call currently executing in scope returns.

      Note: Multiple operation invocations can be in-progress at the same time, each with a different batching scope and debug scope. However, only one can be currently executing.

A trusted origin type is a string or list of strings.

To check for script and context origin match, given trusted origin type itemScriptOrigin, origin actualScriptOrigin, trusted origin type itemContextOrigin, and origin actualContextOrigin, perform the following steps:
  1. If the result of running check for trusted origin match, given itemScriptOrigin and actualScriptOrigin is false, return false.

  2. Return the result of running check for trusted origin match, given itemContextOrigin and actualContextOrigin.

To check for trusted origin match, given trusted origin type itemOrigin and origin actualOrigin, perform the following steps:
  1. If itemOrigin is a string, return the result of running check for trusted origin match on a string, given itemOrigin and actualOrigin.

  2. Otherwise, for each originString in itemOrigin:

    1. If the result of running check for trusted origin match on a string given originString and actualOrigin is true, return true.

  3. Return false.

To check for trusted origin match on a string, given string itemOrigin and origin actualOrigin, perform the following steps:
  1. If itemOrigin is "*", return true.

  2. Let itemOriginUrl be the result of running a URL parser on itemOrigin.

  3. If itemOriginUrl is not a valid URL, then return false.

  4. If itemOriginUrl’s origin and actualOrigin are same origin, return true.

  5. Otherwise, return false.

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

2.3. The SharedStorageWorkletGlobalScope

The SharedStorageWorklet's worklet global scope type is SharedStorageWorkletGlobalScope.

The SharedStorageWorklet's worklet destination type is "sharedstorageworklet".

2.3.1. Monkey Patch for request destination

The fetch request’s destination field should additionally include "sharedstorageworklet" as a valid value.

callback RunFunctionForSharedStorageSelectURLOperation = Promise<unsigned long>(sequence<USVString> urls, optional any data);
[Exposed=SharedStorageWorklet, Global=SharedStorageWorklet]
interface SharedStorageWorkletGlobalScope : WorkletGlobalScope {
  undefined register(DOMString name,
                     Function operationCtor);

  readonly attribute SharedStorage sharedStorage;
  readonly attribute PrivateAggregation privateAggregation;

  Promise<sequence<StorageInterestGroup>> interestGroups();

  readonly attribute SharedStorageWorkletNavigator navigator;
};

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.

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

Each SharedStorageWorkletGlobalScope has an associated SharedStorage instance shared storage instance.

Each SharedStorageWorkletGlobalScope has an associated SharedStorageWorkletNavigator instance navigator instance.

2.3.2. 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. Let operationClassInstance be the result of constructing operationCtor, with no arguments.

  6. Let runFunction be Get(operationClassInstance, "run"). Rethrow any exceptions.

  7. If IsCallable(runFunction) is false, throw a TypeError.

  8. Set the value of operationMap[name] to runFunction.

The "name" and "operationCtor" cannot be missing here given WebIDL. Should just check for default/empty values. [Issue #151]

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

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

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

  4. Let context be globalObject’s browsing context.

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

  6. Let document be context’s active window's associated document.

  7. If document is not fully active, return a promise rejected with a TypeError.

  8. Let workletDataOrigin be current realm's settings object's origin.

  9. Run the following steps in parallel:

    1. Let interestGroups be the result of running get storage interest groups for owner given workletDataOrigin.

    2. If interestGroups is failure:

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

    3. Otherwise:

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

  10. Return promise.

The sharedStorage getter steps are:
  1. If this's addModule success is true, return this's shared storage instance.

  2. Otherwise, throw a TypeError.

The navigator getter steps are:
  1. If this's addModule success is true, return this's navigator instance.

  2. Otherwise, throw a TypeError.

The privateAggregation getter steps are to:
  1. Get the privateAggregation given this.

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

2.4. SharedStorageUrlWithMetadata and Reporting

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

If a SharedStorageUrlWithMetadata dictionary contains a non-empty reportingMetadata object in the form of a dictionary whose keys are FenceEvent's 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.

reportingMetadata should be a dictionary. [Issue #141]

Inside a fenced frame with eventType-URL pairs that have been registered through selectURL() with reportingMetadata objects, if 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 not a dictionary, return false.

  2. If reportingMetadata is empty, return true.

  3. For each eventTypeurlString 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 fencedFrameConfigStruct, run the following steps:
  1. If reportingMetadata is empty, return.

  2. Assert: reportingMetadata is a dictionary.

  3. Let reportingUrlMap be an empty map.

  4. For each eventTypeurlString of reportingMetadata:

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

    2. Assert: url is a valid URL.

    3. Set reportingUrlMap[eventType] to url.

Store reportingUrlMap inside a fenced frame reporter class associated with fencedFrameConfigStruct. Both of these still need to be added to the draft [Fenced-Frame]. [Issue #144]

2.5. Entropy Budgets

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

On a call to selectURL(), when any of these budgets are exhausted, the default selectURL index will be used to determine which URL to select.

To get the default selectURL index, given sequence<USVString> urls, run the following steps:
  1. Return 0.

    Note: We could have chosen to return any unsigned long from the range from 0 to urls’s size, exclusive, as long as the returned index was independent from the registered operation class’s "run" method.

The default selectURL index is the index obtained by running get the default selectURL index, given sequence<USVString> urls.

If a user activates a fenced frame whose node document's browsing context's fenced frame config instance was generated by selectURL() and thereby initiates a top-level traversable 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-site navigation entropy allowance.

A navigation entropy allowance is a maximum allowance of entropy bits that are permitted to leak via fenced frames initiating top-level traversable navigations during a given navigation budget epoch for a given calling site. This allowance is defined by the user agent and is 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 sites to navigation entropy ledgers.

An navigation entropy ledger is a list of bit debits.

A bit debit is a struct with the following items:

bits

a double

timestamp

a DOMHighResTimeStamp (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 site, along with the current time as a timestamp, together as a bit debit in the shared storage navigation budget table.

Each site has an associated double remaining navigation budget, whose value is the navigation entropy allowance minus any bit debits whose timestamps are within the current navigation budget epoch.

When a site has insufficient remaining navigation budget, selectURL() will return a SharedStorageResponse (i.e. either a FencedFrameConfig or a urn uuid) for the url in the SharedStorageUrlWithMetadata at the default selectURL index.

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

  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 maxBitsdebitSum.

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 currentTimeepochLength.

  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-level traversable navigation initiated by a fenced frame whose node document's browsing context's fenced frame config instance 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-level traversable navigation, the bits must be stored as a pending shared storage budget debit in the corresponding fenced frame’s node document's browsing context's fenced frame config instance until this time.

Move the definition of pending shared storage budget debit to fenced frame config instance in the draft [Fenced-Frame] specification. [Issue #148]

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

Need to find a better way to specify timing of the navigation budget charging. [Issue #138]

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 top-level traversable, return.

  2. Let currentNavigable be sourceDocument’s node navigable.

  3. While currentNavigable is not null:

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

    2. Let instance be currentNavigable’s node document's browsing context's fenced frame config instance.

    3. Set currentNavigable to currentNavigable’s parent.

    4. If instance is null or site is an opaque origin, then continue.

    5. Let pendingBits be instance’s pending shared storage budget debit.

    6. If pendingBits is not greater than 0, then continue.

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

    8. Let bitDebit be a new bit debit.

    9. Set bitDebit’s bits to pendingBits.

    10. Let currentTime be the current wall time.

    11. Set bitDebit’s timestamp to currentTime.

    12. Append bitDebit to ledger.

    13. Set pendingBits to 0.

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 originledger 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.

2.5.2. Top-Level Traversable Entropy Budgets

In the short term, while we have less-restrictive fenced frames, it is necessary to impose additional limits as follows.

Each user agent will specify a maximum overall page entropy allowance and a maximum site page entropy allowance, where the former is the total number of bits allowed to leak for selectURL() per top-level traversable, where the latter is the total number of bits allowed to leak for selectURL() per site per top-level traversable

The shared storage page budget is a struct with the following items:

overall budget

a double

site budget map

a map of site to double

saved query map

a map of tuples (origin data origin, URL worklet script URL, string operation name, string query name) to saved query data

The saved query data is a struct with the following items:

index

a long

callbacks

a queue of tasks

2.5.2.1. Monkey patch for Traversable Navigables

In [HTML]'s Traversable navigables section, add the following:

In addition to the properties of a navigable, a traversable navigable has:

2.5.2.2. Monkey patch for Navigables

Modify the initialize the navigable algorithm by adding the following steps at the end:

  1. If parent is null and navigable is a traversable navigable, then:

    1. Let newPageBudget be a shared storage page budget with an empty site budget map.

    2. Set newPageBudget’s overall budget to overall page entropy allowance.

    3. Set navigable’s page budget to newPageBudget.

2.5.2.3. Saved queries
To get the index for a saved query, given navigable navigable, origin origin, URL moduleURLRecord, string operationName, string savedQueryName, and task callbackTask:
  1. Let topLevelTraversable be the result of running get the top-level traversable for navigable.

  2. Assert that topLevelTraversable’s page budget is not null.

  3. If topLevelTraversable’s page budget's saved query map does not contain (origin, moduleURLRecord, operationName, savedQueryName), then:

    1. Set topLevelTraversable’s page budget's saved query map[(origin, moduleURLRecord, operationName, savedQueryName)] to a new saved query data struct queryData.

    2. Set queryData’s index value to -1.

    3. Return "pending current operation".

  4. Let savedIndex be topLevelTraversable’s page budget's saved query map[(origin, moduleURLRecord, operationName, savedQueryName)]'s index.

  5. If savedIndex is -1:

    1. Enqueue callbackTask to queryData’s callbacks.

    2. Return "pending callback".

  6. Return savedIndex.

Note: The get the index for a saved query algorithm returns "pending current operation" to indicate that the index value is pending the result of queueing a task on the SharedStorageWorkletGlobalScope's worklet event loop to perform the registered operation.

Note: The get the index for a saved query algorithm returns "pending callback" to indicate that a task to determine the result of the index was previously queued on the SharedStorageWorkletGlobalScope's worklet event loop, and that we are now queueing an additional callbackTask to be run when the original task has completed.

To store the index for a saved query, given Window window, navigable navigable, origin origin, URL moduleURLRecord, string operationName, string savedQueryName, and unsigned long index:
  1. Let topLevelTraversable be the result of running get the top-level traversable for navigable.

  2. Assert that topLevelTraversable’s page budget is not null.

  3. Let queryData be topLevelTraversable’s page budget's saved query map[(origin, moduleURLRecord, operationName, savedQueryName)].

  4. Set queryData’s index to index.

  5. While queryData’s callbacks is not empty:

    1. Dequeue the next task callbackTask from queryData’s callbacks, and queue a global task on the DOM manipulation task source, given window, to run the steps of callbackTask, given index.

To obtain a callback to process the saved index result, given Window window, list of SharedStorageUrlWithMetadatas urlList, promise promise, perform the following steps. They return an algorithm.
  1. Let processIndexTask be an algorithm to perform the following steps, given an unsigned long index:

    1. If index is greater than urlList’s size, then queue a global task on the DOM manipulation task source, given window, to reject promise with a TypeError.

      Note: The result index is beyond the input urls' size. This violates the selectURL() protocol, and we don’t know which url should be selected.

    2. Otherwise, queue a global task on the DOM manipulation task source, given window, to resolve promise with index.

  2. Return processIndexTask.

2.5.2.4. Charging the Top-Level Traversable Entropy Budgets
To charge shared storage top-level traversable budgets given a navigable navigable, site site, and double pendingBits, run the following steps:
  1. Let topLevelTraversable be the result of running get the top-level traversable for navigable.

  2. Assert that topLevelTraversable’s page budget is not null.

  3. If pendingBits is greater than topLevelTraversable’s page budget's overall budget, then return false and abort these steps.

  4. If topLevelTraversable’s page budget's site budget map does not contain site, then set topLevelTraversable’s page budget's site budget map [site] to site page entropy allowance.

  5. If pendingBits is greater than topLevelTraversable’s page budget's site budget map [site], then return false and abort these steps.

  6. Decrement topLevelTraversable’s page budget's site budget map [site] by pendingBits.

  7. Decrement topLevelTraversable’s page budget's overall budget by pendingBits.

  8. Return true.

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, an environment settings object environment, and an origin origin, run these steps:
  1. Let allowedInOpaqueOriginContext be false.

  2. If the result of running determine whether shared storage is allowed by context given environment, origin, and allowedInOpaqueOriginContext is false, then return failure.

  3. If the result of running check if user preference setting allows access to shared storage given environment and origin is false, then return failure.

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

  5. 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 and an origin origin, 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, environment, and origin.

  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 an exception was thrown, then 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 an exception was thrown, then 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 an exception was thrown, then 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 an exception was thrown, then 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 an exception was thrown, then 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 an exception was thrown, then 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 an exception was thrown, then 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 an exception was thrown, then 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. Extension to the Window interface

Each Window object has an associated SharedStorage instance sharedStorage, which is created alongside the Window if Shared Storage is enabled, with the getter below.
partial interface Window {
  [SecureContext] readonly attribute SharedStorage? sharedStorage;
};
The sharedStorage getter steps are:
  1. If this is fully active, return this's sharedStorage.

  2. Otherwise, return null.

5. The SharedStorageModifierMethod Interface Group

The SharedStorageSetMethod, SharedStorageAppendMethod, SharedStorageDeleteMethod, SharedStorageClearMethod interfaces correspond to the set(), append(), delete(), clear() modifier methods. They all inherit the base SharedStorageModifierMethod interface.
[Exposed=(Window,SharedStorageWorklet)]
interface SharedStorageModifierMethod {};

[Exposed=(Window, SharedStorageWorklet)]
interface SharedStorageSetMethod : SharedStorageModifierMethod {
  constructor(DOMString key, DOMString value, optional SharedStorageSetMethodOptions options = {});
};

[Exposed=(Window, SharedStorageWorklet)]
interface SharedStorageAppendMethod : SharedStorageModifierMethod {
  constructor(DOMString key, DOMString value, optional SharedStorageModifierMethodOptions options = {});
};

[Exposed=(Window, SharedStorageWorklet)]
interface SharedStorageDeleteMethod : SharedStorageModifierMethod {
  constructor(DOMString key, optional SharedStorageModifierMethodOptions options = {});
};

[Exposed=(Window, SharedStorageWorklet)]
interface SharedStorageClearMethod : SharedStorageModifierMethod {
  constructor(optional SharedStorageModifierMethodOptions options = {});
};

dictionary SharedStorageModifierMethodOptions {
  DOMString withLock;
};

dictionary SharedStorageSetMethodOptions : SharedStorageModifierMethodOptions {
  boolean ignoreIfPresent;
};

A SharedStorageModifierMethod has the following associated fields:

with lock

Null or a string. Initially null.

A SharedStorageSetMethod has the following associated fields:

key

A string. Initially empty.

value

A string. Initially empty.

ignore if present

A boolean. Initially false.

A SharedStorageAppendMethod has the following associated fields:

key

A string. Initially empty.

value

A string. Initially empty.

A SharedStorageDeleteMethod has the following associated fields:

key

A string. Initially empty.

The new SharedStorageSetMethod(key, value, options) constructor steps are:

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

  2. Let context be null.

  3. If globalObject is a Window:

    1. Set context to globalObject’s browsing context.

  4. Else:

    1. Set context to globalObject’s outside settings's target browsing context.

    2. If the result of running check whether addModule is finished for SharedStorage's associated SharedStorageWorkletGlobalScope is false, throw a TypeError.

  5. If context is null, throw a TypeError.

  6. If context’s active window's associated document is not fully active, throw a TypeError.

  7. If key’s length exceeds the maximum length, throw a TypeError.

  8. If value’s length exceeds the maximum length, throw a TypeError.

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

  10. Let databaseMap be the result of running obtain a shared storage bottle map given environment and environment’s origin.

  11. If databaseMap is failure, throw a TypeError.

  12. Set this’s key to key.

  13. Set this’s value to value.

  14. Set this’s ignore if present to options["ignoreIfPresent"].

  15. If options["withLock"] exists:

    1. If options["withLock"] starts with U+002D HYPHEN-MINUS (-), throw a TypeError.

    2. Set this’s with lock to options["withLock"].

The new SharedStorageAppendMethod(key, value, options) constructor steps are:

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

  2. Let context be null.

  3. If globalObject is a Window:

    1. Set context to globalObject’s browsing context.

  4. Else:

    1. Set context to globalObject’s outside settings's target browsing context.

    2. If the result of running check whether addModule is finished for SharedStorage's associated SharedStorageWorkletGlobalScope is false, throw a TypeError.

  5. If context is null, throw a TypeError.

  6. If context’s active window's associated document is not fully active, throw a TypeError.

  7. If key’s length exceeds the maximum length, throw a TypeError.

  8. If value’s length exceeds the maximum length, throw a TypeError.

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

  10. Let databaseMap be the result of running obtain a shared storage bottle map given environment and environment’s origin.

  11. If databaseMap is failure, throw a TypeError.

  12. Set this’s key to key.

  13. Set this’s value to value.

  14. If options["withLock"] exists:

    1. If options["withLock"] starts with U+002D HYPHEN-MINUS (-), throw a TypeError.

    2. Set this’s with lock to options["withLock"].

The new SharedStorageAppendMethod(key, options) constructor steps are:

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

  2. Let context be null.

  3. If globalObject is a Window:

    1. Set context to globalObject’s browsing context.

  4. Else:

    1. Set context to globalObject’s outside settings's target browsing context.

    2. If the result of running check whether addModule is finished for SharedStorage's associated SharedStorageWorkletGlobalScope is false, throw a TypeError.

  5. If context is null, throw a TypeError.

  6. If context’s active window's associated document is not fully active, throw a TypeError.

  7. If key’s length exceeds the maximum length, throw 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 given environment and environment’s origin.

  10. If databaseMap is failure, throw a TypeError.

  11. Set this’s key to key.

  12. If options["withLock"] exists:

    1. If options["withLock"] starts with U+002D HYPHEN-MINUS (-), throw a TypeError.

    2. Set this’s with lock to options["withLock"].

The new SharedStorageClearMethod(options) constructor steps are:

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

  2. Let context be null.

  3. If globalObject is a Window:

    1. Set context to globalObject’s browsing context.

  4. Else:

    1. Set context to globalObject’s outside settings's target browsing context.

    2. If the result of running check whether addModule is finished for SharedStorage's associated SharedStorageWorkletGlobalScope is false, throw a TypeError.

  5. If context is null, throw a TypeError.

  6. If context’s active window's associated document is not fully active, throw 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 given environment and environment’s origin.

  9. If databaseMap is failure, throw a TypeError.

  10. If options["withLock"] exists:

    1. If options["withLock"] starts with U+002D HYPHEN-MINUS (-), throw a TypeError.

    2. Set this’s with lock to options["withLock"].

6. The SharedStorage Interface

The SharedStorage interface is exposed to Window and SharedStorageWorklet.

Methods that allow the setting and/or deleting of data are exposed to both the Window and the SharedStorageWorklet, 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 posting operations to run inside SharedStorageWorkletGlobalScope (i.e. selectURL() and run()), along with the worklet attribute which is used to call addModule(), are 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 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,
                      optional SharedStorageModifierMethodOptions options = {});
  Promise<any> delete(DOMString key, optional SharedStorageModifierMethodOptions options = {});
  Promise<any> clear(optional SharedStorageModifierMethodOptions options = {});
  Promise<any> batchUpdate(sequence<SharedStorageModifierMethod> methods,
                           optional SharedStorageModifierMethodOptions options = {});

  [Exposed=Window]
  Promise<SharedStorageResponse> selectURL(DOMString name,
                               sequence<SharedStorageUrlWithMetadata> urls,
                               optional SharedStorageRunOperationMethodOptions options = {});

  [Exposed=Window]
  Promise<any> run(DOMString name,
                   optional SharedStorageRunOperationMethodOptions options = {});

  [Exposed=Window]
  Promise<SharedStorageWorklet> createWorklet(USVString moduleURL, optional SharedStorageWorkletOptions options = {});

  [Exposed=Window]
  readonly attribute SharedStorageWorklet worklet;

  [Exposed=SharedStorageWorklet]
  Promise<DOMString> get(DOMString key);

  [Exposed=SharedStorageWorklet]
  Promise<unsigned long> length();

  [Exposed=SharedStorageWorklet]
  Promise<double> remainingBudget();

  [Exposed=SharedStorageWorklet]
  async iterable<DOMString, DOMString>;
};

dictionary SharedStoragePrivateAggregationConfig {
  USVString aggregationCoordinatorOrigin;
  USVString contextId;
  [EnforceRange] unsigned long long filteringIdMaxBytes;
};

dictionary SharedStorageRunOperationMethodOptions {
  object data;
  boolean resolveToConfig = false;
  boolean keepAlive = false;
  SharedStoragePrivateAggregationConfig privateAggregationConfig;
  DOMString savedQuery;
};

dictionary SharedStorageWorkletOptions : WorkletOptions {
  USVString dataOrigin = "context-origin";
};

6.1. Run Operation Methods on SharedStorage

The selectURL(name, urls, options) method steps are:
  1. Let sharedStorage be this.

  2. Return sharedStorage.worklet.selectURL(name, urls, options).

The run(name, options) method steps are:
  1. Let sharedStorage be this.

  2. Return sharedStorage.worklet.run(name, options).

6.2. Create a new worklet via SharedStorage

The createWorklet(moduleURL, options) method steps are:
  1. Let sharedStorageWorklet be a new SharedStorageWorklet.

  2. If options contains "dataOrigin", set sharedStorageWorklet’s data origin to options["dataOrigin"].

  3. Let addModulePromise be the result of invoking sharedStorageWorklet.addModule(moduleURL, options).

  4. Let resultPromise be a new promise.

  5. Upon fulfillment of addModulePromise, resolve resultPromise to sharedStorageWorklet.

  6. Upon rejection of addModulePromise, reject resultPromise with a TypeError.

  7. Return resultPromise.

6.3. BatchUpdate Method

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

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

  3. Let context be null.

  4. If globalObject is a Window:

    1. Set context to globalObject’s browsing context.

  5. Else:

    1. Set context to globalObject’s outside settings's target browsing context.

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

  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 given environment and environment’s origin.

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

  11. Let unfinishedUpdatesCount be methods’s size.

  12. Let hasFailure be false.

  13. Let onLockGrantedCallback be an algorithm to perform the following steps:

    1. For each method in methods:

      1. Let methodResultPromise be a new promise.

      2. If method is a SharedStorageSetMethod:

        1. Let key be method’s key.

        2. Let value be method’s value.

        3. Let methodOptions be a new SharedStorageSetMethodOptions.

        4. Set methodOptions["ignoreIfPresent"] to method’s ignore if present.

        5. If method’s with lock is not null, set methodOptions["withLock"] to method’s with lock.

        6. Set methodResultPromise to the result of invoking set(key, value, methodOptions).

      3. Else if method is a SharedStorageAppendMethod:

        1. Let key be method’s key.

        2. Let value be method’s value.

        3. Let methodOptions be a new SharedStorageModifierMethodOptions.

        4. If method’s with lock is not null, set methodOptions["withLock"] to method’s with lock.

        5. Set methodResultPromise to the result of invoking append(key, value, methodOptions).

      4. Else if method is a SharedStorageDeleteMethod:

        1. Let key be method’s key.

        2. Let methodOptions be a new SharedStorageModifierMethodOptions.

        3. If method’s with lock is not null, set methodOptions["withLock"] to method’s with lock.

        4. Set methodResultPromise to the result of invoking delete(key, methodOptions).

      5. Else:

        1. Assert: method is a SharedStorageClearMethod.

        2. Let methodOptions be a new SharedStorageModifierMethodOptions.

        3. If method’s with lock is not null, set methodOptions["withLock"] to method’s with lock.

        4. Set methodResultPromise to the result of invoking clear(methodOptions).

      6. Upon fulfillment of methodResultPromise:

        1. Decrement unfinishedUpdatesCount by 1.

        2. If unfinishedUpdatesCount is 0, run finish a batch update given promise and hasFailure.

      7. Upon rejection of methodResultPromise:

        1. Decrement unfinishedUpdatesCount by 1.

        2. Set hasFailure to true.

        3. If unfinishedUpdatesCount is 0, run finish a batch update given promise and hasFailure.

    2. If unfinishedUpdatesCount is 0, run finish a batch update given promise and hasFailure.

  14. If options["withLock"] exists, run handle callback within a shared storage lock given environment’s origin, options["withLock"], onLockGrantedCallback.

  15. Else, run onLockGrantedCallback.

  16. Return promise.

To finish a batch update, given a promise promise and a boolean hasFailure, perform the following steps:
  1. If hasFailure is true, reject promise with a TypeError.

  2. Else, resolve promise with undefined.

6.4. Setter/Deleter Methods

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

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

  3. Let context be null.

  4. If globalObject is a Window:

    1. Set context to globalObject’s browsing context.

  5. Else:

    1. Set context to globalObject’s outside settings's target browsing context.

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

  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. If key’s length exceeds the maximum length, return a promise rejected with a TypeError.

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

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

  11. Let databaseMap be the result of running obtain a shared storage bottle map given environment and environment’s origin.

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

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

  14. Let realm be the current realm.

  15. Let onLockGrantedCallback be an algorithm to perform the following steps:

    1. 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 and if globalObject is a SharedStorageWorkletGlobalScope:

          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. Let result be the result of running store an entry in the database with queue, databaseMap, environment, key, and value.

      3. If result is false and if globalObject is a SharedStorageWorkletGlobalScope:

        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 undefined.

  16. If options["withLock"] exists:

    1. If options["withLock"] starts with U+002D HYPHEN-MINUS (-), return a promise rejected with a TypeError.

    2. Run handle callback within a shared storage lock given environment’s origin, options["withLock"], onLockGrantedCallback.

  17. Else, run onLockGrantedCallback.

  18. Return promise.

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

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

  3. Let context be null.

  4. If globalObject is a Window:

    1. Set context to globalObject’s browsing context.

  5. Else:

    1. Set context to globalObject’s outside settings's target browsing context.

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

  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. If key’s length exceeds the maximum length, return a promise rejected with a TypeError.

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

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

  11. Let databaseMap be the result of running obtain a shared storage bottle map given environment and environment’s origin.

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

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

  14. Let realm be the current realm.

  15. Let onLockGrantedCallback be an algorithm to perform the following steps:

    1. 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. If globalObject is a Window:

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

        2. Else:

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

        3. 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. Let result be the result of running store an entry in the database with queue, databaseMap, environment, key, and value.

      5. If result is false and if globalObject is a SharedStorageWorkletGlobalScope:

        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.

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

  16. If options["withLock"] exists:

    1. If options["withLock"] starts with U+002D HYPHEN-MINUS (-), return a promise rejected with a TypeError.

    2. Run handle callback within a shared storage lock given environment’s origin, options["withLock"], onLockGrantedCallback.

  17. Else, run onLockGrantedCallback.

  18. Return promise.

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

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

  3. Let context be null.

  4. If globalObject is a Window:

    1. Set context to globalObject’s browsing context.

  5. Else:

    1. Set context to globalObject’s outside settings's target browsing context.

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

  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. If key’s length exceeds the maximum length, return a promise rejected with a TypeError.

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

  10. Let databaseMap be the result of running obtain a shared storage bottle map given environment and environment’s origin.

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

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

  13. Let realm be the current realm.

  14. Let onLockGrantedCallback be an algorithm to perform the following steps:

    1. Enqueue the following steps on queue:

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

      2. If result is false and if globalObject is a SharedStorageWorkletGlobalScope:

        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. Queue a global task on the DOM manipulation task source, given realm’s global object, to resolve promise with undefined.

  15. If options["withLock"] exists:

    1. If options["withLock"] starts with U+002D HYPHEN-MINUS (-), return a promise rejected with a TypeError.

    2. Run handle callback within a shared storage lock given environment’s origin, options["withLock"], onLockGrantedCallback.

  16. Else, run onLockGrantedCallback.

  17. Return promise.

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

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

  3. Let context be null.

  4. If globalObject is a Window:

    1. Set context to globalObject’s browsing context.

  5. Else:

    1. Set context to globalObject’s outside settings's target browsing context.

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

  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 given environment and environment’s origin.

  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. Let onLockGrantedCallback be an algorithm to perform the following steps:

    1. Enqueue the following steps on queue:

      1. Let result be the result of running clear all entries in the database with queue and databaseMap.

      2. If result is false and if globalObject is a SharedStorageWorkletGlobalScope:

        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. Queue a global task on the DOM manipulation task source, given realm’s global object, to resolve promise with undefined.

  14. If options["withLock"] exists:

    1. If options["withLock"] starts with U+002D HYPHEN-MINUS (-), return a promise rejected with a TypeError.

    2. Run handle callback within a shared storage lock given environment’s origin, options["withLock"], onLockGrantedCallback.

  15. Else, run onLockGrantedCallback.

  16. Return promise.

6.5. 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 SharedStorage'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 SharedStorage'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 realm be the current realm.

  9. Let databaseMap be the result of running obtain a shared storage bottle map given environment and realm’s settings object's origin.

  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. 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 SharedStorage's associated SharedStorageWorkletGlobalScope is false, return a promise rejected with a TypeError.

  3. Let context be SharedStorage'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 realm be the current realm.

  8. Let databaseMap be the result of running obtain a shared storage bottle map given environment and realm’s settings object's origin.

  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. 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 SharedStorage's associated SharedStorageWorkletGlobalScope is false, return a promise rejected with a TypeError.

  3. Let context be SharedStorage'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 realm be the current realm.

  8. Let allowedInOpaqueOriginContext be false.

  9. If the result of running determine whether shared storage is allowed by context given environment, realm’s settings object's origin, and allowedInOpaqueOriginContext is false, return a promise rejected with a TypeError.

  10. If the result of running check if user preference setting allows access to shared storage given environment and realm’s settings object's origin is false, return a promise rejected with a TypeError.

  11. Let site be the result of running obtain a site with realm’s settings object's origin.

  12. Assert: site is not an opaque origin.

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

  14. Enqueue the following steps on queue:

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

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

  15. Return promise.

6.6. Iteration

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

Each SharedStorage 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 SharedStorage async iterator iterator are:
  1. Let promise be a new promise.

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

  3. Let context be SharedStorage'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 realm be the current realm.

  8. Let databaseMap be the result of running obtain a shared storage bottle map given environment and realm’s settings object's origin.

  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. 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 SharedStorage'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 dequeuing 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.

7. 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.

8. HTML Monkey Patches

8.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.

8.2. HTML Algorithm Modifications

8.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.

8.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.

9. Fetch Monkey Patches

9.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;
};

9.2. Fetch Algorithm Modifications

9.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.

9.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.

9.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 steps

  1. If request’s destination is "sharedstorageworklet":

    1. Let dataOriginValue be the result of getting `Sec-Shared-Storage-Data-Origin` from request’s header list.

    2. If dataOriginValue is not null, then:

      1. Let dataOriginUrl be the result of running a URL parser on dataOriginValue.

      2. Assert that dataOriginUrl is not failure.

      3. Assert that request’s origin is not "client".

      4. Assert that request’s origin and request’s URL's origin are not same origin.

      5. Let allowed be true.

      6. If dataOriginUrl’s origin and request’s URL's origin are same origin:

        1. Let responseHeaders be internalResponse’s header list.

        2. Let allowed be the result of running get a structured field value algorithm given `Shared-Storage-Cross-Origin-Worklet-Allowed`, "item", and responseHeaders as input.

      7. If allowed is false, then return a network error.

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

9.3. Shared Storage HTTP Headers

9.3.1. `Sec-Shared-Storage-Data-Origin` Request Header

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

The `Sec-Shared-Storage-Data-Origin` request header whose value is a string.

When the `Sec-Shared-Storage-Data-Origin` is sent during the fetch a worklet/module worker script graph algorithm, its value is set to the serialized origin that owns the worklet’s shared storage data.

9.3.2. `Shared-Storage-Cross-Origin-Worklet-Allowed` Response Header

This specification defines a Shared-Storage-Cross-Origin-Worklet-Allowed HTTP response header.

The `Shared-Storage-Cross-Origin-Worklet-Allowed` response header is a Structured Header whose value must be a Boolean.

When a response has `Shared-Storage-Cross-Origin-Worklet-Allowed` with value true, the worklet script’s server has given permission for a cross-origin site to create a worklet using shared storage data from the worklet script’s origin.

9.3.3. `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.

9.3.4. `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.

9.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. Let allowedInOpaqueOriginContext be true.

  4. If the result of running determine whether shared storage is allowed by context given window, request’s current URL's origin, and allowedInOpaqueOriginContext is false, return false.

  5. If the result of running check if user preference setting allows access to shared storage given window and request’s current URL's origin is false, return false.

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: 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. Let methods be an empty list.

  11. Let batchWithLock be null.

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

    1. If item is an Inner List, continue.

    2. Assert: item is an Bare Item.

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

    4. If methodOrOptionsString is failure, continue.

    5. Switch on methodOrOptionsString:

      If methodOrOptionsString is "clear":
      Perform the following steps:
      1. Let options be a new SharedStorageModifierMethodOptions.

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

      3. If withLock is not null and withLock does not start with U+002D HYPHEN-MINUS (-), set options["withLock"] to withLock.

      4. Let method be new SharedStorageClearMethod(options).

      5. If an exception was thrown, continue.

      6. Append method to methods.

      7. Continue.

      If methodOrOptionsString 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 null, continue.

      3. Let options be a new SharedStorageModifierMethodOptions.

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

      5. If withLock is not null and withLock does not start with U+002D HYPHEN-MINUS (-), set options["withLock"] to withLock.

      6. Let method be new SharedStorageDeleteMethod(key, options).

      7. If an exception was thrown, continue.

      8. Append method to methods.

      9. Continue.

      If methodOrOptionsString 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 null, continue.

      5. Let options be a new SharedStorageModifierMethodOptions.

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

      7. If withLock is not null and withLock does not start with U+002D HYPHEN-MINUS (-), set options["withLock"] to withLock.

      8. Let method be new SharedStorageAppendMethod(key, value, options).

      9. If an exception was thrown, continue.

      10. Append method to methods.

      11. Continue.

      If methodOrOptionsString 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. Let withLock be the result of running obtain a string-like parameter value with parameters and "with_lock".

      8. If withLock is not null and withLock does not start with U+002D HYPHEN-MINUS (-), set options["withLock"] to withLock.

      9. Let method be new SharedStorageSetMethod(key, value, options).

      10. If an exception was thrown, continue.

      11. Append method to methods.

      12. Continue.

      If methodOrOptionsString is "options":
      Perform the following steps:
      1. Set batchWithLock to the result of running obtain a string-like parameter value with parameters and "with_lock".

      2. Continue.

      If methodOrOptionsString is anything else:
      Continue.
  13. Let batchOptions be a new SharedStorageModifierMethodOptions.

  14. If batchWithLock is not null and batchWithLock does not start with U+002D HYPHEN-MINUS (-), set batchOptions["withLock"] to batchWithLock.

  15. Run sharedStorage.batchUpdate(methods, batchOptions).

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: 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].

10. Web Locks Integration

10.1. User Agent Associated State

A shared storage lock managers map is a map of origins to lock managers. It is initially empty.

A user agent has an associated shared storage lock managers map.

Note: Similar to its data partitioning, the shared storage has its own lock management scope, independent of the Storage Buckets API. These web locks will not interact with web locks created from Window or Worker via the existing, legacy Web Locks API.

10.2. SharedStorageWorkletNavigator interface

[
  Exposed=SharedStorageWorklet
] interface SharedStorageWorkletNavigator {};

10.3. Web Locks IDLs Monkey Patches

Include the NavigatorLocks mixin in SharedStorageWorkletNavigator (i.e., let SharedStorageWorkletNavigator contain a LockManager instance):

SharedStorageWorkletNavigator includes NavigatorLocks;

The LockManager and Lock are additionally exposed to SharedStorageWorklet:

[SecureContext, Exposed=(Window,Worker,SharedStorageWorklet)]
interface LockManager {};
[SecureContext, Exposed=(Window,Worker,SharedStorageWorklet)]
interface Lock {};

10.4. Monkey Patch for lock manager’s description

Add the following sentence at the end of the paragraph that defines lock manager: "Additionally, each user agent includes one shared storage lock managers map for Web Locks API’s integration with the Shared Storage API."

10.5. Monkey Patch for the "obtain a lock manager" algorithm

The obtain a lock manager algorithm should be prepended with the following steps:

  1. If current realm's global object is a SharedStorageWorkletGlobalScope:

    1. Let workletDataOrigin be environment’s origin.

    2. Return shared storage lock managers map[workletDataOrigin].

10.6. "Handle callback within a shared storage lock" algorithm

To handle callback within a shared storage lock given an origin workletDataOrigin, a string name, a task callback, perform the following steps:
  1. Let environment be this's relevant settings object.

  2. Let lockManager be shared storage lock managers map[workletDataOrigin].

  3. Let promise be a new promise.

  4. Let defaultOptions be a new LockOptions.

  5. Request a lock with promise, the current agent, environment’s id, lockManager, callback, name, defaultOptions["mode"], defaultOptions["ifAvailable"], defaultOptions["steal"], defaultOptions["signal"].

Note: With the default LockOptions, callback will eventually be called when the lock is granted (i.e., the lock request won’t fail).

11. 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 extra permission layer to selectURL(). For each of these, the default allowlist is *.

12. Clear Site Data Integration

Add details for Clear Site Data integration.

13. 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 specific 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-level traversable navigation and/or the fenced frame calls the reportEvent() API.

An embedder is also able to send aggregatable reports via the Private Aggregation API, 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/
[DOM]
Anne van Kesteren. DOM Standard. Living Standard. URL: https://dom.spec.whatwg.org/
[ECMASCRIPT]
ECMAScript Language Specification. URL: https://tc39.es/ecma262/multipage/
[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/
[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/
[MEDIA-SOURCE-2]
Jean-Yves Avenard; Mark Watson. Media Source Extensions™. URL: https://w3c.github.io/media-source/
[MIMESNIFF]
Gordon P. Hemsley. MIME Sniffing Standard. Living Standard. URL: https://mimesniff.spec.whatwg.org/
[PERMISSIONS-POLICY-1]
Ian Clelland. Permissions Policy. URL: https://w3c.github.io/webappsec-permissions-policy/
[PRIVATE-AGGREGATION-API]
Private Aggregation API. Unofficial Proposal Draft. URL: https://patcg-individual-drafts.github.io/private-aggregation-api/
[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://httpwg.org/specs/rfc8941.html
[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/
[WEB-LOCKS]
Joshua Bell; Kagami Rosylight. Web Locks API. URL: https://w3c.github.io/web-locks/
[WEBIDL]
Edgar Chen; Timothy Gu. Web IDL Standard. Living Standard. URL: https://webidl.spec.whatwg.org/

Informative References

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

IDL Index

typedef (USVString or FencedFrameConfig) SharedStorageResponse;

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

callback RunFunctionForSharedStorageSelectURLOperation = Promise<unsigned long>(sequence<USVString> urls, optional any data);

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

  readonly attribute SharedStorage sharedStorage;
  readonly attribute PrivateAggregation privateAggregation;

  Promise<sequence<StorageInterestGroup>> interestGroups();

  readonly attribute SharedStorageWorkletNavigator navigator;
};

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

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

[Exposed=(Window,SharedStorageWorklet)]
interface SharedStorageModifierMethod {};

[Exposed=(Window, SharedStorageWorklet)]
interface SharedStorageSetMethod : SharedStorageModifierMethod {
  constructor(DOMString key, DOMString value, optional SharedStorageSetMethodOptions options = {});
};

[Exposed=(Window, SharedStorageWorklet)]
interface SharedStorageAppendMethod : SharedStorageModifierMethod {
  constructor(DOMString key, DOMString value, optional SharedStorageModifierMethodOptions options = {});
};

[Exposed=(Window, SharedStorageWorklet)]
interface SharedStorageDeleteMethod : SharedStorageModifierMethod {
  constructor(DOMString key, optional SharedStorageModifierMethodOptions options = {});
};

[Exposed=(Window, SharedStorageWorklet)]
interface SharedStorageClearMethod : SharedStorageModifierMethod {
  constructor(optional SharedStorageModifierMethodOptions options = {});
};

dictionary SharedStorageModifierMethodOptions {
  DOMString withLock;
};

dictionary SharedStorageSetMethodOptions : SharedStorageModifierMethodOptions {
  boolean ignoreIfPresent;
};

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

  [Exposed=Window]
  Promise<SharedStorageResponse> selectURL(DOMString name,
                               sequence<SharedStorageUrlWithMetadata> urls,
                               optional SharedStorageRunOperationMethodOptions options = {});

  [Exposed=Window]
  Promise<any> run(DOMString name,
                   optional SharedStorageRunOperationMethodOptions options = {});

  [Exposed=Window]
  Promise<SharedStorageWorklet> createWorklet(USVString moduleURL, optional SharedStorageWorkletOptions options = {});

  [Exposed=Window]
  readonly attribute SharedStorageWorklet worklet;

  [Exposed=SharedStorageWorklet]
  Promise<DOMString> get(DOMString key);

  [Exposed=SharedStorageWorklet]
  Promise<unsigned long> length();

  [Exposed=SharedStorageWorklet]
  Promise<double> remainingBudget();

  [Exposed=SharedStorageWorklet]
  async iterable<DOMString, DOMString>;
};

dictionary SharedStoragePrivateAggregationConfig {
  USVString aggregationCoordinatorOrigin;
  USVString contextId;
  [EnforceRange] unsigned long long filteringIdMaxBytes;
};

dictionary SharedStorageRunOperationMethodOptions {
  object data;
  boolean resolveToConfig = false;
  boolean keepAlive = false;
  SharedStoragePrivateAggregationConfig privateAggregationConfig;
  DOMString savedQuery;
};

dictionary SharedStorageWorkletOptions : WorkletOptions {
  USVString dataOrigin = "context-origin";
};

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

HTMLIFrameElement includes HTMLSharedStorageWritableElementUtils;
HTMLImageElement includes HTMLSharedStorageWritableElementUtils;

partial dictionary RequestInit {
  boolean sharedStorageWritable;
};

[
  Exposed=SharedStorageWorklet
] interface SharedStorageWorkletNavigator {};

SharedStorageWorkletNavigator includes NavigatorLocks;

[SecureContext, Exposed=(Window,Worker,SharedStorageWorklet)]
interface LockManager {};

[SecureContext, Exposed=(Window,Worker,SharedStorageWorklet)]
interface Lock {};

Issues Index

Consider adding an early return here if the permissions policy check is made first.
The "name" and "operationCtor" cannot be missing here given WebIDL. Should just check for default/empty values. [Issue #151]
reportingMetadata should be a dictionary. [Issue #141]
Store reportingUrlMap inside a fenced frame reporter class associated with fencedFrameConfigStruct. Both of these still need to be added to the draft [Fenced-Frame]. [Issue #144]
Move the definition of pending shared storage budget debit to fenced frame config instance in the draft [Fenced-Frame] specification. [Issue #148]
Need to find a better way to specify timing of the navigation budget charging. [Issue #138]
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.