From fb2d06f9737643286a1a86cb0710c733bfa7a8ff Mon Sep 17 00:00:00 2001 From: Anne van Kesteren Date: Mon, 15 Sep 2025 09:41:18 +0200 Subject: [PATCH] Review Draft Publication: September 2025 --- index.bs | 2 +- review-drafts/2025-09.bs | 1783 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 1784 insertions(+), 1 deletion(-) create mode 100644 review-drafts/2025-09.bs 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
+
+ + + +
+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<boolean> 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<File> getFile(); + Promise<FileSystemWritableFileStream> createWritable(optional FileSystemCreateWritableOptions options = {}); + [Exposed=DedicatedWorker] + Promise<FileSystemSyncAccessHandle> 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<USVString, FileSystemHandle>; + + Promise<FileSystemFileHandle> getFileHandle(USVString name, optional FileSystemGetFileOptions options = {}); + Promise<FileSystemDirectoryHandle> getDirectoryHandle(USVString name, optional FileSystemGetDirectoryOptions options = {}); + + Promise<undefined> removeEntry(USVString name, optional FileSystemRemoveOptions options = {}); + + Promise<sequence<USVString>?> 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<undefined> write(FileSystemWriteChunkType data); + Promise<undefined> seek(unsigned long long position); + Promise<undefined> 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<FileSystemDirectoryHandle> 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.