Skip to content

Commit

Permalink
Properly specify async iterable behavior.
Browse files Browse the repository at this point in the history
This fixes #158, fixes #127, and fixes #47.
  • Loading branch information
mkruisselbrink committed Apr 29, 2020
1 parent de41e45 commit d815ef1
Show file tree
Hide file tree
Showing 2 changed files with 82 additions and 35 deletions.
7 changes: 4 additions & 3 deletions EXPLAINER.md
Original file line number Diff line number Diff line change
Expand Up @@ -230,8 +230,9 @@ if (!dir_ref) {
return;
}
// Read directory contents.
for await (const entry of dir_ref.getEntries()) {
for await (const [name, entry] of dir_ref) {
// entry is a FileSystemFileHandle or a FileSystemDirectoryHandle.
// name is equal to entry.name
}

// Get a specific file.
Expand Down Expand Up @@ -302,7 +303,7 @@ const sandboxed_dir = await self.getSandboxedFileSystem();

// The website can freely create files and directories in this directory.
const cache_dir = await sandboxed_dir.getDirectory('cache', {create: true});
for await (const entry of cache_dir.getEntries()) {
for await (const entry of cache_dir.values()) {
// Do something with entry.
};

Expand All @@ -318,7 +319,7 @@ similar. Could still include some kind of permission prompt if needed.

```javascript
const font_dir = await FileSystemDirectoryHandle.getSystemDirectory({type: 'fonts'});
for await (const entry of font_dir.getEntries()) {
for await (const entry of font_dir.values()) {
// Use font entry.
};
```
Expand Down
110 changes: 78 additions & 32 deletions index.bs
Original file line number Diff line number Diff line change
Expand Up @@ -460,12 +460,11 @@ dictionary FileSystemRemoveOptions {

[Exposed=(Window,Worker), SecureContext, Serializable]
interface FileSystemDirectoryHandle : FileSystemHandle {
async iterable<USVString, FileSystemHandle>;

Promise<FileSystemFileHandle> getFile(USVString name, optional FileSystemGetFileOptions options = {});
Promise<FileSystemDirectoryHandle> getDirectory(USVString name, optional FileSystemGetDirectoryOptions options = {});

// This really returns an async iterable, but that is not yet expressable in WebIDL.
object getEntries();

Promise<void> removeEntry(USVString name, optional FileSystemRemoveOptions options = {});

Promise<sequence<USVString>?> resolve(FileSystemHandle possibleDescendant);
Expand All @@ -487,8 +486,82 @@ Issue(98): Having getFile methods in both FileSystemDirectoryHandle and FileSyst
with very different behavior might be confusing? Perhaps rename at least one of them (but see also
previous issue).

Issue(47): Should getEntries be its own method, or should FileSystemDirectoryHandle just be an async
iterable itself?
### Directory iteration ### {#api-filesystemdirectoryhandle-asynciterable}

<div class="note domintro">
: for await (let [|name|, |handle|] of |directoryHandle|) {}
: for await (let [|name|, |handle|] of |directoryHandle| . entries()) {}
: for await (let |handle| of |directoryHandle| . values()) {}
: for await (let |name| of |directoryHandle| . keys()) {}
:: Iterates over all entries whose parent is the entry represented by |directoryHandle|. Entries
that are created or deleted while the iteration is in progress might or might not be included.
No guarantees are given either way.
</div>

Advisement: In Chrome this is currently implemented as a `directoryHandle.getEntries()` method that can be used in a `for await..of` loop.
This `getEntries()` method returns more or less the same async iterable as what is returned by `values()` in this specification.
The proper async iterable declaration is not yet implemented.

Issue(173): In the future we might want to add arguments to the async iterable declaration to
support for example recursive iteration.

<div algorithm="iterator initialization">
The [=asynchronous iterator initialization steps=] for a {{FileSystemDirectoryHandle}} |handle|
ant its async iterator |iterator| are:

1. Let |entry| be |handle|'s [=FileSystemHandle/entry=].

1. Let |permissionStatus| be the result of running
|entry|'s [=query permission steps=] given
«[&nbsp;"{{FileSystemHandlePermissionDescriptor/writable}}" → `false`&nbsp;]»
and <b>[=this=]</b>'s [=relevant settings object=].

1. If |permissionStatus| is not {{PermissionState/"granted"}},
throw a {{NotAllowedError}}.

1. Set |iterator|'s <dfn for="FileSystemDirectoryHandle-iterator">past results</dfn> to an empty [=/set=].

</div>

<div algorithm="next iteration result">
To [=get the next iteration result=] for a {{FileSystemDirectoryHandle}} |handle|
and its async iterator |iterator|:

1. Let |promise| be [=a new promise=].

1. Let |directory| be |handle|'s [=FileSystemHandle/entry=].

1. Let |permissionStatus| be the result of running
|directory|'s [=query permission steps=] given
«[&nbsp;"{{FileSystemHandlePermissionDescriptor/writable}}" → `false`&nbsp;]»
and <b>[=this=]</b>'s [=relevant settings object=].

1. If |permissionStatus| is not {{PermissionState/"granted"}},
reject |promise| with a {{NotAllowedError}} and return |promise|.

1. Let |child| be an [=/entry=] in |directory|'s [=directory entry/children=],
such that |child|'s [=entry/name=] is not contained in |iterator|'s [=past results=],
or `null` if no such entry exists.

Note: This is intentionally very vague about the iteration order. Different platforms
and file systems provide different guarantees about iteration order, and we want it to
be possible to efficiently implement this on all platforms. As such no guarantees are given
about the exact order in which elements are returned.

1. If |child| is `null`, then:
1. [=/Resolve=] |promise| with `undefined`.

1. Otherwise:
1. [=set/Append=] |child|'s [=entry/name=] to |iterator|'s [=past results=].
1. If |child| is a [=file entry=]:
1. Let |result| be a new {{FileSystemFileHandle}} associated with |child|.
1. Otherwise:
1. Let |result| be a new {{FileSystemDirectoryHandle}} associated with |child|.
1. [=/Resolve=] |promise| with (|child|'s [=entry/name=], |result|).

1. Return |promise|.

</div>

### The {{FileSystemDirectoryHandle/getFile()}} method ### {#api-filesystemdirectoryhandle-getfile}

Expand Down Expand Up @@ -625,33 +698,6 @@ invoked, must run these steps:

</div>

### The {{FileSystemDirectoryHandle/getEntries()}} method ### {#api-filesystemdirectoryhandle-getentries}

<div class="note domintro">
: for await (const |handle| of |directoryHandle| . {{FileSystemDirectoryHandle/getEntries()}}) {}
:: Iterates over all entries whose parent is the entry represented by |directoryHandle|.
</div>

Issue(158): Should {{FileSystemDirectoryHandle}} itself be async iterable, instead of having this getEntries method?

<div algorithm>
The <dfn method for=FileSystemDirectoryHandle>getEntries()</dfn> method, when invoked, must run
these steps:

1. Let |result| be [=a new promise=].
1. Run the following steps [=in parallel=]:
1. Let |entry| be <b>[=this=]</b>'s [=FileSystemHandle/entry=].
1. Let |permissionStatus| be the result of running
|entry|'s [=query permission steps=] given
«[&nbsp;"{{FileSystemHandlePermissionDescriptor/writable}}" → `false`&nbsp;]»
and <b>[=this=]</b>'s [=relevant settings object=].
1. If |permissionStatus| is not {{PermissionState/"granted"}},
reject |result| with a {{NotAllowedError}} and abort.
1. TODO (depends on WebIDL support for value async-iterators).
1. Return |result|.

</div>

### The {{FileSystemDirectoryHandle/removeEntry()}} method ### {#api-filesystemdirectoryhandle-removeentry}

<div class="note domintro">
Expand Down

0 comments on commit d815ef1

Please sign in to comment.