diff --git a/index.bs b/index.bs
index 206f1cc..267287d 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 2023-09
+Text Macro: LATESTRD 2025-09
Abstract: File System defines infrastructure for file systems as well as their API.
Translation: ja https://triple-underscore.github.io/fs-ja.html
Translation: zh-Hans https://htmlspecs.com/fs/
diff --git a/review-drafts/2025-09.bs b/review-drafts/2025-09.bs
new file mode 100644
index 0000000..5627873
--- /dev/null
+++ b/review-drafts/2025-09.bs
@@ -0,0 +1,1783 @@
+
+Group: WHATWG
+Status: RD
+Date: 2025-09-15
+H1: File System
+Shortname: fs
+Text Macro: TWITTER whatfilesystem
+Text Macro: LATESTRD 2025-09
+Abstract: File System defines infrastructure for file systems as well as their API.
+Translation: ja https://triple-underscore.github.io/fs-ja.html
+Translation: zh-Hans https://htmlspecs.com/fs/
+Translation: ko https://ko.htmlspecs.com/fs/
+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: current realm; url: current-realm
+ type: dfn; text: realm; url: realm
+urlPrefix: https://storage.spec.whatwg.org/; spec: storage
+ type: dfn; text: storage; url: site-storage
+ type: dfn; text: storage bucket; url: storage-bucket
+
+
+
+
+
+# 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}
+
+A file system entry is either a [=file entry=] or a [=directory entry=].
+
+Each [=/file system entry=] has an associated
+query access
+algorithm, which takes "`read`" or "`readwrite`" mode and
+returns a [=/file system access result=].
+Unless specified otherwise it returns a [=/file system access result=] with a
+[=file system access result/permission state=] of "{{PermissionState/denied}}"
+and with an [=file system access result/error name=] of the empty string.
+
+Each [=/file system entry=] has an associated
+request access
+algorithm, which takes "`read`" or "`readwrite`" mode and
+returns a [=/file system access result=].
+Unless specified otherwise it returns a [=/file system access result=] with a
+[=file system access result/permission state=] of "{{PermissionState/denied}}"
+and with an [=file system access result/error name=] of the empty string.
+
+A file system access result is a [=struct=] encapsulating the
+result of [=file system entry/query access|querying=] or
+[=file system entry/request access|requesting=] access to the file system.
+It has the following [=struct/items=]:
+
+: permission state
+:: A {{PermissionState}}
+: error name
+:: A [=string=] which must be the empty string if
+ [=file system access result/permission state=] is
+ "{{PermissionState/granted}}"; otherwise an
+ [=DOMException/name=] listed in the `DOMException` names table.
+ It is expected that in most cases when
+ [=file system access result/permission state=] is not
+ "{{PermissionState/granted}}", this should be "{{NotAllowedError}}".
+
+ Dependent specifications may consider this API a
+[=powerful feature=]. However, unlike other [=powerful features=] whose
+[=permission request algorithm=] may throw, [=/file system entry=]'s
+[=file system entry/query access=] and [=file system entry/request access=]
+algorithms must run [=in parallel=] on the [=file system queue=] and are
+therefore not allowed to throw. Instead, the caller is expected to
+[=queue a storage task=] to [=/reject=], as appropriate,
+should these algorithms return an [=file system access result/error name=]
+other than the empty string.
+
+Note: Implementations that only implement this specification and not dependent
+specifications do not need to bother implementing [=/file system entry=]'s
+[=file system entry/query access=] and [=file system entry/request access=].
+
+Issue(101): Make access check algorithms associated with a FileSystemHandle.
+
+Each [=/file system 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=]), a
+modification timestamp (a number representing the number of milliseconds since the Unix Epoch),
+a lock (a string that may exclusively be "`open`", "`taken-exclusive`" or "`taken-shared`")
+and a shared lock count (a number representing the number shared locks that are taken at a given point in time).
+
+A user agent has an associated file system queue which is the
+result of [=starting a new parallel queue=]. This queue is to be used for all
+file system operations.
+
+
+To take a [=file entry/lock=] with a |value| of
+"`exclusive`" or "`shared`" on a given [=file entry=] |file|:
+
+1. Let |lock| be the |file|'s [=file entry/lock=].
+1. Let |count| be the |file|'s [=file entry/shared lock count=].
+1. If |value| is "`exclusive`":
+ 1. If |lock| is "`open`":
+ 1. Set lock to "`taken-exclusive`".
+ 1. Return "`success`".
+1. If |value| is "`shared`":
+ 1. If |lock| is "`open`":
+ 1. Set |lock| to "`taken-shared`".
+ 1. Set |count| to 1.
+ 1. Return "`success`".
+ 1. Otherwise, if |lock| is "`taken-shared`":
+ 1. Increase |count| by 1.
+ 1. Return "`success`".
+1. Return "`failure`".
+
+Note: These steps have to be run on the [=file system queue=].
+
+
+
+
+To release a [=file entry/lock=] on a given
+[=file entry=] |file|:
+
+1. Let |lock| be the |file|'s associated [=file entry/lock=].
+1. Let |count| be the |file|'s [=file entry/shared lock count=].
+1. If |lock| is "`taken-shared`":
+ 1. Decrease |count| by 1.
+ 1. If |count| is 0, set |lock| to "`open`".
+1. Otherwise, set |lock| to "`open`".
+
+Note: These steps have to be run on the [=file system queue=].
+
+
+
+Note: Locks help prevent concurrent modifications to a file. A {{FileSystemWritableFileStream}}
+requires a shared lock, while a {{FileSystemSyncAccessHandle}} requires an exclusive one.
+
+A directory entry additionally consists of a [=/set=] of
+children, which are themselves [=/file system entries=].
+Each member is either a [=/file entry=] or a [=/directory entry=].
+
+A [=/file system entry=] |entry| should be [=list/contained=] in the [=directory entry/children=] of at most one
+[=directory entry=], and that directory entry is also known as |entry|'s
+parent.
+A [=/file system entry=]'s [=file system entry/parent=] is null if no such directory entry exists.
+
+Note: Two different [=/file system 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.
+
+[=/File system 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 [=directory entry/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.
+
+A [=/file system entry=] |a| is the same entry as
+a [=/file system 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.
+
+
+
+To resolve a
+[=/file system locator=] |child| relative to a [=directory locator=] |root|:
+
+1. Let |result| be [=a new promise=].
+1. [=Enqueue the following steps=] to the [=file system queue=]:
+ 1. If |child|'s [=FileSystemHandle/locator=]'s [=file system locator/root=]
+ is not |root|'s [=FileSystemHandle/locator=]'s [=file system locator/root=],
+ [=/resolve=] |result| with null, and abort these steps.
+
+ 1. Let |childPath| be |child|'s [=FileSystemHandle/locator=]'s [=file system locator/path=].
+ 1. Let |rootPath| be |root|'s [=FileSystemHandle/locator=]'s [=file system locator/path=].
+ 1. If |childPath| is [=the same path as=] |rootPath|,
+ [=/resolve=] |result| with « », and abort these steps.
+
+ 1. If |rootPath|'s [=list/size=] is greater than |childPath|'s [=list/size=],
+ [=/resolve=] |result| with null, and abort these steps.
+
+ 1. [=list/For each=] |index| of |rootPath|'s [=list/indices=]:
+ 1. If |rootPath|.\[[|index|]] is not |childPath|.\[[|index|]], then
+ [=/resolve=] |result| with null, and abort these steps.
+
+ 1. Let |relativePath| be « ».
+ 1. [=list/For each=] |index| of [=the range=] from |rootPath|'s [=list/size=]
+ to |rootPath|'s [=list/size=], exclusive,
+ [=list/append=] |childPath|.\[[|index|]] to |relativePath|.
+
+ 1. [=/Resolve=] |result| with |relativePath|.
+
+1. Return |result|.
+
+
+
+A file system locator represents a potential location of a
+[=/file system entry=]. A [=/file system locator=] is either a [=file locator=]
+or a [=directory locator=].
+
+Each [=/file system locator=] has an associated path (a [=/file system path=]),
+a kind (a {{FileSystemHandleKind}}), and
+a root (a [=file system root=]).
+
+Issue(109): Consider giving each locator a [=storage bucket=].
+
+A file locator is a [=/file system locator=] whose
+[=file system locator/kind=] is "{{FileSystemHandleKind/file}}".
+A directory locator is a [=/file system locator=] whose
+[=file system locator/kind=] is "{{FileSystemHandleKind/directory}}".
+
+A file system root is an opaque [=string=] whose value is
+[=implementation-defined=].
+
+For a [=/file system locator=] |locator|
+whichs [=locate an entry|locates to=] a [=file entry=] |entry| that conceptually
+exists at the path `data/drafts/example.txt` relative to the root directory of
+a [=/bucket file system=],
+|locator|'s [=file system locator/kind=] has to be "{{FileSystemHandleKind/file}}",
+|locator|'s [=file system locator/path=] has to be « "`data`", "`drafts`", "`example.txt`" », and
+|locator|'s [=file system locator/root=] might include relevant identifying
+information such as the [=storage bucket=] and the disk drive.
+
+A [=/file system locator=] |a| is the same locator as
+a [=/file system locator=] |b| if
+|a|'s [=file system locator/kind=] is |b|'s [=file system locator/kind=],
+|a|'s [=file system locator/root=] is |b|'s [=file system locator/root=], and
+|a|'s [=file system locator/path=] is [=the same path as=] |b|'s [=file system locator/path=].
+
+
+The locate an entry algorithm given a
+[=/file system locator=] |locator| runs an [=implementation-defined=] series of steps adhering to
+these constraints:
+
+- If |locator| is a [=file locator=], they return a [=file entry=] or null.
+- If |locator| is a [=directory locator=], they return a [=directory entry=] or null.
+- If these steps return a non-null |entry|, then:
+ - [=Getting the locator=] with |entry| returns |locator|,
+ provided no intermediate file system operations were run.
+ - |entry|'s [=file system entry/name=] is the last [=list/item=] of |locator|'s
+ [=file system locator/path=].
+
+
+
+
+The get the locator algorithm given
+[=/file system entry=] |entry| runs an [=implementation-defined=] series of steps adhering to these
+constraints:
+
+- If |entry| is a [=file entry=], they return a [=file locator=].
+- If |entry| is a [=directory entry=], they return a [=directory locator=].
+- If these steps return |locator|, then:
+ - [=Locating an entry=] with |locator| returns |entry|,
+ provided no intermediate file system operations were run.
+ - |entry|'s [=file system entry/name=] is the last [=list/item=] of |locator|'s
+ [=file system locator/path=].
+
+
+
+A file system path is a [=/list=] of one or more [=strings=].
+This may be a virtual path that is mapped to real location on disk or in memory,
+may correspond directly to a path on the local file system, or may not
+correspond to any file on disk at all. The actual physical location of the
+corresponding [=/file system entry=] is [=implementation-defined=].
+
+Let |path| be the [=/list=]
+« "`data`", "`drafts`", "`example.txt`" ».
+There is no expectation that a file named `example.txt` exists anywhere on disk.
+
+A [=/file system path=] |a| is the same path as
+a [=/file system path=] |b| if
+|a|'s [=list/size=] is the same as |b|'s [=list/size=] and
+[=list/for each=] |index| of |a|'s [=list/indices=]
+|a|.\[[|index|]] is |b|.\[[|index|]].
+
+
The contents of a [=/file system locator=], including its
+[=file system locator/path=], are not expected to be shared in their entirety
+with the website process. The [=/file system path=] might contain components
+which are not known to the website unless the [=/file system locator=] is later
+[=file system locator/resolved=] relative to a parent [=directory locator=].
+
+## 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 is associated with a locator (a
+[=/file system locator=]).
+
+Note: Multiple {{FileSystemHandle}} objects can have
+[=the same locator as|the same=] [=/file system locator=].
+
+A {{FileSystemHandle}}
+is in a bucket file system
+if the first [=list/item=] of its [=FileSystemHandle/locator=]'s
+[=file system locator/path=] is the empty string.
+
+Note: This is a bit magical, but it works since only the root directory of a
+[=/bucket file system=] can have a [=file system locator/path=] which
+[=list/contains=] an empty string. See {{StorageManager/getDirectory()}}.
+All other [=list/item=]s of a [=file system locator/path=] will be a
+[=valid file name=].
+
+Issue(109): Consider improving this situation by giving each locator a
+[=storage bucket=].
+
+
+{{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|.\[[Locator]] to |value|'s [=FileSystemHandle/locator=].
+
+
+
+
+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}}" {{DOMException}}.
+1. Set |value|'s [=FileSystemHandle/locator=] to |serialized|.\[[Locator]].
+
+
+
+
+ : |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 last path component of |handle|'s
+ [=FileSystemHandle/locator=]'s [=file system locator/path=].
+
+
+The kind getter steps are to return
+[=this=]'s [=FileSystemHandle/locator=]'s [=file system locator/kind=].
+
+The name getter steps are to return
+the last [=list/item=] (a [=string=]) of
+[=this=]'s [=FileSystemHandle/locator=]'s [=file system locator/path=].
+
+### 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 steps are:
+
+1. Let |realm| be [=this=]'s [=relevant Realm=].
+1. Let |p| be [=a new promise=] in |realm|.
+1. [=Enqueue the following steps=] to the [=file system queue=]:
+ 1. If [=this=]'s [=FileSystemHandle/locator=] is
+ [=the same locator as=] |other|'s [=FileSystemHandle/locator=],
+ [=/resolve=] |p| with true.
+ 1. Otherwise [=/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 = {});
+ [Exposed=DedicatedWorker]
+ Promise createSyncAccessHandle();
+};
+
+
+Note: A {{FileSystemFileHandle}}'s associated [=FileSystemHandle/locator=]'s
+[=file system locator/kind=] is "{{FileSystemHandleKind/file}}".
+
+
+To
+create a child `FileSystemFileHandle`
+given a [=directory locator=] |parentLocator| and a string |name| in a [=/Realm=] |realm|:
+
+1. Let |handle| be a [=new=] {{FileSystemFileHandle}} in |realm|.
+1. Let |childType| be "{{FileSystemHandleKind/file}}".
+1. Let |childRoot| be a copy of |parentLocator|'s [=file system locator/root=].
+1. Let |childPath| be the result of [=list/clone|cloning=] |parentLocator|'s
+ [=file system locator/path=] and [=list/append|appending=] |name|.
+1. Set |handle|'s [=FileSystemHandle/locator=] to a [=/file system locator=] whose
+ [=file system locator/kind=] is |childType|,
+ [=file system locator/root=] is |childRoot|, and
+ [=file system locator/path=] is |childPath|.
+1. Return |handle|.
+
+
+
+
+To
+create a new `FileSystemFileHandle`
+given a [=/file system root=] |root| and a [=/file system path=] |path|
+in a [=/Realm=] |realm|:
+
+1. Let |handle| be a [=new=] {{FileSystemFileHandle}} in |realm|.
+1. Set |handle|'s [=FileSystemHandle/locator=] to a [=/file system locator=] whose
+ [=file system locator/kind=] is "{{FileSystemHandleKind/file}}",
+ [=file system locator/root=] is |root|, and
+ [=file system locator/path=] is |path|.
+1. Return |handle|.
+
+
+
+{{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 [=file entry=]
+ [=locate an entry|locatable=] by |handle|'s [=FileSystemHandle/locator=].
+ 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 steps are:
+
+1. Let |result| be [=a new promise=].
+1. Let |locator| be [=this=]'s [=FileSystemHandle/locator=].
+1. Let |global| be [=this=]'s [=relevant global object=].
+1. [=Enqueue the following steps=] to the [=file system queue=]:
+ 1. Let |entry| be the result of [=locating an entry=] given |locator|.
+ 1. Let |accessResult| be the result of running |entry|'s
+ [=file system entry/query access=] given "`read`".
+
+ 1. [=Queue a storage task=] with |global| to run these steps:
+ 1. If |accessResult|'s [=file system access result/permission state=]
+ is not "{{PermissionState/granted}}", [=/reject=] |result| with a
+ {{DOMException}} of |accessResult|'s
+ [=file system access result/error name=] and abort these steps.
+
+ 1. If |entry| is null, [=/reject=] |result| with a
+ "{{NotFoundError}}" {{DOMException}} and abort these steps.
+ 1. [=Assert=]: |entry| is a [=file 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. Set |f|'s {{File/name}} to |entry|'s [=file system entry/name=].
+ 1. Set |f|'s {{File/lastModified}} to |entry|'s [=file entry/modification timestamp=].
+ 1. Set |f|'s {{Blob/type}} to an [=implementation-defined=] value, based on
+ for example |entry|'s [=file system 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 entry=] [=locate an entry|locatable=] by
+ |fileHandle|'s [=FileSystemHandle/locator=] until the stream has been closed.
+ User agents try to ensure that no partial writes happen, i.e. the file
+ 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 entry=] [=locate an entry|locatable=] by |fileHandle|'s [=FileSystemHandle/locator=]
+ 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.
+
+ Creating a {{FileSystemWritableFileStream}} [=file entry/lock/take|takes a shared lock=] on the
+ [=file entry=] [=locate an entry|locatable=] with |fileHandle|'s [=FileSystemHandle/locator=].
+ This prevents the creation of {{FileSystemSyncAccessHandle|FileSystemSyncAccessHandles}}
+ for the entry, until the stream is closed.
+
+
+See WICG/file-system-access issue #67
+for 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. In-place writes are available for files in a
+[=/bucket file system=] via the {{FileSystemSyncAccessHandle}} interface.
+
+
+The
createWritable(|options|) method steps are:
+
+1. Let |result| be [=a new promise=].
+1. Let |locator| be [=this=]'s [=FileSystemHandle/locator=].
+1. Let |realm| be [=this=]'s [=relevant Realm=].
+1. Let |global| be [=this=]'s [=relevant global object=].
+1. [=Enqueue the following steps=] to the [=file system queue=]:
+ 1. Let |entry| be the result of [=locating an entry=] given |locator|.
+ 1. Let |accessResult| be the result of running |entry|'s
+ [=file system entry/request access=] given "`readwrite`".
+ 1. If |accessResult|'s [=file system access result/permission state=]
+ is not "{{PermissionState/granted}}", [=queue a storage task=] with
+ |global| to [=/reject=] |result| with a {{DOMException}} of
+ |accessResult|'s [=file system access result/error name=] and
+ abort these steps.
+
+ 1. If |entry| is `null`, [=queue a storage task=] with |global| to [=/reject=]
+ |result| with a "{{NotFoundError}}" {{DOMException}} and abort these steps.
+ 1. [=Assert=]: |entry| is a [=file entry=].
+
+ 1. Let |lockResult| be the result of [=file entry/lock/take|taking a lock=]
+ with "`shared`" on |entry|.
+
+ 1. [=Queue a storage task=] with |global| to run these steps:
+ 1. If |lockResult| is "`failure`", [=/reject=] |result| with a
+ "{{NoModificationAllowedError}}" {{DOMException}} and abort these steps.
+
+ 1. Let |stream| be the result of
creating a new `FileSystemWritableFileStream`
+ for |entry| in |realm|.
+ 1. If |options|["{{FileSystemCreateWritableOptions/keepExistingData}}"]
+ is true:
+ 1. Set |stream|'s [=[[buffer]]=] to a copy of |entry|'s
+ [=file entry/binary data=].
+ 1. [=/Resolve=] |result| with |stream|.
+
+1. Return |result|.
+
+
+
+### The {{FileSystemFileHandle/createSyncAccessHandle()}} method ### {#api-filesystemfilehandle-createsyncaccesshandle}
+
+
+ : |handle| = await |fileHandle| . {{FileSystemFileHandle/createSyncAccessHandle()|createSyncAccessHandle}}()
+ :: Returns a {{FileSystemSyncAccessHandle}} that can be used to read from/write to the file.
+ Changes made through |handle| might be immediately reflected in the
+ [=file entry=] [=locate an entry|locatable=] by |fileHandle|'s [=FileSystemHandle/locator=].
+ To ensure the changes are reflected in this file, the handle can be flushed.
+
+ Creating a {{FileSystemSyncAccessHandle}} [=file entry/lock/take|takes an exclusive lock=] on the
+ [=file entry=] [=locate an entry|locatable=] with |fileHandle|'s [=FileSystemHandle/locator=].
+ This prevents the creation of further {{FileSystemSyncAccessHandle|FileSystemSyncAccessHandles}}
+ or {{FileSystemWritableFileStream|FileSystemWritableFileStreams}}
+ for the entry, until the access handle is closed.
+
+ The returned {{FileSystemSyncAccessHandle}} offers synchronous methods. This allows for higher performance
+ on contexts where asynchronous operations come with high overhead, e.g., WebAssembly.
+
+ For the time being, this method will only succeed when the |fileHandle|
+ [=FileSystemHandle/is in a bucket file system=].
+
+
+
+The
createSyncAccessHandle() method steps are:
+
+1. Let |result| be [=a new promise=].
+1. Let |locator| be [=this=]'s [=FileSystemHandle/locator=].
+1. Let |realm| be [=this=]'s [=relevant Realm=].
+1. Let |global| be [=this=]'s [=relevant global object=].
+1. Let |isInABucketFileSystem| be true if
+ [=this=] [=FileSystemHandle/is in a bucket file system=];
+ otherwise false.
+1. [=Enqueue the following steps=] to the [=file system queue=]:
+ 1. Let |entry| be the result of [=locating an entry=] given |locator|.
+ 1. Let |accessResult| be the result of running |entry|'s
+ [=file system entry/request access=] given "`readwrite`".
+ 1. If |accessResult|'s [=file system access result/permission state=]
+ is not "{{PermissionState/granted}}", [=queue a storage task=] with
+ |global| to [=/reject=] |result| with a {{DOMException}} of
+ |accessResult|'s [=file system access result/error name=] and
+ abort these steps.
+
+ 1. If |isInABucketFileSystem| is false,
+ [=queue a storage task=] with |global| to
+ [=/reject=] |result| with an "{{InvalidStateError}}" {{DOMException}} and
+ abort these steps.
+
+ 1. If |entry| is `null`, [=queue a storage task=] with |global| to [=/reject=]
+ |result| with a "{{NotFoundError}}" {{DOMException}} and abort these steps.
+ 1. [=Assert=]: |entry| is a [=file entry=].
+
+ 1. Let |lockResult| be the result of [=file entry/lock/take|taking a lock=]
+ with "`exclusive`" on |entry|.
+
+ 1. [=Queue a storage task=] with |global| to run these steps:
+ 1. If |lockResult| is "`failure`", [=/reject=] |result| with a
+ "{{NoModificationAllowedError}}" {{DOMException}} and abort these steps.
+
+ 1. Let |handle| be the result of
creating a new `FileSystemSyncAccessHandle`
+ for |entry| in |realm|.
+ 1. [=/Resolve=] |result| with |handle|.
+
+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);
+};
+
+
+Note: A {{FileSystemDirectoryHandle}}'s associated [=FileSystemHandle/locator=]'s
+[=file system locator/kind=] is "{{FileSystemHandleKind/directory}}".
+
+
+To
+create a child `FileSystemDirectoryHandle`
+given a [=directory locator=] |parentLocator| and a string |name| in a [=/Realm=] |realm|:
+
+1. Let |handle| be a [=new=] {{FileSystemDirectoryHandle}} in |realm|.
+1. Let |childType| be "{{FileSystemHandleKind/directory}}".
+1. Let |childRoot| be a copy of |parentLocator|'s [=file system locator/root=].
+1. Let |childPath| be the result of [=list/clone|cloning=] |parentLocator|'s
+ [=file system locator/path=] and [=list/append|appending=] |name|.
+1. Set |handle|'s [=FileSystemHandle/locator=] to a [=/file system locator=] whose
+ [=file system locator/kind=] is |childType|,
+ [=file system locator/root=] is |childRoot|, and
+ [=file system locator/path=] is |childPath|.
+1. Return |handle|.
+
+
+
+
+To
+create a new `FileSystemDirectoryHandle`
+given a [=/file system root=] |root| and a [=/file system path=] |path|
+in a [=/Realm=] |realm|:
+
+1. Let |handle| be a [=new=] {{FileSystemDirectoryHandle}} in |realm|.
+1. Set |handle|'s [=FileSystemHandle/locator=] to a [=/file system locator=] whose
+ [=file system locator/kind=] is "{{FileSystemHandleKind/directory}}",
+ [=file system locator/root=] is |root|, and
+ [=file system locator/path=] is |path|.
+1. Return |handle|.
+
+
+
+{{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 [=directory entry=]
+ [=locate an entry|locatable=] by |directoryHandle|'s [=FileSystemHandle/locator=].
+ 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(15): 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
+and its async iterator |iterator| are:
+
+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. [=Enqueue the following steps=] to the [=file system queue=]:
+ 1. Let |directory| be the result of [=locating an entry=]
+ given |handle|'s [=FileSystemHandle/locator=].
+ 1. Let |accessResult| be the result of running |directory|'s
+ [=file system entry/query access=] given "`read`".
+
+ 1. [=Queue a storage task=] with |handle|'s [=relevant global object=] to
+ run these steps:
+ 1. If |accessResult|'s [=file system access result/permission state=]
+ is not "{{PermissionState/granted}}", [=/reject=] |promise| with a
+ {{DOMException}} of |accessResult|'s
+ [=file system access result/error name=] and abort these steps.:
+
+ 1. If |directory| is `null`, [=/reject=] |result| with a
+ "{{NotFoundError}}" {{DOMException}} and abort these steps.
+ 1. [=Assert=]: |directory| is a [=directory entry=].
+
+ 1. Let |child| be a [=/file system entry=] in
+ |directory|'s [=directory entry/children=], such that
+ |child|'s [=file system 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`, [=/resolve=] |promise| with `undefined` and
+ abort these steps.
+
+ 1. [=set/Append=] |child|'s [=file system entry/name=] to
+ |iterator|'s [=past results=].
+ 1. If |child| is a [=file entry=]:
+ 1. Let |result| be the result of
+
creating a child `FileSystemFileHandle` with
+ |handle|'s [=FileSystemHandle/locator=] and
+ |child|'s [=file system entry/name=] in |handle|'s [=relevant Realm=].
+ 1. Otherwise:
+ 1. Let |result| be the result of
+
creating a child `FileSystemDirectoryHandle` with
+ |handle|'s [=FileSystemHandle/locator=] and
+ |child|'s [=file system entry/name=] in |handle|'s [=relevant Realm=].
+ 1. [=/Resolve=] |promise| with
+ (|child|'s [=file system 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 entry=]
+ [=locate an entry|locatable=] by |directoryHandle|'s [=FileSystemHandle/locator=].
+ 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 entry=]
+ [=locate an entry|locatable=] by |directoryHandle|'s [=FileSystemHandle/locator=].
+ 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 steps are:
+
+1. Let |result| be [=a new promise=].
+1. Let |realm| be [=this=]'s [=relevant Realm=].
+1. Let |locator| be [=this=]'s [=FileSystemHandle/locator=].
+1. Let |global| be [=this=]'s [=relevant global object=].
+1. [=Enqueue the following steps=] to the [=file system queue=]:
+ 1. If |name| is not a [=valid file name=], [=queue a storage task=] with
+ |global| to [=/reject=] |result| with a {{TypeError}} and
+ abort these steps.
+
+ 1. Let |entry| be the result of [=locating an entry=] given |locator|.
+ 1. If |options|["{{FileSystemGetFileOptions/create}}"] is true:
+ 1. Let |accessResult| be the result of running |entry|'s
+ [=file system entry/request access=] given "`readwrite`".
+ 1. Otherwise:
+ 1. Let |accessResult| be the result of running |entry|'s
+ [=file system entry/query access=] given "`read`".
+
+ 1. [=Queue a storage task=] with |global| to run these steps:
+ 1. If |accessResult|'s [=file system access result/permission state=]
+ is not "{{PermissionState/granted}}", [=/reject=] |result| with a
+ {{DOMException}} of |accessResult|'s
+ [=file system access result/error name=] and abort these steps.
+
+ 1. If |entry| is `null`, [=/reject=] |result| with a
+ "{{NotFoundError}}" {{DOMException}} and abort these steps.
+ 1. [=Assert=]: |entry| is a [=directory entry=].
+
+ 1. [=set/For each=] |child| of |entry|'s [=directory entry/children=]:
+ 1. If |child|'s [=file system entry/name=] equals |name|:
+ 1. If |child| is a [=directory entry=]:
+ 1. [=/Reject=] |result| with a
+ "{{TypeMismatchError}}" {{DOMException}} and abort these steps.
+ 1. [=/Resolve=] |result| with the result of
+
creating a child `FileSystemFileHandle` with |locator| and
+ |child|'s [=file system entry/name=] in |realm| and
+ abort these steps.
+ 1. If |options|["{{FileSystemGetFileOptions/create}}"] is false:
+ 1. [=/Reject=] |result| with a "{{NotFoundError}}" {{DOMException}} and
+ abort these steps.
+ 1. Let |child| be a new [=file entry=] whose [=query access=] and
+ [=request access=] algorithms are those of |entry|.
+ 1. Set |child|'s [=file system 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 these steps.
+
+ Issue(11): Better specify what possible exceptions this could throw.
+ 1. [=/Resolve=] |result| with the result of
+
creating a child `FileSystemFileHandle` with |locator| and
+ |child|'s [=file system entry/name=] in |realm|.
+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 entry=]
+ [=locate an entry|locatable=] by |directoryHandle|'s [=FileSystemHandle/locator=].
+ 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 entry=]
+ [=locate an entry|locatable=] by |directoryHandle|'s [=FileSystemHandle/locator=] .
+ 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 steps are:
+
+1. Let |result| be [=a new promise=].
+1. Let |realm| be [=this=]'s [=relevant Realm=].
+1. Let |locator| be [=this=]'s [=FileSystemHandle/locator=].
+1. Let |global| be [=this=]'s [=relevant global object=].
+1. [=Enqueue the following steps=] to the [=file system queue=]:
+ 1. If |name| is not a [=valid file name=], [=queue a storage task=] with
+ |global| to [=/reject=] |result| with a {{TypeError}} and
+ abort these steps.
+
+ 1. Let |entry| be the result of [=locating an entry=] given |locator|.
+ 1. If |options|["{{FileSystemGetDirectoryOptions/create}}"] is true:
+ 1. Let |accessResult| be the result of running |entry|'s
+ [=file system entry/request access=] given "`readwrite`".
+ 1. Otherwise:
+ 1. Let |accessResult| be the result of running |entry|'s
+ [=file system entry/query access=] given "`read`".
+
+ 1. [=Queue a storage task=] with |global| to run these steps:
+ 1. If |accessResult|'s [=file system access result/permission state=]
+ is not "{{PermissionState/granted}}", [=/reject=] |result| with a
+ {{DOMException}} of |accessResult|'s
+ [=file system access result/error name=] and abort these steps.
+
+ 1. If |entry| is `null`, [=/reject=] |result| with a
+ "{{NotFoundError}}" {{DOMException}} and abort these steps.
+ 1. [=Assert=]: |entry| is a [=directory entry=].
+
+ 1. [=set/For each=] |child| of |entry|'s [=directory entry/children=]:
+ 1. If |child|'s [=file system entry/name=] equals |name|:
+ 1. If |child| is a [=file entry=]:
+ 1. [=/Reject=] |result| with a
+ "{{TypeMismatchError}}" {{DOMException}} and abort these steps.
+ 1. [=/Resolve=] |result| with the result of
+
creating a child `FileSystemDirectoryHandle` with
+ |locator| and |child|'s [=file system entry/name=] in |realm| and
+ abort these steps.
+ 1. If |options|["{{FileSystemGetFileOptions/create}}"] is false:
+ 1. [=/Reject=] |result| with a "{{NotFoundError}}" {{DOMException}} and
+ abort these steps.
+ 1. Let |child| be a new [=directory entry=] whose [=query access=] and
+ [=request access=] algorithms are those of |entry|.
+ 1. Set |child|'s [=file system 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 these steps.
+
+ Issue(11): Better specify what possible exceptions this could throw.
+ 1. [=/Resolve=] |result| with the result of
+
creating a child `FileSystemDirectoryHandle` with
+ |locator| and |child|'s [=file system entry/name=] in |realm|.
+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 entry=] [=locate an entry|locatable=] by |directoryHandle|'s
+ [=FileSystemHandle/locator=] 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 [=/file system entry=] named |name| in the [=directory entry=]
+ [=locate an entry|locatable=] by |directoryHandle|'s [=FileSystemHandle/locator=].
+ If that entry is a directory, its contents will also be deleted recursively.
+
+ Attempting to delete a file or directory that does not exist is considered success.
+
+
+
+The removeEntry(|name|, |options|) method steps are:
+
+1. Let |result| be [=a new promise=].
+1. Let |locator| be [=this=]'s [=FileSystemHandle/locator=].
+1. Let |global| be [=this=]'s [=relevant global object=].
+1. [=Enqueue the following steps=] to the [=file system queue=]:
+ 1. If |name| is not a [=valid file name=], [=queue a storage task=] with
+ |global| to [=/reject=] |result| with a {{TypeError}} and
+ abort these steps.
+
+ 1. Let |entry| be the result of [=locating an entry=] given |locator|.
+ 1. Let |accessResult| be the result of running |entry|'s
+ [=file system entry/request access=] given "`readwrite`".
+
+ 1. [=Queue a storage task=] with |global| to run these steps:
+ 1. If |accessResult|'s [=file system access result/permission state=]
+ is not "{{PermissionState/granted}}", [=/reject=] |result| with a
+ {{DOMException}} of |accessResult|'s
+ [=file system access result/error name=] and abort these steps.
+
+ 1. If |entry| is `null`, [=/reject=] |result| with a
+ "{{NotFoundError}}" {{DOMException}} and abort these steps.
+ 1. [=Assert=]: |entry| is a [=directory entry=].
+
+ 1. [=set/For each=] |child| of |entry|'s [=directory entry/children=]:
+ 1. If |child|'s [=file system 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}}" {{DOMException}} and
+ abort these steps.
+ 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 these steps.
+
+ 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(11): Better specify what possible exceptions this could throw.
+ 1. [=/Resolve=] |result| with `undefined`.
+ 1. [=/Reject=] |result| with a "{{NotFoundError}}" {{DOMException}}.
+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 steps are
+to return the result of [=file system locator/resolving=]
+|possibleDescendant|'s [=FileSystemHandle/locator=]
+relative to [=this=]'s [=FileSystemHandle/locator=].
+
+
+
+## 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|:
+
+1. Let |stream| be a [=new=] {{FileSystemWritableFileStream}} in |realm|.
+1. Set |stream|'s [=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 these steps:
+ 1. Let |closeResult| be [=a new promise=].
+ 1. [=Enqueue the following steps=] to the [=file system queue=]:
+ 1. Let |accessResult| be the result of running |file|'s
+ [=file system entry/query access=] given "`readwrite`".
+
+ 1. [=Queue a storage task=] with |file|'s [=relevant global object=]
+ to run these steps:
+ 1. If |accessResult|'s [=file system access result/permission state=]
+ is not "{{PermissionState/granted}}", [=/reject=] |closeResult|
+ with a {{DOMException}} of |accessResult|'s
+ [=file system access result/error name=] and abort these steps.
+ 1. Run [=implementation-defined=] malware scans and safe browsing checks.
+ If these checks fail, [=/reject=] |closeResult| with an
+ "{{AbortError}}" {{DOMException}} and abort these steps.
+ 1. Set |stream|'s [=FileSystemWritableFileStream/[[file]]=]'s
+ [=file entry/binary data=] to |stream|'s [=[[buffer]]=].
+ If that throws an exception, [=/reject=] |closeResult| with that
+ exception and abort these steps.
+
+ Note: It is expected that this atomically updates the contents of the
+ file on disk being written to.
+
+ 1. [=Enqueue the following steps=] to the [=file system queue=]:
+ 1. [=file entry/lock/release|Release the lock=] on
+ |stream|'s [=FileSystemWritableFileStream/[[file]]=].
+ 1. [=Queue a storage task=] with |file|'s [=relevant global object=]
+ to [=/resolve=] |closeResult| with `undefined`.
+
+ 1. Return |closeResult|.
+1. Let |abortAlgorithm| be these steps:
+ 1. [=enqueue steps|Enqueue this step=] to the [=file system queue=]:
+ 1. [=file entry/lock/release|Release the lock=] on
+ |stream|'s [=FileSystemWritableFileStream/[[file]]=].
+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|,
abortAlgorithm set to |abortAlgorithm|,
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 [=converted to an IDL value|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. [=Enqueue the following steps=] to the [=file system queue=]:
+ 1. Let |accessResult| be the result of running
+ |stream|'s [=FileSystemWritableFileStream/[[file]]=]'s
+ [=file system entry/query access=] given "`readwrite`".
+
+ 1. [=Queue a storage task=] with |stream|'s [=relevant global object=] to
+ run these steps:
+ 1. If |accessResult|'s [=file system access result/permission state=]
+ is not "{{PermissionState/granted}}", [=/reject=] |p| with a
+ {{DOMException}} of |accessResult|'s
+ [=file system access result/error name=] and abort these steps.
+
+ 1. Let |command| be |input|["{{WriteParams/type}}"] if
+ |input| is a [=/dictionary=]; otherwise "{{WriteCommandType/write}}".
+ 1. If |command| is "{{WriteCommandType/write}}":
+ 1. If |input| is `undefined` or |input| is a [=/dictionary=] and
+ |input|["{{WriteParams/data}}"] does not [=map/exists|exist=],
+ [=/reject=] |p| with a {{TypeError}} and abort these steps.
+ 1. Let |data| be |input|["{{WriteParams/data}}"] if
+ |input| is a [=/dictionary=]; otherwise |input|.
+ 1. Let |writePosition| be |stream|'s [=[[seekOffset]]=].
+ 1. If |input| is a [=/dictionary=] and |input|["{{WriteParams/position}}"]
+ [=map/exists=], set |writePosition| to
+ |input|["{{WriteParams/position}}"].
+ 1. Let |oldSize| be |stream|'s [=[[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. Otherwise, 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 these steps.
+ 1. Otherwise:
+ 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|'s [=[[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|'s[=[[buffer]]=].
+ 1. Let |tail| be an empty [=byte sequence=].
+ 1. If |writePosition| + |data|'s [=byte sequence/length=] is smaller than |oldSize|:
+ 1. Let |tail| be a [=byte sequence=] containing the last
+ |oldSize| - (|writePosition| + |data|'s [=byte sequence/length=])
+ bytes of |stream|'s [=[[buffer]]=].
+ 1. Set |stream|'s [=[[buffer]]=] to the concatenation of
+ |head|, |data| and |tail|.
+ 1. If the operations modifying |stream|'s [=[[buffer]]=] in the
+ previous steps failed due to exceeding the [=storage quota=],
+ [=/reject=] |p| with a {{QuotaExceededError}} and abort these steps,
+ leaving |stream|'s [=[[buffer]]=] unmodified.
+
+ Note: [=Storage quota=] only applies to files stored in a
+ [=/bucket 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|'s [=[[seekOffset]]=] to |writePosition| + |data|'s
+ [=byte sequence/length=].
+ 1. [=/Resolve=] |p|.
+ 1. Otherwise, if |command| is "{{WriteCommandType/seek}}":
+ 1. [=Assert=]: |chunk| is a [=/dictionary=].
+ 1. If |chunk|["{{WriteParams/position}}"] does not [=map/exists|exist=],
+ [=/reject=] |p| with a {{TypeError}} and abort these steps.
+ 1. Set |stream|'s [=[[seekOffset]]=] to
+ |chunk|["{{WriteParams/position}}"].
+ 1. [=/Resolve=] |p|.
+ 1. Otherwise, if |command| is "{{WriteCommandType/truncate}}":
+ 1. [=Assert=]: |chunk| is a [=/dictionary=].
+ 1. If |chunk|["{{WriteParams/size}}"] does not [=map/exists|exist=],
+ [=/reject=] |p| with a {{TypeError}} and abort these steps.
+ 1. Let |newSize| be |chunk|["{{WriteParams/size}}"].
+ 1. Let |oldSize| be |stream|'s [=[[buffer]]=]'s [=byte sequence/length=].
+ 1. If |newSize| is larger than |oldSize|:
+ 1. Set |stream|'s [=[[buffer]]=] to a [=byte sequence=] formed by
+ concating |stream|'s [=[[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 these steps,
+ leaving |stream|'s [=[[buffer]]=] unmodified.
+
+ Note: [=Storage quota=] only applies to files stored in a
+ [=/bucket 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. Otherwise, if |newSize| is smaller than |oldSize|:
+ 1. Set |stream|'s [=[[buffer]]=] to a [=byte sequence=] containing the
+ first |newSize| bytes in |stream|'s [=[[buffer]]=].
+ 1. If |stream|'s [=[[seekOffset]]=] is bigger than |newSize|,
+ set |stream|'s [=[[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 {{FileSystemWritableFileStream/truncate()|truncate}} is called.
+ If the cursor is smaller than |size|, it remains unchanged. If the cursor is larger than
+ |size|, it 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 steps are:
+
+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 steps are:
+
+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 {{FileSystemWritableFileStream/truncate()|truncate}} is called.
+ If the cursor is smaller than |size|, it remains unchanged. If the cursor is larger than
+ |size|, it 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 steps are:
+
+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|.
+
+
+
+## The {{FileSystemSyncAccessHandle}} interface ## {#api-filesystemsyncaccesshandle}
+
+
+
+dictionary FileSystemReadWriteOptions {
+ [EnforceRange] unsigned long long at;
+};
+
+[Exposed=DedicatedWorker, SecureContext]
+interface FileSystemSyncAccessHandle {
+ unsigned long long read(AllowSharedBufferSource buffer,
+ optional FileSystemReadWriteOptions options = {});
+ unsigned long long write(AllowSharedBufferSource buffer,
+ optional FileSystemReadWriteOptions options = {});
+
+ undefined truncate([EnforceRange] unsigned long long newSize);
+ unsigned long long getSize();
+ undefined flush();
+ undefined close();
+};
+
+
+
+A {{FileSystemSyncAccessHandle}} has an associated \[[file]]
+(a [=file entry=]).
+
+A {{FileSystemSyncAccessHandle}} has an associated \[[state]],
+a string that may exclusively be "`open`" or "`closed`".
+
+A {{FileSystemSyncAccessHandle}} is an object that is capable of reading from/writing to,
+as well as obtaining and changing the size of, a single file.
+
+A {{FileSystemSyncAccessHandle}} offers synchronous methods. This allows for higher performance on
+contexts where asynchronous operations come with high overhead, e.g., WebAssembly.
+
+A {{FileSystemSyncAccessHandle}} has a file position cursor initialized at byte offset 0 from the top of the file.
+
+
+To
+create a new `FileSystemSyncAccessHandle`
+given a [=file entry=] |file| in a [=/Realm=] |realm|:
+
+1. Let |handle| be a [=new=] {{FileSystemSyncAccessHandle}} in |realm|.
+1. Set |handle|'s [=FileSystemSyncAccessHandle/[[file]]=] to |file|.
+1. Set |handle|'s [=FileSystemSyncAccessHandle/[[state]]=] to "`open`".
+1. Return |handle|.
+
+
+
+### The {{FileSystemSyncAccessHandle/read()}} method ### {#api-filesystemsyncaccesshandle-read}
+
+
+ : |handle| . {{FileSystemSyncAccessHandle/read()|read}}(|buffer|)
+ : |handle| . {{FileSystemSyncAccessHandle/read()|read}}(|buffer|, { {{FileSystemReadWriteOptions/at}} })
+ :: Reads the contents of the file associated with |handle| into |buffer|, optionally at a given offset.
+ :: The file cursor is updated when {{FileSystemSyncAccessHandle/read()}} is called to point to the byte after the last byte read.
+
+
+Issue(35): Specify how Access Handles should react when reading from a file that has been modified externally.
+
+
+The read(|buffer|, {{FileSystemReadWriteOptions}}: |options|) method steps are:
+
+1. If [=this=]'s [=[[state]]=] is "`closed`",
+ [=throw=] an "{{InvalidStateError}}" {{DOMException}}.
+1. Let |bufferSize| be |buffer|'s [=byte length=].
+1. Let |fileContents| be [=this=]'s [=FileSystemSyncAccessHandle/[[file]]=]'s [=file entry/binary data=].
+1. Let |fileSize| be |fileContents|'s [=byte sequence/length=].
+1. Let |readStart| be |options|["{{FileSystemReadWriteOptions/at}}"] if
+ |options|["{{FileSystemReadWriteOptions/at}}"] [=map/exists=]; otherwise
+ [=this=]'s [=FileSystemSyncAccessHandle/file position cursor=].
+1. If the underlying file system does not support reading from a file offset of
+ |readStart|, [=throw=] a {{TypeError}}.
+1. If |readStart| is larger than |fileSize|:
+ 1. Set [=this=]'s [=FileSystemSyncAccessHandle/file position cursor=] to |fileSize|.
+ 1. Return 0.
+1. Let |readEnd| be |readStart| + (|bufferSize| − 1).
+1. If |readEnd| is larger than |fileSize|, set |readEnd| to |fileSize|.
+1. Let |bytes| be a [=byte sequence=] containing the bytes from |readStart| to |readEnd| of |fileContents|.
+1. Let |result| be |bytes|'s [=byte sequence/length=].
+1. If the operations reading from |fileContents| in the previous steps failed:
+ 1. If there were partial reads and the number of bytes that were read into |bytes| is known,
+ set |result| to the number of read bytes.
+ 1. Otherwise set |result| to 0.
+1. Let |arrayBuffer| be |buffer|'s [=underlying buffer=].
+1. [=ArrayBuffer/write|Write=] |bytes| into |arrayBuffer|.
+1. Set [=this=]'s [=FileSystemSyncAccessHandle/file position cursor=] to |readStart| + |result|.
+1. Return |result|.
+
+
+
+### The {{FileSystemSyncAccessHandle/write()}} method ### {#api-filesystemsyncaccesshandle-write}
+
+
+ : |handle| . {{FileSystemSyncAccessHandle/write()|write}}(|buffer|)
+ : |handle| . {{FileSystemSyncAccessHandle/write()|write}}(|buffer|, { {{FileSystemReadWriteOptions/at}} })
+ :: Writes the content of |buffer| into the file associated with |handle|, optionally at a given offset, and returns the number of written bytes.
+ Checking the returned number of written bytes allows callers to detect and handle errors and partial writes.
+ :: The file cursor is updated when {{FileSystemSyncAccessHandle/write()|write}} is called to point to the byte after the last byte written.
+
+
+
+
+The write(|buffer|, {{FileSystemReadWriteOptions}}: |options|) method steps are:
+
+1. If [=this=]'s [=[[state]]=] is "`closed`",
+ [=throw=] an "{{InvalidStateError}}" {{DOMException}}.
+1. Let |writePosition| be |options|["{{FileSystemReadWriteOptions/at}}"] if
+ |options|["{{FileSystemReadWriteOptions/at}}"] [=map/exists=]; otherwise
+ [=this=]'s [=FileSystemSyncAccessHandle/file position cursor=].
+1. If the underlying file system does not support writing to a file offset of
+ |writePosition|, [=throw=] a {{TypeError}}.
+1. Let |fileContents| be a copy of [=this=]'s
+ [=FileSystemSyncAccessHandle/[[file]]=]'s [=file entry/binary data=].
+1. Let |oldSize| be |fileContents|'s [=byte sequence/length=].
+1. Let |bufferSize| be |buffer|'s [=byte length=].
+1. If |writePosition| is larger than |oldSize|,
+ append |writePosition| − |oldSize| 0x00 (NUL) bytes to the end of |fileContents|.
+
+ 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 |fileContents|.
+1. Let |tail| be an empty [=byte sequence=].
+1. If |writePosition| + |bufferSize| is smaller than |oldSize|:
+ 1. Set |tail| to a [=byte sequence=] containing the last
+ |oldSize| − (|writePosition| + |bufferSize|) bytes of |fileContents|.
+1. Let |newSize| be |head|'s [=byte sequence/length=] + |bufferSize| + |tail|'s [=byte sequence/length=].
+1. If |newSize| − |oldSize| exceeds the available [=storage quota=],
+ [=throw=] a {{QuotaExceededError}}.
+1. Set [=this=]'s [=FileSystemSyncAccessHandle/[[file]]=]'s
+ [=file entry/binary data=] to the concatenation of
+ |head|, the contents of |buffer| and |tail|.
+
+ Note: The mechanism used to access buffer's contents is left purposely vague.
+ It is likely that implementations will choose to focus on performance by issuing
+ direct write calls to the host operating system (instead of creating a copy of buffer),
+ which prevents a detailed specification of the write order and the results of partial writes.
+
+1. If the operations modifying the [=this=]'s[=FileSystemSyncAccessHandle/[[file]]=]'s
+ [=file entry/binary data=] in the previous steps failed:
+ 1. If there were partial writes and the number of bytes that were written from |buffer| is known:
+ 1. Let |bytesWritten| be the number of bytes that were written from |buffer|.
+ 1. Set [=this=]'s [=FileSystemSyncAccessHandle/file position cursor=] to |writePosition| + |bytesWritten|.
+ 1. Return |bytesWritten|.
+ 1. Otherwise [=throw=] an "{{InvalidStateError}}" {{DOMException}}.
+1. Set [=this=]'s [=FileSystemSyncAccessHandle/file position cursor=] to |writePosition| + |bufferSize|.
+1. Return |bufferSize|.
+
+
+
+### The {{FileSystemSyncAccessHandle/truncate()}} method ### {#api-filesystemsyncaccesshandle-truncate}
+
+
+ : |handle| . {{FileSystemSyncAccessHandle/truncate()|truncate}}(|newSize|)
+ :: Resizes the file associated with |handle| to be |newSize| bytes long. If |newSize| 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 {{FileSystemSyncAccessHandle/truncate()|truncate}} is called. If the cursor is smaller than |newSize|, it remains unchanged. If the cursor is larger than |newSize|, it is set to |newSize|.
+
+
+
+The truncate(|newSize|) method steps are:
+
+1. If [=this=]'s [=[[state]]=] is "`closed`",
+ [=throw=] an "{{InvalidStateError}}" {{DOMException}}.
+1. Let |fileContents| be a copy of [=this=]'s
+ [=FileSystemSyncAccessHandle/[[file]]=]'s [=file entry/binary data=].
+1. Let |oldSize| be the [=byte sequence/length=] of [=this=]'s
+ [=FileSystemSyncAccessHandle/[[file]]=]'s [=file entry/binary data=].
+1. If the underlying file system does not support setting a file's size to
+ |newSize|, [=throw=] a {{TypeError}}.
+1. If |newSize| is larger than |oldSize|:
+ 1. If |newSize| − |oldSize| exceeds the available [=storage quota=],
+ [=throw=] a {{QuotaExceededError}}.
+ 1. Set [=this=]'s [=FileSystemSyncAccessHandle/[[file]]=]'s to a
+ [=byte sequence=] formed by concatenating |fileContents| with a
+ [=byte sequence=] containing |newSize| − |oldSize| 0x00 bytes.
+ 1. If the operations modifying the [=this=]'s
+ [=FileSystemSyncAccessHandle/[[file]]=]'s [=file entry/binary data=]
+ in the previous steps failed,
+ [=throw=] an "{{InvalidStateError}}" {{DOMException}}.
+1. Otherwise, if |newSize| is smaller than |oldSize|:
+ 1. Set [=this=]'s [=FileSystemSyncAccessHandle/[[file]]=]'s to a
+ [=byte sequence=] containing the first |newSize| bytes in |fileContents|.
+ 1. If the operations modifying the [=this=]'s
+ [=FileSystemSyncAccessHandle/[[file]]=]'s [=file entry/binary data=]
+ in the previous steps failed,
+ [=throw=] an "{{InvalidStateError}}" {{DOMException}}.
+1. If [=this=]'s [=FileSystemSyncAccessHandle/file position cursor=] is greater than |newSize|, then set [=FileSystemSyncAccessHandle/file position cursor=] to |newSize|.
+
+
+
+### The {{FileSystemSyncAccessHandle/getSize()}} method ### {#api-filesystemsyncaccesshandle-getsize}
+
+
+ : |handle| . {{FileSystemSyncAccessHandle/getSize()}}
+ :: Returns the size of the file associated with |handle| in bytes.
+
+
+
+The getSize() method steps are:
+
+1. If [=this=]'s [=[[state]]=] is "`closed`",
+ [=throw=] an "{{InvalidStateError}}" {{DOMException}}.
+1. Return [=this=]'s [=FileSystemSyncAccessHandle/[[file]]=]'s
+ [=file entry/binary data=]'s [=byte sequence/length=].
+
+
+
+### The {{FileSystemSyncAccessHandle/flush()}} method ### {#api-filesystemsyncaccesshandle-flush}
+
+
+ : |handle| . {{FileSystemSyncAccessHandle/flush()}}
+ :: Ensures that the contents of the file associated with |handle| contain all the modifications done through {{FileSystemSyncAccessHandle/write()}}.
+
+
+
+The flush() method steps are:
+
+1. If [=this=]'s [=[[state]]=] is "`closed`",
+ [=throw=] an "{{InvalidStateError}}" {{DOMException}}.
+1. Attempt to transfer all cached modifications of the file's content to the
+ file system's underlying storage device.
+
+ Note: This is also known as flushing. This can be a no-op on some file
+ systems, such as in-memory file systems, which do not have a "disk" to flush
+ to.
+
+
+
+### The {{FileSystemSyncAccessHandle/close()}} method ### {#api-filesystemsyncaccesshandle-close}
+
+
+ : |handle| . {{FileSystemSyncAccessHandle/close()}}
+ :: Closes the access handle or no-ops if the access handle is already closed.
+ This disables any further operations on it and
+ [=file entry/lock/release|releases the lock=] on the
+ [=FileSystemSyncAccessHandle/[[file]]=] associated with |handle|.
+
+
+
+The close() method steps are:
+
+1. If [=this=]'s [=[[state]]=] is "`closed`", return.
+1. Set [=this=]'s [=[[state]]=] to "`closed`".
+1. Set |lockReleased| to false.
+1. Let |file| be [=this=]'s [=FileSystemSyncAccessHandle/[[file]]=].
+1. [=Enqueue the following steps=] to the [=file system queue=]:
+ 1. [=file entry/lock/release|Release the lock=] on |file|.
+ 1. Set |lockReleased| to true.
+1. [=Pause=] until |lockReleased| is true.
+
+Note: This method does not guarantee that all file modifications will be
+immediately reflected in the underlying storage device. Call the
+{{FileSystemSyncAccessHandle/flush()}} method first if you require this
+guarantee.
+
+
+
+
+# Accessing the Bucket File System # {#sandboxed-filesystem}
+
+The bucket 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 a
+[=/bucket 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 a [=/bucket file system=] exist.
+
+
+[SecureContext]
+partial interface StorageManager {
+ Promise getDirectory();
+};
+
+
+
+ : |directoryHandle| = await navigator . storage . {{StorageManager/getDirectory()}}
+ :: Returns the root directory of the [=/bucket file system=].
+
+
+
+The
getDirectory() method steps are:
+
+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}}" {{DOMException}}.
+
+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
+ a [=/file system access result=]
+ with a [=file system access result/permission state=]
+ of "{{PermissionState/granted}}" and
+ with an [=file system access result/error name=] of the empty string.
+ 1. Set |dir|'s [=file system entry/name=] to the empty string.
+ 1. Set |dir|'s [=directory entry/children=] to an empty [=/set=].
+ 1. Set |map|["root"] to |dir|.
+
+1. Let |root| be an [=implementation-defined=] opaque [=string=].
+1. Let |path| be « the empty string ».
+1. Let |handle| be the result of
creating a new `FileSystemDirectoryHandle`.
+ given |root| and |path| in the [=current realm=].
+
+ Note: |root| might include relevant identifying information such as the
+ [=storage bucket=].
+
+1. Assert: [=locating an entry=] given |handle|'s [=FileSystemHandle/locator=]
+ returns a [=directory entry=] that is [=the same entry as=] |map|["root"].
+
+1. Return [=a promise resolved with=] |handle|.
+
+
+
+
+Acknowledgments
+
+Many thanks to
+Alex Danilo,
+Anne van Kesteren,
+Anoesj Sadraee,
+Austin Sullivan,
+Chase Phillips,
+Daseul Lee,
+Dru Knox,
+Edgar Chen,
+Emanuel Krivoy,
+Hazim Mohamed,
+Ingvar Stepanyan,
+Jari Jalkanen,
+Joshua Bell,
+Kagami Sascha Rosylight,
+Marcos Cáceres,
+Martin Thomson,
+Olivier Yiptong,
+Philip Jägenstedt,
+Randell Jesup,
+Richard Stotz,
+Ruth John,
+Sid Vishnoi,
+Sihui Liu,
+Stefan Sauer,
+Thomas Steiner,
+Victor Costan, and
+Youenn Fablet
+for being awesome!
+
+
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.