-
Notifications
You must be signed in to change notification settings - Fork 9
/
Copy pathindex.bs
1012 lines (766 loc) · 33.6 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 and Directory Entries API
Shortname: Entries-API
Abstract: This specification documents web browser support for file
and directory upload by drag-and-drop operations. It introduces
types representing directories with methods for asynchronous
traversal, and extends {{HTMLInputElement}} and
{{DataTransferItem}} [[!HTML]].
Status: CG-DRAFT
ED: https://wicg.github.io/entries-api/
Repository: WICG/entries-api
Level: 1
Editor: Joshua Bell, Google Inc. https://google.com, [email protected]
Group: wicg
Favicon: logo-folder.svg
Assume Explicit For: yes
Markup Shorthands: markdown yes, css no
Test Suite: https://github.com/web-platform-tests/wpt/tree/master/entries-api
Complain About: accidental-2119 yes
</pre>
<pre class=anchors>
spec: html; urlPrefix: https://html.spec.whatwg.org/multipage/
urlPrefix: forms.html
type: dfn
text: selected files; url: #concept-input-type-file-selected
text: file upload; url: #file-upload-state-(type=file)
urlPrefix: interaction.html
type: dfn
text: drag data store; url: #drag-data-store
for: drag data store;
text: drag data store item list; url: #drag-data-store-item-list
text: drag data store mode; url: #drag-data-store
for: drag data store item list; text: drag data item kind; url: #the-drag-data-item-kind
text: read/write mode; url: #concept-dnd-rw
text: read-only mode; url: #concept-dnd-ro
spec: ecma262; urlPrefix: https://tc39.github.io/ecma262/
type: dfn
text: Promise; url: #sec-promise-objects
text: AsyncIterator; url: #sec-asynciterator-interface
</pre>
<img id="speclogo" src="logo-folder.svg" alt="logo" width="100" height="100">
<style>
#speclogo { height: 100px; width: 100px; background-color: transparent; }
main #speclogo { position: absolute; right: 20px; top: 30px; }
.logo #speclogo { margin-top: 20px; }
</style>
<script>
(function() {
const logo = document.querySelector('.logo');
if (logo) logo.appendChild(document.querySelector('#speclogo'));
})();
</script>
<!-- ============================================================ -->
# Goals # {#goals}
<!-- ============================================================ -->
This specification documents the types and operations made available
by web browsers to script when a hierarchy of files and directories
are dragged and dropped onto a page or selected using form elements,
or equivalent user actions.
This is heavily based upon earlier drafts of [[file-system-api]] which
defines similar types in the context of a sandboxed file system,
including operations for creating and modifying files and directories,
but which has not been broadly adopted by web browsers.
note:
The APIs described by this document was initially implemented in
Google Chrome. Other browsers (at this time: Edge, Firefox and
Safari) are starting to support subsets of Chrome's APIs
and behavior. The intent of this document is to specify the common
subset to ensure that the implementations are interoperable.
<!-- ============================================================ -->
# Concepts # {#concepts}
<!-- ============================================================ -->
<!-- ============================================================ -->
## Names and Paths ## {#names-paths}
<!-- ============================================================ -->
A <dfn>name</dfn> is a string which:
* does not contain '/' (U+002F SOLIDUS)
* does not contain NUL (U+0000)
* does not contain '\' (U+005C REVERSE SOLIDUS)
* is not '.' (U+002E FULL STOP)
* is not '..' (U+002E FULL STOP, U+002E FULL STOP)
A <dfn>path segment</dfn> is a [=/name=], '.' (U+002E FULL STOP) or
'..' (U+002E FULL STOP, U+002E FULL STOP).
A <dfn>relative path</dfn> is a string consisting of one or more
[=path segments=] joined by '/' (U+002F SOLIDUS) that does not start
with '/' (U+002F SOLIDUS).
An <dfn>absolute path</dfn> is a string consisting of '/' (U+002F
SOLIDUS) followed by zero or more [=path segments=] joined by '/'
(U+002F SOLIDUS).
A <dfn>path</dfn> is either a [=relative path=] or an [=absolute
path=].
A <dfn>valid path</dfn> is a {{USVString}} which is a [=path=].
<!-- ============================================================ -->
## Files and Directories ## {#files-dirs}
<!-- ============================================================ -->
A <dfn>file</dfn> consists of binary data and a <dfn
for=file>name</dfn> (a non-empty [=/name=]).
A <dfn>directory</dfn> consists of a <dfn for=directory>name</dfn> (a
[=/name=]) and an ordered list of members. Each member is either a
[=file=] or a [=directory=]. Each member of a [=directory=] must have
a distinct non-empty [=/name=].
A <dfn>root directory</dfn> is a [=directory=] that is not a member of
a [=directory=]. A [=root directory=]'s [=/name=] is empty.
The <dfn>parent</dfn> of a [=file=] or [=directory=] is the
[=directory=] it is a member of. A [=root directory=] has no
[=parent=].
<div class=issue>
EDITORIAL:
Should [=directory=] be defined as a special type of [=file=]
so that minimal changes are necessary in [[HTML]]?
</div>
note:
In most cases, the files and directories selected by the user will
be presented by the API as if contained by a <em>virtual root</em>
that does not exist as an entity in the actual native file system
backing the interaction with the API.
A <dfn>file system</dfn> consists of a <dfn for="file system">name</dfn>
and a <dfn for="file system">root</dfn> which is an associated [=root
directory=]. The [=file system/name=] of a [=/file system=] is a
{{USVString}} which is implementation defined but is unique to the
[=/file system=]. A [=root directory=] is associated with exactly one
[=/file system=].
note:
Implementations could produce a [=file system/name=] by generating a
UUID for each [=/file system=] instance with some fixed prefix and
suffix strings applied. Authors using the API are adviised not to make
assumptions about the structure or content of the names.
<!-- ============================================================ -->
## Entries ## {#entries}
<!-- ============================================================ -->
An <dfn id=entry-concept>entry</dfn> is either a <dfn>file entry</dfn>
or a <dfn>directory entry</dfn>.
An [=entry=] has an <dfn for=entry>name</dfn> (a [=/name=]) and a
<dfn>full path</dfn> (an [=absolute path=]).
An [=entry=] also has a <dfn for=entry>root</dfn>, which is an
associated [=root directory=].
note:
[=Entries=] are defined in terms of [=paths=] relative to a [=root
directory=] to account for the fact that a native file system
backing the interaction with the API could be modified asynchronously
during operations such as enumerating the contents of a directory.
Operations exposed on [=entries=] will produce errors in such cases
where the [=paths=] no longer reference the same entity.
The <dfn for=entry>file system</dfn> of an [=entry=] is the
[=/file system=] associated with the [=entry=]'s [=entry/root=].
<!-- ============================================================ -->
# Algorithms # {#algorithms}
<!-- ============================================================ -->
<div algorithm>
To <dfn>resolve a relative path</dfn> with |abspath| (an [=absolute
path=]) and |path| (an [=absolute path=], a [=relative path=], or the empty string),
perform the following steps. They return an [=absolute path=].
1. If |path| is an [=absolute path=], return |path|.
1. Let |abspath segments| be the result of [=strictly splitting=]
|abspath| on '/' (U+002F SOLIDUS).
note: The first string will be empty.
1. Let |path segments| be the result of [=strictly splitting=]
|path| on '/' (U+002F SOLIDUS).
1. For each |segment| in |path segments|, switch on |segment|:
<dl class=switch>
: empty string
:: Continue.
: '.' (U+002E FULL STOP)
:: Continue.
: '..' (U+002E FULL STOP, U+002E FULL STOP)
:: Remove the last member of |abspath segments|
unless it is the only member.
: Otherwise
:: Append |segment| to |abspath segments|.
</dl>
1. Return |abspath segments| joined by '/' (U+002F SOLIDUS).
</div>
<div algorithm>
To <dfn>evaluate a path</dfn> with |directory| (an [=root directory=])
and |path| (an [=absolute path=]), perform the following steps. They
return a [=file=], [=directory=], or failure.
1. Let |segments| be the result of [=strictly splitting=] |path| on
'/' (U+002F SOLIDUS).
1. Remove the first entry from |segments|.
note: Since |path| was an [=absolute path=],
this first entry will always be empty.
1. For each |segment| in |segments|, switch on |segment|:
<dl class=switch>
: empty string
:: Continue.
: '.' (U+002E FULL STOP)
:: Continue.
: '..' (U+002E FULL STOP, U+002E FULL STOP)
:: Let |directory| be |directory|'s [=parent=],
or |directory| if none.
: Otherwise
:: Run these steps:
1. Let |item| be the member of |directory| with [=/name=]
equal to |segment|, or return failure if none.
1. If |segment| is the last item in |segments|, return
|item|.
1. If |item| is a [=file=], return failure.
1. Let |directory| be |item|.
</dl>
</div>
<!-- ============================================================ -->
# The {{File}} Interface # {#file-interface}
<!-- ============================================================ -->
<div class=issue>
EDITORIAL:
This section should be merged into [[FileAPI]] once it is complete.
</div>
<xmp class=idl>
partial interface File {
readonly attribute USVString webkitRelativePath;
};
</xmp>
The <dfn attribute for=File>webkitRelativePath</dfn> getter steps are to return [=/this=]'s [=relative path=], or the empty string if not specified.
<!-- ============================================================ -->
# HTML: Forms # {#html-forms}
<!-- ============================================================ -->
<div class=issue>
EDITORIAL:
This section should be merged into [[HTML]] once it is complete.
Sections such as the steps to [=/construct the entry list=]
need to be extended to include the {{File/webkitRelativePath}}
property.
</div>
<xmp class=idl>
partial interface HTMLInputElement {
attribute boolean webkitdirectory;
readonly attribute FrozenArray<FileSystemEntry> webkitEntries;
};
</xmp>
When an <{input}> element's <{input/type}> attribute is in the [=File
Upload=] state, the rules in this section apply.
The {{HTMLInputElement/webkitdirectory}} attribute is a boolean
attribute that indicates whether the user is to be allowed to select a
directory rather than a file or files. When specified, the behavior on
the selection of a directory is as if all files with that directory as
an ancestor were selected. In addition, the
{{File/webkitRelativePath}} property of each {{File}} is set to a
[=relative path=] starting from (and including) the selected directory
to the file.
<div class=example>
Given the following directory structure:
<xmp>
documents/
to_upload/
a/
b/
1.txt
2.txt
3.txt
not_uploaded.txt
</xmp>
If the `to_upload` directory was selected, then
{{HTMLInputElement/files}} would include:
* An entry with {{File/name}} == "`1.txt`" and {{File/webkitRelativePath}} == "`to_upload/a/b/1.txt`"
* An entry with {{File/name}} == "`2.txt`" and {{File/webkitRelativePath}} == "`to_upload/a/b/2.txt`"
* An entry with {{File/name}} == "`3.txt`" and {{File/webkitRelativePath}} == "`to_upload/a/3.txt`"
</div>
note:
A user agent could represent any hierarchical data as directories
during a selection operation. For example, on a device that does not
expose a native file system directly to the user, photo albums could
be presented as directories if `"image/*"` is specified
for the {{HTMLInputElement/accept}} attribute.
<div class=example>
Inspecting the {{File/webkitRelativePath}} properties after a
directory is selected with an <{input}> element:
```html
<input id=b type=file webkitdirectory>
```
```js
document.querySelector('#b').addEventListener('change', e => {
for (file entry of e.target.files) {
console.log(file.name, file.webkitRelativePath);
}
});
```
</div>
The {{HTMLInputElement/webkitEntries}} IDL attribute allows scripts to
access the element's selected entries. On getting, if the IDL
attribute applies, it must return an array of {{FileSystemEntry}}
objects that represent the current [=selected files=] (including
directories, if permitted). If the IDL attribute does not apply, then
it must instead return null.
<div class=example>
Enumerating entries using {{HTMLInputElement/webkitEntries}}:
```html
<input id=a type=file multiple>
```
```js
document.querySelector('#a').addEventListener('change', e => {
for (const entry of e.target.webkitEntries) {
handleEntry(entry);
}
});
```
</div>
<div class=issue>
INTEROP:
In Chrome, {{HTMLInputElement/webkitEntries}} is only populated as
the result of a drag-and-drop operation, not when the element is
clicked. Should we fix this so it is always populated?
</div>
<div class=issue>
INTEROP:
In Chrome, if {{HTMLInputElement/webkitdirectory}} is specified on a
{{HTMLInputElement}}, {{HTMLInputElement/webkitEntries}} is not
populated; the {{HTMLInputElement/files}} collection and
{{File/webkitRelativePath}} properties must be used instead to
reconstruct the directory structure. Should we fix this so it is
always populated?
</div>
<!-- ============================================================ -->
# HTML: Drag and drop # {#html-data}
<!-- ============================================================ -->
<div class=issue>
EDITORIAL:
This section should be merged into [[HTML]] once it is complete.
</div>
During a <em>drag-and-drop operation</em>, [=file=] and
[=directory=] items are associated with [=entries=]. Each
[=entry=] is a member of a [=root directory=] unique to the
[=drag data store=].
Additionally, each [=directory=] item is represented in the [=drag data store/drag
data store item list=] as an item whose [=drag data store item list/drag data item kind|kind=] is <i>File</i>. If it is accessed via
{{DataTransferItem/getAsFile()}} a zero-length {{File}} is returned.
note:
A user agent could represent any hierarchical data as files and
directories during a drag-and-drop operation. For example, audio
data stored in a relational database with separate tables for albums
metadata and blobs for tracks could be exposed to script as
directories and files when dragged from a media player application.
<xmp class=idl>
partial interface DataTransferItem {
FileSystemEntry? webkitGetAsEntry();
};
</xmp>
<div algorithm>
The <dfn method for=DataTransferItem>webkitGetAsEntry()</dfn> method steps are:
1. Let |store| be [=/this=]'s {{DataTransfer}} object's [=drag data store=].
1. If |store|'s [=drag data store/drag data store mode=] is not <a>read/write mode</a> or [=read-only mode=], return null and abort these steps.
1. Let |item| be the item in |store|'s [=drag data store/drag data store item list=] that [=/this=] represents.
1. If |item|'s [=drag data store item list/drag data item kind|kind=] is not <i>File</i>, then return
null and abort these steps.
1. Return a new {{FileSystemEntry}} object representing the [=entry=].
</div>
<div class=example>
Handling drag and drop of files and directories:
```js
elem.addEventListener('dragover', e => {
// Prevent navigation.
e.preventDefault();
});
elem.addEventListener('drop', e => {
// Prevent navigation.
e.preventDefault();
// Process all of the items.
for (const item of e.dataTransfer.items) {
// kind will be 'file' for file/directory entries.
if (item.kind === 'file') {
const entry = item.webkitGetAsEntry();
handleEntry(entry);
}
}
});
```
</div>
<!-- ============================================================ -->
# Files and Directories # {#api-files-directories}
<!-- ============================================================ -->
<div class=issue>
WEB COMPAT:
The legacy {{TypeMismatchError}} has been replaced in most
specifications by {{TypeError}}, but the name differs. Is it
compatible to switch here as well?
</div>
<xmp class=idl>
callback ErrorCallback = undefined (DOMException err);
</xmp>
An {{ErrorCallback}} function is used for operations that may return an
error asynchronously.
<div class=issue>
The [=/task source=] used for [=/queue a task|tasks that are queued=] in steps below is not defined. Chromium-based browsers appear to use the following:
* The <a href="https://w3c.github.io/FileAPI/#fileReadingTaskSource">file reading task source</a> [[FileAPI]], for {{FileSystemDirectoryReader/readEntries()}} success and error callbacks. (The Chromium implementation calls this `TaskType::kFileReading`.)
* The [=/DOM manipulation task source=] [[HTML]] elsewhere. (The Chromium implementation uses `TaskType::kMiscPlatformAPI` which targets the same underlying task queue as `TaskType::kDOMManipulation`.)
</div>
<!-- ============================================================ -->
## The {{FileSystemEntry}} Interface ## {#api-entry}
<!-- ============================================================ -->
<xmp class=idl>
[Exposed=Window]
interface FileSystemEntry {
readonly attribute boolean isFile;
readonly attribute boolean isDirectory;
readonly attribute USVString name;
readonly attribute USVString fullPath;
readonly attribute FileSystem filesystem;
undefined getParent(optional FileSystemEntryCallback successCallback,
optional ErrorCallback errorCallback);
};
</xmp>
An {{FileSystemEntry}} has an associated [=entry=].
The <dfn attribute for=FileSystemEntry>isFile</dfn> getter steps are to return true if [=/this=] is a [=file entry=] and false otherwise.
The <dfn attribute for=FileSystemEntry>isDirectory</dfn> getter steps are to return true if [=/this=] is a [=directory entry=] and false otherwise.
The <dfn attribute for=FileSystemEntry>name</dfn> getter steps are to return [=/this=]'s [=entry/name=].
The <dfn attribute for=FileSystemEntry>fullPath</dfn> getter steps are to return [=/this=]'s [=full path=].
The <dfn attribute for=FileSystemEntry>filesystem</dfn> getter steps are to return [=/this=]'s [=entry/file system=].
<div algorithm>
The <dfn method for=FileSystemEntry>getParent(|successCallback|, |errorCallback|)</dfn> method steps are:
1. [=In parallel=], run these steps:
1. Let |path| be the result of [=resolving a
relative path=] with [=/this=]'s [=full path=] and '..'.
1. Let |item| be the result of [=evaluating a
path=] with [=/this=]'s [=entry/root=] and |path|.
1. If |item| is failure, [=queue a task=] to [=invoke=]
|errorCallback| (if given) with a newly [=exception/created=]
"{{NotFoundError}}" {{DOMException}}, and abort these steps.
1. Let |entry| be a new [=directory entry=] with |item|'s
[=directory/name=] as [=entry/name=] and |path| as [=full
path=].
1. [=Queue a task=] to [=invoke=] |successCallback| with a new
{{FileSystemDirectoryEntry}} object associated with |entry|.
</div>
note:
An error is possible if files have been modified on disk since the
{{FileSystemEntry}} was created.
<div class=example>
Handling an entry:
```js
function handleEntry(entry) {
console.log('name: ' + entry.name);
console.log('path: ' + entry.fullPath);
if (entry.isFile) {
console.log('... is a file');
} else if (entry.isDirectory) {
console.log('... is a directory');
}
}
```
</div>
<div class=example>
Helper function to adapt {{FileSystemEntry/getParent()}} for use with
[=Promises=]
[[ECMA-262]]:
```js
function getParentAsPromise(entry) {
return new Promise((resolve, reject) => {
entry.getParent(resolve, reject);
});
}
```
</div>
<!-- ============================================================ -->
## The {{FileSystemDirectoryEntry}} Interface ## {#api-directoryentry}
<!-- ============================================================ -->
<xmp class=idl>
[Exposed=Window]
interface FileSystemDirectoryEntry : FileSystemEntry {
FileSystemDirectoryReader createReader();
undefined getFile(optional USVString? path,
optional FileSystemFlags options = {},
optional FileSystemEntryCallback successCallback,
optional ErrorCallback errorCallback);
undefined getDirectory(optional USVString? path,
optional FileSystemFlags options = {},
optional FileSystemEntryCallback successCallback,
optional ErrorCallback errorCallback);
};
dictionary FileSystemFlags {
boolean create = false;
boolean exclusive = false;
};
callback FileSystemEntryCallback = undefined (FileSystemEntry entry);
</xmp>
note:
The {{FileSystemFlags/create}} member of {{FileSystemFlags}} and
the associated behavior are included for compatibility with existing
implementations, even though there is no useful behavior when the
flag is specified. Similarly, the {{FileSystemFlags/exclusive}}
member is not explicitly referenced, but the binding behavior is
observable from script if an object with a getter is passed.
A {{FileSystemDirectoryEntry}}'s associated [=entry=] is a [=directory
entry=].
<div algorithm>
The <dfn method for=FileSystemDirectoryEntry>createReader()</dfn> method steps are:
1. Return a newly created {{FileSystemDirectoryReader}} object
associated with [=directory entry=].
</div>
<div algorithm>
The <dfn method for=FileSystemDirectoryEntry>getFile(|path|, |options|, |successCallback|, |errorCallback|)</dfn> method steps are:
1. [=In parallel=], run these steps:
1. If |path| is undefined or null let |path| be the empty string.
1. If |path| is not a [=valid path=], [=queue a task=] to [=invoke=]
|errorCallback| (if given) with a newly [=exception/created=]
"{{TypeMismatchError}}" {{DOMException}}, and abort these steps.
1. If |options|'s {{FileSystemFlags/create}} member is true,
[=queue a task=] to [=invoke=] |errorCallback| (if given) with a
newly [=exception/created=] "{{SecurityError}}" {{DOMException}}, and abort
these steps.
1. Let |path| be the result of [=resolving a
relative path=] with [=/this=]'s [=full path=]
and |path|.
1. Let |item| be the result of [=evaluating a
path=] with [=/this=]'s [=entry/root=] and |path|.
1. If |item| is failure, [=queue a task=] to [=invoke=]
|errorCallback| (if given) with a newly [=exception/created=]
"{{NotFoundError}}" {{DOMException}}, and abort these steps.
1. If |item| is not a [=file=], [=queue a task=] to [=invoke=]
|errorCallback| (if given) with a newly [=exception/created=]
"{{TypeMismatchError}}" {{DOMException}}, and abort these steps.
1. Let |entry| be a new [=file entry=] with |item|'s [=file/name=]
as [=entry/name=] and |path| as [=full path=].
1. [=Queue a task=] to [=invoke=] |successCallback| (if given) with a new
{{FileSystemFileEntry}} object associated with |entry|.
</div>
<div algorithm>
The <dfn method for=FileSystemDirectoryEntry>getDirectory(|path|, |options|, |successCallback|, |errorCallback|)</dfn> method steps are:
1. [=In parallel=], run these steps:
1. If |path| is undefined or null let |path| be the empty string.
1. If |path| is not a [=valid path=], [=queue a task=] to [=invoke=]
|errorCallback| (if given) with a newly [=exception/created=]
"{{TypeMismatchError}}" {{DOMException}}, and abort these steps.
1. If |options|'s {{FileSystemFlags/create}} member is true,
[=queue a task=] to [=invoke=] |errorCallback| (if given) with a
newly [=exception/created=] "{{SecurityError}}" {{DOMException}}, and abort
these steps.
1. Let |path| be the result of [=resolving a
relative path=] with [=/this=]'s [=full path=]
and |path|.
1. Let |item| be the result of [=evaluating a
path=] with [=/this=]'s [=entry/root=] and |path|.
1. If |item| is failure, [=queue a task=] to [=invoke=]
|errorCallback| (if given) with a newly [=exception/created=]
"{{NotFoundError}}" {{DOMException}}, and abort these steps.
1. If |item| is not a [=directory=], [=invoke=]
|errorCallback| (if given) with a newly [=exception/created=]
"{{TypeMismatchError}}" {{DOMException}}, and abort these steps.
1. Let |entry| be a new [=directory entry=] with |item|'s
[=directory/name=] as [=entry/name=] and |path| as [=full
path=].
1. [=Queue a task=] to [=invoke=] |successCallback| (if given) with a new
{{FileSystemDirectoryEntry}} associated with |entry|.
</div>
<div class=example>
Helper functions to adapt {{FileSystemDirectoryEntry/getFile()}} and
{{FileSystemDirectoryEntry/getDirectory()}} for use
with [=Promises=] [[ECMA-262]]:
```js
function getFileAsPromise(entry, path) {
return new Promise((resolve, reject) => {
entry.getFile(path, {}, resolve, reject);
});
}
function getDirectoryAsPromise(entry, path) {
return new Promise((resolve, reject) => {
entry.getDirectory(path, {}, resolve, reject);
});
}
```
</div>
<!-- ============================================================ -->
## The {{FileSystemDirectoryReader}} Interface ## {#api-directoryreader}
<!-- ============================================================ -->
<xmp class=idl>
[Exposed=Window]
interface FileSystemDirectoryReader {
undefined readEntries(FileSystemEntriesCallback successCallback,
optional ErrorCallback errorCallback);
};
callback FileSystemEntriesCallback = undefined (sequence<FileSystemEntry> entries);
</xmp>
<div dfn-for=FileSystemDirectoryReader>
A {{FileSystemDirectoryReader}} has
an associated <dfn>entry</dfn> (a [=directory entry=]),
an associated <dfn>directory</dfn> (initially null),
a <dfn>reading flag</dfn> (initially false),
a <dfn>done flag</dfn> (initially false),
and a <dfn>reader error</dfn> (initially null).
</div>
<div algorithm>
The <dfn method for=FileSystemDirectoryEntry>readEntries(|successCallback|, |errorCallback|)</dfn> method steps are:
1. If [=/this=]'s [=FileSystemDirectoryReader/reading flag=] is true, [=queue a task=] to [=invoke=] |errorCallback| with a newly [=exception/created=] "{{InvalidStateError}}" {{DOMException}}, and abort these steps.
1. If [=/this=]'s [=FileSystemDirectoryReader/reader error=] is not null, [=queue a task=] to [=invoke=] |errorCallback| (if given) with [=FileSystemDirectoryReader/reader error=], and abort these steps.
1. If [=/this=]'s [=FileSystemDirectoryReader/done flag=] is true, [=queue a task=] to [=invoke=] |successCallback| with an empty sequence and abort these steps.
1. Set [=/this=]'s [=FileSystemDirectoryReader/reading flag=] to true.
1. [=In parallel=], run these steps:
1. If [=/this=]'s [=FileSystemDirectoryReader/directory=] is null, then:
1. Let |dir| be the result of [=evaluating a path=] with [=/this=]'s [=FileSystemDirectoryReader/entry=]'s [=entry/root=] and [=full path=].
1. If |dir| is failure, then:
1. [=Queue a task=] to run these steps:
1. Let |error| be a newly [=exception/created=] "{{NotFoundError}}" {{DOMException}}.
1. Set [=/this=]'s [=FileSystemDirectoryReader/reader error=] to |error|.
1. Set [=/this=]'s [=FileSystemDirectoryReader/reading flag=] to false.
1. [=Invoke=] |errorCallback| (if given) with |error|.
1. Abort these steps.
1. Set [=/this=]'s [=FileSystemDirectoryReader/directory=] to |dir|.
1. Let |entries| be a non-zero number of entries from [=/this=]'s [=FileSystemDirectoryReader/directory=] that have not yet been produced by this {{FileSystemDirectoryReader}}, if any.
1. If the previous step failed (for example, the [=directory=] was deleted or permission is denied), then:
1. [=Queue a task=] to run these steps:
1. Let |error| be a an appropriate {{DOMException}}.
1. Set [=/this=]'s [=FileSystemDirectoryReader/reader error=] to |error|.
1. Set [=/this=]'s [=FileSystemDirectoryReader/reading flag=] to false.
1. [=Invoke=] |errorCallback| (if given) with [=FileSystemDirectoryReader/reader error=].
1. Abort these steps.
1. [=Queue a task=] to run these steps:
1. If |entries| is empty, then set [=/this=]'s [=FileSystemDirectoryReader/done flag=] to true.
1. Set [=/this=]'s [=FileSystemDirectoryReader/reading flag=] to false.
1. [=Invoke=] |successCallback| with |entries|.
NOTE: The use of the the [=FileSystemDirectoryReader/reading flag=] prevents multiple copies of the [=in parallel=] steps above from executing simultaneously. This obviates the need to specify a [=parallel queue=].
</div>
<div class=example>
Enumerating a directory:
```js
const reader = dirEntry.createReader();
const doBatch = () => {
// Read a batch.
reader.readEntries(entries => {
// Complete?
if (entries.length === 0) {
return;
}
// Process the batch.
entries.forEach(handleEntry);
// Read the next batch.
doBatch();
}, error => console.warn(error));
};
// Start reading
doBatch();
```
</div>
<div class=example>
Helper function to adapt {{FileSystemDirectoryReader}} for use with
[=Promises=] [[ECMA-262]]:
```js
function getEntriesAsPromise(dirEntry) {
return new Promise((resolve, reject) => {
const result = [];
const reader = dirEntry.createReader();
const doBatch = () => {
reader.readEntries(entries => {
if (entries.length > 0) {
entries.forEach(e => result.push(e));
doBatch();
} else {
resolve(result);
}
}, reject);
};
doBatch();
});
}
```
</div>
<div class=example>
Helper function to adapt {{FileSystemDirectoryReader}} for use with
[=AsyncIterators=] [[ECMA-262]]:
```js
async function* getEntriesAsAsyncIterator(dirEntry) {
const reader = dirEntry.createReader();
const getNextBatch = () => new Promise((resolve, reject) => {
reader.readEntries(resolve, reject);
});
let entries;
do {
entries = await getNextBatch();
for (const entry of entries) {
yield entry;
}
} while (entries.length > 0);
}
```
This allows for ordered asynchronous traversal of a directory tree
using `for-await-of`:
```js
async function show(entry) {
console.log(entry.fullPath);
if (entry.isDirectory) {
for await (const e of getEntriesAsAsyncIterator(entry)) {
await show(e);
}
}
}
```
</div>
<!-- ============================================================ -->
## The {{FileSystemFileEntry}} Interface ## {#api-fileentry}
<!-- ============================================================ -->
<xmp class=idl>
[Exposed=Window]
interface FileSystemFileEntry : FileSystemEntry {
undefined file(FileCallback successCallback,
optional ErrorCallback errorCallback);
};
callback FileCallback = undefined (File file);
</xmp>
A {{FileSystemFileEntry}}'s associated [=entry=] is a [=file entry=].
<div algorithm>
The <dfn method for=FileSystemFileEntry>file(|successCallback|, |errorCallback|)</dfn> method steps are:
1. [=In parallel=], run these steps:
1. Let |item| be the result of [=evaluating a
path=] with [=/this=]'s [=entry/root=] and [=full
path=].
1. If |item| is failure, [=queue a task=] to [=invoke=]
|errorCallback| (if given) with a newly [=exception/created=]
"{{NotFoundError}}" {{DOMException}}, and abort these steps.
1. If |item| is a [=directory=], [=queue a task=] to [=invoke=]
|errorCallback| (if given) with a newly [=exception/created=]
"{{TypeMismatchError}}" {{DOMException}}, and abort these steps.
1. [=Queue a task=] to [=invoke=] |successCallback| with a new {{File}}
object representing |item|.
</div>
<div class=example>
Read the contents of a dropped file using {{FileReader}}:
```js
function readFileEntry(entry) {
entry.file(file => {
const reader = new FileReader();
reader.readAsText(file);
reader.onerror = error => console.warn(error);
reader.onload = () => {
console.log(reader.result);
};
}, error => console.warn(error));
}
```
</div>
<div class=example>
Helper function to adapt {{FileSystemFileEntry/file()}} for use
with [=Promises=] [[ECMA-262]]:
```js
function fileAsPromise(entry) {
return new Promise((resolve, reject) => {
entry.file(resolve, reject);
});
}
```
</div>
<!-- ============================================================ -->
## The {{FileSystem}} Interface ## {#api-domfilesystem}
<!-- ============================================================ -->
<xmp class=idl>
[Exposed=Window]
interface FileSystem {
readonly attribute USVString name;
readonly attribute FileSystemDirectoryEntry root;
};
</xmp>
A {{FileSystem}} has an associated [=/file system=].
The <dfn attribute for=FileSystem>name</dfn> getter steps are to return [=/this=]'s [=file system/name=].
The <dfn attribute for=FileSystem>root</dfn> getter steps are to return a {{FileSystemDirectoryEntry}} associated with [=/this=]'s [=file system/root=].
<!-- ============================================================ -->
# Acknowledgements # {#acknowledgements}
<!-- ============================================================ -->
This specification is based heavily on the work of Eric Uhrhane in
[[file-system-api]], which introduced the {{FileSystemEntry}} types.