diff --git a/index.bs b/index.bs
index 6ebd66e..df746f7 100644
--- a/index.bs
+++ b/index.bs
@@ -3,7 +3,7 @@ Group: WHATWG
H1: File System
Shortname: fs
Text Macro: TWITTER whatfilesystem
-Text Macro: LATESTRD 2022-03
+Text Macro: LATESTRD 2022-09
Abstract: File System defines infrastructure for file systems as well as their API.
Indent: 2
Markup Shorthands: css no, markdown yes
diff --git a/review-drafts/2022-09.bs b/review-drafts/2022-09.bs
new file mode 100644
index 0000000..133766b
--- /dev/null
+++ b/review-drafts/2022-09.bs
@@ -0,0 +1,989 @@
+
+Group: WHATWG
+Status: RD
+Date: 2022-09-19
+H1: File System
+Shortname: fs
+Text Macro: TWITTER whatfilesystem
+Text Macro: LATESTRD 2022-09
+Abstract: File System defines infrastructure for file systems as well as their API.
+Indent: 2
+Markup Shorthands: css no, markdown yes
+
+
+
+spec:webidl; type:dfn; text:resolve
+
+
+
+urlPrefix: https://tc39.es/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
+
+
+
+
+
+# Introduction # {#introduction}
+
+*This section is non-normative.*
+
+This document defines fundamental infrastructure for file system APIs. In addition, it defines an
+API that makes it possible for websites to get access to a file system directory without having to
+first prompt the user for access. This enables use cases where a website wants to save data to disk
+before a user has picked a location to save to, without forcing the website to use a completely
+different storage mechanism with a different API for such files. The entry point for this is the
+{{StorageManager/getDirectory()|navigator.storage.getDirectory()}} method.
+
+
+# Files and Directories # {#files-and-directories}
+
+## Concepts ## {#concepts}
+
+An entry is either a [=file entry=] or a [=directory entry=].
+
+
+Each [=/entry=] has an associated query access algorithm, which takes "`read`"
+or "`readwrite`" mode and returns a {{PermissionState}}. Unless specified
+otherwise it returns "{{PermissionState/denied}}". The algorithm is allowed to throw.
+
+Each [=/entry=] has an associated request access algorithm, which takes
+"`read`" or "`readwrite`" mode and returns a {{PermissionState}}. Unless specified
+otherwise it returns "{{PermissionState/denied}}". The algorithm is allowed to throw.
+
+Note: Implementations that only implement this specification and not dependent specifications do not
+need to bother implementing [=/entry=]'s [=entry/query access=] and [=entry/request access=].
+
+Each [=/entry=] has an associated name (a [=string=]).
+
+A valid file name is a [=string=] that is not an empty string, is not equal to "." or "..",
+and does not contain '/' or any other character used as path separator on the underlying platform.
+
+Note: This means that '\' is not allowed in names on Windows, but might be allowed on
+other operating systems. Additionally underlying file systems might have further restrictions
+on what names are or aren't allowed, so a string merely being a [=valid file name=] is not
+a guarantee that creating a file or directory with that name will succeed.
+
+Issue: We should consider having further normative restrictions on file names that will
+never be allowed using this API, rather than leaving it entirely up to underlying file
+systems.
+
+A file entry additionally consists of
+binary data (a [=byte sequence=]) and a
+modification timestamp (a number representing the number of milliseconds since the Unix Epoch).
+
+A directory entry additionally consists of a [=/set=] of
+children, which are themselves [=/entries=]. Each member is either a [=/file=] or a [=directory=].
+
+An [=/entry=] |entry| should be [=list/contained=] in the [=children=] of at most one
+[=directory entry=], and that directory entry is also known as |entry|'s parent.
+An [=/entry=]'s [=entry/parent=] is null if no such directory entry exists.
+
+Note: Two different [=/entries=] can represent the same file or directory on disk, in which
+case it is possible for both entries to have a different parent, or for one entry to have a
+parent while the other entry does not have a parent.
+
+[=/Entries=] can (but don't have to) be backed by files on the host operating system's local file system,
+so it is possible for the [=binary data=], [=modification timestamp=],
+and [=children=] of entries to be modified by applications outside of this specification.
+Exactly how external changes are reflected in the data structures defined by this specification,
+as well as how changes made to the data structures defined here are reflected externally
+is left up to individual user-agent implementations.
+
+An [=/entry=] |a| is the same as an [=/entry=] |b| if |a| is equal to |b|, or
+if |a| and |b| are backed by the same file or directory on the local file system.
+
+Issue: TODO: Explain better how entries map to files on disk (multiple entries can map to the same file or
+directory on disk but an entry doesn't have to map to any file on disk).
+
+
+To resolve an [=/entry=] |child| relative to a [=directory entry=] |root|,
+run the following steps:
+
+1. Let |result| be [=a new promise=].
+1. Run the following steps [=in parallel=]:
+ 1. If |child| is [=the same as=] |root|,
+ [=/resolve=] |result| with an empty list, and abort.
+ 1. Let |childPromises| be « ».
+ 1. [=set/For each=] |entry| of |root|'s [=FileSystemHandle/entry=]'s [=children=]:
+ 1. Let |p| be the result of [=entry/resolving=] |child| relative to |entry|.
+ 1. [=list/Append=] |p| to |childPromises|.
+ 1. [=Upon fulfillment=] of |p| with value |path|:
+ 1. If |path| is not null:
+ 1. [=list/Prepend=] |entry|'s [=entry/name=] to |path|.
+ 1. [=/Resolve=] |result| with |path|.
+ 1. [=Wait for all=] |childPromises|, with the following success steps:
+ 1. If |result| hasn't been resolved yet, [=/resolve=] |result| with `null`.
+1. Return |result|.
+
+
+
+## The {{FileSystemHandle}} interface ## {#api-filesystemhandle}
+
+
+enum FileSystemHandleKind {
+ "file",
+ "directory",
+};
+
+[Exposed=(Window,Worker), SecureContext, Serializable]
+interface FileSystemHandle {
+ readonly attribute FileSystemHandleKind kind;
+ readonly attribute USVString name;
+
+ Promise isSameEntry(FileSystemHandle other);
+};
+
+
+A {{FileSystemHandle}} object represents an [=/entry=]. Each {{FileSystemHandle}} object is associated
+with an entry (an [=/entry=]). Multiple separate objects implementing
+the {{FileSystemHandle}} interface can all be associated with the same [=/entry=] simultaneously.
+
+
+{{FileSystemHandle}} objects are [=serializable objects=].
+
+Their [=serialization steps=], given |value|, |serialized| and forStorage are:
+
+1. Set |serialized|.\[[Origin]] to |value|'s [=relevant settings object=]'s [=environment settings object/origin=].
+1. Set |serialized|.\[[Entry]] to |value|'s [=FileSystemHandle/entry=].
+
+
+
+
+Their [=deserialization steps=], given |serialized| and |value| are:
+
+1. If |serialized|.\[[Origin]] is not [=same origin=] with
+ |value|'s [=relevant settings object=]'s [=environment settings object/origin=],
+ then throw a {{DataCloneError}}.
+1. Set |value|'s [=FileSystemHandle/entry=] to |serialized|.\[[Entry]]
+
+
+
+
+ : |handle| . {{FileSystemHandle/kind}}
+ :: Returns {{FileSystemHandleKind/"file"}} if |handle| is a {{FileSystemFileHandle}},
+ or {{FileSystemHandleKind/"directory"}} if |handle| is a {{FileSystemDirectoryHandle}}.
+
+ This can be used to distinguish files from directories when iterating over the contents
+ of a directory.
+
+ : |handle| . {{FileSystemHandle/name}}
+ :: Returns the [=entry/name=] of the entry represented by |handle|.
+
+
+The kind attribute must
+return {{FileSystemHandleKind/"file"}} if the associated [=FileSystemHandle/entry=] is a [=file entry=],
+and return {{FileSystemHandleKind/"directory"}} otherwise.
+
+The name attribute must return the [=entry/name=] of the
+associated [=FileSystemHandle/entry=].
+
+### The {{FileSystemHandle/isSameEntry()}} method ### {#api-filesystemhandle-issameentry}
+
+
+ : same = await |handle1| . {{FileSystemHandle/isSameEntry()|isSameEntry}}( |handle2| )
+ :: Returns true if |handle1| and |handle2| represent the same file or directory.
+
+
+
+The isSameEntry(|other|) method, when invoked, must run these steps:
+
+1. Let |realm| be [=this=]'s [=relevant Realm=].
+1. Let |p| be [=a new promise=] in |realm|.
+1. Run the following steps [=in parallel=]:
+ 1. If [=this=]'s [=FileSystemHandle/entry=] is [=the same as=] |other|'s [=FileSystemHandle/entry=],
+ [=/resolve=] |p| with `true`.
+ 1. Else [=/resolve=] |p| with `false`.
+1. Return |p|.
+
+
+
+## The {{FileSystemFileHandle}} interface ## {#api-filesystemfilehandle}
+
+
+dictionary FileSystemCreateWritableOptions {
+ boolean keepExistingData = false;
+};
+
+[Exposed=(Window,Worker), SecureContext, Serializable]
+interface FileSystemFileHandle : FileSystemHandle {
+ Promise getFile();
+ Promise createWritable(optional FileSystemCreateWritableOptions options = {});
+};
+
+
+A {{FileSystemFileHandle}}'s associated [=FileSystemHandle/entry=] must be a [=file entry=].
+
+{{FileSystemFileHandle}} objects are [=serializable objects=]. Their [=serialization steps=] and
+[=deserialization steps=] are the same as those for {{FileSystemHandle}}.
+
+### The {{FileSystemFileHandle/getFile()}} method ### {#api-filesystemfilehandle-getfile}
+
+
+ : file = await |fileHandle| . {{FileSystemFileHandle/getFile()}}
+ :: Returns a {{File}} representing the state on disk of the entry represented by |handle|.
+ If the file on disk changes or is removed after this method is called, the returned
+ {{File}} object will likely be no longer readable.
+
+
+
+The
getFile() method, when invoked, must run these steps:
+
+1. Let |result| be [=a new promise=].
+1. Run the following steps [=in parallel=]:
+ 1. Let |access| be the result of running [=this=]'s [=FileSystemHandle/entry=]'s
+ [=entry/query access=] given "`read`".
+ 1. If |access| is not "{{PermissionState/granted}}",
+ reject |result| with a {{NotAllowedError}} and abort.
+ 1. Let |entry| be [=this=]'s [=FileSystemHandle/entry=].
+ 1. Let |f| be a new {{File}}.
+ 1. Set |f|'s
snapshot state to the current state of |entry|.
+ 1. Set |f|'s underlying byte sequence to a copy of |entry|'s [=binary data=].
+ 1. Initialize the value of |f|'s {{File/name}} attribute to |entry|'s [=entry/name=].
+ 1. Initialize the value of |f|'s {{File/lastModified}} attribute to |entry|'s [=file entry/modification timestamp=].
+ 1. Initialize the value of |f|'s {{Blob/type}} attribute to an [=implementation-defined=] value, based on for example |entry|'s [=entry/name=] or its file extension.
+
+ Issue: The reading and snapshotting behavior needs to be better specified in the [[FILE-API]] spec,
+ for now this is kind of hand-wavy.
+ 1. [=/Resolve=] |result| with |f|.
+1. Return |result|.
+
+
+
+### The {{FileSystemFileHandle/createWritable()}} method ### {#api-filesystemfilehandle-createwritable}
+
+
+ : |stream| = await |fileHandle| . {{FileSystemFileHandle/createWritable()}}
+ : |stream| = await |fileHandle| . {{FileSystemFileHandle/createWritable()|createWritable}}({ {{FileSystemCreateWritableOptions/keepExistingData}}: true/false })
+ :: Returns a {{FileSystemWritableFileStream}} that can be used to write to the file. Any changes made through
+ |stream| won't be reflected in the file represented by |fileHandle| until the stream has been closed.
+ User agents try to ensure that no partial writes happen, i.e. the file represented by
+ |fileHandle| will either contain its old contents or it will contain whatever data was written
+ through |stream| up until the stream has been closed.
+
+ This is typically implemented by writing data to a temporary file, and only replacing the file
+ represented by |fileHandle| with the temporary file when the writable filestream is closed.
+
+ If {{FileSystemCreateWritableOptions/keepExistingData}} is `false` or not specified,
+ the temporary file starts out empty,
+ otherwise the existing file is first copied to this temporary file.
+
+
+Issue(67): There has been some discussion around and desire for a "inPlace" mode for createWritable
+(where changes will be written to the actual underlying file as they are written to the writer, for
+example to support in-place modification of large files or things like databases). This is not
+currently implemented in Chrome. Implementing this is currently blocked on figuring out how to
+combine the desire to run malware checks with the desire to let websites make fast in-place
+modifications to existing large files.
+
+
+The createWritable(|options|) method, when invoked, must run these steps:
+
+1. Let |result| be [=a new promise=].
+1. Run the following steps [=in parallel=]:
+ 1. Let |access| be the result of running [=this=]'s [=FileSystemHandle/entry=]'s
+ [=entry/request access=] given "`readwrite`".
+ If that throws an exception, [=reject=] |result| with that exception and abort.
+ 1. If |access| is not "{{PermissionState/granted}}",
+ reject |result| with a {{NotAllowedError}} and abort.
+ 1. Let |entry| be [=this=]'s [=FileSystemHandle/entry=].
+ 1. Let |stream| be the result of [=create a new FileSystemWritableFileStream|creating a new FileSystemWritableFileStream=]
+ for |entry| in [=this=]'s [=relevant realm=].
+ 1. If |options|.{{FileSystemCreateWritableOptions/keepExistingData}} is `true`:
+ 1. Set |stream|.[=[[buffer]]=] to a copy of |entry|'s [=file entry/binary data=].
+ 1. [=/Resolve=] |result| with |stream|.
+1. Return |result|.
+
+
+
+## The {{FileSystemDirectoryHandle}} interface ## {#api-filesystemdirectoryhandle}
+
+
+dictionary FileSystemGetFileOptions {
+ boolean create = false;
+};
+
+dictionary FileSystemGetDirectoryOptions {
+ boolean create = false;
+};
+
+dictionary FileSystemRemoveOptions {
+ boolean recursive = false;
+};
+
+[Exposed=(Window,Worker), SecureContext, Serializable]
+interface FileSystemDirectoryHandle : FileSystemHandle {
+ async iterable;
+
+ Promise getFileHandle(USVString name, optional FileSystemGetFileOptions options = {});
+ Promise getDirectoryHandle(USVString name, optional FileSystemGetDirectoryOptions options = {});
+
+ Promise removeEntry(USVString name, optional FileSystemRemoveOptions options = {});
+
+ Promise?> resolve(FileSystemHandle possibleDescendant);
+};
+
+
+A {{FileSystemDirectoryHandle}}'s associated [=FileSystemHandle/entry=] must be a [=directory entry=].
+
+{{FileSystemDirectoryHandle}} objects are [=serializable objects=]. Their [=serialization steps=] and
+[=deserialization steps=] are the same as those for {{FileSystemHandle}}.
+
+### Directory iteration ### {#api-filesystemdirectoryhandle-asynciterable}
+
+
+ : 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.
+
+
+Issue(173): In the future we might want to add arguments to the async iterable declaration to
+support for example recursive iteration.
+
+
+The [=asynchronous iterator initialization steps=] for a {{FileSystemDirectoryHandle}} |handle|
+ant its async iterator |iterator| are:
+
+1. Let |access| be the result of running |handle|'s [=FileSystemHandle/entry=]'s
+ [=entry/query access=] given "`read`".
+
+1. If |access| is not "{{PermissionState/granted}}",
+ throw a {{NotAllowedError}}.
+
+1. Set |iterator|'s past results to an empty [=/set=].
+
+
+
+
+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 |access| be the result of running |handle|'s [=FileSystemHandle/entry=]'s
+ [=entry/query access=] given "`read`".
+
+1. If |access| 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|.
+
+
+
+### The {{FileSystemDirectoryHandle/getFileHandle()}} method ### {#api-filesystemdirectoryhandle-getfilehandle}
+
+
+ : |fileHandle| = await |directoryHandle| . {{FileSystemDirectoryHandle/getFileHandle()|getFileHandle}}(|name|)
+ : |fileHandle| = await |directoryHandle| . {{FileSystemDirectoryHandle/getFileHandle()|getFileHandle}}(|name|, { {{FileSystemGetFileOptions/create}}: false })
+ :: Returns a handle for a file named |name| in the directory represented by |directoryHandle|. If
+ no such file exists, this rejects.
+
+ : |fileHandle| = await |directoryHandle| . {{FileSystemDirectoryHandle/getFileHandle()|getFileHandle}}(|name|, { {{FileSystemGetFileOptions/create}}: true })
+ :: Returns a handle for a file named |name| in the directory represented by |directoryHandle|. If
+ no such file exists, this creates a new file. If no file with named |name| can be created this
+ rejects. Creation can fail because there already is a directory with the same name, because the
+ name uses characters that aren't supported in file names on the underlying file system, or
+ because the user agent for security reasons decided not to allow creation of the file.
+
+ This operation requires write permission, even if the file being returned already exists. If
+ this handle doesn't already have write permission, this could result in a prompt being shown to
+ the user. To get an existing file without needing write permission, call this method
+ with { {{FileSystemGetFileOptions/create}}: false }
.
+
+
+
+The getFileHandle(|name|, |options|) method, when invoked,
+must run these steps:
+
+1. Let |result| be [=a new promise=].
+1. Run the following steps [=in parallel=]:
+ 1. If |name| is not a [=valid file name=], [=/reject=] |result| with a {{TypeError}} and abort.
+
+ 1. Let |entry| be [=this=]'s [=FileSystemHandle/entry=].
+ 1. If |options|.{{FileSystemGetFileOptions/create}} is `true`:
+ 1. Let |access| be the result of running [=this=]'s [=FileSystemHandle/entry=]'s
+ [=entry/request access=] given "`readwrite`".
+ If that throws an exception, [=reject=] |result| with that exception and abort.
+ 1. Otherwise:
+ 1. Let |access| be the result of running [=this=]'s [=FileSystemHandle/entry=]'s
+ [=entry/query access=] given "`read`".
+ 1. If |access| is not "{{PermissionState/granted}}",
+ reject |result| with a {{NotAllowedError}} and abort.
+
+ 1. [=set/For each=] |child| of |entry|'s [=directory entry/children=]:
+ 1. If |child|'s [=entry/name=] equals |name|:
+ 1. If |child| is a [=directory entry=]:
+ 1. [=/Reject=] |result| with a {{TypeMismatchError}} and abort.
+ 1. [=/Resolve=] |result| with a new {{FileSystemFileHandle}} whose [=FileSystemHandle/entry=] is |child| and abort.
+ 1. If |options|.{{FileSystemGetFileOptions/create}} is `false`:
+ 1. [=/Reject=] |result| with a {{NotFoundError}} and abort.
+ 1. Let |child| be a new [=file entry=] whose [=query access=] and [=request access=] algorithms
+ are those of |entry|.
+ 1. Set |child|'s [=entry/name=] to |name|.
+ 1. Set |child|'s [=binary data=] to an empty [=byte sequence=].
+ 1. Set |child|'s [=modification timestamp=] to the current time.
+ 1. [=set/Append=] |child| to |entry|'s [=directory entry/children=].
+ 1. If creating |child| in the underlying file system throws an exception,
+ [=/reject=] |result| with that exception and abort.
+
+ Issue(68): Better specify what possible exceptions this could throw.
+ 1. [=/Resolve=] |result| with a new {{FileSystemFileHandle}} whose [=FileSystemHandle/entry=] is |child|.
+1. Return |result|.
+
+
+
+### The {{FileSystemDirectoryHandle/getDirectoryHandle()}} method ### {#api-filesystemdirectoryhandle-getdirectoryhandle}
+
+
+ : |subdirHandle| = await |directoryHandle| . {{FileSystemDirectoryHandle/getDirectoryHandle()|getDirectoryHandle}}(|name|)
+ : |subdirHandle| = await |directoryHandle| . {{FileSystemDirectoryHandle/getDirectoryHandle()|getDirectoryHandle}}(|name|, { {{FileSystemGetDirectoryOptions/create}}: false })
+ :: Returns a handle for a directory named |name| in the directory represented by
+ |directoryHandle|. If no such directory exists, this rejects.
+
+ : |subdirHandle| = await |directoryHandle| . {{FileSystemDirectoryHandle/getDirectoryHandle()|getDirectoryHandle}}(|name|, { {{FileSystemGetDirectoryOptions/create}}: true })
+ :: Returns a handle for a directory named |name| in the directory represented by
+ |directoryHandle|. If no such directory exists, this creates a new directory. If creating the
+ directory failed, this rejects. Creation can fail because there already is a file with the same
+ name, or because the name uses characters that aren't supported in file names on the underlying
+ file system.
+
+ This operation requires write permission, even if the directory being returned already exists.
+ If this handle doesn't already have write permission, this could result in a prompt being shown
+ to the user. To get an existing directory without needing write permission, call this method
+ with { {{FileSystemGetDirectoryOptions/create}}: false }
.
+
+
+
+The getDirectoryHandle(|name|, |options|) method, when
+invoked, must run these steps:
+
+1. Let |result| be [=a new promise=].
+1. Run the following steps [=in parallel=]:
+ 1. If |name| is not a [=valid file name=], [=/reject=] |result| with a {{TypeError}} and abort.
+
+ 1. Let |entry| be [=this=]'s [=FileSystemHandle/entry=].
+ 1. If |options|.{{FileSystemGetDirectoryOptions/create}} is `true`:
+ 1. Let |access| be the result of running [=this=]'s [=FileSystemHandle/entry=]'s
+ [=entry/request access=] given "`readwrite`".
+ If that throws an exception, [=reject=] |result| with that exception and abort.
+ 1. Otherwise:
+ 1. Let |access| be the result of running [=this=]'s [=FileSystemHandle/entry=]'s
+ [=entry/query access=] given "`read`".
+ 1. If |access| is not "{{PermissionState/granted}}",
+ reject |result| with a {{NotAllowedError}} and abort.
+
+ 1. [=set/For each=] |child| of |entry|'s [=directory entry/children=]:
+ 1. If |child|'s [=entry/name=] equals |name|:
+ 1. If |child| is a [=file entry=]:
+ 1. [=/Reject=] |result| with a {{TypeMismatchError}} and abort.
+ 1. [=/Resolve=] |result| with a new {{FileSystemDirectoryHandle}} whose [=FileSystemHandle/entry=] is |child| and abort.
+ 1. If |options|.{{FileSystemGetFileOptions/create}} is `false`:
+ 1. [=/Reject=] |result| with a {{NotFoundError}} and abort.
+ 1. Let |child| be a new [=directory entry=] whose [=query access=] and [=request access=]
+ algorithms are those of |entry|.
+ 1. Set |child|'s [=entry/name=] to |name|.
+ 1. Set |child|'s [=directory entry/children=] to an empty [=/set=].
+ 1. [=set/Append=] |child| to |entry|'s [=directory entry/children=].
+ 1. If creating |child| in the underlying file system throws an exception,
+ [=/reject=] |result| with that exception and abort.
+
+ Issue(68): Better specify what possible exceptions this could throw.
+ 1. [=/Resolve=] |result| with a new {{FileSystemDirectoryHandle}} whose [=FileSystemHandle/entry=] is |child|.
+1. Return |result|.
+
+
+
+### The {{FileSystemDirectoryHandle/removeEntry()}} method ### {#api-filesystemdirectoryhandle-removeentry}
+
+
+ : await |directoryHandle| . {{FileSystemDirectoryHandle/removeEntry()|removeEntry}}(|name|)
+ : await |directoryHandle| . {{FileSystemDirectoryHandle/removeEntry()|removeEntry}}(|name|, { {{FileSystemRemoveOptions/recursive}}: false })
+ :: If the directory represented by |directoryHandle| contains a file named |name|, or an empty
+ directory named |name|, this will attempt to delete that file or directory.
+
+ Attempting to delete a file or directory that does not exist is considered success,
+ while attempting to delete a non-empty directory will result in a promise rejection.
+
+ : await |directoryHandle| . {{FileSystemDirectoryHandle/removeEntry()|removeEntry}}(|name|, { {{FileSystemRemoveOptions/recursive}}: true })
+ :: Removes the entry named |name| in the directory represented by |directoryHandle|.
+ If that entry is a directory, its contents will also be deleted recursively.
+ recursively.
+
+ Attempting to delete a file or directory that does not exist is considered success.
+
+
+
+The removeEntry(|name|, |options|) method, when invoked, must run
+these steps:
+
+1. Let |result| be [=a new promise=].
+1. Run the following steps [=in parallel=]:
+ 1. If |name| is not a [=valid file name=], [=/reject=] |result| with a {{TypeError}} and abort.
+
+ 1. Let |entry| be [=this=]'s [=FileSystemHandle/entry=].
+ 1. Let |access| be the result of running [=this=]'s [=FileSystemHandle/entry=]'s
+ [=entry/request access=] given "`readwrite`".
+ If that throws an exception, [=reject=] |result| with that exception and abort.
+ 1. If |access| is not "{{PermissionState/granted}}",
+ reject |result| with a {{NotAllowedError}} and abort.
+
+ 1. [=set/For each=] |child| of |entry|'s [=directory entry/children=]:
+ 1. If |child|'s [=entry/name=] equals |name|:
+ 1. If |child| is a [=directory entry=]:
+ 1. If |child|'s [=directory entry/children=] is not [=set/is empty|empty=] and |options|.{{FileSystemRemoveOptions/recursive}} is `false`:
+ 1. [=/Reject=] |result| with an {{InvalidModificationError}} and abort.
+ 1. [=set/Remove=] |child| from |entry|'s [=directory entry/children=].
+ 1. If removing |child| in the underlying file system throws an exception,
+ [=/reject=] |result| with that exception and abort.
+
+ Note: If {{FileSystemRemoveOptions/recursive}} is `true`, the removal can fail
+ non-atomically. Some files or directories might have been removed while other files
+ or directories still exist.
+
+ Issue(68): Better specify what possible exceptions this could throw.
+ 1. [=/Resolve=] |result| with `undefined`.
+ 1. [=/Reject=] |result| with a {{NotFoundError}}.
+1. Return |result|.
+
+
+
+
+### The {{FileSystemDirectoryHandle/resolve()}} method ### {#api-filesystemdirectoryhandle-resolve}
+
+
+ : |path| = await |directory| . {{FileSystemDirectoryHandle/resolve()|resolve}}( |child| )
+ :: If |child| is equal to |directory|, |path| will be an empty array.
+ :: If |child| is a direct child of |directory|, |path| will be an array containing |child|'s name.
+ :: If |child| is a descendant of |directory|, |path| will be an array containing the names of
+ all the intermediate directories and |child|'s name as last element.
+ For example if |directory| represents `/home/user/project`
+ and |child| represents `/home/user/project/foo/bar`, this will return
+ `['foo', 'bar']`.
+ :: Otherwise (|directory| and |child| are not related), |path| will be null.
+
+
+
+
+// Assume we at some point got a valid directory handle.
+const dir_ref = current_project_dir;
+if (!dir_ref) return;
+
+// Now get a file reference:
+const file_ref = await dir_ref.getFileHandle(filename, { create: true });
+
+// Check if file_ref exists inside dir_ref:
+const relative_path = await dir_ref.resolve(file_ref);
+if (relative_path === null) {
+ // Not inside dir_ref.
+} else {
+ // relative_path is an array of names, giving the relative path
+ // from dir_ref to the file that is represented by file_ref:
+ assert relative_path.pop() === file_ref.name;
+
+ let entry = dir_ref;
+ for (const name of relative_path) {
+ entry = await entry.getDirectory(name);
+ }
+ entry = await entry.getFile(file_ref.name);
+
+ // Now |entry| will represent the same file on disk as |file_ref|.
+ assert await entry.isSameEntry(file_ref) === true;
+}
+
+
+
+
+The resolve(|possibleDescendant|) method,
+when invoked, must return the result of [=entry/resolving=]
+|possibleDescendant|'s [=FileSystemHandle/entry=] relative to [=this=]'s [=FileSystemHandle/entry=].
+
+
+
+
+
+## The {{FileSystemWritableFileStream}} interface ## {#api-filesystemwritablefilestream}
+
+
+enum WriteCommandType {
+ "write",
+ "seek",
+ "truncate",
+};
+
+dictionary WriteParams {
+ required WriteCommandType type;
+ unsigned long long? size;
+ unsigned long long? position;
+ (BufferSource or Blob or USVString)? data;
+};
+
+typedef (BufferSource or Blob or USVString or WriteParams) FileSystemWriteChunkType;
+
+[Exposed=(Window,Worker), SecureContext]
+interface FileSystemWritableFileStream : WritableStream {
+ Promise write(FileSystemWriteChunkType data);
+ Promise seek(unsigned long long position);
+ Promise truncate(unsigned long long size);
+};
+
+
+A {{FileSystemWritableFileStream}} has an associated \[[file]] (a [=file entry=]).
+
+A {{FileSystemWritableFileStream}} has an associated \[[buffer]] (a [=byte sequence=]).
+It is initially empty.
+
+Note: This buffer can get arbitrarily large, so it is expected that implementations will not keep this in memory,
+but instead use a temporary file for this. All access to \[[buffer]] is done in promise returning methods and
+algorithms, so even though operations on it seem sync, implementations can implement them async.
+
+A {{FileSystemWritableFileStream}} has an associated \[[seekOffset]] (a number).
+It is initially 0.
+
+
+A {{FileSystemWritableFileStream}} object is a {{WritableStream}} object with additional
+convenience methods, which operates on a single file on disk.
+
+Upon creation, an underlying sink will have been created and the stream will be usable.
+All operations executed on the stream are queuable and producers will be able to respond to backpressure.
+
+The underlying sink's write method, and therefore {{WritableStreamDefaultWriter/write()|WritableStreamDefaultWriter's write()}}
+method, will accept byte-like data or {{WriteParams}} as input.
+
+The {{FileSystemWritableFileStream}} has a file position cursor initialized at byte offset 0 from the top of the file.
+When using {{FileSystemWritableFileStream/write()|write()}} or by using WritableStream capabilities through the {{WritableStreamDefaultWriter/write()|WritableStreamDefaultWriter's write()}} method, this position will be advanced based on the number of bytes written through the stream object.
+
+Similarly, when piping a {{ReadableStream}} into a {{FileSystemWritableFileStream}} object, this position is updated with the number of bytes that passed through the stream.
+
+{{WritableStream/getWriter()|getWriter()}} returns an instance of {{WritableStreamDefaultWriter}}.
+
+
+
+To
create a new FileSystemWritableFileStream given a [=file entry=] |file|
+in a [=/Realm=] |realm|, perform the following steps:
+
+1. Let |stream| be a [=new=] {{FileSystemWritableFileStream}} in |realm|.
+1. Set |stream|.[=FileSystemWritableFileStream/[[file]]=] to |file|.
+1. Let |writeAlgorithm| be an algorithm which takes a |chunk| argument
+ and returns the result of running the [=write a chunk=] algorithm with |stream| and |chunk|.
+1. Let |closeAlgorithm| be the following steps:
+ 1. Let |closeResult| be [=a new promise=].
+ 1. Run the following steps [=in parallel=]:
+ 1. Let |access| be the result of running |file|'s [=entry/query access=] given "`readwrite`".
+ 1. If |access| is not "{{PermissionState/granted}}",
+ reject |closeResult| with a {{NotAllowedError}} and abort.
+ 1. Perform [=implementation-defined=] malware scans and safe browsing checks.
+ If these checks fail, [=/reject=] |closeResult| with an {{AbortError}} and abort.
+ 1. Set |stream|.[=[[file]]=]'s [=file entry/binary data=] to |stream|.[=[[buffer]]=].
+ If that throws an exception, [=/reject=] |closeResult| with that exception and abort.
+
+ Note: It is expected that this atomically updates the contents of the file on disk
+ being written to.
+ 1. [=/Resolve=] |closeResult| with `undefined`.
+ 1. Return |closeResult|.
+1. Let |highWaterMark| be 1.
+1. Let |sizeAlgorithm| be an algorithm that returns `1`.
+1. [=WritableStream/Set up=] |stream| with
writeAlgorithm set to |writeAlgorithm|,
closeAlgorithm set to |closeAlgorithm|,
highWaterMark set to |highWaterMark|, and
sizeAlgorithm set to |sizeAlgorithm|.
+1. Return |stream|.
+
+
+
+
+The
write a chunk algorithm,
+given a {{FileSystemWritableFileStream}} |stream| and |chunk|,
+runs these steps:
+
+1. Let |input| be the result of [=converting=] |chunk| to a {{FileSystemWriteChunkType}}.
+ If this throws an exception, then return [=a promise rejected with=] that exception.
+1. Let |p| be [=a new promise=].
+1. Run the following steps [=in parallel=]:
+ 1. Let |access| be the result of running |stream|'s [=FileSystemWritableFileStream/[[file]]=]'s
+ [=entry/query access=] given "`readwrite`".
+ 1. If |access| is not "{{PermissionState/granted}}",
+ reject |p| with a {{NotAllowedError}} and abort.
+ 1. Let |command| be |input|.{{WriteParams/type}} if |input| is a {{WriteParams}},
+ and {{WriteCommandType/"write"}} otherwise.
+ 1. If |command| is {{WriteCommandType/"write"}}:
+ 1. Let |data| be |input|.{{WriteParams/data}} if |input| is a {{WriteParams}},
+ and |input| otherwise.
+ 1. If |data| is `undefined`,
+ reject |p| with a {{TypeError}} and abort.
+ 1. Let |writePosition| be |stream|.[=[[seekOffset]]=].
+ 1. If |input| is a {{WriteParams}} and |input|.{{WriteParams/position}} is not `undefined`,
+ set |writePosition| to |input|.{{WriteParams/position}}.
+ 1. Let |oldSize| be |stream|.[=[[buffer]]=]'s [=byte sequence/length=].
+ 1. If |data| is a {{BufferSource}},
+ let |dataBytes| be [=get a copy of the buffer source|a copy of=] |data|.
+ 1. Else if |data| is a {{Blob}}:
+ 1. Let |dataBytes| be the result of performing the
+
read operation on |data|.
+ If this throws an exception, [=/reject=] |p| with that exception and abort.
+ 1. Else:
+ 1. [=Assert=]: |data| is a {{USVString}}.
+ 1. Let |dataBytes| be the result of [=UTF-8 encoding=] |data|.
+ 1. If |writePosition| is larger than |oldSize|,
+ append |writePosition| - |oldSize| `0x00` (NUL) bytes to the end of |stream|.[=[[buffer]]=].
+
+ Note: Implementations are expected to behave as if the skipped over file contents
+ are indeed filled with NUL bytes. That doesn't mean these bytes have to actually be
+ written to disk and take up disk space. Instead most file systems support so called
+ sparse files, where these NUL bytes don't take up actual disk space.
+
+ 1. Let |head| be a [=byte sequence=] containing the first |writePosition| bytes of |stream|.[=[[buffer]]=].
+ 1. Let |tail| be an empty [=byte sequence=].
+ 1. If |writePosition| + |data|.[=byte sequence/length=] is smaller than |oldSize|:
+ 1. Let |tail| be a [=byte sequence=] containing the last
+ |oldSize| - (|writePosition| + |data|.[=byte sequence/length=]) bytes of |stream|.[=[[buffer]]=].
+ 1. Set |stream|.[=[[buffer]]=] to the concatenation of |head|, |data| and |tail|.
+ 1. If the operations modifying |stream|.[=[[buffer]]=] in the previous steps failed
+ due to exceeding the [=storage quota=], [=/reject=] |p| with a {{QuotaExceededError}} and abort,
+ leaving |stream|.[=[[buffer]]=] unmodified.
+
+ Note: [=Storage quota=] only applies to files stored in the [=origin private file system=].
+ However this operation could still fail for other files, for example if the disk being written
+ to runs out of disk space.
+ 1. Set |stream|.[=[[seekOffset]]=] to |writePosition| + |data|.[=byte sequence/length=].
+ 1. [=/Resolve=] |p|.
+ 1. Else if |command| is {{WriteCommandType/"seek"}}:
+ 1. If |chunk|.{{WriteParams/position}} is `undefined`,
+ [=/reject=] |p| with a {{TypeError}} and abort.
+ 1. Set |stream|.[=[[seekOffset]]=] to |chunk|.{{WriteParams/position}}.
+ 1. [=/Resolve=] |p|.
+ 1. Else if |command| is {{WriteCommandType/"truncate"}}:
+ 1. If |chunk|.{{WriteParams/size}} is `undefined`,
+ [=/reject=] |p| with a {{TypeError}} and abort.
+ 1. Let |newSize| be |chunk|.{{WriteParams/size}}.
+ 1. Let |oldSize| be |stream|.[=[[buffer]]=]'s [=byte sequence/length=].
+ 1. If |newSize| is larger than |oldSize|:
+ 1. Set |stream|.[=[[buffer]]=] to a [=byte sequence=] formed by concating
+ |stream|.[=[[buffer]]=] with a [=byte sequence=] containing |newSize|-|oldSize| `0x00` bytes.
+ 1. If the operation in the previous step failed due to exceeding the [=storage quota=],
+ [=/reject=] |p| with a {{QuotaExceededError}} and abort,
+ leaving |stream|.[=[[buffer]]=] unmodified.
+
+ Note: [=Storage quota=] only applies to files stored in the [=origin private file system=].
+ However this operation could still fail for other files, for example if the disk being written
+ to runs out of disk space.
+ 1. Else if |newSize| is smaller than |oldSize|:
+ 1. Set |stream|.[=[[buffer]]=] to a [=byte sequence=] containing the first |newSize| bytes
+ in |stream|.[=[[buffer]]=].
+ 1. If |stream|.[=[[seekOffset]]=] is bigger than |newSize|,
+ set |stream|.[=[[seekOffset]]=] to |newSize|.
+ 1. [=/Resolve=] |p|.
+1. Return |p|.
+
+
+
+### The {{FileSystemWritableFileStream/write()}} method ### {#api-filesystemwritablefilestream-write}
+
+
+ : await |stream| . {{FileSystemWritableFileStream/write()|write}}(|data|)
+ : await |stream| . {{FileSystemWritableFileStream/write()|write}}({
+ {{WriteParams/type}}: {{WriteCommandType/"write"}},
+ {{WriteParams/data}}: |data| })
+ :: Writes the content of |data| into the file associated with |stream| at the current file
+ cursor offset.
+
+ No changes are written to the actual file on disk until the stream has been closed.
+ Changes are typically written to a temporary file instead.
+
+ : await |stream| . {{FileSystemWritableFileStream/write()|write}}({
+ {{WriteParams/type}}: {{WriteCommandType/"write"}},
+ {{WriteParams/position}}: |position|,
+ {{WriteParams/data}}: |data| })
+ :: Writes the content of |data| into the file associated with |stream| at |position|
+ bytes from the top of the file. Also updates the current file cursor offset to the
+ end of the written data.
+
+ No changes are written to the actual file on disk until the stream has been closed.
+ Changes are typically written to a temporary file instead.
+
+ : await |stream| . {{FileSystemWritableFileStream/write()|write}}({
+ {{WriteParams/type}}: {{WriteCommandType/"seek"}},
+ {{WriteParams/position}}: |position| })
+ :: Updates the current file cursor offset the |position| bytes from the top of the file.
+
+ : await |stream| . {{FileSystemWritableFileStream/write()|write}}({
+ {{WriteParams/type}}: {{WriteCommandType/"truncate"}},
+ {{WriteParams/size}}: |size| })
+ :: Resizes the file associated with |stream| to be |size| bytes long. If |size| is larger than
+ the current file size this pads the file with null bytes, otherwise it truncates the file.
+
+ The file cursor is updated when {{truncate}} is called. If the offset is smaller than offset,
+ it remains unchanged. If the offset is larger than |size|, the offset is set to |size| to
+ ensure that subsequent writes do not error.
+
+ No changes are written to the actual file until on disk until the stream has been closed.
+ Changes are typically written to a temporary file instead.
+
+
+
+The write(|data|) method, when invoked, must run
+these steps:
+
+1. Let |writer| be the result of [=WritableStream/getting a writer=] for [=this=].
+1. Let |result| be the result of [=WritableStreamDefaultWriter/writing a chunk=] to |writer| given
+ |data|.
+1. [=WritableStreamDefaultWriter/Release=] |writer|.
+1. Return |result|.
+
+
+
+### The {{FileSystemWritableFileStream/seek()}} method ### {#api-filesystemwritablefilestream-seek}
+
+
+ : await |stream| . {{FileSystemWritableFileStream/seek()|seek}}(|position|)
+ :: Updates the current file cursor offset the |position| bytes from the top of the file.
+
+
+
+The seek(|position|) method, when invoked, must run these
+steps:
+
+1. Let |writer| be the result of [=WritableStream/getting a writer=] for [=this=].
+1. Let |result| be the result of [=WritableStreamDefaultWriter/writing a chunk=] to |writer| given
+ «[ "{{WriteParams/type}}" → {{WriteCommandType/"seek"}}, "{{WriteParams/position}}" →
+ |position| ]».
+1. [=WritableStreamDefaultWriter/Release=] |writer|.
+1. Return |result|.
+
+
+
+### The {{FileSystemWritableFileStream/truncate()}} method ### {#api-filesystemwritablefilestream-truncate}
+
+
+ : await |stream| . {{FileSystemWritableFileStream/truncate()|truncate}}(|size|)
+ :: Resizes the file associated with |stream| to be |size| bytes long. If |size| is larger than
+ the current file size this pads the file with null bytes, otherwise it truncates the file.
+
+ The file cursor is updated when {{truncate}} is called. If the offset is smaller than offset,
+ it remains unchanged. If the offset is larger than |size|, the offset is set to |size| to
+ ensure that subsequent writes do not error.
+
+ No changes are written to the actual file until on disk until the stream has been closed.
+ Changes are typically written to a temporary file instead.
+
+
+
+The truncate(|size|) method, when invoked, must run these
+steps:
+
+1. Let |writer| be the result of [=WritableStream/getting a writer=] for [=this=].
+1. Let |result| be the result of [=WritableStreamDefaultWriter/writing a chunk=] to |writer| given
+ «[ "{{WriteParams/type}}" → {{WriteCommandType/"truncate"}}, "{{WriteParams/size}}" →
+ |size| ]».
+1. [=WritableStreamDefaultWriter/Release=] |writer|.
+1. Return |result|.
+
+
+
+
+# Accessing the Origin Private File System # {#sandboxed-filesystem}
+
+The origin private file system is a [=storage endpoint=] whose
+identifier is `"fileSystem"`,
+types are `« "local" »`,
+and quota is null.
+
+Issue: Storage endpoints should be defined in [[storage]] itself, rather
+than being defined here. So merge this into the table there.
+
+Note: While user agents will typically implement this by persisting the contents of this
+[=origin private file system=] to disk, it is not intended that the contents are easily
+user accessible. Similarly there is no expectation that files or directories with names
+matching the names of children of the [=origin private file system=] exist.
+
+
+[SecureContext]
+partial interface StorageManager {
+ Promise getDirectory();
+};
+
+
+
+ : |directoryHandle| = await navigator . storage . {{StorageManager/getDirectory()}}
+ :: Returns the root directory of the origin private file system.
+
+
+
+The getDirectory() method, when
+invoked, must run these steps:
+
+1. Let |environment| be the [=current settings object=].
+
+1. Let |map| be the result of running [=obtain a local storage bottle map=]
+ with |environment| and `"fileSystem"`. If this returns failure,
+ return [=a promise rejected with=] a {{SecurityError}}.
+
+1. If |map|["root"] does not [=map/exist=]:
+ 1. Let |dir| be a new [=directory entry=] whose [=query access=] and [=request access=] algorithms
+ always return "{{PermissionState/granted}}".
+ 1. Set |dir|'s [=entry/name=] to the empty string.
+ 1. Set |dir|'s [=directory entry/children=] to an empty [=/set=].
+ 1. Set |map|["root"] to |dir|.
+
+1. Return [=a promise resolved with=] a new {{FileSystemDirectoryHandle}},
+ whose associated [=FileSystemHandle/entry=] is |map|["root"].
+
+
+
+
+Acknowledgments
+
+This standard is written by Marijn Kruisselbrink
+(Google, mek@chromium.org).
+
+
+
This Living Standard includes material copied from W3C WICG's
+File System Access, which is
+available under the
+W3C Software and Document License.