forked from WICG/shared-storage
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathspec.bs
1709 lines (1342 loc) · 122 KB
/
spec.bs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<pre class='metadata'>
Title: Shared Storage API
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, [email protected]
Markup Shorthands: markdown yes
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>
spec:infra;
type:dfn;
text:user agent
for:/; text:string
type:dfn;
for:/; text:list
spec:webidl;
type:interface;
text:double
type:dfn;
text:an exception was thrown
spec:html;
type:dfn;
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
for:browsing context; text:fenced frame config instance
</pre>
<pre class="anchors">
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
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: 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
text: boolean attributes; url: common-microsyntaxes.html#boolean-attributes
text: content attributes; url: dom.html#concept-element-attributes
text: update the image data; url: images.html#update-the-image-data
text: create navigation params by fetching; url: browsing-the-web.html#create-navigation-params-by-fetchin
text: serialization; for: origin; url: browsers.html#ascii-serialisation-of-an-origin
spec: url; urlPrefix: https://url.spec.whatwg.org/
type: dfn
text: URL; for: /; url: concept-url
spec: dom; urlPrefix: https://dom.spec.whatwg.org/
type: dfn
text: origin; for: document; url: concept-document-origin
spec: infra; urlPrefix: https://infra.spec.whatwg.org
type: dfn
text: empty; for: map; url: map-is-empty
text: ASCII; url: ascii-code-point
spec: webidl; urlPrefix: https://webidl.spec.whatwg.org
type: dfn
text: Web IDL Standard; url: introduction
text: async iterator; url: idl-async-iterable
text: promise; url: idl-promise
text: promise rejected; url: a-promise-rejected-with
text: convert; for: ecmascript-to-idl; url: dfn-convert-ecmascript-to-idl-value
spec: storage; urlPrefix: https://storage.spec.whatwg.org/
type: dfn
text: storage model; url: model
text: storage type; url: storage-type
text: storage identifier; url: storage-identifier
text: storage shed; url: storage-shed
text: storage shelf; url: storage-shelf
text: storage bucket; url: storage-bucket
text: storage bottle; url: storage-bottle
text: quota; for: storage bottle; url: storage-bottle-quota
text: register; for: storage endpoint; url: registered-storage-endpoints
text: quota; for: storage endpoint; url: storage-endpoint-quota
text: bucket map; url: bucket-map
text: bottle map; url: bottle-map
text: storage proxy map; url: storage-proxy-map
text: backing map; url: storage-proxy-map-backing-map
text: proxy map reference set; url: storage-bottle-proxy-map-reference-set
spec: beacon; urlPrefix: https://w3c.github.io/beacon/
type: dfn
text: beacon; url: beacon
spec: ecma; urlPrefix: https://tc39.es/ecma262/
type: dfn
text: call; url: sec-call
text: current realm; url: current-realm
text: casting; url: sec-touint32
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/
type: dfn
text: client-side storage partitioning
spec: fetch; urlPrefix: https://fetch.spec.whatwg.org/
type: dfn
text: http network or cache fetch; url: concept-http-network-or-cache-fetch
text: request-constructor; url: dom-request
spec: permissions-policy; urlPrefix: https://www.w3.org/TR/permissions-policy/
type: dfn
text: Is feature enabled in document for origin?; url: algo-is-feature-enabled
spec: attestation; urlPrefix: https://github.com/privacysandbox/attestation
type: dfn
text: enrolled
spec: private-aggregation-api; urlPrefix: https://github.com/patcg-individual-drafts/private-aggregation-api/blob/main/README.md
type: dfn
text: private aggregation
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
text: eventData; for: FenceEvent; url: dom-fenceevent-eventdata
text: eventType; for: FenceEvent; url: dom-fenceevent-eventtype
type: interface
text: FencedFrameConfig; url: fencedframeconfig
spec: rfc8941; urlPrefix: https://httpwg.org/specs/rfc8941.html
type: dfn
text: structured header; url: specify
text: list; for: structured header; url: list
text: parameters; for: structured header; url: param
text: item; for: structured header; url: item
text: string; for: structured header; url: string
text: token; for: structured header; url: token
text: byte sequence; for: structured header; url: binary
text: boolean; for: structured header; url: boolean
text: inner list; for: structured header; url: inner-list
text: bare item; for: structured header; url: item
spec: wikipedia-entropy; urlPrefix: https://en.wikipedia.org/wiki/Entropy_(information_theory)
type: dfn
text: bits of entropy
text: entropy bits
spec: shared-storage-explainer; urlPrefix: https://github.com/WICG/shared-storage
type:dfn
text: legitimate use cases; url: example-scenarios
</pre>
<style>
/* adapted from .XXX at https://resources.whatwg.org/standard.css */
.todo {
color: #D50606;
background: white;
border: solid #D50606;
}
span.todo {
padding-top: 0;
padding-bottom: 0;
}
.todo::before { content: 'TODO: '; }
span.todo::before {
left: 0;
top: -0.25em;
}
</style>
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-level traversable=] site; see [=Client-Side Storage Partitioning=]. But, there are many [=legitimate use cases=] currently relying on unpartitioned storage.
This document introduces a new storage API that is intentionally not partitioned by [=top-level traversable=] site (though still partitioned by context origin), in order to serve a number of the use cases needing unpartitioned storage. To limit cross-site reidentification of users, data in Shared Storage may only be read in a restricted environment, called a worklet, and any output from the worklet is in the form of a [=fenced frame=] or a [=private aggregation|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.
Inside an `a.example` iframe:
<pre class="lang-js">
function generateSeed() { … }
await window.sharedStorage.worklet.addModule('experiment.js');
// Only write a cross-site seed to a.example's storage if there isn't one yet.
window.sharedStorage.set('seed', generateSeed(), { ignoreIfPresent: true });
let fencedFrameConfig = await window.sharedStorage.selectURL(
'select-url-for-experiment',
[
{url: "blob:https://a.example/123…", reportingMetadata: {"click": "https://report.example/1..."}},
{url: "blob:https://b.example/abc…", reportingMetadata: {"click": "https://report.example/a..."}},
{url: "blob:https://c.example/789…"}
],
{ data: { name: 'experimentA' } }
);
// Assumes that the fenced frame 'my-fenced-frame' has already been attached.
document.getElementById('my-fenced-frame').config = fencedFrameConfig;
</pre>
inside the `experiment.js` worklet script:
<pre class="lang-js">
class SelectURLOperation {
hash(experimentName, seed) { … }
async run(urls, data) {
const seed = await this.sharedStorage.get('seed');
return hash(data.name, seed) % urls.length;
}
}
register('select-url-for-experiment', SelectURLOperation);
</pre>
</div>
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 {{SharedStorageWorklet/selectURL()|URL-selection}} output gate.
<xmp class='idl'>
typedef (USVString or FencedFrameConfig) SharedStorageResponse;
enum SharedStorageDataOrigin { "context-origin", "script-origin" };
</xmp>
<xmp class='idl'>
[Exposed=(Window)]
interface SharedStorageWorklet : Worklet {
Promise<SharedStorageResponse> selectURL(DOMString name,
sequence<SharedStorageUrlWithMetadata> urls,
optional SharedStorageRunOperationMethodOptions options = {});
Promise<any> run(DOMString name,
optional SharedStorageRunOperationMethodOptions options = {});
};
</xmp>
Each {{SharedStorageWorklet}} has an associated boolean <dfn for="SharedStorageWorklet">addModule initiated</dfn>, initialized to false.
Each {{SharedStorageWorklet}} has an associated {{SharedStorageDataOrigin}} <dfn for="SharedStorageWorklet">data origin</dfn>, initialized to `"context-origin"`.
Each {{SharedStorageWorklet}} has an associated boolean <dfn for="SharedStorageWorklet">has cross-origin data origin</dfn>, 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/has cross-origin data origin=] is false, it will cause {{Worklet/addModule()}} to fail, as detailed in the [[#add-module-monkey-patch]].
<div algorithm>
To <dfn>check if user preference setting allows access to shared storage</dfn> 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.
</div>
<div algorithm>
To <dfn>determine whether shared storage is allowed by context</dfn>, given an [=environment settings object=] |environment|, an [=/origin=] |origin|, and a boolean |allowedInOpaqueOriginContext|, run these steps:
1. If |environment| is not a [=secure context=], then return false.
1. If |allowedInOpaqueOriginContext| is false and |environment|'s [=environment settings object/origin=] 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=]: |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. If the result of running [=obtaining a site|obtain a site=] with |origin| is not [=enrolled=], then return false.
1. Return true.
</div>
<div class="note">
Here are the scenarios where the algorithms [=determine whether shared storage is allowed by context=] and [=check if user preference setting allows access to shared storage=] are used:
- 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 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=].
- For [[#ss-fetch-algo]], for {{WindowSharedStorage/createWorklet()}} called with a cross-origin worklet script using the <var ignore=''>dataOrigin</var> option with value `"script-origin"` (which would result in a worklet where [=SharedStorageWorklet/has cross-origin data origin=] is true), and for {{SharedStorageWorklet/selectURL()}} and {{SharedStorageWorklet/run()}} that operate on a worklet where [=SharedStorageWorklet/has cross-origin data origin=] is true, |allowedInOpaqueOriginContext| is true. For other methods, |allowedInOpaqueOriginContext| is false.
</div>
<div algorithm>
To <dfn>check if addModule is allowed and update state</dfn> 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. Let |workletDataOrigin| be the [=current settings object=]'s [=environment settings object/origin=].
1. If |worklet|'s [=SharedStorageWorklet/data origin=] is `"script-origin"`, set |workletDataOrigin| to |moduleURLRecord|'s [=url/origin=].
1. Let |hasCrossOriginDataOrigin| be false.
1. If |workletDataOrigin| and the [=current settings object=]'s [=environment settings object/origin=] are not [=same origin=], then set |hasCrossOriginDataOrigin| to true.
1. Let |allowedInOpaqueOriginContext| be |hasCrossOriginDataOrigin|.
1. If the result of running [=determine whether shared storage is allowed by context=] given the [=current settings object=], |workletDataOrigin|, and |allowedInOpaqueOriginContext| is false, return "DisallowedDueToNonPreferenceError".
1. Set |worklet|'s [=SharedStorageWorklet/has cross-origin data origin=] to |hasCrossOriginDataOrigin|.
1. If the result of running [=check if user preference setting allows access to shared storage=] given the [=current settings object=] and |workletDataOrigin| is false, return "DisallowedDueToPreferenceError".
1. Return "Allowed".
</div>
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}}.
## Run Operation Methods on {{SharedStorageWorklet}} ## {#run-op-shared-storage-worklet}
<div algorithm>
To <dfn>get the select-url result index</dfn>, given {{SharedStorageWorklet}} |worklet|, {{DOMString}} |operationName|, [=/list=] of {{SharedStorageUrlWithMetadata}}s |urlList|, and {{SharedStorageRunOperationMethodOptions}} |options|:
1. Let |promise| be a new [=promise=].
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. Return |promise|, and immediately [=obtain a worklet agent=] given |window| and run the rest of these steps in that agent:
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. [=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 |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: 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. 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.
1. [=Queue a global task=] on the [=DOM manipulation task source=], given |window|, to [=resolve=] |promise| with |index|.
</div>
<div algorithm>
The <dfn method for="SharedStorageWorklet">selectURL(|name|, |urls|, |options|)</dfn> 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 [=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. [=Assert=]: [=this=]'s [=global scopes=]'s [=list/size=] is 1.
1. Let |globalScope| be [=this=]'s [=global scopes=][0].
1. Let |workletDataOrigin| 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 |workletDataOrigin| 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}}.
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}}.
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 |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}}.
1. Let |environment| be |window|'s [=relevant settings object=].
1. Let |allowedInOpaqueOriginContext| be [=this=]'s [=SharedStorageWorklet/has cross-origin data origin=].
1. If the result of running [=determine whether shared storage is allowed by context=] given |environment|, |workletDataOrigin|, and |allowedInOpaqueOriginContext| is false, return a [=promise rejected=] with a {{TypeError}}.
1. If the result of running [=check if user preference setting allows access to shared storage=] given |environment| and |workletDataOrigin| is false:
1. If [=this=]'s [=SharedStorageWorklet/has cross-origin data origin=] is false, return a [=promise rejected=] with a {{TypeError}}.
1. If |options|["`resolveToConfig`"] is true, [=resolve=] |resultPromise| with |pendingConfig|.
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. 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=].
1. Set |finalConfig|'s [=fenced frame config/mapped url=] to |urlList|[|resultIndex|].
1. Set |finalConfig|'s <span class=todo>a "pending shared storage budget debit" field</span> 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. 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|.
</div>
<div algorithm>
The <dfn method for="SharedStorageWorklet">run(|name|, |options|)</dfn> 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 [=this=]'s [=relevant settings object=].
1. [=Assert=]: |window| is a {{Window}}.
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. Let |workletDataOrigin| be |globalScope|'s [=global object/realm=]'s [=realm/settings object=]'s [=environment settings object/origin=].
1. Let |allowedInOpaqueOriginContext| be [=this=]'s [=SharedStorageWorklet/has cross-origin data origin=].
1. If the result of running [=determine whether shared storage is allowed by context=] given |window|, |workletDataOrigin|, and |allowedInOpaqueOriginContext| is false, [=reject=] |promise| with a {{TypeError}}.
1. If the result of running [=check if user preference setting allows access to shared storage=] given |window| and |workletDataOrigin| is false:
1. If [=this=]'s [=SharedStorageWorklet/has cross-origin data origin=] 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 |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=].
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|.
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=].
</div>
## Monkey Patch for [=Worklets=] ## {#worklet-monkey-patch}
This specification will make some modifications to the [=Worklet=] standard to accommodate the needs of Shared Storage.
### 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:
......
<b>The [=environment settings object/origin=]</b>
1. Let |workletGlobalScope| be the [=global object=] of <var ignore=''>realmExecutionContext</var>'s Realm component.
1. If |workletGlobalScope| is not {{SharedStorageWorkletGlobalScope}}, return |origin|.
1. [=Assert=] that |worklet| is a {{SharedStorageWorklet}}.
1. If |worklet|'s [=SharedStorageWorklet/data origin=] is `"context-origin"`, return <var ignore=''>outsideSettings</var>'s [=environment settings object/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 [=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 [=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/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 [=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.
1. If |fetchClient|'s [=environment settings object/origin=] and |settingsObject|'s [=environment settings object/origin=] are not [=same origin=]:
1. Let |dataOriginValue| be the [=origin/serialization=] of |settingsObject|'s [=environment settings object/origin=].
1. [=Assert=] that |dataOriginValue| is not null.
1. [=header list/Append=] the [=header=] (`"Sec-Shared-Storage-Data-Origin"`, |dataOriginValue|) to |request|'s [=request/header list=].
<h4 id="shared-storage-cross-origin-worklet-allowed">The \`<dfn export http-header><code>Shared-Storage-Cross-Origin-Worklet-Allowed</code></dfn>\` HTTP response header</h4>
The \`<a http-header><code>Shared-Storage-Cross-Origin-Worklet-Allowed</code></a>\` HTTP response header, along with the traditional CORS headers, can be used to grant a cross-origin site the permission to create a worklet from the module script's [=/URL=]'s [=url/origin=], and to run subsequent operations on the worklet using the module script's [=/URL=]'s [=url/origin=] as the <dfn for="SharedStorage">data partition origin</dfn> for accessing shared storage data, i.e. the [=environment settings object/origin=] set in [[#set-up-a-worklet-environment-settings-object-monkey-patch]], which becomes the [=url/origin=] used in all {{WorkletSharedStorage}} calls to [=obtain a shared storage bottle map=].
Worklets that load cross-origin scripts rely on CORS as a baseline permission mechanism to indicate trusted external origins. However, CORS alone is insufficient for creation of a worklet with cross-origin script whose [=data partition origin=] is the script origin. Unlike simple resource sharing, worklets allow the creator site to execute JavaScript within the context of the target origin. To ensure security, an additional response header, \`<a http-header><code>Shared-Storage-Cross-Origin-Worklet-Allowed</code></a>\`, is required from the script origin.
### 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. "6. If |internalResponse|'s status is a redirect status, ..."):
6. If |request|'s [=request/destination=] is "sharedstorageworklet":
1. Let |dataOriginValue| be the result of [=header list/getting=] `"Sec-Shared-Storage-Data-Origin"` from |request|'s [=request/header list=].
1. If |dataOriginValue| is not null, then:
1. Let |dataOriginUrl| be the result of running a [=URL parser=] on |dataOriginValue|.
1. [=Assert=] that |dataOriginUrl| is not failure.
1. [=Assert=] that |request|'s [=request/origin=] is not "<code>client</code>".
1. [=Assert=] that |request|'s [=request/origin=] and |request|'s [=request/URL=]'s [=url/origin=] are not [=same origin=].
1. [=Assert=] that |dataOriginUrl|'s [=url/origin=] and |request|'s [=request/URL=]'s [=url/origin=] are [=same origin=].
1. Let |responseHeaders| be |internalResponse|'s [=response/header list=].
1. Let |allowed| be the result of running [=get a structured field value=] algorithm given \`<a http-header><code>Shared-Storage-Cross-Origin-Worklet-Allowed</code></a>\`, "item", and |responseHeaders| as input.
1. If |allowed| is false, then return a [=network error=].
Note: It is the responsibility of the site serving the module script to carefully consider the security implications: when the module script's [=/URL=]'s [=url/origin=] and the worklet's creator {{Window}} origin are not [=same origin=], by sending permissive CORS headers the \`<a http-header><code>Shared-Storage-Cross-Origin-Worklet-Allowed</code></a>\` header on the module script response, the server will be granting the worklet's creation and subsequent operations on the worklet, while allowing the worklet to use the worklet's script's [=url/origin=] as the [=url/origin=] for accessing the shared storage data, i.e. the [=data partition origin=]. For example, the worklet's creator {{Window}} could poison and use up the worklet origin's [=remaining navigation budget=] by calling {{SharedStorageWorklet/selectURL()}} or {{SharedStorageWorklet/run()}}, where the worklet origin is the global scope's [=global object/realm=]'s [=realm/settings object=]'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":
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 <var ignore=''>moduleURLRecord</var>.
1. If |addModuleAllowedResult| is "DisallowedDueToNonPreferenceError":
1. Return [=a promise rejected with=] a {{TypeError}}.
1. Else if |addModuleAllowedResult| is "DisallowedDueToPreferenceError":
1. If |this|'s [=SharedStorageWorklet/has cross-origin data origin=] is false, then return [=a promise rejected with=] a {{TypeError}}.
1. Else:
1. [=Assert=]: |addModuleAllowedResult| is "Allowed".
<div class="note">
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()}}.
</div>
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|.
<span class=todo>Add additional monkey patch pieces for out-of-process worklets.</span>
## The {{SharedStorageWorkletGlobalScope}} ## {#global-scope}
The {{SharedStorageWorklet}}'s [=worklet global scope type=] is {{SharedStorageWorkletGlobalScope}}.
The {{SharedStorageWorklet}}'s [=worklet destination type=] is "sharedstorageworklet".
### Monkey Patch for request [=request/destination=] ### {#request-destination-monkey-patch}
The fetch request's [=request/destination=] field should additionally include "sharedstorageworklet" as a valid value.
<xmp class='idl'>
callback RunFunctionForSharedStorageSelectURLOperation = Promise<unsigned long>(sequence<USVString> urls, optional any data);
</xmp>
<xmp class='idl'>
[Exposed=SharedStorageWorklet, Global=SharedStorageWorklet]
interface SharedStorageWorkletGlobalScope : WorkletGlobalScope {
undefined register(DOMString name,
Function operationCtor);
readonly attribute WorkletSharedStorage sharedStorage;
};
</xmp>
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 [=/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=].
Each {{SharedStorageWorkletGlobalScope}} also has an associated {{WorkletSharedStorage}} instance, with the [=SharedStorageWorkletGlobalScope/sharedStorage getter=] algorithm as described below.
## {{SharedStorageWorkletGlobalScope}} algorithms ## {#scope-algo}
<div algorithm>
The <dfn method for="SharedStorageWorkletGlobalScope">register(|name|, |operationCtor|)</dfn> method steps are:
1. If |name| is missing or empty, throw a {{TypeError}}.
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. 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 [=ecmascript-to-idl/converting=] |runFunction| to a Web IDL {{RunFunctionForSharedStorageSelectURLOperation}} instance.
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:
1. If [=this=]'s [=addModule success=] is true, return [=this=]'s {{SharedStorageWorkletGlobalScope/sharedStorage}}.
1. Otherwise, throw a {{TypeError}}.
</div>
<div algorithm>
To <dfn for="SharedStorageWorkletGlobalScope">check whether addModule is finished</dfn>, the step is:
1. Return the value of [=addModule success=].
</div>
## {{SharedStorageUrlWithMetadata}} and Reporting ## {#reporting}
<xmp class='idl'>
dictionary SharedStorageUrlWithMetadata {
required USVString url;
object reportingMetadata;
};
</xmp>
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}}.
<div algorithm>
To <dfn>validate reporting metadata</dfn>, given an {{/object}} |reportingMetadata|, run the following steps:
1. If |reportingMetadata| is not a [=dictionary=], return false.
1. If |reportingMetadata| is [=map/empty=], return true.
1. [=map/iterate|For each=] <var ignore="">eventType</var> → |urlString| of |reportingMetadata|, if the result of running [=get the canonical URL string if valid=] with |urlString| is undefined, return false.
1. Return true.
</div>
<div algorithm>
To <dfn>get the canonical URL string if valid</dfn>, 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|.
</div>
<div algorithm>
To <dfn>register reporting metadata</dfn>, 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. 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|.
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]].
</div>
## 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 [=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=].
A <dfn>navigation entropy allowance</dfn> 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=] <dfn>navigation budget lifetime</dfn>.
An <dfn>navigation budget epoch</dfn> 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 <dfn>shared storage navigation budget table</dfn>, which is a [=map=] of [=calling sites=] to [=navigation entropy ledgers=].
An <dfn>navigation entropy ledger</dfn> is a [=/list=] of [=bit debits=].
A <dfn>bit debit</dfn> is a [=struct=] with the following [=struct/items=]:
<dl dfn-for="bit debit">
: <dfn>bits</dfn>
:: a double
: <dfn>timestamp</dfn>
:: a {{DOMHighResTimeStamp}} (from the [=Unix Epoch=])
</dl>
[=Bit debits=] whose [=bit debit/timestamps=] precede the start of the current [=navigation budget epoch=] are said to be <dfn for="bit debit">expired</dfn>.
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 <dfn for="calling site">remaining navigation budget</dfn> 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 <dfn for=selectURL>input URL list</dfn>.
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 <dfn>default index</dfn> 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.
<div class="example">
The [=default index=] could be defined to be 0.
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=].
</div>
<div class="example">
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=].
</div>
<div algorithm>
To <dfn>determine remaining navigation budget</dfn>, given an [=environment settings object=] |environment| and a [=calling site=] |site|, run the following steps:
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|].
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| − |debitSum|.
</div>
<div algorithm>
To <dfn>check whether a bit debit is expired</dfn>, 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| − |epochLength|.
1. If |bitDebit|'s [=bit debit/timestamp=] is less than |threshold|, return true.
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-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 <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.
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(138): Need to find a better way to specify timing of the navigation budget charging.
Issue(149): 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. Fix the names when they are added.
<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:
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. Let |instance| be |currentNavigable|'s [=Node/node document=]'s [=Document/browsing context=]'s [=browsing context/fenced frame config instance=].
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.
</div>
### Reporting Entropy Budget ### {#report-budget}
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 <dfn>page load</dfn> referring to a [=top-level traversable=]'s (i.e. primary main frame's) lifecycle.
A <dfn>reporting entropy allowance</dfn> 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}} <dfn>shared storage reporting budget</dfn> associated to it which will be initialized with the value of [=user agent=]'s [=reporting entropy allowance=] upon [=top-level traversable=]'s creation.
When {{reportEvent()}} is called with a {{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]].
<div algorithm>
To <dfn>determine reporting budget to charge</dfn>, 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 [=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|.
</div>
Issue(149): 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. Fix the names when they are added.
<div algorithm>
To <dfn>charge shared storage reporting budget</dfn> 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 |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 |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 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.
</div>
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.
<div algorithm>
To <dfn>purge expired bit debits from all navigation entropy ledgers</dfn>, run the following steps:
1. [=map/iterate|For each=] <var ignore="">origin</var> → |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|.
</div>
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=].
## Monkey Patch for the [=Storage Model=] ## {#storage-monkey-patch}
This standard will add a new [=storage type=] "`shared`" to the [=Storage Model=].
A [=user agent=] holds a [=shared storage shed=] for [=storage endpoints=] of [=storage type|type=] "`shared`".
This standard will also [=storage endpoint/register=] a [=storage endpoint=] of [=storage type|type=] "`shared`" with [=storage identifier=] "`sharedStorage`" and [=storage endpoint/quota=] `5`<sup>`4`</sup> `*` `2`<sup>`16`</sup> bytes (i.e. 39.0625 mebibytes).
<span class=todo>This [=storage endpoint/quota=] is calculated from the current implementation. Consider bringing the current implementation in line with the spec for [=storage endpoints=] "`localStorage`" and "`sessionStorage`", i.e. `5 * 2`<sup>`20`</sup> bytes. For example, decreasing the per-origin entry limit from 10,000 to 1,280 would achieve this.</span>
A <dfn>shared storage shed</dfn> is a [=map=] of [=/origins=] to [=storage shelf|storage shelves=]. It is initially empty.
Note: Unlike [=storage sheds=], whose keys are [=storage keys=], [=shared storage sheds=] use [=/origins=] as keys directly. [=Shared storage=] will be intentionally excluded from [=client-side storage partitioning=].
For each [=storage shelf=] in a [=shared storage shed=], the [=storage shelf=]'s [=bucket map=] currently has only a single key of "`default`".
A [=user agent=]'s [=shared storage shed=] holds all <dfn>shared storage</dfn> data.
<div algorithm>
To <dfn>obtain a shared storage shelf</dfn>, given a [=shared storage shed=] |shed|, an [=environment settings object=] |environment|, and an [=/origin=] |origin|, run these steps:
1. Let |allowedInOpaqueOriginContext| be false.
1. If the result of running [=determine whether shared storage is allowed by context=] given |environment|, |origin|, and |allowedInOpaqueOriginContext| is false, then return failure.
1. If the result of running [=check if user preference setting allows access to shared storage=] 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|].
</div>
<div algorithm>
To <dfn>create a shared storage shelf</dfn>, run these steps:
1. Let |shelf| be a new [=storage shelf=].
1. Set |shelf|'s [=bucket map=]["`default`"] to the result of running [=create a shared storage bucket=].
1. Return |shelf|.
</div>
A <dfn>shared storage bucket</dfn> is a [=storage bucket=] in one of a [=shared storage shed=]'s [=storage shelf|shelves=].
<div algorithm>
To <dfn>create a shared storage bucket</dfn>, run these steps:
1. Let |endpoint| be the [=storage endpoint=] with [=storage identifier=] "`sharedStorage`".
1. Let |bucket| be a new [=shared storage bucket=].
1. Set |bucket|'s [=bottle map=]["`sharedStorage`"] to a new [=storage bottle=] whose [=storage bottle/quota=] is |endpoint|'s [=storage endpoint/quota=].
1. Return |bucket|.
</div>
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`".
<div algorithm>
To <dfn>obtain a shared storage bottle map</dfn>, 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|.
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`"].
1. Let |proxyMap| be a new [=storage proxy map=] whose [=backing map=] is |bottle|'s [=map=].
1. [=set/Append=] |proxyMap| to |bottle|'s [=proxy map reference set=].
1. Return |proxyMap|.
</div>
## The [=Shared Storage Database=] ## {#database}
A [=/browsing context=] has an associated <dfn>shared storage database</dfn>, which provides methods to [=shared storage database/store an entry in the database|store=], [=shared storage database/retrieve an entry from the database|retrieve=], [=shared storage database/delete an entry from the database|delete=], [=shared storage database/clear all entries in the database|clear=], and [=shared storage database/purge expired entries from the database|purge expired=] data, and additional methods as below. The data in the [=shared storage database|database=] take the form of [=shared storage database/entry|entries=].
Each [=shared storage database=] has a <dfn for="shared storage database">shared storage database queue</dfn>, which is the result of [=start a new parallel queue|starting a new parallel queue=]. This [=shared storage database queue|queue=] is used to run each of the [=shared storage database=]'s methods when calls to them are initiated from that [=/browsing context=].
Each <dfn for="shared storage database">entry</dfn> consists of a [=entry/key=] and a [=entry/value struct=].
An [=shared storage database/entry=]'s <dfn for=entry>key</dfn> is a [=string=].
[=User agents=] may specify the <dfn for=key>maximum length</dfn> of a [=entry/key=].
Since [=entry/keys=] are used to organize and efficiently retrieve [=shared storage database/entry|entries=], [=entry/keys=] must appear at most once in any given [=shared storage database=].
An [=shared storage database/entry=]'s <dfn for=entry>value struct</dfn> is a [=struct=] composed of [=string=] <dfn for="value struct">value</dfn> and {{DOMHighResTimeStamp}} <dfn for="value struct">last updated</dfn> (from the [=Unix Epoch=]).
[=User agents=] may specify the <dfn for=value>maximum length</dfn> of a [=value struct/value=].
[=User agents=] may specify a <dfn>default entry lifetime</dfn>, the default [=duration=] between when an [=shared storage database/entry=] is [=shared storage database/store an entry in the database|stored=] and when it expires. If the [=user agent=] specifies a [=default entry lifetime=], then it should have a timer periodically [=shared storage database/purge expired entries from the database=].
## The [=Shared Storage Database|Database=] Algorithms ## {#database-algorithms}
<div algorithm>
To <dfn for="shared storage database">store an entry in the database</dfn>, given a [=shared storage database/shared storage database queue=] |queue|, a [=storage proxy map=] |databaseMap|, an [=environment settings object=] |environment|, a [=entry/key=] |key|, and a [=value struct/value=] |value|, run the following steps on |queue|:
1. Let |valueStruct| be a new [=entry/value struct=].
1. Set |valueStruct|'s [=value struct/value=] to |value|.
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 [=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.
</div>
<div algorithm>
To <dfn for="shared storage database">retrieve an entry from the database</dfn>, given a [=shared storage database/shared storage database queue=] |queue|, a [=storage proxy map=] |databaseMap|, an [=environment settings object=] |environment|, and a [=entry/key=] |key|, run the following steps on |queue|:
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 [=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.
1. Return |valueStruct|'s [=value struct/value=].
</div>
<div algorithm>
To <dfn for="shared storage database">delete an entry from the database</dfn>, 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 [=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.
</div>
<div algorithm>
To <dfn for="shared storage database">clear all entries in the database</dfn>, 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 [=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.
</div>
<div algorithm>
To <dfn for="shared storage database">retrieve all entries from the database</dfn>, 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 [=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|.
</div>
<div algorithm>
To <dfn for="shared storage database">count entries in the database</dfn>, 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 [=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|.
</div>
<div algorithm>
To <dfn for="shared storage database">purge expired entries from the database</dfn>, given a [=shared storage database/shared storage database queue=] |queue|, a [=storage proxy map=] |databaseMap|, and an [=environment settings object=] |environment|, run the following steps on |queue|:
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 [=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 [=an exception was thrown=], then return false.
1. Return true.
</div>
<div algorithm>
To <dfn for="shared storage database">determine whether an entry is expired</dfn>, given an [=environment settings object=] |environment| and a [=entry/value struct=] |valueStruct|, run the following steps:
1. Let |lastUpdated| be |valueStruct|'s [=value struct/last updated=].
1. Let |lifetime| be [=user agent=]'s [=default entry lifetime=].
1. Let |expiration| be the sum of |lastUpdated| and |lifetime|.
1. Let |currentTime| be the |environment|'s [=environment settings object/current wall time=].
1. If |expiration| is less than or equal to |currentTime|, return true.
1. Otherwise, return false.
</div>
The {{SharedStorage}} Interface {#shared-storage-interface}
==========================================================
The {{SharedStorage}} interface is the base for derived interfaces {{WindowSharedStorage}} and {{WorkletSharedStorage}}, which are exposed to the {{Window}} and the {{SharedStorageWorklet}}, respectively.
Methods that allow the setting and/or deleting of data are exposed to both the {{Window}} and the {{SharedStorageWorklet}} and hence are declared in the base {{SharedStorage}} interface, although their implementations may vary depending on their [=environment=]. This makes it possible to modify the data in Shared Storage from multiple contexts.
Meanwhile, methods for posting operations to run inside {{SharedStorageWorkletGlobalScope}} (i.e. {{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=].
<xmp class='idl'>
[Exposed=(Window,SharedStorageWorklet)]
interface SharedStorage {
Promise<any> set(DOMString key,
DOMString value,
optional SharedStorageSetMethodOptions options = {});
Promise<any> append(DOMString key,
DOMString value);
Promise<any> delete(DOMString key);
Promise<any> clear();
};