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 ouput gates included in the standard.
a.example
randomly assigns users to groups in a way that is consistent cross-site.
Inside an a.example
iframe:
function generateSeed() { …} await window. sharedStorage. worklet. addModule( 'experiment.js' ); // Only write a cross-site seed to a.example’s storage if there isn’t one yet. window. sharedStorage. set( 'seed' , generateSeed(), { ignoreIfPresent: true }); let fencedFrameConfig= await window. sharedStorage. selectURL( 'select-url-for-experiment' , [ { url: "blob:https://a.example/123…" , reportingMetadata: { "click" : "https://report.example/1..." }}, { url: "blob:https://b.example/abc…" , reportingMetadata: { "click" : "https://report.example/a..." }}, { url: "blob:https://c.example/789…" } ], { data: { name: 'experimentA' } } ); // Assumes that the fenced frame 'my-fenced-frame' has already been attached. document. getElementById( 'my-fenced-frame' ). config= fencedFrameConfig;
inside the experiment.js
worklet script:
class SelectURLOperation{ hash( experimentName, seed) { …} async run( urls, data) { const seed= await this . sharedStorage. get( 'seed' ); return hash( data. name, seed) % urls. length; } } register( 'select-url-for-experiment' , SelectURLOperation);
2. The SharedStorageWorklet
Interface
The SharedStorageWorklet
object allows developers to supply module scripts to process Shared Storage data and then output the result through one or more of the output gates. Currently there are two output gates, the private aggregation output gate and the URL-selection
output gate.
typedef (USVString or FencedFrameConfig );
SharedStorageResponse enum {
SharedStorageDataOrigin ,
"context-origin" };
"script-origin"
[Exposed =(Window )]interface :
SharedStorageWorklet Worklet {Promise <SharedStorageResponse >selectURL (DOMString ,
name FrozenArray <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 SharedStorageDataOrigin
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().
-
Using values available in environment and origin as needed, perform an implementation-defined algorithm to return either true or false.
-
If environment is not a secure context, then return false.
-
If allowedInOpaqueOriginContext is false and environment’s origin is an opaque origin, then return false.
-
If origin is an opaque origin, then return false.
-
Let globalObject be the current realm's global object.
-
Assert: globalObject is a
Window
or aSharedStorageWorkletGlobalScope
. -
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. -
If the result of running obtain a site with origin is not enrolled, then return false.
-
Return true.
-
For each method under § 4.1.1 Window Setter/Deleter Methods, environment is the current context, and origin is environment’s origin.
-
For creating a worklet, environment is the environment settings object associated with the
Window
that created the worklet, and origin is the module script url’s origin. -
For running operations on a worklet (from a
Window
), and for each method under § 4.3.1 Worklet Setter/Deleter Methods (fromSharedStorageWorkletGlobalScope
), environment is the environment settings object associated with theWindow
that created the worklet, and origin is the worklet’s global scopes[0]'s realm's settings object's origin. -
For § 7.4 Shared Storage Fetch-Related Algorithms, environment is the request’s window, and origin is the request’s current URL's origin.
-
For § 7.4 Shared Storage Fetch-Related Algorithms, for
createWorklet()
called with a cross-origin worklet script using the dataOrigin option with value"script-origin"
(which would result in a worklet where has cross-origin data origin is true), and forselectURL()
andrun()
that operate on a worklet where has cross-origin data origin is true, allowedInOpaqueOriginContext is true. For other methods, allowedInOpaqueOriginContext is false.
SharedStorageWorklet
worklet and a URL moduleURLRecord, run the following steps:
-
If worklet’s addModule initiated is true, return "DisallowedDueToNonPreferenceError".
-
Set worklet’s addModule initiated to true.
-
Let workletDataOrigin be the current settings object's origin.
-
If worklet’s data origin is
"script-origin"
, set workletDataOrigin to moduleURLRecord’s origin. -
Let hasCrossOriginDataOrigin be false.
-
If workletDataOrigin and the current settings object's origin are not same origin, then set hasCrossOriginDataOrigin to true.
-
Let allowedInOpaqueOriginContext be hasCrossOriginDataOrigin.
-
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".
-
Set worklet’s has cross-origin data origin to hasCrossOriginDataOrigin.
-
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".
-
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
SharedStorageWorklet
worklet, DOMString
operationName, list of SharedStorageUrlWithMetadata
s urlList, and SharedStorageRunOperationMethodOptions
options:
-
Let promise be a new promise.
-
Let window be worklet’s relevant settings object.
-
If window’s browsing context is null, then return a promise rejected with a
TypeError
. -
If window’s associated document is not fully active, return a promise rejected with a
TypeError
. -
Return promise, and immediately obtain a worklet agent given window and run the rest of these steps in that agent:
-
Assert: worklet’s global scopes's size is 1.
-
Assert: worklet’s module map is not empty.
-
Let globalScope be this's global scopes[0].
-
Let operationMap be globalScope’s operation map.
-
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. -
Let operation be operationMap[operationName].
-
Assert: operation’s associated realm is this's relevant realm.
-
Let argumentsList be the list « urlList ».
-
Let index be the result of invoking operation with argumentsList.
-
If an exception was thrown, 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 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. -
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
, 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.
-
Queue a global task on the DOM manipulation task source, given window, to resolve promise with index.
-
selectURL(name, urls, options)
method steps are:
-
Let resultPromise be a new promise.
-
If this's addModule initiated is false, then return a promise rejected with a
TypeError
. -
Let window be this's relevant settings object.
-
Let context be window’s browsing context.
-
If context is null, then return a promise rejected with a
TypeError
. -
Let document be context’s active document.
-
Assert: this's global scopes's size is 1.
-
Let globalScope be this's global scopes[0].
-
Let workletDataOrigin be globalScope’s realm's settings object's origin.
-
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
. -
If this's global scopes is empty, then return a promise rejected with a
TypeError
.Note: This can happen if either
selectURL()
orselectURL()
is called beforeaddModule()
. -
If the result of running check whether addModule is finished for globalScope is false, return a promise rejected with a
TypeError
. -
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. -
Let urlList be an empty list.
-
For each urlWithMetadata in urls:
-
If urlWithMetadata has no field "
url
", return a promise rejected with aTypeError
. -
Otherwise, let urlString be urlWithMetadata["
url
"]. -
Let serializedUrl be the result of running get the canonical URL string if valid with urlString.
-
If serializedUrl is undefined, return a promise rejected with a
TypeError
. -
Otherwise, append serializedUrl to urlList.
-
If urlWithMetadata has field "
reportingMetadata
":-
Let reportingMetadata be urlWithMetadata["
reportingMetadata
"]. -
If the result of running validate reporting metadata with reportingMetadata is false, reject resultPromise with a
TypeError
and abort these steps.
-
-
-
Let fencedFrameConfigMapping be window’s associated document's node navigable's traversable navigable's fenced frame config mapping.
-
Let pendingConfig be a new fenced frame config.
-
Let urn be the result of running store a pending config on fencedFrameConfigMapping with pendingConfig.
-
If urn is failure, then return a promise rejected with a
TypeError
. -
Let environment be window’s relevant settings object.
-
Let allowedInOpaqueOriginContext be this's has cross-origin data origin.
-
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
. -
If the result of running check if user preference setting allows access to shared storage given environment and workletDataOrigin is false:
-
If this's has cross-origin data origin is false, return a promise rejected with a
TypeError
.
-
-
If options["
resolveToConfig
"] is true, resolve resultPromise with pendingConfig. -
Otherwise, resolve resultPromise with urn.
-
Let indexPromise be the result of running get the select-url result index, given this, name, urlList, and options.
-
Upon fulfillment of indexPromise with resultIndex, perform the following steps:
-
Let site be the result of running obtain a site with document’s origin.
-
Let remainingBudget be the result of running determine remaining navigation budget with environment and site.
-
Let pendingBits be the logarithm base 2 of urlList’s size.
-
If pendingBits is greather than remainingBudget, set resultIndex to default index.
-
Let finalConfig be a new fenced frame config.
-
Set finalConfig’s mapped url to urlList[resultIndex].
-
Set finalConfig’s a "pending shared storage budget debit" field to pendingBits.
-
Finalize a pending config on fencedFrameConfigMapping with urn and finalConfig.
-
Let resultURLWithMetadata be urls[resultIndex].
-
If resultURLWithMetadata has field "
reportingMetadata
", run register reporting metadata with resultURLWithMetadata["reportingMetadata
"]. -
If options["
keepAlive
"] is false, run terminate a worklet global scope with this.
-
-
Upon rejection of indexPromise, perform the following steps:
-
Let finalConfig be a new fenced frame config.
-
Set finalConfig’s mapped url to urlList[default index].
-
Finalize a pending config on fencedFrameConfigMapping with urn and finalConfig.
-
If options["
keepAlive
"] is false, run terminate a worklet global scope with this.
-
-
Return resultPromise.
run(name, options)
method steps are:
-
Let promise be a new promise.
-
If this's addModule initiated is false, then return a promise rejected with a
TypeError
. -
Let window be this's relevant settings object.
-
If this's global scopes is empty, then return a promise rejected with a
TypeError
. -
Assert: this's global scopes's size is 1.
-
Let globalScope be this's global scopes[0].
-
If the result of running check whether addModule is finished for globalScope is false, return a promise rejected with a
TypeError
. -
Let workletDataOrigin be globalScope’s realm's settings object's origin.
-
Let allowedInOpaqueOriginContext be this's has cross-origin data origin.
-
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
. -
If the result of running check if user preference setting allows access to shared storage given window and workletDataOrigin is false:
-
If this's has cross-origin data origin is false, reject promise with a
TypeError
. -
Else, resolve promise with undefined.
-
Return promise.
-
-
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 aSharedStorageWorkletGlobalScope
can have access to cross-site data, which shouldn’t be leaked viarun()
(via its success/error result).-
Queue a global task on the DOM manipulation task source, given window, to resolve promise with undefined.
-
If this's module map is not empty:
-
Let operationMap be this's
SharedStorageWorkletGlobalScope
's operation map. -
If operationMap contains name:
-
Let operation be operationMap[name].
-
Assert: operation’s associated realm is this's relevant realm.
-
If options contains data:
-
Otherwise, invoke operation without any arguments list.
-
-
-
If options["
keepAlive
"] is false:-
Wait for operation to finish running, if applicable.
-
Run terminate a worklet global scope with this.
-
-
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:
-
Let settingsObject be a new environment settings object whose algorithms are defined as follows:
......
The origin
-
Let workletGlobalScope be the global object of realmExecutionContext’s Realm component.
-
If workletGlobalScope is not
SharedStorageWorkletGlobalScope
, return origin. -
Assert that worklet is a
SharedStorageWorklet
. -
If worklet’s data origin is
"context-origin"
, return outsideSettings’s origin. -
Let pendingAddedModules be a clone of worklet’s added modules list.
-
Let moduleURL be pendingAddedModules[0].
-
Return moduleURL’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:
-
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, ...":
-
If fetchClient’s global object is
SharedStorageWorkletGlobalScope
:-
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. -
If fetchClient’s origin and settingsObject’s origin are not same origin:
-
Let dataOriginValue be the serialization of settingsObject’s origin.
-
Assert that dataOriginValue is not null.
-
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 WorkletSharedStorage
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
The following step will be added to the HTTP fetch steps, before checking the redirect status (i.e. "6. If internalResponse’s status is a redirect status, ..."):-
If request’s destination is "sharedstorageworklet":
-
Let dataOriginValue be the result of getting
"Sec-Shared-Storage-Data-Origin"
from request’s header list. -
If dataOriginValue is not null, then:
-
Let dataOriginUrl be the result of running a URL parser on dataOriginValue.
-
Assert that dataOriginUrl is not failure.
-
Assert that request’s origin and request’s URL's origin are not same origin.
-
Assert that dataOriginUrl’s origin and request’s URL's origin are same origin.
-
Let responseHeaders be internalResponse’s header list.
-
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. -
If allowed is false, then return a network error.
-
-
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 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":
-
If this is of type
SharedStorageWorklet
:-
Let addModuleAllowedResult be the result of running check if addModule is allowed and update state given this and moduleURLRecord.
-
If addModuleAllowedResult is "DisallowedDueToNonPreferenceError":
-
Return a promise rejected with a
TypeError
.
-
-
Else if addModuleAllowedResult is "DisallowedDueToPreferenceError":
-
If this’s has cross-origin data origin is false, then return a promise rejected with a
TypeError
.
-
-
Else:
-
Assert: addModuleAllowedResult is "Allowed".
-
-
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()
.
The penultimate step (i.e. the final indented step), currently "If pendingTasks is 0, then resolve promise.", should be updated to:
-
If pendingTasks is 0, perform the following steps:
-
If workletGlobalScope has an associated boolean addModule success, set workletGlobalScope’s addModule success to true.
-
Resolve promise.
-
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 WorkletSharedStorage ; };
sharedStorage
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 functions.
Each SharedStorageWorkletGlobalScope
also has an associated WorkletSharedStorage
instance, with the sharedStorage getter algorithm as described below.
2.4. SharedStorageWorkletGlobalScope
algorithms
register(name, operationCtor)
method steps are:
-
If name is missing or empty, throw a
TypeError
. -
Let operationMap be this
SharedStorageWorkletGlobalScope
's operation map. -
If operationMap contains an entry with key name, throw a
TypeError
. -
If operationCtor is missing, throw a
TypeError
. -
Let operationClassInstance be the result of constructing operationCtor, with no arguments.
-
Let runFunction be Get(operationClassInstance, "
run
"). Rethrow any exceptions. -
Let runIDLFunction be the result of converting runFunction to a Web IDL
RunFunctionForSharedStorageSelectURLOperation
instance. -
Set the value of operationMap[name] to runIDLFunction.
The "name" and "operationCtor" cannot be missing here given WebIDL. Should just check for default/empty values. [Issue #151]
sharedStorage
getter steps are:
-
If this's addModule success is true, return this's
sharedStorage
. -
Otherwise, throw a
TypeError
.
-
Return the value of addModule success.
2.5. 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 eventType
s 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
object
s, 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
.
object
reportingMetadata, run the following steps:
-
If reportingMetadata is not a dictionary, return false.
-
If reportingMetadata is empty, return true.
-
For each eventType → urlString of reportingMetadata, if the result of running get the canonical URL string if valid with urlString is undefined, return false.
-
Return true.
-
Let url be the result of running a URL parser on urlString.
-
If url is not a valid URL, return undefined.
-
Otherwise, return the result of running a URL serializer on url.
object
reportingMetadata and a fenced frame config fencedFrameConfigStruct, run the following steps:
-
If reportingMetadata is empty, return.
-
Assert: reportingMetadata is a dictionary.
-
For each eventType → urlString of reportingMetadata:
-
Let url be the result of running a URL parser on urlString.
-
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.6. Entropy Budgets
Because bits of entropy can leak via selectURL()
, the user agent will need to maintain budgets to limit these leaks.
2.6.1. Navigation Entropy Budget
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-calling site navigation entropy allowance.
A calling site for selectURL()
is a site.
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 calling site. This allowance is defined by the user agent and is calling site-agnostic.
A user agent will define a fixed predetermined duration navigation budget lifetime.
An navigation budget epoch is any interval of time whose duration is the navigation budget lifetime.
To keep track of how this navigation entropy allowance is used, the user agent uses a shared storage navigation budget table, which is a map of calling sites to navigation entropy ledgers.
An navigation entropy ledger is a list of bit debits.
A bit debit is a struct 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 calling site, along with the current time as a timestamp, together as a bit debit in the shared storage navigation budget table.
A calling site's remaining navigation budget is the navigation entropy allowance minus any bit debits whose timestamps are within the current navigation budget epoch.
selectURL()
's argument "urls
" is its input URL list.
When a calling site has insufficient remaining navigation budget, selectURL()
will return a SharedStorageResponse
(i.e. either a FencedFrameConfig
or a urn uuid) for the url
in the SharedStorageUrlWithMetadata
at the default index in its input URL list.
The default index for a call to selectURL()
is implementation-defined in such a way that it is independent from the result of the registered operation class’s "run
" method.
Methods can’t have state attached to them. Many definitions in this section needs improving. [Issue #147]
In this case, whenever the registered operation class’s "run
" method encounters an error, or whenever there is insufficient remaining navigation budget, the "run
" method would return 0, and hence selectURL()
would return a SharedStorageResponse
for the first url
in its input URL list.
In this case, whenever the registered operation class’s "run
" method encounters an error, or whenever there is insufficient remaining navigation budget, selectURL()
would return a SharedStorageResponse
for the last url
in its input URL list.
-
Assert: site is not an opaque origin.
-
Let maxBits be the user agent's navigation entropy allowance.
-
If the user agent's shared storage navigation budget table does not contain site, then return maxBits.
-
Otherwise, let ledger be user agent's shared storage navigation budget table[site].
-
Let debitSum be 0.
-
For each item bitDebit in ledger, do the following steps:
-
Let debit be bitDebit’s bits.
-
If the result of running check whether a bit debit is expired with environment and bitDebit is false, then increment debitSum by debit.
-
-
Return maxBits − debitSum.
-
Let epochLength be the user agent's navigation budget lifetime.
-
Let currentTime be environment’s current wall time.
-
Let threshold be currentTime − epochLength.
-
If bitDebit’s timestamp is less than threshold, return true.
-
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]
The boolean shared storage navigation budget charged have not yet been added to fenced frame config instance in the draft [Fenced-Frame] specification. Some form of them will be added, although their names are subject to bikeshedding. Fix the names when they are added. [Issue #149]
Document
sourceDocument, run the following steps:
-
If navigable is not a top-level traversable, return.
-
Let currentNavigable be sourceDocument’s node navigable.
-
While currentNavigable is not null:
-
Let site be the result of running obtain a site with currentNavigable’s active document's origin.
-
Let instance be currentNavigable’s node document's browsing context's fenced frame config instance.
-
Set currentNavigable to currentNavigable’s parent.
-
If instance is null or site is an opaque origin, then continue.
-
Let pendingBits be instance’s pending shared storage budget debit.
-
If pendingBits is not greater than 0, or if instance’s shared storage navigation budget charged is true, then continue.
-
Let ledger be user agent's shared storage navigation budget table[site].
-
Let bitDebit be a new bit debit.
-
Set bitDebit’s bits to pendingBits.
-
Let currentTime be the current wall time.
-
Set bitDebit’s timestamp to currentTime.
-
Append bitDebit to ledger.
-
Set instance’s shared storage navigation budget charged to true.
-
2.6.2. Reporting Entropy Budget
Likewise, each time a call to reportEvent()
from a fenced frame originating via selectURL()
whose destination
contains "shared-storage-select-url
" and whose eventType
is triggered, there is a leak of up to logarithm base 2 of the number of main input URLs entropy bits. The user agent will need to set a per-page load reporting entropy allowance to restrict the information leaked, with page load referring to a top-level traversable's (i.e. primary main frame’s) lifecycle.
A reporting entropy allowance is a maximum allowance of entropy bits that are permitted to leak via reportEvent()
during a given page load. This allowance is defined by the user agent.
Each top-level traversable will have a new double
shared storage reporting budget associated to it which will be initialized with the value of user agent's reporting entropy allowance upon top-level traversable's creation.
When reportEvent()
is called with a destination
containing "shared-storage-select-url
", it will be necessary to charge shared storage reporting budget as below.
Move this to reportEvent()
in [Fenced-Frame]. [Issue #150]
Document
sourceDocument, run the following steps:
-
Let debitSum be 0.
-
Let currentNavigable be sourceDocument’s node navigable.
-
While currentNavigable is not null:
-
Let instance be currentNavigable’s initiator fenced frame config instance.
-
Set currentNavigable to currentNavigable’s parent.
-
If instance is null, then continue.
-
Let pendingBits be instance’s pending shared storage budget debit.
-
If pendingBits is greater than 0 and if instance’s shared storage reporting budget charged is false, increment debitSum by pendingBits.
-
-
Return debitSum.
The boolean shared storage reporting budget charged have not yet been added to fenced frame config instance in the draft [Fenced-Frame] specification. Some form of them will be added, although their names are subject to bikeshedding. Fix the names when they are added. [Issue #149]
Document
sourceDocument, run the following steps:
-
Let toCharge be the result of running determine reporting budget to charge with sourceDocument.
-
Let currentNavigable be sourceDocument’s node navigable.
-
Let topNode be the result of running get the top-level traversable for currentNavigable.
-
If topNode’s shared storage reporting budget is less than toCharge, return false.
-
While currentNavigable is not null:
-
Let instance be currentNavigable’s node document's browsing context's fenced frame config instance.
-
If instance is not null and if instance’s pending shared storage budget debit is greater than 0, set instance’s shared storage reporting budget charged to true.
-
Set currentNavigable to currentNavigable’s parent.
-
-
Decrement topNode’s shared storage reporting budget by toCharge.
-
Return true.
A user agent may wish to set a timer to periodically purge expired bit debits from all navigation entropy ledgers, as the expired bit debits will no longer be needed.
-
For each origin → ledger of user agent's shared storage navigation budget table:
-
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.
-
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 5
4
*
2
16
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 * 2
20
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.
-
Let allowedInOpaqueOriginContext be false.
-
If the result of running determine whether shared storage is allowed by context given environment, origin, and allowedInOpaqueOriginContext is false, then return failure.
-
If the result of running check if user preference setting allows access to shared storage given environment and origin is false, then return failure.
-
If shed[origin] does not exist, then set shed[origin] to the result of running create a shared storage shelf with type "
shared
". -
Return shed[origin].
-
Let shelf be a new storage shelf.
-
Set shelf’s bucket map["
default
"] to the result of running create a shared storage bucket. -
Return shelf.
A shared storage bucket is a storage bucket in one of a shared storage shed's shelves.
-
Let endpoint be the storage endpoint with storage identifier "
sharedStorage
". -
Let bucket be a new shared storage bucket.
-
Set bucket’s bottle map["
sharedStorage
"] to a new storage bottle whose quota is endpoint’s quota. -
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
".
-
Let shed be the user agent's shared storage shed.
-
Let shelf be the result of running obtain a shared storage shelf with shed, environment, and origin.
-
If shelf is failure, then return failure.
-
Let bucket be shelf’s bucket map["
default
"]. -
Let bottle be bucket’s bottle map["
sharedStorage
"]. -
Let proxyMap be a new storage proxy map whose backing map is bottle’s map.
-
Append proxyMap to bottle’s proxy map reference set.
-
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.
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
-
Let valueStruct be a new value struct.
-
Set valueStruct’s value to value.
-
Let currentTime be the environment’s current wall time.
-
Set valueStruct’s last updated to currentTime.
-
Set databaseMap[key] to valueStruct.
-
If an exception was thrown, then return false.
Note: Errors with storage proxy map databaseMap’s methods are possible depending on its implementation.
-
Otherwise, return true.
-
If databaseMap does not contain key, return undefined.
-
Let valueStruct be the result of running Get on databaseMap with key.
-
If an exception was thrown, then return failure.
Note: Errors with storage proxy map databaseMap’s methods are possible depending on its implementation.
-
If the result of running determine whether an entry is expired with environment and valueStruct is true, return undefined.
-
Return valueStruct’s value.
-
Remove databaseMap[key].
-
If an exception was thrown, then return false.
Note: Errors with storage proxy map databaseMap’s methods are possible depending on its implementation.
-
Return true.
-
Run Clear on databaseMap.
-
If an exception was thrown, then return false.
Note: Errors with storage proxy map databaseMap’s methods are possible depending on its implementation.
-
Return true.
-
Let values be the result of running getting the values on databaseMap.
-
If an exception was thrown, then return failure.
Note: Errors with storage proxy map databaseMap’s methods are possible depending on its implementation.
-
Return values.
-
Let size be databaseMap’s size.
-
If an exception was thrown, then return failure.
Note: Errors with storage proxy map databaseMap’s members are possible depending on its implementation.
-
Return size.
-
For each key key in databaseMap:
-
Let valueStruct be the result of running Get on databaseMap with key.
-
If an exception was thrown, then return false.
-
If the result of running determine whether an entry is expired with environment and valueStruct is true, Remove databaseMap[key].
-
If an exception was thrown, then return false.
-
-
Return true.
To determine whether an entry is expired, given an environment settings object environment and a value struct valueStruct, run the following steps:
-
Let lastUpdated be valueStruct’s last updated.
-
Let lifetime be user agent's default entry lifetime.
-
Let expiration be the sum of lastUpdated and lifetime.
-
Let currentTime be the environment’s current wall time.
-
If expiration is less than or equal to currentTime, return true.
-
Otherwise, return false.
4. The SharedStorage
Interface
The SharedStorage
interface is the base for derived interfaces WindowSharedStorage
and WorkletSharedStorage
, which are exposed to the Window
and the SharedStorageWorklet
, respectively.
Methods that allow the setting and/or deleting of data are exposed to both the Window
and the SharedStorageWorklet
and hence are declared in the base SharedStorage
interface, although their implementations may vary depending on their environment. This makes it possible to modify the data in Shared Storage from multiple contexts.
Meanwhile, methods for posting operations to run inside SharedStorageWorkletGlobalScope
(i.e. selectURL()
and run()
), along with the worklet
attribute which is used to call addModule()
, are declared in WindowSharedStorage
and exposed to the Window
only, as these are the means by which the Window
interacts with the SharedStorageWorklet
.
On the other hand, methods for getting data from the shared storage database are declared in WorkletSharedStorage
and exposed to the SharedStorageWorklet
only, in order to carefully control the flow of data read from the database.
[Exposed =(Window ,SharedStorageWorklet )]interface {
SharedStorage Promise <any >(
set DOMString ,
key DOMString ,
value optional SharedStorageSetMethodOptions = {});
options Promise <any >(
append DOMString ,
key DOMString );
value Promise <any >(
delete DOMString );
key Promise <any >(); };
clear dictionary {
SharedStorageSetMethodOptions boolean =
ignoreIfPresent false ; };
4.1. The WindowSharedStorage
interface
The WindowSharedStorage
interface is as follows.
[Exposed =(Window )]interface :
WindowSharedStorage SharedStorage {Promise <SharedStorageResponse >selectURL (DOMString ,
name FrozenArray <SharedStorageUrlWithMetadata >,
urls optional SharedStorageRunOperationMethodOptions = {});
options Promise <any >run (DOMString ,
name optional SharedStorageRunOperationMethodOptions = {});
options Promise <SharedStorageWorklet >createWorklet (USVString ,
moduleURL optional SharedStorageWorkletOptions = {});
options readonly attribute SharedStorageWorklet ; };
worklet dictionary {
SharedStorageRunOperationMethodOptions object ;
data boolean =
resolveToConfig false ;boolean =
keepAlive false ; };dictionary :
SharedStorageWorkletOptions WorkletOptions {SharedStorageDataOrigin = "context-origin"; };
dataOrigin
4.1.1. Window Setter/Deleter Methods
set(key, value, options)
method step is:
-
Return the result of running set a key-value pair on
WindowSharedStorage
, key, value, and options.
append(key, value)
method step is:
-
Return the result of running append a key-value pair on
WindowSharedStorage
, key, and value.
delete(key)
method step is:
-
Return the result of running delete a key on
WindowSharedStorage
and key.
clear()
method step is:
-
Return the result of running clear all keys on
WindowSharedStorage
.
WindowSharedStorage
sharedStorage, string key, string value, and SharedStorageSetMethodOptions
options, perform the following steps:
-
Let promise be a new promise.
-
If key’s length exceeds the maximum length, return a promise rejected with a
TypeError
. -
If value’s length exceeds the maximum length, return a promise rejected with a
TypeError
. -
Let context be sharedStorage’s
Window
's browsing context. -
If context is null, return a promise rejected with a
TypeError
. -
Let environment be context’s active window's relevant settings object.
-
Let databaseMap be the result of running obtain a shared storage bottle map given environment and environment’s origin.
-
If databaseMap is failure, then return a promise rejected with a
TypeError
. -
Let queue be context’s associated database's shared storage database queue.
-
Let realm be the current realm.
-
Enqueue the following steps on queue:
-
If options["
ignoreIfPresent
"] is true and the result of running retrieve an entry from the database with queue, databaseMap, environment, and key is not undefined:-
Queue a global task on the DOM manipulation task source, given realm’s global object, to resolve promise with undefined.
-
Abort these steps.
-
-
Run store an entry in the database with queue, databaseMap, environment, key, and value.
-
Queue a global task on the DOM manipulation task source, given realm’s global object, to resolve promise with undefined.
-
-
Return promise.
WindowSharedStorage
sharedStorage, string key, and string value, perform the following steps:
-
Let promise be a new promise.
-
If key’s length exceeds the maximum length, return a promise rejected with a
TypeError
. -
If value’s length exceeds the maximum length, return a promise rejected with a
TypeError
. -
Let context be sharedStorage’s
Window
's browsing context. -
If context is null, return a promise rejected with a
TypeError
. -
Let environment be context’s active window's relevant settings object.
-
Let databaseMap be the result of running obtain a shared storage bottle map given environment and environment’s origin.
-
If databaseMap is failure, then return a promise rejected with a
TypeError
. -
Let queue be context’s associated database's shared storage database queue.
-
Let realm be the current realm.
-
Enqueue the following steps on queue:
-
Let currentValue be the result of running retrieve an entry from the database with queue, databaseMap, environment, and key.
-
If currentValue is failure:
-
Queue a global task on the DOM manipulation task source, given realm’s global object, to resolve promise with undefined.
-
Abort these steps.
-
-
If currentValue is not undefined:
-
Let list be a new list.
-
Append currentValue to list.
-
Append value to list.
-
Set value to the result of running concatenate on list.
-
-
Run store an entry in the database with queue, databaseMap, environment, key, and value.
-
Queue a global task on the DOM manipulation task source, given realm’s global object, to resolve promise with undefined.
-
-
Return promise.
WindowSharedStorage
sharedStorage and string key, perform the following steps:
-
Let promise be a new promise.
-
If key’s length exceeds the maximum length, return a promise rejected with a
TypeError
. -
Let context be sharedStorage’s
Window
's browsing context. -
If context is null, return a promise rejected with a
TypeError
. -
Let environment be context’s active window's relevant settings object.
-
Let databaseMap be the result of running obtain a shared storage bottle map given environment and environment’s origin.
-
If databaseMap is failure, then return a promise rejected with a
TypeError
. -
Let queue be context’s associated database's shared storage database queue.
-
Let realm be the current realm.
-
Enqueue the following steps on queue:
-
Run delete an entry from the database with queue, environment, and key.
-
Queue a global task on the DOM manipulation task source, given realm’s global object, to resolve promise with undefined.
-
-
Return promise.
WindowSharedStorage
sharedStorage, perform the following steps:
-
Let promise be a new promise.
-
Let context be sharedStorage’s
Window
's browsing context. -
If context is null, return a promise rejected with a
TypeError
. -
Let environment be context’s active window's relevant settings object.
-
Let databaseMap be the result of running obtain a shared storage bottle map given environment and environment’s origin
-
If databaseMap is failure, then return a promise rejected with a
TypeError
. -
Let queue be context’s associated database's shared storage database queue.
-
Let realm be the current realm.
-
Enqueue the following steps on queue:
-
Run clear all entries in the database with queue and environment.
-
Queue a global task on the DOM manipulation task source, given realm’s global object, to resolve promise with undefined.
-
-
Return promise.
4.1.2. Run Operation Methods on WindowSharedStorage
selectURL(name, urls, options)
method steps are:
run(name, options)
method steps are:
4.1.3. Create a new worklet via WindowSharedStorage
createWorklet(moduleURL, options)
method steps are:
-
Let sharedStorageWorklet be a new
SharedStorageWorklet
. -
If options contains dataOrigin, set sharedStorageWorklet’s data origin to options[dataOrigin].
-
Let addModulePromise be the result of invoking sharedStorageWorklet.
addModule
(moduleURL, options). -
Let resultPromise be a new promise.
-
Upon fulfillment of addModulePromise, resolve resultPromise to sharedStorageWorklet.
-
Upon rejection of addModulePromise, reject resultPromise with a
TypeError
. -
Return resultPromise.
4.2. Extension to the Window
interface
Each Window
object has an associated WindowSharedStorage
instance sharedStorage
, which is created alongside the .Window
if Shared Storage is enabled, with the getter below.
partial interface Window { [SecureContext ]readonly attribute WindowSharedStorage ?; };
sharedStorage
sharedStorage
getter steps are:
-
If this is fully active, return this's
sharedStorage
. -
Otherwise, return null.
4.3. The WorkletSharedStorage
interface
The WorkletSharedStorage
interface is as follows.
[Exposed =(SharedStorageWorklet )]interface :
WorkletSharedStorage SharedStorage {Promise <DOMString >get (DOMString );
key Promise <unsigned long >length ();Promise <double >remainingBudget ();async iterable <DOMString ,DOMString >; };
4.3.1. Worklet Setter/Deleter Methods
set(key, value, options)
method steps are:
-
Let promise be a new promise.
-
If the result of running check whether addModule is finished for
WorkletSharedStorage
's associatedSharedStorageWorkletGlobalScope
is false, return a promise rejected with aTypeError
. -
If key’s length exceeds the maximum length, return a promise rejected with a
TypeError
. -
If value’s length exceeds the maximum length, return a promise rejected with a
TypeError
. -
Let context be
WorkletSharedStorage
'sSharedStorageWorkletGlobalScope
's outside settings's target browsing context. -
If context is null, return a promise rejected with a
TypeError
. -
If context’s active window's associated document is not fully active, return a promise rejected with a
TypeError
. -
Let environment be context’s active window's relevant settings object.
-
Let realm be the current realm.
-
Let databaseMap be the result of running obtain a shared storage bottle map given environment and realm’s settings object's origin.
-
If databaseMap is failure, then return a promise rejected with a
TypeError
. -
Let queue be context’s associated database's shared storage database queue.
-
Enqueue the following steps on queue:
-
If options["
ignoreIfPresent
"] is true:-
Let currentValue be the result of running retrieve an entry from the database with queue, databaseMap, environment, and key.
-
If currentValue is failure:
-
Queue a global task on the DOM manipulation task source, given realm’s global object, to reject promise with a
TypeError
. -
Abort these steps.
-
-
If currentValue is not undefined:
-
Queue a global task on the DOM manipulation task source, given realm’s global object, to resolve promise with undefined.
-
Abort these steps.
-
-
-
If the result of running store an entry in the database with queue, databaseMap, environment, key, and value is false, queue a global task on the DOM manipulation task source, given realm’s global object, to reject promise with a
TypeError
. -
Otherwise, queue a global task on the DOM manipulation task source, given realm’s global object, to resolve promise with undefined.
-
-
Return promise.
append(key, value)
method steps are:
-
Let promise be a new promise.
-
If the result of running check whether addModule is finished for
WorkletSharedStorage
's associatedSharedStorageWorkletGlobalScope
is false, return a promise rejected with aTypeError
. -
If key’s length exceeds the maximum length, return a promise rejected with a
TypeError
. -
If value’s length exceeds the maximum length, return a promise rejected with a
TypeError
. -
Let context be
WorkletSharedStorage
'sSharedStorageWorkletGlobalScope
's outside settings's target browsing context. -
If context is null, return a promise rejected with a
TypeError
. -
If context’s active window's associated document is not fully active, return a promise rejected with a
TypeError
. -
Let environment be context’s active window's relevant settings object.
-
Let realm be the current realm.
-
Let databaseMap be the result of running obtain a shared storage bottle map given environment and realm’s settings object's origin.
-
If databaseMap is failure, then return a promise rejected with a
TypeError
. -
Let queue be context’s associated database's shared storage database queue.
-
Enqueue the following steps on queue:
-
Let currentValue be the result of running retrieve an entry from the database with queue, databaseMap, environment, and key.
-
If currentValue is failure:
-
Queue a global task on the DOM manipulation task source, given realm’s global object, to reject promise with a
TypeError
. -
Abort these steps.
-
-
If currentValue is not undefined:
-
Let list be a new list.
-
Append currentValue to list.
-
Append value to list.
-
Set value to the result of running concatenate on list.
-
-
If the result of running store an entry in the database with queue, databaseMap, environment, key, and value is false, queue a global task on the DOM manipulation task source, given realm’s global object, to reject promise with a
TypeError
. -
Otherwise, queue a global task on the DOM manipulation task source, given realm’s global object, to resolve promise with undefined.
-
-
Return promise.
delete(key)
method steps are:
-
Let promise be a new promise.
-
If the result of running check whether addModule is finished for
WorkletSharedStorage
's associatedSharedStorageWorkletGlobalScope
is false, return a promise rejected with aTypeError
. -
If key’s length exceeds the maximum length, return a promise rejected with a
TypeError
. -
Let context be
WorkletSharedStorage
'sSharedStorageWorkletGlobalScope
's outside settings's target browsing context. -
If context is null, return a promise rejected with a
TypeError
. -
If context’s active window's associated document is not fully active, return a promise rejected with a
TypeError
. -
Let environment be context’s active window's relevant settings object.
-
Let realm be the current realm.
-
Let databaseMap be the result of running obtain a shared storage bottle map given environment and realm’s settings object's origin.
-
If databaseMap is failure, then return a promise rejected with a
TypeError
. -
Let queue be context’s associated database's shared storage database queue.
-
Enqueue the following steps on queue:
-
If the result of running delete an entry from the database with queue, environment, and key is false, queue a global task on the DOM manipulation task source, given realm’s global object, to reject promise with a
TypeError
. -
Otherwise, queue a global task on the DOM manipulation task source, given realm’s global object, to resolve promise with undefined.
-
-
Return promise.
clear()
method steps are:
-
Let promise be a new promise.
-
If the result of running check whether addModule is finished for
WorkletSharedStorage
's associatedSharedStorageWorkletGlobalScope
is false, return a promise rejected with aTypeError
. -
Let context be
WorkletSharedStorage
'sSharedStorageWorkletGlobalScope
's outside settings's target browsing context. -
If context is null, return a promise rejected with a
TypeError
. -
If context’s active window's associated document is not fully active, return a promise rejected with a
TypeError
. -
Let environment be context’s active window's relevant settings object.
-
Let realm be the current realm.
-
Let databaseMap be the result of running obtain a shared storage bottle map given environment and realm’s settings object's origin.
-
If databaseMap is failure, then return a promise rejected with a
TypeError
. -
Let queue be context’s associated database's shared storage database queue.
-
Enqueue the following steps on queue:
-
If the result of running clear all entries in the database with queue and environment is false, queue a global task on the DOM manipulation task source, given realm’s global object, to reject promise with a
TypeError
. -
Otherwise, queue a global task on the DOM manipulation task source, given realm’s global object, to resolve promise with undefined.
-
-
Return promise.
4.3.2. Getter Methods
get(key)
method steps are:
-
Let promise be a new promise.
-
If the result of running check whether addModule is finished for
WorkletSharedStorage
's associatedSharedStorageWorkletGlobalScope
is false, return a promise rejected with aTypeError
. -
If key’s length exceeds the maximum length, return a promise rejected with a
TypeError
. -
Let context be
WorkletSharedStorage
'sSharedStorageWorkletGlobalScope
's outside settings's target browsing context. -
If context is null, return a promise rejected with a
TypeError
. -
If context’s active window's associated document is not fully active, return a promise rejected with a
TypeError
. -
Let environment be context’s active window's relevant settings object.
-
Let realm be the current realm.
-
Let databaseMap be the result of running obtain a shared storage bottle map given environment and realm’s settings object's origin.
-
If databaseMap is failure, then return a promise rejected with a
TypeError
. -
Let queue be context’s associated database's shared storage database queue.
-
Enqueue the following steps on queue:
-
Let value be the result of running retrieve an entry from the database with queue, databaseMap, environment, and key.
-
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
. -
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.
-
Otherwise, queue a global task on the DOM manipulation task source, given realm’s global object, to resolve promise with value.
-
-
Return promise.
length()
method steps are:
-
Let promise be a new promise.
-
If the result of running check whether addModule is finished for
WorkletSharedStorage
's associatedSharedStorageWorkletGlobalScope
is false, return a promise rejected with aTypeError
. -
Let context be
WorkletSharedStorage
'sSharedStorageWorkletGlobalScope
's outside settings's target browsing context. -
If context is null, return a promise rejected with a
TypeError
. -
If context’s active window's associated document is not fully active, return a promise rejected with a
TypeError
. -
Let environment be context’s active window's relevant settings object.
-
Let realm be the current realm.
-
Let databaseMap be the result of running obtain a shared storage bottle map given environment and realm’s settings object's origin.
-
If databaseMap is failure, then return a promise rejected with a
TypeError
. -
Let queue be context’s associated database's shared storage database queue.
-
Enqueue the following steps on queue:
-
Let numEntries be the result of running count entries in the database with queue and environment.
-
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
. -
Otherwise, queue a global task on the DOM manipulation task source, given realm’s global object, to resolve promise with numEntries.
-
-
Return promise.
remainingBudget()
method steps are:
-
Let promise be a new promise.
-
If the result of running check whether addModule is finished for
WorkletSharedStorage
's associatedSharedStorageWorkletGlobalScope
is false, return a promise rejected with aTypeError
. -
Let context be
WorkletSharedStorage
'sSharedStorageWorkletGlobalScope
's outside settings's target browsing context. -
If context is null, return a promise rejected with a
TypeError
. -
If context’s active window's associated document is not fully active, return a promise rejected with a
TypeError
. -
Let environment be context’s active window's relevant settings object.
-
Let realm be the current realm.
-
Let allowedInOpaqueOriginContext be false.
-
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
. -
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
. -
Let site be the result of running obtain a site with realm’s settings object's origin.
-
Assert: site is not an opaque origin.
-
Let queue be context’s associated database's shared storage database queue.
-
Enqueue the following steps on queue:
-
Let remainingBudget be the result of running determine remaining navigation budget with site.
-
Resolve queue a global task on the DOM manipulation task source, given realm’s global object, to resolve promise with remainingBudget.
-
-
Return promise.
4.3.3. Iteration
Each WorkletSharedStorage
async iterator instance has a queue pending entries of entries, initially empty.
Each WorkletSharedStorage
async iterator instance also has a boolean
error, initially false.
The asynchronous iterator initialization steps and get the next iteration result algorithms defined below correspond to those referred to as the asynchronous iterator initialization steps and get the next iteration result algorithms in the Web IDL Standard.
WorkletSharedStorage
async iterator iterator are:
-
Let promise be a new promise.
-
If the result of running check whether addModule is finished for
WorkletSharedStorage
's associatedSharedStorageWorkletGlobalScope
is false, return a promise rejected with aTypeError
. -
Let context be
WorkletSharedStorage
'sSharedStorageWorkletGlobalScope
's outside settings's target browsing context. -
If context is null, return a promise rejected with a
TypeError
. -
If context’s active window's associated document is not fully active, return a promise rejected with a
TypeError
. -
Let environment be context’s active window's relevant settings object.
-
Let realm be the current realm.
-
Let databaseMap be the result of running obtain a shared storage bottle map given environment and realm’s settings object's origin.
-
If databaseMap is failure, then return a promise rejected with a
TypeError
. -
Let queue be context’s associated database's shared storage database queue.
-
Enqueue the following steps on queue:
-
Let entries be the result of running retrieve all entries from the database with queue and environment.
-
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
. -
Otherwise, queue a global task on the DOM manipulation task source, given realm’s global object, to resolve promise with entries.
-
-
Upon fulfillment of promise, run the following:
-
Let promiseEntries be the value of promise.
-
For each entry entry in promiseEntries, enqueue entry in iterator’s pending entries.
-
-
Upon rejection of promise, set iterator’s error to true.
WorkletSharedStorage
's async iterator iterator, run the following steps:
-
Let promise be a new promise.
-
-
If iterator’s error is true, return a promise rejected with a
TypeError
. -
If iterator’s pending entries is empty:
-
Create an object doneObject.
-
Queue a global task on the DOM manipulation task source, given realm’s global object, to resolve promise with doneObject.
-
Abort these steps.
-
-
Otherwise, let entry be the result of dequeueing from iterator’s pending entries.
-
Queue a global task on the DOM manipulation task source, given realm’s global object, to resolve promise with entry.
-
-
Return promise.
5. Triggering Operations Via HTTP Response Header
While setter and deleter operations (e.g.. set()
, append()
, delete()
, clear()
) can be initiated via the above APIs for Window
or SharedStorageWorkletGlobalScope
, setter/deleter operations can alternatively be triggered via HTTP response header.
This will require monkey patches to the HTML and Fetch specifications.
6. HTML Monkey Patches
6.1. sharedStorageWritable
& sharedstoragewritable
Attributes
Define the following interface mixin and include it in the IDL interfaces for HTMLIFrameElement
and HTMLImageElement
:
interface mixin { [
HTMLSharedStorageWritableElementUtils CEReactions ,SecureContext ]attribute boolean ; };
sharedStorageWritable HTMLIFrameElement includes HTMLSharedStorageWritableElementUtils ;HTMLImageElement includes HTMLSharedStorageWritableElementUtils ;
Add the following boolean content attributes:
iframe
-
sharedstoragewritable
img
-
sharedstoragewritable
The IDL attribute sharedStorageWritable
must reflect the respective content attribute of the same name.
6.2. HTML Algorithm Modifications
6.2.1. Modification to Update the image data Algorithm
After the step
Set request’s priority to the current state...
add the step
-
If the element has the
sharedstoragewritable
attribute present, set request’s shared storage writable to true.
6.2.2. Modification to Create navigation params by fetching Algorithm
After the step
Let request be a new request, with ...
add the step
-
If navigable’s container is an
iframe
element, and if it has asharedstoragewritable
content attribute, then set request’s shared storage writable to true.
7. Fetch Monkey Patches
7.1. sharedStorageWritable
Key
A request has an associated boolean shared storage writable. Unless stated otherwise it is false.
The RequestInit
dictionary contains a sharedStorageWritable
key:
partial dictionary RequestInit {boolean ; };
sharedStorageWritable
7.2. Fetch Algorithm Modifications
7.2.1. Modification to the Request Constructor Algorithm
Before the step
Set this’s request to request.
add the step
-
If init["
sharedStorageWritable
"] exists, then set request’s shared storage writable to it.
7.2.2. Modification to HTTP network or cache fetch Algorithm
Before the step
Modify httpRequest’s header list per HTTP. ...
add the step
-
Append or modify a Sec-Shared-Storage-Writable request header for httpRequest.
7.2.3. Modification to HTTP fetch Algorithm
Before the step
If internalResponse’s status is a redirect status: ...
add the step
-
Handle a Shared-Storage-Write response, given response internalResponse and request request as input.
7.3. Shared Storage HTTP Headers
7.3.1. `Sec-Shared-Storage-Writable
` Request Header
This specification defines a Sec-Shared-Storage-Writable HTTP request header.
The `Sec-Shared-Storage-Writable
` request header is a Structured Header whose value must be a Boolean.
When a request sets `Sec-Shared-Storage-Writable
` to true its response will be able to write to shared storage.
7.3.2. `Shared-Storage-Write
` Response Header
This specification defines a Shared-Storage-Write HTTP response header.
The `Shared-Storage-Write
` response header is a Structured Header whose value must be a List. The following list members are defined. Tokens and Strings holding the same sequence of characters are considered equivalent. Byte Sequences representing UTF-8 encoded bytes are also allowed, in order to provide functionality for writing and deleting non-ASCII Unicode keys and values via HTTP headers.
Unknown list members, including types that are neither strings nor Byte Sequences, are skipped, and the rest of the list is processed as if they weren’t present. Members are also skipped if they’re missing required parameters or those parameters have unexpected types.
-
set
-
Required parameters:
-
key
(Token, String, or Byte Sequence) -
value
(Token, String, or Byte Sequence)
-
-
Optional parameter:
-
ignore_if_present
(Boolean)
-
-
If the parameter value of
ignore_if_present
is null or false, or if the parameter value ofkey
does not already exist in the shared storage database for the responding server’s origin,set
writes the entry consisting of the parameter values forkey
andvalue
, as the entry's key and entry's value struct's value respectively, in the shared storage database for the responding server’s origin. -
If the parameter value of
ignore_if_present
is true and the parameter value ofkey
already exists in the shared storage database for the responding server’s origin, thenset
is a no-op.
-
-
append
-
Required parameters:
-
key
(Token, String, or Byte Sequence) -
value
(Token, String, or Byte Sequence)
-
-
If the parameter value of
key
already exists in the shared storage database for the responding server’s origin, thenappend
updates the key's entry's value struct's value by appending the parameter value forvalue
to it. -
If the parameter value of
key
does not already exist in the shared storage database for the responding server’s origin,append
is equivalent toset
withignore_if_present
false, i.e.append
writes the entry consisting of the parameter values forkey
andvalue
, as the entry's key and entry's value struct's value respectively, in the shared storage database for the responding server’s origin.
-
-
delete
-
Required parameter:
-
key
(Token, String, or Byte Sequence)
-
-
delete
erases any entry in the shared storage database for the responding server’s origin for which the key is equal to the parameter value ofkey
.
-
-
clear
-
clear
erases all entries in the shared storage database for the responding server’s origin.
-
Note: We allow Byte Sequences in order to accommodate Unicode keys and values. Any Byte Sequence will be assumed to be UTF-8 encoded and will fail to parse otherwise.
Add examples.
7.4. Shared Storage Fetch-Related Algorithms
-
Let window to request’s window.
-
If window is not an environment settings object whose global object is a
Window
, return false. -
Let allowedInOpaqueOriginContext be true.
-
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.
-
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.
-
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.
-
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. -
Otherwise, set a structured field value (`
Sec-Shared-Storage-Writable
`, true) in request’s header list.
-
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. -
If sharedStorageWritable is null, or sharedStorageWritable is not a Boolean, or the value of sharedStorageWritable is false, return.
-
Let window to request’s window.
-
Assert: window is an environment settings object whose global object is a
Window
. -
Let sharedStorage be window’s global object's
sharedStorage
. -
If sharedStorage is null, then return.
-
Let list be response’s header list.
-
Let operationsToParse be the result of running get a structured field value algorithm given `
Shared-Storage-Write
`, "list
", and list as input. -
If operationsToParse is null or empty, then return.
-
For each tuple (item, parameters) in operationsToParse, perform the following steps:
-
If item is an Inner List, continue.
-
Let operationString be the result of running get the string value for item.
-
If operationString is failure, continue.
-
Switch on operationString:
- If operationString is "
clear
": -
Perform the following steps:
-
Run clear all keys on sharedStorage.
-
Continue.
-
- If operationString is "
delete
": -
Perform the following steps:
-
Let key be the result of running obtain a string-like parameter value with parameters and "
key
". -
If key is not null, run delete a key on sharedStorage with key.
-
Continue.
-
- If operationString is "
append
": -
Perform the following steps:
-
Let key be the result of running obtain a string-like parameter value with parameters and "
key
". -
If key is null, continue.
-
Let value be the result of running obtain a string-like parameter value with parameters and "
value
". -
If value is not null, run append a key-value pair on sharedStorage with key and value.
-
Continue.
-
- If operationString is "
set
": -
Perform the following steps:
-
Let key be the result of running obtain a string-like parameter value with parameters and "
key
". -
If key is null, continue.
-
Let value be the result of running obtain a string-like parameter value with parameters and "
value
". -
If value is null, continue.
-
Let options be a new
SharedStorageSetMethodOptions
. -
If the result of running obtain a boolean parameter value with parameters and "
ignore_if_present
" is true, set options["ignoreIfPresent
"] to true.. -
Run set a key-value pair on sharedStorage with key, value, and options.
-
Continue.
-
- If operationString is anything else:
- Continue.
- If operationString is "
-
-
If the item is a String, Token, or Byte Sequence, return true.
-
Otherwise, return false.
-
If the result of running check if string-like on item is false, return failure.
-
Switch on the type of item:
- If item is a Token:
- If item is a String:
-
Perform the following steps:
-
Assert: item is an ASCII string.
-
Return item.
-
- If item is a Byte Sequence:
-
Perform the following steps:
-
Let fromUTF8 be the result of running UTF-8 decode on item.
-
If fromUTF8 is an error, return null.
-
Return fromUTF8.
-
-
If parameters does not contain paramKey, return null.
-
If the result of check if string-like for parameters[paramKey] is false, return null.
-
Return the result of running get the string value for parameters[paramKey].
8. Permissions Policy Integration
This specification defines a policy-controlled feature identified by the string "shared-storage," along with a second policy-controlled feature identified by "shared-storage-select-url".
"shared-storage" gates access to Shared Storage in general, whereas "shared-storage-select-url" adds an exra permission layer to selectURL()
. For each of these, the default allowlist is *.
9. Clear Site Data Integration
Add details for Clear Site Data integration.10. Privacy Considerations
The Shared Storage API attempts to provide the ability to use cross-site data for a range of use cases in a way that better protects user privacy than the use of third-party cookies. Shared Storage’s main privacy safeguard is that read access of the data stored in its storage may only occur within an embedder’s SharedStorageWorklet
. Well-defined limits restrict output of data from the SharedStorageWorklet
to a minimum.
In particular, an embedder can select a URL from a short list of URLs based on data in their shared storage and then display the result in a fenced frame. The embedder will not be able to know which URL was chosen except through specifc mechanisms that will be better-mitigated in the longer term. Currently, a few bits of entropy can leak each time that the user clicks on the fenced frame to initiate a top-level traversable navigation and/or the fenced frame calls the reportEvent()
API.
An embedder is also able to send aggregatable reports through the Private Aggregation Service, which adds noise in order to achieve differential privacy, uses a time delay to send reports, imposes limits on the number of reports sent, and processes the reports into aggregate data so that individual privacy is protected.