From 1df8c381be23a8d1f97698899cba48ea8ca72fd8 Mon Sep 17 00:00:00 2001 From: Yao Xiao Date: Wed, 17 Jan 2024 22:23:49 -0500 Subject: [PATCH 01/15] Spec update: Support multiple & cross-origin worklets - selectURL() and run() will be exposed to the shared storage worklet object. When calling on the default scoped worklet (i.e. sharedStorage.worklet.selectURL()/run()), the behavior is equivalent to sharedStorage.selectURL()/run(). - Users can create new worklets via `const worklet = await sharedStorage.createWorklet(url, options)`. This can be used to start multiple and potentially cross-origin worklets from a single document. - User settings error won't be exposed to the caller (i.e. will be treated as success) in the case of creating or using a cross-origin worklet. This is to prevent leaking user's settings for an arbitrary site. --- spec.bs | 386 ++++++++++++++++++++++++++++++++++---------------------- 1 file changed, 236 insertions(+), 150 deletions(-) diff --git a/spec.bs b/spec.bs index 2c838c6..bca7ed4 100644 --- a/spec.bs +++ b/spec.bs @@ -201,56 +201,227 @@ This document introduces a new storage API that is intentionally not partitioned The {{SharedStorageWorklet}} Interface {#worklet} ================================================= -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 {{WindowSharedStorage/selectURL()|URL-selection}} output gate. +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 {{SharedStorageWorklet/selectURL()|URL-selection}} output gate. + + + typedef (USVString or FencedFrameConfig) SharedStorageResponse; + [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 boolean addModule initiated, initialized to false. + +Each {{SharedStorageWorklet}} has an associated [=url/origin=] worklet origin, initialized to null. It will be set to the |moduleURL|'s [=url/origin=] when {{Worklet/addModule()|addModule}}(|moduleURL|, options) is called. + +Each {{SharedStorageWorklet}} has an associated boolean cross-origin worklet allowed, initialized to false. + +Each {{SharedStorageWorklet}} has an associated boolean is cross-origin worklet, initialized to false. Because adding multiple [=module scripts=] via {{Worklet/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 {{Worklet/addModule()}}, each {{SharedStorageWorklet}} can only call {{Worklet/addModule()}} once. The [=addModule initiated=] boolean makes it possible to enforce this restriction. When {{Worklet/addModule()}} is called for a worklet, it will run [=check if addModule is allowed and update status=], and if the result is false, abort the remaining steps in the {{Worklet/addModule()}} call, as detailed in the [[#worklet-monkey-patch]].
- To check if user preference setting allows access to shared storage from an [=environment settings object=] |environment|, run the following step: - 1. Using values available in |environment| as needed, perform an [=implementation-defined=] algorithm to return either true or false. + To check if user preference setting allows access to shared storage given an [=environment settings object=] |environment| and an [=url/origin=] |origin|, run the following step: + 1. Using values available in |environment| and |origin| as needed, perform an [=implementation-defined=] algorithm to return either true or false.
- To determine whether shared storage is allowed, given an [=environment settings object=] |environment|, run these steps: + To determine whether shared storage is allowed, given an [=environment settings object=] |environment| and an [=url/origin=] |origin|, run these steps: 1. If |environment| is not a [=secure context=], then return false. - 1. Let |origin| be |environment|'s [=url/origin=]. + 1. Let |outsideSettingsOrigin| be |environment|'s [=environment settings object/origin=]. + 1. If |outsideSettingsOrigin| is an [=opaque origin=], then return false. 1. If |origin| is an [=opaque origin=], then return false. 1. Let |globalObject| be the [=current realm=]'s [=global object=]. 1. [=Assert=] that |globalObject| is a {{Window}} or a {{SharedStorageWorkletGlobalScope}}. - 1. If |globalObject| is a {{Window}} and |globalObject|'s [=associated document=] is not [=allowed to use=] the "[=PermissionsPolicy/shared-storage=]" feature, return false. + 1. If |globalObject| is a {{Window}}, and if the result of running [=Is feature enabled in document for origin?=] on "[=PermissionsPolicy/shared-storage=]", |globalObject|'s [=associated document=], and |origin| returns false, then return false. 1. If the result of running [=obtaining a site|obtain a site=] with |origin| is not [=enrolled=], then return false. - 1. If the result of running [=check if user preference setting allows access to shared storage=] from |environment| is false, then return false. + 1. If the result of running [=check if user preference setting allows access to shared storage=] given |environment| and |origin| is false, then return false. 1. Return true. + +
+ Here are the scenarios where this algorithm could be used: + - For Window setter/deleter methods, |environment|'s [=environment settings object/origin=] should equal |origin|. + - For creating a worklet, and for initiating (from {{Window}}) and running (from {{SharedStorageWorkletGlobalScope}}) operations on a worklet, |environment| should be the worklet's creator window, and |origin| should be the [=SharedStorageWorklet/worklet origin=]. + - For fetch request, |environment| should be the request's initiator window, and |origin| should be the request's URL's origin. + +
+
- To check if addModule is allowed and update status for a {{SharedStorageWorklet}} |worklet|, run the following steps: - 1. If the result of running [=determine whether shared storage is allowed=] on the [=relevant settings object=] of [=this=] is false, return false. + To check if addModule is allowed and update status given a {{SharedStorageWorklet}} |worklet| and an [=/URL=] |moduleURL|, run the following steps: + 1. Let |workletOrigin| be |moduleURL|'s [=url/origin=]. + 1. If the result of running [=determine whether shared storage is allowed=] given [=this=]'s [=relevant settings object=] and |workletOrigin| is false, return false. 1. If |worklet|'s [=addModule initiated=] is true, return false. 1. Set |worklet|'s [=addModule initiated=] to true. + 1. If |workletOrigin| does not equal [=this=]'s [=relevant settings object=]'s [=environment settings object/origin=], set |worklet|'s [=SharedStorageWorklet/is cross-origin worklet=] to true. + 1. If |worklet|'s [=cross-origin worklet allowed=] is false, and if |worklet|'s [=SharedStorageWorklet/is cross-origin worklet=] is true, return false. + 1. Set |worklet|'s [=SharedStorageWorklet/worklet origin=] to |workletOrigin|. 1. Return true.
Moreover, each {{SharedStorageWorklet}}'s [=global scopes|list of global scopes=], initially empty, can contain at most one instance of its [=worklet global scope type=], the {{SharedStorageWorkletGlobalScope}}. + Note: The website that serves the module script should be aware of the implication of CORS: when the the module script's URL's origin is cross-origin with the worklet's creator window's origin, and by granting the module script resource via CORS, it will also grant the worklet's creation and subsequent operations on the worklet, under module script's URL's origin. The worklet's creator context could poison and use up the [=SharedStorageWorklet/worklet origin=]'s budget. + + ## Run Operation Methods on {{SharedStorageWorklet}} ## {#run-op-shared-storage-worklet} + +
+ To get the select-url result index, given {{SharedStorageWorklet}} |worklet|, {{DOMString}} |operationName|, [=/list=] |urlList|, and {{SharedStorageRunOperationMethodOptions}} |options|: + + 1. Let |promise| be a new [=promise=]. + 1. Let |window| be [=current global object=]. + 1. Assert that |window| is {{Window}}. + 1. Let |context| be |window|'s [=Window/browsing context=]. + 1. If |context| is null, return a [=promise rejected=] with a {{TypeError}}. + 1. Let |environment| be |context|'s [=active window=]'s [=relevant settings object=]. + 1. If |environment|'s [=associated document=] is not [=fully active=], return a [=promise rejected=] with a {{TypeError}}. + 1. Let |realm| be the [=current realm=]. + 1. Let |outsideSettings| be |worklet|'s [=relevant settings object=]. + 1. Let |agent| be the result of [=obtaining a worklet agent=] given |outsideSettings|. + 1. Run the following steps in |agent|: + 1. Let |index| be [=default index=]. + 1. If |worklet|'s [=module map=] is not [=map/empty=]: + 1. Let |operationMap| be the associated {{SharedStorageWorkletGlobalScope}}'s [=SharedStorageWorkletGlobalScope/operation map=]. + 1. If |operationMap| [=map/contains=] |operationName|: + 1. Let |operation| be |operationMap|[|operationName|]. + 1. Let |argumentsList| be a new [=/list=] with a single entry [=list/contain|containing=] |urlList|. + 1. If |options| [=map/contains=] |data|, [=list/append=] |data| to |argumentsList|. + 1. Let |operationResult| be the result of running [=Call=] on |operation| with |argumentsList|. + 1. If |operationResult| has any error(s), then [=queue a global task=] on the [=DOM manipulation task source=], given |realm|'s [=global object=], to reject |promise| with a {{TypeError}}. + 1. Otherwise: + 1. Set |index| to the result of [=casting=] |operationResult| to an {{unsigned long}}. + 1. If this throws an exception: + 1. Catch it and [=queue a global task=] on the [=DOM manipulation task source=], given |realm|'s [=global object=], to reject |promise| with a {{TypeError}}. + 1. Abort these steps. + 1. Otherwise, if |index| is greater than |urlList|'s [=list/size=]: + 1. [=Queue a global task=] on the [=DOM manipulation task source=], given |realm|'s [=global object=], to reject |promise| with a {{TypeError}}. + 1. Abort these steps. + 1. [=Queue a global task=] on the [=DOM manipulation task source=], given |realm|'s [=global object=], to resolve |promise| with |index|. + 1. Return |promise|. +
+ +
+ The selectURL(|name|, |urls|, |options|) method steps are: + + 1. Let |resultPromise| be a new [=promise=]. + 1. If [=this=]'s [=addModule initiated=] is false, then return a [=promise rejected=] with a {{TypeError}}. + 1. Let |window| be [=current global object=]. + 1. Assert that |window| is {{Window}}. + 1. Let |context| be |window|'s [=Window/browsing context=]. + 1. If |context| is null, return a [=promise rejected=] with a {{TypeError}}. + 1. Let |document| be |context|'s [=active document=]. + 1. If the result of running [=Is feature enabled in document for origin?=] on "[=PermissionsPolicy/shared-storage=]", |document|, and [=this=]'s [=SharedStorageWorklet/worklet origin=] returns false, return a [=promise rejected=] with a {{TypeError}}. + 1. If [=this=]'s [=global scopes|list of global scopes=] is [=list/empty=], then return a [=promise rejected=] with a {{TypeError}}. + 1. [=Assert=] that [=this=]'s [=global scopes|list of global scopes=] [=list/contains=] a single {{SharedStorageWorkletGlobalScope}}. + 1. If the result of running [=SharedStorageWorkletGlobalScope/check whether addModule is finished=] for [=this=]'s {{SharedStorageWorkletGlobalScope}} is false, return a [=promise rejected=] with a {{TypeError}}. + 1. If |urls| is empty or exceeds the maximum allowed length, return a [=promise rejected=] with a {{TypeError}}. + 1. Let |urlList| be an empty {{list}}. + 1. [=map/iterate|For each=] |urlWithMetadata| in |urls|: + 1. If |urlWithMetadata| has no field "`url`", return a [=promise rejected=] with a {{TypeError}}. + 1. Otherwise, let |urlString| be |urlWithMetadata|["`url`"]. + 1. Let |serializedUrl| be the result of running [=get the canonical URL string if valid=] with |urlString|. + 1. If |serializedUrl| is undefined, return a [=promise rejected=] with a {{TypeError}}. + 1. Otherwise, [=list/append=] |serializedUrl| to |urlList|. + 1. If |urlWithMetadata| has field "`reportingMetadata`": + 1. Let |reportingMetadata| be |urlWithMetadata|["`reportingMetadata`"]. + 1. If the result of running [=validate reporting metadata=] with |reportingMetadata| is false, reject |resultPromise| with a {{TypeError}} and abort these steps. + 1. Let |fencedFrameConfigStruct| be a "fenced frame config struct" with an invalid "urn uuid". Add correct struct name as well as linking when Fenced Frame API updates their draft spec to include it. + 1. Let |environment| be |context|'s [=active window=]'s [=relevant settings object=]. + 1. If the result of running [=determine whether shared storage is allowed=] given |environment| and [=this=]'s [=SharedStorageWorklet/worklet origin=] is false: + 1. If [=this=]'s [=SharedStorageWorklet/is cross-origin worklet=] is false, return a [=promise rejected=] with a {{TypeError}}. + 1. Else, set |fencedFrameConfigStruct| to a "fenced frame config struct" with a pending mapped "url uuid". Add correct struct name as well as linking when Fenced Frame API updates their draft spec to include it. + + Issue: 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. + + 1. If |options|["`resolveToConfig`"] is true, resolve |resultPromise| with the result of running "obtain a {{FencedFrameConfig}} from a fenced frame config struct" with |fencedFrameConfigStruct|. Add correct struct and algorithms names as well as linking when Fenced Frame API updates their draft spec to include it. + 1. 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. + 1. If |fencedFrameConfigStruct|'s "urn uuid" is invalid (i.e. instead of pending mapped), then return. Add correct struct name and urn:uuid name as well as linking when Fenced Frame API updates their draft spec to include it. + 1. Let |indexPromise| be the result of running [=get the select-url result index=], given [=this=], |name|, |urlList|, and |options|. + 1. [=Upon fulfillment=] of |indexPromise|, perform the following steps: + 1. Let |resultIndex| be the numerical value of |indexPromise|. + 1. Let |site| be the result of running [=obtain a site=] with |document|'s [=url/origin=]. + 1. Let |remainingBudget| be the result of running [=determine remaining navigation budget=] with |environment| and |site|. + 1. [=Assert=] that |remainingBudget| is not undefined. + 1. Let |pendingBits| be the logarithm base 2 of |urlList|'s [=list/size=]. + 1. If |pendingBits| is greather than |remainingBudget|, set |resultIndex| to [=default index=]. + 1. Set |fencedFrameConfigStruct|'s [=pending shared storage budget debit=] to |pendingBits|. + 1. Set |fencedFrameConfigStruct|'s [=/url=] to |urlList|[|resultIndex|]. + 1. Let |resultURLWithMetadata| be |urls|[|resultIndex|]. + 1. If |resultURLWithMetadata| has field "`reportingMetadata`", run [=register reporting metadata=] with |resultURLWithMetadata|["`reportingMetadata`"]. + 1. If |options|["`keepAlive`"] is false, run [=terminate a worklet global scope=] with the associated {{SharedStorageWorkletGlobalScope}}. + 1. [=Upon rejection=] of |indexPromise|, perform the following steps: + 1. Set |fencedFrameConfigStruct|'s [=/url=] to |urlList|[[=default index=]]. + 1. If |options|["`keepAlive`"] is false, run [=terminate a worklet global scope=] with the associated {{SharedStorageWorkletGlobalScope}}. + 1. Return |resultPromise|. +
+ +
+ The run(|name|, |options|) method steps are: + + 1. Let |promise| be a new [=promise=]. + 1. If [=this=]'s [=addModule initiated=] is false, then return a [=promise rejected=] with a {{TypeError}}. + 1. Let |window| be [=current global object=]. + 1. Assert that |window| is {{Window}}. + 1. If [=this=]'s [=global scopes|list of global scopes=] is [=list/empty=], then return a [=promise rejected=] with a {{TypeError}}. + 1. [=Assert=] that [=this=]'s [=global scopes|list of global scopes=] [=list/contains=] a single {{SharedStorageWorkletGlobalScope}}. + 1. If the result of running [=SharedStorageWorkletGlobalScope/check whether addModule is finished=] for [=this=]'s {{SharedStorageWorkletGlobalScope}} is false, return a [=promise rejected=] with a {{TypeError}}. + 1. Let |realm| be the [=current realm=]. + 1. Let |outsideSettings| be {{WindowSharedStorage/worklet}}'s [=relevant settings object=]. + 1. If the result of running [=determine whether shared storage is allowed=] given |outsideSettings| and [=this=]'s [=SharedStorageWorklet/worklet origin=] is false: + 1. If [=this=]'s [=SharedStorageWorklet/is cross-origin worklet=] is false, reject |promise| with a {{TypeError}}. + 1. Else, resolve |promise| with undefined. + 1. Return |promise|. + 1. Let |agent| be the result of [=obtaining a worklet agent=] given |outsideSettings|. + 1. Run the following steps in |agent|: + 1. [=Queue a global task=] on the [=DOM manipulation task source=], given |realm|'s [=global object=], to resolve |promise| with undefined. + 1. If {{WindowSharedStorage/worklet}}'s [=module map=] is not [=map/empty=]: + 1. Let |operationMap| be [=this=]'s {{SharedStorageWorkletGlobalScope}}'s [=SharedStorageWorkletGlobalScope/operation map=]. + 1. If |operationMap| [=map/contains=] |name|: + 1. Let |operation| be |operationMap|[|name|]. + 1. If |options| [=map/contains=] |data|: + 1. Let |argumentsList| be a new [=/list=]. + 1. [=list/Append=] |data| to |argumentsList|. + 1. [=Call=] |operation| with |argumentsList|. + 1. Otherwise, [=call=] |operation| without any arguments list. + 1. If |options|["`keepAlive`"] is false: + 1. Wait for |operation| to finish running, if applicable. + 1. Run [=terminate a worklet global scope=] with {{SharedStorageWorkletGlobalScope}}. + 1. Return |promise|. +
+ ## Monkey Patch for [=Worklets=] ## {#worklet-monkey-patch} This specification will make some modifications to the [=Worklet=] standard to accommodate the needs of Shared Storage. In particular, the {{Worklet/addModule()}} method steps for {{Worklet}} will need to be prepended with the following step: - 0. If {{Worklet}} has an associated boolean [=addModule initiated=], and the result of running [=check if addModule is allowed and update status=] on {{Worklet}} is false, return a [=promise rejected=] with a {{TypeError}}. + 0. If |this| is of type {{SharedStorageWorklet}}, and the result of running [=check if addModule is allowed and update status=] given |this| and |moduleURL| is false: + 1. If |this|'s [=SharedStorageWorklet/is cross-origin worklet=] is true, then reject promise with a {{TypeError}}. Else, resolve promise. + 1. Return promise. + +
+ + On user preferences error, addModule() will be aborted at an early stage. However, the error will only be exposed to the caller for same-origin worklet (i.e. where the initiator document's origin is same-origin with the module script's origin). For 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 {{SharedStorageWorklet/selectURL()}} and {{SharedStorageWorklet/run()}}. + +
+ + Specify that redirects are disallowed when fetching the module script for {{SharedStorageWorklet}}. And the penultimate step (i.e. the final indented step), currently "If |pendingTasks| is 0, then resolve |promise|.", should be updated to: @@ -289,6 +460,8 @@ Moreover, each {{SharedStorageWorklet}}'s [=global scopes|list of global scopes= Each {{SharedStorageWorkletGlobalScope}} has an associated [=environment settings object=] outside settings, which is the associated {{SharedStorageWorklet}}'s [=relevant settings object=]. + Each {{SharedStorageWorkletGlobalScope}} has an associated [=url/origin=] worklet origin, which is initialized to the associated {{SharedStorageWorklet}}'s [=SharedStorageWorklet/worklet origin=]. + Each {{SharedStorageWorkletGlobalScope}} has an associated [=/boolean=] addModule success, which is initialized to false. The {{SharedStorageWorkletGlobalScope}}'s [=module map=]'s [=module scripts=] should each define and {{register}} one or more {{SharedStorageOperation}}s. @@ -337,7 +510,7 @@ Moreover, each {{SharedStorageWorklet}}'s [=global scopes|list of global scopes=
The {{SharedStorageWorkletGlobalScope/sharedStorage}} getter steps are: - 1. If |this|'s [=addModule success=] is true, return |this|'s {{SharedStorageWorkletGlobalScope/sharedStorage}}. + 1. If [=this=]'s [=addModule success=] is true, return [=this=]'s {{SharedStorageWorkletGlobalScope/sharedStorage}}. 1. Otherwise, throw a {{TypeError}}.
@@ -370,10 +543,9 @@ The Shared Storage API will integrate into the [=Storage Model|Storage API=] as A [=user agent=]'s [=shared storage shed=] holds all shared storage data.
- To obtain a shared storage shelf, given a [=shared storage shed=] |shed| and an [=environment settings object=] |environment|, run these steps: + To obtain a shared storage shelf, given a [=shared storage shed=] |shed|, an [=environment settings object=] |environment|, and an [=url/origin=] |origin|, run these steps: - 1. If the result of running [=determine whether shared storage is allowed=] on |environment| is false, then return failure. - 1. Let |origin| be |environment|'s [=url/origin=]. + 1. If the result of running [=determine whether shared storage is allowed=] given |environment| and |origin| is false, then return failure. 1. If |shed|[origin] does not exist, then set |shed|[origin] to the result of running [=create a shared storage shelf=] with [=storage type|type=] "`shared`". 1. Return |shed|[|origin|].
@@ -400,10 +572,10 @@ The Shared Storage API will integrate into the [=Storage Model|Storage API=] as Note: Currently, a [=shared storage bucket=]'s [=bottle map=] has [=map/size=] `1`, since there is only one [=storage endpoint=] [=storage endpoint/registered=] with [=storage type|type=] "`shared`".
- To obtain a shared storage bottle map, given an [=environment settings object=] |environment|, run these steps: + To obtain a shared storage bottle map, given an [=environment settings object=] |environment| and an [=url/origin=] |origin|, run these steps: 1. Let |shed| be the [=user agent=]'s [=shared storage shed=]. - 1. Let |shelf| be the result of running [=obtain a shared storage shelf=] with |shed| and |environment|. + 1. Let |shelf| be the result of running [=obtain a shared storage shelf=] with |shed|, |environment|, and |origin|. 1. If |shelf| is failure, then return failure. 1. Let |bucket| be |shelf|'s [=bucket map=]["`default`"]. 1. Let |bottle| be |bucket|'s [=bottle map=]["`sharedStorage`"]. @@ -555,15 +727,15 @@ On the other hand, methods for getting data from the [=shared storage database=] 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 = {}); + Promise<any> run(DOMString name, + optional SharedStorageRunOperationMethodOptions options = {}); + + Promise<SharedStorageWorklet> createWorklet(USVString moduleURL, optional WorkletOptions options = {}); readonly attribute SharedStorageWorklet worklet; }; @@ -600,7 +772,7 @@ On the other hand, methods for getting data from the [=shared storage database=] 1. Let |context| be |sharedStorage|'s {{Window}}'s [=Window/browsing context=]. 1. If |context| is null, return a [=promise rejected=] with a {{TypeError}}. 1. Let |environment| be |context|'s [=active window=]'s [=relevant settings object=]. - 1. Let |databaseMap| be the result of running [=obtain a shared storage bottle map=] for |environment|. + 1. Let |databaseMap| be the result of running [=obtain a shared storage bottle map=] given |environment| and |environment|'s [=environment settings object/origin=]. 1. If |databaseMap| is failure, then return a [=promise rejected=] with a {{TypeError}}. 1. Let |queue| be |context|'s associated [=shared storage database|database=]'s [=shared storage database/shared storage database queue=]. 1. Let |realm| be the [=current realm=]. @@ -622,7 +794,7 @@ On the other hand, methods for getting data from the [=shared storage database=] 1. Let |context| be |sharedStorage|'s {{Window}}'s [=Window/browsing context=]. 1. If |context| is null, return a [=promise rejected=] with a {{TypeError}}. 1. Let |environment| be |context|'s [=active window=]'s [=relevant settings object=]. - 1. Let |databaseMap| be the result of running [=obtain a shared storage bottle map=] for |environment|. + 1. Let |databaseMap| be the result of running [=obtain a shared storage bottle map=] given |environment| and |environment|'s [=environment settings object/origin=]. 1. If |databaseMap| is failure, then return a [=promise rejected=] with a {{TypeError}}. 1. Let |queue| be |context|'s associated [=shared storage database|database=]'s [=shared storage database/shared storage database queue=]. 1. Let |realm| be the [=current realm=]. @@ -649,7 +821,7 @@ On the other hand, methods for getting data from the [=shared storage database=] 1. Let |context| be |sharedStorage|'s {{Window}}'s [=Window/browsing context=]. 1. If |context| is null, return a [=promise rejected=] with a {{TypeError}}. 1. Let |environment| be |context|'s [=active window=]'s [=relevant settings object=]. - 1. Let |databaseMap| be the result of running [=obtain a shared storage bottle map=] for |environment|. + 1. Let |databaseMap| be the result of running [=obtain a shared storage bottle map=] given |environment| and |environment|'s [=environment settings object/origin=]. 1. If |databaseMap| is failure, then return a [=promise rejected=] with a {{TypeError}}. 1. Let |queue| be |context|'s associated [=shared storage database|database=]'s [=shared storage database/shared storage database queue=]. 1. Let |realm| be the [=current realm=]. @@ -666,7 +838,7 @@ On the other hand, methods for getting data from the [=shared storage database=] 1. Let |context| be |sharedStorage|'s {{Window}}'s [=Window/browsing context=]. 1. If |context| is null, return a [=promise rejected=] with a {{TypeError}}. 1. Let |environment| be |context|'s [=active window=]'s [=relevant settings object=]. - 1. Let |databaseMap| be the result of running [=obtain a shared storage bottle map=] for |environment|. + 1. Let |databaseMap| be the result of running [=obtain a shared storage bottle map=] given |environment| and |environment|'s [=environment settings object/origin=] 1. If |databaseMap| is failure, then return a [=promise rejected=] with a {{TypeError}}. 1. Let |queue| be |context|'s associated [=shared storage database|database=]'s [=shared storage database/shared storage database queue=]. 1. Let |realm| be the [=current realm=]. @@ -687,9 +859,9 @@ On the other hand, methods for getting data from the [=shared storage database=] }; - If a {{SharedStorageUrlWithMetadata}} {{/object}} contains a non-[=map/empty=] {{SharedStorageUrlWithMetadata/reportingMetadata}} {{/object}} in the form of a [=dictionary=] whose [=map/keys=] are [=FenceEvent/eventTypes=] and whose [=map/values=] are [=strings=] that parse to valid [=/URLs=], then these [=FenceEvent/eventType=]-[=/URL=] pairs will be [=register reporting metadata|registered=] for later access within any [=fenced frame=] that loads the {{SharedStorageResponse}} resulting from this {{WindowSharedStorage/selectURL()}} call. + If a {{SharedStorageUrlWithMetadata}} {{/object}} contains a non-[=map/empty=] {{SharedStorageUrlWithMetadata/reportingMetadata}} {{/object}} in the form of a [=dictionary=] whose [=map/keys=] are [=FenceEvent/eventTypes=] and whose [=map/values=] are [=strings=] that parse to valid [=/URLs=], then these [=FenceEvent/eventType=]-[=/URL=] pairs will be [=register reporting metadata|registered=] for later access within any [=fenced frame=] that loads the {{SharedStorageResponse}} resulting from this {{SharedStorageWorklet/selectURL()}} call. - Inside a [=fenced frame=] with [=FenceEvent/eventType=]-[=/URL=] pairs that have been [=register reporting metadata|registered=] through {{WindowSharedStorage/selectURL()}} with {{SharedStorageUrlWithMetadata/reportingMetadata}} {{/object}}s, if [=fence.reportEvent()=] is called on a [=FenceEvent=] with a [=FenceEvent/destination=] [=list/containing=] "`shared-storage-select-url`" and that [=FenceEvent=]'s corresponding [=FenceEvent/eventType=] is triggered, then the [=FenceEvent=]'s [=FenceEvent/eventData=] will be sent as a [=beacon=] to the registered [=/URL=] for that [=FenceEvent/eventType=]. + Inside a [=fenced frame=] with [=FenceEvent/eventType=]-[=/URL=] pairs that have been [=register reporting metadata|registered=] through {{SharedStorageWorklet/selectURL()}} with {{SharedStorageUrlWithMetadata/reportingMetadata}} {{/object}}s, if [=fence.reportEvent()=] is called on a [=FenceEvent=] with a [=FenceEvent/destination=] [=list/containing=] "`shared-storage-select-url`" and that [=FenceEvent=]'s corresponding [=FenceEvent/eventType=] is triggered, then the [=FenceEvent=]'s [=FenceEvent/eventData=] will be sent as a [=beacon=] to the registered [=/URL=] for that [=FenceEvent/eventType=].
To validate reporting metadata, given an {{/object}} |reportingMetadata|, run the following steps: @@ -723,13 +895,13 @@ On the other hand, methods for getting data from the [=shared storage database=] ### Entropy Budgets ### {#budgets} - Because [=bits of entropy=] can leak via {{WindowSharedStorage/selectURL()}}, the [=user agent=] will need to maintain budgets to limit these leaks. + Because [=bits of entropy=] can leak via {{SharedStorageWorklet/selectURL()}}, the [=user agent=] will need to maintain budgets to limit these leaks. #### Navigation Entropy Budget #### {#nav-budget} - If a user activates a [=fenced frame=] whose {{FencedFrameConfig}} was generated by {{WindowSharedStorage/selectURL()}} and thereby initiates a [=top-frame=] [=navigate|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 {{WindowSharedStorage/selectURL()}}. To mitigate this, a [=user agent=] will set a per-[=calling site=] [=navigation entropy allowance=]. + If a user activates a [=fenced frame=] whose {{FencedFrameConfig}} was generated by {{SharedStorageWorklet/selectURL()}} and thereby initiates a [=top-frame=] [=navigate|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 {{SharedStorageWorklet/selectURL()}}. To mitigate this, a [=user agent=] will set a per-[=calling site=] [=navigation entropy allowance=]. - A calling site for {{WindowSharedStorage/selectURL()}} is the [=site=] resulting from running [=obtain a site=] with the [=url/origin=] of an [=environment=] that makes a {{WindowSharedStorage/selectURL()}} call. + A calling site for {{SharedStorageWorklet/selectURL()}} is the [=site=] resulting from running [=obtain a site=] with the [=url/origin=] of an [=environment=] that makes a {{SharedStorageWorklet/selectURL()}} call. A navigation entropy allowance is a maximum allowance of [=entropy bits=] that are permitted to leak via [=fenced frames=] initiating [=top-frame=] [=navigate|navigations=] during a given [=navigation budget epoch=] for a given calling [=calling site=]. This [=navigation entropy allowance|allowance=] is defined by the [=user agent=] and is [=calling site=]-agnostic. @@ -749,22 +921,22 @@ On the other hand, methods for getting data from the [=shared storage database=] A [=calling site=]'s remaining navigation budget is the [=navigation entropy allowance=] minus any [=bit debits=] whose [=bit debit/timestamps=] are within the current [=navigation budget epoch=]. - {{WindowSharedStorage/selectURL()}}'s argument "`urls`" is its input URL list. + {{SharedStorageWorklet/selectURL()}}'s argument "`urls`" is its input URL list. - When a [=calling site=] has insufficient [=calling site/remaining navigation budget=], {{WindowSharedStorage/selectURL()}} will return a {{SharedStorageResponse}} (i.e. either a {{FencedFrameConfig}} or an opaque [=/URL=]) for the {{SharedStorageUrlWithMetadata/url}} in the {{SharedStorageUrlWithMetadata}} at the [=default index=] in its [=selectURL/input URL list=]. + When a [=calling site=] has insufficient [=calling site/remaining navigation budget=], {{SharedStorageWorklet/selectURL()}} will return a {{SharedStorageResponse}} (i.e. either a {{FencedFrameConfig}} or an opaque [=/URL=]) for the {{SharedStorageUrlWithMetadata/url}} in the {{SharedStorageUrlWithMetadata}} at the [=default index=] in its [=selectURL/input URL list=]. - The default index for a call to {{WindowSharedStorage/selectURL()}} is implementation-defined in such a way that it is independent from the result of the associated {{SharedStorageSelectURLOperation}}'s "`run`" method. + The default index for a call to {{SharedStorageWorklet/selectURL()}} is implementation-defined in such a way that it is independent from the result of the associated {{SharedStorageSelectURLOperation}}'s "`run`" method.
The [=default index=] could be defined to be 0. - In this case, whenever the {{SharedStorageSelectURLOperation}}'s "`run`" method encounters an error, or whenever there is insufficient [=calling site/remaining navigation budget=], the "`run`" method would return 0, and hence {{WindowSharedStorage/selectURL()}} would return a {{SharedStorageResponse}} for the first {{SharedStorageUrlWithMetadata/url}} in its [=selectURL/input URL list=]. + In this case, whenever the {{SharedStorageSelectURLOperation}}'s "`run`" method encounters an error, or whenever there is insufficient [=calling site/remaining navigation budget=], the "`run`" method would return 0, and hence {{SharedStorageWorklet/selectURL()}} would return a {{SharedStorageResponse}} for the first {{SharedStorageUrlWithMetadata/url}} in its [=selectURL/input URL list=].
The [=default index=] could be defined to be [=selectURL/input URL list=]'s [=list/size=] minus 1. - In this case, whenever the {{SharedStorageSelectURLOperation}}'s "`run`" method encounters an error, or whenever there is insufficient [=calling site/remaining navigation budget=], {{WindowSharedStorage/selectURL()}} would return a {{SharedStorageResponse}} for the last {{SharedStorageUrlWithMetadata/url}} in its [=selectURL/input URL list=]. + In this case, whenever the {{SharedStorageSelectURLOperation}}'s "`run`" method encounters an error, or whenever there is insufficient [=calling site/remaining navigation budget=], {{SharedStorageWorklet/selectURL()}} would return a {{SharedStorageResponse}} for the last {{SharedStorageUrlWithMetadata/url}} in its [=selectURL/input URL list=].
@@ -791,7 +963,7 @@ On the other hand, methods for getting data from the [=shared storage database=] 1. Otherwise, return false.
- A [=bit debit=] will need to be [=charge shared storage navigation budget|charged=] to the [=shared storage navigation budget table=] for each [=top-frame=] [=navigate|navigation=] initiated by a [=fenced frame=] whose {{FencedFrameConfig}} was generated via {{WindowSharedStorage/selectURL()}}, as this can leak cross-site data. Since the [=bit debit/bits=] to charge is calculated during the call to {{WindowSharedStorage/selectURL()}} but only actually recorded in the [=shared storage navigation budget table=] if and when the resulting fenced frame initiates a [=top-frame=] [=beginning navigation|navigation=], the [=bit debit/bits=] must be stored as a pending shared storage budget debit in the corresponding {{FencedFrameConfig}} until this time. + A [=bit debit=] will need to be [=charge shared storage navigation budget|charged=] to the [=shared storage navigation budget table=] for each [=top-frame=] [=navigate|navigation=] initiated by a [=fenced frame=] whose {{FencedFrameConfig}} was generated via {{SharedStorageWorklet/selectURL()}}, as this can leak cross-site data. Since the [=bit debit/bits=] to charge is calculated during the call to {{SharedStorageWorklet/selectURL()}} but only actually recorded in the [=shared storage navigation budget table=] if and when the resulting fenced frame initiates a [=top-frame=] [=beginning navigation|navigation=], the [=bit debit/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. @@ -819,7 +991,7 @@ On the other hand, methods for getting data from the [=shared storage database=] #### Reporting Entropy Budget #### {#report-budget} - Likewise, each time a call to [=fence.reportEvent()=] from a [=fenced frame=] originating via {{WindowSharedStorage/selectURL()}} whose [=FenceEvent/destination=] [=list/contains=] "`shared-storage-select-url`" and whose [=FenceEvent/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. + Likewise, each time a call to [=fence.reportEvent()=] from a [=fenced frame=] originating via {{SharedStorageWorklet/selectURL()}} whose [=FenceEvent/destination=] [=list/contains=] "`shared-storage-select-url`" and whose [=FenceEvent/eventType=] is triggered, there is a leak of up to logarithm base 2 of the number of main input [=/URLs=] [=entropy bits=]. The [=user agent=] will need to set a per-[=page load=] [=reporting entropy allowance=] to restrict the information leaked, with page load referring to a [=top-level traversable=]'s (i.e. primary main frame's) lifecycle. A reporting entropy allowance is a maximum allowance of [=entropy bits=] that are permitted to leak via [=fence.reportEvent()=] during a given page load. This [=reporting entropy allowance|allowance=] is defined by the [=user agent=]. @@ -866,117 +1038,33 @@ On the other hand, methods for getting data from the [=shared storage database=] 1. [=map/iterate|For each=] |bitDebit| in |ledger|, if the result of running [=check whether a bit debit is expired=] with |bitDebit| is true, [=list/remove=] |bitDebit| from |ledger|.
- ### Run Operation Methods ### {#run-op} + ### Run Operation Methods on {{WindowSharedStorage}} ### {#run-op-shared-storage}
- The run(|name|, |options|) method steps are: + The selectURL(|name|, |urls|, |options|) method steps are: - 1. Let |promise| be a new [=promise=]. - 1. Let |worklet| be {{WindowSharedStorage}}'s {{WindowSharedStorage/worklet}}. - 1. If |worklet|'s [=global scopes|list of global scopes=] is [=list/empty=], then return a [=promise rejected=] with a {{TypeError}}. - 1. [=Assert=] that |worklet|'s [=global scopes|list of global scopes=] [=list/contains=] a single {{SharedStorageWorkletGlobalScope}}. - 1. If the result of running [=SharedStorageWorkletGlobalScope/check whether addModule is finished=] for |worklet|'s {{SharedStorageWorkletGlobalScope}} is false, return a [=promise rejected=] with a {{TypeError}}. - 1. Let |realm| be the [=current realm=]. - 1. Let |outsideSettings| be {{WindowSharedStorage/worklet}}'s [=relevant settings object=]. - 1. If the result of running [=determine whether shared storage is allowed=] on |outsideSettings| is false, return a [=promise rejected=] with a {{TypeError}}. - 1. Let |agent| be the result of [=obtaining a worklet agent=] given |outsideSettings|. - 1. Run the following steps in |agent|: - 1. [=Queue a global task=] on the [=DOM manipulation task source=], given |realm|'s [=global object=], to resolve |promise| with undefined. - 1. If {{WindowSharedStorage/worklet}}'s [=module map=] is not [=map/empty=]: - 1. Let |operationMap| be this {{SharedStorageWorkletGlobalScope}}'s [=SharedStorageWorkletGlobalScope/operation map=]. - 1. If |operationMap| [=map/contains=] |name|: - 1. Let |operation| be |operationMap|[|name|]. - 1. If |options| [=map/contains=] |data|: - 1. Let |argumentsList| be a new [=/list=]. - 1. [=list/Append=] |data| to |argumentsList|. - 1. [=Call=] |operation| with |argumentsList|. - 1. Otherwise, [=call=] |operation| without any arguments list. - 1. If |options|["`keepAlive`"] is false: - 1. Wait for |operation| to finish running, if applicable. - 1. Run [=terminate a worklet global scope=] with {{SharedStorageWorkletGlobalScope}}. - 1. Return |promise|. + 1. Let |sharedStorage| be [=this=]. + 1. Return |sharedStorage|.{{WindowSharedStorage/worklet}}.{{SharedStorageWorklet/selectURL()|selectURL}}(|name|, |urls|, |options|).
- To get the select-url result index, given {{WindowSharedStorage/worklet}} |worklet|, {{DOMString}} |operationName|, [=/list=] |urlList|, and {{SharedStorageRunOperationMethodOptions}} |options|: + The run(|name|, |options|) method steps are: - 1. Let |promise| be a new [=promise=]. - 1. Let |context| be {{WindowSharedStorage}}'s {{Window}}'s [=Window/browsing context=]. - 1. If |context| is null, return a [=promise rejected=] with a {{TypeError}}. - 1. Let |environment| be |context|'s [=active window=]'s [=relevant settings object=]. - 1. If |environment|'s [=associated document=] is not [=fully active=], return a [=promise rejected=] with a {{TypeError}}. - 1. Let |realm| be the [=current realm=]. - 1. Let |outsideSettings| be |worklet|'s [=relevant settings object=]. - 1. Let |agent| be the result of [=obtaining a worklet agent=] given |outsideSettings|. - 1. Run the following steps in |agent|: - 1. Let |index| be [=default index=]. - 1. If {{WindowSharedStorage/worklet}}'s [=module map=] is not [=map/empty=]: - 1. Let |operationMap| be the associated {{SharedStorageWorkletGlobalScope}}'s [=SharedStorageWorkletGlobalScope/operation map=]. - 1. If |operationMap| [=map/contains=] |operationName|: - 1. Let |operation| be |operationMap|[|operationName|]. - 1. Let |argumentsList| be a new [=/list=] with a single entry [=list/contain|containing=] |urlList|. - 1. If |options| [=map/contains=] |data|, [=list/append=] |data| to |argumentsList|. - 1. Let |operationResult| be the result of running [=Call=] on |operation| with |argumentsList|. - 1. If |operationResult| has any error(s), then [=queue a global task=] on the [=DOM manipulation task source=], given |realm|'s [=global object=], to reject |promise| with a {{TypeError}}. - 1. Otherwise: - 1. Set |index| to the result of [=casting=] |operationResult| to an {{unsigned long}}. - 1. If this throws an exception: - 1. Catch it and [=queue a global task=] on the [=DOM manipulation task source=], given |realm|'s [=global object=], to reject |promise| with a {{TypeError}}. - 1. Abort these steps. - 1. Otherwise, if |index| is greater than |urlList|'s [=list/size=]: - 1. [=Queue a global task=] on the [=DOM manipulation task source=], given |realm|'s [=global object=], to reject |promise| with a {{TypeError}}. - 1. Abort these steps. - 1. [=Queue a global task=] on the [=DOM manipulation task source=], given |realm|'s [=global object=], to resolve |promise| with |index|. - 1. Return |promise|. + 1. Let |sharedStorage| be [=this=]. + 1. Return |sharedStorage|.{{WindowSharedStorage/worklet}}.{{SharedStorageWorklet/run()|run}}(|name|, |options|).
+ ### Create a new worklet via {{WindowSharedStorage}} ### {#create-a-new-worklet-via-shared-storage} +
- The selectURL(|name|, |urls|, |options|) method steps are: + The createWorklet(|moduleURL|, |options|) method steps are: + 1. Let |sharedStorageWorklet| be a new {{SharedStorageWorklet}}. + 1. Set |sharedStorageWorklet|'s [=cross-origin worklet allowed=] to true. + 1. Let |addModulePromise| be the result of invoking sharedStorageWorklet.{{Worklet/addModule()|addModule}}(|moduleURL|, |options|). 1. Let |resultPromise| be a new [=promise=]. - 1. Let |context| be {{WindowSharedStorage}}'s {{Window}}'s [=Window/browsing context=]. - 1. If |context| is null, return a [=promise rejected=] with a {{TypeError}}. - 1. Let |environment| be |context|'s [=active window=]'s [=relevant settings object=]. - 1. If the result of running [=determine whether shared storage is allowed=] on |environment| is false, return a [=promise rejected=] with a {{TypeError}}. - 1. Let |document| be |context|'s [=active document=]. - 1. If |document| is not [=allowed to use=] the "[=PermissionsPolicy/shared-storage-select-url=]" feature, return a [=promise rejected=] with a {{TypeError}}. - 1. Let |worklet| be {{WindowSharedStorage}}'s {{WindowSharedStorage/worklet}}. - 1. If |worklet|'s [=global scopes|list of global scopes=] is [=list/empty=], then return a [=promise rejected=] with a {{TypeError}}. - 1. [=Assert=] that |worklet|'s [=global scopes|list of global scopes=] [=list/contains=] a single {{SharedStorageWorkletGlobalScope}}. - 1. If the result of running [=SharedStorageWorkletGlobalScope/check whether addModule is finished=] for |worklet|'s {{SharedStorageWorkletGlobalScope}} is false, return a [=promise rejected=] with a {{TypeError}}. - 1. If |urls| is empty or exceeds the maximum allowed length, return a [=promise rejected=] with a {{TypeError}}. - 1. Let |urlList| be an empty {{list}}. - 1. [=map/iterate|For each=] |urlWithMetadata| in |urls|: - 1. If |urlWithMetadata| has no field "`url`", return a [=promise rejected=] with a {{TypeError}}. - 1. Otherwise, let |urlString| be |urlWithMetadata|["`url`"]. - 1. Let |serializedUrl| be the result of running [=get the canonical URL string if valid=] with |urlString|. - 1. If |serializedUrl| is undefined, return a [=promise rejected=] with a {{TypeError}}. - 1. Otherwise, [=list/append=] |serializedUrl| to |urlList|. - 1. If |urlWithMetadata| has field "`reportingMetadata`": - 1. Let |reportingMetadata| be |urlWithMetadata|["`reportingMetadata`"]. - 1. If the result of running [=validate reporting metadata=] with |reportingMetadata| is false, reject |resultPromise| with a {{TypeError}} and abort these steps. - 1. 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. - - Issue: 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. - - 1. If |options|["`resolveToConfig`"] is true, resolve |resultPromise| with the result of running "obtain a {{FencedFrameConfig}} from a fenced frame config struct" with |fencedFrameConfigStruct|. Add correct struct and algorithms names as well as linking when Fenced Frame API updates their draft spec to include it. - 1. 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. - 1. Let |indexPromise| be the result of running [=get the select-url result index=], given |worklet|, |name|, |urlList|, and |options|. - 1. [=Upon fulfillment=] of |indexPromise|, perform the following steps: - 1. Let |resultIndex| be the numerical value of |indexPromise|. - 1. Let |site| be the result of running [=obtain a site=] with |document|'s [=url/origin=]. - 1. Let |remainingBudget| be the result of running [=determine remaining navigation budget=] with |environment| and |site|. - 1. [=Assert=] that |remainingBudget| is not undefined. - 1. Let |pendingBits| be the logarithm base 2 of |urlList|'s [=list/size=]. - 1. If |pendingBits| is greather than |remainingBudget|, set |resultIndex| to [=default index=]. - 1. Set |fencedFrameConfigStruct|'s [=pending shared storage budget debit=] to |pendingBits|. - 1. Set |fencedFrameConfigStruct|'s [=/url=] to |urlList|[|resultIndex|]. - 1. Let |resultURLWithMetadata| be |urls|[|resultIndex|]. - 1. If |resultURLWithMetadata| has field "`reportingMetadata`", run [=register reporting metadata=] with |resultURLWithMetadata|["`reportingMetadata`"]. - 1. If |options|["`keepAlive`"] is false, run [=terminate a worklet global scope=] with the associated {{SharedStorageWorkletGlobalScope}}. - 1. [=Upon rejection=] of |indexPromise|, perform the following steps: - 1. Set |fencedFrameConfigStruct|'s [=/url=] to |urlList|[[=default index=]]. - 1. If |options|["`keepAlive`"] is false, run [=terminate a worklet global scope=] with the associated {{SharedStorageWorkletGlobalScope}}. + 1. [=Upon fulfillment=] of |addModulePromise|, resolve |resultPromise| to |sharedStorageWorklet|. + 1. [=Upon rejection=] of |addModulePromise|, reject |resultPromise| with a {{TypeError}}. 1. Return |resultPromise|.
@@ -993,7 +1081,7 @@ On the other hand, methods for getting data from the [=shared storage database=]
The {{Window/sharedStorage}} getter steps are: - 1. If |this| is [=fully active=], return |this|'s {{Window/sharedStorage}}. + 1. If [=this=] is [=fully active=], return [=this=]'s {{Window/sharedStorage}}. 1. Otherwise, return null.
@@ -1025,7 +1113,7 @@ On the other hand, methods for getting data from the [=shared storage database=] 1. If |context| is null, return a [=promise rejected=] with a {{TypeError}}. 1. If |context|'s [=active window=]'s [=associated document=] is not [=fully active=], return a [=promise rejected=] with a {{TypeError}}. 1. Let |environment| be |context|'s [=active window=]'s [=relevant settings object=]. - 1. Let |databaseMap| be the result of running [=obtain a shared storage bottle map=] for |environment|. + 1. Let |databaseMap| be the result of running [=obtain a shared storage bottle map=] given |environment| and [=current global object=]'s [=SharedStorageWorkletGlobalScope/worklet origin=]. 1. If |databaseMap| is failure, then return a [=promise rejected=] with a {{TypeError}}. 1. Let |queue| be |context|'s associated [=shared storage database|database=]'s [=shared storage database/shared storage database queue=]. 1. Let |realm| be the [=current realm=]. @@ -1054,7 +1142,7 @@ On the other hand, methods for getting data from the [=shared storage database=] 1. If |context| is null, return a [=promise rejected=] with a {{TypeError}}. 1. If |context|'s [=active window=]'s [=associated document=] is not [=fully active=], return a [=promise rejected=] with a {{TypeError}}. 1. Let |environment| be |context|'s [=active window=]'s [=relevant settings object=]. - 1. Let |databaseMap| be the result of running [=obtain a shared storage bottle map=] for |environment|. + 1. Let |databaseMap| be the result of running [=obtain a shared storage bottle map=] given |environment| and [=current global object=]'s [=SharedStorageWorkletGlobalScope/worklet origin=]. 1. If |databaseMap| is failure, then return a [=promise rejected=] with a {{TypeError}}. 1. Let |queue| be |context|'s associated [=shared storage database|database=]'s [=shared storage database/shared storage database queue=]. 1. Let |realm| be the [=current realm=]. @@ -1083,7 +1171,7 @@ On the other hand, methods for getting data from the [=shared storage database=] 1. If |context| is null, return a [=promise rejected=] with a {{TypeError}}. 1. If |context|'s [=active window=]'s [=associated document=] is not [=fully active=], return a [=promise rejected=] with a {{TypeError}}. 1. Let |environment| be |context|'s [=active window=]'s [=relevant settings object=]. - 1. Let |databaseMap| be the result of running [=obtain a shared storage bottle map=] for |environment|. + 1. Let |databaseMap| be the result of running [=obtain a shared storage bottle map=] given |environment| and [=current global object=]'s [=SharedStorageWorkletGlobalScope/worklet origin=]. 1. If |databaseMap| is failure, then return a [=promise rejected=] with a {{TypeError}}. 1. Let |queue| be |context|'s associated [=shared storage database|database=]'s [=shared storage database/shared storage database queue=]. 1. Let |realm| be the [=current realm=]. @@ -1102,7 +1190,7 @@ On the other hand, methods for getting data from the [=shared storage database=] 1. If |context| is null, return a [=promise rejected=] with a {{TypeError}}. 1. If |context|'s [=active window=]'s [=associated document=] is not [=fully active=], return a [=promise rejected=] with a {{TypeError}}. 1. Let |environment| be |context|'s [=active window=]'s [=relevant settings object=]. - 1. Let |databaseMap| be the result of running [=obtain a shared storage bottle map=] for |environment|. + 1. Let |databaseMap| be the result of running [=obtain a shared storage bottle map=] given |environment| and [=current global object=]'s [=SharedStorageWorkletGlobalScope/worklet origin=]. 1. If |databaseMap| is failure, then return a [=promise rejected=] with a {{TypeError}}. 1. Let |queue| be |context|'s associated [=shared storage database|database=]'s [=shared storage database/shared storage database queue=]. 1. Let |realm| be the [=current realm=]. @@ -1124,7 +1212,7 @@ On the other hand, methods for getting data from the [=shared storage database=] 1. If |context| is null, return a [=promise rejected=] with a {{TypeError}}. 1. If |context|'s [=active window=]'s [=associated document=] is not [=fully active=], return a [=promise rejected=] with a {{TypeError}}. 1. Let |environment| be |context|'s [=active window=]'s [=relevant settings object=]. - 1. Let |databaseMap| be the result of running [=obtain a shared storage bottle map=] for |environment|. + 1. Let |databaseMap| be the result of running [=obtain a shared storage bottle map=] given |environment| and [=current global object=]'s [=SharedStorageWorkletGlobalScope/worklet origin=]. 1. If |databaseMap| is failure, then return a [=promise rejected=] with a {{TypeError}}. 1. Let |queue| be |context|'s associated [=shared storage database|database=]'s [=shared storage database/shared storage database queue=]. 1. Let |realm| be the [=current realm=]. @@ -1145,7 +1233,7 @@ On the other hand, methods for getting data from the [=shared storage database=] 1. If |context| is null, return a [=promise rejected=] with a {{TypeError}}. 1. If |context|'s [=active window=]'s [=associated document=] is not [=fully active=], return a [=promise rejected=] with a {{TypeError}}. 1. Let |environment| be |context|'s [=active window=]'s [=relevant settings object=]. - 1. Let |databaseMap| be the result of running [=obtain a shared storage bottle map=] for |environment|. + 1. Let |databaseMap| be the result of running [=obtain a shared storage bottle map=] given |environment| and [=current global object=]'s [=SharedStorageWorkletGlobalScope/worklet origin=]. 1. If |databaseMap| is failure, then return a [=promise rejected=] with a {{TypeError}}. 1. Let |queue| be |context|'s associated [=shared storage database|database=]'s [=shared storage database/shared storage database queue=]. 1. Let |realm| be the [=current realm=]. @@ -1165,8 +1253,8 @@ On the other hand, methods for getting data from the [=shared storage database=] 1. If |context| is null, return a [=promise rejected=] with a {{TypeError}}. 1. If |context|'s [=active window=]'s [=associated document=] is not [=fully active=], return a [=promise rejected=] with a {{TypeError}}. 1. Let |environment| be |context|'s [=active window=]'s [=relevant settings object=]. - 1. If the result of running [=determine whether shared storage is allowed=] on |environment| is false, return a [=promise rejected=] with a {{TypeError}}. - 1. Let |site| be the result of running [=obtain a site=] with |context|'s [=active document=]'s [=document/origin=]. + 1. If the result of running [=determine whether shared storage is allowed=] given |environment| and [=current global object=]'s [=SharedStorageWorkletGlobalScope/worklet origin=] is false, return a [=promise rejected=] with a {{TypeError}}. + 1. Let |site| be the result of running [=obtain a site=] with [=current global object=]'s [=SharedStorageWorkletGlobalScope/worklet origin=]. 1. [=Assert=] that |site| is not an [=opaque origin=]. 1. Let |queue| be |context|'s associated [=shared storage database|database=]'s [=shared storage database/shared storage database queue=]. 1. Let |realm| be the [=current realm=]. @@ -1194,7 +1282,7 @@ On the other hand, methods for getting data from the [=shared storage database=] 1. If |context| is null, return a [=promise rejected=] with a {{TypeError}}. 1. If |context|'s [=active window=]'s [=associated document=] is not [=fully active=], return a [=promise rejected=] with a {{TypeError}}. 1. Let |environment| be |context|'s [=active window=]'s [=relevant settings object=]. - 1. Let |databaseMap| be the result of running [=obtain a shared storage bottle map=] for |environment|. + 1. Let |databaseMap| be the result of running [=obtain a shared storage bottle map=] given |environment| and [=current global object=]'s [=SharedStorageWorkletGlobalScope/worklet origin=]. 1. If |databaseMap| is failure, then return a [=promise rejected=] with a {{TypeError}}. 1. Let |queue| be |context|'s associated [=shared storage database|database=]'s [=shared storage database/shared storage database queue=]. 1. Let |realm| be the [=current realm=]. @@ -1392,9 +1480,7 @@ The IDL attribute {{HTMLSharedStorageWritableElementUtils/sharedStorageWritable} 1. Let |window| to |request|’s [=request/window=]. 1. If |window| is not an [=environment settings object=] whose [=global object=] is a {{Window}}, return false. - 1. If the result of running [=determine whether shared storage is allowed=] for |window| is false, return false. - 1. Let |document| be |window|'s [=global object=]'s [=associated document=]. - 1. Return the result of running [=Is feature enabled in document for origin?=] on "[=PermissionsPolicy/shared-storage=]", |document|, and |request|'s [=request/current URL=]'s [=url/origin=]. + 1. If the result of running [=determine whether shared storage is allowed=] given |window| and |request|'s [=request/current URL=]'s [=url/origin=] is false, return false. Issue: 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.
@@ -1504,7 +1590,7 @@ Permissions Policy Integration {#permission} 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". -"[=PermissionsPolicy/shared-storage=]" gates access to Shared Storage in general, whereas "[=shared-storage-select-url=]" adds an exra permission layer to {{WindowSharedStorage/selectURL()}}. For each of these, the default allowlist is *. +"[=PermissionsPolicy/shared-storage=]" gates access to Shared Storage in general, whereas "[=shared-storage-select-url=]" adds an exra permission layer to {{SharedStorageWorklet/selectURL()}}. For each of these, the default allowlist is *. Clear Site Data Integration {#clear} ==================================== From 6606cc0e380b8ff1c2aa2d927c3dd3b03febadbb Mon Sep 17 00:00:00 2001 From: Yao Xiao Date: Tue, 30 Jan 2024 19:25:56 -0500 Subject: [PATCH 02/15] Address review comments --- spec.bs | 460 +++++++++++++++++++++++++++++--------------------------- 1 file changed, 239 insertions(+), 221 deletions(-) diff --git a/spec.bs b/spec.bs index bca7ed4..d99b437 100644 --- a/spec.bs +++ b/spec.bs @@ -228,7 +228,7 @@ Each {{SharedStorageWorklet}} has an associated boolean To check if user preference setting allows access to shared storage given an [=environment settings object=] |environment| and an [=url/origin=] |origin|, run the following step: @@ -236,7 +236,7 @@ When {{Worklet/addModule()}} is called for a worklet, it will run [=check if add
- To determine whether shared storage is allowed, given an [=environment settings object=] |environment| and an [=url/origin=] |origin|, run these steps: + To determine whether shared storage is allowed by context, given an [=environment settings object=] |environment| and an [=url/origin=] |origin|, run these steps: 1. If |environment| is not a [=secure context=], then return false. 1. Let |outsideSettingsOrigin| be |environment|'s [=environment settings object/origin=]. @@ -245,30 +245,36 @@ When {{Worklet/addModule()}} is called for a worklet, it will run [=check if add 1. Let |globalObject| be the [=current realm=]'s [=global object=]. 1. [=Assert=] that |globalObject| is a {{Window}} or a {{SharedStorageWorkletGlobalScope}}. 1. If |globalObject| is a {{Window}}, and if the result of running [=Is feature enabled in document for origin?=] on "[=PermissionsPolicy/shared-storage=]", |globalObject|'s [=associated document=], and |origin| returns false, then return false. + 1. Return true. +
+ +
+ To determine whether shared storage is allowed by enrollment and user preference, given an [=environment settings object=] |environment| and an [=url/origin=] |origin|, run these steps: + 1. If the result of running [=obtaining a site|obtain a site=] with |origin| is not [=enrolled=], then return false. 1. If the result of running [=check if user preference setting allows access to shared storage=] given |environment| and |origin| is false, then return false. 1. Return true. +
-
- Here are the scenarios where this algorithm could be used: - - For Window setter/deleter methods, |environment|'s [=environment settings object/origin=] should equal |origin|. - - For creating a worklet, and for initiating (from {{Window}}) and running (from {{SharedStorageWorkletGlobalScope}}) operations on a worklet, |environment| should be the worklet's creator window, and |origin| should be the [=SharedStorageWorklet/worklet origin=]. - - For fetch request, |environment| should be the request's initiator window, and |origin| should be the request's URL's origin. - -
+
+ Here are the scenarios where the algorithms [=determine whether shared storage is allowed by context=] and [=determine whether shared storage is allowed by enrollment and user preference=] could be used: + - For Window setter/deleter methods, |environment|'s [=environment settings object/origin=] should equal |origin|. + - For creating a worklet, and for initiating (from {{Window}}) and running (from {{SharedStorageWorkletGlobalScope}}) operations on a worklet, |environment| should be the worklet's creator window, and |origin| should be the [=SharedStorageWorklet/worklet origin=]. + - For a fetch request, |environment| should be the request's initiator window, and |origin| should be the request's URL's origin.
To check if addModule is allowed and update status given a {{SharedStorageWorklet}} |worklet| and an [=/URL=] |moduleURL|, run the following steps: 1. Let |workletOrigin| be |moduleURL|'s [=url/origin=]. - 1. If the result of running [=determine whether shared storage is allowed=] given [=this=]'s [=relevant settings object=] and |workletOrigin| is false, return false. - 1. If |worklet|'s [=addModule initiated=] is true, return false. + 1. If the result of running [=determine whether shared storage is allowed by context=] given [=this=]'s [=relevant settings object=] and |workletOrigin| is false, return "DisallowedDueToNonPreferenceError". + 1. If |worklet|'s [=addModule initiated=] is true, return "DisallowedDueToNonPreferenceError". 1. Set |worklet|'s [=addModule initiated=] to true. 1. If |workletOrigin| does not equal [=this=]'s [=relevant settings object=]'s [=environment settings object/origin=], set |worklet|'s [=SharedStorageWorklet/is cross-origin worklet=] to true. - 1. If |worklet|'s [=cross-origin worklet allowed=] is false, and if |worklet|'s [=SharedStorageWorklet/is cross-origin worklet=] is true, return false. + 1. If |worklet|'s [=cross-origin worklet allowed=] is false, and if |worklet|'s [=SharedStorageWorklet/is cross-origin worklet=] is true, return "DisallowedDueToNonPreferenceError". + 1. If the result of running [=determine whether shared storage is allowed by enrollment and user preference=] given [=this=]'s [=relevant settings object=] and |workletOrigin| is false, return "DisallowedDueToPreferenceError". 1. Set |worklet|'s [=SharedStorageWorklet/worklet origin=] to |workletOrigin|. - 1. Return true. + 1. Return "Allowed".
Moreover, each {{SharedStorageWorklet}}'s [=global scopes|list of global scopes=], initially empty, can contain at most one instance of its [=worklet global scope type=], the {{SharedStorageWorkletGlobalScope}}. @@ -282,7 +288,7 @@ Moreover, each {{SharedStorageWorklet}}'s [=global scopes|list of global scopes= 1. Let |promise| be a new [=promise=]. 1. Let |window| be [=current global object=]. - 1. Assert that |window| is {{Window}}. + 1. Assert that |window| is a {{Window}}. 1. Let |context| be |window|'s [=Window/browsing context=]. 1. If |context| is null, return a [=promise rejected=] with a {{TypeError}}. 1. Let |environment| be |context|'s [=active window=]'s [=relevant settings object=]. @@ -318,11 +324,11 @@ Moreover, each {{SharedStorageWorklet}}'s [=global scopes|list of global scopes= 1. Let |resultPromise| be a new [=promise=]. 1. If [=this=]'s [=addModule initiated=] is false, then return a [=promise rejected=] with a {{TypeError}}. 1. Let |window| be [=current global object=]. - 1. Assert that |window| is {{Window}}. + 1. Assert that |window| is a {{Window}}. 1. Let |context| be |window|'s [=Window/browsing context=]. 1. If |context| is null, return a [=promise rejected=] with a {{TypeError}}. 1. Let |document| be |context|'s [=active document=]. - 1. If the result of running [=Is feature enabled in document for origin?=] on "[=PermissionsPolicy/shared-storage=]", |document|, and [=this=]'s [=SharedStorageWorklet/worklet origin=] returns false, return a [=promise rejected=] with a {{TypeError}}. + 1. If the result of running [=Is feature enabled in document for origin?=] on "[=PermissionsPolicy/shared-storage-select-url=]", |document|, and [=this=]'s [=SharedStorageWorklet/worklet origin=] returns false, return a [=promise rejected=] with a {{TypeError}}. 1. If [=this=]'s [=global scopes|list of global scopes=] is [=list/empty=], then return a [=promise rejected=] with a {{TypeError}}. 1. [=Assert=] that [=this=]'s [=global scopes|list of global scopes=] [=list/contains=] a single {{SharedStorageWorkletGlobalScope}}. 1. If the result of running [=SharedStorageWorkletGlobalScope/check whether addModule is finished=] for [=this=]'s {{SharedStorageWorkletGlobalScope}} is false, return a [=promise rejected=] with a {{TypeError}}. @@ -339,7 +345,8 @@ Moreover, each {{SharedStorageWorklet}}'s [=global scopes|list of global scopes= 1. If the result of running [=validate reporting metadata=] with |reportingMetadata| is false, reject |resultPromise| with a {{TypeError}} and abort these steps. 1. Let |fencedFrameConfigStruct| be a "fenced frame config struct" with an invalid "urn uuid". Add correct struct name as well as linking when Fenced Frame API updates their draft spec to include it. 1. Let |environment| be |context|'s [=active window=]'s [=relevant settings object=]. - 1. If the result of running [=determine whether shared storage is allowed=] given |environment| and [=this=]'s [=SharedStorageWorklet/worklet origin=] is false: + 1. If the result of running [=determine whether shared storage is allowed by context=] given |environment| and [=this=]'s [=SharedStorageWorklet/worklet origin=] is false, return a [=promise rejected=] with a {{TypeError}}. + 1. If the result of running [=determine whether shared storage is allowed by enrollment and user preference=] given |environment| and [=this=]'s [=SharedStorageWorklet/worklet origin=] is false: 1. If [=this=]'s [=SharedStorageWorklet/is cross-origin worklet=] is false, return a [=promise rejected=] with a {{TypeError}}. 1. Else, set |fencedFrameConfigStruct| to a "fenced frame config struct" with a pending mapped "url uuid". Add correct struct name as well as linking when Fenced Frame API updates their draft spec to include it. @@ -360,10 +367,10 @@ Moreover, each {{SharedStorageWorklet}}'s [=global scopes|list of global scopes= 1. Set |fencedFrameConfigStruct|'s [=/url=] to |urlList|[|resultIndex|]. 1. Let |resultURLWithMetadata| be |urls|[|resultIndex|]. 1. If |resultURLWithMetadata| has field "`reportingMetadata`", run [=register reporting metadata=] with |resultURLWithMetadata|["`reportingMetadata`"]. - 1. If |options|["`keepAlive`"] is false, run [=terminate a worklet global scope=] with the associated {{SharedStorageWorkletGlobalScope}}. + 1. If |options|["`keepAlive`"] is false, run [=terminate a worklet global scope=] with [=this=]'s {{SharedStorageWorkletGlobalScope}}. 1. [=Upon rejection=] of |indexPromise|, perform the following steps: 1. Set |fencedFrameConfigStruct|'s [=/url=] to |urlList|[[=default index=]]. - 1. If |options|["`keepAlive`"] is false, run [=terminate a worklet global scope=] with the associated {{SharedStorageWorkletGlobalScope}}. + 1. If |options|["`keepAlive`"] is false, run [=terminate a worklet global scope=] with [=this=]'s {{SharedStorageWorkletGlobalScope}}. 1. Return |resultPromise|. @@ -373,20 +380,21 @@ Moreover, each {{SharedStorageWorklet}}'s [=global scopes|list of global scopes= 1. Let |promise| be a new [=promise=]. 1. If [=this=]'s [=addModule initiated=] is false, then return a [=promise rejected=] with a {{TypeError}}. 1. Let |window| be [=current global object=]. - 1. Assert that |window| is {{Window}}. + 1. Assert that |window| is a {{Window}}. 1. If [=this=]'s [=global scopes|list of global scopes=] is [=list/empty=], then return a [=promise rejected=] with a {{TypeError}}. 1. [=Assert=] that [=this=]'s [=global scopes|list of global scopes=] [=list/contains=] a single {{SharedStorageWorkletGlobalScope}}. 1. If the result of running [=SharedStorageWorkletGlobalScope/check whether addModule is finished=] for [=this=]'s {{SharedStorageWorkletGlobalScope}} is false, return a [=promise rejected=] with a {{TypeError}}. 1. Let |realm| be the [=current realm=]. - 1. Let |outsideSettings| be {{WindowSharedStorage/worklet}}'s [=relevant settings object=]. - 1. If the result of running [=determine whether shared storage is allowed=] given |outsideSettings| and [=this=]'s [=SharedStorageWorklet/worklet origin=] is false: + 1. Let |outsideSettings| be [=this=]'s [=relevant settings object=]. + 1. If the result of running [=determine whether shared storage is allowed by context=] given |outsideSettings| and [=this=]'s [=SharedStorageWorklet/worklet origin=] is false, reject |promise| with a {{TypeError}}. + 1. If the result of running [=determine whether shared storage is allowed by enrollment and user preference=] given |outsideSettings| and [=this=]'s [=SharedStorageWorklet/worklet origin=] is false: 1. If [=this=]'s [=SharedStorageWorklet/is cross-origin worklet=] is false, reject |promise| with a {{TypeError}}. 1. Else, resolve |promise| with undefined. 1. Return |promise|. 1. Let |agent| be the result of [=obtaining a worklet agent=] given |outsideSettings|. 1. Run the following steps in |agent|: 1. [=Queue a global task=] on the [=DOM manipulation task source=], given |realm|'s [=global object=], to resolve |promise| with undefined. - 1. If {{WindowSharedStorage/worklet}}'s [=module map=] is not [=map/empty=]: + 1. If [=this=]'s [=module map=] is not [=map/empty=]: 1. Let |operationMap| be [=this=]'s {{SharedStorageWorkletGlobalScope}}'s [=SharedStorageWorkletGlobalScope/operation map=]. 1. If |operationMap| [=map/contains=] |name|: 1. Let |operation| be |operationMap|[|name|]. @@ -397,7 +405,7 @@ Moreover, each {{SharedStorageWorklet}}'s [=global scopes|list of global scopes= 1. Otherwise, [=call=] |operation| without any arguments list. 1. If |options|["`keepAlive`"] is false: 1. Wait for |operation| to finish running, if applicable. - 1. Run [=terminate a worklet global scope=] with {{SharedStorageWorkletGlobalScope}}. + 1. Run [=terminate a worklet global scope=] with [=this=]'s {{SharedStorageWorkletGlobalScope}}. 1. Return |promise|. @@ -407,13 +415,20 @@ Moreover, each {{SharedStorageWorklet}}'s [=global scopes|list of global scopes= In particular, the {{Worklet/addModule()}} method steps for {{Worklet}} will need to be prepended with the following step: - 0. If |this| is of type {{SharedStorageWorklet}}, and the result of running [=check if addModule is allowed and update status=] given |this| and |moduleURL| is false: - 1. If |this|'s [=SharedStorageWorklet/is cross-origin worklet=] is true, then reject promise with a {{TypeError}}. Else, resolve promise. + 0. If |this| is of type {{SharedStorageWorklet}}: + 1. Let |addModuleAllowedResult| be the result of running [=check if addModule is allowed and update status=] given |this| and |moduleURL|. + 1. If |addModuleAllowedResult| is "DisallowedDueToNonPreferenceError": + 1. Reject promise with a {{TypeError}}. + 1. Else if |addModuleAllowedResult| is "DisallowedDueToPreferenceError": + 1. If |this|'s [=SharedStorageWorklet/is cross-origin worklet=] is false, then reject promise with a {{TypeError}}. Else, resolve promise. + 1. Else: + 1. [=Assert=] that |addModuleAllowedResult| is "Allowed". + 1. Resolve promise. 1. Return promise.
- On user preferences error, addModule() will be aborted at an early stage. However, the error will only be exposed to the caller for same-origin worklet (i.e. where the initiator document's origin is same-origin with the module script's origin). For 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). + On user preferences error, addModule() will be aborted at an early stage. However, the error will only be exposed to the caller for a same-origin worklet (i.e. where the initiator document's origin is same-origin with the module script's origin). For a cross-origin worklet, the error will be hidden. This is to prevent a caller from knowing which origins the user has disabled shared storage for via preferences (if a per-origin preference exists for that browser vendor). A caller may still use timing attacks to know this information, but this is a minor security/privacy issue, as in reality very few users would set such preferences, and doing a wide search would incur a significant performance cost spinning up the worklets. @@ -433,7 +448,7 @@ Moreover, each {{SharedStorageWorklet}}'s [=global scopes|list of global scopes= ## The {{SharedStorageWorkletGlobalScope}} ## {#global-scope} - The {{SharedStorageWorklet}}'s [=worklet global scope type=] is {{SharedStorageWorkletGlobalScope}}. + The {{SharedStorageWorklet}}'s [=worklet global scope type=] is {{SharedStorageWorkletGlobalScope}}. [Exposed=SharedStorageWorklet, Global=SharedStorageWorklet] @@ -520,6 +535,196 @@ Moreover, each {{SharedStorageWorklet}}'s [=global scopes|list of global scopes= 1. Return the value of [=addModule success=]. </div> + ## {{SharedStorageUrlWithMetadata}} and Reporting ## {#reporting} + + A {{SharedStorageUrlWithMetadata}} {{/object}} is a [=dictionary=] containing a [=string=] representing a [=/URL=] and, optionally, a {{reportingMetadata}} {{/object}}. + + <xmp class='idl'> + dictionary SharedStorageUrlWithMetadata { + required USVString url; + object reportingMetadata; + }; + + + If a {{SharedStorageUrlWithMetadata}} {{/object}} contains a non-[=map/empty=] {{SharedStorageUrlWithMetadata/reportingMetadata}} {{/object}} in the form of a [=dictionary=] whose [=map/keys=] are [=FenceEvent/eventTypes=] and whose [=map/values=] are [=strings=] that parse to valid [=/URLs=], then these [=FenceEvent/eventType=]-[=/URL=] pairs will be [=register reporting metadata|registered=] for later access within any [=fenced frame=] that loads the {{SharedStorageResponse}} resulting from this {{SharedStorageWorklet/selectURL()}} call. + + Inside a [=fenced frame=] with [=FenceEvent/eventType=]-[=/URL=] pairs that have been [=register reporting metadata|registered=] through {{SharedStorageWorklet/selectURL()}} with {{SharedStorageUrlWithMetadata/reportingMetadata}} {{/object}}s, if [=fence.reportEvent()=] is called on a [=FenceEvent=] with a [=FenceEvent/destination=] [=list/containing=] "`shared-storage-select-url`" and that [=FenceEvent=]'s corresponding [=FenceEvent/eventType=] is triggered, then the [=FenceEvent=]'s [=FenceEvent/eventData=] will be sent as a [=beacon=] to the registered [=/URL=] for that [=FenceEvent/eventType=]. + +
+ To validate reporting metadata, given an {{/object}} |reportingMetadata|, run the following steps: + + 1. If |reportingMetadata| is [=map/empty=], return true. + 1. If |reportingMetadata| is not a [=dictionary=], return false. + 1. [=map/iterate|For each=] eventType -> |urlString| of |reportingMetadata|, if the result of running [=get the canonical URL string if valid=] with |urlString| is undefined, return false. + 1. Return true. +
+ +
+ To get the canonical URL string if valid, given a [=string=] |urlString|, run the following steps: + + 1. Let |url| be the result of running a [=URL parser=] on |urlString|. + 1. If |url| is not a valid [=/URL=], return undefined. + 1. Otherwise, return the result of running a [=URL serializer=] on |url|. +
+ +
+ To register reporting metadata, given an {{/object}} |reportingMetadata| and a "fenced frame config struct" |fencedFrameConfigStruct|, run the following steps: + + 1. If |reportingMetadata| is [=map/empty=], return. + 1. [=Assert=] that |reportingMetadata| is a [=dictionary=]. + 1. Let |reportingUrlMap| be an [=map/empty=] [=map=]. + 1. [=map/iterate|For each=] |eventType| -> |urlString| of |reportingMetadata|: + 1. Let |url| be the result of running a [=URL parser=] on |urlString|. + 1. [=Assert=] that |url| is a valid [=/URL=]. + 1. [=map/Set=] |reportingUrlMap|[|eventType|] to |url|. + 1. Store |reportingUrlMap| inside a "fenced frame reporter" class associated with |fencedFrameConfigStruct|. Both of these still need to be added to the draft [=fenced frame|Fenced Frame specification=]. +
+ + ## Entropy Budgets ## {#budgets} + + Because [=bits of entropy=] can leak via {{SharedStorageWorklet/selectURL()}}, the [=user agent=] will need to maintain budgets to limit these leaks. + + ### Navigation Entropy Budget ### {#nav-budget} + + If a user activates a [=fenced frame=] whose {{FencedFrameConfig}} was generated by {{SharedStorageWorklet/selectURL()}} and thereby initiates a [=top-frame=] [=navigate|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 {{SharedStorageWorklet/selectURL()}}. To mitigate this, a [=user agent=] will set a per-[=calling site=] [=navigation entropy allowance=]. + + A calling site for {{SharedStorageWorklet/selectURL()}} is the [=site=] resulting from running [=obtain a site=] with the [=url/origin=] of an [=environment=] that makes a {{SharedStorageWorklet/selectURL()}} call. + + A navigation entropy allowance is a maximum allowance of [=entropy bits=] that are permitted to leak via [=fenced frames=] initiating [=top-frame=] [=navigate|navigations=] during a given [=navigation budget epoch=] for a given calling [=calling site=]. This [=navigation entropy allowance|allowance=] is defined by the [=user agent=] and is [=calling site=]-agnostic. + + A [=user agent=] will define a fixed predetermined [=duration=] navigation budget lifetime. + + An navigation budget epoch is any interval of time whose [=duration=] is the [=navigation budget lifetime=]. + + To keep track of how this [=navigation entropy allowance=] is used, the [=user agent=] uses a shared storage navigation budget table, which is a [=map=] of [=calling sites=] to [=navigation entropy ledgers=]. + + An navigation entropy ledger is a [=/list=] of [=bit debits=]. + + A bit debit is a [=struct=] containing a {{double}} bits, indicating a value in [=entropy bits=], along with a {{DOMHighResTimeStamp}} timestamp (from the [=Unix Epoch=]). + + [=Bit debits=] whose [=bit debit/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 [=bit debit/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 [=bit debit/timestamps=] are within the current [=navigation budget epoch=]. + + {{SharedStorageWorklet/selectURL()}}'s argument "`urls`" is its input URL list. + + When a [=calling site=] has insufficient [=calling site/remaining navigation budget=], {{SharedStorageWorklet/selectURL()}} will return a {{SharedStorageResponse}} (i.e. either a {{FencedFrameConfig}} or an opaque [=/URL=]) for the {{SharedStorageUrlWithMetadata/url}} in the {{SharedStorageUrlWithMetadata}} at the [=default index=] in its [=selectURL/input URL list=]. + + The default index for a call to {{SharedStorageWorklet/selectURL()}} is implementation-defined in such a way that it is independent from the result of the associated {{SharedStorageSelectURLOperation}}'s "`run`" method. + +
+ The [=default index=] could be defined to be 0. + + In this case, whenever the {{SharedStorageSelectURLOperation}}'s "`run`" method encounters an error, or whenever there is insufficient [=calling site/remaining navigation budget=], the "`run`" method would return 0, and hence {{SharedStorageWorklet/selectURL()}} would return a {{SharedStorageResponse}} for the first {{SharedStorageUrlWithMetadata/url}} in its [=selectURL/input URL list=]. +
+ +
+ The [=default index=] could be defined to be [=selectURL/input URL list=]'s [=list/size=] minus 1. + + In this case, whenever the {{SharedStorageSelectURLOperation}}'s "`run`" method encounters an error, or whenever there is insufficient [=calling site/remaining navigation budget=], {{SharedStorageWorklet/selectURL()}} would return a {{SharedStorageResponse}} for the last {{SharedStorageUrlWithMetadata/url}} in its [=selectURL/input URL list=]. +
+ +
+ To determine remaining navigation budget, given an [=environment settings object=] |environment| and a [=calling site=] |site|, run the following steps: + + 1. If |site| is an [=opaque origin=], return undefined. + 1. Let |maxBits| be the [=user agent=]'s [=navigation entropy allowance=]. + 1. If the [=user agent=]'s [=shared storage navigation budget table=] does not [=map/contain=] |site|, then return |maxBits|. + 1. Otherwise, let |ledger| be [=user agent=]'s [=shared storage navigation budget table=][|site|]. + 1. Let |debitSum| be 0. + 1. [=map/iterate|For each=] [=list/item=] |bitDebit| in |ledger|, do the following steps: + 1. Let |debit| be |bitDebit|'s [=bit debit/bits=]. + 1. If the result of running [=check whether a bit debit is expired=] with |environment| and |bitDebit| is false, then increment |debitSum| by |debit|. + 1. Return |maxBits| minus |debitSum|. +
+ +
+ To check whether a bit debit is expired, given an [=environment settings object=] |environment| and a [=bit debit=] |bitDebit|, run the following steps: + + 1. Let |epochLength| be the [=user agent=]'s [=navigation budget lifetime=]. + 1. Let |currentTime| be |environment|'s [=environment settings object/current wall time=]. + 1. Let |threshold| be |currentTime| minus |epochLength|. + 1. If |bitDebit|'s [=bit debit/timestamp=] is less than |threshold|, return true. + 1. Otherwise, return false. +
+ + A [=bit debit=] will need to be [=charge shared storage navigation budget|charged=] to the [=shared storage navigation budget table=] for each [=top-frame=] [=navigate|navigation=] initiated by a [=fenced frame=] whose {{FencedFrameConfig}} was generated via {{SharedStorageWorklet/selectURL()}}, as this can leak cross-site data. Since the [=bit debit/bits=] to charge is calculated during the call to {{SharedStorageWorklet/selectURL()}} but only actually recorded in the [=shared storage navigation budget table=] if and when the resulting fenced frame initiates a [=top-frame=] [=beginning navigation|navigation=], the [=bit debit/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. + + Issue: The "fenced frame config struct" and its boolean has navigated have not yet been added to the draft [=Fenced Frame=] specification. Some form of them will be added, although their names are subject to bikeshedding. Fix the names when they are added. + +
+ To charge shared storage navigation budget during a [=beginning navigation|navigation=] with [=/navigable=] |navigable| and {{Document}} |sourceDocument|, run the following steps: + + 1. If |navigable| is not a [=navigable/traversable navigable=], return. + 1. Let |node| be |sourceDocument|'s [=node navigable=]. + 1. While |node| is not null: + 1. Let |site| be the result of running [=obtain a site=] with |node|'s [=active document=]'s [=document/origin=]. + 1. If |node| has a "fenced frame config struct" and |site| is not an [=opaque origin=], perform the following steps: + 1. Let |pendingBits| be |node|'s "fenced frame config struct"'s [=pending shared storage budget debit=]. + 1. If |pendingBits| is greater than 0 and if "fenced frame config struct"'s [=has navigated=] is false, run the following steps: + 1. Let |ledger| be [=user agent=]'s [=shared storage navigation budget table=][|site|]. + 1. Let |bitDebit| be a new [=bit debit=]. + 1. Set |bitDebit|'s [=bit debit/bits=] to |pendingBits|. + 1. Let |currentTime| be the [=/current wall time=]. + 1. Set |bitDebit|'s [=bit debit/timestamp=] to |currentTime|. + 1. [=list/Append=] |bitDebit| to |ledger|. + 1. Set |node|'s "fenced frame config struct"'s [=has navigated=] to true. + 1. Set |node| to |node|'s [=navigable/parent=]. +
+ + ### Reporting Entropy Budget ### {#report-budget} + + Likewise, each time a call to [=fence.reportEvent()=] from a [=fenced frame=] originating via {{SharedStorageWorklet/selectURL()}} whose [=FenceEvent/destination=] [=list/contains=] "`shared-storage-select-url`" and whose [=FenceEvent/eventType=] is triggered, there is a leak of up to logarithm base 2 of the number of main input [=/URLs=] [=entropy bits=]. The [=user agent=] will need to set a per-[=page load=] [=reporting entropy allowance=] to restrict the information leaked, with page load referring to a [=top-level traversable=]'s (i.e. primary main frame's) lifecycle. + + A reporting entropy allowance is a maximum allowance of [=entropy bits=] that are permitted to leak via [=fence.reportEvent()=] during a given page load. This [=reporting entropy allowance|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 [=FenceEvent/destination=] [=list/containing=] "`shared-storage-select-url`", it will be necessary to [=charge shared storage reporting budget=] as below. + +
+ To determine reporting budget to charge, given a {{Document}} |sourceDocument|, run the following steps: + + 1. Let |debitSum| be 0. + 1. Let |node| be |sourceDocument|'s [=node navigable=]. + 1. While |node| is not null: + 1. If |node| has a "fenced frame config struct": + 1. Let |pendingBits| be |node|'s "fenced frame config struct"'s [=pending shared storage budget debit=]. + 1. If |pendingBits| is greater than 0 and if "fenced frame config struct"'s [=has reported=] is false, increment |debitSum| by |pendingBits| + 1. Set |node| to |node|'s [=navigable/parent=]. + 1. Return |debitSum|. +
+ + + Issue: The "fenced frame config struct" and its boolean has reported have not yet been added to the draft [=Fenced Frame=] specification. Some form of them will be added, although their names are subject to bikeshedding. Fix the names when they are added. + +
+ To charge shared storage reporting budget given a {{Document}} |sourceDocument|, run the following steps: + + 1. Let |toCharge| be the result of running [=determine reporting budget to charge=] with |sourceDocument|. + 1. Let |node| be |sourceDocument|'s [=node navigable=]. + 1. Let |topNode| be the result of running [=get the top-level traversable=] for |node|. + 1. If |topNode|'s [=shared storage reporting budget=] is less than |toCharge|, return false. + 1. While |node| is not null: + 1. If |node| has a "fenced frame config struct" and if |node|'s "fenced frame config struct"'s [=pending shared storage budget debit=] is greater than 0, set |node|'s "fenced frame config struct"'s [=has reported=] to true. + 1. Set |node| to |node|'s [=navigable/parent=]. + 1. Decrement |topNode|'s [=shared storage reporting budget=] by |toCharge|. + 1. Return true. +
+ + A [=user agent=] may wish to set a timer to periodically [=purge expired bit debits from all navigation entropy ledgers=], as the [=bit debit/expired=] [=bit debits=] will no longer be needed. + +
+ To purge expired bit debits from all navigation entropy ledgers, run the following steps: + + 1. [=map/iterate|For each=] origin -> |ledger| of [=user agent=]'s [=shared storage navigation budget table=]: + 1. [=map/iterate|For each=] |bitDebit| in |ledger|, if the result of running [=check whether a bit debit is expired=] with |bitDebit| is true, [=list/remove=] |bitDebit| from |ledger|. +
+ Shared Storage's Backend {#backend} =================================== The Shared Storage API will integrate into the [=Storage Model|Storage API=] as below, via [=storage endpoint/registering=] a new [=storage endpoint=]. @@ -545,7 +750,8 @@ The Shared Storage API will integrate into the [=Storage Model|Storage API=] as
To obtain a shared storage shelf, given a [=shared storage shed=] |shed|, an [=environment settings object=] |environment|, and an [=url/origin=] |origin|, run these steps: - 1. If the result of running [=determine whether shared storage is allowed=] given |environment| and |origin| is false, then return failure. + 1. If the result of running [=determine whether shared storage is allowed by context=] given |environment| and |origin| is false, then return failure. + 1. If the result of running [=determine whether shared storage is allowed by enrollment and user preference=] given |environment| and |origin| is false, then return failure. 1. If |shed|[origin] does not exist, then set |shed|[origin] to the result of running [=create a shared storage shelf=] with [=storage type|type=] "`shared`". 1. Return |shed|[|origin|].
@@ -848,196 +1054,6 @@ On the other hand, methods for getting data from the [=shared storage database=] 1. Return |promise|.
- ### {{SharedStorageUrlWithMetadata}} and Reporting ### {#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-[=map/empty=] {{SharedStorageUrlWithMetadata/reportingMetadata}} {{/object}} in the form of a [=dictionary=] whose [=map/keys=] are [=FenceEvent/eventTypes=] and whose [=map/values=] are [=strings=] that parse to valid [=/URLs=], then these [=FenceEvent/eventType=]-[=/URL=] pairs will be [=register reporting metadata|registered=] for later access within any [=fenced frame=] that loads the {{SharedStorageResponse}} resulting from this {{SharedStorageWorklet/selectURL()}} call. - - Inside a [=fenced frame=] with [=FenceEvent/eventType=]-[=/URL=] pairs that have been [=register reporting metadata|registered=] through {{SharedStorageWorklet/selectURL()}} with {{SharedStorageUrlWithMetadata/reportingMetadata}} {{/object}}s, if [=fence.reportEvent()=] is called on a [=FenceEvent=] with a [=FenceEvent/destination=] [=list/containing=] "`shared-storage-select-url`" and that [=FenceEvent=]'s corresponding [=FenceEvent/eventType=] is triggered, then the [=FenceEvent=]'s [=FenceEvent/eventData=] will be sent as a [=beacon=] to the registered [=/URL=] for that [=FenceEvent/eventType=]. - -
- To validate reporting metadata, given an {{/object}} |reportingMetadata|, run the following steps: - - 1. If |reportingMetadata| is [=map/empty=], return true. - 1. If |reportingMetadata| is not a [=dictionary=], return false. - 1. [=map/iterate|For each=] eventType -> |urlString| of |reportingMetadata|, if the result of running [=get the canonical URL string if valid=] with |urlString| is undefined, return false. - 1. Return true. -
- -
- To get the canonical URL string if valid, given a [=string=] |urlString|, run the following steps: - - 1. Let |url| be the result of running a [=URL parser=] on |urlString|. - 1. If |url| is not a valid [=/URL=], return undefined. - 1. Otherwise, return the result of running a [=URL serializer=] on |url|. -
- -
- To register reporting metadata, given an {{/object}} |reportingMetadata| and a "fenced frame config struct" |fencedFrameConfigStruct|, run the following steps: - - 1. If |reportingMetadata| is [=map/empty=], return. - 1. [=Assert=] that |reportingMetadata| is a [=dictionary=]. - 1. Let |reportingUrlMap| be an [=map/empty=] [=map=]. - 1. [=map/iterate|For each=] |eventType| -> |urlString| of |reportingMetadata|: - 1. Let |url| be the result of running a [=URL parser=] on |urlString|. - 1. [=Assert=] that |url| is a valid [=/URL=]. - 1. [=map/Set=] |reportingUrlMap|[|eventType|] to |url|. - 1. Store |reportingUrlMap| inside a "fenced frame reporter" class associated with |fencedFrameConfigStruct|. Both of these still need to be added to the draft [=fenced frame|Fenced Frame specification=]. -
- - ### Entropy Budgets ### {#budgets} - - Because [=bits of entropy=] can leak via {{SharedStorageWorklet/selectURL()}}, the [=user agent=] will need to maintain budgets to limit these leaks. - - #### Navigation Entropy Budget #### {#nav-budget} - - If a user activates a [=fenced frame=] whose {{FencedFrameConfig}} was generated by {{SharedStorageWorklet/selectURL()}} and thereby initiates a [=top-frame=] [=navigate|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 {{SharedStorageWorklet/selectURL()}}. To mitigate this, a [=user agent=] will set a per-[=calling site=] [=navigation entropy allowance=]. - - A calling site for {{SharedStorageWorklet/selectURL()}} is the [=site=] resulting from running [=obtain a site=] with the [=url/origin=] of an [=environment=] that makes a {{SharedStorageWorklet/selectURL()}} call. - - A navigation entropy allowance is a maximum allowance of [=entropy bits=] that are permitted to leak via [=fenced frames=] initiating [=top-frame=] [=navigate|navigations=] during a given [=navigation budget epoch=] for a given calling [=calling site=]. This [=navigation entropy allowance|allowance=] is defined by the [=user agent=] and is [=calling site=]-agnostic. - - A [=user agent=] will define a fixed predetermined [=duration=] navigation budget lifetime. - - An navigation budget epoch is any interval of time whose [=duration=] is the [=navigation budget lifetime=]. - - To keep track of how this [=navigation entropy allowance=] is used, the [=user agent=] uses a shared storage navigation budget table, which is a [=map=] of [=calling sites=] to [=navigation entropy ledgers=]. - - An navigation entropy ledger is a [=/list=] of [=bit debits=]. - - A bit debit is a [=struct=] containing a {{double}} bits, indicating a value in [=entropy bits=], along with a {{DOMHighResTimeStamp}} timestamp (from the [=Unix Epoch=]). - - [=Bit debits=] whose [=bit debit/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 [=bit debit/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 [=bit debit/timestamps=] are within the current [=navigation budget epoch=]. - - {{SharedStorageWorklet/selectURL()}}'s argument "`urls`" is its input URL list. - - When a [=calling site=] has insufficient [=calling site/remaining navigation budget=], {{SharedStorageWorklet/selectURL()}} will return a {{SharedStorageResponse}} (i.e. either a {{FencedFrameConfig}} or an opaque [=/URL=]) for the {{SharedStorageUrlWithMetadata/url}} in the {{SharedStorageUrlWithMetadata}} at the [=default index=] in its [=selectURL/input URL list=]. - - The default index for a call to {{SharedStorageWorklet/selectURL()}} is implementation-defined in such a way that it is independent from the result of the associated {{SharedStorageSelectURLOperation}}'s "`run`" method. - -
- The [=default index=] could be defined to be 0. - - In this case, whenever the {{SharedStorageSelectURLOperation}}'s "`run`" method encounters an error, or whenever there is insufficient [=calling site/remaining navigation budget=], the "`run`" method would return 0, and hence {{SharedStorageWorklet/selectURL()}} would return a {{SharedStorageResponse}} for the first {{SharedStorageUrlWithMetadata/url}} in its [=selectURL/input URL list=]. -
- -
- The [=default index=] could be defined to be [=selectURL/input URL list=]'s [=list/size=] minus 1. - - In this case, whenever the {{SharedStorageSelectURLOperation}}'s "`run`" method encounters an error, or whenever there is insufficient [=calling site/remaining navigation budget=], {{SharedStorageWorklet/selectURL()}} would return a {{SharedStorageResponse}} for the last {{SharedStorageUrlWithMetadata/url}} in its [=selectURL/input URL list=]. -
- -
- To determine remaining navigation budget, given an [=environment settings object=] |environment| and a [=calling site=] |site|, run the following steps: - - 1. If |site| is an [=opaque origin=], return undefined. - 1. Let |maxBits| be the [=user agent=]'s [=navigation entropy allowance=]. - 1. If the [=user agent=]'s [=shared storage navigation budget table=] does not [=map/contain=] |site|, then return |maxBits|. - 1. Otherwise, let |ledger| be [=user agent=]'s [=shared storage navigation budget table=][|site|]. - 1. Let |debitSum| be 0. - 1. [=map/iterate|For each=] [=list/item=] |bitDebit| in |ledger|, do the following steps: - 1. Let |debit| be |bitDebit|'s [=bit debit/bits=]. - 1. If the result of running [=check whether a bit debit is expired=] with |environment| and |bitDebit| is false, then increment |debitSum| by |debit|. - 1. Return |maxBits| minus |debitSum|. -
- -
- To check whether a bit debit is expired, given an [=environment settings object=] |environment| and a [=bit debit=] |bitDebit|, run the following steps: - - 1. Let |epochLength| be the [=user agent=]'s [=navigation budget lifetime=]. - 1. Let |currentTime| be |environment|'s [=environment settings object/current wall time=]. - 1. Let |threshold| be |currentTime| minus |epochLength|. - 1. If |bitDebit|'s [=bit debit/timestamp=] is less than |threshold|, return true. - 1. Otherwise, return false. -
- - A [=bit debit=] will need to be [=charge shared storage navigation budget|charged=] to the [=shared storage navigation budget table=] for each [=top-frame=] [=navigate|navigation=] initiated by a [=fenced frame=] whose {{FencedFrameConfig}} was generated via {{SharedStorageWorklet/selectURL()}}, as this can leak cross-site data. Since the [=bit debit/bits=] to charge is calculated during the call to {{SharedStorageWorklet/selectURL()}} but only actually recorded in the [=shared storage navigation budget table=] if and when the resulting fenced frame initiates a [=top-frame=] [=beginning navigation|navigation=], the [=bit debit/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. - - Issue: The "fenced frame config struct" and its boolean has navigated have not yet been added to the draft [=Fenced Frame=] specification. Some form of them will be added, although their names are subject to bikeshedding. Fix the names when they are added. - -
- To charge shared storage navigation budget during a [=beginning navigation|navigation=] with [=/navigable=] |navigable| and {{Document}} |sourceDocument|, run the following steps: - - 1. If |navigable| is not a [=navigable/traversable navigable=], return. - 1. Let |node| be |sourceDocument|'s [=node navigable=]. - 1. While |node| is not null: - 1. Let |site| be the result of running [=obtain a site=] with |node|'s [=active document=]'s [=document/origin=]. - 1. If |node| has a "fenced frame config struct" and |site| is not an [=opaque origin=], perform the following steps: - 1. Let |pendingBits| be |node|'s "fenced frame config struct"'s [=pending shared storage budget debit=]. - 1. If |pendingBits| is greater than 0 and if "fenced frame config struct"'s [=has navigated=] is false, run the following steps: - 1. Let |ledger| be [=user agent=]'s [=shared storage navigation budget table=][|site|]. - 1. Let |bitDebit| be a new [=bit debit=]. - 1. Set |bitDebit|'s [=bit debit/bits=] to |pendingBits|. - 1. Let |currentTime| be the [=/current wall time=]. - 1. Set |bitDebit|'s [=bit debit/timestamp=] to |currentTime|. - 1. [=list/Append=] |bitDebit| to |ledger|. - 1. Set |node|'s "fenced frame config struct"'s [=has navigated=] to true. - 1. Set |node| to |node|'s [=navigable/parent=]. -
- - #### Reporting Entropy Budget #### {#report-budget} - - Likewise, each time a call to [=fence.reportEvent()=] from a [=fenced frame=] originating via {{SharedStorageWorklet/selectURL()}} whose [=FenceEvent/destination=] [=list/contains=] "`shared-storage-select-url`" and whose [=FenceEvent/eventType=] is triggered, there is a leak of up to logarithm base 2 of the number of main input [=/URLs=] [=entropy bits=]. The [=user agent=] will need to set a per-[=page load=] [=reporting entropy allowance=] to restrict the information leaked, with page load referring to a [=top-level traversable=]'s (i.e. primary main frame's) lifecycle. - - A reporting entropy allowance is a maximum allowance of [=entropy bits=] that are permitted to leak via [=fence.reportEvent()=] during a given page load. This [=reporting entropy allowance|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 [=FenceEvent/destination=] [=list/containing=] "`shared-storage-select-url`", it will be necessary to [=charge shared storage reporting budget=] as below. - -
- To determine reporting budget to charge, given a {{Document}} |sourceDocument|, run the following steps: - - 1. Let |debitSum| be 0. - 1. Let |node| be |sourceDocument|'s [=node navigable=]. - 1. While |node| is not null: - 1. If |node| has a "fenced frame config struct": - 1. Let |pendingBits| be |node|'s "fenced frame config struct"'s [=pending shared storage budget debit=]. - 1. If |pendingBits| is greater than 0 and if "fenced frame config struct"'s [=has reported=] is false, increment |debitSum| by |pendingBits| - 1. Set |node| to |node|'s [=navigable/parent=]. - 1. Return |debitSum|. -
- - - Issue: The "fenced frame config struct" and its boolean has reported have not yet been added to the draft [=Fenced Frame=] specification. Some form of them will be added, although their names are subject to bikeshedding. Fix the names when they are added. - -
- To charge shared storage reporting budget given a {{Document}} |sourceDocument|, run the following steps: - - 1. Let |toCharge| be the result of running [=determine reporting budget to charge=] with |sourceDocument|. - 1. Let |node| be |sourceDocument|'s [=node navigable=]. - 1. Let |topNode| be the result of running [=get the top-level traversable=] for |node|. - 1. If |topNode|'s [=shared storage reporting budget=] is less than |toCharge|, return false. - 1. While |node| is not null: - 1. If |node| has a "fenced frame config struct" and if |node|'s "fenced frame config struct"'s [=pending shared storage budget debit=] is greater than 0, set |node|'s "fenced frame config struct"'s [=has reported=] to true. - 1. Set |node| to |node|'s [=navigable/parent=]. - 1. Decrement |topNode|'s [=shared storage reporting budget=] by |toCharge|. - 1. Return true. -
- - A [=user agent=] may wish to set a timer to periodically [=purge expired bit debits from all navigation entropy ledgers=], as the [=bit debit/expired=] [=bit debits=] will no longer be needed. - -
- To purge expired bit debits from all navigation entropy ledgers, run the following steps: - - 1. [=map/iterate|For each=] origin -> |ledger| of [=user agent=]'s [=shared storage navigation budget table=]: - 1. [=map/iterate|For each=] |bitDebit| in |ledger|, if the result of running [=check whether a bit debit is expired=] with |bitDebit| is true, [=list/remove=] |bitDebit| from |ledger|. -
- ### Run Operation Methods on {{WindowSharedStorage}} ### {#run-op-shared-storage}
@@ -1253,7 +1269,8 @@ On the other hand, methods for getting data from the [=shared storage database=] 1. If |context| is null, return a [=promise rejected=] with a {{TypeError}}. 1. If |context|'s [=active window=]'s [=associated document=] is not [=fully active=], return a [=promise rejected=] with a {{TypeError}}. 1. Let |environment| be |context|'s [=active window=]'s [=relevant settings object=]. - 1. If the result of running [=determine whether shared storage is allowed=] given |environment| and [=current global object=]'s [=SharedStorageWorkletGlobalScope/worklet origin=] is false, return a [=promise rejected=] with a {{TypeError}}. + 1. If the result of running [=determine whether shared storage is allowed by context=] given |environment| and [=current global object=]'s [=SharedStorageWorkletGlobalScope/worklet origin=] is false, return a [=promise rejected=] with a {{TypeError}}. + 1. If the result of running [=determine whether shared storage is allowed by enrollment and user preference=] given |environment| and [=current global object=]'s [=SharedStorageWorkletGlobalScope/worklet origin=] is false, return a [=promise rejected=] with a {{TypeError}}. 1. Let |site| be the result of running [=obtain a site=] with [=current global object=]'s [=SharedStorageWorkletGlobalScope/worklet origin=]. 1. [=Assert=] that |site| is not an [=opaque origin=]. 1. Let |queue| be |context|'s associated [=shared storage database|database=]'s [=shared storage database/shared storage database queue=]. @@ -1480,7 +1497,8 @@ The IDL attribute {{HTMLSharedStorageWritableElementUtils/sharedStorageWritable} 1. Let |window| to |request|’s [=request/window=]. 1. If |window| is not an [=environment settings object=] whose [=global object=] is a {{Window}}, return false. - 1. If the result of running [=determine whether shared storage is allowed=] given |window| and |request|'s [=request/current URL=]'s [=url/origin=] is false, return false. + 1. If the result of running [=determine whether shared storage is allowed by context=] given |window| and |request|'s [=request/current URL=]'s [=url/origin=] is false, return false. + 1. If the result of running [=determine whether shared storage is allowed by enrollment and user preference=] given |window| and |request|'s [=request/current URL=]'s [=url/origin=] is false, return false. Issue: 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.
From 06954f4f1776502603a506c7f7a0372ea84c3ba4 Mon Sep 17 00:00:00 2001 From: Yao Xiao Date: Wed, 21 Feb 2024 01:16:39 -0500 Subject: [PATCH 03/15] Update spec.bs --- spec.bs | 193 +++++++++++++++++++++++++++----------------------------- 1 file changed, 94 insertions(+), 99 deletions(-) diff --git a/spec.bs b/spec.bs index d99b437..81dd628 100644 --- a/spec.bs +++ b/spec.bs @@ -18,6 +18,8 @@ spec:infra; spec:webidl; type:interface; text:double + type:dfn; + text:an exception was thrown spec:html; type:dfn; for:realm; text:global object @@ -220,7 +222,7 @@ The {{SharedStorageWorklet}} object allows developers to supply [=module scripts Each {{SharedStorageWorklet}} has an associated boolean addModule initiated, initialized to false. -Each {{SharedStorageWorklet}} has an associated [=url/origin=] worklet origin, initialized to null. It will be set to the |moduleURL|'s [=url/origin=] when {{Worklet/addModule()|addModule}}(|moduleURL|, options) is called. +Each {{SharedStorageWorklet}} has an associated [=/origin=] worklet origin, initialized to null. It will be set to the |moduleURL|'s [=url/origin=] when {{Worklet/addModule()|addModule}}(|moduleURL|, options) is called. Each {{SharedStorageWorklet}} has an associated boolean cross-origin worklet allowed, initialized to false. @@ -228,10 +230,11 @@ Each {{SharedStorageWorklet}} has an associated boolean To check if user preference setting allows access to shared storage given an [=environment settings object=] |environment| and an [=url/origin=] |origin|, run the following step: + 1. Using values available in |environment| and |origin| as needed, perform an [=implementation-defined=] algorithm to return either true or false. @@ -243,7 +246,7 @@ When {{Worklet/addModule()}} is called for a worklet, it will run [=check if add 1. If |outsideSettingsOrigin| is an [=opaque origin=], then return false. 1. If |origin| is an [=opaque origin=], then return false. 1. Let |globalObject| be the [=current realm=]'s [=global object=]. - 1. [=Assert=] that |globalObject| is a {{Window}} or a {{SharedStorageWorkletGlobalScope}}. + 1. [=Assert=]: |globalObject| is a {{Window}} or a {{SharedStorageWorkletGlobalScope}}. 1. If |globalObject| is a {{Window}}, and if the result of running [=Is feature enabled in document for origin?=] on "[=PermissionsPolicy/shared-storage=]", |globalObject|'s [=associated document=], and |origin| returns false, then return false. 1. Return true. @@ -257,65 +260,60 @@ When {{Worklet/addModule()}} is called for a worklet, it will run [=check if add
- Here are the scenarios where the algorithms [=determine whether shared storage is allowed by context=] and [=determine whether shared storage is allowed by enrollment and user preference=] could be used: + Here are the scenarios where the algorithms [=determine whether shared storage is allowed by context=] and [=determine whether shared storage is allowed by enrollment and user preference=] are used: - - For Window setter/deleter methods, |environment|'s [=environment settings object/origin=] should equal |origin|. - - For creating a worklet, and for initiating (from {{Window}}) and running (from {{SharedStorageWorkletGlobalScope}}) operations on a worklet, |environment| should be the worklet's creator window, and |origin| should be the [=SharedStorageWorklet/worklet origin=]. - - For a fetch request, |environment| should be the request's initiator window, and |origin| should be the request's URL's origin. + - For [[#window-setter]], |environment| is the current context, and |origin| is |environment|'s [=environment settings object/origin=]. + - For creating a worklet, and for initiating (from {{Window}}) and running (from {{SharedStorageWorkletGlobalScope}}) operations on a worklet, |environment| is the worklet's creator window, and |origin| is the [=SharedStorageWorklet/worklet origin=]. + - For [[#ss-fetch-algo]], |environment| is the request's [=request/window=], and |origin| is the request's [=request/current URL=]'s [=url/origin=] origin.
- To check if addModule is allowed and update status given a {{SharedStorageWorklet}} |worklet| and an [=/URL=] |moduleURL|, run the following steps: + To check if addModule is allowed and update state given a {{SharedStorageWorklet}} |worklet| and an [=/URL=] |moduleURL|, run the following steps: 1. Let |workletOrigin| be |moduleURL|'s [=url/origin=]. - 1. If the result of running [=determine whether shared storage is allowed by context=] given [=this=]'s [=relevant settings object=] and |workletOrigin| is false, return "DisallowedDueToNonPreferenceError". + 1. If the result of running [=determine whether shared storage is allowed by context=] given [=current global object=]'s [=relevant settings object=] and |workletOrigin| is false, return "DisallowedDueToNonPreferenceError". 1. If |worklet|'s [=addModule initiated=] is true, return "DisallowedDueToNonPreferenceError". 1. Set |worklet|'s [=addModule initiated=] to true. - 1. If |workletOrigin| does not equal [=this=]'s [=relevant settings object=]'s [=environment settings object/origin=], set |worklet|'s [=SharedStorageWorklet/is cross-origin worklet=] to true. + 1. If |workletOrigin| and [=current global object=]'s [=relevant settings object=]'s [=environment settings object/origin=] are [=same origin=], set |worklet|'s [=SharedStorageWorklet/is cross-origin worklet=] to true. 1. If |worklet|'s [=cross-origin worklet allowed=] is false, and if |worklet|'s [=SharedStorageWorklet/is cross-origin worklet=] is true, return "DisallowedDueToNonPreferenceError". - 1. If the result of running [=determine whether shared storage is allowed by enrollment and user preference=] given [=this=]'s [=relevant settings object=] and |workletOrigin| is false, return "DisallowedDueToPreferenceError". + 1. If the result of running [=determine whether shared storage is allowed by enrollment and user preference=] given [=current global object=]'s [=relevant settings object=] and |workletOrigin| is false, return "DisallowedDueToPreferenceError". 1. Set |worklet|'s [=SharedStorageWorklet/worklet origin=] to |workletOrigin|. 1. Return "Allowed".
Moreover, each {{SharedStorageWorklet}}'s [=global scopes|list of global scopes=], initially empty, can contain at most one instance of its [=worklet global scope type=], the {{SharedStorageWorkletGlobalScope}}. - Note: The website that serves the module script should be aware of the implication of CORS: when the the module script's URL's origin is cross-origin with the worklet's creator window's origin, and by granting the module script resource via CORS, it will also grant the worklet's creation and subsequent operations on the worklet, under module script's URL's origin. The worklet's creator context could poison and use up the [=SharedStorageWorklet/worklet origin=]'s budget. + Note: The website that serves the module script should be aware of the implication of CORS: when the the module script's [=/URL=]'s [=url/origin=] and the worklet's creator window's origin is not [=same origin=], and by granting the module script resource via CORS, it will also grant the worklet's creation and subsequent operations on the worklet, under module script's [=/URL=]'s [=url/origin=]. For example, the worklet's creator context could poison and use up the [=SharedStorageWorklet/worklet origin=]'s [=remaining navigation budget=]. ## Run Operation Methods on {{SharedStorageWorklet}} ## {#run-op-shared-storage-worklet}
- To get the select-url result index, given {{SharedStorageWorklet}} |worklet|, {{DOMString}} |operationName|, [=/list=] |urlList|, and {{SharedStorageRunOperationMethodOptions}} |options|: + To get the select-url result index, given {{SharedStorageWorklet}} |worklet|, {{DOMString}} |operationName|, [=/list=] of {{SharedStorageUrlWithMetadata}}s |urlList|, and {{SharedStorageRunOperationMethodOptions}} |options|: 1. Let |promise| be a new [=promise=]. - 1. Let |window| be [=current global object=]. - 1. Assert that |window| is a {{Window}}. - 1. Let |context| be |window|'s [=Window/browsing context=]. - 1. If |context| is null, return a [=promise rejected=] with a {{TypeError}}. - 1. Let |environment| be |context|'s [=active window=]'s [=relevant settings object=]. - 1. If |environment|'s [=associated document=] is not [=fully active=], return a [=promise rejected=] with a {{TypeError}}. - 1. Let |realm| be the [=current realm=]. + 1. Let |window| be the [=current global object=]. + 1. [=Assert=]: |window| is a {{Window}}. + 1. If |window|'s [=Window/browsing context=] is null, then return a [=promise rejected=] with a {{TypeError}}. + 1. If |window|'s [=associated document=] is not [=fully active=], return a [=promise rejected=] with a {{TypeError}}. 1. Let |outsideSettings| be |worklet|'s [=relevant settings object=]. - 1. Let |agent| be the result of [=obtaining a worklet agent=] given |outsideSettings|. - 1. Run the following steps in |agent|: + 1. Return |promise|, and immediately [=obtain a worklet agent=] given |outsideSettings| and run the rest of these steps in that agent: 1. Let |index| be [=default index=]. 1. If |worklet|'s [=module map=] is not [=map/empty=]: 1. Let |operationMap| be the associated {{SharedStorageWorkletGlobalScope}}'s [=SharedStorageWorkletGlobalScope/operation map=]. 1. If |operationMap| [=map/contains=] |operationName|: 1. Let |operation| be |operationMap|[|operationName|]. - 1. Let |argumentsList| be a new [=/list=] with a single entry [=list/contain|containing=] |urlList|. + 1. Let |argumentsList| be the [=/list=] « |urlList| ». 1. If |options| [=map/contains=] |data|, [=list/append=] |data| to |argumentsList|. - 1. Let |operationResult| be the result of running [=Call=] on |operation| with |argumentsList|. - 1. If |operationResult| has any error(s), then [=queue a global task=] on the [=DOM manipulation task source=], given |realm|'s [=global object=], to reject |promise| with a {{TypeError}}. + 1. Let |operationResult| be the result of [=invoking=] |operation| with |argumentsList|. + 1. If [=an exception was thrown=], then [=queue a global task=] on the [=DOM manipulation task source=], given |window|, to [=reject=] |promise| with a {{TypeError}}. 1. Otherwise: - 1. Set |index| to the result of [=casting=] |operationResult| to an {{unsigned long}}. - 1. If this throws an exception: - 1. Catch it and [=queue a global task=] on the [=DOM manipulation task source=], given |realm|'s [=global object=], to reject |promise| with a {{TypeError}}. + 1. Set |index| to |operationResult|, [=converted to a JavaScript value=] of type {{unsigned long}}. + 1. If [=an exception was thrown=]: + 1. [=Queue a global task=] on the [=DOM manipulation task source=], given |window|, to [=reject=] |promise| with a {{TypeError}}. 1. Abort these steps. 1. Otherwise, if |index| is greater than |urlList|'s [=list/size=]: - 1. [=Queue a global task=] on the [=DOM manipulation task source=], given |realm|'s [=global object=], to reject |promise| with a {{TypeError}}. + 1. [=Queue a global task=] on the [=DOM manipulation task source=], given |window|, to [=reject=] |promise| with a {{TypeError}}. 1. Abort these steps. - 1. [=Queue a global task=] on the [=DOM manipulation task source=], given |realm|'s [=global object=], to resolve |promise| with |index|. - 1. Return |promise|. + 1. [=Queue a global task=] on the [=DOM manipulation task source=], given |window|, to [=resolve=] |promise| with |index|.
@@ -323,14 +321,14 @@ Moreover, each {{SharedStorageWorklet}}'s [=global scopes|list of global scopes= 1. Let |resultPromise| be a new [=promise=]. 1. If [=this=]'s [=addModule initiated=] is false, then return a [=promise rejected=] with a {{TypeError}}. - 1. Let |window| be [=current global object=]. - 1. Assert that |window| is a {{Window}}. + 1. Let |window| be the [=current global object=]. + 1. [=Assert=]: |window| is a {{Window}}. 1. Let |context| be |window|'s [=Window/browsing context=]. - 1. If |context| is null, return a [=promise rejected=] with a {{TypeError}}. + 1. If |context| is null, then return a [=promise rejected=] with a {{TypeError}}. 1. Let |document| be |context|'s [=active document=]. 1. If the result of running [=Is feature enabled in document for origin?=] on "[=PermissionsPolicy/shared-storage-select-url=]", |document|, and [=this=]'s [=SharedStorageWorklet/worklet origin=] returns false, return a [=promise rejected=] with a {{TypeError}}. 1. If [=this=]'s [=global scopes|list of global scopes=] is [=list/empty=], then return a [=promise rejected=] with a {{TypeError}}. - 1. [=Assert=] that [=this=]'s [=global scopes|list of global scopes=] [=list/contains=] a single {{SharedStorageWorkletGlobalScope}}. + 1. [=Assert=]: [=this=]'s [=global scopes|list of global scopes=] [=list/contains=] a single {{SharedStorageWorkletGlobalScope}}. 1. If the result of running [=SharedStorageWorkletGlobalScope/check whether addModule is finished=] for [=this=]'s {{SharedStorageWorkletGlobalScope}} is false, return a [=promise rejected=] with a {{TypeError}}. 1. If |urls| is empty or exceeds the maximum allowed length, return a [=promise rejected=] with a {{TypeError}}. 1. Let |urlList| be an empty {{list}}. @@ -344,13 +342,13 @@ Moreover, each {{SharedStorageWorklet}}'s [=global scopes|list of global scopes= 1. Let |reportingMetadata| be |urlWithMetadata|["`reportingMetadata`"]. 1. If the result of running [=validate reporting metadata=] with |reportingMetadata| is false, reject |resultPromise| with a {{TypeError}} and abort these steps. 1. Let |fencedFrameConfigStruct| be a "fenced frame config struct" with an invalid "urn uuid". Add correct struct name as well as linking when Fenced Frame API updates their draft spec to include it. - 1. Let |environment| be |context|'s [=active window=]'s [=relevant settings object=]. + 1. Let |environment| be |window|'s [=relevant settings object=]. 1. If the result of running [=determine whether shared storage is allowed by context=] given |environment| and [=this=]'s [=SharedStorageWorklet/worklet origin=] is false, return a [=promise rejected=] with a {{TypeError}}. 1. If the result of running [=determine whether shared storage is allowed by enrollment and user preference=] given |environment| and [=this=]'s [=SharedStorageWorklet/worklet origin=] is false: 1. If [=this=]'s [=SharedStorageWorklet/is cross-origin worklet=] is false, return a [=promise rejected=] with a {{TypeError}}. 1. Else, set |fencedFrameConfigStruct| to a "fenced frame config struct" with a pending mapped "url uuid". Add correct struct name as well as linking when Fenced Frame API updates their draft spec to include it. - Issue: 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. + Issue: 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. 1. If |options|["`resolveToConfig`"] is true, resolve |resultPromise| with the result of running "obtain a {{FencedFrameConfig}} from a fenced frame config struct" with |fencedFrameConfigStruct|. Add correct struct and algorithms names as well as linking when Fenced Frame API updates their draft spec to include it. 1. 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. @@ -360,7 +358,7 @@ Moreover, each {{SharedStorageWorklet}}'s [=global scopes|list of global scopes= 1. Let |resultIndex| be the numerical value of |indexPromise|. 1. Let |site| be the result of running [=obtain a site=] with |document|'s [=url/origin=]. 1. Let |remainingBudget| be the result of running [=determine remaining navigation budget=] with |environment| and |site|. - 1. [=Assert=] that |remainingBudget| is not undefined. + 1. [=Assert=]: |remainingBudget| is not undefined. 1. Let |pendingBits| be the logarithm base 2 of |urlList|'s [=list/size=]. 1. If |pendingBits| is greather than |remainingBudget|, set |resultIndex| to [=default index=]. 1. Set |fencedFrameConfigStruct|'s [=pending shared storage budget debit=] to |pendingBits|. @@ -379,21 +377,19 @@ Moreover, each {{SharedStorageWorklet}}'s [=global scopes|list of global scopes= 1. Let |promise| be a new [=promise=]. 1. If [=this=]'s [=addModule initiated=] is false, then return a [=promise rejected=] with a {{TypeError}}. - 1. Let |window| be [=current global object=]. - 1. Assert that |window| is a {{Window}}. + 1. Let |window| be the [=current global object=]. + 1. [=Assert=]: |window| is a {{Window}}. 1. If [=this=]'s [=global scopes|list of global scopes=] is [=list/empty=], then return a [=promise rejected=] with a {{TypeError}}. - 1. [=Assert=] that [=this=]'s [=global scopes|list of global scopes=] [=list/contains=] a single {{SharedStorageWorkletGlobalScope}}. + 1. [=Assert=]: [=this=]'s [=global scopes|list of global scopes=] [=list/contains=] a single {{SharedStorageWorkletGlobalScope}}. 1. If the result of running [=SharedStorageWorkletGlobalScope/check whether addModule is finished=] for [=this=]'s {{SharedStorageWorkletGlobalScope}} is false, return a [=promise rejected=] with a {{TypeError}}. - 1. Let |realm| be the [=current realm=]. 1. Let |outsideSettings| be [=this=]'s [=relevant settings object=]. - 1. If the result of running [=determine whether shared storage is allowed by context=] given |outsideSettings| and [=this=]'s [=SharedStorageWorklet/worklet origin=] is false, reject |promise| with a {{TypeError}}. + 1. If the result of running [=determine whether shared storage is allowed by context=] given |outsideSettings| and [=this=]'s [=SharedStorageWorklet/worklet origin=] is false, [=reject=] |promise| with a {{TypeError}}. 1. If the result of running [=determine whether shared storage is allowed by enrollment and user preference=] given |outsideSettings| and [=this=]'s [=SharedStorageWorklet/worklet origin=] is false: - 1. If [=this=]'s [=SharedStorageWorklet/is cross-origin worklet=] is false, reject |promise| with a {{TypeError}}. - 1. Else, resolve |promise| with undefined. + 1. If [=this=]'s [=SharedStorageWorklet/is cross-origin worklet=] is false, [=reject=] |promise| with a {{TypeError}}. + 1. Else, [=resolve=] |promise| with undefined. 1. Return |promise|. - 1. Let |agent| be the result of [=obtaining a worklet agent=] given |outsideSettings|. - 1. Run the following steps in |agent|: - 1. [=Queue a global task=] on the [=DOM manipulation task source=], given |realm|'s [=global object=], to resolve |promise| with undefined. + 1. Return |promise|, and immediately [=obtaining a worklet agent=] given |outsideSettings| and run the rest of these steps in that agent: + 1. [=Queue a global task=] on the [=DOM manipulation task source=], given |window|, to [=resolve=] |promise| with undefined. 1. If [=this=]'s [=module map=] is not [=map/empty=]: 1. Let |operationMap| be [=this=]'s {{SharedStorageWorkletGlobalScope}}'s [=SharedStorageWorkletGlobalScope/operation map=]. 1. If |operationMap| [=map/contains=] |name|: @@ -401,12 +397,11 @@ Moreover, each {{SharedStorageWorklet}}'s [=global scopes|list of global scopes= 1. If |options| [=map/contains=] |data|: 1. Let |argumentsList| be a new [=/list=]. 1. [=list/Append=] |data| to |argumentsList|. - 1. [=Call=] |operation| with |argumentsList|. - 1. Otherwise, [=call=] |operation| without any arguments list. + 1. [=Invoke=] |operation| with |argumentsList|. + 1. Otherwise, [=invoke=] |operation| without any arguments list. 1. If |options|["`keepAlive`"] is false: 1. Wait for |operation| to finish running, if applicable. 1. Run [=terminate a worklet global scope=] with [=this=]'s {{SharedStorageWorkletGlobalScope}}. - 1. Return |promise|.
## Monkey Patch for [=Worklets=] ## {#worklet-monkey-patch} @@ -416,13 +411,13 @@ Moreover, each {{SharedStorageWorklet}}'s [=global scopes|list of global scopes= In particular, the {{Worklet/addModule()}} method steps for {{Worklet}} will need to be prepended with the following step: 0. If |this| is of type {{SharedStorageWorklet}}: - 1. Let |addModuleAllowedResult| be the result of running [=check if addModule is allowed and update status=] given |this| and |moduleURL|. + 1. Let |addModuleAllowedResult| be the result of running [=check if addModule is allowed and update state=] given |this| and |moduleURL|. 1. If |addModuleAllowedResult| is "DisallowedDueToNonPreferenceError": 1. Reject promise with a {{TypeError}}. 1. Else if |addModuleAllowedResult| is "DisallowedDueToPreferenceError": 1. If |this|'s [=SharedStorageWorklet/is cross-origin worklet=] is false, then reject promise with a {{TypeError}}. Else, resolve promise. 1. Else: - 1. [=Assert=] that |addModuleAllowedResult| is "Allowed". + 1. [=Assert=]: |addModuleAllowedResult| is "Allowed". 1. Resolve promise. 1. Return promise. @@ -438,11 +433,11 @@ Moreover, each {{SharedStorageWorklet}}'s [=global scopes|list of global scopes= Specify that redirects are disallowed when fetching the module script for {{SharedStorageWorklet}}. - And the penultimate step (i.e. the final indented step), currently "If |pendingTasks| is 0, then resolve |promise|.", should be updated to: + And the penultimate step (i.e. the final indented step), currently "If |pendingTasks| is 0, then [=resolve=] |promise|.", should be updated to: 2. If |pendingTasks| is 0, perform the following steps: 1. If |workletGlobalScope| has an associated boolean [=addModule success=], set |workletGlobalScope|'s [=addModule success=] to true. - 2. Resolve |promise|. + 2. [=resolve=] |promise|. Add additional monkey patch pieces for out-of-process worklets. @@ -571,11 +566,11 @@ Moreover, each {{SharedStorageWorklet}}'s [=global scopes|list of global scopes= To register reporting metadata, given an {{/object}} |reportingMetadata| and a "fenced frame config struct" |fencedFrameConfigStruct|, run the following steps: 1. If |reportingMetadata| is [=map/empty=], return. - 1. [=Assert=] that |reportingMetadata| is a [=dictionary=]. + 1. [=Assert=]: |reportingMetadata| is a [=dictionary=]. 1. Let |reportingUrlMap| be an [=map/empty=] [=map=]. 1. [=map/iterate|For each=] |eventType| -> |urlString| of |reportingMetadata|: 1. Let |url| be the result of running a [=URL parser=] on |urlString|. - 1. [=Assert=] that |url| is a valid [=/URL=]. + 1. [=Assert=]: |url| is a valid [=/URL=]. 1. [=map/Set=] |reportingUrlMap|[|eventType|] to |url|. 1. Store |reportingUrlMap| inside a "fenced frame reporter" class associated with |fencedFrameConfigStruct|. Both of these still need to be added to the draft [=fenced frame|Fenced Frame specification=]. @@ -820,7 +815,7 @@ The Shared Storage API will integrate into the [=Storage Model|Storage API=] as 1. Let |currentTime| be the |environment|'s [=environment settings object/current wall time=]. 1. Set |valueStruct|'s [=value struct/last updated=] to |currentTime|. 1. [=map/Set=] |databaseMap|[|key|] to |valueStruct|. - 1. If this throws an exception, catch it and return false. + 1. If [=an exception was thrown=], then return false. Note: Errors with [=storage proxy map=] |databaseMap|'s methods are possible depending on its implementation. 1. Otherwise, return true. @@ -831,7 +826,7 @@ The Shared Storage API will integrate into the [=Storage Model|Storage API=] as 1. If |databaseMap| does not [=map/contain=] |key|, return undefined. 1. Let |valueStruct| be the result of running [=map/Get=] on |databaseMap| with |key|. - 1. If this throws an exception, catch it and return failure. + 1. If [=an exception was thrown=], then return failure. Note: Errors with [=storage proxy map=] |databaseMap|'s methods are possible depending on its implementation. 1. If the result of running [=shared storage database/determine whether an entry is expired=] with |environment| and |valueStruct| is true, return undefined. @@ -842,7 +837,7 @@ The Shared Storage API will integrate into the [=Storage Model|Storage API=] as To delete an entry from the database, given a [=shared storage database/shared storage database queue=] |queue|, a [=storage proxy map=] |databaseMap|, and a [=entry/key=] |key|, run the following steps on |queue|: 1. [=map/Remove=] |databaseMap|[|key|]. - 1. If this throws an exception, catch it and return false. + 1. If [=an exception was thrown=], then return false. Note: Errors with [=storage proxy map=] |databaseMap|'s methods are possible depending on its implementation. 1. Return true. @@ -852,7 +847,7 @@ The Shared Storage API will integrate into the [=Storage Model|Storage API=] as To clear all entries in the database, given a [=shared storage database/shared storage database queue=] |queue| and a [=storage proxy map=] |databaseMap|, run the following steps on |queue|: 1. Run [=map/Clear=] on |databaseMap|. - 1. If this throws an exception, catch it and return false. + 1. If [=an exception was thrown=], then return false. Note: Errors with [=storage proxy map=] |databaseMap|'s methods are possible depending on its implementation. 1. Return true. @@ -862,7 +857,7 @@ The Shared Storage API will integrate into the [=Storage Model|Storage API=] as To retrieve all entries from the database, given a [=shared storage database/shared storage database queue=] |queue| and a [=storage proxy map=] |databaseMap|, run the following steps on |queue|: 1. Let |values| be the result of running [=map/getting the values=] on |databaseMap|. - 1. If this throws an exception, catch it and return failure. + 1. If [=an exception was thrown=], then return failure. Note: Errors with [=storage proxy map=] |databaseMap|'s methods are possible depending on its implementation. 1. Return |values|. @@ -872,7 +867,7 @@ The Shared Storage API will integrate into the [=Storage Model|Storage API=] as To count entries in the database, given a [=shared storage database/shared storage database queue=] |queue| and a [=storage proxy map=] |databaseMap|, run the following steps on |queue|: 1. Let |size| be |databaseMap|'s [=map/size=]. - 1. If this throws an exception, catch it and return failure. + 1. If [=an exception was thrown=], then return failure. Note: Errors with [=storage proxy map=] |databaseMap|'s members are possible depending on its implementation. 1. Return |size|. @@ -883,9 +878,9 @@ The Shared Storage API will integrate into the [=Storage Model|Storage API=] as 1. [=map/iterate|For each=] [=entry/key=] |key| in |databaseMap|: 1. Let |valueStruct| be the result of running [=map/Get=] on |databaseMap| with |key|. - 1. If this throws an exception, catch it and return false. + 1. If [=an exception was thrown=], then return false. 1. If the result of running [=shared storage database/determine whether an entry is expired=] with |environment| and |valueStruct| is true, [=map/Remove=] |databaseMap|[|key|]. - 1. If this throws an exception, catch it and return false. + 1. If [=an exception was thrown=], then return false. 1. Return true. @@ -984,10 +979,10 @@ On the other hand, methods for getting data from the [=shared storage database=] 1. Let |realm| be the [=current realm=]. 1. [=Enqueue the following steps=] on |queue|: 1. If |options|["`ignoreIfPresent`"] is true and the result of running [=shared storage database/retrieve an entry from the database=] with |queue|, |databaseMap|, |environment|, and |key| is not undefined: - 1. [=Queue a global task=] on the [=DOM manipulation task source=], given |realm|'s [=global object=], to resolve |promise| with undefined. + 1. [=Queue a global task=] on the [=DOM manipulation task source=], given |realm|'s [=global object=], to [=resolve=] |promise| with undefined. 1. Abort these steps. 1. Run [=shared storage database/store an entry in the database=] with |queue|, |databaseMap|, |environment|, |key|, and |value|. - 1. [=Queue a global task=] on the [=DOM manipulation task source=], given |realm|'s [=global object=], to resolve |promise| with undefined. + 1. [=Queue a global task=] on the [=DOM manipulation task source=], given |realm|'s [=global object=], to [=resolve=] |promise| with undefined. 1. Return |promise|. @@ -1007,7 +1002,7 @@ On the other hand, methods for getting data from the [=shared storage database=] 1. [=Enqueue the following steps=] on |queue|: 1. Let |currentValue| be the result of running [=shared storage database/retrieve an entry from the database=] with |queue|, |databaseMap|, |environment|, and |key|. 1. If |currentValue| is failure: - 1. [=Queue a global task=] on the [=DOM manipulation task source=], given |realm|'s [=global object=], to resolve |promise| with undefined. + 1. [=Queue a global task=] on the [=DOM manipulation task source=], given |realm|'s [=global object=], to [=resolve=] |promise| with undefined. 1. Abort these steps. 1. If |currentValue| is not undefined: 1. Let |list| be a new [=/list=]. @@ -1015,7 +1010,7 @@ On the other hand, methods for getting data from the [=shared storage database=] 1. [=list/Append=] |value| to |list|. 1. Set |value| to the result of running [=string/concatenate=] on |list|. 1. Run [=shared storage database/store an entry in the database=] with |queue|, |databaseMap|, |environment|, |key|, and |value|. - 1. [=Queue a global task=] on the [=DOM manipulation task source=], given |realm|'s [=global object=], to resolve |promise| with undefined. + 1. [=Queue a global task=] on the [=DOM manipulation task source=], given |realm|'s [=global object=], to [=resolve=] |promise| with undefined. 1. Return |promise|. @@ -1033,7 +1028,7 @@ On the other hand, methods for getting data from the [=shared storage database=] 1. Let |realm| be the [=current realm=]. 1. [=Enqueue the following steps=] on |queue|: 1. Run [=shared storage database/delete an entry from the database=] with |queue|, |environment|, and |key|. - 1. [=Queue a global task=] on the [=DOM manipulation task source=], given |realm|'s [=global object=], to resolve |promise| with undefined. + 1. [=Queue a global task=] on the [=DOM manipulation task source=], given |realm|'s [=global object=], to [=resolve=] |promise| with undefined. 1. Return |promise|. @@ -1050,7 +1045,7 @@ On the other hand, methods for getting data from the [=shared storage database=] 1. Let |realm| be the [=current realm=]. 1. [=Enqueue the following steps=] on |queue|: 1. Run [=shared storage database/clear all entries in the database=] with |queue| and |environment|. - 1. [=Queue a global task=] on the [=DOM manipulation task source=], given |realm|'s [=global object=], to resolve |promise| with undefined. + 1. [=Queue a global task=] on the [=DOM manipulation task source=], given |realm|'s [=global object=], to [=resolve=] |promise| with undefined. 1. Return |promise|. @@ -1137,13 +1132,13 @@ On the other hand, methods for getting data from the [=shared storage database=] 1. If |options|["`ignoreIfPresent`"] is true: 1. Let |currentValue| be the result of running [=shared storage database/retrieve an entry from the database=] with |queue|, |databaseMap|, |environment|, and |key|. 1. If |currentValue| is failure: - 1. [=Queue a global task=] on the [=DOM manipulation task source=], given |realm|'s [=global object=], to reject |promise| with a {{TypeError}}. + 1. [=Queue a global task=] on the [=DOM manipulation task source=], given |realm|'s [=global object=], to [=reject=] |promise| with a {{TypeError}}. 1. Abort these steps. 1. If |currentValue| is not undefined: - 1. [=Queue a global task=] on the [=DOM manipulation task source=], given |realm|'s [=global object=], to resolve |promise| with undefined. + 1. [=Queue a global task=] on the [=DOM manipulation task source=], given |realm|'s [=global object=], to [=resolve=] |promise| with undefined. 1. Abort these steps. - 1. If the result of running [=shared storage database/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}}. - 1. Otherwise, [=queue a global task=] on the [=DOM manipulation task source=], given |realm|'s [=global object=], to resolve |promise| with undefined. + 1. If the result of running [=shared storage database/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}}. + 1. Otherwise, [=queue a global task=] on the [=DOM manipulation task source=], given |realm|'s [=global object=], to [=resolve=] |promise| with undefined. 1. Return |promise|. @@ -1165,15 +1160,15 @@ On the other hand, methods for getting data from the [=shared storage database=] 1. [=Enqueue the following steps=] on |queue|: 1. Let |currentValue| be the result of running [=shared storage database/retrieve an entry from the database=] with |queue|, |databaseMap|, |environment|, and |key|. 1. If |currentValue| is failure: - 1. [=Queue a global task=] on the [=DOM manipulation task source=], given |realm|'s [=global object=], to reject |promise| with a {{TypeError}}. + 1. [=Queue a global task=] on the [=DOM manipulation task source=], given |realm|'s [=global object=], to [=reject=] |promise| with a {{TypeError}}. 1. Abort these steps. 1. If |currentValue| is not undefined: 1. Let |list| be a new [=/list=]. 1. [=list/Append=] |currentValue| to |list|. 1. [=list/Append=] |value| to |list|. 1. Set |value| to the result of running [=string/concatenate=] on |list|. - 1. If the result of running [=shared storage database/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}}. - 1. Otherwise, [=queue a global task=] on the [=DOM manipulation task source=], given |realm|'s [=global object=], to resolve |promise| with undefined. + 1. If the result of running [=shared storage database/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}}. + 1. Otherwise, [=queue a global task=] on the [=DOM manipulation task source=], given |realm|'s [=global object=], to [=resolve=] |promise| with undefined. 1. Return |promise|. @@ -1192,8 +1187,8 @@ On the other hand, methods for getting data from the [=shared storage database=] 1. Let |queue| be |context|'s associated [=shared storage database|database=]'s [=shared storage database/shared storage database queue=]. 1. Let |realm| be the [=current realm=]. 1. [=Enqueue the following steps=] on |queue|: - 1. If the result of running [=shared storage database/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}}. - 1. Otherwise, [=queue a global task=] on the [=DOM manipulation task source=], given |realm|'s [=global object=], to resolve |promise| with undefined. + 1. If the result of running [=shared storage database/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}}. + 1. Otherwise, [=queue a global task=] on the [=DOM manipulation task source=], given |realm|'s [=global object=], to [=resolve=] |promise| with undefined. 1. Return |promise|. @@ -1211,8 +1206,8 @@ On the other hand, methods for getting data from the [=shared storage database=] 1. Let |queue| be |context|'s associated [=shared storage database|database=]'s [=shared storage database/shared storage database queue=]. 1. Let |realm| be the [=current realm=]. 1. [=Enqueue the following steps=] on |queue|: - 1. If the result of running [=shared storage database/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}}. - 1. Otherwise, [=queue a global task=] on the [=DOM manipulation task source=], given |realm|'s [=global object=], to resolve |promise| with undefined. + 1. If the result of running [=shared storage database/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}}. + 1. Otherwise, [=queue a global task=] on the [=DOM manipulation task source=], given |realm|'s [=global object=], to [=resolve=] |promise| with undefined. 1. Return |promise|. @@ -1234,9 +1229,9 @@ On the other hand, methods for getting data from the [=shared storage database=] 1. Let |realm| be the [=current realm=]. 1. [=Enqueue the following steps=] on |queue|: 1. Let |value| be the result of running [=shared storage database/retrieve an entry from the database=] with |queue|, |databaseMap|, |environment|, and |key|. - 1. 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}}. - 1. 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. - 1. Otherwise, [=queue a global task=] on the [=DOM manipulation task source=], given |realm|'s [=global object=], to resolve |promise| with |value|. + 1. 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}}. + 1. 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. + 1. Otherwise, [=queue a global task=] on the [=DOM manipulation task source=], given |realm|'s [=global object=], to [=resolve=] |promise| with |value|. 1. Return |promise|. @@ -1255,8 +1250,8 @@ On the other hand, methods for getting data from the [=shared storage database=] 1. Let |realm| be the [=current realm=]. 1. [=Enqueue the following steps=] on |queue|: 1. Let |numEntries| be the result of running [=shared storage database/count entries in the database=] with |queue| and |environment|. - 1. 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}}. - 1. Otherwise, [=queue a global task=] on the [=DOM manipulation task source=], given |realm|'s [=global object=], to resolve |promise| with |numEntries|. + 1. 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}}. + 1. Otherwise, [=queue a global task=] on the [=DOM manipulation task source=], given |realm|'s [=global object=], to [=resolve=] |promise| with |numEntries|. 1. Return |promise|. @@ -1272,13 +1267,13 @@ On the other hand, methods for getting data from the [=shared storage database=] 1. If the result of running [=determine whether shared storage is allowed by context=] given |environment| and [=current global object=]'s [=SharedStorageWorkletGlobalScope/worklet origin=] is false, return a [=promise rejected=] with a {{TypeError}}. 1. If the result of running [=determine whether shared storage is allowed by enrollment and user preference=] given |environment| and [=current global object=]'s [=SharedStorageWorkletGlobalScope/worklet origin=] is false, return a [=promise rejected=] with a {{TypeError}}. 1. Let |site| be the result of running [=obtain a site=] with [=current global object=]'s [=SharedStorageWorkletGlobalScope/worklet origin=]. - 1. [=Assert=] that |site| is not an [=opaque origin=]. + 1. [=Assert=]: |site| is not an [=opaque origin=]. 1. Let |queue| be |context|'s associated [=shared storage database|database=]'s [=shared storage database/shared storage database queue=]. 1. Let |realm| be the [=current realm=]. 1. [=Enqueue the following steps=] on |queue|: 1. Let |remainingBudget| be the result of running [=determine remaining navigation budget=] with |site|. - 1. [=Assert=] that |remainingBudget| is not undefined. - 1. Resolve [=queue a global task=] on the [=DOM manipulation task source=], given |realm|'s [=global object=], to resolve |promise| with |remainingBudget|. + 1. [=Assert=]: |remainingBudget| is not undefined. + 1. Resolve [=queue a global task=] on the [=DOM manipulation task source=], given |realm|'s [=global object=], to [=resolve=] |promise| with |remainingBudget|. 1. Return |promise|. @@ -1305,8 +1300,8 @@ On the other hand, methods for getting data from the [=shared storage database=] 1. Let |realm| be the [=current realm=]. 1. [=Enqueue the following steps=] on |queue|: 1. Let |entries| be the result of running [=shared storage database/retrieve all entries from the database=] with |queue| and |environment|. - 1. 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}}. - 1. Otherwise, [=queue a global task=] on the [=DOM manipulation task source=], given |realm|'s [=global object=], to resolve |promise| with |entries|. + 1. 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}}. + 1. Otherwise, [=queue a global task=] on the [=DOM manipulation task source=], given |realm|'s [=global object=], to [=resolve=] |promise| with |entries|. 1. [=Upon fulfillment=] of |promise|, run the following: 1. Let |promiseEntries| be the value of |promise|. 1. [=map/iterate|For each=] [=shared storage database/entry=] |entry| in |promiseEntries|, [=queue/enqueue=] |entry| in |iterator|'s [=WorkletSharedStorageIterator/pending entries=]. @@ -1321,10 +1316,10 @@ On the other hand, methods for getting data from the [=shared storage database=] 1. If |iterator|'s [=WorkletSharedStorageIterator/error=] is true, return a [=promise rejected=] with a {{TypeError}}. 1. If |iterator|'s [=WorkletSharedStorageIterator/pending entries=] is [=list/empty=]: 1. Create an object |doneObject|. - 1. [=Queue a global task=] on the [=DOM manipulation task source=], given |realm|'s [=global object=], to resolve |promise| with |doneObject|. + 1. [=Queue a global task=] on the [=DOM manipulation task source=], given |realm|'s [=global object=], to [=resolve=] |promise| with |doneObject|. 1. Abort these steps. 1. Otherwise, let |entry| be the result of [=queue/dequeue|dequeueing=] from |iterator|'s [=WorkletSharedStorageIterator/pending entries=]. - 1. [=Queue a global task=] on the [=DOM manipulation task source=], given |realm|'s [=global object=], to resolve |promise| with |entry|. + 1. [=Queue a global task=] on the [=DOM manipulation task source=], given |realm|'s [=global object=], to [=resolve=] |promise| with |entry|. 1. Return |promise|. @@ -1495,7 +1490,7 @@ The IDL attribute {{HTMLSharedStorageWritableElementUtils/sharedStorageWritable}
To determine whether a request can currently use shared storage, given a [=/request=] |request|, perform the following steps: - 1. Let |window| to |request|’s [=request/window=]. + 1. Let |window| to |request|'s [=request/window=]. 1. If |window| is not an [=environment settings object=] whose [=global object=] is a {{Window}}, return false. 1. If the result of running [=determine whether shared storage is allowed by context=] given |window| and |request|'s [=request/current URL=]'s [=url/origin=] is false, return false. 1. If the result of running [=determine whether shared storage is allowed by enrollment and user preference=] given |window| and |request|'s [=request/current URL=]'s [=url/origin=] is false, return false. @@ -1520,8 +1515,8 @@ The IDL attribute {{HTMLSharedStorageWritableElementUtils/sharedStorageWritable} 1. Let |sharedStorageWritable| the result of running [=get a structured field value=] algorithm given [:Sec-Shared-Storage-Writable:], "`item`", and |request|'s [=request/header list=] as input. 1. If |sharedStorageWritable| is null, or |sharedStorageWritable| is not a [=structured header/Boolean=], or the value of |sharedStorageWritable| is false, return. - 1. Let |window| to |request|’s [=request/window=]. - 1. [=Assert=] that |window| is an [=environment settings object=] whose [=global object=] is a {{Window}}. + 1. Let |window| to |request|'s [=request/window=]. + 1. [=Assert=]: |window| is an [=environment settings object=] whose [=global object=] is a {{Window}}. 1. Let |sharedStorage| be |window|'s [=global object=]'s {{Window/sharedStorage}}. 1. If |sharedStorage| is null, then return. 1. Let |list| be |response|'s [=response/header list=]. @@ -1529,7 +1524,7 @@ The IDL attribute {{HTMLSharedStorageWritableElementUtils/sharedStorageWritable} 1. If |operationsToParse| is null or [=list/empty=], then return. 1. For each tuple (|item|, |parameters|) in |operationsToParse|, perform the following steps: 1. If |item| is an [=structured header/Inner List=], continue. - 1. [=Assert=] that |item| is an [=structured header/Bare Item=]. + 1. [=Assert=]: |item| is an [=structured header/Bare Item=]. 1. Let |operationString| be the result of running [=get the string value=] for |item|. 1. If |operationString| is failure, continue. 1. Switch on |operationString|: @@ -1579,7 +1574,7 @@ The IDL attribute {{HTMLSharedStorageWritableElementUtils/sharedStorageWritable}
If |item| is a [=structured header/Token=]:
If |item| is a [=structured header/String=]:
Perform the following steps: - 1. [=Assert=] that |item| is an [=ASCII string=]. + 1. [=Assert=]: |item| is an [=ASCII string=]. 1. Return |item|.
If |item| is a [=structured header/Byte Sequence=]:
Perform the following steps: From 025d94c26f3666c29f201f35e493b69729d20e50 Mon Sep 17 00:00:00 2001 From: Yao Xiao Date: Thu, 29 Feb 2024 19:55:31 -0500 Subject: [PATCH 04/15] address PR review comments --- spec.bs | 126 +++++++++++++++++++++++++++++++------------------------- 1 file changed, 71 insertions(+), 55 deletions(-) diff --git a/spec.bs b/spec.bs index 81dd628..dfb3ef4 100644 --- a/spec.bs +++ b/spec.bs @@ -25,9 +25,14 @@ spec:html; for:realm; text:global object for:WorkerGlobalScope; text:module map for:navigable; text:top-level traversable +spec:fenced-frame; + type:dfn; + for:fencedframetype; text:fenced frame reporter
+urlPrefix: https://www.ietf.org/rfc/rfc4122.txt
+    type: dfn; text: urn uuid
 spec: html; urlPrefix: https://html.spec.whatwg.org/multipage/
     type: dfn
         text: worklets; url: worklets.html#worklets
@@ -222,7 +227,11 @@ The {{SharedStorageWorklet}} object allows developers to supply [=module scripts
 
 Each {{SharedStorageWorklet}} has an associated boolean addModule initiated, initialized to false.
 
-Each {{SharedStorageWorklet}} has an associated [=/origin=] worklet origin, initialized to null. It will be set to the |moduleURL|'s [=url/origin=] when {{Worklet/addModule()|addModule}}(|moduleURL|, options) is called.
+Each {{SharedStorageWorklet}} has an associated [=/origin=]-or-null worklet origin, initialized to null.
+
+
+The intent is for [=SharedStorageWorklet/worklet origin=] to match, identically, the insideSettings's [=environment settings object/origin=] (as computed in [=create a worklet global scope=]). +
Each {{SharedStorageWorklet}} has an associated boolean cross-origin worklet allowed, initialized to false. @@ -262,18 +271,19 @@ When {{Worklet/addModule()}} is called for a worklet, it will run [=check if add
Here are the scenarios where the algorithms [=determine whether shared storage is allowed by context=] and [=determine whether shared storage is allowed by enrollment and user preference=] are used: - - For [[#window-setter]], |environment| is the current context, and |origin| is |environment|'s [=environment settings object/origin=]. - - For creating a worklet, and for initiating (from {{Window}}) and running (from {{SharedStorageWorkletGlobalScope}}) operations on a worklet, |environment| is the worklet's creator window, and |origin| is the [=SharedStorageWorklet/worklet origin=]. + - For each method under [[#window-setter]], |environment| is the current context, and |origin| is |environment|'s [=environment settings object/origin=]. + - For creating a worklet, and for initiating (from {{Window}}) and running (from {{SharedStorageWorkletGlobalScope}}) operations on a worklet, |environment| is the [=environment settings object=] associated with the {{Window}} that created the worklet, and |origin| is the [=SharedStorageWorklet/worklet origin=]. - For [[#ss-fetch-algo]], |environment| is the request's [=request/window=], and |origin| is the request's [=request/current URL=]'s [=url/origin=] origin.
- To check if addModule is allowed and update state given a {{SharedStorageWorklet}} |worklet| and an [=/URL=] |moduleURL|, run the following steps: - 1. Let |workletOrigin| be |moduleURL|'s [=url/origin=]. - 1. If the result of running [=determine whether shared storage is allowed by context=] given [=current global object=]'s [=relevant settings object=] and |workletOrigin| is false, return "DisallowedDueToNonPreferenceError". + To check if addModule is allowed and update state given a {{SharedStorageWorklet}} |worklet| and an [=/URL=] |moduleURLRecord|, run the following steps: 1. If |worklet|'s [=addModule initiated=] is true, return "DisallowedDueToNonPreferenceError". 1. Set |worklet|'s [=addModule initiated=] to true. - 1. If |workletOrigin| and [=current global object=]'s [=relevant settings object=]'s [=environment settings object/origin=] are [=same origin=], set |worklet|'s [=SharedStorageWorklet/is cross-origin worklet=] to true. + 1. [=Assert=]: |worklet|'s [=SharedStorageWorklet/worklet origin=] is null. + 1. Let |workletOrigin| be |moduleURLRecord|'s [=url/origin=]. + 1. If the result of running [=determine whether shared storage is allowed by context=] given [=current global object=]'s [=relevant settings object=] and |workletOrigin| is false, return "DisallowedDueToNonPreferenceError". + 1. If |workletOrigin| and [=current global object=]'s [=relevant settings object=]'s [=environment settings object/origin=] are not [=same origin=], set |worklet|'s [=SharedStorageWorklet/is cross-origin worklet=] to true. 1. If |worklet|'s [=cross-origin worklet allowed=] is false, and if |worklet|'s [=SharedStorageWorklet/is cross-origin worklet=] is true, return "DisallowedDueToNonPreferenceError". 1. If the result of running [=determine whether shared storage is allowed by enrollment and user preference=] given [=current global object=]'s [=relevant settings object=] and |workletOrigin| is false, return "DisallowedDueToPreferenceError". 1. Set |worklet|'s [=SharedStorageWorklet/worklet origin=] to |workletOrigin|. @@ -341,14 +351,14 @@ Moreover, each {{SharedStorageWorklet}}'s [=global scopes|list of global scopes= 1. If |urlWithMetadata| has field "`reportingMetadata`": 1. Let |reportingMetadata| be |urlWithMetadata|["`reportingMetadata`"]. 1. If the result of running [=validate reporting metadata=] with |reportingMetadata| is false, reject |resultPromise| with a {{TypeError}} and abort these steps. - 1. Let |fencedFrameConfigStruct| be a "fenced frame config struct" with an invalid "urn uuid". Add correct struct name as well as linking when Fenced Frame API updates their draft spec to include it. + 1. Let |fencedFrameConfigStruct| be a [=fenced frame config=] with an invalid "urn uuid". Add correct struct name as well as linking when Fenced Frame API updates their draft spec to include it. 1. Let |environment| be |window|'s [=relevant settings object=]. 1. If the result of running [=determine whether shared storage is allowed by context=] given |environment| and [=this=]'s [=SharedStorageWorklet/worklet origin=] is false, return a [=promise rejected=] with a {{TypeError}}. 1. If the result of running [=determine whether shared storage is allowed by enrollment and user preference=] given |environment| and [=this=]'s [=SharedStorageWorklet/worklet origin=] is false: 1. If [=this=]'s [=SharedStorageWorklet/is cross-origin worklet=] is false, return a [=promise rejected=] with a {{TypeError}}. - 1. Else, set |fencedFrameConfigStruct| to a "fenced frame config struct" with a pending mapped "url uuid". Add correct struct name as well as linking when Fenced Frame API updates their draft spec to include it. + 1. Else, set |fencedFrameConfigStruct| to a [=fenced frame config=] with a pending mapped "url uuid". Add correct struct name as well as linking when Fenced Frame API updates their draft spec to include it. - Issue: 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. + Issue: The [=fenced frame config=] 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. 1. If |options|["`resolveToConfig`"] is true, resolve |resultPromise| with the result of running "obtain a {{FencedFrameConfig}} from a fenced frame config struct" with |fencedFrameConfigStruct|. Add correct struct and algorithms names as well as linking when Fenced Frame API updates their draft spec to include it. 1. 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. @@ -408,10 +418,10 @@ Moreover, each {{SharedStorageWorklet}}'s [=global scopes|list of global scopes= This specification will make some modifications to the [=Worklet=] standard to accommodate the needs of Shared Storage. - In particular, the {{Worklet/addModule()}} method steps for {{Worklet}} will need to be prepended with the following step: + In particular, the {{Worklet/addModule()}} method steps for {{Worklet}} will need to include the following step before the step "Let |promise| be a new promise": - 0. If |this| is of type {{SharedStorageWorklet}}: - 1. Let |addModuleAllowedResult| be the result of running [=check if addModule is allowed and update state=] given |this| and |moduleURL|. + 4. If |this| is of type {{SharedStorageWorklet}}: + 1. Let |addModuleAllowedResult| be the result of running [=check if addModule is allowed and update state=] given |this| and moduleURLRecord. 1. If |addModuleAllowedResult| is "DisallowedDueToNonPreferenceError": 1. Reject promise with a {{TypeError}}. 1. Else if |addModuleAllowedResult| is "DisallowedDueToPreferenceError": @@ -422,13 +432,11 @@ Moreover, each {{SharedStorageWorklet}}'s [=global scopes|list of global scopes= 1. Return promise.
- - On user preferences error, addModule() will be aborted at an early stage. However, the error will only be exposed to the caller for a same-origin worklet (i.e. where the initiator document's origin is same-origin with the module script's origin). For a cross-origin worklet, the error will be hidden. This is to prevent a caller from knowing which origins the user has disabled shared storage for via preferences (if a per-origin preference exists for that browser vendor). + On user preferences error, {{Worklet/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 {{SharedStorageWorklet/selectURL()}} and {{SharedStorageWorklet/run()}}. -
Specify that redirects are disallowed when fetching the module script for {{SharedStorageWorklet}}. @@ -437,7 +445,7 @@ Moreover, each {{SharedStorageWorklet}}'s [=global scopes|list of global scopes= 2. If |pendingTasks| is 0, perform the following steps: 1. If |workletGlobalScope| has an associated boolean [=addModule success=], set |workletGlobalScope|'s [=addModule success=] to true. - 2. [=resolve=] |promise|. + 2. [=Resolve=] |promise|. Add additional monkey patch pieces for out-of-process worklets. @@ -532,7 +540,7 @@ Moreover, each {{SharedStorageWorklet}}'s [=global scopes|list of global scopes= ## {{SharedStorageUrlWithMetadata}} and Reporting ## {#reporting} - A {{SharedStorageUrlWithMetadata}} {{/object}} is a [=dictionary=] containing a [=string=] representing a [=/URL=] and, optionally, a {{reportingMetadata}} {{/object}}. + {{SharedStorageUrlWithMetadata}} [=dictionaries=] contain a [=string=] representing a [=/URL=] and, optionally, a {{reportingMetadata}} {{/object}}. dictionary SharedStorageUrlWithMetadata { @@ -541,16 +549,16 @@ Moreover, each {{SharedStorageWorklet}}'s [=global scopes|list of global scopes= }; - If a {{SharedStorageUrlWithMetadata}} {{/object}} contains a non-[=map/empty=] {{SharedStorageUrlWithMetadata/reportingMetadata}} {{/object}} in the form of a [=dictionary=] whose [=map/keys=] are [=FenceEvent/eventTypes=] and whose [=map/values=] are [=strings=] that parse to valid [=/URLs=], then these [=FenceEvent/eventType=]-[=/URL=] pairs will be [=register reporting metadata|registered=] for later access within any [=fenced frame=] that loads the {{SharedStorageResponse}} resulting from this {{SharedStorageWorklet/selectURL()}} call. + If a {{SharedStorageUrlWithMetadata}} [=dictionary=] contains a non-[=map/empty=] {{SharedStorageUrlWithMetadata/reportingMetadata}} {{/object}} in the form of a [=dictionary=] whose [=map/keys=] are {{FenceEvent/eventType}}s and whose [=map/values=] are [=strings=] that parse to valid [=/URLs=], then these {{FenceEvent/eventType}}-[=/URL=] pairs will be [=register reporting metadata|registered=] for later access within any [=fenced frame=] that loads the {{SharedStorageResponse}} resulting from this {{SharedStorageWorklet/selectURL()}} call. - Inside a [=fenced frame=] with [=FenceEvent/eventType=]-[=/URL=] pairs that have been [=register reporting metadata|registered=] through {{SharedStorageWorklet/selectURL()}} with {{SharedStorageUrlWithMetadata/reportingMetadata}} {{/object}}s, if [=fence.reportEvent()=] is called on a [=FenceEvent=] with a [=FenceEvent/destination=] [=list/containing=] "`shared-storage-select-url`" and that [=FenceEvent=]'s corresponding [=FenceEvent/eventType=] is triggered, then the [=FenceEvent=]'s [=FenceEvent/eventData=] will be sent as a [=beacon=] to the registered [=/URL=] for that [=FenceEvent/eventType=]. + Inside a [=fenced frame=] with {{FenceEvent/eventType}}-[=/URL=] pairs that have been [=register reporting metadata|registered=] through {{SharedStorageWorklet/selectURL()}} with {{SharedStorageUrlWithMetadata/reportingMetadata}} {{/object}}s, if [=fence.reportEvent()=] is called on a {{FenceEvent}} with a {{FenceEvent/destination}} [=list/containing=] "`shared-storage-select-url`" and that {{FenceEvent}}'s corresponding {{FenceEvent/eventType}} is triggered, then the {{FenceEvent}}'s {{FenceEvent/eventData}} will be sent as a [=beacon=] to the registered [=/URL=] for that {{FenceEvent/eventType}}.
To validate reporting metadata, given an {{/object}} |reportingMetadata|, run the following steps: - 1. If |reportingMetadata| is [=map/empty=], return true. 1. If |reportingMetadata| is not a [=dictionary=], return false. - 1. [=map/iterate|For each=] eventType -> |urlString| of |reportingMetadata|, if the result of running [=get the canonical URL string if valid=] with |urlString| is undefined, return false. + 1. If |reportingMetadata| is [=map/empty=], return true. + 1. [=map/iterate|For each=] eventType → |urlString| of |reportingMetadata|, if the result of running [=get the canonical URL string if valid=] with |urlString| is undefined, return false. 1. Return true.
@@ -563,16 +571,16 @@ Moreover, each {{SharedStorageWorklet}}'s [=global scopes|list of global scopes=
- To register reporting metadata, given an {{/object}} |reportingMetadata| and a "fenced frame config struct" |fencedFrameConfigStruct|, run the following steps: + To register reporting metadata, given an {{/object}} |reportingMetadata| and a [=fenced frame config=] |fencedFrameConfigStruct|, run the following steps: 1. If |reportingMetadata| is [=map/empty=], return. 1. [=Assert=]: |reportingMetadata| is a [=dictionary=]. 1. Let |reportingUrlMap| be an [=map/empty=] [=map=]. - 1. [=map/iterate|For each=] |eventType| -> |urlString| of |reportingMetadata|: + 1. [=map/iterate|For each=] |eventType| → |urlString| of |reportingMetadata|: 1. Let |url| be the result of running a [=URL parser=] on |urlString|. 1. [=Assert=]: |url| is a valid [=/URL=]. 1. [=map/Set=] |reportingUrlMap|[|eventType|] to |url|. - 1. Store |reportingUrlMap| inside a "fenced frame reporter" class associated with |fencedFrameConfigStruct|. Both of these still need to be added to the draft [=fenced frame|Fenced Frame specification=]. + 1. Store |reportingUrlMap| inside a [=fenced frame reporter=] class associated with |fencedFrameConfigStruct|. Both of these still need to be added to the draft [[Fenced-Frame]].
## Entropy Budgets ## {#budgets} @@ -581,9 +589,9 @@ Moreover, each {{SharedStorageWorklet}}'s [=global scopes|list of global scopes= ### Navigation Entropy Budget ### {#nav-budget} - If a user activates a [=fenced frame=] whose {{FencedFrameConfig}} was generated by {{SharedStorageWorklet/selectURL()}} and thereby initiates a [=top-frame=] [=navigate|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 {{SharedStorageWorklet/selectURL()}}. To mitigate this, a [=user agent=] will set a per-[=calling site=] [=navigation entropy allowance=]. + If a user [=user activation|activates=] a [=fenced frame=] whose {{FencedFrameConfig}} was generated by {{SharedStorageWorklet/selectURL()}} and thereby initiates a [=top-level traversable=] [=navigate|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 {{SharedStorageWorklet/selectURL()}}. To mitigate this, a [=user agent=] will set a per-[=calling site=] [=navigation entropy allowance=]. - A calling site for {{SharedStorageWorklet/selectURL()}} is the [=site=] resulting from running [=obtain a site=] with the [=url/origin=] of an [=environment=] that makes a {{SharedStorageWorklet/selectURL()}} call. + A calling site for {{SharedStorageWorklet/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-frame=] [=navigate|navigations=] during a given [=navigation budget epoch=] for a given calling [=calling site=]. This [=navigation entropy allowance|allowance=] is defined by the [=user agent=] and is [=calling site=]-agnostic. @@ -595,7 +603,15 @@ Moreover, each {{SharedStorageWorklet}}'s [=global scopes|list of global scopes= 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=]). + A bit debit is a [=struct=] with the following [=struct/items=]: + +
+ : bits + :: a double + + : timestamp + :: a {{DOMHighResTimeStamp}} (from the [=Unix Epoch=]) +
[=Bit debits=] whose [=bit debit/timestamps=] precede the start of the current [=navigation budget epoch=] are said to be expired. @@ -605,9 +621,9 @@ Moreover, each {{SharedStorageWorklet}}'s [=global scopes|list of global scopes= {{SharedStorageWorklet/selectURL()}}'s argument "`urls`" is its input URL list. - When a [=calling site=] has insufficient [=calling site/remaining navigation budget=], {{SharedStorageWorklet/selectURL()}} will return a {{SharedStorageResponse}} (i.e. either a {{FencedFrameConfig}} or an opaque [=/URL=]) for the {{SharedStorageUrlWithMetadata/url}} in the {{SharedStorageUrlWithMetadata}} at the [=default index=] in its [=selectURL/input URL list=]. + When a [=calling site=] has insufficient [=calling site/remaining navigation budget=], {{SharedStorageWorklet/selectURL()}} will return a {{SharedStorageResponse}} (i.e. either a {{FencedFrameConfig}} or a [=urn uuid=]) for the {{SharedStorageUrlWithMetadata/url}} in the {{SharedStorageUrlWithMetadata}} at the [=default index=] in its [=selectURL/input URL list=]. - The default index for a call to {{SharedStorageWorklet/selectURL()}} is implementation-defined in such a way that it is independent from the result of the associated {{SharedStorageSelectURLOperation}}'s "`run`" method. + The default index for a call to {{SharedStorageWorklet/selectURL()}} is [=implementation-defined=] in such a way that it is independent from the result of the associated {{SharedStorageSelectURLOperation}}'s "`run`" method.
The [=default index=] could be defined to be 0. @@ -645,68 +661,68 @@ Moreover, each {{SharedStorageWorklet}}'s [=global scopes|list of global scopes= 1. Otherwise, return false.
- A [=bit debit=] will need to be [=charge shared storage navigation budget|charged=] to the [=shared storage navigation budget table=] for each [=top-frame=] [=navigate|navigation=] initiated by a [=fenced frame=] whose {{FencedFrameConfig}} was generated via {{SharedStorageWorklet/selectURL()}}, as this can leak cross-site data. Since the [=bit debit/bits=] to charge is calculated during the call to {{SharedStorageWorklet/selectURL()}} but only actually recorded in the [=shared storage navigation budget table=] if and when the resulting fenced frame initiates a [=top-frame=] [=beginning navigation|navigation=], the [=bit debit/bits=] must be stored as a pending shared storage budget debit in the corresponding {{FencedFrameConfig}} until this time. + A [=bit debit=] will need to be [=charge shared storage navigation budget|charged=] to the [=shared storage navigation budget table=] for each [=top-frame=] [=navigate|navigation=] initiated by a [=fenced frame=] whose {{FencedFrameConfig}} was generated via {{SharedStorageWorklet/selectURL()}}, as this can leak cross-site data. Since the [=bit debit/bits=] to charge is calculated during the call to {{SharedStorageWorklet/selectURL()}} but only actually recorded in the [=shared storage navigation budget table=] if and when the resulting fenced frame initiates a [=top-level traversable=] [=beginning navigation|navigation=], the [=bit debit/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. - Issue: 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. + Issue: The [=fenced frame config=] and its boolean has navigated have not yet been added to the draft [=Fenced Frame=] specification. Some form of them will be added, although their names are subject to bikeshedding. Fix the names when they are added.
To charge shared storage navigation budget during a [=beginning navigation|navigation=] with [=/navigable=] |navigable| and {{Document}} |sourceDocument|, run the following steps: - 1. If |navigable| is not a [=navigable/traversable navigable=], return. - 1. Let |node| be |sourceDocument|'s [=node navigable=]. - 1. While |node| is not null: - 1. Let |site| be the result of running [=obtain a site=] with |node|'s [=active document=]'s [=document/origin=]. - 1. If |node| has a "fenced frame config struct" and |site| is not an [=opaque origin=], perform the following steps: - 1. Let |pendingBits| be |node|'s "fenced frame config struct"'s [=pending shared storage budget debit=]. - 1. If |pendingBits| is greater than 0 and if "fenced frame config struct"'s [=has navigated=] is false, run the following steps: + 1. If |navigable| is not a [=top-level traversable=], return. + 1. Let |currentNavigable| be |sourceDocument|'s [=node navigable=]. + 1. While |currentNavigable| is not null: + 1. Let |site| be the result of running [=obtain a site=] with |currentNavigable|'s [=active document=]'s [=document/origin=]. + 1. If |currentNavigable| has a [=fenced frame config=] and |site| is not an [=opaque origin=], perform the following steps: + 1. Let |pendingBits| be |currentNavigable|'s [=fenced frame config=]'s [=pending shared storage budget debit=]. + 1. If |pendingBits| is greater than 0 and if [=fenced frame config=]'s [=has navigated=] is false, run the following steps: 1. Let |ledger| be [=user agent=]'s [=shared storage navigation budget table=][|site|]. 1. Let |bitDebit| be a new [=bit debit=]. 1. Set |bitDebit|'s [=bit debit/bits=] to |pendingBits|. 1. Let |currentTime| be the [=/current wall time=]. 1. Set |bitDebit|'s [=bit debit/timestamp=] to |currentTime|. 1. [=list/Append=] |bitDebit| to |ledger|. - 1. Set |node|'s "fenced frame config struct"'s [=has navigated=] to true. - 1. Set |node| to |node|'s [=navigable/parent=]. + 1. Set |currentNavigable|'s [=fenced frame config=]'s [=has navigated=] to true. + 1. Set |currentNavigable| to |currentNavigable|'s [=navigable/parent=].
### Reporting Entropy Budget ### {#report-budget} - Likewise, each time a call to [=fence.reportEvent()=] from a [=fenced frame=] originating via {{SharedStorageWorklet/selectURL()}} whose [=FenceEvent/destination=] [=list/contains=] "`shared-storage-select-url`" and whose [=FenceEvent/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. + Likewise, each time a call to [=fence.reportEvent()=] from a [=fenced frame=] originating via {{SharedStorageWorklet/selectURL()}} whose {{FenceEvent/destination}} [=list/contains=] "`shared-storage-select-url`" and whose {{FenceEvent/eventType}} is triggered, there is a leak of up to logarithm base 2 of the number of main input [=/URLs=] [=entropy bits=]. The [=user agent=] will need to set a per-[=page load=] [=reporting entropy allowance=] to restrict the information leaked, with page load referring to a [=top-level traversable=]'s (i.e. primary main frame's) lifecycle. A reporting entropy allowance is a maximum allowance of [=entropy bits=] that are permitted to leak via [=fence.reportEvent()=] during a given page load. This [=reporting entropy allowance|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 [=FenceEvent/destination=] [=list/containing=] "`shared-storage-select-url`", it will be necessary to [=charge shared storage reporting budget=] as below. + When [=fence.reportEvent()=] is called with a {{FenceEvent/destination}} [=list/containing=] "`shared-storage-select-url`", it will be necessary to [=charge shared storage reporting budget=] as below.
To determine reporting budget to charge, given a {{Document}} |sourceDocument|, run the following steps: 1. Let |debitSum| be 0. - 1. Let |node| be |sourceDocument|'s [=node navigable=]. - 1. While |node| is not null: - 1. If |node| has a "fenced frame config struct": - 1. Let |pendingBits| be |node|'s "fenced frame config struct"'s [=pending shared storage budget debit=]. - 1. If |pendingBits| is greater than 0 and if "fenced frame config struct"'s [=has reported=] is false, increment |debitSum| by |pendingBits| - 1. Set |node| to |node|'s [=navigable/parent=]. + 1. Let |currentNavigable| be |sourceDocument|'s [=node navigable=]. + 1. While |currentNavigable| is not null: + 1. If |currentNavigable| has a [=fenced frame config=]: + 1. Let |pendingBits| be |currentNavigable|'s [=fenced frame config=]'s [=pending shared storage budget debit=]. + 1. If |pendingBits| is greater than 0 and if [=fenced frame config=]'s [=has reported=] is false, increment |debitSum| by |pendingBits| + 1. Set |currentNavigable| to |currentNavigable|'s [=navigable/parent=]. 1. Return |debitSum|.
- Issue: 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. + Issue: The [=fenced frame config=] and its boolean has reported have not yet been added to the draft [=Fenced Frame=] specification. Some form of them will be added, although their names are subject to bikeshedding. Fix the names when they are added.
To charge shared storage reporting budget given a {{Document}} |sourceDocument|, run the following steps: 1. Let |toCharge| be the result of running [=determine reporting budget to charge=] with |sourceDocument|. - 1. Let |node| be |sourceDocument|'s [=node navigable=]. - 1. Let |topNode| be the result of running [=get the top-level traversable=] for |node|. + 1. Let |currentNavigable| be |sourceDocument|'s [=node navigable=]. + 1. Let |topNode| be the result of running [=get the top-level traversable=] for |currentNavigable|. 1. If |topNode|'s [=shared storage reporting budget=] is less than |toCharge|, return false. - 1. While |node| is not null: - 1. If |node| has a "fenced frame config struct" and if |node|'s "fenced frame config struct"'s [=pending shared storage budget debit=] is greater than 0, set |node|'s "fenced frame config struct"'s [=has reported=] to true. - 1. Set |node| to |node|'s [=navigable/parent=]. + 1. While |currentNavigable| is not null: + 1. If |currentNavigable| has a [=fenced frame config=] and if |currentNavigable|'s [=fenced frame config=]'s [=pending shared storage budget debit=] is greater than 0, set |currentNavigable|'s [=fenced frame config=]'s [=has reported=] to true. + 1. Set |currentNavigable| to |currentNavigable|'s [=navigable/parent=]. 1. Decrement |topNode|'s [=shared storage reporting budget=] by |toCharge|. 1. Return true.
From f3e5847bef5ef5edac86bffa95e3aadb7cd95158 Mon Sep 17 00:00:00 2001 From: Yao Xiao Date: Wed, 13 Mar 2024 19:38:12 -0400 Subject: [PATCH 05/15] address PR feedback --- spec.bs | 114 ++++++++++++++++++++++++++++++++------------------------ 1 file changed, 65 insertions(+), 49 deletions(-) diff --git a/spec.bs b/spec.bs index dfb3ef4..cb9665e 100644 --- a/spec.bs +++ b/spec.bs @@ -15,6 +15,8 @@ spec:infra; type:dfn; text:user agent for:/; text:string + type:dfn; + for:/; text:list spec:webidl; type:interface; text:double @@ -300,30 +302,43 @@ Moreover, each {{SharedStorageWorklet}}'s [=global scopes|list of global scopes= To get the select-url result index, given {{SharedStorageWorklet}} |worklet|, {{DOMString}} |operationName|, [=/list=] of {{SharedStorageUrlWithMetadata}}s |urlList|, and {{SharedStorageRunOperationMethodOptions}} |options|: 1. Let |promise| be a new [=promise=]. - 1. Let |window| be the [=current global object=]. + 1. Let |window| be |worklet|'s [=relevant settings object=]. 1. [=Assert=]: |window| is a {{Window}}. 1. If |window|'s [=Window/browsing context=] is null, then return a [=promise rejected=] with a {{TypeError}}. 1. If |window|'s [=associated document=] is not [=fully active=], return a [=promise rejected=] with a {{TypeError}}. - 1. Let |outsideSettings| be |worklet|'s [=relevant settings object=]. - 1. Return |promise|, and immediately [=obtain a worklet agent=] given |outsideSettings| and run the rest of these steps in that agent: + 1. Return |promise|, and immediately [=obtain a worklet agent=] given |window| and run the rest of these steps in that agent: 1. Let |index| be [=default index=]. - 1. If |worklet|'s [=module map=] is not [=map/empty=]: - 1. Let |operationMap| be the associated {{SharedStorageWorkletGlobalScope}}'s [=SharedStorageWorkletGlobalScope/operation map=]. - 1. If |operationMap| [=map/contains=] |operationName|: - 1. Let |operation| be |operationMap|[|operationName|]. - 1. Let |argumentsList| be the [=/list=] « |urlList| ». - 1. If |options| [=map/contains=] |data|, [=list/append=] |data| to |argumentsList|. - 1. Let |operationResult| be the result of [=invoking=] |operation| with |argumentsList|. - 1. If [=an exception was thrown=], then [=queue a global task=] on the [=DOM manipulation task source=], given |window|, to [=reject=] |promise| with a {{TypeError}}. - 1. Otherwise: - 1. Set |index| to |operationResult|, [=converted to a JavaScript value=] of type {{unsigned long}}. - 1. If [=an exception was thrown=]: - 1. [=Queue a global task=] on the [=DOM manipulation task source=], given |window|, to [=reject=] |promise| with a {{TypeError}}. - 1. Abort these steps. - 1. Otherwise, if |index| is greater than |urlList|'s [=list/size=]: - 1. [=Queue a global task=] on the [=DOM manipulation task source=], given |window|, to [=reject=] |promise| with a {{TypeError}}. - 1. Abort these steps. - 1. [=Queue a global task=] on the [=DOM manipulation task source=], given |window|, to [=resolve=] |promise| with |index|. + 1. [=Assert=]: |worklet|'s [=global scopes=]'s [=list/size=] is 1. + 1. [=Assert=]: |worklet|'s [=module map=] is not [=map/empty=]. + 1. Let |globalScope| be [=this=]'s [=global scopes=][0]. + 1. Let |operationMap| be |globalScope|'s [=SharedStorageWorkletGlobalScope/operation map=]. + 1. If |operationMap| does not [=map/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 {{SharedStorageWorkletGlobalScope/register()}} was never called with |operationName|. + + 1. Let |operation| be |operationMap|[|operationName|]. + 1. Let |argumentsList| be the [=/list=] « |urlList| ». + 1. If |options| [=map/contains=] |data|, [=list/append=] |data| to |argumentsList|. + 1. Let |operationResult| be the result of [=invoking=] |operation| with |argumentsList|. + 1. If [=an exception was thrown=], then [=queue a global task=] on the [=DOM manipulation task source=], given |window|, to [=reject=] |promise| with a {{TypeError}}. + + Note: This indicates that |operationCtor|'s run() method encounters an error, where |operationCtor| is the parameter in {{SharedStorageWorkletGlobalScope/register()}}. + + 1. Otherwise: + 1. Set |index| to |operationResult|, [=converted to a JavaScript value=] of type {{unsigned long}}. + 1. If [=an exception was thrown=]: + 1. [=Queue a global task=] on the [=DOM manipulation task source=], given |window|, to [=reject=] |promise| with a {{TypeError}}. + 1. Abort these steps. + + Note: The result index is a non-integer value. This violates the selectURL() protocol, and we don't know which url should be selected. + + 1. Otherwise, if |index| is greater than |urlList|'s [=list/size=]: + 1. [=Queue a global task=] on the [=DOM manipulation task source=], given |window|, to [=reject=] |promise| with a {{TypeError}}. + 1. 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. + + 1. [=Queue a global task=] on the [=DOM manipulation task source=], given |window|, to [=resolve=] |promise| with |index|.
@@ -331,17 +346,18 @@ Moreover, each {{SharedStorageWorklet}}'s [=global scopes|list of global scopes= 1. Let |resultPromise| be a new [=promise=]. 1. If [=this=]'s [=addModule initiated=] is false, then return a [=promise rejected=] with a {{TypeError}}. - 1. Let |window| be the [=current global object=]. + 1. Let |window| be [=this=]'s [=relevant settings object=]. 1. [=Assert=]: |window| is a {{Window}}. 1. Let |context| be |window|'s [=Window/browsing context=]. 1. If |context| is null, then return a [=promise rejected=] with a {{TypeError}}. 1. Let |document| be |context|'s [=active document=]. 1. If the result of running [=Is feature enabled in document for origin?=] on "[=PermissionsPolicy/shared-storage-select-url=]", |document|, and [=this=]'s [=SharedStorageWorklet/worklet origin=] returns false, return a [=promise rejected=] with a {{TypeError}}. - 1. If [=this=]'s [=global scopes|list of global scopes=] is [=list/empty=], then return a [=promise rejected=] with a {{TypeError}}. - 1. [=Assert=]: [=this=]'s [=global scopes|list of global scopes=] [=list/contains=] a single {{SharedStorageWorkletGlobalScope}}. - 1. If the result of running [=SharedStorageWorkletGlobalScope/check whether addModule is finished=] for [=this=]'s {{SharedStorageWorkletGlobalScope}} is false, return a [=promise rejected=] with a {{TypeError}}. - 1. If |urls| is empty or exceeds the maximum allowed length, return a [=promise rejected=] with a {{TypeError}}. - 1. Let |urlList| be an empty {{list}}. + 1. If [=this=]'s [=global scopes=] is [=list/empty=], then return a [=promise rejected=] with a {{TypeError}}. + 1. [=Assert=]: [=this=]'s [=global scopes=]'s [=list/size=] is 1. + 1. Let |globalScope| be [=this=]'s [=global scopes=][0]. + 1. If the result of running [=SharedStorageWorkletGlobalScope/check whether addModule is finished=] for |globalScope| is false, return a [=promise rejected=] with a {{TypeError}}. + 1. If |urls| is empty or if |urls|'s [=list/size=] is greater than 8, return a [=promise rejected=] with a {{TypeError}}. + 1. Let |urlList| be an empty [=list=]. 1. [=map/iterate|For each=] |urlWithMetadata| in |urls|: 1. If |urlWithMetadata| has no field "`url`", return a [=promise rejected=] with a {{TypeError}}. 1. Otherwise, let |urlString| be |urlWithMetadata|["`url`"]. @@ -350,7 +366,7 @@ Moreover, each {{SharedStorageWorklet}}'s [=global scopes|list of global scopes= 1. Otherwise, [=list/append=] |serializedUrl| to |urlList|. 1. If |urlWithMetadata| has field "`reportingMetadata`": 1. Let |reportingMetadata| be |urlWithMetadata|["`reportingMetadata`"]. - 1. If the result of running [=validate reporting metadata=] with |reportingMetadata| is false, reject |resultPromise| with a {{TypeError}} and abort these steps. + 1. If the result of running [=validate reporting metadata=] with |reportingMetadata| is false, [=reject=] |resultPromise| with a {{TypeError}} and abort these steps. 1. Let |fencedFrameConfigStruct| be a [=fenced frame config=] with an invalid "urn uuid". Add correct struct name as well as linking when Fenced Frame API updates their draft spec to include it. 1. Let |environment| be |window|'s [=relevant settings object=]. 1. If the result of running [=determine whether shared storage is allowed by context=] given |environment| and [=this=]'s [=SharedStorageWorklet/worklet origin=] is false, return a [=promise rejected=] with a {{TypeError}}. @@ -360,12 +376,11 @@ Moreover, each {{SharedStorageWorklet}}'s [=global scopes|list of global scopes= Issue: The [=fenced frame config=] 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. - 1. If |options|["`resolveToConfig`"] is true, resolve |resultPromise| with the result of running "obtain a {{FencedFrameConfig}} from a fenced frame config struct" with |fencedFrameConfigStruct|. Add correct struct and algorithms names as well as linking when Fenced Frame API updates their draft spec to include it. - 1. 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. + 1. If |options|["`resolveToConfig`"] is true, [=resolve=] |resultPromise| with the result of running "obtain a {{FencedFrameConfig}} from a fenced frame config struct" with |fencedFrameConfigStruct|. Add correct struct and algorithms names as well as linking when Fenced Frame API updates their draft spec to include it. + 1. 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. 1. If |fencedFrameConfigStruct|'s "urn uuid" is invalid (i.e. instead of pending mapped), then return. Add correct struct name and urn:uuid name as well as linking when Fenced Frame API updates their draft spec to include it. 1. Let |indexPromise| be the result of running [=get the select-url result index=], given [=this=], |name|, |urlList|, and |options|. - 1. [=Upon fulfillment=] of |indexPromise|, perform the following steps: - 1. Let |resultIndex| be the numerical value of |indexPromise|. + 1. [=Upon fulfillment=] of |indexPromise| with |resultIndex|, perform the following steps: 1. Let |site| be the result of running [=obtain a site=] with |document|'s [=url/origin=]. 1. Let |remainingBudget| be the result of running [=determine remaining navigation budget=] with |environment| and |site|. 1. [=Assert=]: |remainingBudget| is not undefined. @@ -375,10 +390,10 @@ Moreover, each {{SharedStorageWorklet}}'s [=global scopes|list of global scopes= 1. Set |fencedFrameConfigStruct|'s [=/url=] to |urlList|[|resultIndex|]. 1. Let |resultURLWithMetadata| be |urls|[|resultIndex|]. 1. If |resultURLWithMetadata| has field "`reportingMetadata`", run [=register reporting metadata=] with |resultURLWithMetadata|["`reportingMetadata`"]. - 1. If |options|["`keepAlive`"] is false, run [=terminate a worklet global scope=] with [=this=]'s {{SharedStorageWorkletGlobalScope}}. + 1. If |options|["`keepAlive`"] is false, run [=terminate a worklet global scope=] with [=this=]. 1. [=Upon rejection=] of |indexPromise|, perform the following steps: 1. Set |fencedFrameConfigStruct|'s [=/url=] to |urlList|[[=default index=]]. - 1. If |options|["`keepAlive`"] is false, run [=terminate a worklet global scope=] with [=this=]'s {{SharedStorageWorkletGlobalScope}}. + 1. If |options|["`keepAlive`"] is false, run [=terminate a worklet global scope=] with [=this=]. 1. Return |resultPromise|.
@@ -387,18 +402,21 @@ Moreover, each {{SharedStorageWorklet}}'s [=global scopes|list of global scopes= 1. Let |promise| be a new [=promise=]. 1. If [=this=]'s [=addModule initiated=] is false, then return a [=promise rejected=] with a {{TypeError}}. - 1. Let |window| be the [=current global object=]. + 1. Let |window| be [=this=]'s [=relevant settings object=]. 1. [=Assert=]: |window| is a {{Window}}. - 1. If [=this=]'s [=global scopes|list of global scopes=] is [=list/empty=], then return a [=promise rejected=] with a {{TypeError}}. - 1. [=Assert=]: [=this=]'s [=global scopes|list of global scopes=] [=list/contains=] a single {{SharedStorageWorkletGlobalScope}}. - 1. If the result of running [=SharedStorageWorkletGlobalScope/check whether addModule is finished=] for [=this=]'s {{SharedStorageWorkletGlobalScope}} is false, return a [=promise rejected=] with a {{TypeError}}. - 1. Let |outsideSettings| be [=this=]'s [=relevant settings object=]. - 1. If the result of running [=determine whether shared storage is allowed by context=] given |outsideSettings| and [=this=]'s [=SharedStorageWorklet/worklet origin=] is false, [=reject=] |promise| with a {{TypeError}}. - 1. If the result of running [=determine whether shared storage is allowed by enrollment and user preference=] given |outsideSettings| and [=this=]'s [=SharedStorageWorklet/worklet origin=] is false: + 1. If [=this=]'s [=global scopes=] is [=list/empty=], then return a [=promise rejected=] with a {{TypeError}}. + 1. [=Assert=]: [=this=]'s [=global scopes=]'s [=list/size=] is 1. + 1. Let |globalScope| be [=this=]'s [=global scopes=][0]. + 1. If the result of running [=SharedStorageWorkletGlobalScope/check whether addModule is finished=] for |globalScope| is false, return a [=promise rejected=] with a {{TypeError}}. + 1. If the result of running [=determine whether shared storage is allowed by context=] given |window| and [=this=]'s [=SharedStorageWorklet/worklet origin=] is false, [=reject=] |promise| with a {{TypeError}}. + 1. If the result of running [=determine whether shared storage is allowed by enrollment and user preference=] given |window| and [=this=]'s [=SharedStorageWorklet/worklet origin=] is false: 1. If [=this=]'s [=SharedStorageWorklet/is cross-origin worklet=] is false, [=reject=] |promise| with a {{TypeError}}. 1. Else, [=resolve=] |promise| with undefined. 1. Return |promise|. - 1. Return |promise|, and immediately [=obtaining a worklet agent=] given |outsideSettings| and run the rest of these steps in that agent: + 1. Return |promise|, and immediately [=obtaining a worklet agent=] given |window| and run the rest of these steps in that agent: + + Note: The |promise|'s resolution should be before and not depend on the execution inside {{SharedStorageWorkletGlobalScope}}. This is because shared storage is a type of unpartitioned storage, and a {{SharedStorageWorkletGlobalScope}} can have access to cross-site data, which shouldn't be leaked via {{SharedStorageWorklet/run()}} (via its success/error result). + 1. [=Queue a global task=] on the [=DOM manipulation task source=], given |window|, to [=resolve=] |promise| with undefined. 1. If [=this=]'s [=module map=] is not [=map/empty=]: 1. Let |operationMap| be [=this=]'s {{SharedStorageWorkletGlobalScope}}'s [=SharedStorageWorkletGlobalScope/operation map=]. @@ -411,7 +429,7 @@ Moreover, each {{SharedStorageWorklet}}'s [=global scopes|list of global scopes= 1. Otherwise, [=invoke=] |operation| without any arguments list. 1. If |options|["`keepAlive`"] is false: 1. Wait for |operation| to finish running, if applicable. - 1. Run [=terminate a worklet global scope=] with [=this=]'s {{SharedStorageWorkletGlobalScope}}. + 1. Run [=terminate a worklet global scope=] with [=this=]. ## Monkey Patch for [=Worklets=] ## {#worklet-monkey-patch} @@ -423,13 +441,11 @@ Moreover, each {{SharedStorageWorklet}}'s [=global scopes|list of global scopes= 4. If |this| is of type {{SharedStorageWorklet}}: 1. Let |addModuleAllowedResult| be the result of running [=check if addModule is allowed and update state=] given |this| and moduleURLRecord. 1. If |addModuleAllowedResult| is "DisallowedDueToNonPreferenceError": - 1. Reject promise with a {{TypeError}}. + 1. Return [=a promise rejected with=] a {{TypeError}}. 1. Else if |addModuleAllowedResult| is "DisallowedDueToPreferenceError": - 1. If |this|'s [=SharedStorageWorklet/is cross-origin worklet=] is false, then reject promise with a {{TypeError}}. Else, resolve promise. + 1. If |this|'s [=SharedStorageWorklet/is cross-origin worklet=] is false, then return [=a promise rejected with=] a {{TypeError}}. 1. Else: 1. [=Assert=]: |addModuleAllowedResult| is "Allowed". - 1. Resolve promise. - 1. Return promise.
On user preferences error, {{Worklet/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). @@ -1090,8 +1106,8 @@ On the other hand, methods for getting data from the [=shared storage database=] 1. Set |sharedStorageWorklet|'s [=cross-origin worklet allowed=] to true. 1. Let |addModulePromise| be the result of invoking sharedStorageWorklet.{{Worklet/addModule()|addModule}}(|moduleURL|, |options|). 1. Let |resultPromise| be a new [=promise=]. - 1. [=Upon fulfillment=] of |addModulePromise|, resolve |resultPromise| to |sharedStorageWorklet|. - 1. [=Upon rejection=] of |addModulePromise|, reject |resultPromise| with a {{TypeError}}. + 1. [=Upon fulfillment=] of |addModulePromise|, [=resolve=] |resultPromise| to |sharedStorageWorklet|. + 1. [=Upon rejection=] of |addModulePromise|, [=reject=] |resultPromise| with a {{TypeError}}. 1. Return |resultPromise|.
@@ -1289,7 +1305,7 @@ On the other hand, methods for getting data from the [=shared storage database=] 1. [=Enqueue the following steps=] on |queue|: 1. Let |remainingBudget| be the result of running [=determine remaining navigation budget=] with |site|. 1. [=Assert=]: |remainingBudget| is not undefined. - 1. Resolve [=queue a global task=] on the [=DOM manipulation task source=], given |realm|'s [=global object=], to [=resolve=] |promise| with |remainingBudget|. + 1. [=Resolve=] [=queue a global task=] on the [=DOM manipulation task source=], given |realm|'s [=global object=], to [=resolve=] |promise| with |remainingBudget|. 1. Return |promise|. From 1118025f1eb3a76890165d95f5d9ec0568fed267 Mon Sep 17 00:00:00 2001 From: Yao Xiao Date: Thu, 14 Mar 2024 13:12:05 -0400 Subject: [PATCH 06/15] address comments --- spec.bs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/spec.bs b/spec.bs index cb9665e..1537190 100644 --- a/spec.bs +++ b/spec.bs @@ -317,6 +317,7 @@ Moreover, each {{SharedStorageWorklet}}'s [=global scopes|list of global scopes= Note: This could happen if {{SharedStorageWorkletGlobalScope/register()}} was never called with |operationName|. 1. Let |operation| be |operationMap|[|operationName|]. + 1. [=Assert=]: |operation|'s [=associated realm=] is [=this=]'s [=relevant realm=]. 1. Let |argumentsList| be the [=/list=] « |urlList| ». 1. If |options| [=map/contains=] |data|, [=list/append=] |data| to |argumentsList|. 1. Let |operationResult| be the result of [=invoking=] |operation| with |argumentsList|. @@ -422,6 +423,7 @@ Moreover, each {{SharedStorageWorklet}}'s [=global scopes|list of global scopes= 1. Let |operationMap| be [=this=]'s {{SharedStorageWorkletGlobalScope}}'s [=SharedStorageWorkletGlobalScope/operation map=]. 1. If |operationMap| [=map/contains=] |name|: 1. Let |operation| be |operationMap|[|name|]. + 1. [=Assert=]: |operation|'s [=associated realm=] is [=this=]'s [=relevant realm=]. 1. If |options| [=map/contains=] |data|: 1. Let |argumentsList| be a new [=/list=]. 1. [=list/Append=] |data| to |argumentsList|. From aedd123ee3b0959c53fdb4e88396aca6ae2bd164 Mon Sep 17 00:00:00 2001 From: Yao Xiao Date: Thu, 14 Mar 2024 19:06:20 -0400 Subject: [PATCH 07/15] address comments --- spec.bs | 109 ++++++++++++++++++++++---------------------------------- 1 file changed, 42 insertions(+), 67 deletions(-) diff --git a/spec.bs b/spec.bs index 1537190..4e8b9b4 100644 --- a/spec.bs +++ b/spec.bs @@ -89,7 +89,7 @@ spec: ecma; urlPrefix: https://tc39.es/ecma262/ text: call; url: sec-call text: current realm; url: current-realm text: casting; url: sec-touint32 - text: GetMethod(); url: sec-getmethod + text: Get; url: sec-get-o-p text: [[GetPrototypeOf]](); for: object; url: sec-ordinary-object-internal-methods-and-internal-slots-getprototypeof text: IsConstructor(); url: sec-isconstructor spec: storage-partitioning; urlPrefix: https://privacycg.github.io/storage-partitioning/ @@ -244,13 +244,13 @@ Because adding multiple [=module scripts=] via {{Worklet/addModule()}} for the s When {{Worklet/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 [=SharedStorageWorklet/is cross-origin worklet=] is false, it will cause {{Worklet/addModule()}} to fail, as detailed in the [[#worklet-monkey-patch]].
- To check if user preference setting allows access to shared storage given an [=environment settings object=] |environment| and an [=url/origin=] |origin|, run the following step: + To check if user preference setting allows access to shared storage given an [=environment settings object=] |environment| and an [=/origin=] |origin|, run the following step: 1. Using values available in |environment| and |origin| as needed, perform an [=implementation-defined=] algorithm to return either true or false.
- To determine whether shared storage is allowed by context, given an [=environment settings object=] |environment| and an [=url/origin=] |origin|, run these steps: + To determine whether shared storage is allowed by context, given an [=environment settings object=] |environment| and an [=/origin=] |origin|, run these steps: 1. If |environment| is not a [=secure context=], then return false. 1. Let |outsideSettingsOrigin| be |environment|'s [=environment settings object/origin=]. @@ -263,7 +263,7 @@ When {{Worklet/addModule()}} is called for a worklet, it will run [=check if add
- To determine whether shared storage is allowed by enrollment and user preference, given an [=environment settings object=] |environment| and an [=url/origin=] |origin|, run these steps: + To determine whether shared storage is allowed by enrollment and user preference, given an [=environment settings object=] |environment| and an [=/origin=] |origin|, run these steps: 1. If the result of running [=obtaining a site|obtain a site=] with |origin| is not [=enrolled=], then return false. 1. If the result of running [=check if user preference setting allows access to shared storage=] given |environment| and |origin| is false, then return false. @@ -274,27 +274,27 @@ When {{Worklet/addModule()}} is called for a worklet, it will run [=check if add Here are the scenarios where the algorithms [=determine whether shared storage is allowed by context=] and [=determine whether shared storage is allowed by enrollment and user preference=] are used: - For each method under [[#window-setter]], |environment| is the current context, and |origin| is |environment|'s [=environment settings object/origin=]. - - For creating a worklet, and for initiating (from {{Window}}) and running (from {{SharedStorageWorkletGlobalScope}}) operations on a worklet, |environment| is the [=environment settings object=] associated with the {{Window}} that created the worklet, and |origin| is the [=SharedStorageWorklet/worklet origin=]. - - For [[#ss-fetch-algo]], |environment| is the request's [=request/window=], and |origin| is the request's [=request/current URL=]'s [=url/origin=] origin. + - For creating a worklet, and for initiating (from a {{Window}}) and running (from {{SharedStorageWorkletGlobalScope}}) operations on a worklet, |environment| is the [=environment settings object=] associated with the {{Window}} that created the worklet, and |origin| is the [=SharedStorageWorklet/worklet origin=]. + - For [[#ss-fetch-algo]], |environment| is the request's [=request/window=], and |origin| is the request's [=request/current URL=]'s [=url/origin=].
- To check if addModule is allowed and update state given a {{SharedStorageWorklet}} |worklet| and an [=/URL=] |moduleURLRecord|, run the following steps: + To check if addModule is allowed and update state given a {{SharedStorageWorklet}} |worklet| and a [=/URL=] |moduleURLRecord|, run the following steps: 1. If |worklet|'s [=addModule initiated=] is true, return "DisallowedDueToNonPreferenceError". 1. Set |worklet|'s [=addModule initiated=] to true. 1. [=Assert=]: |worklet|'s [=SharedStorageWorklet/worklet origin=] is null. 1. Let |workletOrigin| be |moduleURLRecord|'s [=url/origin=]. - 1. If the result of running [=determine whether shared storage is allowed by context=] given [=current global object=]'s [=relevant settings object=] and |workletOrigin| is false, return "DisallowedDueToNonPreferenceError". - 1. If |workletOrigin| and [=current global object=]'s [=relevant settings object=]'s [=environment settings object/origin=] are not [=same origin=], set |worklet|'s [=SharedStorageWorklet/is cross-origin worklet=] to true. + 1. If the result of running [=determine whether shared storage is allowed by context=] given the [=current settings object=] and |workletOrigin| is false, return "DisallowedDueToNonPreferenceError". + 1. If |workletOrigin| and the [=current settings object=]'s [=environment settings object/origin=] are not [=same origin=], then set |worklet|'s [=SharedStorageWorklet/is cross-origin worklet=] to true. 1. If |worklet|'s [=cross-origin worklet allowed=] is false, and if |worklet|'s [=SharedStorageWorklet/is cross-origin worklet=] is true, return "DisallowedDueToNonPreferenceError". - 1. If the result of running [=determine whether shared storage is allowed by enrollment and user preference=] given [=current global object=]'s [=relevant settings object=] and |workletOrigin| is false, return "DisallowedDueToPreferenceError". + 1. If the result of running [=determine whether shared storage is allowed by enrollment and user preference=] given the [=current settings object=] and |workletOrigin| is false, return "DisallowedDueToPreferenceError". 1. Set |worklet|'s [=SharedStorageWorklet/worklet origin=] to |workletOrigin|. 1. Return "Allowed".
Moreover, each {{SharedStorageWorklet}}'s [=global scopes|list of global scopes=], initially empty, can contain at most one instance of its [=worklet global scope type=], the {{SharedStorageWorkletGlobalScope}}. - Note: The website that serves the module script should be aware of the implication of CORS: when the the module script's [=/URL=]'s [=url/origin=] and the worklet's creator window's origin is not [=same origin=], and by granting the module script resource via CORS, it will also grant the worklet's creation and subsequent operations on the worklet, under module script's [=/URL=]'s [=url/origin=]. For example, the worklet's creator context could poison and use up the [=SharedStorageWorklet/worklet origin=]'s [=remaining navigation budget=]. + Note: The website that serves the module script should be aware of the implication of CORS: when the module script's [=/URL=]'s [=url/origin=] and the worklet's creator {{Window}} origin are not [=same origin=], by sending permissive CORS headers on the module script response, the server will be granting the worklet's creation and subsequent operations on the worklet, under that module script's [=url/origin=]. For example, the worklet's creator {{Window}} could poison and use up the [=SharedStorageWorklet/worklet origin=]'s [=remaining navigation budget=] by calling {{SharedStorageWorklet/selectURL()}} or {{SharedStorageWorklet/run()}}. ## Run Operation Methods on {{SharedStorageWorklet}} ## {#run-op-shared-storage-worklet} @@ -358,6 +358,9 @@ Moreover, each {{SharedStorageWorklet}}'s [=global scopes|list of global scopes= 1. Let |globalScope| be [=this=]'s [=global scopes=][0]. 1. If the result of running [=SharedStorageWorkletGlobalScope/check whether addModule is finished=] for |globalScope| is false, return a [=promise rejected=] with a {{TypeError}}. 1. If |urls| is empty or if |urls|'s [=list/size=] is greater than 8, return a [=promise rejected=] with a {{TypeError}}. + + Note: 8 is chosen here so that each call of {{SharedStorageWorklet/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. + 1. Let |urlList| be an empty [=list=]. 1. [=map/iterate|For each=] |urlWithMetadata| in |urls|: 1. If |urlWithMetadata| has no field "`url`", return a [=promise rejected=] with a {{TypeError}}. @@ -382,7 +385,7 @@ Moreover, each {{SharedStorageWorklet}}'s [=global scopes|list of global scopes= 1. If |fencedFrameConfigStruct|'s "urn uuid" is invalid (i.e. instead of pending mapped), then return. Add correct struct name and urn:uuid name as well as linking when Fenced Frame API updates their draft spec to include it. 1. Let |indexPromise| be the result of running [=get the select-url result index=], given [=this=], |name|, |urlList|, and |options|. 1. [=Upon fulfillment=] of |indexPromise| with |resultIndex|, perform the following steps: - 1. Let |site| be the result of running [=obtain a site=] with |document|'s [=url/origin=]. + 1. Let |site| be the result of running [=obtain a site=] with |document|'s [=Document/origin=]. 1. Let |remainingBudget| be the result of running [=determine remaining navigation budget=] with |environment| and |site|. 1. [=Assert=]: |remainingBudget| is not undefined. 1. Let |pendingBits| be the logarithm base 2 of |urlList|'s [=list/size=]. @@ -471,60 +474,28 @@ Moreover, each {{SharedStorageWorklet}}'s [=global scopes|list of global scopes= The {{SharedStorageWorklet}}'s [=worklet global scope type=] is {{SharedStorageWorkletGlobalScope}}. + The {{SharedStorageWorklet}}'s [=worklet destination type=] is "sharedstorageworklet". + + Add "sharedstorageworklet" to the possible strings that a request [=request/destination=] can have. + [Exposed=SharedStorageWorklet, Global=SharedStorageWorklet] interface SharedStorageWorkletGlobalScope : WorkletGlobalScope { undefined register(DOMString name, - SharedStorageOperationConstructor operationCtor); + Function operationCtor); readonly attribute WorkletSharedStorage sharedStorage; }; - - callback SharedStorageOperationConstructor = - SharedStorageOperation(optional SharedStorageRunOperationMethodOptions options); - - [Exposed=SharedStorageWorklet] - interface SharedStorageOperation { - }; - - dictionary SharedStorageRunOperationMethodOptions { - object data; - boolean resolveToConfig = false; - boolean keepAlive = false; - }; Each {{SharedStorageWorkletGlobalScope}} has an associated [=environment settings object=] outside settings, which is the associated {{SharedStorageWorklet}}'s [=relevant settings object=]. - Each {{SharedStorageWorkletGlobalScope}} has an associated [=url/origin=] worklet origin, which is initialized to the associated {{SharedStorageWorklet}}'s [=SharedStorageWorklet/worklet origin=]. + Each {{SharedStorageWorkletGlobalScope}} has an associated [=/origin=] worklet origin, which is initialized to the associated {{SharedStorageWorklet}}'s [=SharedStorageWorklet/worklet origin=]. Each {{SharedStorageWorkletGlobalScope}} has an associated [=/boolean=] addModule success, which is initialized to false. - The {{SharedStorageWorkletGlobalScope}}'s [=module map=]'s [=module scripts=] should each define and {{register}} one or more {{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 {{SharedStorageWorkletGlobalScope/register()}} must be one of the following two types: - * {{SharedStorageRunOperation}} - * {{SharedStorageSelectURLOperation}} - - 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 [=/URLs=] each wrapped with optional metadata), performs an async operation, and then returns a promise to a {{long}} integer index specifying which of these [=/URLs=] should be selected. - - - [Exposed=SharedStorageWorklet] - interface SharedStorageRunOperation : SharedStorageOperation { - Promise<undefined> run(object data); - }; - - [Exposed=SharedStorageWorklet] - interface SharedStorageSelectURLOperation : SharedStorageOperation { - Promise<long> run(object data, - FrozenArray<SharedStorageUrlWithMetadata> urls); - }; - - Each {{SharedStorageWorkletGlobalScope}} also has an associated {{WorkletSharedStorage}} instance, with the [=SharedStorageWorkletGlobalScope/sharedStorage getter=] algorithm as described below. ## {{SharedStorageWorkletGlobalScope}} algorithms ## {#scope-algo} @@ -535,12 +506,10 @@ Moreover, each {{SharedStorageWorklet}}'s [=global scopes|list of global scopes= 1. Let |operationMap| be this {{SharedStorageWorkletGlobalScope}}'s [=SharedStorageWorkletGlobalScope/operation map=]. 1. If |operationMap| [=map/contains=] an [=map/entry=] with [=map/key=] |name|, throw a {{TypeError}}. 1. If |operationCtor| is missing, throw a {{TypeError}}. - 1. If the result of running [=IsConstructor()=] with |operationCtor| is false, throw a {{TypeError}}. - 1. Let |prototype| be the result of running |operationCtor|'s [=object/[[GetPrototypeOf]]()=] method. - 1. If |prototype| is not an [=object=], throw a {{TypeError}}. - 1. Let |run| be the result of running [=GetMethod()=] with |prototype| and "`run`". - 1. If |run| is undefined, throw a {{TypeError}}. - 1. [=map/Set=] the value of |operationMap|[|name|] to |run|. + 1. Let |operationClassInstance| be the result of [=constructing=] |operationCtor|, with no arguments. + 1. Let |runFunction| be [=Get=](|operationClassInstance|, "`run`"). Rethrow any exceptions. + 1. Let |runIDLFunction| be the result of [=converting=] |runFunction| to a Web IDL Function instance. + 1. [=map/Set=] the value of |operationMap|[|name|] to |runIDLFunction|.
@@ -569,7 +538,7 @@ Moreover, each {{SharedStorageWorklet}}'s [=global scopes|list of global scopes= If a {{SharedStorageUrlWithMetadata}} [=dictionary=] contains a non-[=map/empty=] {{SharedStorageUrlWithMetadata/reportingMetadata}} {{/object}} in the form of a [=dictionary=] whose [=map/keys=] are {{FenceEvent/eventType}}s and whose [=map/values=] are [=strings=] that parse to valid [=/URLs=], then these {{FenceEvent/eventType}}-[=/URL=] pairs will be [=register reporting metadata|registered=] for later access within any [=fenced frame=] that loads the {{SharedStorageResponse}} resulting from this {{SharedStorageWorklet/selectURL()}} call. - Inside a [=fenced frame=] with {{FenceEvent/eventType}}-[=/URL=] pairs that have been [=register reporting metadata|registered=] through {{SharedStorageWorklet/selectURL()}} with {{SharedStorageUrlWithMetadata/reportingMetadata}} {{/object}}s, if [=fence.reportEvent()=] is called on a {{FenceEvent}} with a {{FenceEvent/destination}} [=list/containing=] "`shared-storage-select-url`" and that {{FenceEvent}}'s corresponding {{FenceEvent/eventType}} is triggered, then the {{FenceEvent}}'s {{FenceEvent/eventData}} will be sent as a [=beacon=] to the registered [=/URL=] for that {{FenceEvent/eventType}}. + Inside a [=fenced frame=] with {{FenceEvent/eventType}}-[=/URL=] pairs that have been [=register reporting metadata|registered=] through {{SharedStorageWorklet/selectURL()}} with {{SharedStorageUrlWithMetadata/reportingMetadata}} {{/object}}s, if {{reportEvent()}} is called on a {{FenceEvent}} with a {{FenceEvent/destination}} [=list/containing=] "`shared-storage-select-url`" and that {{FenceEvent}}'s corresponding {{FenceEvent/eventType}} is triggered, then the {{FenceEvent}}'s {{FenceEvent/eventData}} will be sent as a [=beacon=] to the registered [=/URL=] for that {{FenceEvent/eventType}}.
To validate reporting metadata, given an {{/object}} |reportingMetadata|, run the following steps: @@ -641,18 +610,18 @@ Moreover, each {{SharedStorageWorklet}}'s [=global scopes|list of global scopes= When a [=calling site=] has insufficient [=calling site/remaining navigation budget=], {{SharedStorageWorklet/selectURL()}} will return a {{SharedStorageResponse}} (i.e. either a {{FencedFrameConfig}} or a [=urn uuid=]) for the {{SharedStorageUrlWithMetadata/url}} in the {{SharedStorageUrlWithMetadata}} at the [=default index=] in its [=selectURL/input URL list=]. - The default index for a call to {{SharedStorageWorklet/selectURL()}} is [=implementation-defined=] in such a way that it is independent from the result of the associated {{SharedStorageSelectURLOperation}}'s "`run`" method. + The default index for a call to {{SharedStorageWorklet/selectURL()}} is [=implementation-defined=] in such a way that it is independent from the result of the registered operation class's "`run`" method.
The [=default index=] could be defined to be 0. - In this case, whenever the {{SharedStorageSelectURLOperation}}'s "`run`" method encounters an error, or whenever there is insufficient [=calling site/remaining navigation budget=], the "`run`" method would return 0, and hence {{SharedStorageWorklet/selectURL()}} would return a {{SharedStorageResponse}} for the first {{SharedStorageUrlWithMetadata/url}} in its [=selectURL/input URL list=]. + In this case, whenever the registered operation class's "`run`" method encounters an error, or whenever there is insufficient [=calling site/remaining navigation budget=], the "`run`" method would return 0, and hence {{SharedStorageWorklet/selectURL()}} would return a {{SharedStorageResponse}} for the first {{SharedStorageUrlWithMetadata/url}} in its [=selectURL/input URL list=].
The [=default index=] could be defined to be [=selectURL/input URL list=]'s [=list/size=] minus 1. - In this case, whenever the {{SharedStorageSelectURLOperation}}'s "`run`" method encounters an error, or whenever there is insufficient [=calling site/remaining navigation budget=], {{SharedStorageWorklet/selectURL()}} would return a {{SharedStorageResponse}} for the last {{SharedStorageUrlWithMetadata/url}} in its [=selectURL/input URL list=]. + In this case, whenever the registered operation class's "`run`" method encounters an error, or whenever there is insufficient [=calling site/remaining navigation budget=], {{SharedStorageWorklet/selectURL()}} would return a {{SharedStorageResponse}} for the last {{SharedStorageUrlWithMetadata/url}} in its [=selectURL/input URL list=].
@@ -707,13 +676,13 @@ Moreover, each {{SharedStorageWorklet}}'s [=global scopes|list of global scopes= ### Reporting Entropy Budget ### {#report-budget} - Likewise, each time a call to [=fence.reportEvent()=] from a [=fenced frame=] originating via {{SharedStorageWorklet/selectURL()}} whose {{FenceEvent/destination}} [=list/contains=] "`shared-storage-select-url`" and whose {{FenceEvent/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. + Likewise, each time a call to {{reportEvent()}} from a [=fenced frame=] originating via {{SharedStorageWorklet/selectURL()}} whose {{FenceEvent/destination}} [=list/contains=] "`shared-storage-select-url`" and whose {{FenceEvent/eventType}} is triggered, there is a leak of up to logarithm base 2 of the number of main input [=/URLs=] [=entropy bits=]. The [=user agent=] will need to set a per-[=page load=] [=reporting entropy allowance=] to restrict the information leaked, with page load referring to a [=top-level traversable=]'s (i.e. primary main frame's) lifecycle. - A reporting entropy allowance is a maximum allowance of [=entropy bits=] that are permitted to leak via [=fence.reportEvent()=] during a given page load. This [=reporting entropy allowance|allowance=] is defined by the [=user agent=]. + A reporting entropy allowance is a maximum allowance of [=entropy bits=] that are permitted to leak via {{reportEvent()}} during a given page load. This [=reporting entropy allowance|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 {{FenceEvent/destination}} [=list/containing=] "`shared-storage-select-url`", it will be necessary to [=charge shared storage reporting budget=] as below. + When {{reportEvent()}} is called with a {{FenceEvent/destination}} [=list/containing=] "`shared-storage-select-url`", it will be necessary to [=charge shared storage reporting budget=] as below.
To determine reporting budget to charge, given a {{Document}} |sourceDocument|, run the following steps: @@ -777,7 +746,7 @@ The Shared Storage API will integrate into the [=Storage Model|Storage API=] as A [=user agent=]'s [=shared storage shed=] holds all shared storage data.
- To obtain a shared storage shelf, given a [=shared storage shed=] |shed|, an [=environment settings object=] |environment|, and an [=url/origin=] |origin|, run these steps: + To obtain a shared storage shelf, given a [=shared storage shed=] |shed|, an [=environment settings object=] |environment|, and an [=/origin=] |origin|, run these steps: 1. If the result of running [=determine whether shared storage is allowed by context=] given |environment| and |origin| is false, then return failure. 1. If the result of running [=determine whether shared storage is allowed by enrollment and user preference=] given |environment| and |origin| is false, then return failure. @@ -807,7 +776,7 @@ The Shared Storage API will integrate into the [=Storage Model|Storage API=] as Note: Currently, a [=shared storage bucket=]'s [=bottle map=] has [=map/size=] `1`, since there is only one [=storage endpoint=] [=storage endpoint/registered=] with [=storage type|type=] "`shared`".
- To obtain a shared storage bottle map, given an [=environment settings object=] |environment| and an [=url/origin=] |origin|, run these steps: + To obtain a shared storage bottle map, given an [=environment settings object=] |environment| and an [=/origin=] |origin|, run these steps: 1. Let |shed| be the [=user agent=]'s [=shared storage shed=]. 1. Let |shelf| be the result of running [=obtain a shared storage shelf=] with |shed|, |environment|, and |origin|. @@ -936,7 +905,7 @@ The {{SharedStorage}} interface is the base for derived interfaces {{WindowShare 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 {{WindowSharedStorage/worklet}} attribute which is used to call {{Worklet/addModule()}}, are declared in {{WindowSharedStorage}} and exposed to the {{Window}} only, as these are the means by which the {{Window}} interacts with the {{SharedStorageWorklet}}. +Meanwhile, methods for posting operations to run inside {{SharedStorageWorkletGlobalScope}} (i.e. {{SharedStorageWorklet/selectURL()}} and {{SharedStorageWorklet/run()}}), along with the {{WindowSharedStorage/worklet}} attribute which is used to call {{Worklet/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 [=shared storage database|database=]. @@ -974,6 +943,12 @@ On the other hand, methods for getting data from the [=shared storage database=] readonly attribute SharedStorageWorklet worklet; }; + + dictionary SharedStorageRunOperationMethodOptions { + object data; + boolean resolveToConfig = false; + boolean keepAlive = false; + }; ### Window Setter/Deleter Methods ### {#window-setter} @@ -1648,6 +1623,6 @@ Privacy Considerations {#privacy} 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 [=/URL=]s 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=] [=navigate|navigation=] and/or the [=fenced frame=] calls the [=fence.reportEvent()=] API. + In particular, an embedder can select a [=/URL=] from a short list of [=/URL=]s 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=] [=navigate|navigation=] and/or the [=fenced frame=] calls the {{reportEvent()}} API. An embedder is also able to send aggregatable reports through the [=Private Aggregation|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. From f785feaf3a68176a17037e5c372c7490f4d5e5d1 Mon Sep 17 00:00:00 2001 From: Yao Xiao Date: Wed, 20 Mar 2024 14:02:21 -0400 Subject: [PATCH 08/15] Address comments --- spec.bs | 174 +++++++++++++++++++++++++++++++++++--------------------- 1 file changed, 110 insertions(+), 64 deletions(-) diff --git a/spec.bs b/spec.bs index 4e8b9b4..332b4f8 100644 --- a/spec.bs +++ b/spec.bs @@ -30,6 +30,7 @@ spec:html; spec:fenced-frame; type:dfn; for:fencedframetype; text:fenced frame reporter + for:browsing context; text:fenced frame config instance
@@ -38,6 +39,11 @@ urlPrefix: https://www.ietf.org/rfc/rfc4122.txt
 spec: html; urlPrefix: https://html.spec.whatwg.org/multipage/
     type: dfn
         text: worklets; url: worklets.html#worklets
+        text: added modules list; url: worklets.html#concept-worklet-added-modules-list
+        text: set up a worklet environment settings object; url: worklets.html#set-up-a-worklet-environment-settings-object
+        text: fetch a worklet/module worker script graph; url: webappapis.html#fetch-a-worklet/module-worker-script-graph
+        text: fetch a worklet script graph; url: webappapis.html#fetch-a-worklet-script-graph
+        text: processCustomFetchResponse; url: webappapis.html#fetching-scripts-processcustomfetchresponse
         text: environment; url: webappapis.html#environment
         text: obtaining a worklet agent; url: webappapis.html#obtain-a-worklet-agent
         text: top-frame; url: webappapis.html#top-level-traversable
@@ -229,19 +235,13 @@ The {{SharedStorageWorklet}} object allows developers to supply [=module scripts
 
 Each {{SharedStorageWorklet}} has an associated boolean addModule initiated, initialized to false.
 
-Each {{SharedStorageWorklet}} has an associated [=/origin=]-or-null worklet origin, initialized to null.
-
-
-The intent is for [=SharedStorageWorklet/worklet origin=] to match, identically, the insideSettings's [=environment settings object/origin=] (as computed in [=create a worklet global scope=]). -
- Each {{SharedStorageWorklet}} has an associated boolean cross-origin worklet allowed, initialized to false. Each {{SharedStorageWorklet}} has an associated boolean is cross-origin worklet, initialized to false. Because adding multiple [=module scripts=] via {{Worklet/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 {{Worklet/addModule()}}, each {{SharedStorageWorklet}} can only call {{Worklet/addModule()}} once. The [=addModule initiated=] boolean makes it possible to enforce this restriction. -When {{Worklet/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 [=SharedStorageWorklet/is cross-origin worklet=] is false, it will cause {{Worklet/addModule()}} to fail, as detailed in the [[#worklet-monkey-patch]]. +When {{Worklet/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 [=SharedStorageWorklet/is cross-origin worklet=] is false, it will cause {{Worklet/addModule()}} to fail, as detailed in the [[#add-module-monkey-patch]].
To check if user preference setting allows access to shared storage given an [=environment settings object=] |environment| and an [=/origin=] |origin|, run the following step: @@ -274,7 +274,8 @@ When {{Worklet/addModule()}} is called for a worklet, it will run [=check if add Here are the scenarios where the algorithms [=determine whether shared storage is allowed by context=] and [=determine whether shared storage is allowed by enrollment and user preference=] are used: - For each method under [[#window-setter]], |environment| is the current context, and |origin| is |environment|'s [=environment settings object/origin=]. - - For creating a worklet, and for initiating (from a {{Window}}) and running (from {{SharedStorageWorkletGlobalScope}}) operations on a worklet, |environment| is the [=environment settings object=] associated with the {{Window}} that created the worklet, and |origin| is the [=SharedStorageWorklet/worklet origin=]. + - For creating a worklet, |environment| is the [=environment settings object=] associated with the {{Window}} that created the worklet, and |origin| is the module scirpt url's [=url/origin=]. + - For initiating (from a {{Window}}) and for running (from {{SharedStorageWorkletGlobalScope}}) operations on a worklet, |environment| is the [=environment settings object=] associated with the {{Window}} that created the worklet, and |origin| is the worklet's [=global scopes=][0]'s [=global object/realm=]'s HostDefined field's [=environment settings object/origin=]. - For [[#ss-fetch-algo]], |environment| is the request's [=request/window=], and |origin| is the request's [=request/current URL=]'s [=url/origin=].
@@ -282,19 +283,17 @@ When {{Worklet/addModule()}} is called for a worklet, it will run [=check if add To check if addModule is allowed and update state given a {{SharedStorageWorklet}} |worklet| and a [=/URL=] |moduleURLRecord|, run the following steps: 1. If |worklet|'s [=addModule initiated=] is true, return "DisallowedDueToNonPreferenceError". 1. Set |worklet|'s [=addModule initiated=] to true. - 1. [=Assert=]: |worklet|'s [=SharedStorageWorklet/worklet origin=] is null. 1. Let |workletOrigin| be |moduleURLRecord|'s [=url/origin=]. 1. If the result of running [=determine whether shared storage is allowed by context=] given the [=current settings object=] and |workletOrigin| is false, return "DisallowedDueToNonPreferenceError". 1. If |workletOrigin| and the [=current settings object=]'s [=environment settings object/origin=] are not [=same origin=], then set |worklet|'s [=SharedStorageWorklet/is cross-origin worklet=] to true. 1. If |worklet|'s [=cross-origin worklet allowed=] is false, and if |worklet|'s [=SharedStorageWorklet/is cross-origin worklet=] is true, return "DisallowedDueToNonPreferenceError". 1. If the result of running [=determine whether shared storage is allowed by enrollment and user preference=] given the [=current settings object=] and |workletOrigin| is false, return "DisallowedDueToPreferenceError". - 1. Set |worklet|'s [=SharedStorageWorklet/worklet origin=] to |workletOrigin|. 1. Return "Allowed".
Moreover, each {{SharedStorageWorklet}}'s [=global scopes|list of global scopes=], initially empty, can contain at most one instance of its [=worklet global scope type=], the {{SharedStorageWorkletGlobalScope}}. - Note: The website that serves the module script should be aware of the implication of CORS: when the module script's [=/URL=]'s [=url/origin=] and the worklet's creator {{Window}} origin are not [=same origin=], by sending permissive CORS headers on the module script response, the server will be granting the worklet's creation and subsequent operations on the worklet, under that module script's [=url/origin=]. For example, the worklet's creator {{Window}} could poison and use up the [=SharedStorageWorklet/worklet origin=]'s [=remaining navigation budget=] by calling {{SharedStorageWorklet/selectURL()}} or {{SharedStorageWorklet/run()}}. + Note: The website that serves the module script should be aware of the implication of CORS: when the module script's [=/URL=]'s [=url/origin=] and the worklet's creator {{Window}} origin are not [=same origin=], by sending permissive CORS headers on the module script response, the server will be granting the worklet's creation and subsequent operations on the worklet, under that module script's [=url/origin=]. For example, the worklet's creator {{Window}} could poison and use up the worklet origin's [=remaining navigation budget=] by calling {{SharedStorageWorklet/selectURL()}} or {{SharedStorageWorklet/run()}}, where the worklet origin is the global scope's [=global object/realm=]'s HostDefined field's [=environment settings object/origin=]. ## Run Operation Methods on {{SharedStorageWorklet}} ## {#run-op-shared-storage-worklet} @@ -326,7 +325,7 @@ Moreover, each {{SharedStorageWorklet}}'s [=global scopes|list of global scopes= Note: This indicates that |operationCtor|'s run() method encounters an error, where |operationCtor| is the parameter in {{SharedStorageWorkletGlobalScope/register()}}. 1. Otherwise: - 1. Set |index| to |operationResult|, [=converted to a JavaScript value=] of type {{unsigned long}}. + 1. Set |index| to |operationResult|. 1. If [=an exception was thrown=]: 1. [=Queue a global task=] on the [=DOM manipulation task source=], given |window|, to [=reject=] |promise| with a {{TypeError}}. 1. Abort these steps. @@ -352,10 +351,11 @@ Moreover, each {{SharedStorageWorklet}}'s [=global scopes|list of global scopes= 1. Let |context| be |window|'s [=Window/browsing context=]. 1. If |context| is null, then return a [=promise rejected=] with a {{TypeError}}. 1. Let |document| be |context|'s [=active document=]. - 1. If the result of running [=Is feature enabled in document for origin?=] on "[=PermissionsPolicy/shared-storage-select-url=]", |document|, and [=this=]'s [=SharedStorageWorklet/worklet origin=] returns false, return a [=promise rejected=] with a {{TypeError}}. - 1. If [=this=]'s [=global scopes=] is [=list/empty=], then return a [=promise rejected=] with a {{TypeError}}. 1. [=Assert=]: [=this=]'s [=global scopes=]'s [=list/size=] is 1. 1. Let |globalScope| be [=this=]'s [=global scopes=][0]. + 1. Let |workletOrigin| be |globalScope|'s [=global object/realm=]'s HostDefined field's [=environment settings object/origin=]. + 1. If the result of running [=Is feature enabled in document for origin?=] on "[=PermissionsPolicy/shared-storage-select-url=]", |document|, and |workletOrigin| returns false, return a [=promise rejected=] with a {{TypeError}}. + 1. If [=this=]'s [=global scopes=] is [=list/empty=], then return a [=promise rejected=] with a {{TypeError}}. 1. If the result of running [=SharedStorageWorkletGlobalScope/check whether addModule is finished=] for |globalScope| is false, return a [=promise rejected=] with a {{TypeError}}. 1. If |urls| is empty or if |urls|'s [=list/size=] is greater than 8, return a [=promise rejected=] with a {{TypeError}}. @@ -371,18 +371,17 @@ Moreover, each {{SharedStorageWorklet}}'s [=global scopes|list of global scopes= 1. If |urlWithMetadata| has field "`reportingMetadata`": 1. Let |reportingMetadata| be |urlWithMetadata|["`reportingMetadata`"]. 1. If the result of running [=validate reporting metadata=] with |reportingMetadata| is false, [=reject=] |resultPromise| with a {{TypeError}} and abort these steps. - 1. Let |fencedFrameConfigStruct| be a [=fenced frame config=] with an invalid "urn uuid". Add correct struct name as well as linking when Fenced Frame API updates their draft spec to include it. + 1. Let |fencedFrameConfigMapping| be |globalScope|'s [=associated Document=]'s [=node navigable=]'s + [=navigable/traversable navigable=]'s [=traversable navigable/fenced frame config mapping=]. + 1. Let |pendingConfig| be a new [=fenced frame config=]. + 1. Let |urn| be the result of running [=fenced frame config mapping/store a pending config=] on |fencedFrameConfigMapping| with |pendingConfig|. + 1. If |urn| is failure, then return a [=promise rejected=] with a {{TypeError}}. 1. Let |environment| be |window|'s [=relevant settings object=]. - 1. If the result of running [=determine whether shared storage is allowed by context=] given |environment| and [=this=]'s [=SharedStorageWorklet/worklet origin=] is false, return a [=promise rejected=] with a {{TypeError}}. - 1. If the result of running [=determine whether shared storage is allowed by enrollment and user preference=] given |environment| and [=this=]'s [=SharedStorageWorklet/worklet origin=] is false: + 1. If the result of running [=determine whether shared storage is allowed by context=] given |environment| and |workletOrigin| is false, return a [=promise rejected=] with a {{TypeError}}. + 1. If the result of running [=determine whether shared storage is allowed by enrollment and user preference=] given |environment| and |workletOrigin| is false: 1. If [=this=]'s [=SharedStorageWorklet/is cross-origin worklet=] is false, return a [=promise rejected=] with a {{TypeError}}. - 1. Else, set |fencedFrameConfigStruct| to a [=fenced frame config=] with a pending mapped "url uuid". Add correct struct name as well as linking when Fenced Frame API updates their draft spec to include it. - - Issue: The [=fenced frame config=] 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. - - 1. If |options|["`resolveToConfig`"] is true, [=resolve=] |resultPromise| with the result of running "obtain a {{FencedFrameConfig}} from a fenced frame config struct" with |fencedFrameConfigStruct|. Add correct struct and algorithms names as well as linking when Fenced Frame API updates their draft spec to include it. - 1. 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. - 1. If |fencedFrameConfigStruct|'s "urn uuid" is invalid (i.e. instead of pending mapped), then return. Add correct struct name and urn:uuid name as well as linking when Fenced Frame API updates their draft spec to include it. + 1. If |options|["`resolveToConfig`"] is true, [=resolve=] |resultPromise| with |pendingConfig|. + 1. Othewise, [=resolve=] |resultPromise| to |urn|. 1. Let |indexPromise| be the result of running [=get the select-url result index=], given [=this=], |name|, |urlList|, and |options|. 1. [=Upon fulfillment=] of |indexPromise| with |resultIndex|, perform the following steps: 1. Let |site| be the result of running [=obtain a site=] with |document|'s [=Document/origin=]. @@ -390,13 +389,17 @@ Moreover, each {{SharedStorageWorklet}}'s [=global scopes|list of global scopes= 1. [=Assert=]: |remainingBudget| is not undefined. 1. Let |pendingBits| be the logarithm base 2 of |urlList|'s [=list/size=]. 1. If |pendingBits| is greather than |remainingBudget|, set |resultIndex| to [=default index=]. - 1. Set |fencedFrameConfigStruct|'s [=pending shared storage budget debit=] to |pendingBits|. - 1. Set |fencedFrameConfigStruct|'s [=/url=] to |urlList|[|resultIndex|]. + 1. Let |finalConfig| be a new [=fenced frame config=]. + 1. Set |finalConfig|'s [=fenced frame config/mapped url=] to |urlList|[|resultIndex|]. + 1. Set |finalConfig|'s a "pending shared storage budget debit" field to |pendingBits|. + 1. [=Finalize a pending config=] on |fencedFrameConfigMapping| with |urn| and |finalConfig|. 1. Let |resultURLWithMetadata| be |urls|[|resultIndex|]. 1. If |resultURLWithMetadata| has field "`reportingMetadata`", run [=register reporting metadata=] with |resultURLWithMetadata|["`reportingMetadata`"]. 1. If |options|["`keepAlive`"] is false, run [=terminate a worklet global scope=] with [=this=]. 1. [=Upon rejection=] of |indexPromise|, perform the following steps: - 1. Set |fencedFrameConfigStruct|'s [=/url=] to |urlList|[[=default index=]]. + 1. Let |finalConfig| be a new [=fenced frame config=]. + 1. Set |finalConfig|'s [=fenced frame config/mapped url=] to |urlList|[[=default index=]]. + 1. [=Finalize a pending config=] on |fencedFrameConfigMapping| with |urn| and |finalConfig|. 1. If |options|["`keepAlive`"] is false, run [=terminate a worklet global scope=] with [=this=]. 1. Return |resultPromise|.
@@ -412,8 +415,9 @@ Moreover, each {{SharedStorageWorklet}}'s [=global scopes|list of global scopes= 1. [=Assert=]: [=this=]'s [=global scopes=]'s [=list/size=] is 1. 1. Let |globalScope| be [=this=]'s [=global scopes=][0]. 1. If the result of running [=SharedStorageWorkletGlobalScope/check whether addModule is finished=] for |globalScope| is false, return a [=promise rejected=] with a {{TypeError}}. - 1. If the result of running [=determine whether shared storage is allowed by context=] given |window| and [=this=]'s [=SharedStorageWorklet/worklet origin=] is false, [=reject=] |promise| with a {{TypeError}}. - 1. If the result of running [=determine whether shared storage is allowed by enrollment and user preference=] given |window| and [=this=]'s [=SharedStorageWorklet/worklet origin=] is false: + 1. Let |workletOrigin| be |globalScope|'s [=global object/realm=]'s HostDefined field's [=environment settings object/origin=]. + 1. If the result of running [=determine whether shared storage is allowed by context=] given |window| and |workletOrigin| is false, [=reject=] |promise| with a {{TypeError}}. + 1. If the result of running [=determine whether shared storage is allowed by enrollment and user preference=] given |window| and |workletOrigin| is false: 1. If [=this=]'s [=SharedStorageWorklet/is cross-origin worklet=] is false, [=reject=] |promise| with a {{TypeError}}. 1. Else, [=resolve=] |promise| with undefined. 1. Return |promise|. @@ -441,7 +445,42 @@ Moreover, each {{SharedStorageWorklet}}'s [=global scopes|list of global scopes= This specification will make some modifications to the [=Worklet=] standard to accommodate the needs of Shared Storage. - In particular, the {{Worklet/addModule()}} method steps for {{Worklet}} will need to include the following step before the step "Let |promise| be a new promise": + ### Monkey Patch for [=set up a worklet environment settings object=] ### {#set-up-a-worklet-environment-settings-object-monkey-patch} + + 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 [=environment settings object/origin=] should be modified as follows: + + 6. Let |settingsObject| be a new [=environment settings object=] whose algorithms are defined as follows: + + ...... + + The [=environment settings object/origin=] + 1. Let |workletGlobalScope| be the [=global object=] of realmExecutionContext's Realm component. + 1. If |workletGlobalScope| is not {{SharedStorageWorkletGlobalScope}}, return |origin|. + 1. Let |pendingAddedModules| be a [=list/clone=] of |worklet|'s [=added modules list=]. + 1. [=Assert=]: |pendingAddedModules|'s [=list/size=] is 1. + 1. Let |moduleURL| be |pendingAddedModules|[0]. + 1. Return |moduleURL|'s [=url/origin=]. + + ...... + + ### Monkey Patch for [=set up a worklet environment settings object=] ### {#create-a-worklet-global-scope-monkey-patch} + + The [=create a worklet global scope=] algorithm will need to be modified to pass in the |worklet| parameter: + + 5. Let insideSettings be the result of [=setting up a worklet environment settings object=] given realmExecutionContext, outsideSettings, and |worklet|. + + ### Monkey Patch for [=fetch a worklet script graph=] ### {#fetch-a-worklet-script-graph-monkey-patch} + + 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|, ...": + + 5. Set |request|'s [=request/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 HostDefined field's [=environment settings object/origin=] (as described in [[#set-up-a-worklet-environment-settings-object-monkey-patch]]) 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 HostDefined field's [=environment settings object/origin=] should be updated to return the final request's [=request/URL=]'s [=url/origin=] after receiving the final request's response, and the user preference checkings shall only be done after that point. + + + ### Monkey Patch for {{Worklet/addModule()}} ### {#add-module-monkey-patch} + + The {{Worklet/addModule()}} method steps for {{Worklet}} will need to include the following step before the step "Let |promise| be a new promise": 4. If |this| is of type {{SharedStorageWorklet}}: 1. Let |addModuleAllowedResult| be the result of running [=check if addModule is allowed and update state=] given |this| and moduleURLRecord. @@ -462,7 +501,7 @@ Moreover, each {{SharedStorageWorklet}}'s [=global scopes|list of global scopes= Specify that redirects are disallowed when fetching the module script for {{SharedStorageWorklet}}. - And the penultimate step (i.e. the final indented step), currently "If |pendingTasks| is 0, then [=resolve=] |promise|.", should be updated to: + The penultimate step (i.e. the final indented step), currently "If |pendingTasks| is 0, then [=resolve=] |promise|.", should be updated to: 2. If |pendingTasks| is 0, perform the following steps: 1. If |workletGlobalScope| has an associated boolean [=addModule success=], set |workletGlobalScope|'s [=addModule success=] to true. @@ -478,6 +517,10 @@ Moreover, each {{SharedStorageWorklet}}'s [=global scopes|list of global scopes= Add "sharedstorageworklet" to the possible strings that a request [=request/destination=] can have. + + callback RunFunctionForSharedStorageSelectURLOperation = Promise<unsigned long>(sequence<DOMString> urls, optional any data); + + [Exposed=SharedStorageWorklet, Global=SharedStorageWorklet] interface SharedStorageWorkletGlobalScope : WorkletGlobalScope { @@ -490,8 +533,6 @@ Moreover, each {{SharedStorageWorklet}}'s [=global scopes|list of global scopes= Each {{SharedStorageWorkletGlobalScope}} has an associated [=environment settings object=] <dfn for=SharedStorageWorkletGlobalScope>outside settings</dfn>, which is the associated {{SharedStorageWorklet}}'s [=relevant settings object=]. - Each {{SharedStorageWorkletGlobalScope}} has an associated [=/origin=] <dfn for=SharedStorageWorkletGlobalScope>worklet origin</dfn>, which is initialized to the associated {{SharedStorageWorklet}}'s [=SharedStorageWorklet/worklet origin=]. - Each {{SharedStorageWorkletGlobalScope}} has an associated [=/boolean=] <dfn for=SharedStorageWorkletGlobalScope>addModule success</dfn>, which is initialized to false. Each {{SharedStorageWorkletGlobalScope}} also has an associated <dfn for=SharedStorageWorkletGlobalScope>operation map</dfn>, which is a [=map=], initially empty, of [=strings=] (denoting operation names) to [=functions=]. @@ -508,7 +549,7 @@ Moreover, each {{SharedStorageWorklet}}'s [=global scopes|list of global scopes= 1. If |operationCtor| is missing, throw a {{TypeError}}. 1. Let |operationClassInstance| be the result of [=constructing=] |operationCtor|, with no arguments. 1. Let |runFunction| be [=Get=](|operationClassInstance|, "`run`"). Rethrow any exceptions. - 1. Let |runIDLFunction| be the result of [=converting=] |runFunction| to a Web IDL Function instance. + 1. Let |runIDLFunction| be the result of [=converting=] |runFunction| to a Web IDL {{RunFunctionForSharedStorageSelectURLOperation}} instance. 1. [=map/Set=] the value of |operationMap|[|name|] to |runIDLFunction|. </div> @@ -576,7 +617,7 @@ Moreover, each {{SharedStorageWorklet}}'s [=global scopes|list of global scopes= ### Navigation Entropy Budget ### {#nav-budget} - If a user [=user activation|activates=] a [=fenced frame=] whose {{FencedFrameConfig}} was generated by {{SharedStorageWorklet/selectURL()}} and thereby initiates a [=top-level traversable=] [=navigate|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 {{SharedStorageWorklet/selectURL()}}. To mitigate this, a [=user agent=] will set a per-[=calling site=] [=navigation entropy allowance=]. + If a user [=user activation|activates=] a [=fenced frame=] whose [=fenced frame config instance=] was generated by {{SharedStorageWorklet/selectURL()}} and thereby initiates a [=top-level traversable=] [=navigate|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 {{SharedStorageWorklet/selectURL()}}. To mitigate this, a [=user agent=] will set a per-[=calling site=] [=navigation entropy allowance=]. A <dfn>calling site</dfn> for {{SharedStorageWorklet/selectURL()}} is a [=site=]. @@ -648,11 +689,13 @@ Moreover, each {{SharedStorageWorklet}}'s [=global scopes|list of global scopes= 1. Otherwise, return false. </div> - A [=bit debit=] will need to be [=charge shared storage navigation budget|charged=] to the [=shared storage navigation budget table=] for each [=top-frame=] [=navigate|navigation=] initiated by a [=fenced frame=] whose {{FencedFrameConfig}} was generated via {{SharedStorageWorklet/selectURL()}}, as this can leak cross-site data. Since the [=bit debit/bits=] to charge is calculated during the call to {{SharedStorageWorklet/selectURL()}} but only actually recorded in the [=shared storage navigation budget table=] if and when the resulting fenced frame initiates a [=top-level traversable=] [=beginning navigation|navigation=], the [=bit debit/bits=] must be stored as a <dfn>pending shared storage budget debit</dfn> in the corresponding {{FencedFrameConfig}} until this time. + A [=bit debit=] will need to be [=charge shared storage navigation budget|charged=] to the [=shared storage navigation budget table=] for each [=top-frame=] [=navigate|navigation=] initiated by a [=fenced frame=] whose [=fenced frame config instance=] was generated via {{SharedStorageWorklet/selectURL()}}, as this can leak cross-site data. Since the [=bit debit/bits=] to charge is calculated during the call to {{SharedStorageWorklet/selectURL()}} but only actually recorded in the [=shared storage navigation budget table=] if and when the resulting fenced frame initiates a [=top-level traversable=] [=beginning navigation|navigation=], the [=bit debit/bits=] must be stored as a <dfn>pending shared storage budget debit</dfn> in the corresponding [=fenced frame config instance=] until this time. + + <span class=todo>Move the definition of [=pending shared storage budget debit=] to [=fenced frame config instance=] in the draft [=Fenced Frame=] specification.</span> Between [=beginning navigation=] and [=ending navigation=], a [=user agent=] will perform the [=charge shared storage navigation budget=] algorithm. - Issue: The [=fenced frame config=] and its boolean <dfn>has navigated</dfn> 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. <span class=todo>Fix the names when they are added.</span> + Issue: The boolean <dfn>shared storage navigation budget charged</dfn> 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. <span class=todo>Fix the names when they are added.</span> <div algorithm> To <dfn>charge shared storage navigation budget</dfn> during a [=beginning navigation|navigation=] with [=/navigable=] |navigable| and {{Document}} |sourceDocument|, run the following steps: @@ -661,16 +704,17 @@ Moreover, each {{SharedStorageWorklet}}'s [=global scopes|list of global scopes= 1. Let |currentNavigable| be |sourceDocument|'s [=node navigable=]. 1. While |currentNavigable| is not null: 1. Let |site| be the result of running [=obtain a site=] with |currentNavigable|'s [=active document=]'s [=document/origin=]. - 1. If |currentNavigable| has a [=fenced frame config=] and |site| is not an [=opaque origin=], perform the following steps: - 1. Let |pendingBits| be |currentNavigable|'s [=fenced frame config=]'s [=pending shared storage budget debit=]. - 1. If |pendingBits| is greater than 0 and if [=fenced frame config=]'s [=has navigated=] is false, run the following steps: + 1. Let |instance| be |currentNavigable|'s [=Node/node document=]'s [=Document/browsing context=]'s [=browsing context/fenced frame config instance=]. + 1. If |instance| is not null and |site| is not an [=opaque origin=], perform the following steps: + 1. Let |pendingBits| be |instance|'s [=pending shared storage budget debit=]. + 1. If |pendingBits| is greater than 0 and if |instance|'s [=shared storage navigation budget charged=] is false, run the following steps: 1. Let |ledger| be [=user agent=]'s [=shared storage navigation budget table=][|site|]. 1. Let |bitDebit| be a new [=bit debit=]. 1. Set |bitDebit|'s [=bit debit/bits=] to |pendingBits|. 1. Let |currentTime| be the [=/current wall time=]. 1. Set |bitDebit|'s [=bit debit/timestamp=] to |currentTime|. 1. [=list/Append=] |bitDebit| to |ledger|. - 1. Set |currentNavigable|'s [=fenced frame config=]'s [=has navigated=] to true. + 1. Set |instance|'s [=shared storage navigation budget charged=] to true. 1. Set |currentNavigable| to |currentNavigable|'s [=navigable/parent=]. </div> @@ -690,15 +734,16 @@ Moreover, each {{SharedStorageWorklet}}'s [=global scopes|list of global scopes= 1. Let |debitSum| be 0. 1. Let |currentNavigable| be |sourceDocument|'s [=node navigable=]. 1. While |currentNavigable| is not null: - 1. If |currentNavigable| has a [=fenced frame config=]: - 1. Let |pendingBits| be |currentNavigable|'s [=fenced frame config=]'s [=pending shared storage budget debit=]. - 1. If |pendingBits| is greater than 0 and if [=fenced frame config=]'s [=has reported=] is false, increment |debitSum| by |pendingBits| + 1. Let |instance| be |currentNavigable|'s [=Node/node document=]'s [=Document/browsing context=]'s [=browsing context/fenced frame config instance=]. + 1. If |instance| is not null: + 1. Let |pendingBits| be |instance|'s [=pending shared storage budget debit=]. + 1. If |pendingBits| is greater than 0 and if |instance|'s [=shared storage reporting budget charged=] is false, increment |debitSum| by |pendingBits| 1. Set |currentNavigable| to |currentNavigable|'s [=navigable/parent=]. 1. Return |debitSum|. </div> - Issue: The [=fenced frame config=] and its boolean <dfn>has reported</dfn> 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. <span class=todo>Fix the names when they are added.</span> + Issue: The boolean <dfn>shared storage reporting budget charged</dfn> 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. <span class=todo>Fix the names when they are added.</span> <div algorithm> To <dfn>charge shared storage reporting budget</dfn> given a {{Document}} |sourceDocument|, run the following steps: @@ -708,7 +753,8 @@ Moreover, each {{SharedStorageWorklet}}'s [=global scopes|list of global scopes= 1. Let |topNode| be the result of running [=get the top-level traversable=] for |currentNavigable|. 1. If |topNode|'s [=shared storage reporting budget=] is less than |toCharge|, return false. 1. While |currentNavigable| is not null: - 1. If |currentNavigable| has a [=fenced frame config=] and if |currentNavigable|'s [=fenced frame config=]'s [=pending shared storage budget debit=] is greater than 0, set |currentNavigable|'s [=fenced frame config=]'s [=has reported=] to true. + 1. Let |instance| be |currentNavigable|'s [=Node/node document=]'s [=Document/browsing context=]'s [=browsing context/fenced frame config instance=]. + 1. 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. 1. Set |currentNavigable| to |currentNavigable|'s [=navigable/parent=]. 1. Decrement |topNode|'s [=shared storage reporting budget=] by |toCharge|. 1. Return true. @@ -1133,10 +1179,10 @@ On the other hand, methods for getting data from the [=shared storage database=] 1. If |context| is null, return a [=promise rejected=] with a {{TypeError}}. 1. If |context|'s [=active window=]'s [=associated document=] is not [=fully active=], return a [=promise rejected=] with a {{TypeError}}. 1. Let |environment| be |context|'s [=active window=]'s [=relevant settings object=]. - 1. Let |databaseMap| be the result of running [=obtain a shared storage bottle map=] given |environment| and [=current global object=]'s [=SharedStorageWorkletGlobalScope/worklet origin=]. + 1. Let |realm| be the [=current realm=]. + 1. Let |databaseMap| be the result of running [=obtain a shared storage bottle map=] given |environment| and |realm|'s <span class=todo>HostDefined</span> field's [=environment settings object/origin=]. 1. If |databaseMap| is failure, then return a [=promise rejected=] with a {{TypeError}}. 1. Let |queue| be |context|'s associated [=shared storage database|database=]'s [=shared storage database/shared storage database queue=]. - 1. Let |realm| be the [=current realm=]. 1. [=Enqueue the following steps=] on |queue|: 1. If |options|["`ignoreIfPresent`"] is true: 1. Let |currentValue| be the result of running [=shared storage database/retrieve an entry from the database=] with |queue|, |databaseMap|, |environment|, and |key|. @@ -1162,10 +1208,10 @@ On the other hand, methods for getting data from the [=shared storage database=] 1. If |context| is null, return a [=promise rejected=] with a {{TypeError}}. 1. If |context|'s [=active window=]'s [=associated document=] is not [=fully active=], return a [=promise rejected=] with a {{TypeError}}. 1. Let |environment| be |context|'s [=active window=]'s [=relevant settings object=]. - 1. Let |databaseMap| be the result of running [=obtain a shared storage bottle map=] given |environment| and [=current global object=]'s [=SharedStorageWorkletGlobalScope/worklet origin=]. + 1. Let |realm| be the [=current realm=]. + 1. Let |databaseMap| be the result of running [=obtain a shared storage bottle map=] given |environment| and |realm|'s <span class=todo>HostDefined</span> field's [=environment settings object/origin=]. 1. If |databaseMap| is failure, then return a [=promise rejected=] with a {{TypeError}}. 1. Let |queue| be |context|'s associated [=shared storage database|database=]'s [=shared storage database/shared storage database queue=]. - 1. Let |realm| be the [=current realm=]. 1. [=Enqueue the following steps=] on |queue|: 1. Let |currentValue| be the result of running [=shared storage database/retrieve an entry from the database=] with |queue|, |databaseMap|, |environment|, and |key|. 1. If |currentValue| is failure: @@ -1191,10 +1237,10 @@ On the other hand, methods for getting data from the [=shared storage database=] 1. If |context| is null, return a [=promise rejected=] with a {{TypeError}}. 1. If |context|'s [=active window=]'s [=associated document=] is not [=fully active=], return a [=promise rejected=] with a {{TypeError}}. 1. Let |environment| be |context|'s [=active window=]'s [=relevant settings object=]. - 1. Let |databaseMap| be the result of running [=obtain a shared storage bottle map=] given |environment| and [=current global object=]'s [=SharedStorageWorkletGlobalScope/worklet origin=]. + 1. Let |realm| be the [=current realm=]. + 1. Let |databaseMap| be the result of running [=obtain a shared storage bottle map=] given |environment| and |realm|'s <span class=todo>HostDefined</span> field's [=environment settings object/origin=]. 1. If |databaseMap| is failure, then return a [=promise rejected=] with a {{TypeError}}. 1. Let |queue| be |context|'s associated [=shared storage database|database=]'s [=shared storage database/shared storage database queue=]. - 1. Let |realm| be the [=current realm=]. 1. [=Enqueue the following steps=] on |queue|: 1. If the result of running [=shared storage database/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}}. 1. Otherwise, [=queue a global task=] on the [=DOM manipulation task source=], given |realm|'s [=global object=], to [=resolve=] |promise| with undefined. @@ -1210,10 +1256,10 @@ On the other hand, methods for getting data from the [=shared storage database=] 1. If |context| is null, return a [=promise rejected=] with a {{TypeError}}. 1. If |context|'s [=active window=]'s [=associated document=] is not [=fully active=], return a [=promise rejected=] with a {{TypeError}}. 1. Let |environment| be |context|'s [=active window=]'s [=relevant settings object=]. - 1. Let |databaseMap| be the result of running [=obtain a shared storage bottle map=] given |environment| and [=current global object=]'s [=SharedStorageWorkletGlobalScope/worklet origin=]. + 1. Let |realm| be the [=current realm=]. + 1. Let |databaseMap| be the result of running [=obtain a shared storage bottle map=] given |environment| and |realm|'s <span class=todo>HostDefined</span> field's [=environment settings object/origin=]. 1. If |databaseMap| is failure, then return a [=promise rejected=] with a {{TypeError}}. 1. Let |queue| be |context|'s associated [=shared storage database|database=]'s [=shared storage database/shared storage database queue=]. - 1. Let |realm| be the [=current realm=]. 1. [=Enqueue the following steps=] on |queue|: 1. If the result of running [=shared storage database/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}}. 1. Otherwise, [=queue a global task=] on the [=DOM manipulation task source=], given |realm|'s [=global object=], to [=resolve=] |promise| with undefined. @@ -1232,10 +1278,10 @@ On the other hand, methods for getting data from the [=shared storage database=] 1. If |context| is null, return a [=promise rejected=] with a {{TypeError}}. 1. If |context|'s [=active window=]'s [=associated document=] is not [=fully active=], return a [=promise rejected=] with a {{TypeError}}. 1. Let |environment| be |context|'s [=active window=]'s [=relevant settings object=]. - 1. Let |databaseMap| be the result of running [=obtain a shared storage bottle map=] given |environment| and [=current global object=]'s [=SharedStorageWorkletGlobalScope/worklet origin=]. + 1. Let |realm| be the [=current realm=]. + 1. Let |databaseMap| be the result of running [=obtain a shared storage bottle map=] given |environment| and |realm|'s <span class=todo>HostDefined</span> field's [=environment settings object/origin=]. 1. If |databaseMap| is failure, then return a [=promise rejected=] with a {{TypeError}}. 1. Let |queue| be |context|'s associated [=shared storage database|database=]'s [=shared storage database/shared storage database queue=]. - 1. Let |realm| be the [=current realm=]. 1. [=Enqueue the following steps=] on |queue|: 1. Let |value| be the result of running [=shared storage database/retrieve an entry from the database=] with |queue|, |databaseMap|, |environment|, and |key|. 1. 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}}. @@ -1253,10 +1299,10 @@ On the other hand, methods for getting data from the [=shared storage database=] 1. If |context| is null, return a [=promise rejected=] with a {{TypeError}}. 1. If |context|'s [=active window=]'s [=associated document=] is not [=fully active=], return a [=promise rejected=] with a {{TypeError}}. 1. Let |environment| be |context|'s [=active window=]'s [=relevant settings object=]. - 1. Let |databaseMap| be the result of running [=obtain a shared storage bottle map=] given |environment| and [=current global object=]'s [=SharedStorageWorkletGlobalScope/worklet origin=]. + 1. Let |realm| be the [=current realm=]. + 1. Let |databaseMap| be the result of running [=obtain a shared storage bottle map=] given |environment| and |realm|'s <span class=todo>HostDefined</span> field's [=environment settings object/origin=]. 1. If |databaseMap| is failure, then return a [=promise rejected=] with a {{TypeError}}. 1. Let |queue| be |context|'s associated [=shared storage database|database=]'s [=shared storage database/shared storage database queue=]. - 1. Let |realm| be the [=current realm=]. 1. [=Enqueue the following steps=] on |queue|: 1. Let |numEntries| be the result of running [=shared storage database/count entries in the database=] with |queue| and |environment|. 1. 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}}. @@ -1273,12 +1319,12 @@ On the other hand, methods for getting data from the [=shared storage database=] 1. If |context| is null, return a [=promise rejected=] with a {{TypeError}}. 1. If |context|'s [=active window=]'s [=associated document=] is not [=fully active=], return a [=promise rejected=] with a {{TypeError}}. 1. Let |environment| be |context|'s [=active window=]'s [=relevant settings object=]. - 1. If the result of running [=determine whether shared storage is allowed by context=] given |environment| and [=current global object=]'s [=SharedStorageWorkletGlobalScope/worklet origin=] is false, return a [=promise rejected=] with a {{TypeError}}. - 1. If the result of running [=determine whether shared storage is allowed by enrollment and user preference=] given |environment| and [=current global object=]'s [=SharedStorageWorkletGlobalScope/worklet origin=] is false, return a [=promise rejected=] with a {{TypeError}}. - 1. Let |site| be the result of running [=obtain a site=] with [=current global object=]'s [=SharedStorageWorkletGlobalScope/worklet origin=]. + 1. Let |realm| be the [=current realm=]. + 1. If the result of running [=determine whether shared storage is allowed by context=] given |environment| and |realm|'s <span class=todo>HostDefined</span> field's [=environment settings object/origin=] is false, return a [=promise rejected=] with a {{TypeError}}. + 1. If the result of running [=determine whether shared storage is allowed by enrollment and user preference=] given |environment| and |realm|'s <span class=todo>HostDefined</span> field's [=environment settings object/origin=] is false, return a [=promise rejected=] with a {{TypeError}}. + 1. Let |site| be the result of running [=obtain a site=] with |realm|'s <span class=todo>HostDefined</span> field's [=environment settings object/origin=]. 1. [=Assert=]: |site| is not an [=opaque origin=]. 1. Let |queue| be |context|'s associated [=shared storage database|database=]'s [=shared storage database/shared storage database queue=]. - 1. Let |realm| be the [=current realm=]. 1. [=Enqueue the following steps=] on |queue|: 1. Let |remainingBudget| be the result of running [=determine remaining navigation budget=] with |site|. 1. [=Assert=]: |remainingBudget| is not undefined. @@ -1303,10 +1349,10 @@ On the other hand, methods for getting data from the [=shared storage database=] 1. If |context| is null, return a [=promise rejected=] with a {{TypeError}}. 1. If |context|'s [=active window=]'s [=associated document=] is not [=fully active=], return a [=promise rejected=] with a {{TypeError}}. 1. Let |environment| be |context|'s [=active window=]'s [=relevant settings object=]. - 1. Let |databaseMap| be the result of running [=obtain a shared storage bottle map=] given |environment| and [=current global object=]'s [=SharedStorageWorkletGlobalScope/worklet origin=]. + 1. Let |realm| be the [=current realm=]. + 1. Let |databaseMap| be the result of running [=obtain a shared storage bottle map=] given |environment| and |realm|'s <span class=todo>HostDefined</span> field's [=environment settings object/origin=]. 1. If |databaseMap| is failure, then return a [=promise rejected=] with a {{TypeError}}. 1. Let |queue| be |context|'s associated [=shared storage database|database=]'s [=shared storage database/shared storage database queue=]. - 1. Let |realm| be the [=current realm=]. 1. [=Enqueue the following steps=] on |queue|: 1. Let |entries| be the result of running [=shared storage database/retrieve all entries from the database=] with |queue| and |environment|. 1. 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}}. From 6466052c00512654dc4967a95627d00d437772cf Mon Sep 17 00:00:00 2001 From: Yao Xiao <xyaof3@126.com> Date: Fri, 22 Mar 2024 20:07:22 -0400 Subject: [PATCH 09/15] address feedback --- spec.bs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/spec.bs b/spec.bs index 332b4f8..298d9db 100644 --- a/spec.bs +++ b/spec.bs @@ -691,11 +691,11 @@ Moreover, each {{SharedStorageWorklet}}'s [=global scopes|list of global scopes= A [=bit debit=] will need to be [=charge shared storage navigation budget|charged=] to the [=shared storage navigation budget table=] for each [=top-frame=] [=navigate|navigation=] initiated by a [=fenced frame=] whose [=fenced frame config instance=] was generated via {{SharedStorageWorklet/selectURL()}}, as this can leak cross-site data. Since the [=bit debit/bits=] to charge is calculated during the call to {{SharedStorageWorklet/selectURL()}} but only actually recorded in the [=shared storage navigation budget table=] if and when the resulting fenced frame initiates a [=top-level traversable=] [=beginning navigation|navigation=], the [=bit debit/bits=] must be stored as a <dfn>pending shared storage budget debit</dfn> in the corresponding [=fenced frame config instance=] until this time. - <span class=todo>Move the definition of [=pending shared storage budget debit=] to [=fenced frame config instance=] in the draft [=Fenced Frame=] specification.</span> + <span class=todo>Move the definition of [=pending shared storage budget debit=] to [=fenced frame config instance=] in the draft [[Fenced-Frame]] specification.</span> Between [=beginning navigation=] and [=ending navigation=], a [=user agent=] will perform the [=charge shared storage navigation budget=] algorithm. - Issue: The boolean <dfn>shared storage navigation budget charged</dfn> 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. <span class=todo>Fix the names when they are added.</span> + Issue: The boolean <dfn>shared storage navigation budget charged</dfn> 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. <span class=todo>Fix the names when they are added.</span> <div algorithm> To <dfn>charge shared storage navigation budget</dfn> during a [=beginning navigation|navigation=] with [=/navigable=] |navigable| and {{Document}} |sourceDocument|, run the following steps: @@ -743,7 +743,7 @@ Moreover, each {{SharedStorageWorklet}}'s [=global scopes|list of global scopes= </div> - Issue: The boolean <dfn>shared storage reporting budget charged</dfn> 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. <span class=todo>Fix the names when they are added.</span> + Issue: The boolean <dfn>shared storage reporting budget charged</dfn> 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. <span class=todo>Fix the names when they are added.</span> <div algorithm> To <dfn>charge shared storage reporting budget</dfn> given a {{Document}} |sourceDocument|, run the following steps: From 4ddc5e78747efc5cf3bbbcc679abe01b3242e6d1 Mon Sep 17 00:00:00 2001 From: Yao Xiao <xyaof3@126.com> Date: Wed, 27 Mar 2024 16:32:25 -0400 Subject: [PATCH 10/15] Add "Shared-Storage-Worklet-Allowed" response header check --- spec.bs | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/spec.bs b/spec.bs index 298d9db..98e4e4b 100644 --- a/spec.bs +++ b/spec.bs @@ -463,20 +463,34 @@ Moreover, each {{SharedStorageWorklet}}'s [=global scopes|list of global scopes= ...... - ### Monkey Patch for [=set up a worklet environment settings object=] ### {#create-a-worklet-global-scope-monkey-patch} + ### Monkey Patch for [=create a worklet global scope=] ### {#create-a-worklet-global-scope-monkey-patch} The [=create a worklet global scope=] algorithm will need to be modified to pass in the |worklet| parameter: 5. Let <var ignore=''>insideSettings</var> be the result of [=setting up a worklet environment settings object=] given <var ignore=''>realmExecutionContext</var>, <var ignore=''>outsideSettings</var>, and |worklet|. + ### Monkey Patch for [=/request=] ### {#request-monkey-patch} + A [=/request=] has an associated <dfn for=request>is cross origin shared storage worklet request boolean</dfn>. Unless stated otherwise it is false. + ### Monkey Patch for [=fetch a worklet script graph=] ### {#fetch-a-worklet-script-graph-monkey-patch} The algorithm [=fetch a worklet script graph=] calls into the <a href="https://html.spec.whatwg.org/multipage/webappapis.html#fetch-a-worklet/module-worker-script-graph">fetch a worklet/module worker script graph</a> 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|, ...": - 5. Set |request|'s [=request/redirect mode=] to "<code>error</code>". + 5. If |fetchClient|'s [=environment settings object/realm execution context=]'s Realm component's [=global object=] is {{SharedStorageWorkletGlobalScope}}: + 1. Set |request|'s [=request/redirect mode=] to "<code>error</code>". + + 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 <span class=todo>HostDefined</span> field's [=environment settings object/origin=] (as described in [[#set-up-a-worklet-environment-settings-object-monkey-patch]]) 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 <span class=todo>HostDefined</span> field's [=environment settings object/origin=] should be updated to return the final request's [=request/URL=]'s [=url/origin=] after receiving the final request's response, and the user preference checkings shall only be done after that point. + + 1. If <var ignore=''>moduleResponsesMap</var>'s [=associated realm=]'s <span class=todo>HostDefined</span> field's [=environment settings object/origin=] and |fetchClient|'s [=environment settings object/origin=] are not [=same origin=]: + 1. Set |request|'s [=request/is cross origin shared storage worklet request boolean=] to true. - 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 <span class=todo>HostDefined</span> field's [=environment settings object/origin=] (as described in [[#set-up-a-worklet-environment-settings-object-monkey-patch]]) 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 <span class=todo>HostDefined</span> field's [=environment settings object/origin=] should be updated to return the final request's [=request/URL=]'s [=url/origin=] after receiving the final request's response, and the user preference checkings shall only be done after that point. + ### Monkey Patch for [=HTTP fetch=] ### {#http-fetch-monkey-patch} + The following step will be added to the [=HTTP fetch=] steps, before checking the redirect status (i.e. "If |actualResponse|'s status is a redirect status, ..."): + 1. If |request|'s [=request/is cross origin shared storage worklet request boolean=] is true: + 1. Let |list| be |actualResponse|'s [=response/header list=]. + 1. Let |sharedStorageWorkletAllowed| be the result of running [=get a structured field value=] algorithm given "Shared-Storage-Worklet-Allowed", "item", and |list| as input. + 1. If |sharedStorageWorkletAllowed| is false, then return a [=network error=]. ### Monkey Patch for {{Worklet/addModule()}} ### {#add-module-monkey-patch} From 81836402aa4f2bc1abe044ce213927107c20eb8b Mon Sep 17 00:00:00 2001 From: Yao Xiao <xyaof3@126.com> Date: Wed, 27 Mar 2024 16:41:38 -0400 Subject: [PATCH 11/15] Move and improve a "Note" --- spec.bs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec.bs b/spec.bs index 98e4e4b..b09e41a 100644 --- a/spec.bs +++ b/spec.bs @@ -293,8 +293,6 @@ When {{Worklet/addModule()}} is called for a worklet, it will run [=check if add Moreover, each {{SharedStorageWorklet}}'s [=global scopes|list of global scopes=], initially empty, can contain at most one instance of its [=worklet global scope type=], the {{SharedStorageWorkletGlobalScope}}. - Note: The website that serves the module script should be aware of the implication of CORS: when the module script's [=/URL=]'s [=url/origin=] and the worklet's creator {{Window}} origin are not [=same origin=], by sending permissive CORS headers on the module script response, the server will be granting the worklet's creation and subsequent operations on the worklet, under that module script's [=url/origin=]. For example, the worklet's creator {{Window}} could poison and use up the worklet origin's [=remaining navigation budget=] by calling {{SharedStorageWorklet/selectURL()}} or {{SharedStorageWorklet/run()}}, where the worklet origin is the global scope's [=global object/realm=]'s <span class=todo>HostDefined</span> field's [=environment settings object/origin=]. - ## Run Operation Methods on {{SharedStorageWorklet}} ## {#run-op-shared-storage-worklet} <div algorithm> @@ -492,6 +490,8 @@ Moreover, each {{SharedStorageWorklet}}'s [=global scopes|list of global scopes= 1. Let |sharedStorageWorkletAllowed| be the result of running [=get a structured field value=] algorithm given "Shared-Storage-Worklet-Allowed", "item", and |list| as input. 1. If |sharedStorageWorkletAllowed| is false, then return a [=network error=]. + Note: The website that serves the module script must carefully consider the security risks: when the module script's [=/URL=]'s [=url/origin=] and the worklet's creator {{Window}} origin are not [=same origin=], by sending permissive CORS headers and the "Shared-Storage-Worklet-Allowed" header on the module script response, the server will be granting the worklet's creation and subsequent operations on the worklet, under that module script's [=url/origin=]. For example, the worklet's creator {{Window}} could poison and use up the worklet origin's [=remaining navigation budget=] by calling {{SharedStorageWorklet/selectURL()}} or {{SharedStorageWorklet/run()}}, where the worklet origin is the global scope's [=global object/realm=]'s <span class=todo>HostDefined</span> field's [=environment settings object/origin=]. + ### Monkey Patch for {{Worklet/addModule()}} ### {#add-module-monkey-patch} The {{Worklet/addModule()}} method steps for {{Worklet}} will need to include the following step before the step "Let |promise| be a new promise": From 931713d34cebc8c1b0e1f3ec51713be590e797d2 Mon Sep 17 00:00:00 2001 From: Yao Xiao <xyaof3@126.com> Date: Thu, 4 Apr 2024 11:42:38 -0400 Subject: [PATCH 12/15] Rename the header to "Shared-Storage-Cross-Origin-Worklet-Allowed" --- spec.bs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec.bs b/spec.bs index b09e41a..7b52c72 100644 --- a/spec.bs +++ b/spec.bs @@ -487,10 +487,10 @@ Moreover, each {{SharedStorageWorklet}}'s [=global scopes|list of global scopes= 1. If |request|'s [=request/is cross origin shared storage worklet request boolean=] is true: 1. Let |list| be |actualResponse|'s [=response/header list=]. - 1. Let |sharedStorageWorkletAllowed| be the result of running [=get a structured field value=] algorithm given "Shared-Storage-Worklet-Allowed", "item", and |list| as input. + 1. Let |sharedStorageWorkletAllowed| be the result of running [=get a structured field value=] algorithm given "Shared-Storage-Cross-Origin-Worklet-Allowed", "item", and |list| as input. 1. If |sharedStorageWorkletAllowed| is false, then return a [=network error=]. - Note: The website that serves the module script must carefully consider the security risks: when the module script's [=/URL=]'s [=url/origin=] and the worklet's creator {{Window}} origin are not [=same origin=], by sending permissive CORS headers and the "Shared-Storage-Worklet-Allowed" header on the module script response, the server will be granting the worklet's creation and subsequent operations on the worklet, under that module script's [=url/origin=]. For example, the worklet's creator {{Window}} could poison and use up the worklet origin's [=remaining navigation budget=] by calling {{SharedStorageWorklet/selectURL()}} or {{SharedStorageWorklet/run()}}, where the worklet origin is the global scope's [=global object/realm=]'s <span class=todo>HostDefined</span> field's [=environment settings object/origin=]. + Note: The website that serves the module script must carefully consider the security risks: when the module script's [=/URL=]'s [=url/origin=] and the worklet's creator {{Window}} origin are not [=same origin=], by sending permissive CORS headers and 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, under that module script's [=url/origin=]. For example, the worklet's creator {{Window}} could poison and use up the worklet origin's [=remaining navigation budget=] by calling {{SharedStorageWorklet/selectURL()}} or {{SharedStorageWorklet/run()}}, where the worklet origin is the global scope's [=global object/realm=]'s <span class=todo>HostDefined</span> field's [=environment settings object/origin=]. ### Monkey Patch for {{Worklet/addModule()}} ### {#add-module-monkey-patch} From 9507e051575171051b33898ad4a376f576786da4 Mon Sep 17 00:00:00 2001 From: Yao Xiao <xyaof3@126.com> Date: Thu, 4 Apr 2024 12:25:49 -0400 Subject: [PATCH 13/15] Update the request's boolean field name --- spec.bs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/spec.bs b/spec.bs index 7b52c72..54895c7 100644 --- a/spec.bs +++ b/spec.bs @@ -468,7 +468,7 @@ Moreover, each {{SharedStorageWorklet}}'s [=global scopes|list of global scopes= 5. Let <var ignore=''>insideSettings</var> be the result of [=setting up a worklet environment settings object=] given <var ignore=''>realmExecutionContext</var>, <var ignore=''>outsideSettings</var>, and |worklet|. ### Monkey Patch for [=/request=] ### {#request-monkey-patch} - A [=/request=] has an associated <dfn for=request>is cross origin shared storage worklet request boolean</dfn>. Unless stated otherwise it is false. + A [=/request=] has an associated <dfn for=request>is shared storage cross origin worklet request boolean</dfn>. Unless stated otherwise it is false. ### Monkey Patch for [=fetch a worklet script graph=] ### {#fetch-a-worklet-script-graph-monkey-patch} @@ -480,12 +480,12 @@ Moreover, each {{SharedStorageWorklet}}'s [=global scopes|list of global scopes= 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 <span class=todo>HostDefined</span> field's [=environment settings object/origin=] (as described in [[#set-up-a-worklet-environment-settings-object-monkey-patch]]) 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 <span class=todo>HostDefined</span> field's [=environment settings object/origin=] should be updated to return the final request's [=request/URL=]'s [=url/origin=] after receiving the final request's response, and the user preference checkings shall only be done after that point. 1. If <var ignore=''>moduleResponsesMap</var>'s [=associated realm=]'s <span class=todo>HostDefined</span> field's [=environment settings object/origin=] and |fetchClient|'s [=environment settings object/origin=] are not [=same origin=]: - 1. Set |request|'s [=request/is cross origin shared storage worklet request boolean=] to true. + 1. Set |request|'s [=request/is shared storage cross origin worklet request boolean=] to true. ### Monkey Patch for [=HTTP fetch=] ### {#http-fetch-monkey-patch} The following step will be added to the [=HTTP fetch=] steps, before checking the redirect status (i.e. "If |actualResponse|'s status is a redirect status, ..."): - 1. If |request|'s [=request/is cross origin shared storage worklet request boolean=] is true: + 1. If |request|'s [=request/is shared storage cross origin worklet request boolean=] is true: 1. Let |list| be |actualResponse|'s [=response/header list=]. 1. Let |sharedStorageWorkletAllowed| be the result of running [=get a structured field value=] algorithm given "Shared-Storage-Cross-Origin-Worklet-Allowed", "item", and |list| as input. 1. If |sharedStorageWorkletAllowed| is false, then return a [=network error=]. From cdb209d69682c9437d697ccf45029445004bf754 Mon Sep 17 00:00:00 2001 From: Yao Xiao <xyaof3@126.com> Date: Wed, 10 Apr 2024 12:57:14 -0400 Subject: [PATCH 14/15] Address review comments - Addressed review comments - Took out the header change, to make this patch shorter --- spec.bs | 52 ++++++++++++++++++---------------------------------- 1 file changed, 18 insertions(+), 34 deletions(-) diff --git a/spec.bs b/spec.bs index 54895c7..74e3fd2 100644 --- a/spec.bs +++ b/spec.bs @@ -275,7 +275,7 @@ When {{Worklet/addModule()}} is called for a worklet, it will run [=check if add - For each method under [[#window-setter]], |environment| is the current context, and |origin| is |environment|'s [=environment settings object/origin=]. - For creating a worklet, |environment| is the [=environment settings object=] associated with the {{Window}} that created the worklet, and |origin| is the module scirpt url's [=url/origin=]. - - For initiating (from a {{Window}}) and for running (from {{SharedStorageWorkletGlobalScope}}) operations on a worklet, |environment| is the [=environment settings object=] associated with the {{Window}} that created the worklet, and |origin| is the worklet's [=global scopes=][0]'s [=global object/realm=]'s <span class=todo>HostDefined</span> field's [=environment settings object/origin=]. + - For initiating (from a {{Window}}) and for running (from {{SharedStorageWorkletGlobalScope}}) operations on a worklet, |environment| is the [=environment settings object=] associated with the {{Window}} that created the worklet, and |origin| is the worklet's [=global scopes=][0]'s [=global object/realm=]'s \[[HostDefined]] field's [=environment settings object/origin=]. - For [[#ss-fetch-algo]], |environment| is the request's [=request/window=], and |origin| is the request's [=request/current URL=]'s [=url/origin=]. </div> @@ -351,7 +351,7 @@ Moreover, each {{SharedStorageWorklet}}'s [=global scopes|list of global scopes= 1. Let |document| be |context|'s [=active document=]. 1. [=Assert=]: [=this=]'s [=global scopes=]'s [=list/size=] is 1. 1. Let |globalScope| be [=this=]'s [=global scopes=][0]. - 1. Let |workletOrigin| be |globalScope|'s [=global object/realm=]'s <span class=todo>HostDefined</span> field's [=environment settings object/origin=]. + 1. Let |workletOrigin| be |globalScope|'s [=global object/realm=]'s \[[HostDefined]] field's [=environment settings object/origin=]. 1. If the result of running [=Is feature enabled in document for origin?=] on "[=PermissionsPolicy/shared-storage-select-url=]", |document|, and |workletOrigin| returns false, return a [=promise rejected=] with a {{TypeError}}. 1. If [=this=]'s [=global scopes=] is [=list/empty=], then return a [=promise rejected=] with a {{TypeError}}. 1. If the result of running [=SharedStorageWorkletGlobalScope/check whether addModule is finished=] for |globalScope| is false, return a [=promise rejected=] with a {{TypeError}}. @@ -379,7 +379,7 @@ Moreover, each {{SharedStorageWorklet}}'s [=global scopes|list of global scopes= 1. If the result of running [=determine whether shared storage is allowed by enrollment and user preference=] given |environment| and |workletOrigin| is false: 1. If [=this=]'s [=SharedStorageWorklet/is cross-origin worklet=] is false, return a [=promise rejected=] with a {{TypeError}}. 1. If |options|["`resolveToConfig`"] is true, [=resolve=] |resultPromise| with |pendingConfig|. - 1. Othewise, [=resolve=] |resultPromise| to |urn|. + 1. Otherwise, [=resolve=] |resultPromise| to |urn|. 1. Let |indexPromise| be the result of running [=get the select-url result index=], given [=this=], |name|, |urlList|, and |options|. 1. [=Upon fulfillment=] of |indexPromise| with |resultIndex|, perform the following steps: 1. Let |site| be the result of running [=obtain a site=] with |document|'s [=Document/origin=]. @@ -413,7 +413,7 @@ Moreover, each {{SharedStorageWorklet}}'s [=global scopes|list of global scopes= 1. [=Assert=]: [=this=]'s [=global scopes=]'s [=list/size=] is 1. 1. Let |globalScope| be [=this=]'s [=global scopes=][0]. 1. If the result of running [=SharedStorageWorkletGlobalScope/check whether addModule is finished=] for |globalScope| is false, return a [=promise rejected=] with a {{TypeError}}. - 1. Let |workletOrigin| be |globalScope|'s [=global object/realm=]'s <span class=todo>HostDefined</span> field's [=environment settings object/origin=]. + 1. Let |workletOrigin| be |globalScope|'s [=global object/realm=]'s \[[HostDefined]] field's [=environment settings object/origin=]. 1. If the result of running [=determine whether shared storage is allowed by context=] given |window| and |workletOrigin| is false, [=reject=] |promise| with a {{TypeError}}. 1. If the result of running [=determine whether shared storage is allowed by enrollment and user preference=] given |window| and |workletOrigin| is false: 1. If [=this=]'s [=SharedStorageWorklet/is cross-origin worklet=] is false, [=reject=] |promise| with a {{TypeError}}. @@ -467,30 +467,14 @@ Moreover, each {{SharedStorageWorklet}}'s [=global scopes|list of global scopes= 5. Let <var ignore=''>insideSettings</var> be the result of [=setting up a worklet environment settings object=] given <var ignore=''>realmExecutionContext</var>, <var ignore=''>outsideSettings</var>, and |worklet|. - ### Monkey Patch for [=/request=] ### {#request-monkey-patch} - A [=/request=] has an associated <dfn for=request>is shared storage cross origin worklet request boolean</dfn>. Unless stated otherwise it is false. - ### Monkey Patch for [=fetch a worklet script graph=] ### {#fetch-a-worklet-script-graph-monkey-patch} The algorithm [=fetch a worklet script graph=] calls into the <a href="https://html.spec.whatwg.org/multipage/webappapis.html#fetch-a-worklet/module-worker-script-graph">fetch a worklet/module worker script graph</a> 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|, ...": - 5. If |fetchClient|'s [=environment settings object/realm execution context=]'s Realm component's [=global object=] is {{SharedStorageWorkletGlobalScope}}: + 5. If <var ignore=''>fetchClient</var>'s [=environment settings object/realm execution context=]'s Realm component's [=global object=] is {{SharedStorageWorkletGlobalScope}}: 1. Set |request|'s [=request/redirect mode=] to "<code>error</code>". - 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 <span class=todo>HostDefined</span> field's [=environment settings object/origin=] (as described in [[#set-up-a-worklet-environment-settings-object-monkey-patch]]) 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 <span class=todo>HostDefined</span> field's [=environment settings object/origin=] should be updated to return the final request's [=request/URL=]'s [=url/origin=] after receiving the final request's response, and the user preference checkings shall only be done after that point. - - 1. If <var ignore=''>moduleResponsesMap</var>'s [=associated realm=]'s <span class=todo>HostDefined</span> field's [=environment settings object/origin=] and |fetchClient|'s [=environment settings object/origin=] are not [=same origin=]: - 1. Set |request|'s [=request/is shared storage cross origin worklet request boolean=] to true. - - ### Monkey Patch for [=HTTP fetch=] ### {#http-fetch-monkey-patch} - The following step will be added to the [=HTTP fetch=] steps, before checking the redirect status (i.e. "If |actualResponse|'s status is a redirect status, ..."): - - 1. If |request|'s [=request/is shared storage cross origin worklet request boolean=] is true: - 1. Let |list| be |actualResponse|'s [=response/header list=]. - 1. Let |sharedStorageWorkletAllowed| be the result of running [=get a structured field value=] algorithm given "Shared-Storage-Cross-Origin-Worklet-Allowed", "item", and |list| as input. - 1. If |sharedStorageWorkletAllowed| is false, then return a [=network error=]. - - Note: The website that serves the module script must carefully consider the security risks: when the module script's [=/URL=]'s [=url/origin=] and the worklet's creator {{Window}} origin are not [=same origin=], by sending permissive CORS headers and 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, under that module script's [=url/origin=]. For example, the worklet's creator {{Window}} could poison and use up the worklet origin's [=remaining navigation budget=] by calling {{SharedStorageWorklet/selectURL()}} or {{SharedStorageWorklet/run()}}, where the worklet origin is the global scope's [=global object/realm=]'s <span class=todo>HostDefined</span> field's [=environment settings object/origin=]. + 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 \[[HostDefined]] field's [=environment settings object/origin=] (as described in [[#set-up-a-worklet-environment-settings-object-monkey-patch]]) 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 \[[HostDefined]] field's [=environment settings object/origin=] should be updated to return the final request's [=request/URL=]'s [=url/origin=] after receiving the final request's response, and the user preference checkings shall only be done after that point. ### Monkey Patch for {{Worklet/addModule()}} ### {#add-module-monkey-patch} @@ -631,7 +615,7 @@ Moreover, each {{SharedStorageWorklet}}'s [=global scopes|list of global scopes= ### Navigation Entropy Budget ### {#nav-budget} - If a user [=user activation|activates=] a [=fenced frame=] whose [=fenced frame config instance=] was generated by {{SharedStorageWorklet/selectURL()}} and thereby initiates a [=top-level traversable=] [=navigate|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 {{SharedStorageWorklet/selectURL()}}. To mitigate this, a [=user agent=] will set a per-[=calling site=] [=navigation entropy allowance=]. + If a user [=user activation|activates=] a [=fenced frame=] whose [=Node/node document=]'s [=Document/browsing context=]'s [=browsing context/fenced frame config instance=] was generated by {{SharedStorageWorklet/selectURL()}} and thereby initiates a [=top-level traversable=] [=navigate|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 {{SharedStorageWorklet/selectURL()}}. To mitigate this, a [=user agent=] will set a per-[=calling site=] [=navigation entropy allowance=]. A <dfn>calling site</dfn> for {{SharedStorageWorklet/selectURL()}} is a [=site=]. @@ -703,7 +687,7 @@ Moreover, each {{SharedStorageWorklet}}'s [=global scopes|list of global scopes= 1. Otherwise, return false. </div> - A [=bit debit=] will need to be [=charge shared storage navigation budget|charged=] to the [=shared storage navigation budget table=] for each [=top-frame=] [=navigate|navigation=] initiated by a [=fenced frame=] whose [=fenced frame config instance=] was generated via {{SharedStorageWorklet/selectURL()}}, as this can leak cross-site data. Since the [=bit debit/bits=] to charge is calculated during the call to {{SharedStorageWorklet/selectURL()}} but only actually recorded in the [=shared storage navigation budget table=] if and when the resulting fenced frame initiates a [=top-level traversable=] [=beginning navigation|navigation=], the [=bit debit/bits=] must be stored as a <dfn>pending shared storage budget debit</dfn> in the corresponding [=fenced frame config instance=] until this time. + A [=bit debit=] will need to be [=charge shared storage navigation budget|charged=] to the [=shared storage navigation budget table=] for each [=top-frame=] [=navigate|navigation=] initiated by a [=fenced frame=] whose [=Node/node document=]'s [=Document/browsing context=]'s [=browsing context/fenced frame config instance=] was generated via {{SharedStorageWorklet/selectURL()}}, as this can leak cross-site data. Since the [=bit debit/bits=] to charge is calculated during the call to {{SharedStorageWorklet/selectURL()}} but only actually recorded in the [=shared storage navigation budget table=] if and when the resulting fenced frame initiates a [=top-level traversable=] [=beginning navigation|navigation=], the [=bit debit/bits=] must be stored as a <dfn>pending shared storage budget debit</dfn> in the corresponding fenced frame's [=Node/node document=]'s [=Document/browsing context=]'s [=browsing context/fenced frame config instance=] until this time. <span class=todo>Move the definition of [=pending shared storage budget debit=] to [=fenced frame config instance=] in the draft [[Fenced-Frame]] specification.</span> @@ -1194,7 +1178,7 @@ On the other hand, methods for getting data from the [=shared storage database=] 1. If |context|'s [=active window=]'s [=associated document=] is not [=fully active=], return a [=promise rejected=] with a {{TypeError}}. 1. Let |environment| be |context|'s [=active window=]'s [=relevant settings object=]. 1. Let |realm| be the [=current realm=]. - 1. Let |databaseMap| be the result of running [=obtain a shared storage bottle map=] given |environment| and |realm|'s <span class=todo>HostDefined</span> field's [=environment settings object/origin=]. + 1. Let |databaseMap| be the result of running [=obtain a shared storage bottle map=] given |environment| and |realm|'s \[[HostDefined]] field's [=environment settings object/origin=]. 1. If |databaseMap| is failure, then return a [=promise rejected=] with a {{TypeError}}. 1. Let |queue| be |context|'s associated [=shared storage database|database=]'s [=shared storage database/shared storage database queue=]. 1. [=Enqueue the following steps=] on |queue|: @@ -1223,7 +1207,7 @@ On the other hand, methods for getting data from the [=shared storage database=] 1. If |context|'s [=active window=]'s [=associated document=] is not [=fully active=], return a [=promise rejected=] with a {{TypeError}}. 1. Let |environment| be |context|'s [=active window=]'s [=relevant settings object=]. 1. Let |realm| be the [=current realm=]. - 1. Let |databaseMap| be the result of running [=obtain a shared storage bottle map=] given |environment| and |realm|'s <span class=todo>HostDefined</span> field's [=environment settings object/origin=]. + 1. Let |databaseMap| be the result of running [=obtain a shared storage bottle map=] given |environment| and |realm|'s \[[HostDefined]] field's [=environment settings object/origin=]. 1. If |databaseMap| is failure, then return a [=promise rejected=] with a {{TypeError}}. 1. Let |queue| be |context|'s associated [=shared storage database|database=]'s [=shared storage database/shared storage database queue=]. 1. [=Enqueue the following steps=] on |queue|: @@ -1252,7 +1236,7 @@ On the other hand, methods for getting data from the [=shared storage database=] 1. If |context|'s [=active window=]'s [=associated document=] is not [=fully active=], return a [=promise rejected=] with a {{TypeError}}. 1. Let |environment| be |context|'s [=active window=]'s [=relevant settings object=]. 1. Let |realm| be the [=current realm=]. - 1. Let |databaseMap| be the result of running [=obtain a shared storage bottle map=] given |environment| and |realm|'s <span class=todo>HostDefined</span> field's [=environment settings object/origin=]. + 1. Let |databaseMap| be the result of running [=obtain a shared storage bottle map=] given |environment| and |realm|'s \[[HostDefined]] field's [=environment settings object/origin=]. 1. If |databaseMap| is failure, then return a [=promise rejected=] with a {{TypeError}}. 1. Let |queue| be |context|'s associated [=shared storage database|database=]'s [=shared storage database/shared storage database queue=]. 1. [=Enqueue the following steps=] on |queue|: @@ -1271,7 +1255,7 @@ On the other hand, methods for getting data from the [=shared storage database=] 1. If |context|'s [=active window=]'s [=associated document=] is not [=fully active=], return a [=promise rejected=] with a {{TypeError}}. 1. Let |environment| be |context|'s [=active window=]'s [=relevant settings object=]. 1. Let |realm| be the [=current realm=]. - 1. Let |databaseMap| be the result of running [=obtain a shared storage bottle map=] given |environment| and |realm|'s <span class=todo>HostDefined</span> field's [=environment settings object/origin=]. + 1. Let |databaseMap| be the result of running [=obtain a shared storage bottle map=] given |environment| and |realm|'s \[[HostDefined]] field's [=environment settings object/origin=]. 1. If |databaseMap| is failure, then return a [=promise rejected=] with a {{TypeError}}. 1. Let |queue| be |context|'s associated [=shared storage database|database=]'s [=shared storage database/shared storage database queue=]. 1. [=Enqueue the following steps=] on |queue|: @@ -1293,7 +1277,7 @@ On the other hand, methods for getting data from the [=shared storage database=] 1. If |context|'s [=active window=]'s [=associated document=] is not [=fully active=], return a [=promise rejected=] with a {{TypeError}}. 1. Let |environment| be |context|'s [=active window=]'s [=relevant settings object=]. 1. Let |realm| be the [=current realm=]. - 1. Let |databaseMap| be the result of running [=obtain a shared storage bottle map=] given |environment| and |realm|'s <span class=todo>HostDefined</span> field's [=environment settings object/origin=]. + 1. Let |databaseMap| be the result of running [=obtain a shared storage bottle map=] given |environment| and |realm|'s \[[HostDefined]] field's [=environment settings object/origin=]. 1. If |databaseMap| is failure, then return a [=promise rejected=] with a {{TypeError}}. 1. Let |queue| be |context|'s associated [=shared storage database|database=]'s [=shared storage database/shared storage database queue=]. 1. [=Enqueue the following steps=] on |queue|: @@ -1314,7 +1298,7 @@ On the other hand, methods for getting data from the [=shared storage database=] 1. If |context|'s [=active window=]'s [=associated document=] is not [=fully active=], return a [=promise rejected=] with a {{TypeError}}. 1. Let |environment| be |context|'s [=active window=]'s [=relevant settings object=]. 1. Let |realm| be the [=current realm=]. - 1. Let |databaseMap| be the result of running [=obtain a shared storage bottle map=] given |environment| and |realm|'s <span class=todo>HostDefined</span> field's [=environment settings object/origin=]. + 1. Let |databaseMap| be the result of running [=obtain a shared storage bottle map=] given |environment| and |realm|'s \[[HostDefined]] field's [=environment settings object/origin=]. 1. If |databaseMap| is failure, then return a [=promise rejected=] with a {{TypeError}}. 1. Let |queue| be |context|'s associated [=shared storage database|database=]'s [=shared storage database/shared storage database queue=]. 1. [=Enqueue the following steps=] on |queue|: @@ -1334,9 +1318,9 @@ On the other hand, methods for getting data from the [=shared storage database=] 1. If |context|'s [=active window=]'s [=associated document=] is not [=fully active=], return a [=promise rejected=] with a {{TypeError}}. 1. Let |environment| be |context|'s [=active window=]'s [=relevant settings object=]. 1. Let |realm| be the [=current realm=]. - 1. If the result of running [=determine whether shared storage is allowed by context=] given |environment| and |realm|'s <span class=todo>HostDefined</span> field's [=environment settings object/origin=] is false, return a [=promise rejected=] with a {{TypeError}}. - 1. If the result of running [=determine whether shared storage is allowed by enrollment and user preference=] given |environment| and |realm|'s <span class=todo>HostDefined</span> field's [=environment settings object/origin=] is false, return a [=promise rejected=] with a {{TypeError}}. - 1. Let |site| be the result of running [=obtain a site=] with |realm|'s <span class=todo>HostDefined</span> field's [=environment settings object/origin=]. + 1. If the result of running [=determine whether shared storage is allowed by context=] given |environment| and |realm|'s \[[HostDefined]] field's [=environment settings object/origin=] is false, return a [=promise rejected=] with a {{TypeError}}. + 1. If the result of running [=determine whether shared storage is allowed by enrollment and user preference=] given |environment| and |realm|'s \[[HostDefined]] field's [=environment settings object/origin=] is false, return a [=promise rejected=] with a {{TypeError}}. + 1. Let |site| be the result of running [=obtain a site=] with |realm|'s \[[HostDefined]] field's [=environment settings object/origin=]. 1. [=Assert=]: |site| is not an [=opaque origin=]. 1. Let |queue| be |context|'s associated [=shared storage database|database=]'s [=shared storage database/shared storage database queue=]. 1. [=Enqueue the following steps=] on |queue|: @@ -1364,7 +1348,7 @@ On the other hand, methods for getting data from the [=shared storage database=] 1. If |context|'s [=active window=]'s [=associated document=] is not [=fully active=], return a [=promise rejected=] with a {{TypeError}}. 1. Let |environment| be |context|'s [=active window=]'s [=relevant settings object=]. 1. Let |realm| be the [=current realm=]. - 1. Let |databaseMap| be the result of running [=obtain a shared storage bottle map=] given |environment| and |realm|'s <span class=todo>HostDefined</span> field's [=environment settings object/origin=]. + 1. Let |databaseMap| be the result of running [=obtain a shared storage bottle map=] given |environment| and |realm|'s \[[HostDefined]] field's [=environment settings object/origin=]. 1. If |databaseMap| is failure, then return a [=promise rejected=] with a {{TypeError}}. 1. Let |queue| be |context|'s associated [=shared storage database|database=]'s [=shared storage database/shared storage database queue=]. 1. [=Enqueue the following steps=] on |queue|: From 91001295aabf74526368d8883037fc988bea5f94 Mon Sep 17 00:00:00 2001 From: Yao Xiao <xyaof3@126.com> Date: Fri, 12 Apr 2024 22:47:23 -0400 Subject: [PATCH 15/15] Address comments --- spec.bs | 150 +++++++++++++++++++++++++++----------------------------- 1 file changed, 73 insertions(+), 77 deletions(-) diff --git a/spec.bs b/spec.bs index 74e3fd2..fd69a2f 100644 --- a/spec.bs +++ b/spec.bs @@ -4,10 +4,11 @@ Shortname: sharedStorage Level: 1 Status: CG-DRAFT Group: WICG +Repository: WICG/shared-storage URL: https://github.com/WICG/shared-storage Editor: Camillia Smith Barnes, Google https://google.com, cammie@chromium.org Markup Shorthands: markdown yes -Abstract: Shared Storage is a storage API that is intentionally not partitioned by top-frame site (though still partitioned by context origin of course!). To limit cross-site reidentification of users, data in Shared Storage may only be read in a restricted environment that has carefully constructed output gates. +Abstract: Shared Storage is a storage API that is intentionally not partitioned by top-level traversable site (though still partitioned by context origin of course!). To limit cross-site reidentification of users, data in Shared Storage may only be read in a restricted environment that has carefully constructed output gates. </pre> <pre class=link-defaults> @@ -46,7 +47,6 @@ spec: html; urlPrefix: https://html.spec.whatwg.org/multipage/ text: processCustomFetchResponse; url: webappapis.html#fetching-scripts-processcustomfetchresponse text: environment; url: webappapis.html#environment text: obtaining a worklet agent; url: webappapis.html#obtain-a-worklet-agent - text: top-frame; url: webappapis.html#top-level-traversable text: beginning navigation; url: webappapis.html#beginning-navigation text: ending navigation; url: webappapis.html#ending-navigation text: get the top-level traversable; url: webappapis.html#nav-top @@ -118,6 +118,7 @@ spec: fenced-frame; urlPrefix: https://wicg.github.io/fenced-frame/ type: dfn text: fenced frame; url: the-fencedframe-element text: url; for: FencedFrameConfig; url: dom-fencedframeconfig-url + text: initiator fenced frame config instance; for: source snapshot params; url: source-snapshot-params-initiator-fenced-frame-config-instance text: fence.reportEvent(); url: dom-fence-reportevent text: FenceEvent; url: dictdef-fenceevent text: destination; for: FenceEvent; url: dom-fenceevent-destination @@ -171,9 +172,9 @@ Introduction {#intro} ===================== <em>This section is not normative.</em> -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. +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-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|private aggregation report=]. Over time, there may be additional ouput gates included in the standard. +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|private aggregation report=]. Over time, there may be additional ouput gates included in the standard. <div class="example"> `a.example` randomly assigns users to groups in a way that is consistent cross-site. @@ -266,16 +267,15 @@ When {{Worklet/addModule()}} is called for a worklet, it will run [=check if add To <dfn>determine whether shared storage is allowed by enrollment and user preference</dfn>, given an [=environment settings object=] |environment| and an [=/origin=] |origin|, run these steps: 1. If the result of running [=obtaining a site|obtain a site=] with |origin| is not [=enrolled=], then return false. - 1. If the result of running [=check if user preference setting allows access to shared storage=] given |environment| and |origin| is false, then return false. - 1. Return true. + 1. Return the result of running [=check if user preference setting allows access to shared storage=] given |environment| and |origin|. </div> <div class="note"> Here are the scenarios where the algorithms [=determine whether shared storage is allowed by context=] and [=determine whether shared storage is allowed by enrollment and user preference=] are used: - For each method under [[#window-setter]], |environment| is the current context, and |origin| is |environment|'s [=environment settings object/origin=]. - - For creating a worklet, |environment| is the [=environment settings object=] associated with the {{Window}} that created the worklet, and |origin| is the module scirpt url's [=url/origin=]. - - For initiating (from a {{Window}}) and for running (from {{SharedStorageWorkletGlobalScope}}) operations on a worklet, |environment| is the [=environment settings object=] associated with the {{Window}} that created the worklet, and |origin| is the worklet's [=global scopes=][0]'s [=global object/realm=]'s \[[HostDefined]] field's [=environment settings object/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 [=url/origin=]. + - For running operations on a worklet (from a {{Window}}), and for each method under [[#worklet-setter]] (from {{SharedStorageWorkletGlobalScope}}), |environment| is the [=environment settings object=] associated with the {{Window}} that created the worklet, and |origin| is the worklet's [=global scopes=][0]'s [=global object/realm=]'s [=realm/settings object=]'s [=environment settings object/origin=]. - For [[#ss-fetch-algo]], |environment| is the request's [=request/window=], and |origin| is the request's [=request/current URL=]'s [=url/origin=]. </div> @@ -304,7 +304,6 @@ Moreover, each {{SharedStorageWorklet}}'s [=global scopes|list of global scopes= 1. If |window|'s [=Window/browsing context=] is null, then return a [=promise rejected=] with a {{TypeError}}. 1. If |window|'s [=associated document=] is not [=fully active=], return a [=promise rejected=] with a {{TypeError}}. 1. Return |promise|, and immediately [=obtain a worklet agent=] given |window| and run the rest of these steps in that agent: - 1. Let |index| be [=default index=]. 1. [=Assert=]: |worklet|'s [=global scopes=]'s [=list/size=] is 1. 1. [=Assert=]: |worklet|'s [=module map=] is not [=map/empty=]. 1. Let |globalScope| be [=this=]'s [=global scopes=][0]. @@ -317,26 +316,16 @@ Moreover, each {{SharedStorageWorklet}}'s [=global scopes|list of global scopes= 1. [=Assert=]: |operation|'s [=associated realm=] is [=this=]'s [=relevant realm=]. 1. Let |argumentsList| be the [=/list=] « |urlList| ». 1. If |options| [=map/contains=] |data|, [=list/append=] |data| to |argumentsList|. - 1. Let |operationResult| be the result of [=invoking=] |operation| with |argumentsList|. - 1. If [=an exception was thrown=], then [=queue a global task=] on the [=DOM manipulation task source=], given |window|, to [=reject=] |promise| with a {{TypeError}}. - - Note: This indicates that |operationCtor|'s run() method encounters an error, where |operationCtor| is the parameter in {{SharedStorageWorkletGlobalScope/register()}}. - - 1. Otherwise: - 1. Set |index| to |operationResult|. - 1. If [=an exception was thrown=]: - 1. [=Queue a global task=] on the [=DOM manipulation task source=], given |window|, to [=reject=] |promise| with a {{TypeError}}. - 1. Abort these steps. + 1. Let |index| be the result of [=invoking=] |operation| with |argumentsList|. + 1. 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: The result index is a non-integer value. This violates the selectURL() protocol, and we don't know which url should be selected. + Note: This indicates that either |operationCtor|'s run() method encounters an error (where |operationCtor| is the parameter in {{SharedStorageWorkletGlobalScope/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. - 1. Otherwise, if |index| is greater than |urlList|'s [=list/size=]: - 1. [=Queue a global task=] on the [=DOM manipulation task source=], given |window|, to [=reject=] |promise| with a {{TypeError}}. - 1. Abort these steps. + 1. If |index| is greater than |urlList|'s [=list/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. + 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. - 1. [=Queue a global task=] on the [=DOM manipulation task source=], given |window|, to [=resolve=] |promise| with |index|. + 1. [=Queue a global task=] on the [=DOM manipulation task source=], given |window|, to [=resolve=] |promise| with |index|. </div> <div algorithm> @@ -351,9 +340,12 @@ Moreover, each {{SharedStorageWorklet}}'s [=global scopes|list of global scopes= 1. Let |document| be |context|'s [=active document=]. 1. [=Assert=]: [=this=]'s [=global scopes=]'s [=list/size=] is 1. 1. Let |globalScope| be [=this=]'s [=global scopes=][0]. - 1. Let |workletOrigin| be |globalScope|'s [=global object/realm=]'s \[[HostDefined]] field's [=environment settings object/origin=]. + 1. Let |workletOrigin| be |globalScope|'s [=global object/realm=]'s [=realm/settings object=]'s [=environment settings object/origin=]. 1. If the result of running [=Is feature enabled in document for origin?=] on "[=PermissionsPolicy/shared-storage-select-url=]", |document|, and |workletOrigin| returns false, return a [=promise rejected=] with a {{TypeError}}. 1. If [=this=]'s [=global scopes=] is [=list/empty=], then return a [=promise rejected=] with a {{TypeError}}. + + Note: This can happen if either {{WindowSharedStorage/selectURL()}} or {{SharedStorageWorklet/selectURL()}} is called before {{addModule()}}. + 1. If the result of running [=SharedStorageWorkletGlobalScope/check whether addModule is finished=] for |globalScope| is false, return a [=promise rejected=] with a {{TypeError}}. 1. If |urls| is empty or if |urls|'s [=list/size=] is greater than 8, return a [=promise rejected=] with a {{TypeError}}. @@ -369,8 +361,7 @@ Moreover, each {{SharedStorageWorklet}}'s [=global scopes|list of global scopes= 1. If |urlWithMetadata| has field "`reportingMetadata`": 1. Let |reportingMetadata| be |urlWithMetadata|["`reportingMetadata`"]. 1. If the result of running [=validate reporting metadata=] with |reportingMetadata| is false, [=reject=] |resultPromise| with a {{TypeError}} and abort these steps. - 1. Let |fencedFrameConfigMapping| be |globalScope|'s [=associated Document=]'s [=node navigable=]'s - [=navigable/traversable navigable=]'s [=traversable navigable/fenced frame config mapping=]. + 1. Let |fencedFrameConfigMapping| be |window|'s [=associated document=]'s [=node navigable=]'s [=navigable/traversable navigable=]'s [=traversable navigable/fenced frame config mapping=]. 1. Let |pendingConfig| be a new [=fenced frame config=]. 1. Let |urn| be the result of running [=fenced frame config mapping/store a pending config=] on |fencedFrameConfigMapping| with |pendingConfig|. 1. If |urn| is failure, then return a [=promise rejected=] with a {{TypeError}}. @@ -379,12 +370,11 @@ Moreover, each {{SharedStorageWorklet}}'s [=global scopes|list of global scopes= 1. If the result of running [=determine whether shared storage is allowed by enrollment and user preference=] given |environment| and |workletOrigin| is false: 1. If [=this=]'s [=SharedStorageWorklet/is cross-origin worklet=] is false, return a [=promise rejected=] with a {{TypeError}}. 1. If |options|["`resolveToConfig`"] is true, [=resolve=] |resultPromise| with |pendingConfig|. - 1. Otherwise, [=resolve=] |resultPromise| to |urn|. + 1. Otherwise, [=resolve=] |resultPromise| with |urn|. 1. Let |indexPromise| be the result of running [=get the select-url result index=], given [=this=], |name|, |urlList|, and |options|. 1. [=Upon fulfillment=] of |indexPromise| with |resultIndex|, perform the following steps: 1. Let |site| be the result of running [=obtain a site=] with |document|'s [=Document/origin=]. 1. Let |remainingBudget| be the result of running [=determine remaining navigation budget=] with |environment| and |site|. - 1. [=Assert=]: |remainingBudget| is not undefined. 1. Let |pendingBits| be the logarithm base 2 of |urlList|'s [=list/size=]. 1. If |pendingBits| is greather than |remainingBudget|, set |resultIndex| to [=default index=]. 1. Let |finalConfig| be a new [=fenced frame config=]. @@ -413,7 +403,7 @@ Moreover, each {{SharedStorageWorklet}}'s [=global scopes|list of global scopes= 1. [=Assert=]: [=this=]'s [=global scopes=]'s [=list/size=] is 1. 1. Let |globalScope| be [=this=]'s [=global scopes=][0]. 1. If the result of running [=SharedStorageWorkletGlobalScope/check whether addModule is finished=] for |globalScope| is false, return a [=promise rejected=] with a {{TypeError}}. - 1. Let |workletOrigin| be |globalScope|'s [=global object/realm=]'s \[[HostDefined]] field's [=environment settings object/origin=]. + 1. Let |workletOrigin| be |globalScope|'s [=global object/realm=]'s [=realm/settings object=]'s [=environment settings object/origin=]. 1. If the result of running [=determine whether shared storage is allowed by context=] given |window| and |workletOrigin| is false, [=reject=] |promise| with a {{TypeError}}. 1. If the result of running [=determine whether shared storage is allowed by enrollment and user preference=] given |window| and |workletOrigin| is false: 1. If [=this=]'s [=SharedStorageWorklet/is cross-origin worklet=] is false, [=reject=] |promise| with a {{TypeError}}. @@ -471,10 +461,10 @@ Moreover, each {{SharedStorageWorklet}}'s [=global scopes|list of global scopes= The algorithm [=fetch a worklet script graph=] calls into the <a href="https://html.spec.whatwg.org/multipage/webappapis.html#fetch-a-worklet/module-worker-script-graph">fetch a worklet/module worker script graph</a> 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|, ...": - 5. If <var ignore=''>fetchClient</var>'s [=environment settings object/realm execution context=]'s Realm component's [=global object=] is {{SharedStorageWorkletGlobalScope}}: + 5. If <var ignore=''>fetchClient</var>'s [=environment settings object/global object=] is {{SharedStorageWorkletGlobalScope}}: 1. Set |request|'s [=request/redirect mode=] to "<code>error</code>". - 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 \[[HostDefined]] field's [=environment settings object/origin=] (as described in [[#set-up-a-worklet-environment-settings-object-monkey-patch]]) 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 \[[HostDefined]] field's [=environment settings object/origin=] should be updated to return the final request's [=request/URL=]'s [=url/origin=] after receiving the final request's response, and the user preference checkings shall only be done after that point. + 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 [=realm/settings object=]'s [=environment settings object/origin=] (as described in [[#set-up-a-worklet-environment-settings-object-monkey-patch]]) 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 [=realm/settings object=]'s [=environment settings object/origin=] should be updated to return the final request's [=request/URL=]'s [=url/origin=] after receiving the final request's response, and the user preference checkings shall only be done after that point. ### Monkey Patch for {{Worklet/addModule()}} ### {#add-module-monkey-patch} @@ -497,8 +487,6 @@ Moreover, each {{SharedStorageWorklet}}'s [=global scopes|list of global scopes= This rationale also applies to the handling for user preferences error for {{SharedStorageWorklet/selectURL()}} and {{SharedStorageWorklet/run()}}. </div> - <span class=todo>Specify that redirects are disallowed when fetching the module script for {{SharedStorageWorklet}}.</span> - The penultimate step (i.e. the final indented step), currently "If |pendingTasks| is 0, then [=resolve=] |promise|.", should be updated to: 2. If |pendingTasks| is 0, perform the following steps: @@ -513,10 +501,10 @@ Moreover, each {{SharedStorageWorklet}}'s [=global scopes|list of global scopes= The {{SharedStorageWorklet}}'s [=worklet destination type=] is "sharedstorageworklet". - <span class=todo>Add "sharedstorageworklet" to the possible strings that a request [=request/destination=] can have.</span> + Issue(145): Add "sharedstorageworklet" to the possible strings that a request [=request/destination=] can have. <xmp class='idl'> - callback RunFunctionForSharedStorageSelectURLOperation = Promise<unsigned long>(sequence<DOMString> urls, optional any data); + callback RunFunctionForSharedStorageSelectURLOperation = Promise<unsigned long>(sequence<USVString> urls, optional any data); @@ -551,6 +539,8 @@ Moreover, each {{SharedStorageWorklet}}'s [=global scopes|list of global scopes= 1. [=map/Set=] the value of |operationMap|[|name|] to |runIDLFunction|. </div> + Issue(151): The "name" and "operationCtor" cannot be missing here given WebIDL. Should just check for default/empty values. + <div algorithm> The <dfn for="SharedStorageWorkletGlobalScope">{{SharedStorageWorkletGlobalScope/sharedStorage}} getter</dfn> steps are: @@ -566,8 +556,6 @@ Moreover, each {{SharedStorageWorklet}}'s [=global scopes|list of global scopes= ## {{SharedStorageUrlWithMetadata}} and Reporting ## {#reporting} - {{SharedStorageUrlWithMetadata}} [=dictionaries=] contain a [=string=] representing a [=/URL=] and, optionally, a {{reportingMetadata}} {{/object}}. - <xmp class='idl'> dictionary SharedStorageUrlWithMetadata { required USVString url; @@ -575,7 +563,9 @@ Moreover, each {{SharedStorageWorklet}}'s [=global scopes|list of global scopes= }; - If a {{SharedStorageUrlWithMetadata}} [=dictionary=] contains a non-[=map/empty=] {{SharedStorageUrlWithMetadata/reportingMetadata}} {{/object}} in the form of a [=dictionary=] whose [=map/keys=] are {{FenceEvent/eventType}}s and whose [=map/values=] are [=strings=] that parse to valid [=/URLs=], then these {{FenceEvent/eventType}}-[=/URL=] pairs will be [=register reporting metadata|registered=] for later access within any [=fenced frame=] that loads the {{SharedStorageResponse}} resulting from this {{SharedStorageWorklet/selectURL()}} call. + If a {{SharedStorageUrlWithMetadata}} [=dictionary=] contains a non-[=map/empty=] {{SharedStorageUrlWithMetadata/reportingMetadata}} {{/object}} in the form of a [=dictionary=] whose [=map/keys=] are {{FenceEvent}}'s {{FenceEvent/eventType}}s and whose [=map/values=] are [=strings=] that parse to valid [=/URLs=], then these {{FenceEvent/eventType}}-[=/URL=] pairs will be [=register reporting metadata|registered=] for later access within any [=fenced frame=] that loads the {{SharedStorageResponse}} resulting from this {{SharedStorageWorklet/selectURL()}} call. + + Issue(141): {{SharedStorageUrlWithMetadata/reportingMetadata}} should be a [=dictionary=]. Inside a [=fenced frame=] with {{FenceEvent/eventType}}-[=/URL=] pairs that have been [=register reporting metadata|registered=] through {{SharedStorageWorklet/selectURL()}} with {{SharedStorageUrlWithMetadata/reportingMetadata}} {{/object}}s, if {{reportEvent()}} is called on a {{FenceEvent}} with a {{FenceEvent/destination}} [=list/containing=] "`shared-storage-select-url`" and that {{FenceEvent}}'s corresponding {{FenceEvent/eventType}} is triggered, then the {{FenceEvent}}'s {{FenceEvent/eventData}} will be sent as a [=beacon=] to the registered [=/URL=] for that {{FenceEvent/eventType}}. @@ -606,7 +596,8 @@ Moreover, each {{SharedStorageWorklet}}'s [=global scopes|list of global scopes= 1. Let |url| be the result of running a [=URL parser=] on |urlString|. 1. [=Assert=]: |url| is a valid [=/URL=]. 1. [=map/Set=] |reportingUrlMap|[|eventType|] to |url|. - 1. 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): Store |reportingUrlMap| inside a [=fenced frame reporter=] class associated with |fencedFrameConfigStruct|. Both of these still need to be added to the draft [[Fenced-Frame]].
## Entropy Budgets ## {#budgets} @@ -619,7 +610,7 @@ Moreover, each {{SharedStorageWorklet}}'s [=global scopes|list of global scopes= A calling site for {{SharedStorageWorklet/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-frame=] [=navigate|navigations=] during a given [=navigation budget epoch=] for a given calling [=calling site=]. This [=navigation entropy allowance|allowance=] is defined by the [=user agent=] and is [=calling site=]-agnostic. + A navigation entropy allowance is a maximum allowance of [=entropy bits=] that are permitted to leak via [=fenced frames=] initiating [=top-level traversable=] [=navigate|navigations=] during a given [=navigation budget epoch=] for a given calling [=calling site=]. This [=navigation entropy allowance|allowance=] is defined by the [=user agent=] and is [=calling site=]-agnostic. A [=user agent=] will define a fixed predetermined [=duration=] navigation budget lifetime. @@ -651,6 +642,8 @@ Moreover, each {{SharedStorageWorklet}}'s [=global scopes|list of global scopes= The default index for a call to {{SharedStorageWorklet/selectURL()}} is [=implementation-defined=] in such a way that it is independent from the result of the registered operation class's "`run`" method. + Issue(147): Methods can't have state attached to them. Many definitions in this section needs improving. +
The [=default index=] could be defined to be 0. @@ -658,7 +651,7 @@ Moreover, each {{SharedStorageWorklet}}'s [=global scopes|list of global scopes=
- The [=default index=] could be defined to be [=selectURL/input URL list=]'s [=list/size=] minus 1. + The [=default index=] could be defined to be [=selectURL/input URL list=]'s [=list/size=] − 1. In this case, whenever the registered operation class's "`run`" method encounters an error, or whenever there is insufficient [=calling site/remaining navigation budget=], {{SharedStorageWorklet/selectURL()}} would return a {{SharedStorageResponse}} for the last {{SharedStorageUrlWithMetadata/url}} in its [=selectURL/input URL list=].
@@ -666,7 +659,7 @@ Moreover, each {{SharedStorageWorklet}}'s [=global scopes|list of global scopes=
To determine remaining navigation budget, given an [=environment settings object=] |environment| and a [=calling site=] |site|, run the following steps: - 1. If |site| is an [=opaque origin=], return undefined. + 1. [=Assert=]: |site| is not an [=opaque origin=]. 1. Let |maxBits| be the [=user agent=]'s [=navigation entropy allowance=]. 1. If the [=user agent=]'s [=shared storage navigation budget table=] does not [=map/contain=] |site|, then return |maxBits|. 1. Otherwise, let |ledger| be [=user agent=]'s [=shared storage navigation budget table=][|site|]. @@ -674,7 +667,7 @@ Moreover, each {{SharedStorageWorklet}}'s [=global scopes|list of global scopes= 1. [=map/iterate|For each=] [=list/item=] |bitDebit| in |ledger|, do the following steps: 1. Let |debit| be |bitDebit|'s [=bit debit/bits=]. 1. If the result of running [=check whether a bit debit is expired=] with |environment| and |bitDebit| is false, then increment |debitSum| by |debit|. - 1. Return |maxBits| minus |debitSum|. + 1. Return |maxBits| − |debitSum|.
@@ -682,18 +675,20 @@ Moreover, each {{SharedStorageWorklet}}'s [=global scopes|list of global scopes= 1. Let |epochLength| be the [=user agent=]'s [=navigation budget lifetime=]. 1. Let |currentTime| be |environment|'s [=environment settings object/current wall time=]. - 1. Let |threshold| be |currentTime| minus |epochLength|. + 1. Let |threshold| be |currentTime| − |epochLength|. 1. If |bitDebit|'s [=bit debit/timestamp=] is less than |threshold|, return true. 1. Otherwise, return false.
- A [=bit debit=] will need to be [=charge shared storage navigation budget|charged=] to the [=shared storage navigation budget table=] for each [=top-frame=] [=navigate|navigation=] initiated by a [=fenced frame=] whose [=Node/node document=]'s [=Document/browsing context=]'s [=browsing context/fenced frame config instance=] was generated via {{SharedStorageWorklet/selectURL()}}, as this can leak cross-site data. Since the [=bit debit/bits=] to charge is calculated during the call to {{SharedStorageWorklet/selectURL()}} but only actually recorded in the [=shared storage navigation budget table=] if and when the resulting fenced frame initiates a [=top-level traversable=] [=beginning navigation|navigation=], the [=bit debit/bits=] must be stored as a pending shared storage budget debit in the corresponding fenced frame's [=Node/node document=]'s [=Document/browsing context=]'s [=browsing context/fenced frame config instance=] until this time. + A [=bit debit=] will need to be [=charge shared storage navigation budget|charged=] to the [=shared storage navigation budget table=] for each [=top-level traversable=] [=navigate|navigation=] initiated by a [=fenced frame=] whose [=Node/node document=]'s [=Document/browsing context=]'s [=browsing context/fenced frame config instance=] was generated via {{SharedStorageWorklet/selectURL()}}, as this can leak cross-site data. Since the [=bit debit/bits=] to charge is calculated during the call to {{SharedStorageWorklet/selectURL()}} but only actually recorded in the [=shared storage navigation budget table=] if and when the resulting fenced frame initiates a [=top-level traversable=] [=beginning navigation|navigation=], the [=bit debit/bits=] must be stored as a pending shared storage budget debit in the corresponding fenced frame's [=Node/node document=]'s [=Document/browsing context=]'s [=browsing context/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): Move the definition of [=pending shared storage budget debit=] to [=fenced frame config instance=] in the draft [[Fenced-Frame]] specification. Between [=beginning navigation=] and [=ending navigation=], a [=user agent=] will perform the [=charge shared storage navigation budget=] algorithm. - Issue: 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(138): Need to find a better way to specify timing of the navigation budget charging. + + Issue(149): 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.
To charge shared storage navigation budget during a [=beginning navigation|navigation=] with [=/navigable=] |navigable| and {{Document}} |sourceDocument|, run the following steps: @@ -703,17 +698,17 @@ Moreover, each {{SharedStorageWorklet}}'s [=global scopes|list of global scopes= 1. While |currentNavigable| is not null: 1. Let |site| be the result of running [=obtain a site=] with |currentNavigable|'s [=active document=]'s [=document/origin=]. 1. Let |instance| be |currentNavigable|'s [=Node/node document=]'s [=Document/browsing context=]'s [=browsing context/fenced frame config instance=]. - 1. If |instance| is not null and |site| is not an [=opaque origin=], perform the following steps: - 1. Let |pendingBits| be |instance|'s [=pending shared storage budget debit=]. - 1. If |pendingBits| is greater than 0 and if |instance|'s [=shared storage navigation budget charged=] is false, run the following steps: - 1. Let |ledger| be [=user agent=]'s [=shared storage navigation budget table=][|site|]. - 1. Let |bitDebit| be a new [=bit debit=]. - 1. Set |bitDebit|'s [=bit debit/bits=] to |pendingBits|. - 1. Let |currentTime| be the [=/current wall time=]. - 1. Set |bitDebit|'s [=bit debit/timestamp=] to |currentTime|. - 1. [=list/Append=] |bitDebit| to |ledger|. - 1. Set |instance|'s [=shared storage navigation budget charged=] to true. 1. Set |currentNavigable| to |currentNavigable|'s [=navigable/parent=]. + 1. If |instance| is null or |site| is an [=opaque origin=], then [=iteration/continue=]. + 1. Let |pendingBits| be |instance|'s [=pending shared storage budget debit=]. + 1. If |pendingBits| is not greater than 0, or if |instance|'s [=shared storage navigation budget charged=] is true, then [=iteration/continue=]. + 1. Let |ledger| be [=user agent=]'s [=shared storage navigation budget table=][|site|]. + 1. Let |bitDebit| be a new [=bit debit=]. + 1. Set |bitDebit|'s [=bit debit/bits=] to |pendingBits|. + 1. Let |currentTime| be the [=/current wall time=]. + 1. Set |bitDebit|'s [=bit debit/timestamp=] to |currentTime|. + 1. [=list/Append=] |bitDebit| to |ledger|. + 1. Set |instance|'s [=shared storage navigation budget charged=] to true.
### Reporting Entropy Budget ### {#report-budget} @@ -726,22 +721,24 @@ Moreover, each {{SharedStorageWorklet}}'s [=global scopes|list of global scopes= When {{reportEvent()}} is called with a {{FenceEvent/destination}} [=list/containing=] "`shared-storage-select-url`", it will be necessary to [=charge shared storage reporting budget=] as below. + Issue(150): Move this to {{reportEvent()}} in [[Fenced-Frame]]. +
To determine reporting budget to charge, given a {{Document}} |sourceDocument|, run the following steps: 1. Let |debitSum| be 0. 1. Let |currentNavigable| be |sourceDocument|'s [=node navigable=]. 1. While |currentNavigable| is not null: - 1. Let |instance| be |currentNavigable|'s [=Node/node document=]'s [=Document/browsing context=]'s [=browsing context/fenced frame config instance=]. - 1. If |instance| is not null: - 1. Let |pendingBits| be |instance|'s [=pending shared storage budget debit=]. - 1. If |pendingBits| is greater than 0 and if |instance|'s [=shared storage reporting budget charged=] is false, increment |debitSum| by |pendingBits| + 1. Let |instance| be |currentNavigable|'s [=source snapshot params/initiator fenced frame config instance=]. 1. Set |currentNavigable| to |currentNavigable|'s [=navigable/parent=]. + 1. If |instance| is null, then [=iteration/continue=]. + 1. Let |pendingBits| be |instance|'s [=pending shared storage budget debit=]. + 1. If |pendingBits| is greater than 0 and if |instance|'s [=shared storage reporting budget charged=] is false, increment |debitSum| by |pendingBits|. 1. Return |debitSum|.
- Issue: 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): 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.
To charge shared storage reporting budget given a {{Document}} |sourceDocument|, run the following steps: @@ -763,7 +760,7 @@ Moreover, each {{SharedStorageWorklet}}'s [=global scopes|list of global scopes=
To purge expired bit debits from all navigation entropy ledgers, run the following steps: - 1. [=map/iterate|For each=] origin -> |ledger| of [=user agent=]'s [=shared storage navigation budget table=]: + 1. [=map/iterate|For each=] origin → |ledger| of [=user agent=]'s [=shared storage navigation budget table=]: 1. [=map/iterate|For each=] |bitDebit| in |ledger|, if the result of running [=check whether a bit debit is expired=] with |bitDebit| is true, [=list/remove=] |bitDebit| from |ledger|.
@@ -1178,7 +1175,7 @@ On the other hand, methods for getting data from the [=shared storage database=] 1. If |context|'s [=active window=]'s [=associated document=] is not [=fully active=], return a [=promise rejected=] with a {{TypeError}}. 1. Let |environment| be |context|'s [=active window=]'s [=relevant settings object=]. 1. Let |realm| be the [=current realm=]. - 1. Let |databaseMap| be the result of running [=obtain a shared storage bottle map=] given |environment| and |realm|'s \[[HostDefined]] field's [=environment settings object/origin=]. + 1. Let |databaseMap| be the result of running [=obtain a shared storage bottle map=] given |environment| and |realm|'s [=realm/settings object=]'s [=environment settings object/origin=]. 1. If |databaseMap| is failure, then return a [=promise rejected=] with a {{TypeError}}. 1. Let |queue| be |context|'s associated [=shared storage database|database=]'s [=shared storage database/shared storage database queue=]. 1. [=Enqueue the following steps=] on |queue|: @@ -1207,7 +1204,7 @@ On the other hand, methods for getting data from the [=shared storage database=] 1. If |context|'s [=active window=]'s [=associated document=] is not [=fully active=], return a [=promise rejected=] with a {{TypeError}}. 1. Let |environment| be |context|'s [=active window=]'s [=relevant settings object=]. 1. Let |realm| be the [=current realm=]. - 1. Let |databaseMap| be the result of running [=obtain a shared storage bottle map=] given |environment| and |realm|'s \[[HostDefined]] field's [=environment settings object/origin=]. + 1. Let |databaseMap| be the result of running [=obtain a shared storage bottle map=] given |environment| and |realm|'s [=realm/settings object=]'s [=environment settings object/origin=]. 1. If |databaseMap| is failure, then return a [=promise rejected=] with a {{TypeError}}. 1. Let |queue| be |context|'s associated [=shared storage database|database=]'s [=shared storage database/shared storage database queue=]. 1. [=Enqueue the following steps=] on |queue|: @@ -1236,7 +1233,7 @@ On the other hand, methods for getting data from the [=shared storage database=] 1. If |context|'s [=active window=]'s [=associated document=] is not [=fully active=], return a [=promise rejected=] with a {{TypeError}}. 1. Let |environment| be |context|'s [=active window=]'s [=relevant settings object=]. 1. Let |realm| be the [=current realm=]. - 1. Let |databaseMap| be the result of running [=obtain a shared storage bottle map=] given |environment| and |realm|'s \[[HostDefined]] field's [=environment settings object/origin=]. + 1. Let |databaseMap| be the result of running [=obtain a shared storage bottle map=] given |environment| and |realm|'s [=realm/settings object=]'s [=environment settings object/origin=]. 1. If |databaseMap| is failure, then return a [=promise rejected=] with a {{TypeError}}. 1. Let |queue| be |context|'s associated [=shared storage database|database=]'s [=shared storage database/shared storage database queue=]. 1. [=Enqueue the following steps=] on |queue|: @@ -1255,7 +1252,7 @@ On the other hand, methods for getting data from the [=shared storage database=] 1. If |context|'s [=active window=]'s [=associated document=] is not [=fully active=], return a [=promise rejected=] with a {{TypeError}}. 1. Let |environment| be |context|'s [=active window=]'s [=relevant settings object=]. 1. Let |realm| be the [=current realm=]. - 1. Let |databaseMap| be the result of running [=obtain a shared storage bottle map=] given |environment| and |realm|'s \[[HostDefined]] field's [=environment settings object/origin=]. + 1. Let |databaseMap| be the result of running [=obtain a shared storage bottle map=] given |environment| and |realm|'s [=realm/settings object=]'s [=environment settings object/origin=]. 1. If |databaseMap| is failure, then return a [=promise rejected=] with a {{TypeError}}. 1. Let |queue| be |context|'s associated [=shared storage database|database=]'s [=shared storage database/shared storage database queue=]. 1. [=Enqueue the following steps=] on |queue|: @@ -1277,7 +1274,7 @@ On the other hand, methods for getting data from the [=shared storage database=] 1. If |context|'s [=active window=]'s [=associated document=] is not [=fully active=], return a [=promise rejected=] with a {{TypeError}}. 1. Let |environment| be |context|'s [=active window=]'s [=relevant settings object=]. 1. Let |realm| be the [=current realm=]. - 1. Let |databaseMap| be the result of running [=obtain a shared storage bottle map=] given |environment| and |realm|'s \[[HostDefined]] field's [=environment settings object/origin=]. + 1. Let |databaseMap| be the result of running [=obtain a shared storage bottle map=] given |environment| and |realm|'s [=realm/settings object=]'s [=environment settings object/origin=]. 1. If |databaseMap| is failure, then return a [=promise rejected=] with a {{TypeError}}. 1. Let |queue| be |context|'s associated [=shared storage database|database=]'s [=shared storage database/shared storage database queue=]. 1. [=Enqueue the following steps=] on |queue|: @@ -1298,7 +1295,7 @@ On the other hand, methods for getting data from the [=shared storage database=] 1. If |context|'s [=active window=]'s [=associated document=] is not [=fully active=], return a [=promise rejected=] with a {{TypeError}}. 1. Let |environment| be |context|'s [=active window=]'s [=relevant settings object=]. 1. Let |realm| be the [=current realm=]. - 1. Let |databaseMap| be the result of running [=obtain a shared storage bottle map=] given |environment| and |realm|'s \[[HostDefined]] field's [=environment settings object/origin=]. + 1. Let |databaseMap| be the result of running [=obtain a shared storage bottle map=] given |environment| and |realm|'s [=realm/settings object=]'s [=environment settings object/origin=]. 1. If |databaseMap| is failure, then return a [=promise rejected=] with a {{TypeError}}. 1. Let |queue| be |context|'s associated [=shared storage database|database=]'s [=shared storage database/shared storage database queue=]. 1. [=Enqueue the following steps=] on |queue|: @@ -1318,14 +1315,13 @@ On the other hand, methods for getting data from the [=shared storage database=] 1. If |context|'s [=active window=]'s [=associated document=] is not [=fully active=], return a [=promise rejected=] with a {{TypeError}}. 1. Let |environment| be |context|'s [=active window=]'s [=relevant settings object=]. 1. Let |realm| be the [=current realm=]. - 1. If the result of running [=determine whether shared storage is allowed by context=] given |environment| and |realm|'s \[[HostDefined]] field's [=environment settings object/origin=] is false, return a [=promise rejected=] with a {{TypeError}}. - 1. If the result of running [=determine whether shared storage is allowed by enrollment and user preference=] given |environment| and |realm|'s \[[HostDefined]] field's [=environment settings object/origin=] is false, return a [=promise rejected=] with a {{TypeError}}. - 1. Let |site| be the result of running [=obtain a site=] with |realm|'s \[[HostDefined]] field's [=environment settings object/origin=]. + 1. If the result of running [=determine whether shared storage is allowed by context=] given |environment| and |realm|'s [=realm/settings object=]'s [=environment settings object/origin=] is false, return a [=promise rejected=] with a {{TypeError}}. + 1. If the result of running [=determine whether shared storage is allowed by enrollment and user preference=] given |environment| and |realm|'s [=realm/settings object=]'s [=environment settings object/origin=] is false, return a [=promise rejected=] with a {{TypeError}}. + 1. Let |site| be the result of running [=obtain a site=] with |realm|'s [=realm/settings object=]'s [=environment settings object/origin=]. 1. [=Assert=]: |site| is not an [=opaque origin=]. 1. Let |queue| be |context|'s associated [=shared storage database|database=]'s [=shared storage database/shared storage database queue=]. 1. [=Enqueue the following steps=] on |queue|: 1. Let |remainingBudget| be the result of running [=determine remaining navigation budget=] with |site|. - 1. [=Assert=]: |remainingBudget| is not undefined. 1. [=Resolve=] [=queue a global task=] on the [=DOM manipulation task source=], given |realm|'s [=global object=], to [=resolve=] |promise| with |remainingBudget|. 1. Return |promise|.
@@ -1348,7 +1344,7 @@ On the other hand, methods for getting data from the [=shared storage database=] 1. If |context|'s [=active window=]'s [=associated document=] is not [=fully active=], return a [=promise rejected=] with a {{TypeError}}. 1. Let |environment| be |context|'s [=active window=]'s [=relevant settings object=]. 1. Let |realm| be the [=current realm=]. - 1. Let |databaseMap| be the result of running [=obtain a shared storage bottle map=] given |environment| and |realm|'s \[[HostDefined]] field's [=environment settings object/origin=]. + 1. Let |databaseMap| be the result of running [=obtain a shared storage bottle map=] given |environment| and |realm|'s [=realm/settings object=]'s [=environment settings object/origin=]. 1. If |databaseMap| is failure, then return a [=promise rejected=] with a {{TypeError}}. 1. Let |queue| be |context|'s associated [=shared storage database|database=]'s [=shared storage database/shared storage database queue=]. 1. [=Enqueue the following steps=] on |queue|: @@ -1667,6 +1663,6 @@ Privacy Considerations {#privacy} 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 [=/URL=]s 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=] [=navigate|navigation=] and/or the [=fenced frame=] calls the {{reportEvent()}} API. + In particular, an embedder can select a [=/URL=] from a short list of [=/URL=]s 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=] [=navigate|navigation=] and/or the [=fenced frame=] calls the {{reportEvent()}} API. An embedder is also able to send aggregatable reports through the [=Private Aggregation|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.