1. Introduction
This section is not normative.In order to prevent cross-site user tracking, browsers are partitioning all forms of storage by top-frame site; see Client-Side Storage Partitioning. But, there are many legitimate use cases currently relying on unpartitioned storage.
This document introduces a new storage API that is intentionally not partitioned by top-frame site (though still partitioned by context origin), in order to serve a number of the use cases needing unpartitioned storage. To limit cross-site reidentification of users, data in Shared Storage may only be read in a restricted environment, called a worklet, and any output from the worklet is in the form of a fenced frame or a private aggregation report. Over time, there may be additional ouput gates included in the standard.
a.example
randomly assigns users to groups in a way that is consistent cross-site.
Inside an a.example
iframe:
function generateSeed() { …} await window. sharedStorage. worklet. addModule( 'experiment.js' ); // Only write a cross-site seed to a.example’s storage if there isn’t one yet. window. sharedStorage. set( 'seed' , generateSeed(), { ignoreIfPresent: true }); let fencedFrameConfig= await window. sharedStorage. selectURL( 'select-url-for-experiment' , [ { url: "blob:https://a.example/123…" , reportingMetadata: { "click" : "https://report.example/1..." }}, { url: "blob:https://b.example/abc…" , reportingMetadata: { "click" : "https://report.example/a..." }}, { url: "blob:https://c.example/789…" } ], { data: { name: 'experimentA' } } ); // Assumes that the fenced frame 'my-fenced-frame' has already been attached. document. getElementById( 'my-fenced-frame' ). config= fencedFrameConfig;
inside the experiment.js
worklet script:
class SelectURLOperation{ hash( experimentName, seed) { …} async run( data, urls) { const seed= await this . sharedStorage. get( 'seed' ); return hash( data. name, seed) % urls. length; } } register( 'select-url-for-experiment' , SelectURLOperation);
2. The SharedStorageWorklet
Interface
The SharedStorageWorklet
object allows developers to supply module scripts to process Shared Storage data and then output the result through one or more of the output gates. Currently there are two output gates, the private aggregation output gate and the URL-selection
output gate.
[Exposed =(Window )]interface :
SharedStorageWorklet Worklet { };
Each SharedStorageWorklet
has an associated boolean module added, 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 module added boolean makes it possible to enforce this restriction.
When addModule()
is called for a worklet, it will run check if module added and update status, and if the result is true, abort the remaining steps in the addModule()
call, as detailed in the § 2.1 Monkey Patch for Worklets.
SharedStorageWorklet
worklet, run the following steps:
-
If worklet’s module added is true, return true.
-
Otherwise, set
Worklet
's module added to true. -
Return false.
2.1. Monkey Patch for Worklets
This specification will make some modifications to the Worklet standard to accommodate the needs of Shared Storage.
In particular, the addModule()
method steps for Worklet
will need to be prepended with the following step:
-
If
Worklet
has an associated boolean module added, and the result of running check if module added and update status onWorklet
is true, return a promise rejected with aTypeError
.
Add additional monkey patch pieces for out-of-process worklets.
2.2. The SharedStorageWorkletGlobalScope
The SharedStorageWorklet
's worklet global scope type is SharedStorageWorkletGlobalScope
.
[Exposed =SharedStorageWorklet ,Global =SharedStorageWorklet ]interface :
SharedStorageWorkletGlobalScope WorkletGlobalScope {undefined register (DOMString ,
name SharedStorageOperationConstructor ); };
operationCtor callback =
SharedStorageOperationConstructor SharedStorageOperation (optional SharedStorageRunOperationMethodOptions ); [
options Exposed =SharedStorageWorklet ]interface { };
SharedStorageOperation dictionary {
SharedStorageRunOperationMethodOptions object ;
data boolean =
resolveToConfig false ;boolean =
keepAlive false ; };
Each SharedStorageWorkletGlobalScope
has an associated environment settings object outside settings, which is the associated SharedStorageWorklet
's relevant settings object.
The SharedStorageWorkletGlobalScope
's module map's module scripts should each define and register
one or more SharedStorageOperation
s.
Each SharedStorageWorkletGlobalScope
also has an associated operation map, which is a map, initially empty, of strings (denoting operation names) to functions.
Currently each SharedStorageOperation
registered via register()
must be one of the following two types:
The SharedStorageRunOperation
is designed to work with output gates that do not need a return value, like the private aggregation service. A SharedStorageRunOperation
performs an async operation and returns a promise that resolves to undefined.
A SharedStorageSelectURLOperation
is an SharedStorageOperation
that takes in a list of SharedStorageUrlWithMetadata
s (i.e. dictionaries containing strings representing URL
s each wrapped with optional metadata), performs an async operation, and then returns a promise to a long
integer index specifying which of these URL
s should be selected.
[Exposed =SharedStorageWorklet ]interface :
SharedStorageRunOperation SharedStorageOperation {Promise <undefined >(
run object ); }; [
data Exposed =SharedStorageWorklet ]interface :
SharedStorageSelectURLOperation SharedStorageOperation {Promise <long >(
run object ,
data FrozenArray <SharedStorageUrlWithMetadata >); };
urls
2.3. Registering a Worklet
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
. -
If the result of running IsConstructor() with operationCtor is false, throw a
TypeError
. -
Let prototype be the result of running operationCtor’s [[GetPrototypeOf]]() method.
-
Let run be the result of running GetMethod() with prototype and "
run
". -
If run is undefined, throw a
TypeError
. -
Set the value of operationMap[name] to run.
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 origin be environment’s origin.
-
If origin is an opaque origin, then return failure.
-
If the user has disabled shared storage, 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 and environment.
-
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 databaseMap be the result of running obtain a shared storage bottle map for environment.
-
If databaseMap is failure, then return false.
-
Let valueStruct be a new value struct.
-
Set valueStruct’s value to value.
-
Let currentTime be the current wall time.
-
Set valueStruct’s last updated to currentTime.
-
Set databaseMap[key] to valueStruct.
-
Return true.
-
Let databaseMap be the result of running obtain a shared storage bottle map for environment.
-
If databaseMap is failure, then return failure.
-
If databaseMap does not contain key, return undefined.
-
Let valueStruct be the result of running Get on databaseMap with key.
-
If the result of running determine whether an entry is expired with valueStruct is true, return undefined.
-
Return valueStruct’s value.
-
Let databaseMap be the result of running obtain a shared storage bottle map for environment.
-
If databaseMap is failure, then return false.
-
Remove databaseMap[key].
-
Return true.
-
Let databaseMap be the result of running obtain a shared storage bottle map for environment.
-
If databaseMap is failure, then return false.
-
Run Clear on databaseMap.
-
Return true.
-
Let databaseMap be the result of running obtain a shared storage bottle map for environment.
-
If databaseMap is failure, then return failure.
-
Return the result of running getting the values on databaseMap.
-
Let databaseMap be the result of running obtain a shared storage bottle map for environment.
-
If databaseMap is failure, then return failure.
-
Return databaseMap’s size.
-
Let databaseMap be the result of running obtain a shared storage bottle map for environment.
-
If databaseMap is failure, then return false.
-
For each key key in databaseMap:
-
Let valueStruct be the result of running Get on databaseMap with key.
-
If the result of running determine whether an entry is expired with valueStruct is true, Remove databaseMap[key].
-
-
Return true.
To determine whether an entry is expired, given 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 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 running SharedStorageOperation
s, along with the worklet
attribute which is used to call addModule()
, are declared in WindowSharedStorage
and exposed to the Window
only, as these are the means by which the Window
interacts with the SharedStorageWorklet
.
On the other hand, methods for getting data from the shared storage database are declared in WorkletSharedStorage
and exposed to the SharedStorageWorklet
only, in order to carefully control the flow of data read from the database.
[Exposed =(Window ,SharedStorageWorklet )]interface {
SharedStorage Promise <any >(
set DOMString ,
key DOMString ,
value optional SharedStorageSetMethodOptions = {});
options Promise <any >(
append DOMString ,
key DOMString );
value Promise <any >(
delete DOMString );
key Promise <any >(); };
clear dictionary {
SharedStorageSetMethodOptions boolean =
ignoreIfPresent false ; };
4.1. The WindowSharedStorage
interface
The WindowSharedStorage
interface is as follows.
typedef (USVString or FencedFrameConfig ); [
SharedStorageResponse Exposed =(Window )]interface :
WindowSharedStorage SharedStorage {Promise <any >run (DOMString ,
name optional SharedStorageRunOperationMethodOptions = {});
options Promise <SharedStorageResponse >selectURL (DOMString ,
name FrozenArray <SharedStorageUrlWithMetadata >,
urls optional SharedStorageRunOperationMethodOptions = {});
options readonly attribute SharedStorageWorklet ; };
worklet
4.1.1. Window Setter/Deleter Methods
set(key, value, options)
method steps are:
-
Let promise be a new promise.
-
Let context be
WindowSharedStorage
'sWindow
's browsing context. -
Assert that context is not null.
-
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 environment be context’s active window's relevant settings object.
-
Let queue be context’s associated shared storage database queue.
-
Enqueue the following steps on queue:
-
If options["
ignoreIfPresent
"] is true and the result of running retrieve an entry from the database with queue, environment, and key is not undefined, resolve promise with undefined and abort these steps. -
Run store an entry in the database with queue, environment, key, and value.
-
Resolve promise with undefined.
-
-
Return promise.
append(key, value)
method steps are:
-
Let promise be a new promise.
-
Let context be
WindowSharedStorage
'sWindow
's browsing context. -
Assert that context is not null.
-
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 environment be context’s active window's relevant settings object.
-
Let queue be context’s associated 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, environment, and key.
-
If currentValue is failure, resolve promise with undefined and 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, environment, key, and value.
-
Resolve promise with undefined.
-
-
Return promise.
delete(key)
method steps are:
-
Let promise be a new promise.
-
Let context be
WindowSharedStorage
'sWindow
's browsing context. -
Assert that context is not null.
-
If key’s length exceeds the maximum length, return a promise rejected with a
TypeError
. -
Let environment be context’s active window's relevant settings object.
-
Let queue be context’s associated shared storage database queue.
-
Enqueue the following steps on queue:
-
Run delete an entry from the database with queue, environment, and key.
-
Resolve promise with undefined.
-
-
Return promise.
clear()
method steps are:
-
Let promise be a new promise.
-
Let context be
WindowSharedStorage
'sWindow
's browsing context. -
Assert that context is not null.
-
Let environment be context’s active window's relevant settings object.
-
Let queue be context’s associated shared storage database queue.
-
Enqueue the following steps on queue:
-
Run clear all entries in the database with queue and environment.
-
Resolve promise with undefined.
-
-
Return promise.
4.1.2. SharedStorageUrlWithMetadata
and Reporting
A SharedStorageUrlWithMetadata
object
is a dictionary containing a string representing a URL
and, optionally, a reportingMetadata
object
.
dictionary {
SharedStorageUrlWithMetadata required USVString ;
url object ; };
reportingMetadata
If a SharedStorageUrlWithMetadata
object
contains a non-empty reportingMetadata
object
in the form of a dictionary whose keys are eventTypes and whose values are strings that parse to valid URLs, then these eventType-URL pairs will be registered for later access within any fenced frame that loads the SharedStorageResponse
resulting from this selectURL()
call.
Inside a fenced frame with eventType-URL pairs that have been registered through selectURL()
with reportingMetadata
object
s, if fence.reportEvent() is called on a FenceEvent with a destination containing "shared-storage-select-url
" and that FenceEvent's corresponding eventType is triggered, then the FenceEvent's eventData will be sent as a beacon to the registered URL for that eventType.
object
reportingMetadata, run the following steps:
-
If reportingMetadata is empty, return true.
-
If reportingMetadata is not a dictionary, return false.
-
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 struct" fencedFrameConfigStruct, run the following steps:
-
If reportingMetadata is empty, return.
-
Assert that 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 specification.
4.1.3. Entropy Budgets
Because bits of entropy can leak via selectURL()
, the user agent will need to maintain budgets to limit these leaks.
4.1.3.1. Navigation Entropy Budget
If a user activates a fenced frame whose FencedFrameConfig
was generated by selectURL()
and thereby initiates a top-frame navigation, this will reveal to the landing page that its URL was selected, which is a leak in entropy bits of up to logarithm base 2 of the number of input URLs for the call to selectURL()
. To mitigate this, a user agent will set a per-calling origin navigation entropy allowance.
A calling origin for selectURL()
is the origin of an environment that makes a selectURL()
call.
A navigation entropy allowance is a maximum allowance of entropy bits that are permitted to leak via fenced frames initiating top-frame navigations during a given navigation budget epoch for a given calling calling origin. This allowance is defined by the user agent and is calling origin-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 origins to navigation entropy ledgers.
An navigation entropy ledger is a list of bit debits.
A bit debit is a struct containing a double
bits, indicating a value in entropy bits, along with a DOMHighResTimeStamp
timestamp (from the Unix Epoch).
Bit debits whose timestamps precede the start of the current navigation budget epoch are said to be expired.
When a leak occurs, its value in entropy bits is calculated and stored for that calling origin, along with the current time as a timestamp, together as a bit debit in the shared storage navigation budget table.
A calling origin'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 an calling origin has insufficient remaining navigation budget, selectURL()
will return a SharedStorageResponse
(i.e. either a FencedFrameConfig
or an opaque URL) for the url
in the SharedStorageUrlWithMetadata
at the default index in its input URL list.
The default index for a call to selectURL()
is implementation-defined in such a way that it is independent from the result of the associated SharedStorageSelectURLOperation
's "run
" method.
In this case, whenever the SharedStorageSelectURLOperation
's "run
" method encounters an error, or whenever there is insufficient remaining navigation budget, the "run
" method would return 0, and hence selectURL()
would return a SharedStorageResponse
for the first url
in its input URL list.
In this case, whenever the SharedStorageSelectURLOperation
's "run
" method encounters an error, or whenever there is insufficient remaining navigation budget, selectURL()
would return a SharedStorageResponse
for the last url
in its input URL list.
-
If origin is opaque, return undefined.
-
Let maxBits be the user agent's navigation entropy allowance.
-
If the user agent's shared storage navigation budget table does not contain origin, then return maxBits.
-
Otherwise, let ledger be user agent's shared storage navigation budget table[origin].
-
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 bitDebit is false, then increment debitSum by debit.
-
-
Return maxBits minus debitSum.
-
Let epochLength be the user agent's navigation budget lifetime.
-
Let currentTime be the current wall time.
-
Let threshold be currentTime minus 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-frame navigation initiated by a fenced frame whose FencedFrameConfig
was generated via selectURL()
, as this can leak cross-site data. Since the bits to charge is calculated during the call to selectURL()
but only actually recorded in the shared storage navigation budget table if and when the resulting fenced frame initiates a top-frame navigation, the bits must be stored as a pending shared storage budget debit in the corresponding FencedFrameConfig
until this time.
Between beginning navigation and ending navigation, a user agent will perform the charge shared storage navigation budget algorithm.
The "fenced frame config struct" and its boolean has navigated have not yet been added to the draft Fenced Frame specification. Some form of them will be added, although their names are subject to bikeshedding. Fix the names when they are added.
Document
sourceDocument, run the following steps:
-
If navigable is not a traversable navigable, return.
-
Let node be sourceDocument’s node navigable.
-
While node is not null:
-
If node has a "fenced frame config struct":
-
Let origin be node’s active document's origin.
-
Let pendingBits be node’s "fenced frame config struct"'s pending shared storage budget debit.
-
If pendingBits is greater than 0 and if "fenced frame config struct"'s has navigated is false, run the following steps:
-
Let ledger be user agent's shared storage navigation budget table[origin].
-
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 node’s "fenced frame config struct"'s has navigated to true.
-
-
-
Set node to node’s parent.
-
4.1.3.2. Reporting Entropy Budget
Likewise, each time a call to fence.reportEvent() from a fenced frame originating via selectURL()
whose destination contains "shared-storage-select-url
" and whose eventType is triggered, there is a leak of up to logarithm base 2 of the number of main input URLs entropy bits. The user agent will need to set a per-page load reporting entropy allowance to restrict the information leaked, with page load referring to a top-level traversable's lifecycle.
A reporting entropy allowance is a maximum allowance of entropy bits that are permitted to leak via fence.reportEvent() during a given page load. This allowance is defined by the user agent.
Each top-level traversable will have a new double
shared storage reporting budget associated to it which will be initialized with the value of user agent's reporting entropy allowance upon top-level traversable's creation.
When fence.reportEvent() is called with a destination containing "shared-storage-select-url
", it will be necessary to charge shared storage reporting budget as below.
Document
sourceDocument, run the following steps:
-
Let debitSum be 0.
-
Let node be sourceDocument’s node navigable.
-
While node is not null:
-
If node has a "fenced frame config struct":
-
Let pendingBits be node’s "fenced frame config struct"'s pending shared storage budget debit.
-
If pendingBits is greater than 0 and if "fenced frame config struct"'s has reported is false, increment debitSum by pendingBits
-
-
Set node to node’s parent.
-
-
Return debitSum.
The "fenced frame config struct" and its boolean has reported have not yet been added to the draft Fenced Frame specification. Some form of them will be added, although their names are subject to bikeshedding. Fix the names when they are added.
Document
sourceDocument, run the following steps:
-
Let toCharge be the result of running determine reporting budget to charge with sourceDocument.
-
Let node be sourceDocument’s node navigable.
-
Let topNode be the result of running get the top-level traversable for node.
-
If topNode’s shared storage reporting budget is less than toCharge, return false.
-
While node is not null:
-
If node has a "fenced frame config struct" and if node’s "fenced frame config struct"'s pending shared storage budget debit is greater than 0, set node’s "fenced frame config struct"'s has reported to true.
-
Set node to node’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.
-
4.1.4. Run Operation Methods
run(name, options)
method steps are:
-
Let promise be a new promise.
-
If
addModule()
has not yet been called, return a promise rejected with aTypeError
. -
Let outsideSettings be
worklet
's relevant settings object. -
Let agent be the result of obtaining a worklet agent given outsideSettings.
-
Run the following steps in agent:
-
If
worklet
's module map is not empty:-
Let operationMap be this
SharedStorageWorkletGlobalScope
's operation map. -
If operationMap contains name:
-
-
Resolve promise with undefined.
-
-
Return promise.
-
If options["
keepAlive
"] is false, upon fulfillment of promise or upon rejection of promise, run terminate a worklet global scope with the associatedSharedStorageWorkletGlobalScope
.
selectURL(name, urls, options)
method steps are:
-
Let resultPromise be a new promise.
-
If urls is empty or exceeds the maximum allowed length, return a promise rejected with a
TypeError
. -
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 promise with a
TypeError
and abort these steps.
-
-
-
If
addModule()
has not yet been called, reject promise with aTypeError
and abort these steps. -
Let fencedFrameConfigStruct be a "fenced frame config struct". Add correct struct name as well as linking when Fenced Frame API updates their draft spec to include it.
The "fenced frame config struct" and the following "obtain a FencedFrameConfig
from a fenced frame config struct" algorithm have not yet been added to the draft Fenced Frame specification. Some form of them will be added, although their names are subject to bikeshedding.
-
If options["
resolveToConfig
"] is true, resolve resultPromise with the result of running "obtain aFencedFrameConfig
from a fenced frame config struct" with fencedFrameConfigStruct. Add correct struct and algorithms names as well as linking when Fenced Frame API updates their draft spec to include it. -
Othewise, resolve resultPromise to fencedFrameConfigStruct’s "urn uuid". Add correct struct name and urn:uuid name as well as linking when Fenced Frame API updates their draft spec to include it.
-
Let indexPromise be a new promise.
-
Let outsideSettings be
worklet
's relevant settings object. -
Let agent be the result of obtaining a worklet agent given outsideSettings.
-
Run the following steps in agent:
-
Let index be default index.
-
If
worklet
's module map is not empty:-
Let operationMap be the associated
SharedStorageWorkletGlobalScope
's operation map. -
If operationMap contains name:
-
Let operation be operationMap[name].
-
Let argumentsList be a new list.
-
Append urlList to argumentsList.
-
Let operationResult be the result of running Call on operation with argumentsList.
-
If operationResult has any error(s), then reject indexPromise with a
TypeError
. -
Otherwise:
-
Set index to the result of casting operationResult to an
unsigned long
. If this throws an exception, catch it and reject indexPromise with aTypeError
. -
Otherwise, if index is greater than urlList.size, then reject indexPromise with a
TypeError
.
-
-
-
-
If indexPromise has not been rejected, resolve indexPromise with index.
-
Upon fulfillment of indexPromise, perform the following steps:
-
Let resultIndex be the numerical value of indexPromise.
-
Let context be
WindowSharedStorage
'sWindow
's browsing context. -
Assert that context is not null.
-
Let origin be context’s active document's origin.
-
Let remainingBudget be the result of running determine remaining navigation budget with origin.
-
Assert that remainingBudget is not undefined.
-
Let listSize be urlList’s size.
-
Let pendingBits be the logarithm base 2 of listSize.
-
If pendingBits is greather than remainingBudget, set resultIndex to default index.
-
Set fencedFrameConfigStruct’s pending shared storage budget debit to pendingBits.
-
Set fencedFrameConfigStruct’s url to urlList[resultIndex].
-
Let resultURLWithMetadata be urls[resultIndex].
-
If resultURLWithMetadata has field "
reportingMetadata
", run register reporting metadata with resultURLWithMetadata["reportingMetadata
"].
-
-
Upon rejection of indexPromise, set fencedFrameConfigStruct’s url to urlList[default index].
-
-
Return resultPromise.
-
If options["
keepAlive
"] is false, upon fulfillment of resultPromise or upon rejection of resultPromise, run terminate a worklet global scope with the associatedSharedStorageWorkletGlobalScope
.
4.2. 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.2.1. Worklet Setter/Deleter Methods
set(key, value, options)
method steps are:
-
Let promise be a new promise.
-
Let context be
WorkletSharedStorage
'sSharedStorageWorkletGlobalScope
's outside settings's target browsing context. -
Assert that context is not null.
-
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 environment be context’s active window's relevant settings object.
-
Let queue be context’s associated 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, environment, and key.
-
If currentValue is failure, reject promise with a
TypeError
and abort these steps. -
If currentValue is not undefined, resolve promise with undefined and abort these steps.
-
-
If the result of running store an entry in the database with queue, environment, key, and value is false, reject promise with a
TypeError
. -
Otherwise, resolve promise with undefined.
-
-
Return promise.
append(key, value)
method steps are:
-
Let promise be a new promise.
-
Let context be
WorkletSharedStorage
'sSharedStorageWorkletGlobalScope
's outside settings's target browsing context. -
Assert that context is not null.
-
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 environment be context’s active window's relevant settings object.
-
Let queue be context’s associated 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, environment, and key.
-
If currentValue is failure, reject promise with a
TypeError
and 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, environment, key, and value is false, reject promise with a
TypeError
. -
Otherwise, resolve promise with undefined.
-
-
Return promise.
delete(key)
method steps are:
-
Let promise be a new promise.
-
Let context be
WorkletSharedStorage
'sSharedStorageWorkletGlobalScope
's outside settings's target browsing context. -
Assert that context is not null.
-
If key’s length exceeds the maximum length, return a promise rejected with a
TypeError
. -
Let environment be context’s active window's relevant settings object.
-
Let queue be context’s associated 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, reject promise with a
TypeError
. -
Otherwise, resolve promise with undefined.
-
-
Return promise.
clear()
method steps are:
-
Let promise be a new promise.
-
Let context be
WorkletSharedStorage
'sSharedStorageWorkletGlobalScope
's outside settings's target browsing context. -
Assert that context is not null.
-
Let environment be context’s active window's relevant settings object.
-
Let queue be context’s associated 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, reject promise with a
TypeError
. -
Otherwise, resolve promise with undefined.
-
-
Return promise.
4.2.2. Getter Methods
get(key)
method steps are:
-
Let promise be a new promise.
-
Let context be
WorkletSharedStorage
'sSharedStorageWorkletGlobalScope
's outside settings's target browsing context. -
Assert that context is not null.
-
If key’s length exceeds the maximum length, return a promise rejected with a
TypeError
. -
Let environment be context’s active window's relevant settings object.
-
Let queue be context’s associated 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, environment, and key.
-
If value is failure, reject promise with a
TypeError
. -
Otherwise, if value is undefined, resolve promise with undefined.
-
Otherwise, resolve promise with value.
-
-
Return promise.
length()
method steps are:
-
Let promise be a new promise.
-
Let context be
WorkletSharedStorage
'sSharedStorageWorkletGlobalScope
's outside settings's target browsing context. -
Let environment be context’s active window's relevant settings object.
-
Let queue be context’s associated 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, reject promise with a
TypeError
. -
Otherwise, resolve promise with numEntries.
-
-
Return promise.
remainingBudget()
method steps are:
-
Let promise be a new promise.
-
Let context be
WorkletSharedStorage
'sSharedStorageWorkletGlobalScope
's outside settings's target browsing context. -
Assert that context is not null.
-
Let origin be context’s active document's origin.
-
Let queue be context’s associated shared storage database queue.
-
Enqueue the following steps on queue:
-
Let remainingBudget be the result of running determine remaining navigation budget with origin.
-
Assert that remainingBudget is not undefined.
-
Resolve promise with remainingBudget.
-
-
Return promise.
4.2.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 context be
WorkletSharedStorage
'sSharedStorageWorkletGlobalScope
's outside settings's target browsing context. -
Assert that context is not null.
-
Let promise be a new promise.
-
Let environment be context’s active window's relevant settings object.
-
Let queue be context’s associated 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, reject promise with a
TypeError
. -
Otherwise, 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.
-
Resolve promise with doneObject and abort these steps.
-
-
Otherwise, let entry be the result of dequeueing from iterator’s pending entries.
-
Resolve promise with entry.
-
-
Return promise.
5. Privacy Considerations
The Shared Storage API attempts to provide the ability to use cross-site data for a range of use cases in a way that better protects user privacy than the use of third-party cookies. Shared Storage’s main privacy safeguard is that read access of the data stored in its storage may only occur within an embedder’s SharedStorageWorklet
. Well-defined limits restrict output of data from the SharedStorageWorklet
to a minimum.
In particular, an embedder can select a URL from a short list of URLs based on data in their shared storage and then display the result in a fenced frame. The embedder will not be able to know which URL was chosen except through specifc mechanisms that will be better-mitigated in the longer term. Currently, a few bits of entropy can leak each time that the user clicks on the fenced frame to initiate a top-frame navigation and/or the fenced frame calls the fence.reportEvent() API.
An embedder is also able to send aggregatable reports through the Private Aggregation Service, which adds noise in order to achieve differential privacy, uses a time delay to send reports, imposes limits on the number of reports sent, and processes the reports into aggregate data so that individual privacy is protected.