forked from WICG/file-system-access
-
Notifications
You must be signed in to change notification settings - Fork 0
/
index.bs
1127 lines (868 loc) · 52.1 KB
/
index.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: File System Access
Shortname: file-system-access
Abstract: This document extends the API in [[FS]] to enable developers to build
powerful web apps that interact with files on the user's local device.
It builds on [[FILE-API|File API]] for file reading capabilities, and adds new API
surface to enable modifying files, as well as working with directories.
Status: CG-DRAFT
ED: https://wicg.github.io/file-system-access/
Level: 1
Editor: Austin Sullivan, Google, [email protected], w3cid 126126
Former Editor: Marijn Kruisselbrink, Google, [email protected], w3cid 72440
Group: WICG
Repository: wicg/file-system-access
Indent: 2
Complain About: accidental-2119 yes, missing-example-ids yes
Markup Shorthands: css no, markdown yes
</pre>
<pre class=link-defaults>
spec:webidl; type:dfn; text:resolve
</pre>
<pre class=anchors>
urlPrefix: https://tc39.github.io/ecma262/; spec: ECMA-262
type: dfn; text: realm; url: realm
urlPrefix: https://storage.spec.whatwg.org/; spec: storage
type: dfn; text: storage; url: site-storage
</pre>
<style>
.domintro dt {
font-family: Menlo, Consolas, "DejaVu Sans Mono", Monaco, monospace;
padding-top: 0.5em;
padding-bottom: 1em;
}
.domintro dt a {
color: inherit; border-bottom-style: none;
}
.domintro dt code {
font-size: inherit;
}
.domintro::before {
content: 'For web developers (non-normative)';
text-transform: initial;
}
</style>
# Introduction # {#introduction}
*This section is non-normative.*
This API enables developers to build powerful apps that interact with other
(non-Web) apps on the user's device via the device's file system. Prominent
examples of applications where users expect this functionality are IDEs,
photo and video editors, text editors, and more.
After a user grants a web app access, this API allows the app to read or save
changes directly to files and folders on the user's device. Beyond reading and
writing files, this API provides the ability to open a directory and enumerate
its contents. Additionally, web apps can use this API to store references to
files and directories they've been given access to, allowing the web apps to
later regain access to the same content without requiring the user to select the
same file again.
This API is similar to <a href="https://html.spec.whatwg.org/multipage/input.html#file-upload-state-(type=file)">`<input type=file>`</a>
and <a href="https://wicg.github.io/entries-api/#html-forms">`<input type=file webkitdirectory>`</a>
[[entries-api]]
in that user interaction happens through file and directory picker dialogs.
Unlike those APIs, this API is currently purely a javascript API, and
does not integrate with forms and/or input elements.
This API extends the API in [[FS]], which specifies a [=/bucket file system=]
which websites can get access to without having to first prompt the user for access.
# Files and Directories # {#files-and-directories}
## Concepts ## {#concepts}
A <dfn>valid suffix code point</dfn> is a [=code point=] that is [=ASCII alphanumeric=],
U+002B (+), or U+002E (.).
Note: These code points were chosen to support most pre-existing file formats. The vast
majority of file extensions are purely alphanumeric, but compound extensions (such as
`.tar.gz`) and extensions such as `.c++` for C++ source code are also fairly common,
hence the inclusion of + and . as allowed code points.
## Permissions ## {#permissions}
The <dfn for=PermissionName enum-value>"file-system"</dfn> [=powerful feature=]'s
permission-related algorithms and types are defined as follows:
: [=permission descriptor type=]
:: {{FileSystemPermissionDescriptor}}, defined as:
<xmp class=idl>
enum FileSystemPermissionMode {
"read",
"readwrite"
};
dictionary FileSystemPermissionDescriptor : PermissionDescriptor {
required FileSystemHandle handle;
FileSystemPermissionMode mode = "read";
};
</xmp>
: [=permission state constraints=]
:: <div algorithm="permission state constraints">
To determine [=permission state constraints=] for a {{FileSystemPermissionDescriptor}} |desc|,
run these steps:
1. Let |entry| be |desc|["{{FileSystemPermissionDescriptor/handle}}"]'s
[=FileSystemHandle/entry=].
1. If |entry| represents a [=/file system entry=] in a [=/bucket file system=],
this descriptor's [=permission state=] must always be
"{{PermissionState/granted}}".
1. Otherwise, if |entry|'s [=file system entry/parent=] is not null, this descriptor's [=permission state=] must be
equal to the [=permission state=] for a descriptor with the same {{FileSystemPermissionDescriptor/mode}},
and a {{FileSystemPermissionDescriptor/handle}} representing |entry|'s [=file system entry/parent=].
1. Otherwise, if |desc|["{{FileSystemPermissionDescriptor/mode}}"] is
"{{FileSystemPermissionMode/readwrite}}":
1. Let |read state| be the [=permission state=] for a descriptor
with the same {{FileSystemPermissionDescriptor/handle}},
but whose {{FileSystemPermissionDescriptor/mode}} is
"{{FileSystemPermissionMode/read}}".
1. If |read state| is not "{{PermissionState/granted}}", this descriptor's [=permission state=]
must be equal to |read state|.
Issue(whatwg/fs#101): Make these checks no longer associated with an entry.
: [=permission request algorithm=]
:: <div algorithm="permission request algorithm">
Given a {{FileSystemPermissionDescriptor}} |desc| and a {{PermissionStatus}} |status|,
run these steps:
1. Run the [=default permission query algorithm=] on |desc| and |status|.
1. If |status|'s {{PermissionStatus/state}} is not
"{{PermissionState/prompt}}", then abort these steps.
1. Let |settings| be |desc|["{{FileSystemPermissionDescriptor/handle}}"]'s
[=relevant settings object=].
1. Let |global| be |settings|'s [=environment settings object/global object=].
1. If |global| is not a {{Window}}, then
[=throw=] a "{{SecurityError}}" {{DOMException}}.
1. If |global| does not have [=transient activation=], then
[=throw=] a "{{SecurityError}}" {{DOMException}}.
1. If |settings|'s [=environment settings object/origin=]
is not [=same origin=] with |settings|'s [=top-level origin=], then
[=throw=] a "{{SecurityError}}" {{DOMException}}.
1. [=Request permission to use=] |desc|.
1. Run the [=default permission query algorithm=] on |desc| and |status|.
Issue(WICG/permissions-request#2): Ideally this user activation requirement would be defined upstream.
<div algorithm>
To <dfn lt="querying file system permission|query file system permission">query file system permission</dfn>
given a {{FileSystemHandle}} |handle| and a {{FileSystemPermissionMode}} |mode|, run these steps:
1. Let |desc| be a {{FileSystemPermissionDescriptor}}.
1. Set |desc|["{{PermissionDescriptor/name}}"] to
"{{PermissionName/file-system}}".
1. Set |desc|["{{FileSystemPermissionDescriptor/handle}}"] to |handle|.
1. Set |desc|["{{FileSystemPermissionDescriptor/mode}}"] to |mode|.
1. Return |desc|'s [=permission state=].
</div>
<div algorithm>
To <dfn lt="requesting file system permission|request file system permission">request file system permission</dfn>
given a {{FileSystemHandle}} |handle| and a {{FileSystemPermissionMode}} |mode|, run these steps:
1. Let |desc| be a {{FileSystemPermissionDescriptor}}.
1. Set |desc|["{{PermissionDescriptor/name}}"] to
"{{PermissionName/file-system}}".
1. Set |desc|["{{FileSystemPermissionDescriptor/handle}}"] to |handle|.
1. Set |desc|["{{FileSystemPermissionDescriptor/mode}}"] to |mode|.
1. Let |status| be the result of running <a spec=permissions>create a PermissionStatus</a> for |desc|.
1. Run the [=permission request algorithm=] for the
"{{PermissionName/file-system}}" feature, given |desc| and |status|.
1. Return |desc|'s [=permission state=].
</div>
Issue(119): Currently {{FileSystemPermissionMode}} can only be
"{{FileSystemPermissionMode/read}}" or "{{FileSystemPermissionMode/readwrite}}".
In the future we might want to add a "write" mode as well to support write-only
handles.
## The {{FileSystemHandle}} interface ## {#api-filesystemhandle}
<xmp class=idl>
dictionary FileSystemHandlePermissionDescriptor {
FileSystemPermissionMode mode = "read";
};
[Exposed=(Window,Worker), SecureContext, Serializable]
partial interface FileSystemHandle {
Promise<PermissionState> queryPermission(optional FileSystemHandlePermissionDescriptor descriptor = {});
Promise<PermissionState> requestPermission(optional FileSystemHandlePermissionDescriptor descriptor = {});
};
</xmp>
### The {{FileSystemHandle/queryPermission()}} method ### {#api-filesystemhandle-querypermission}
<div class="note domintro">
: |status| = await |handle| . {{FileSystemHandle/queryPermission()|queryPermission}}({ {{FileSystemHandlePermissionDescriptor/mode}} : "{{FileSystemPermissionMode/read}}" })
: |status| = await |handle| . {{FileSystemHandle/queryPermission()}}
: |status| = (await navigator.{{Navigator/permissions}}.{{Permissions/query()|query}}({ {{PermissionDescriptor/name}} : "{{PermissionName/file-system}}", {{FileSystemPermissionDescriptor/handle}} : |handle| })).{{PermissionStatus/state}}
:: Queries the current state of the read permission of this handle.
If this returns "{{PermissionState/prompt}}" the website will have to call
{{FileSystemHandle/requestPermission()}} before any operations on the
handle can be done.
If this returns "{{PermissionState/denied}}" any operations will reject.
Usually handles returned by the [=local file system handle factories=] will
initially return "{{PermissionState/granted}}" for their read permission
state, however other than through the user revoking permission, a handle
retrieved from IndexedDB is also likely to return
"{{PermissionState/prompt}}".
: |status| = await |handle| . {{FileSystemHandle/queryPermission()|queryPermission}}({ {{FileSystemHandlePermissionDescriptor/mode}} : "{{FileSystemPermissionMode/readwrite}}" })
: |status| = (await navigator.{{Navigator/permissions}}.{{Permissions/query()|query}}({ {{PermissionDescriptor/name}} : "{{PermissionName/file-system}}", {{FileSystemPermissionDescriptor/handle}} : |handle|, {{FileSystemPermissionDescriptor/mode}} : "{{FileSystemPermissionMode/readwrite}}" }).{{PermissionStatus/state}}
:: Queries the current state of the write permission of this handle.
If this returns "{{PermissionState/prompt}}", attempting to modify the
file or directory this handle represents will require user activation
and will result in a confirmation prompt being shown to the user.
However if the state of the read permission of this handle is also
"{{PermissionState/prompt}}" the website will need to call
{{FileSystemHandle/requestPermission()}}.
There is no automatic prompting for read access when attempting to
read from a file or directory.
</div>
Advisement: The integration with the permissions API's {{Permissions/query()}} method is not yet implemented in Chrome.
<div algorithm>
The <dfn method for=FileSystemHandle>queryPermission(|descriptor|)</dfn> method, when invoked, must run these steps:
1. Let |result| be [=a new promise=].
1. Run the following steps [=in parallel=]:
1. Let |state| be the result of [=querying file system permission=]
given <b>[=this=]</b> and
|descriptor|["{{FileSystemHandlePermissionDescriptor/mode}}"].
1. [=/Resolve=] |result| with |state|.
1. Return |result|.
</div>
### The {{FileSystemHandle/requestPermission()}} method ### {#api-filesystemhandle-requestpermission}
<div class="note domintro">
: |status| = await |handle| . {{FileSystemHandle/requestPermission()|requestPermission}}({ {{FileSystemHandlePermissionDescriptor/mode}} : "{{FileSystemPermissionMode/read}}" })
: |status| = await |handle| . {{FileSystemHandle/requestPermission()}}
:: If the state of the read permission of this handle is anything other than
"{{PermissionState/prompt}}", this will return that state directly.
If it is "{{PermissionState/prompt}}" however, user activation is needed and
this will show a confirmation prompt to the user.
The new read permission state is then returned, depending on
the user's response to the prompt.
: |status| = await |handle| . {{FileSystemHandle/requestPermission()|requestPermission}}({ {{FileSystemHandlePermissionDescriptor/mode}} : "{{FileSystemPermissionMode/readwrite}}" })
:: If the state of the write permission of this handle is anything other than
"{{PermissionState/prompt}}", this will return that state directly.
If the status of the read permission of this handle is
"{{PermissionState/denied}}" this will return that.
Otherwise the state of the write permission is "{{PermissionState/prompt}}"
and this will show a confirmation prompt to the user.
The new write permission state is then returned, depending on
what the user selected.
</div>
<div algorithm>
The <dfn method for=FileSystemHandle>requestPermission(|descriptor|)</dfn> method, when invoked, must run these steps:
1. Let |result| be [=a new promise=].
1. Run the following steps [=in parallel=]:
1. Let |state| be the result of [=requesting file system permission=]
given <b>[=this=]</b> and
|descriptor|["{{FileSystemHandlePermissionDescriptor/mode}}"].
If that throws an exception, [=/reject=] |result| with that exception and abort.
1. [=/Resolve=] |result| with |state|.
1. Return |result|.
</div>
# Accessing Local File System # {#local-filesystem}
<xmp class=idl>
enum WellKnownDirectory {
"desktop",
"documents",
"downloads",
"music",
"pictures",
"videos",
};
typedef (WellKnownDirectory or FileSystemHandle) StartInDirectory;
dictionary FilePickerAcceptType {
USVString description = "";
record<USVString, (USVString or sequence<USVString>)> accept;
};
dictionary FilePickerOptions {
sequence<FilePickerAcceptType> types;
boolean excludeAcceptAllOption = false;
DOMString id;
StartInDirectory startIn;
};
dictionary OpenFilePickerOptions : FilePickerOptions {
boolean multiple = false;
};
dictionary SaveFilePickerOptions : FilePickerOptions {
USVString? suggestedName;
};
dictionary DirectoryPickerOptions {
DOMString id;
StartInDirectory startIn;
FileSystemPermissionMode mode = "read";
};
[SecureContext]
partial interface Window {
Promise<sequence<FileSystemFileHandle>> showOpenFilePicker(optional OpenFilePickerOptions options = {});
Promise<FileSystemFileHandle> showSaveFilePicker(optional SaveFilePickerOptions options = {});
Promise<FileSystemDirectoryHandle> showDirectoryPicker(optional DirectoryPickerOptions options = {});
};
</xmp>
The {{showOpenFilePicker()}}, {{showSaveFilePicker()}} and {{showDirectoryPicker()}} methods
are together known as the <dfn>local file system handle factories</dfn>.
Note: What is referred to as the "local file system" in this spec, does not have to
strictly refer to the file system on the local device. What we call the local file system
could just as well be backed by a cloud provider. For example on Chrome OS these
file pickers will also let you pick files and directories on Google Drive.
Advisement: In Chrome versions earlier than 85, this was implemented as a generic `chooseFileSystemEntries` method.
## Local File System Permissions ## {#local-file-system-permissions}
The fact that the user picked the specific files returned by the [=local file system handle factories=] in a prompt
should be treated by the user agent as the user intending to grant read access to the website
for the returned files. As such, at the time the promise returned by one of the [=local file system handle factories=]
resolves, [=permission state=] for a descriptor with {{FileSystemPermissionDescriptor/handle}} set to the returned handle,
and {{FileSystemPermissionDescriptor/mode}} set to "{{FileSystemPermissionMode/read}}"
should be "{{PermissionState/granted}}".
Additionally for calls to {{showSaveFilePicker}}
the [=permission state=] for a descriptor with {{FileSystemPermissionDescriptor/handle}} set to the returned handle,
and {{FileSystemPermissionDescriptor/mode}} set to "{{FileSystemPermissionMode/readwrite}}"
should be "{{PermissionState/granted}}".
<div algorithm>
To verify that an |environment| <dfn>is allowed to show a file picker</dfn>, run these steps:
1. If |environment|'s [=environment settings object/origin=] is an [=opaque origin=],
return [=a promise rejected with=] a "{{SecurityError}}" {{DOMException}}.
1. If |environment|'s [=environment settings object/origin=] is not [=same origin=] with
|environment|'s [=top-level origin=],
return [=a promise rejected with=] a "{{SecurityError}}" {{DOMException}}.
1. Let |global| be |environment|'s [=environment settings object/global object=].
1. If |global| does not have [=transient activation=], then
[=throw=] a "{{SecurityError}}" {{DOMException}}.
</div>
## File picker options ## {#api-filepickeroptions}
### Accepted file types ### {#api-filepickeroptions-types}
<div class="note domintro">
The {{showOpenFilePicker(options)}} and {{showSaveFilePicker(options)}} methods accept a
{{FilePickerOptions}} argument, which lets the website specify the types of files
the file picker will let the user select.
Each entry in {{FilePickerOptions/types}} specifies a single user selectable option
for filtering the files displayed in the file picker.
Each option consists of an <span class=allow-2119>optional</span> {{FilePickerAcceptType/description}}
and a number of MIME types and extensions (specified as a mapping of
MIME type to a list of extensions). If no description is provided one will be generated.
Extensions have to be strings that start with a "." and only contain [=valid suffix code points=].
Additionally extensions are limited to a length of 16 code points.
In addition to complete MIME types, "\*" can be used as the subtype of a MIME type to match
for example all image formats with "image/\*".
Websites <span class=allow-2119>should</span> always provide both MIME types and file
extensions for each option. On platforms that only use file extensions to describe file types
user agents can match on the extensions, while on platforms that don't use extensions,
user agents can match on MIME type.
By default the file picker will also include an option to not apply any filter,
letting the user select any file. Set {{excludeAcceptAllOption}} to `true` to not
include this option in the file picker.
For example , the following options will let the user pick one of three different filters.
One for text files (either plain text or HTML), one for images, and a third one that doesn't apply
any filter and lets the user select any file.
<pre class=example id="filepickeroptions-types-example1" highlight=js>
const options = {
<l>{{FilePickerOptions/types}}</l>: [
{
<l>{{FilePickerAcceptType/description}}</l>: 'Text Files',
<l>{{FilePickerAcceptType/accept}}</l>: {
'text/plain': ['.txt', '.text'],
'text/html': ['.html', '.htm']
}
},
{
<l>{{FilePickerAcceptType/description}}</l>: 'Images',
<l>{{FilePickerAcceptType/accept}}</l>: {
'image/*': ['.png', '.gif', '.jpeg', '.jpg']
}
}
],
};
</pre>
On the other hand, the following example will only let the user select SVG files. The dialog
will not show an option to not apply any filters.
<pre class=example id="filepickeroptions-types-example2" highlight=js>
const options = {
<l>{{FilePickerOptions/types}}</l>: [
{
<l>{{FilePickerAcceptType/accept}}</l>: {
'image/svg+xml': '.svg'
}
},
],
<l>{{FilePickerOptions/excludeAcceptAllOption}}</l>: true
};
</pre>
</div>
<div algorithm>
To <dfn>process accept types</dfn>, given {{FilePickerOptions}} |options|,
run these steps:
1. Let |accepts options| be a empty [=/list=] of [=tuples=]
consisting of a description and a filter.
1. [=list/For each=] |type| of |options|["{{FilePickerOptions/types}}"]:
1. [=map/For each=] |typeString| → |suffixes| of
|type|["{{FilePickerAcceptType/accept}}"]:
1. Let |parsedType| be the result of [=parse a MIME type=] with |typeString|.
1. If |parsedType| is failure, then [=throw=] a {{TypeError}}.
1. If |parsedType|'s [=MIME type/parameters=] are not empty, then
[=throw=] a {{TypeError}}.
1. If |suffixes| is a string:
1. [=Validate a suffix=] given |suffixes|.
1. Otherwise, [=list/for each=] |suffix| of |suffixes|:
1. [=Validate a suffix=] given |suffix|.
1. Let |filter| be these steps, given a |filename| (a [=string=]) and a |type| (a [=MIME type=]):
1. [=map/For each=] |typeString| → |suffixes| of
|type|["{{FilePickerAcceptType/accept}}"]:
1. Let |parsedType| be the result of [=parse a MIME type=] with |typeString|.
1. If |parsedType|'s [=MIME type/subtype=] is "*":
1. If |parsedType|'s [=MIME type/type=] is "*", return `true`.
1. If |parsedType|'s [=MIME type/type=] is |type|'s [=MIME type/type=], return `true`.
1. |parsedType|'s [=MIME type/essence=] is |type|'s [=MIME type/essence=], return `true`.
1. If |suffixes| is a string, set |suffixes| to « |suffixes| ».
1. [=list/For each=] |suffix| of |suffixes|:
1. If |filename| ends with |suffix|, return `true`.
1. Return `false`.
1. Let |description| be |type|["{{FilePickerAcceptType/description}}"].
1. If |description| is an empty string,
set |description| to some user understandable string describing |filter|.
1. [=list/Append=] (|description|, |filter|) to |accepts options|.
1. If either |accepts options| is [=list/empty=],
or |options|["{{FilePickerOptions/excludeAcceptAllOption}}"] is `false`:
1. Let |description| be a user understandable string describing "all files".
1. Let |filter| be an algorithm that returns `true`.
1. [=list/Append=] (|description|, |filter|) to |accepts options|.
1. If |accepts options| is empty, then [=throw=] a {{TypeError}}.
1. Return |accepts options|.
</div>
<div algorithm>
To <dfn>validate a suffix</dfn> |suffix|, run the following steps:
1. If |suffix| does not [=string/starts with|start with=] ".", then
[=throw=] a {{TypeError}}.
1. If |suffix| contains any [=code points=] that are not
[=valid suffix code points=], then [=throw=] a {{TypeError}}.
1. If |suffix| ends with ".", then [=throw=] a {{TypeError}}.
1. If |suffix|'s [=string/length=] is more than 16, then
[=throw=] a {{TypeError}}.
</div>
### Starting Directory ### {#api-filepickeroptions-starting-directory}
<div class="note domintro">
The {{FilePickerOptions/id}} and {{FilePickerOptions/startIn}} fields can be specified to suggest
the directory in which the file picker opens.
If neither of these options are specified, the user agent remembers the last directory a file
or directory was picked from, and new pickers will start out in that directory. By specifying an
{{FilePickerOptions/id}} the user agent can remember different directories for different IDs
(user agents will only remember directories for a limited number of IDs).
<pre class=example id="filepickeroptions-starting-directory-example1" highlight=js>
// If a mapping exists from this ID to a previousy picked directory, start in
// this directory. Otherwise, a mapping will be created from this ID to the
// directory of the resulting file picker invocation.
const options = {
<l>{{FilePickerOptions/id}}</l>: 'foo',
};
</pre>
Specifying {{FilePickerOptions/startIn}} as a {{FileSystemFileHandle}} will result in the dialog
starting in the parent directory of that file, while passing in a {{FileSystemDirectoryHandle}}
will result in the dialog to start in the passed in directory. These take precedence even if
an explicit {{FilePickerOptions/id}} is also passed in.
For example, given a {{FileSystemDirectoryHandle}} <var>project_dir</var>, the following will show
a file picker that starts out in that directory:
<pre class=example id="filepickeroptions-starting-directory-example2" highlight=js>
// The picker will open to the directory of |project_dir| regardless of whether
// 'foo' has a valid mapping.
const options = {
<l>{{FilePickerOptions/id}}</l>: 'foo',
<l>{{FilePickerOptions/startIn}}</l>: |project_dir|,
};
</pre>
The {{FilePickerOptions/id}} and {{FilePickerOptions/startIn}} fields control only
the directory the picker opens to. In the above example, it cannot be assumed that
the {{FilePickerOptions/id}} 'foo' will map to the same directory as |project_dir|
once the file picker operation has completed.
Specifying {{FilePickerOptions/startIn}} as a {{WellKnownDirectory}} will result in the dialog
starting in that directory, unless an explicit {{FilePickerOptions/id}} was also passed
in which has a mapping to a valid directory.
Below is an example of specifying both an {{FilePickerOptions/id}} and
{{FilePickerOptions/startIn}} as a {{WellKnownDirectory}}. If there is an existing
mapping from the given ID to a path, this mapping is used. Otherwise, the path suggested
via the {{WellKnownDirectory}} is used.
<pre class=example id="filepickeroptions-starting-directory-example3" highlight=js>
// First time specifying the ID 'foo'. It is not mapped to a directory.
// The file picker will fall back to opening to the Downloads directory. TODO: link this.
const options = {
<l>{{FilePickerOptions/id}}</l>: 'foo', // Unmapped.
<l>{{FilePickerOptions/startIn}}</l>: "<l>{{WellKnownDirectory/downloads}}</l>", // Start here.
};
// Later...
// The ID 'foo' might or might not be mapped. For example, the mapping for this ID
// might have been evicted.
const options = {
<l>{{FilePickerOptions/id}}</l>: 'foo', // Maybe mapped. If so, start here.
<l>{{FilePickerOptions/startIn}}</l>: "<l>{{WellKnownDirectory/downloads}}</l>", // Otherwise, start here.
};
</pre>
</div>
Advisement: The {{FilePickerOptions/startIn}} and {{FilePickerOptions/id}} options were first introduced in Chrome 91.
A user agent holds a <dfn>recently picked directory map</dfn>, which is a
[=map=] of [=/origins=] to [=path id maps=].
A <dfn>path id map</dfn> is a [=map=] of [=valid path ids=] to paths.
A <dfn>valid path id</dfn> is a [=string=] where each character is [=ASCII alphanumeric=] or "_" or "-".
To prevent a [=path id map=] from growing without a bound, user agents should implement
some mechanism to limit how many recently picked directories will be remembered. This
can for example be done by evicting least recently used entries.
User agents should allow at least 16 entries to be stored in a [=path id map=].
The <dfn enum>WellKnownDirectory</dfn> enum lets a website pick one of several well-known
directories. The exact paths the various values of this enum map to is [=implementation-defined=]
(and in some cases these might not even represent actual paths on disk).
The following list describes the meaning of each of the values, and gives possible example paths on different operating systems:
<dl dfn-for=WellKnownDirectory>
: <dfn enum-value>"desktop"</dfn>
:: The user's Desktop directory, if such a thing exists. For example this could be
`C:\Documents and Settings\username\Desktop`, `/Users/username/Desktop`, or `/home/username/Desktop`.
: <dfn enum-value>"documents"</dfn>
:: Directory in which documents created by the user would typically be stored.
For example `C:\Documents and Settings\username\My Documents`, `/Users/username/Documents`, or `/home/username/Documents`.
: <dfn enum-value>"downloads"</dfn>
:: Directory where downloaded files would typically be stored.
For example `C:\Documents and Settings\username\Downloads`, `/Users/username/Downloads`, or `/home/username/Downloads`.
: <dfn enum-value>"music"</dfn>
:: Directory where audio files would typically be stored.
For example `C:\Documents and Settings\username\My Documents\My Music`, `/Users/username/Music`, or `/home/username/Music`.
: <dfn enum-value>"pictures"</dfn>
:: Directory where photos and other still images would typically be stored.
For example `C:\Documents and Settings\username\My Documents\My Pictures`, `/Users/username/Pictures`, or `/home/username/Pictures`.
: <dfn enum-value>"videos"</dfn>
:: Directory where videos/movies would typically be stored.
For example `C:\Documents and Settings\username\My Documents\My Videos`, `/Users/username/Movies`, or `/home/username/Videos`.
<div algorithm>
To <dfn>determine the directory the picker will start in</dfn>, given an optional [=string=] |id|,
an optional {{StartInDirectory}} |startIn| and an [=environment settings object=] |environment|,
run the following steps:
1. If |id| given, and is not a [=valid path id=], then
[=throw=] a {{TypeError}}.
1. If |id|'s [=string/length=] is more than 32, then
[=throw=] a {{TypeError}}.
1. Let |origin| be |environment|'s [=environment settings object/origin=].
1. If |startIn| is a {{FileSystemHandle}} and |startIn| is not
[=FileSystemHandle/is in a bucket file system|in a bucket file system=]:
1. Let |entry| be the result of [=locating an entry=] given
|startIn|'s [=FileSystemHandle/locator=].
1. If |entry| is a [=file entry=], return the path of
|entry|'s [=file system entry/parent=] in the local file system.
1. If |entry| is a [=directory entry=], return
|entry|'s path in the local file system.
1. If |id| is non-empty:
1. If [=recently picked directory map=][|origin|] [=map/exists=]:
1. Let |path map| be [=recently picked directory map=][|origin|].
1. If |path map|[|id|] [=map/exists=], then return |path map|[|id|].
1. If |startIn| is a {{WellKnownDirectory}}:
1. Return a user agent defined path corresponding to the {{WellKnownDirectory}} value of |startIn|.
1. If |id| is not specified, or is an empty string:
1. If [=recently picked directory map=][|origin|] [=map/exists=]:
1. Let |path map| be [=recently picked directory map=][|origin|].
1. If |path map|[""] [=map/exists=], then return |path map|[""].
1. Return a default path in a user agent specific manner.
</div>
<div algorithm>
To <dfn>remember a picked directory</dfn>, given an optional [=string=] |id|,
a [=/file system entry=] |entry|, and an [=environment settings object=] |environment|,
run the following steps:
1. Let |origin| be |environment|'s [=environment settings object/origin=].
1. If [=recently picked directory map=][|origin|] does not [=map/exist=],
then set [=recently picked directory map=][|origin|] to an empty [=path id map=].
1. If |id| is not specified, let |id| be an empty string.
1. Set [=recently picked directory map=][|origin|][|id|] to the path on the local file system corresponding to |entry|,
if such a path can be determined.
</div>
## The {{showOpenFilePicker()}} method ## {#api-showopenfilepicker}
<div class="note domintro">
: [ |handle| ] = await window . {{showOpenFilePicker()}}
: [ |handle| ] = await window . {{showOpenFilePicker()|showOpenFilePicker}}({ {{OpenFilePickerOptions/multiple}}: false })
:: Shows a file picker that lets a user select a single existing file, returning a handle for
the selected file.
: handles = await window . {{showOpenFilePicker()|showOpenFilePicker}}({ {{OpenFilePickerOptions/multiple}}: true })
:: Shows a file picker that lets a user select multiple existing files, returning handles for
the selected files.
Additional options can be passed to {{showOpenFilePicker()}} to indicate the types of files
the website wants the user to select and the directory in which the
file picker will open. See [[#api-filepickeroptions]] for details.
</div>
<div algorithm>
The <dfn method for=Window>showOpenFilePicker(|options|)</dfn> method, when invoked, must run
these steps:
1. Let |environment| be <b>[=this=]</b>'s [=relevant settings object=].
1. Let |accepts options| be the result of [=process accept types|processing accept types=] given |options|.
1. Let |starting directory| be the result of
[=determine the directory the picker will start in|determining the directory the picker will start in=]
given |options|["{{FilePickerOptions/id}}"],
|options|["{{FilePickerOptions/startIn}}"], and |environment|.
1. Let |global| be |environment|'s [=environment settings object/global object=].
1. Verify that |environment| [=is allowed to show a file picker=].
1. Let |p| be [=a new promise=].
1. Run the following steps [=in parallel=]:
1. Optionally, wait until any prior execution of this algorithm has terminated.
1. Display a prompt to the user requesting that the user pick some files.
If |options|["{{OpenFilePickerOptions/multiple}}"] is false,
there must be no more than one file selected;
otherwise any number may be selected.
The displayed prompt should let the user pick one of the |accepts options| to filter the list of displayed files.
Exactly how this is implemented, and what this prompt looks like is [=implementation-defined=].
When possible, this prompt should start out showing |starting directory|.
1. Wait for the user to have made their selection.
1. If the user dismissed the prompt without making a selection,
[=/reject=] |p| with an "{{AbortError}}" {{DOMException}} and abort.
1. Let |entries| be a [=/list=] of [=file entries=] representing the selected files or directories.
1. Let |result| be a empty [=/list=].
1. [=list/For each=] |entry| of |entries|:
1. If |entry| is deemed [=too sensitive or dangerous=] to be exposed to this website by the user agent:
1. Inform the user that the selected files or directories can't be exposed to this website.
1. At the discretion of the user agent,
either go back to the beginning of these [=in parallel=] steps,
or [=/reject=] |p| with an "{{AbortError}}" {{DOMException}} and abort.
1. Add a new {{FileSystemFileHandle}} associated with |entry| to |result|.
1. [=Remember a picked directory=] given
|options|["{{FilePickerOptions/id}}"], |entries|[0] and |environment|.
1. Perform the <a spec=html>activation notification</a> steps in |global|'s [=Window/browsing context=].
Note: This lets a website immediately perform operations on the returned handles that
might require user activation, such as requesting more permissions.
1. [=/Resolve=] |p| with |result|.
1. Return |p|.
</div>
## The {{Window/showSaveFilePicker()}} method ## {#api-showsavefilepicker}
<div class="note domintro">
: |handle| = await window . {{showSaveFilePicker()|showSaveFilePicker}}( |options| )
:: Shows a file picker that lets a user select a single file, returning a handle for
the selected file. The selected file does not have to exist already. If the selected
file does not exist a new empty file is created before this method returns, otherwise
the existing file is cleared before this method returned.
: |handle| = await window . {{showSaveFilePicker()|showSaveFilePicker}}({ {{SaveFilePickerOptions/suggestedName}}: "README.md" })
:: Shows a file picker with the suggested "README.md" file name pre-filled as the default file name to save as.
Additional |options| can be passed to {{showSaveFilePicker()}} to indicate the types of files
the website wants the user to select and the directory in which the
file picker will open. See [[#api-filepickeroptions]] for details.
</div>
Advisement: The {{SaveFilePickerOptions/suggestedName}} option was first introduced in Chrome 91.
<div algorithm>
The <dfn method for=Window>showSaveFilePicker(|options|)</dfn> method, when invoked, must run
these steps:
1. Let |environment| be <b>[=this=]</b>'s [=relevant settings object=].
1. Let |accepts options| be the result of [=process accept types|processing accept types=] given |options|.
1. Let |starting directory| be the result of
[=determine the directory the picker will start in|determining the directory the picker will start in=]
given |options|["{{FilePickerOptions/id}}"],
|options|["{{FilePickerOptions/startIn}}"] and |environment|.
1. Let |global| be |environment|'s [=environment settings object/global object=].
1. Verify that |environment| [=is allowed to show a file picker=].
1. Let |p| be [=a new promise=].
1. Run the following steps [=in parallel=]:
1. Optionally, wait until any prior execution of this algorithm has terminated.
1. Display a prompt to the user requesting that the user pick exactly one file.
The displayed prompt should let the user pick one of the |accepts options| to filter the list of displayed files.
Exactly how this is implemented, and what this prompt looks like is [=implementation-defined=].
If |accepts options| are displayed in the UI, the selected option should also be used to suggest an extension
to append to a user provided file name, but this is not required. In particular user agents are free to ignore
potentially dangerous suffixes such as those ending in `".lnk"` or `".local"`.
When possible, this prompt should start out showing |starting directory|.
If |options|["{{SaveFilePickerOptions/suggestedName}}"] [=map/exists=] and
is not null, the file picker prompt will be pre-filled with the
|options|["{{SaveFilePickerOptions/suggestedName}}"] as the default name
to save as. The interaction between the
{{SaveFilePickerOptions/suggestedName}} and |accepts options| is [=implementation-defined=].
If the {{SaveFilePickerOptions/suggestedName}} is deemed too dangerous, user agents should ignore or sanitize the
suggested file name, similar to the sanitization done when fetching something <a spec=html>as a download</a>.
Note: A user agent could for example pick whichever option in |accepts options| that matches
{{SaveFilePickerOptions/suggestedName}} as the default filter.
1. Wait for the user to have made their selection.
1. If the user dismissed the prompt without making a selection,
[=/reject=] |p| with an "{{AbortError}}" {{DOMException}} and abort.
1. Let |entry| be a [=file entry=] representing the selected file.
1. If |entry| is deemed [=too sensitive or dangerous=] to be exposed to this website by the user agent:
1. Inform the user that the selected files or directories can't be exposed to this website.
1. At the discretion of the user agent,
either go back to the beginning of these [=in parallel=] steps,
or [=/reject=] |p| with an "{{AbortError}}" {{DOMException}} and abort.
1. Set |entry|'s [=binary data=] to an empty [=byte sequence=].
1. Set |result| to a new {{FileSystemFileHandle}} associated with |entry|.
1. [=Remember a picked directory=] given
|options|["{{FilePickerOptions/id}}"], |entry| and |environment|.
1. Perform the <a spec=html>activation notification</a> steps in |global|'s [=Window/browsing context=].
Note: This lets a website immediately perform operations on the returned handles that
might require user activation, such as requesting more permissions.
1. [=/Resolve=] |p| with |result|.
1. Return |p|.
</div>
## The {{Window/showDirectoryPicker()}} method ## {#api-showdirectorypicker}
<div class="note domintro">
: |handle| = await window . {{Window/showDirectoryPicker()}}
: |handle| = await window . {{Window/showDirectoryPicker()}}({ {{DirectoryPickerOptions/mode}}: 'read' })
:: Shows a directory picker that lets the user select a single directory, returning a handle for
the selected directory if the user grants read permission.
: handles = await window . {{Window/showDirectoryPicker()}}({ {{DirectoryPickerOptions/mode}}: 'readwrite' })
:: Shows a directory picker that lets the user select a single directory, returning a handle for
the selected directory. The user agent can combine read and write permission requests on this handle into
one subsequent prompt.
The {{DirectoryPickerOptions/id}} and {{DirectoryPickerOptions/startIn}} fields behave
identically to the {{FilePickerOptions/id}} and {{FilePickerOptions/startIn}} fields, respectively.
See [[#api-filepickeroptions-starting-directory]] for details on how to use these fields.
</div>
<div algorithm>
The <dfn method for=Window>showDirectoryPicker(<var>options</var>)</dfn> method, when invoked, must run
these steps:
1. Let |environment| be <b>[=this=]</b>'s [=relevant settings object=].
1. Let |starting directory| be the result of [=determine the directory the picker will start in|determining the directory the picker will start in=]
given |options|["{{DirectoryPickerOptions/id}}"],
|options|["{{DirectoryPickerOptions/startIn}}"] and |environment|.
1. Let |global| be |environment|'s [=environment settings object/global object=].
1. Verify that |environment| [=is allowed to show a file picker=].
1. Let |p| be [=a new promise=].
1. Run the following steps [=in parallel=]:
1. Optionally, wait until any prior execution of this algorithm has terminated.
1. Display a prompt to the user requesting that the user pick a directory.
When possible, this prompt should start out showing |starting directory|.
1. Wait for the user to have made their selection.
1. If the user dismissed the prompt without making a selection,
[=/reject=] |p| with an "{{AbortError}}" {{DOMException}} and abort.
1. Let |entry| be a [=directory entry=] representing the selected directory.
1. If |entry| is deemed [=too sensitive or dangerous=] to be exposed to this website by the user agent:
1. Inform the user that the selected files or directories can't be exposed to this website.
1. At the discretion of the user agent,
either go back to the beginning of these [=in parallel=] steps,
or [=/reject=] |p| with an "{{AbortError}}" {{DOMException}} and abort.
1. Set |result| to a new {{FileSystemDirectoryHandle}} associated with |entry|.
1. [=Remember a picked directory=] given
|options|["{{DirectoryPickerOptions/id}}"], |entry| and |environment|.
1. Let |desc| be a {{FileSystemPermissionDescriptor}}.
1. Set |desc|["{{PermissionDescriptor/name}}"] to
"{{PermissionName/file-system}}".
1. Set |desc|["{{FileSystemPermissionDescriptor/handle}}"] to |result|.
1. Set |desc|["{{FileSystemPermissionDescriptor/mode}}"] to
|options|["{{DirectoryPickerOptions/mode}}"].
1. Let |status| be the result of running <a spec=permissions>create a PermissionStatus</a> for |desc|.
1. Perform the <a spec=html>activation notification</a> steps in |global|'s [=Window/browsing context=].
1. [=Request permission to use=] |desc|.
1. Run the [=default permission query algorithm=] on |desc| and |status|.
1. If |status| is not "{{PermissionState/granted}}",
[=/reject=] |result| with a "{{AbortError}}" {{DOMException}} and abort.
1. Perform the <a spec=html>activation notification</a> steps in |global|'s [=Window/browsing context=].
1. [=/Resolve=] |p| with |result|.
1. Return |p|.
</div>
## Drag and Drop ## {#drag-and-drop}
<xmp class=idl>
partial interface DataTransferItem {
Promise<FileSystemHandle?> getAsFileSystemHandle();
};
</xmp>
During a <em>drag-and-drop operation</em>, dragged file and
directory items are associated with [=file entries=] and [=directory entries=]
respectively.
<div class="note domintro">
: |handle| = await item . {{getAsFileSystemHandle()}}
:: Returns a {{FileSystemFileHandle}} object if the dragged item is a file and a {{FileSystemDirectoryHandle}} object if the dragged item is a directory.
</div>
<div algorithm>
The <dfn method for=DataTransferItem>getAsFileSystemHandle()</dfn> method steps are:
1. If the {{DataTransferItem}} object is not in the <a spec=html>read/write
mode</a> or the <a spec=html>read-only mode</a>, return
[=a promise resolved with=] `null`.
1. If the <a spec=html>the drag data item kind</a> is not <em>File</em>,
then return [=a promise resolved with=] `null`.
1. Let |p| be [=a new promise=].
1. Run the following steps [=in parallel=]:
1. Let |entry| be the [=/file system entry=] representing the dragged file or directory.
1. If |entry| is a [=file entry=]:
1. Let |handle| be a {{FileSystemFileHandle}} associated with |entry|.
1. Else if |entry| is a [=directory entry=]:
1. Let |handle| be a {{FileSystemDirectoryHandle}} associated with |entry|.
1. [=/Resolve=] |p| with |entry|.
1. Return |p|.
</div>
<div class=example id=draganddrop-example>
Handling drag and drop of files and directories:
<xmp highlight=js>
elem.addEventListener('dragover', (e) => {
// Prevent navigation.
e.preventDefault();
});
elem.addEventListener('drop', async (e) => {
e.preventDefault();
const fileHandlesPromises = [...e.dataTransfer.items]
.filter(item => item.kind === 'file')
.map(item => item.getAsFileSystemHandle());
for await (const handle of fileHandlesPromises) {
if (handle.kind === 'directory') {
console.log(`Directory: ${handle.name}`);
} else {
console.log(`File: ${handle.name}`);
}
}
});
</xmp>
</div>
Issue: This currently does not block access to [=too sensitive or dangerous=] directories, to
be consistent with other APIs that give access to dropped files and directories. This is inconsistent
with the [=local file system handle factories=] though, so we might want to reconsider this.
# Accessibility Considerations # {#accessibility-considerations}