From bab25df761d5fc90bd6a4b920aa956e90f231bf8 Mon Sep 17 00:00:00 2001 From: Emanuel Krivoy Date: Tue, 19 Oct 2021 16:15:51 +0200 Subject: [PATCH 01/19] Add SyncAccessHandle to spec, with TODOs --- index.bs | 172 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 171 insertions(+), 1 deletion(-) diff --git a/index.bs b/index.bs index a020bec..bc0ae06 100644 --- a/index.bs +++ b/index.bs @@ -415,10 +415,18 @@ dictionary FileSystemCreateWritableOptions { boolean keepExistingData = false; }; +enum AccessHandleMode { "in-place" }; + +dictionary FileSystemFileHandleCreateAccessHandleOptions { + required AccessHandleMode mode; +}; + [Exposed=(Window,Worker), SecureContext, Serializable] interface FileSystemFileHandle : FileSystemHandle { Promise getFile(); Promise createWritable(optional FileSystemCreateWritableOptions options = {}); + [Exposed=DedicatedWorker] + Promise createSyncAccessHandle(optional FileSystemFileHandleCreateAccessHandleOptions options = {}); }; @@ -427,7 +435,7 @@ A {{FileSystemFileHandle}}'s associated [=FileSystemHandle/entry=] must be a [=f {{FileSystemFileHandle}} objects are [=serializable objects=]. Their [=serialization steps=] and [=deserialization steps=] are the same as those for {{FileSystemHandle}}. -Advisement: In the Origin Trial as available in Chrome 78, these objects are not yet serializable. +Advisement: In the Origin Trial as available in Chrome 78, these objects are not yet serializable. In Chrome 82 they are. ### The {{FileSystemFileHandle/getFile()}} method ### {#api-filesystemfilehandle-getfile} @@ -467,6 +475,7 @@ The getFile() method, when invoked, m Advisement: In the Origin Trial as available in Chrome 82, createWritable replaces the createWriter method. +// TODO(fivedots): Add info on shared locks.
: |stream| = await |fileHandle| . {{FileSystemFileHandle/createWritable()}} : |stream| = await |fileHandle| . {{FileSystemFileHandle/createWritable()|createWritable}}({ {{FileSystemCreateWritableOptions/keepExistingData}}: true/false }) @@ -511,6 +520,34 @@ The createWritable(|options|) method,
+### The {{FileSystemFileHandle/createSyncAccessHandle()}} method ### {#api-filesystemfilehandle-createsyncaccesshandle} + +
+ : |stream| = await |fileHandle| . {{FileSystemFileHandle/createSyncAccessHandle()|createSyncAccessHandle}}({ {{FileSystemFileHandleCreateAccessHandleOptions/mode}}: "in-place" }) + :: Returns a {{FileSystemSyncAccessHandle}} + +// TODO(fivedots): Add description, mentioning direct modifications of the file, locking, only OPFS, mode flag being there for future development, the syncyness of it. + +
+ +
+The createSyncAccessHandle(|options|) method, when invoked, must run these steps: + +1. Let |result| be [=a new promise=]. +1. Run the following steps [=in parallel=]: + 1. Let |permissionStatus| be the result of [=requesting file system permission=] + given [=this=] and {{"readwrite"}}. + If that throws an exception, [=reject=] |result| with that exception and abort. + 1. If |permissionStatus| is not {{PermissionState/"granted"}}, + reject |result| with a {{NotAllowedError}} and abort. + 1. Let |entry| be [=this=]'s [=FileSystemHandle/entry=]. + 1. Let |handle| be the result of [=create a new FileSystemSyncAccessHandle|creating a new FileSystemSyncAccessHandle=] + for |entry| in [=this=]'s [=relevant realm=]. + 1. [=/Resolve=] |result| with |handle|. +1. Return |result|. + +
+ ## The {{FileSystemDirectoryHandle}} interface ## {#api-filesystemdirectoryhandle} @@ -1141,6 +1178,139 @@ steps: </div> +## The {{FileSystemSyncAccessHandle}} interface ## {#api-filesystemsyncaccesshandle} + +<xmp class=idl> + +dictionary FilesystemReadWriteOptions { + [EnforceRange] unsigned long long at; +} + +[Exposed=DedicatedWorker, SecureContext] +interface FileSystemSyncAccessHandle { + unsigned long long read([AllowShared] BufferSource buffer, + FilesystemReadWriteOptions options); + unsigned long long write([AllowShared] BufferSource buffer, + FilesystemReadWriteOptions options); + + Promise<undefined> truncate([EnforceRange] unsigned long long size); + Promise<unsigned long long> getSize(); + Promise<undefined> flush(); + Promise<undefined> close(); +}; + + + +A {{FileSystemSyncAccessHandle}} has an associated \[[file]] (a [=file entry=]). + +
+// TODO(fivedots): update. +
+ +
+To create a new FileSystemSyncAccessHandle given a [=file entry=] |file| +in a [=/Realm=] |realm|, perform the following steps: + +1. Let |handle| be a [=new=] {{FileSystemSyncAccessHandle}} in |realm|. +1. Set |handle|.[=FileSystemSyncAccessHandle/[[file]]=] to |file|. +1. Return |handle|. + +
+ +### The {{FileSystemSyncAccessHandle/write()}} method ### {#api-filesystemsyncaccesshandle-write} +// TODO(fivedots): do we need to explicit interact with storage quota here? + +
+ : |handle| . {{FileSystemSyncAccessHandle/write()|write}}(|buffer|, {{FilesystemReadWriteOptions/at}}: |position|) + :: Writes the content of |buffer| into the file associated with |handle| with |position| as the offset. +
+ +
+The write(|buffer|, {{FilesystemReadWriteOptions/at}}: |position|) method, when invoked, must run +these steps: + +// TODO(fivedots): fill in. + +
+ +### The {{FileSystemSyncAccessHandle/read()}} method ### {#api-filesystemsyncaccesshandle-read} + +
+ : |handle| . {{FileSystemSyncAccessHandle/read()|read}}(|buffer|, {{FilesystemReadWriteOptions/at}}: |position|) + :: Reads the contents of the file associated with |handle| into |buffer|, with |position| as the offset. +
+ +// TODO(fivedots): does this algorithm need to check that the access handle has not been closed? +
+The read(|buffer|, {{FilesystemReadWriteOptions/at}}: |position|) method, when invoked, must run +these steps: + +// TODO(fivedots): fill in. + +
+ +### The {{FileSystemSyncAccessHandle/truncate()}} method ### {#api-filesystemsyncaccesshandle-truncate} + +
+ : |handle| . {{FileSystemSyncAccessHandle/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 truncate(|size|) method, when invoked, must run +these steps: + +// TODO(fivedots): fill in. + +
+ +### The {{FileSystemSyncAccessHandle/getSize()}} method ### {#api-filesystemsyncaccesshandle-getsize} + +
+ : |handle| . {{FileSystemSyncAccessHandle/getSize()}} + :: Returns the size of the file associated with |handle| in bytes. +
+ +
+The getSize() method, when invoked, must run +these steps: + +// TODO(fivedots): fill in. + +
+ +### The {{FileSystemSyncAccessHandle/flush()}} method ### {#api-filesystemsyncaccesshandle-flush} + +// TODO(fivedots): does the definition of flush require the introduction of a buffer for the access handle, which is optinally flushed on read/write, but mandatorily so with flush? +
+ : |handle| . {{FileSystemSyncAccessHandle/flush()}} + :: Ensures that the contents of the file associated with |handle| contain all the modifications done through {{FileSystemSyncAccessHandle/read()}} and {{FileSystemSyncAccessHandle/write()}}. +
+ +
+The flush() method, when invoked, must run +these steps: + +// TODO(fivedots): fill in. + +
+ +### The {{FileSystemSyncAccessHandle/close()}} method ### {#api-filesystemsyncaccesshandle-close} +// TODO(fivedots): |entry| is used here, it likely needs to be defined above when discussing locks, and as a contructor parameter for access handles. + +
+ : |handle| . {{FileSystemSyncAccessHandle/close()}} + :: Flushes the access handle and then closes it. Closing an access handle disables any further operations on it and releases the lock associated with the |entry|. +
+ +
+The close() method, when invoked, must run +these steps: + +// TODO(fivedots): fill in. + +
+ # Accessing Local File System # {#local-filesystem} From 8c77176d11a0379bc6c2a575b79bbe28a6a2393a Mon Sep 17 00:00:00 2001 From: Emanuel Krivoy <krivoy@google.com> Date: Tue, 2 Nov 2021 17:53:17 +0100 Subject: [PATCH 02/19] Add non-normative description to create method --- index.bs | 50 ++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 36 insertions(+), 14 deletions(-) diff --git a/index.bs b/index.bs index bc0ae06..150b007 100644 --- a/index.bs +++ b/index.bs @@ -84,6 +84,8 @@ is similar to the temporary file system as defined in earlier drafts of ## Concepts ## {#concepts} +// TODO(fivedots): add concept of locks here. + An <dfn>entry</dfn> is either a [=file entry=] or a [=directory entry=]. Each [=/entry=] has an associated <dfn for=entry>name</dfn> (a [=string=]). @@ -523,10 +525,21 @@ The <dfn method for=FileSystemFileHandle>createWritable(|options|)</dfn> method, ### The {{FileSystemFileHandle/createSyncAccessHandle()}} method ### {#api-filesystemfilehandle-createsyncaccesshandle} <div class="note domintro"> - : |stream| = await |fileHandle| . {{FileSystemFileHandle/createSyncAccessHandle()|createSyncAccessHandle}}({ {{FileSystemFileHandleCreateAccessHandleOptions/mode}}: "in-place" }) - :: Returns a {{FileSystemSyncAccessHandle}} + : |handle| = await |fileHandle| . {{FileSystemFileHandle/createSyncAccessHandle()|createSyncAccessHandle}}({ {{FileSystemFileHandleCreateAccessHandleOptions/mode}}: "in-place" }) + :: Returns a {{FileSystemSyncAccessHandle}} that can be used to read/write from/to the file. + Changes made through |handle| might be immediately reflected in the file represented by |fileHandle|. + To ensure the changes are reflected in this file, the handle must be flushed or closed. + + The returned {{FileSystemSyncAccessHandle}} offers synchronous {{FileSystemFileHandle/read()}} and + {{FileSystemFileHandle/write()}} methods. This allows for higher performance for critical methods on + contexts where asynchronous operations come with high overhead i.e., WebAssembly. -// TODO(fivedots): Add description, mentioning direct modifications of the file, locking, only OPFS, mode flag being there for future development, the syncyness of it. + For the time being, this method will only succeed when the |fileHandle| belongs to the + [=origin private file system=]. Also temporarily, the {{FileSystemFileHandleCreateAccessHandleOptions/mode}} parameter + must be set to "in-place". This prevents us from setting a default behavior and allows us to bring access handles to + other filesytems in the future. + +// TODO(fivedots): Mention that an exclusive lock will be taken. </div> @@ -541,6 +554,8 @@ The <dfn method for=FileSystemFileHandle>createSyncAccessHandle(|options|)</dfn> 1. If |permissionStatus| is not {{PermissionState/"granted"}}, reject |result| with a {{NotAllowedError}} and abort. 1. Let |entry| be <b>[=this=]</b>'s [=FileSystemHandle/entry=]. + 1. If |entry| does not represent an [=/entry=] in an [=origin private file system=], + reject |result| with a {{InvalidStateError}} and abort. 1. Let |handle| be the result of [=create a new FileSystemSyncAccessHandle|creating a new FileSystemSyncAccessHandle=] for |entry| in <b>[=this=]</b>'s [=relevant realm=]. 1. [=/Resolve=] |result| with |handle|. @@ -1204,7 +1219,14 @@ interface FileSystemSyncAccessHandle { A {{FileSystemSyncAccessHandle}} has an associated <dfn for=FileSystemSyncAccessHandle>\[[file]]</dfn> (a [=file entry=]). <div class="note domintro"> -// TODO(fivedots): update. + +A {{FileSystemSyncAccessHandle}} is an object that is capable of reading/writing from/to, +as well as obtaining and changing the size of, a single file. + +The {{FileSystemFileHandle/read()}} and {{FileSystemFileHandle/write()}} methods are synchronous. +This allows for higher performance for critical methods on contexts where asynchronous +operations come with high overhead i.e., WebAssembly. + </div> <div algorithm> @@ -1217,32 +1239,32 @@ in a [=/Realm=] |realm|, perform the following steps: </div> -### The {{FileSystemSyncAccessHandle/write()}} method ### {#api-filesystemsyncaccesshandle-write} -// TODO(fivedots): do we need to explicit interact with storage quota here? +### The {{FileSystemSyncAccessHandle/read()}} method ### {#api-filesystemsyncaccesshandle-read} <div class="note domintro"> - : |handle| . {{FileSystemSyncAccessHandle/write()|write}}(|buffer|, {{FilesystemReadWriteOptions/at}}: |position|) - :: Writes the content of |buffer| into the file associated with |handle| with |position| as the offset. + : |handle| . {{FileSystemSyncAccessHandle/read()|read}}(|buffer|, {{FilesystemReadWriteOptions/at}}: |position|) + :: Reads the contents of the file associated with |handle| into |buffer|, with |position| as the offset. </div> +// TODO(fivedots): does this algorithm need to check that the access handle has not been closed? <div algorithm> -The <dfn method for=FileSystemSyncAccessHandle>write(|buffer|, {{FilesystemReadWriteOptions/at}}: |position|)</dfn> method, when invoked, must run +The <dfn method for=FileSystemSyncAccessHandle>read(|buffer|, {{FilesystemReadWriteOptions/at}}: |position|)</dfn> method, when invoked, must run these steps: // TODO(fivedots): fill in. </div> -### The {{FileSystemSyncAccessHandle/read()}} method ### {#api-filesystemsyncaccesshandle-read} +### The {{FileSystemSyncAccessHandle/write()}} method ### {#api-filesystemsyncaccesshandle-write} +// TODO(fivedots): do we need to explicit interact with storage quota here? <div class="note domintro"> - : |handle| . {{FileSystemSyncAccessHandle/read()|read}}(|buffer|, {{FilesystemReadWriteOptions/at}}: |position|) - :: Reads the contents of the file associated with |handle| into |buffer|, with |position| as the offset. + : |handle| . {{FileSystemSyncAccessHandle/write()|write}}(|buffer|, {{FilesystemReadWriteOptions/at}}: |position|) + :: Writes the content of |buffer| into the file associated with |handle| with |position| as the offset. </div> -// TODO(fivedots): does this algorithm need to check that the access handle has not been closed? <div algorithm> -The <dfn method for=FileSystemSyncAccessHandle>read(|buffer|, {{FilesystemReadWriteOptions/at}}: |position|)</dfn> method, when invoked, must run +The <dfn method for=FileSystemSyncAccessHandle>write(|buffer|, {{FilesystemReadWriteOptions/at}}: |position|)</dfn> method, when invoked, must run these steps: // TODO(fivedots): fill in. From dff9235bee36adbd028c2c045b2b750d5994084a Mon Sep 17 00:00:00 2001 From: Emanuel Krivoy <krivoy@google.com> Date: Wed, 3 Nov 2021 16:15:53 +0100 Subject: [PATCH 03/19] Add locks --- index.bs | 81 +++++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 63 insertions(+), 18 deletions(-) diff --git a/index.bs b/index.bs index 150b007..dd409a9 100644 --- a/index.bs +++ b/index.bs @@ -84,8 +84,6 @@ is similar to the temporary file system as defined in earlier drafts of ## Concepts ## {#concepts} -// TODO(fivedots): add concept of locks here. - An <dfn>entry</dfn> is either a [=file entry=] or a [=directory entry=]. Each [=/entry=] has an associated <dfn for=entry>name</dfn> (a [=string=]). @@ -111,8 +109,44 @@ majority of file extensions are purely alphanumeric, but compound extensions (su hence the inclusion of + and . as allowed code points. A <dfn lt="file|file entry">file entry</dfn> additionally consists of -<dfn for="file entry">binary data</dfn> (a [=byte sequence=]) and a -<dfn for="file entry">modification timestamp</dfn> (a number representing the number of milliseconds since the <a spec=FileAPI>Unix Epoch</a>). +<dfn for="file entry">binary data</dfn> (a [=byte sequence=]), a +<dfn for="file entry">modification timestamp</dfn> (a number representing the number of milliseconds since the <a spec=FileAPI>Unix Epoch</a>) +and a <dfn for="file entry">lock</dfn>. A lock is a string that may exclusively be "open", "taken-exclusive" and "taken-shared". + +<div algorithm> +To <dfn for="file entry/lock">take</dfn> a [=lock=] with a |value| of "exclusive" or "shared", for a given |file|, +run the following steps: + +1. Let |result| be [=a new promise=]. +1. Let |lock| be the |file|'s associated [=lock=] +1. Run the following steps [=in parallel=]: + 1. If |value| is "exclusive": + 1. If |lock| is "open": + 1. Set lock to "taken-exclusive". + 1. [=/resolve=] |result|. + 1. Else [=/reject=] |result|. + 1. If |value| is "shared": + 1. If |lock| is "open": + 1. Set lock to "taken-exclusive". + 1. [=/resolve=] |result|. + 1. Else if |lock| is "taken-shared": + 1. [=/resolve=] |result|. + 1. Else [=/reject=] |result|. +1. Return |result|. + +</div> + +<div algorithm> +To <dfn for="file entry/lock">release</dfn> a [=lock=] for a given |file|, +run the following steps: + +1. Let |lock| be the |file|'s associated [=lock=] +1. Set lock to "open". + +</div> + +Note: Locks help prevent concurrent modifications to a file. A {{FileSystemWritableFileStream}} +requires a shared lock, while a {{FileSystemSyncAccessHandle}} requires an exclusive one. A <dfn lt="directory|directory entry">directory entry</dfn> additionally consists of a [=/set=] of <dfn for="directory entry">children</dfn>, which are themselves [=/entries=]. Each member is either a [=/file=] or a [=directory=]. @@ -278,7 +312,7 @@ the {{FileSystemHandle}} interface can all be associated with the same [=/entry= <div algorithm="serialization steps"> {{FileSystemHandle}} objects are [=serializable objects=]. -Advisement: In the Origin Trial as available in Chrome 78, these objects are not yet serializable. +Advisement: In the Origin Trial as available in Chrome 78, these objects are not yet serializable. In Chrome 82 they are. Their [=serialization steps=], given |value|, |serialized| and <var ignore>forStorage</var> are: @@ -477,7 +511,6 @@ The <dfn method for=FileSystemFileHandle>getFile()</dfn> method, when invoked, m Advisement: In the Origin Trial as available in Chrome 82, createWritable replaces the createWriter method. -// TODO(fivedots): Add info on shared locks. <div class="note domintro"> : |stream| = await |fileHandle| . {{FileSystemFileHandle/createWritable()}} : |stream| = await |fileHandle| . {{FileSystemFileHandle/createWritable()|createWritable}}({ {{FileSystemCreateWritableOptions/keepExistingData}}: true/false }) @@ -493,6 +526,10 @@ Advisement: In the Origin Trial as available in Chrome 82, createWritable replac 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 + [=FileSystemHandle/entry=] associated with |fileHandle|. This prevents the creation of + {{FileSystemSyncAccessHandle|FileSystemSyncAccessHandles}} for the entry, until the stream is closed. </div> Issue(67): There has been some discussion around and desire for a "inPlace" mode for createWritable @@ -502,6 +539,8 @@ currently implemented in Chrome. Implementing this is currently blocked on figur combine the desire to run malware checks with the desire to let websites make fast in-place modifications to existing large files. +// TODO(fivedots): release lock once the stream is closed. + <div algorithm> The <dfn method for=FileSystemFileHandle>createWritable(|options|)</dfn> method, when invoked, must run these steps: @@ -513,6 +552,9 @@ The <dfn method for=FileSystemFileHandle>createWritable(|options|)</dfn> method, 1. If |permissionStatus| is not {{PermissionState/"granted"}}, reject |result| with a {{NotAllowedError}} and abort. 1. Let |entry| be <b>[=this=]</b>'s [=FileSystemHandle/entry=]. + 1. Let |lockValue| be "shared". + 1. Let |lockResult| be the result of [=file entry/lock/take|taking a lock=] with |lockValue| on |entry|. + If that throws an exception, [=reject=] |result| with a {{NoModificationAllowedError}} and abort. 1. Let |stream| be the result of [=create a new FileSystemWritableFileStream|creating a new FileSystemWritableFileStream=] for |entry| in <b>[=this=]</b>'s [=relevant realm=]. 1. If |options|.{{FileSystemCreateWritableOptions/keepExistingData}} is `true`: @@ -530,6 +572,11 @@ The <dfn method for=FileSystemFileHandle>createWritable(|options|)</dfn> method, Changes made through |handle| might be immediately reflected in the file represented by |fileHandle|. To ensure the changes are reflected in this file, the handle must be flushed or closed. + Creating a {{FileSystemSyncAccessHandle}} [=file entry/lock/take|takes an exclusive lock=] on the + [=FileSystemHandle/entry=] associated with |fileHandle|. 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 {{FileSystemFileHandle/read()}} and {{FileSystemFileHandle/write()}} methods. This allows for higher performance for critical methods on contexts where asynchronous operations come with high overhead i.e., WebAssembly. @@ -539,8 +586,6 @@ The <dfn method for=FileSystemFileHandle>createWritable(|options|)</dfn> method, must be set to "in-place". This prevents us from setting a default behavior and allows us to bring access handles to other filesytems in the future. -// TODO(fivedots): Mention that an exclusive lock will be taken. - </div> <div algorithm> @@ -556,6 +601,9 @@ The <dfn method for=FileSystemFileHandle>createSyncAccessHandle(|options|)</dfn> 1. Let |entry| be <b>[=this=]</b>'s [=FileSystemHandle/entry=]. 1. If |entry| does not represent an [=/entry=] in an [=origin private file system=], reject |result| with a {{InvalidStateError}} and abort. + 1. Let |lockValue| be "exclusive". + 1. Let |lockResult| be the result of [=file entry/lock/take|taking a lock=] with |lockValue| on |entry|. + If that throws an exception, [=reject=] |result| with a {{NoModificationAllowedError}} and abort. 1. Let |handle| be the result of [=create a new FileSystemSyncAccessHandle|creating a new FileSystemSyncAccessHandle=] for |entry| in <b>[=this=]</b>'s [=relevant realm=]. 1. [=/Resolve=] |result| with |handle|. @@ -596,7 +644,7 @@ A {{FileSystemDirectoryHandle}}'s associated [=FileSystemHandle/entry=] must be {{FileSystemDirectoryHandle}} objects are [=serializable objects=]. Their [=serialization steps=] and [=deserialization steps=] are the same as those for {{FileSystemHandle}}. -Advisement: In the Origin Trial as available in Chrome 78, these objects are not yet serializable. +Advisement: In the Origin Trial as available in Chrome 78, these objects are not yet serializable. In Chrome 82 they are. Advisement: In Chrome versions upto Chrome 85 `getFileHandle` and `getDirectoryHandle` where @@ -1246,17 +1294,15 @@ in a [=/Realm=] |realm|, perform the following steps: :: Reads the contents of the file associated with |handle| into |buffer|, with |position| as the offset. </div> -// TODO(fivedots): does this algorithm need to check that the access handle has not been closed? <div algorithm> The <dfn method for=FileSystemSyncAccessHandle>read(|buffer|, {{FilesystemReadWriteOptions/at}}: |position|)</dfn> method, when invoked, must run these steps: -// TODO(fivedots): fill in. +// TODO(fivedots): fill in. Does this algorithm need to check that the access handle has not been closed? </div> ### The {{FileSystemSyncAccessHandle/write()}} method ### {#api-filesystemsyncaccesshandle-write} -// TODO(fivedots): do we need to explicit interact with storage quota here? <div class="note domintro"> : |handle| . {{FileSystemSyncAccessHandle/write()|write}}(|buffer|, {{FilesystemReadWriteOptions/at}}: |position|) @@ -1267,7 +1313,7 @@ these steps: The <dfn method for=FileSystemSyncAccessHandle>write(|buffer|, {{FilesystemReadWriteOptions/at}}: |position|)</dfn> method, when invoked, must run these steps: -// TODO(fivedots): fill in. +// TODO(fivedots): fill in. Does this algorithm need to explicitly interact with storage quota? </div> @@ -1303,7 +1349,6 @@ these steps: ### The {{FileSystemSyncAccessHandle/flush()}} method ### {#api-filesystemsyncaccesshandle-flush} -// TODO(fivedots): does the definition of flush require the introduction of a buffer for the access handle, which is optinally flushed on read/write, but mandatorily so with flush? <div class="note domintro"> : |handle| . {{FileSystemSyncAccessHandle/flush()}} :: Ensures that the contents of the file associated with |handle| contain all the modifications done through {{FileSystemSyncAccessHandle/read()}} and {{FileSystemSyncAccessHandle/write()}}. @@ -1318,11 +1363,11 @@ these steps: </div> ### The {{FileSystemSyncAccessHandle/close()}} method ### {#api-filesystemsyncaccesshandle-close} -// TODO(fivedots): |entry| is used here, it likely needs to be defined above when discussing locks, and as a contructor parameter for access handles. <div class="note domintro"> : |handle| . {{FileSystemSyncAccessHandle/close()}} - :: Flushes the access handle and then closes it. Closing an access handle disables any further operations on it and releases the lock associated with the |entry|. + :: Flushes the access handle and then closes it. Closing an access handle disables any further operations on it and + [=file entry/lock/release|releases the lock=] on the [=FileSystemHandle/entry=] associated with |handle|. </div> <div algorithm> @@ -1955,10 +2000,10 @@ respectively. The <dfn method for=DataTransferItem>getAsFileSystemHandle()</dfn> method steps are: 1. If the {{DataTransferItem}} object is not in the <a spec=html>read/write - mode</a> or the <a spec=html>read-only mode</a>, return + mode</a> or the <a spec=html>read-only mode</a>, return [=a promise resolved with=] `null`. -1. If the <a spec=html>the drag data item kind</a> is not <em>File</em>, +1. If the <a spec=html>the drag data item kind</a> is not <em>File</em>, then return [=a promise resolved with=] `null`. 1. Let |p| be [=a new promise=]. From 8da0508fd5fd6ef7e0be66f48c724b302ef44e4f Mon Sep 17 00:00:00 2001 From: Emanuel Krivoy <krivoy@google.com> Date: Wed, 3 Nov 2021 16:20:17 +0100 Subject: [PATCH 04/19] Make create options mandatory --- index.bs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.bs b/index.bs index dd409a9..6fe48a8 100644 --- a/index.bs +++ b/index.bs @@ -462,7 +462,7 @@ interface FileSystemFileHandle : FileSystemHandle { Promise<File> getFile(); Promise<FileSystemWritableFileStream> createWritable(optional FileSystemCreateWritableOptions options = {}); [Exposed=DedicatedWorker] - Promise<FileSystemSyncAccessHandle> createSyncAccessHandle(optional FileSystemFileHandleCreateAccessHandleOptions options = {}); + Promise<FileSystemSyncAccessHandle> createSyncAccessHandle(FileSystemFileHandleCreateAccessHandleOptions options); }; From a1a13376b4f14f6e33d84dfcf88359b17913ddde Mon Sep 17 00:00:00 2001 From: Emanuel Krivoy Date: Wed, 3 Nov 2021 16:24:45 +0100 Subject: [PATCH 05/19] Fix |handle|/|fileHandle| confusion on close() --- index.bs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/index.bs b/index.bs index 6fe48a8..25e98a4 100644 --- a/index.bs +++ b/index.bs @@ -1364,10 +1364,12 @@ these steps: ### The {{FileSystemSyncAccessHandle/close()}} method ### {#api-filesystemsyncaccesshandle-close} +// TOOD(fivedots): |fileHandle| is not properly defined here, consider adding an attribute to |handle|. +
: |handle| . {{FileSystemSyncAccessHandle/close()}} :: Flushes the access handle and then closes it. Closing an access handle disables any further operations on it and - [=file entry/lock/release|releases the lock=] on the [=FileSystemHandle/entry=] associated with |handle|. + [=file entry/lock/release|releases the lock=] on the [=FileSystemHandle/entry=] associated with |fileHandle|.
From e97fba4129b5572b3bef17773a2b6ea73d5b422c Mon Sep 17 00:00:00 2001 From: Emanuel Krivoy Date: Wed, 3 Nov 2021 16:25:54 +0100 Subject: [PATCH 06/19] Typo --- index.bs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.bs b/index.bs index 25e98a4..258ab65 100644 --- a/index.bs +++ b/index.bs @@ -1364,7 +1364,7 @@ these steps: ### The {{FileSystemSyncAccessHandle/close()}} method ### {#api-filesystemsyncaccesshandle-close} -// TOOD(fivedots): |fileHandle| is not properly defined here, consider adding an attribute to |handle|. +// TODO(fivedots): |fileHandle| is not properly defined here, consider adding an attribute to |handle|.
: |handle| . {{FileSystemSyncAccessHandle/close()}} From ad31fa930a2e2a3581b3a3ff72bc7038f6c98615 Mon Sep 17 00:00:00 2001 From: Emanuel Krivoy Date: Tue, 23 Nov 2021 00:58:09 +0100 Subject: [PATCH 07/19] Update index.bs Co-authored-by: Thomas Steiner --- index.bs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.bs b/index.bs index 258ab65..350e763 100644 --- a/index.bs +++ b/index.bs @@ -111,7 +111,7 @@ hence the inclusion of + and . as allowed code points. 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) -and a lock. A lock is a string that may exclusively be "open", "taken-exclusive" and "taken-shared". +and a lock. A lock is a string that may exclusively be "open", "taken-exclusive", and "taken-shared".
To take a [=lock=] with a |value| of "exclusive" or "shared", for a given |file|, From 4309d6ce47cfd77c84d79fca613b7270c4126176 Mon Sep 17 00:00:00 2001 From: Emanuel Krivoy Date: Tue, 23 Nov 2021 00:58:16 +0100 Subject: [PATCH 08/19] Update index.bs Co-authored-by: Thomas Steiner --- index.bs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.bs b/index.bs index 350e763..91f4794 100644 --- a/index.bs +++ b/index.bs @@ -579,7 +579,7 @@ The createWritable(|options|) method, The returned {{FileSystemSyncAccessHandle}} offers synchronous {{FileSystemFileHandle/read()}} and {{FileSystemFileHandle/write()}} methods. This allows for higher performance for critical methods on - contexts where asynchronous operations come with high overhead i.e., WebAssembly. + contexts where asynchronous operations come with high overhead, i.e., WebAssembly. For the time being, this method will only succeed when the |fileHandle| belongs to the [=origin private file system=]. Also temporarily, the {{FileSystemFileHandleCreateAccessHandleOptions/mode}} parameter From d2491bc31b4ecc2fc6082da72b9d40b448f9ae65 Mon Sep 17 00:00:00 2001 From: Emanuel Krivoy Date: Tue, 23 Nov 2021 00:58:21 +0100 Subject: [PATCH 09/19] Update index.bs Co-authored-by: Thomas Steiner --- index.bs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.bs b/index.bs index 91f4794..b5a391d 100644 --- a/index.bs +++ b/index.bs @@ -1273,7 +1273,7 @@ as well as obtaining and changing the size of, a single file. The {{FileSystemFileHandle/read()}} and {{FileSystemFileHandle/write()}} methods are synchronous. This allows for higher performance for critical methods on contexts where asynchronous -operations come with high overhead i.e., WebAssembly. +operations come with high overhead, i.e., WebAssembly.
From ddbf19248ff81354f24f847d4d51dd6210765d8b Mon Sep 17 00:00:00 2001 From: Emanuel Krivoy Date: Wed, 2 Feb 2022 18:59:15 +0100 Subject: [PATCH 10/19] Remove Naming question, update TOC This PR removes the naming open question and updates the table of contents. Lately there hasn't been an active discussion on naming, nor consensus on a better scheme, so it seems like the right time to remove it in preparation for shipment. --- AccessHandle.md | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/AccessHandle.md b/AccessHandle.md index 01163f3..ddecdc8 100644 --- a/AccessHandle.md +++ b/AccessHandle.md @@ -22,7 +22,7 @@ - [New data access surface](#new-data-access-surface) - [Locking semantics](#locking-semantics) - [Open Questions](#open-questions) - - [Naming](#naming) + - [Exposing AccessHandles on all filesystems](#exposing-accessHandles-on-all-filesystems) - [Assurances on non-awaited consistency](#assurances-on-non-awaited-consistency) - [Appendix](#appendix) - [AccessHandle IDL](#accesshandle-idl) @@ -184,12 +184,6 @@ observe changes done through the new API, even if a lock is still being held. ## Open Questions -### Naming - -The exact name of the new methods hasn’t been defined. The current placeholder -for data access is *createAccessHandle()* and *createSyncAccessHandle()*. -*createUnflushedStreams()* and *createDuplexStream()* have been suggested. - ### Exposing AccessHandles on all filesystems This proposal only currently considers additions to OPFS, but it would probably From 1adb25989c2598e8b59c012b9e405391ca3e29fa Mon Sep 17 00:00:00 2001 From: Emanuel Krivoy Date: Wed, 2 Feb 2022 19:01:45 +0100 Subject: [PATCH 11/19] Fix TOC --- AccessHandle.md | 2 +- index.html | 6247 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 6248 insertions(+), 1 deletion(-) create mode 100644 index.html diff --git a/AccessHandle.md b/AccessHandle.md index ddecdc8..2015e8e 100644 --- a/AccessHandle.md +++ b/AccessHandle.md @@ -22,7 +22,7 @@ - [New data access surface](#new-data-access-surface) - [Locking semantics](#locking-semantics) - [Open Questions](#open-questions) - - [Exposing AccessHandles on all filesystems](#exposing-accessHandles-on-all-filesystems) + - [Exposing AccessHandles on all filesystems](#exposing-accesshandles-on-all-filesystems) - [Assurances on non-awaited consistency](#assurances-on-non-awaited-consistency) - [Appendix](#appendix) - [AccessHandle IDL](#accesshandle-idl) diff --git a/index.html b/index.html new file mode 100644 index 0000000..a4bb2a7 --- /dev/null +++ b/index.html @@ -0,0 +1,6247 @@ + + + + File System Access + + + + + + + + + + + + + + + + + +
+

+

File System Access

+

Draft Community Group Report,

+
+
+
This version: +
https://wicg.github.io/file-system-access/ +
Issue Tracking: +
GitHub +
Inline In Spec +
Editor: +
(Google) +
+
+
+ +
+
+
+

Abstract

+

This document defines a web platform API that enables developers to build + + powerful web apps that interact with files on the user’s local device. + It builds on File API for file reading capabilities, and adds new API + surface to enable modifying files, as well as working with directories.

+
+
+

Status of this document

+
+

This specification was published by the Web Platform Incubator Community Group. + It is not a W3C Standard nor is it on the W3C Standards Track. + + Please note that under the W3C Community Contributor License Agreement (CLA) there is a limited opt-out and other conditions apply. + + Learn more about W3C Community and Business Groups.

+

+
+
+ +
+

1. Introduction

+

This section is non-normative.

+

This API enables developers to build powerful apps that interact with other +(non-Web) apps on the user’s device via the device’s file system. Prominent +examples of applications where users expect this functionality are IDEs, +photo and video editors, text editors, and more. +After a user grants a web app access, this API allows the app to read or save +changes directly to files and folders on the user’s device. Beyond reading and +writing files, this API provides the ability to open a directory and enumerate +its contents. Additionally, web apps can use this API to store references to +files and directories they’ve been given access to, allowing the web apps to +later regain access to the same content without requiring the user to select the +same file again.

+

This API is similar to <input type=file> and <input type=file webkitdirectory> [entries-api] in that user interaction happens through file and directory picker dialogs. +Unlike those APIs, this API is currently purely a javascript API, and +does not integrate with forms and/or input elements.

+

Additionally this API also makes it possible for websites to get access to some +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. It also makes it easier +to write automated tests for code using this API. The entry point for this is the navigator.storage.getDirectory() method. This +is similar to the temporary file system as defined in earlier drafts of File API: Directories and System.

+

2. Files and Directories

+

2.1. Concepts

+

An entry is either a file entry or a directory entry.

+

Each entry has an associated name (a string).

+

A valid file name is a string that is not an empty string, is not equal to "." or "..", +and does not contain '/' or any other character used as path separator on the underlying platform.

+

Note: This means that '\' is not allowed in names on Windows, but might be allowed on +other operating systems. Additionally underlying file systems might have further restrictions +on what names are or aren’t allowed, so a string merely being a valid file name is not +a guarantee that creating a file or directory with that name will succeed.

+

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 valid suffix code point is a code point that is ASCII alphanumeric, +U+002B (+), or U+002E (.).

+

Note: These code points were chosen to support most pre-existing file formats. The vast +majority of file extensions are purely alphanumeric, but compound extensions (such as .tar.gz) and extensions such as .c++ for C++ source code are also fairly common, +hence the inclusion of + and . as allowed code points.

+

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) +and a lock. A lock is a string that may exclusively be "open", "taken-exclusive" and "taken-shared".

+
+ To take a lock with a value of "exclusive" or "shared", for a given file, +run the following steps: +
    +
  1. +

    Let result be a new promise.

    +
  2. +

    Let lock be the file’s associated lock

    +
  3. +

    Run the following steps in parallel:

    +
      +
    1. +

      If value is "exclusive":

      +
        +
      1. +

        If lock is "open":

        +
          +
        1. +

          Set lock to "taken-exclusive".

          +
        2. +

          resolve result.

          +
        +
      2. +

        Else reject result.

        +
      +
    2. +

      If value is "shared":

      +
        +
      1. +

        If lock is "open":

        +
          +
        1. +

          Set lock to "taken-exclusive".

          +
        2. +

          resolve result.

          +
        +
      2. +

        Else if lock is "taken-shared":

        +
          +
        1. +

          resolve result.

          +
        +
      3. +

        Else reject result.

        +
      +
    +
  4. +

    Return result.

    +
+
+
+ To release a lock for a given file, +run the following steps: +
    +
  1. +

    Let lock be the file’s associated lock

    +
  2. +

    Set lock to "open".

    +
+
+ //TODO(fivedots): Define algorithm for releasing a lock. +

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 entries. Each member is either a file or a directory.

+

An entry entry should be contained in the children of at most one directory entry, and that directory entry is also known as entry’s parent. +An entry's parent is null if no such directory entry exists.

+

Note: Two different entries can represent the same file or directory on disk, in which +case it is possible for both entries to have a different parent, or for one entry to have a +parent while the other entry does not have a parent. Typically an entry does not have a parent +if it was returned by navigator.storage.getDirectory() or one of the local file system handle factories, +and an entry will have a parent in all other cases.

+

Entries can (but don’t have to) be backed by files on the host operating system’s local file system, +so it is possible for the binary data, modification timestamp, +and children of entries to be modified by applications outside of this specification. +Exactly how external changes are reflected in the data structures defined by this specification, +as well as how changes made to the data structures defined here are reflected externally +is left up to individual user-agent implementations.

+

An entry a is the same as an entry b if a is equal to b, or +if a and b are backed by the same file or directory on the local file system.

+

TODO: Explain better how entries map to files on disk (multiple entries can map to the same file or +directory on disk but an entry doesn’t have to map to any file on disk).

+
+ To resolve an entry child relative to a directory entry root, +run the following steps: +
    +
  1. +

    Let result be a new promise.

    +
  2. +

    Run the following steps in parallel:

    +
      +
    1. +

      If child is the same as root, resolve result with an empty list, and abort.

      +
    2. +

      Let childPromises be « ».

      +
    3. +

      For each entry of root’s entry's children:

      +
        +
      1. +

        Let p be the result of resolving child relative to entry.

        +
      2. +

        Append p to childPromises.

        +
      3. +

        Upon fulfillment of p with value path:

        +
          +
        1. +

          If path is not null:

          +
            +
          1. +

            Prepend entry’s name to path.

            +
          2. +

            Resolve result with path.

            +
          +
        +
      +
    4. +

      Wait for all childPromises, with the following success steps:

      +
        +
      1. +

        If result hasn’t been resolved yet, resolve result with null.

        +
      +
    +
  3. +

    Return result.

    +
+
+

2.2. Permissions

+

The "file-system" powerful feature's +permission-related algorithms and types are defined as follows:

+
+
permission descriptor type +
+

FileSystemPermissionDescriptor, defined as:

+
enum FileSystemPermissionMode {
+  "read",
+  "readwrite"
+};
+
+dictionary FileSystemPermissionDescriptor : PermissionDescriptor {
+  required FileSystemHandle handle;
+  FileSystemPermissionMode mode = "read";
+};
+
+
permission state constraints +
+
+ To determine permission state constraints for a FileSystemPermissionDescriptor desc, +run these steps: +
    +
  1. +

    Let entry be desc.handle's entry.

    +
  2. +

    If entry represents an entry in an origin private file system, +this descriptor’s permission state must always be "granted".

    +
  3. +

    Otherwise, if entry’s parent is not null, this descriptor’s permission state must be +equal to the permission state for a descriptor with the same mode, +and a handle representing entry’s parent.

    +
  4. +

    Otherwise, if desc.mode is "readwrite":

    +
      +
    1. +

      Let read state be the permission state for a descriptor +with the same handle, +but mode = "read".

      +
    2. +

      If read state is not "granted", this descriptor’s permission state must be equal to read state.

      +
    +
+
+
permission request algorithm +
+
+ Given a FileSystemPermissionDescriptor desc and a PermissionStatus status, +run these steps: +
    +
  1. +

    Run the boolean permission query algorithm on desc and status.

    +
  2. +

    If status.state is not "prompt", abort.

    +
  3. +

    Let settings be desc.handle's relevant settings object.

    +
  4. +

    Let global be settings’s global object.

    +
  5. +

    If global is not a Window, throw a SecurityError.

    +
  6. +

    If global does not have transient activation, throw a SecurityError.

    +
  7. +

    If settings’s origin is not same origin with settings’s top-level origin, + throw a SecurityError.

    +
  8. +

    Request permission to use desc.

    +
  9. +

    Run the boolean permission query algorithm on desc and status.

    +
+

Ideally this user activation requirement would be defined upstream. [Issue #WICG/permissions-request#2]

+
+
+
+ To query file system permission given a FileSystemHandle handle and a FileSystemPermissionMode mode, run these steps: +
    +
  1. +

    Let desc be a FileSystemPermissionDescriptor.

    +
  2. +

    Set desc.name to "file-system".

    +
  3. +

    Set desc.handle to handle.

    +
  4. +

    Set desc.mode to mode.

    +
  5. +

    Return desc’s permission state.

    +
+
+
+ To request file system permission given a FileSystemHandle handle and a FileSystemPermissionMode mode, run these steps: +
    +
  1. +

    Let desc be a FileSystemPermissionDescriptor.

    +
  2. +

    Set desc.name to "file-system".

    +
  3. +

    Set desc.handle to handle.

    +
  4. +

    Set desc.mode to mode.

    +
  5. +

    Let status be the result of running create a PermissionStatus for desc.

    +
  6. +

    Run the permission request algorithm for the "file-system" feature, given desc and status.

    +
  7. +

    Return desc’s permission state.

    +
+
+

Currently FileSystemPermissionMode can only be "read" or "readwrite". In +the future we might want to add a "write" mode as well to support write-only handles. [Issue #119]

+

2.3. The FileSystemHandle interface

+
+ +
+

FileSystemHandle

+

In only one current engine.

+
+ FirefoxNoneSafariNoneChrome86+ +
+ Opera72+Edge86+ +
+ Edge (Legacy)NoneIENone +
+ Firefox for AndroidNoneiOS SafariNoneChrome for Android86+Android WebViewNoneSamsung Internet14.0+Opera MobileNone +
+
+
+
dictionary FileSystemHandlePermissionDescriptor {
+  FileSystemPermissionMode mode = "read";
+};
+
+enum FileSystemHandleKind {
+  "file",
+  "directory",
+};
+
+[Exposed=(Window,Worker), SecureContext, Serializable]
+interface FileSystemHandle {
+  readonly attribute FileSystemHandleKind kind;
+  readonly attribute USVString name;
+
+  Promise<boolean> isSameEntry(FileSystemHandle other);
+
+  Promise<PermissionState> queryPermission(optional FileSystemHandlePermissionDescriptor descriptor = {});
+  Promise<PermissionState> requestPermission(optional FileSystemHandlePermissionDescriptor descriptor = {});
+};
+
+

A FileSystemHandle object represents an entry. Each FileSystemHandle object is associated +with an entry (an entry). Multiple separate objects implementing +the FileSystemHandle interface can all be associated with the same entry simultaneously.

+
+ FileSystemHandle objects are serializable objects. +

In the Origin Trial as available in Chrome 78, these objects are not yet serializable. +In Chrome 82 they are.

+

Their serialization steps, given value, serialized and forStorage are:

+
    +
  1. +

    Set serialized.[[Origin]] to value’s relevant settings object's origin.

    +
  2. +

    Set serialized.[[Entry]] to value’s entry.

    +
+
+
+ Their deserialization steps, given serialized and value are: +
    +
  1. +

    If serialized.[[Origin]] is not same origin with value’s relevant settings object's origin, + then throw a DataCloneError.

    +
  2. +

    Set value’s entry to serialized.[[Entry]]

    +
+
+
+
+
+
+ +
+

FileSystemHandle/kind

+

In only one current engine.

+
+ FirefoxNoneSafariNoneChrome86+ +
+ Opera72+Edge86+ +
+ Edge (Legacy)NoneIENone +
+ Firefox for AndroidNoneiOS SafariNoneChrome for Android86+Android WebViewNoneSamsung Internet14.0+Opera MobileNone +
+
+
+

handle . kind

+
+

Returns "file" if handle is a FileSystemFileHandle, + or "directory" if handle is a FileSystemDirectoryHandle.

+

This can be used to distinguish files from directories when iterating over the contents + of a directory.

+
+
+ +
+

FileSystemHandle/name

+

In only one current engine.

+
+ FirefoxNoneSafariNoneChrome86+ +
+ Opera72+Edge86+ +
+ Edge (Legacy)NoneIENone +
+ Firefox for AndroidNoneiOS SafariNoneChrome for Android86+Android WebViewNoneSamsung Internet14.0+Opera MobileNone +
+
+
+

handle . name

+
+

Returns the name of the entry represented by handle.

+
+
+

The kind attribute must +return "file" if the associated entry is a file entry, +and return "directory" otherwise.

+

The name attribute must return the name of the +associated entry.

+

2.3.1. The isSameEntry() method

+
+ +
+

FileSystemHandle/isSameEntry

+

In only one current engine.

+
+ FirefoxNoneSafariNoneChrome86+ +
+ Opera72+Edge86+ +
+ Edge (Legacy)NoneIENone +
+ Firefox for AndroidNoneiOS SafariNoneChrome for Android86+Android WebViewNoneSamsung Internet14.0+Opera MobileNone +
+
+
+
+
+
same = await handle1 . isSameEntry( handle2 ) +
+

Returns true if handle1 and handle2 represent the same file or directory.

+
+
+

This method is first available in Chrome 82.

+
+ The isSameEntry(other) method, when invoked, must run these steps: +
    +
  1. +

    Let realm be this's relevant Realm.

    +
  2. +

    Let p be a new promise in realm.

    +
  3. +

    Run the following steps in parallel:

    +
      +
    1. +

      If this's entry is the same as other’s entry, resolve p with true.

      +
    2. +

      Else resolve p with false.

      +
    +
  4. +

    Return p.

    +
+
+

2.3.2. The queryPermission() method

+
+ +
+

FileSystemHandle/queryPermission

+

In only one current engine.

+
+ FirefoxNoneSafariNoneChrome86+ +
+ Opera72+Edge86+ +
+ Edge (Legacy)NoneIENone +
+ Firefox for AndroidNoneiOS SafariNoneChrome for Android86+Android WebViewNoneSamsung Internet14.0+Opera MobileNone +
+
+
+
+
+
status = await handle . queryPermission({ mode : "read" }) +
status = await handle . queryPermission() +
status = (await navigator.permissions.query({ name : "file-system", handle : handle })).state +
+

Queries the current state of the read permission of this handle. If this returns "prompt" the website will have to call requestPermission() before any + operations on the handle can be done. If this returns "denied" any operations will reject.

+

Usually handles returned by the local file system handle factories will initially return "granted" for + their read permission state, however other than through the user revoking permission, a handle + retrieved from IndexedDB is also likely to return "prompt".

+
status = await handle . queryPermission({ mode : "readwrite" }) +
status = (await navigator.permissions.query({ name : "file-system", handle : handle, mode : "readwrite"}).state +
+

Queries the current state of the write permission of this handle. If this returns "prompt", + attempting to modify the file or directory this handle represents will require user activation + and will result in a confirmation prompt being shown to the user. However if the state of the + read permission of this handle is also "prompt" the website will need to call requestPermission(). There is no automatic prompting for read access when + attempting to read from a file or directory.

+
+
+

The integration with the permissions API’s query() method is not yet implemented in Chrome.

+
+ The queryPermission(descriptor) method, when invoked, must run these steps: +
    +
  1. +

    Let result be a new promise.

    +
  2. +

    Run the following steps in parallel:

    +
      +
    1. +

      Let state be the result of querying file system permission given this and descriptor.mode.

      +
    2. +

      Resolve result with state.

      +
    +
  3. +

    Return result.

    +
+
+

2.3.3. The requestPermission() method

+
+ +
+

FileSystemHandle/requestPermission

+

In only one current engine.

+
+ FirefoxNoneSafariNoneChrome86+ +
+ Opera72+Edge86+ +
+ Edge (Legacy)NoneIENone +
+ Firefox for AndroidNoneiOS SafariNoneChrome for Android86+Android WebViewNoneSamsung Internet14.0+Opera MobileNone +
+
+
+
+
+
status = await handle . requestPermission({ mode : "read" }) +
status = await handle . requestPermission() +
+

If the state of the read permission of this handle is anything other than "prompt", this + will return that state directly. If it is "prompt" however, user activation is needed and + this will show a confirmation prompt to the user. The new read permission state is then + returned, depending on the user’s response to the prompt.

+
status = await handle . requestPermission({ mode : "readwrite" }) +
+

If the state of the write permission of this handle is anything other than "prompt", this + will return that state directly. If the status of the read permission of this handle is "denied" this will return that.

+

Otherwise the state of the write permission is "prompt" and this will show a confirmation + prompt to the user. The new write permission state is then returned, depending on what the user + selected.

+
+
+
+ The requestPermission(descriptor) method, when invoked, must run these steps: +
    +
  1. +

    Let result be a new promise.

    +
  2. +

    Run the following steps in parallel:

    +
      +
    1. +

      Let state be the result of requesting file system permission given this and descriptor.mode. + If that throws an exception, reject result with that exception and abort.

      +
    2. +

      Resolve result with state.

      +
    +
  3. +

    Return result.

    +
+
+

2.4. The FileSystemFileHandle interface

+
+ +
+

FileSystemFileHandle

+

In only one current engine.

+
+ FirefoxNoneSafariNoneChrome86+ +
+ Opera72+Edge86+ +
+ Edge (Legacy)NoneIENone +
+ Firefox for AndroidNoneiOS SafariNoneChrome for Android86+Android WebViewNoneSamsung Internet14.0+Opera MobileNone +
+
+
+
dictionary FileSystemCreateWritableOptions {
+  boolean keepExistingData = false;
+};
+
+enum AccessHandleMode { "in-place" };
+
+dictionary FileSystemFileHandleCreateAccessHandleOptions {
+  required AccessHandleMode mode;
+};
+
+[Exposed=(Window,Worker), SecureContext, Serializable]
+interface FileSystemFileHandle : FileSystemHandle {
+  Promise<File> getFile();
+  Promise<FileSystemWritableFileStream> createWritable(optional FileSystemCreateWritableOptions options = {});
+  [Exposed=DedicatedWorker]
+  Promise<FileSystemSyncAccessHandle> createSyncAccessHandle(optional FileSystemFileHandleCreateAccessHandleOptions options = {});
+};
+
+

A FileSystemFileHandle's associated entry must be a file entry.

+

FileSystemFileHandle objects are serializable objects. Their serialization steps and deserialization steps are the same as those for FileSystemHandle.

+

In the Origin Trial as available in Chrome 78, these objects are not yet serializable. +In Chrome 82 they are.

+

2.4.1. The getFile() method

+
+ +
+

FileSystemFileHandle/getFile

+

In only one current engine.

+
+ FirefoxNoneSafariNoneChrome86+ +
+ Opera72+Edge86+ +
+ Edge (Legacy)NoneIENone +
+ Firefox for AndroidNoneiOS SafariNoneChrome for Android86+Android WebViewNoneSamsung Internet14.0+Opera MobileNone +
+
+
+
+
+
file = await fileHandle . getFile() +
+

Returns a File representing the state on disk of the entry represented by handle. + If the file on disk changes or is removed after this method is called, the returned File object will likely be no longer readable.

+
+
+
+ The getFile() method, when invoked, must run these steps: +
    +
  1. +

    Let result be a new promise.

    +
  2. +

    Run the following steps in parallel:

    +
      +
    1. +

      Let permissionStatus be the result of querying file system permission given this and "read".

      +
    2. +

      If permissionStatus is not "granted", + reject result with a NotAllowedError and abort.

      +
    3. +

      Let entry be this’s entry.

      +
    4. +

      Let f be a new File.

      +
    5. +

      Set f’s snapshot state to the current state of entry.

      +
    6. +

      Set f’s underlying byte sequence to a copy of entry’s binary data.

      +
    7. +

      Initialize the value of f’s name attribute to entry’s name.

      +
    8. +

      Initialize the value of f’s lastModified attribute to entry’s modification timestamp.

      +
    9. +

      Initialize the value of f’s type attribute to an implementation defined value, based on for example entry’s name and/or its file extension.

      +

      The reading and snapshotting behavior needs to be better specified in the [FILE-API] spec, + for now this is kind of hand-wavy.

      +
    10. +

      Resolve result with f.

      +
    +
  3. +

    Return result.

    +
+
+

2.4.2. The createWritable() method

+
+ +
+

FileSystemFileHandle/createWritable

+

In only one current engine.

+
+ FirefoxNoneSafariNoneChrome86+ +
+ Opera72+Edge86+ +
+ Edge (Legacy)NoneIENone +
+ Firefox for AndroidNoneiOS SafariNoneChrome for Android86+Android WebViewNoneSamsung Internet14.0+Opera MobileNone +
+
+
+

In the Origin Trial as available in Chrome 82, createWritable replaces the createWriter method.

+
+
+
stream = await fileHandle . createWritable() +
stream = await fileHandle . createWritable({ keepExistingData: true/false }) +
+

Returns a FileSystemWritableFileStream that can be used to write to the file. Any changes made through stream won’t be reflected in the file represented by fileHandle until the stream has been closed. + User agents try to ensure that no partial writes happen, i.e. the file represented by fileHandle will either contain its old contents or it will contain whatever data was written + through stream up until the stream has been closed.

+

This is typically implemented by writing data to a temporary file, and only replacing the file + represented by fileHandle with the temporary file when the writable filestream is closed.

+

If 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 takes a shared lock on the entry associated with fileHandle. This prevents the creation of FileSystemSyncAccessHandles for the entry, until the stream is closed.

+
+
+

There has been some discussion around and desire for a "inPlace" mode for createWritable +(where changes will be written to the actual underlying file as they are written to the writer, for +example to support in-place modification of large files or things like databases). This is not +currently implemented in Chrome. Implementing this is currently blocked on figuring out how to +combine the desire to run malware checks with the desire to let websites make fast in-place +modifications to existing large files. [Issue #67]

+

// TODO(fivedots): release lock once the stream is closed.

+
+ The createWritable(options) method, when invoked, must run these steps: +
    +
  1. +

    Let result be a new promise.

    +
  2. +

    Run the following steps in parallel:

    +
      +
    1. +

      Let permissionStatus be the result of requesting file system permission given this and "readwrite". + If that throws an exception, reject result with that exception and abort.

      +
    2. +

      If permissionStatus is not "granted", + reject result with a NotAllowedError and abort.

      +
    3. +

      Let entry be this’s entry.

      +
    4. +

      Let lockValue be "shared".

      +
    5. +

      Let lockResult be the result of taking a lock with lockValue on entry. + If that throws an exception, reject result with a NoModificationAllowedError and abort.

      +
    6. +

      Let stream be the result of creating a new FileSystemWritableFileStream for entry in this’s relevant realm.

      +
    7. +

      If options.keepExistingData is true:

      +
        +
      1. +

        Set stream.[[buffer]] to a copy of entry’s binary data.

        +
      +
    8. +

      Resolve result with stream.

      +
    +
  3. +

    Return result.

    +
+
+

2.4.3. The createSyncAccessHandle() method

+
+
+
handle = await fileHandle . createSyncAccessHandle({ mode: "in-place" }) +
+

Returns a FileSystemSyncAccessHandle that can be used to read/write from/to the file. + Changes made through handle might be immediately reflected in the file represented by fileHandle. + To ensure the changes are reflected in this file, the handle must be flushed or closed.

+

Creating a FileSystemSyncAccessHandle takes an exclusive lock on the entry associated with fileHandle. This prevents the creation of +further FileSystemSyncAccessHandles or FileSystemWritableFileStreams for the entry, until the access handle is closed.

+

The returned FileSystemSyncAccessHandle offers synchronous read() and write() methods. This allows for higher performance for critical methods on +contexts where asynchronous operations come with high overhead i.e., WebAssembly.

+

For the time being, this method will only succeed when the fileHandle belongs to the origin private file system. Also temporarily, the mode parameter +must be set to "in-place". This prevents us from setting a default behavior and allows us to bring access handles to +other filesytems in the future.

+
+
+
+ The createSyncAccessHandle(options) method, when invoked, must run these steps: +
    +
  1. +

    Let result be a new promise.

    +
  2. +

    Run the following steps in parallel:

    +
      +
    1. +

      Let permissionStatus be the result of requesting file system permission given this and "readwrite". + If that throws an exception, reject result with that exception and abort.

      +
    2. +

      If permissionStatus is not "granted", + reject result with a NotAllowedError and abort.

      +
    3. +

      Let entry be this’s entry.

      +
    4. +

      If entry does not represent an entry in an origin private file system, + reject result with a InvalidStateError and abort.

      +
    5. +

      Let lockValue be "exclusive".

      +
    6. +

      Let lockResult be the result of taking a lock with lockValue on entry. + If that throws an exception, reject result with a NoModificationAllowedError and abort.

      +
    7. +

      Let handle be the result of creating a new FileSystemSyncAccessHandle for entry in this’s relevant realm.

      +
    8. +

      Resolve result with handle.

      +
    +
  3. +

    Return result.

    +
+
+

2.5. The FileSystemDirectoryHandle interface

+
+ +
+

FileSystemDirectoryHandle

+

In only one current engine.

+
+ FirefoxNoneSafariNoneChrome86+ +
+ Opera72+Edge86+ +
+ Edge (Legacy)NoneIENone +
+ Firefox for AndroidNoneiOS SafariNoneChrome for Android86+Android WebViewNoneSamsung Internet14.0+Opera MobileNone +
+
+
+
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);
+};
+
+

A FileSystemDirectoryHandle's associated entry must be a directory entry.

+

FileSystemDirectoryHandle objects are serializable objects. Their serialization steps and deserialization steps are the same as those for FileSystemHandle.

+

In the Origin Trial as available in Chrome 78, these objects are not yet serializable. +In Chrome 82 they are.

+

In Chrome versions upto Chrome 85 getFileHandle and getDirectoryHandle where +called getFile and getDirectory instead.

+

2.5.1. Directory iteration

+
+
+
for await (let [name, handle] of directoryHandle) {} +
for await (let [name, handle] of directoryHandle . entries()) {} +
for await (let handle of directoryHandle . values()) {} +
for await (let name of directoryHandle . keys()) {} +
+

Iterates over all entries whose parent is the entry represented by directoryHandle. Entries + that are created or deleted while the iteration is in progress might or might not be included. + No guarantees are given either way.

+
+
+

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

+

In the future we might want to add arguments to the async iterable declaration to +support for example recursive iteration. [Issue #173]

+
+ The asynchronous iterator initialization steps for a FileSystemDirectoryHandle handle ant its async iterator iterator are: +
    +
  1. +

    Let permissionStatus be the result of querying file system permission given handle and "read".

    +
  2. +

    If permissionStatus is not "granted", + throw a NotAllowedError.

    +
  3. +

    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.

    +
  2. +

    Let directory be handle’s entry.

    +
  3. +

    Let permissionStatus be the result of querying file system permission given handle and "read".

    +
  4. +

    If permissionStatus is not "granted", + reject promise with a NotAllowedError and return promise.

    +
  5. +

    Let child be an entry in directory’s children, + such that child’s 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.

    +
  6. +

    If child is null, then:

    +
      +
    1. +

      Resolve promise with undefined.

      +
    +
  7. +

    Otherwise:

    +
      +
    1. +

      Append child’s name to iterator’s past results.

      +
    2. +

      If child is a file entry:

      +
        +
      1. +

        Let result be a new FileSystemFileHandle associated with child.

        +
      +
    3. +

      Otherwise:

      +
        +
      1. +

        Let result be a new FileSystemDirectoryHandle associated with child.

        +
      +
    4. +

      Resolve promise with (child’s name, result).

      +
    +
  8. +

    Return promise.

    +
+
+

2.5.2. The getFileHandle() method

+
+ +
+

FileSystemDirectoryHandle/getFileHandle

+

In only one current engine.

+
+ FirefoxNoneSafariNoneChrome86+ +
+ Opera72+Edge86+ +
+ Edge (Legacy)NoneIENone +
+ Firefox for AndroidNoneiOS SafariNoneChrome for Android86+Android WebViewNoneSamsung Internet14.0+Opera MobileNone +
+
+
+
+
+
fileHandle = await directoryHandle . getFileHandle(name) +
fileHandle = await directoryHandle . getFileHandle(name, { create: false }) +
+

Returns a handle for a file named name in the directory represented by directoryHandle. If + no such file exists, this rejects.

+
fileHandle = await directoryHandle . getFileHandle(name, { create: true }) +
+

Returns a handle for a file named name in the directory represented by directoryHandle. If + no such file exists, this creates a new file. If no file with named name can be created this + rejects. Creation can fail because there already is a directory with the same name, because the + name uses characters that aren’t supported in file names on the underlying file system, or + because the user agent for security reasons decided not to allow creation of the file.

+

This operation requires write permission, even if the file being returned already exists. If + this handle doesn’t already have write permission, this could result in a prompt being shown to + the user. To get an existing file without needing write permission, call this method + with { create: false }.

+
+
+
+ The getFileHandle(name, options) method, when invoked, +must run these steps: +
    +
  1. +

    Let result be a new promise.

    +
  2. +

    Run the following steps in parallel:

    +
      +
    1. +

      If name is not a valid file name, reject result with a TypeError and abort.

      +
    2. +

      Let entry be this’s entry.

      +
    3. +

      If options.create is true:

      +
        +
      1. +

        Let permissionStatus be the result of requesting file system permission given this and "readwrite". + If that throws an exception, reject result with that exception and abort.

        +
      +
    4. +

      Otherwise:

      +
        +
      1. +

        Let permissionStatus be the result of querying file system permission given this and "read".

        +
      +
    5. +

      If permissionStatus is not "granted", + reject result with a NotAllowedError and abort.

      +
    6. +

      For each child of entry’s children:

      +
        +
      1. +

        If child’s name equals name:

        +
          +
        1. +

          If child is a directory entry:

          +
            +
          1. +

            Reject result with a TypeMismatchError and abort.

            +
          +
        2. +

          Resolve result with a new FileSystemFileHandle whose entry is child and abort.

          +
        +
      +
    7. +

      If options.create is false:

      +
        +
      1. +

        Reject result with a NotFoundError and abort.

        +
      +
    8. +

      Let child be a new file entry.

      +
    9. +

      Set child’s name to name.

      +
    10. +

      Set child’s binary data to an empty byte sequence.

      +
    11. +

      Set child’s modification timestamp to the current time.

      +
    12. +

      Append child to entry’s children.

      +
    13. +

      If creating child in the underlying file system throws an exception, reject result with that exception and abort.

      +

      Better specify what possible exceptions this could throw. [Issue #68]

      +
    14. +

      Resolve result with a new FileSystemFileHandle whose entry is child.

      +
    +
  3. +

    Return result.

    +
+
+

2.5.3. The getDirectoryHandle() method

+
+ +
+

FileSystemDirectoryHandle/getDirectoryHandle

+

In only one current engine.

+
+ FirefoxNoneSafariNoneChrome86+ +
+ Opera72+Edge86+ +
+ Edge (Legacy)NoneIENone +
+ Firefox for AndroidNoneiOS SafariNoneChrome for Android86+Android WebViewNoneSamsung Internet14.0+Opera MobileNone +
+
+
+
+
+
subdirHandle = await directoryHandle . getDirectoryHandle(name) +
subdirHandle = await directoryHandle . getDirectoryHandle(name, { create: false }) +
+

Returns a handle for a directory named name in the directory represented by directoryHandle. If no such directory exists, this rejects.

+
subdirHandle = await directoryHandle . getDirectoryHandle(name, { create: true }) +
+

Returns a handle for a directory named name in the directory represented by directoryHandle. If no such directory exists, this creates a new directory. If creating the + directory failed, this rejects. Creation can fail because there already is a file with the same + name, or because the name uses characters that aren’t supported in file names on the underlying + file system.

+

This operation requires write permission, even if the directory being returned already exists. + If this handle doesn’t already have write permission, this could result in a prompt being shown + to the user. To get an existing directory without needing write permission, call this method + with { create: false }.

+
+
+
+ The getDirectoryHandle(name, options) method, when +invoked, must run these steps: +
    +
  1. +

    Let result be a new promise.

    +
  2. +

    Run the following steps in parallel:

    +
      +
    1. +

      If name is not a valid file name, reject result with a TypeError and abort.

      +
    2. +

      Let entry be this’s entry.

      +
    3. +

      If options.create is true:

      +
        +
      1. +

        Let permissionStatus be the result of requesting file system permission given this and "readwrite". + If that throws an exception, reject result with that exception and abort.

        +
      +
    4. +

      Otherwise:

      +
        +
      1. +

        Let permissionStatus be the result of querying file system permission given this and "read".

        +
      +
    5. +

      If permissionStatus is not "granted", + reject result with a NotAllowedError and abort.

      +
    6. +

      For each child of entry’s children:

      +
        +
      1. +

        If child’s name equals name:

        +
          +
        1. +

          If child is a file entry:

          +
            +
          1. +

            Reject result with a TypeMismatchError and abort.

            +
          +
        2. +

          Resolve result with a new FileSystemDirectoryHandle whose entry is child and abort.

          +
        +
      +
    7. +

      If options.create is false:

      +
        +
      1. +

        Reject result with a NotFoundError and abort.

        +
      +
    8. +

      Let child be a new directory entry.

      +
    9. +

      Set child’s name to name.

      +
    10. +

      Set child’s children to an empty set.

      +
    11. +

      Append child to entry’s children.

      +
    12. +

      If creating child in the underlying file system throws an exception, reject result with that exception and abort.

      +

      Better specify what possible exceptions this could throw. [Issue #68]

      +
    13. +

      Resolve result with a new FileSystemDirectoryHandle whose entry is child.

      +
    +
  3. +

    Return result.

    +
+
+

2.5.4. The removeEntry() method

+
+ +
+

FileSystemDirectoryHandle/removeEntry

+

In only one current engine.

+
+ FirefoxNoneSafariNoneChrome86+ +
+ Opera72+Edge86+ +
+ Edge (Legacy)NoneIENone +
+ Firefox for AndroidNoneiOS SafariNoneChrome for Android86+Android WebViewNoneSamsung Internet14.0+Opera MobileNone +
+
+
+
+
+
await directoryHandle . removeEntry(name) +
await directoryHandle . removeEntry(name, { recursive: false }) +
+

If the directory represented by directoryHandle contains a file named name, or an empty + directory named name, this will attempt to delete that file or directory.

+

Attempting to delete a file or directory that does not exist is considered success, + while attempting to delete a non-empty directory will result in a promise rejection.

+
await directoryHandle . removeEntry(name, { recursive: true }) +
+

Removes the entry named name in the directory represented by directoryHandle. + If that entry is a directory, its contents will also be deleted recursively. + recursively.

+

Attempting to delete a file or directory that does not exist is considered success.

+
+
+
+ The removeEntry(name, options) method, when invoked, must run +these steps: +
    +
  1. +

    Let result be a new promise.

    +
  2. +

    Run the following steps in parallel:

    +
      +
    1. +

      If name is not a valid file name, reject result with a TypeError and abort.

      +
    2. +

      Let entry be this’s entry.

      +
    3. +

      Let permissionStatus be the result of requesting file system permission given this and "readwrite". + If that throws an exception, reject result with that exception and abort.

      +
    4. +

      If permissionStatus is not "granted", + reject result with a NotAllowedError and abort.

      +
    5. +

      For each child of entry’s children:

      +
        +
      1. +

        If child’s name equals name:

        +
          +
        1. +

          If child is a directory entry:

          +
            +
          1. +

            If child’s children is not empty and options.recursive is false:

            +
              +
            1. +

              Reject result with an InvalidModificationError and abort.

              +
            +
          +
        2. +

          Remove child from entry’s children.

          +
        3. +

          If removing child in the underlying file system throws an exception, reject result with that exception and abort.

          +

          Note: If recursive is true, the removal can fail + non-atomically. Some files or directories might have been removed while other files + or directories still exist.

          +

          Better specify what possible exceptions this could throw. [Issue #68]

          +
        4. +

          Resolve result with undefined.

          +
        +
      +
    6. +

      Reject result with a NotFoundError.

      +
    +
  3. +

    Return result.

    +
+
+

2.5.5. The resolve() method

+
+ +
+

FileSystemDirectoryHandle/resolve

+

In only one current engine.

+
+ FirefoxNoneSafariNoneChrome86+ +
+ Opera72+Edge86+ +
+ Edge (Legacy)NoneIENone +
+ Firefox for AndroidNoneiOS SafariNoneChrome for Android86+Android WebViewNoneSamsung Internet14.0+Opera MobileNone +
+
+
+
+
+
path = await directory . 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.

+

This functionality can be useful if a web application shows a directory + listing to highlight a file opened through a file picker in that directory listing.

+
+
+

This method is first available in Chrome 82.

+
+ +
// 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 by showing a file picker:
+const file_ref = await self.showOpenFilePicker();
+if (!file_ref) {
+    // User cancelled, or otherwise failed to open a file.
+    return;
+}
+
+// Check if file_ref exists inside dir_ref:
+const relative_path = await dir_ref.resolve(file_ref);
+if (relative_path === null) {
+    // Not inside dir_ref
+} else {
+    // relative_path is an array of names, giving the relative path
+    // from dir_ref to the file that is represented by file_ref:
+    assert relative_path.pop() === file_ref.name;
+
+    let entry = dir_ref;
+    for (const name of relative_path) {
+        entry = await entry.getDirectory(name);
+    }
+    entry = await entry.getFile(file_ref.name);
+
+    // Now |entry| will represent the same file on disk as |file_ref|.
+    assert await entry.isSameEntry(file_ref) === true;
+}
+
+
+
The resolve(possibleDescendant) method, +when invoked, must return the result of resolving possibleDescendant’s entry relative to this's entry.
+

2.6. The FileSystemWritableFileStream interface

+
+ +
+

FileSystemWritableFileStream

+

In only one current engine.

+
+ FirefoxNoneSafariNoneChrome86+ +
+ OperaNoneEdge86+ +
+ Edge (Legacy)NoneIENone +
+ Firefox for AndroidNoneiOS SafariNoneChrome for AndroidNoneAndroid WebViewNoneSamsung InternetNoneOpera MobileNone +
+
+
+
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’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 write() or by using WritableStream capabilities through the 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.

+

getWriter() returns an instance of WritableStreamDefaultWriter.

+
+
+ To create a new FileSystemWritableFileStream given a file entry file in a Realm realm, perform the following steps: +
    +
  1. +

    Let stream be a new FileSystemWritableFileStream in realm.

    +
  2. +

    Set stream.[[file]] to file.

    +
  3. +

    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.

    +
  4. +

    Let closeAlgorithm be the following steps:

    +
      +
    1. +

      Let closeResult be a new promise.

      +
    2. +

      Run the following steps in parallel:

      +
        +
      1. +

        Let permissionStatus be the permission state for a FileSystemPermissionDescriptor with handle representing stream.[[file]], + and mode = "readwrite".

        +
      2. +

        If permissionStatus is not "granted", + reject closeResult with a NotAllowedError and abort.

        +
      3. +

        Perform user agent-specific malware scans and safe browsing checks. + If these checks fail, reject closeResult with an AbortError and abort.

        +
      4. +

        Set stream.[[file]]'s binary data to stream.[[buffer]]. + If that throws an exception, reject closeResult with that exception and abort.

        +

        Note: It is expected that this atomically updates the contents of the file on disk + being written to.

        +
      5. +

        Resolve closeResult with undefined.

        +
      +
    3. +

      Return closeResult.

      +
    +
  5. +

    Let highWaterMark be 1.

    +
  6. +

    Let sizeAlgorithm be an algorithm that returns 1.

    +
  7. +

    Set up stream with writeAlgorithm set to writeAlgorithm, closeAlgorithm set to closeAlgorithm, highWaterMark set to highWaterMark, and sizeAlgorithm set to sizeAlgorithm.

    +
  8. +

    Return stream.

    +
+
+
+ The write a chunk algorithm, +given a FileSystemWritableFileStream stream and chunk, +runs these steps: +
    +
  1. +

    Let input be the result of converting chunk to a FileSystemWriteChunkType. + If this throws an exception, then return a promise rejected with that exception.

    +
  2. +

    Let p be a new promise.

    +
  3. +

    Run the following steps in parallel:

    +
      +
    1. +

      Let permissionStatus be the permission state for a FileSystemPermissionDescriptor with handle representing stream.[[file]], + and mode = "readwrite".

      +
    2. +

      If permissionStatus is not "granted", + reject p with a NotAllowedError and abort.

      +
    3. +

      Let command be input.type if input is a WriteParams, + and "write" otherwise.

      +
    4. +

      If command is "write":

      +
        +
      1. +

        Let data be input.data if input is a WriteParams, + and input otherwise.

        +
      2. +

        If data is undefined, + reject p with a TypeError and abort.

        +
      3. +

        Let writePosition be stream.[[seekOffset]].

        +
      4. +

        If input is a WriteParams and input.position is not undefined, + set writePosition to input.position.

        +
      5. +

        Let oldSize be stream.[[buffer]]'s length.

        +
      6. +

        If data is a BufferSource, + let dataBytes be a copy of data.

        +
      7. +

        Else if data is a Blob:

        +
          +
        1. +

          Let dataBytes be the result of performing the read operation on data. + If this throws an exception, reject p with that exception and abort.

          +
        +
      8. +

        Else:

        +
          +
        1. +

          Assert: data is a USVString.

          +
        2. +

          Let dataBytes be the result of UTF-8 encoding data.

          +
        +
      9. +

        If writePosition is larger than oldSize, + append writePosition - oldSize 0x00 (NUL) bytes to the end of stream.[[buffer]].

        +

        Note: Implementations are expected to behave as if the skipped over file contents + are indeed filled with NUL bytes. That doesn’t mean these bytes have to actually be + written to disk and take up disk space. Instead most file systems support so called + sparse files, where these NUL bytes don’t take up actual disk space.

        +
      10. +

        Let head be a byte sequence containing the first writePosition bytes of stream.[[buffer]].

        +
      11. +

        Let tail be an empty byte sequence.

        +
      12. +

        If writePosition + data.length is smaller than oldSize:

        +
          +
        1. +

          Let tail be a byte sequence containing the last oldSize - (writePosition + data.length) bytes of stream.[[buffer]].

          +
        +
      13. +

        Set stream.[[buffer]] to the concatenation of head, data and tail.

        +
      14. +

        If the operations modifying stream.[[buffer]] in the previous steps failed + due to exceeding the storage quota, reject p with a QuotaExceededError and abort, + leaving stream.[[buffer]] unmodified.

        +

        Note: Storage quota only applies to files stored in the origin private file system. + However this operation could still fail for other files, for example if the disk being written + to runs out of disk space.

        +
      15. +

        Set stream.[[seekOffset]] to writePosition + data.length.

        +
      16. +

        Resolve p.

        +
      +
    5. +

      Else if command is "seek":

      +
        +
      1. +

        If chunk.position is undefined, reject p with a TypeError and abort.

        +
      2. +

        Set stream.[[seekOffset]] to chunk.position.

        +
      3. +

        Resolve p.

        +
      +
    6. +

      Else if command is "truncate":

      +
        +
      1. +

        If chunk.size is undefined, reject p with a TypeError and abort.

        +
      2. +

        Let newSize be chunk.size.

        +
      3. +

        Let oldSize be stream.[[buffer]]'s length.

        +
      4. +

        If newSize is larger than oldSize:

        +
          +
        1. +

          Set stream.[[buffer]] to a byte sequence formed by concating stream.[[buffer]] with a byte sequence containing newSize-oldSize 0x00 bytes.

          +
        2. +

          If the operation in the previous step failed due to exceeding the storage quota, reject p with a QuotaExceededError and abort, + leaving stream.[[buffer]] unmodified.

          +

          Note: Storage quota only applies to files stored in the origin private file system. + However this operation could still fail for other files, for example if the disk being written + to runs out of disk space.

          +
        +
      5. +

        Else if newSize is smaller than oldSize:

        +
          +
        1. +

          Set stream.[[buffer]] to a byte sequence containing the first newSize bytes + in stream.[[buffer]].

          +
        +
      6. +

        If stream.[[seekOffset]] is bigger than newSize, + set stream.[[seekOffset]] to newSize.

        +
      7. +

        Resolve p.

        +
      +
    +
  4. +

    Return p.

    +
+
+

2.6.1. The write() method

+
+
+
await stream . write(data) +
await stream . write({ type: "write", 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 . write({ type: "write", position: position, 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 . write({ type: "seek", position: position }) +
+

Updates the current file cursor offset the position bytes from the top of the file.

+
await stream . write({ type: "truncate", size: size }) +
+

Resizes the file associated with stream to be size bytes long. If size is larger than + the current file size this pads the file with null bytes, otherwise it truncates the file.

+

The file cursor is updated when truncate is called. If the offset is smaller than offset, + it remains unchanged. If the offset is larger than size, the offset is set to size to + ensure that subsequent writes do not error.

+

No changes are written to the actual file until on disk until the stream has been closed. + Changes are typically written to a temporary file instead.

+
+
+
+ +
+

FileSystemWritableFileStream/write

+

In only one current engine.

+
+ FirefoxNoneSafariNoneChrome86+ +
+ OperaNoneEdge86+ +
+ Edge (Legacy)NoneIENone +
+ Firefox for AndroidNoneiOS SafariNoneChrome for AndroidNoneAndroid WebViewNoneSamsung InternetNoneOpera MobileNone +
+
+
+
+ The write(data) method, when invoked, must run +these steps: +
    +
  1. +

    Let writer be the result of getting a writer for this.

    +
  2. +

    Let result be the result of writing a chunk to writer given data.

    +
  3. +

    Release writer.

    +
  4. +

    Return result.

    +
+
+

2.6.2. The seek() method

+
+
+
await stream . seek(position) +
+

Updates the current file cursor offset the position bytes from the top of the file.

+
+
+
+ +
+

FileSystemWritableFileStream/seek

+

In only one current engine.

+
+ FirefoxNoneSafariNoneChrome86+ +
+ OperaNoneEdge86+ +
+ Edge (Legacy)NoneIENone +
+ Firefox for AndroidNoneiOS SafariNoneChrome for AndroidNoneAndroid WebViewNoneSamsung InternetNoneOpera MobileNone +
+
+
+
+ The seek(position) method, when invoked, must run these +steps: +
    +
  1. +

    Let writer be the result of getting a writer for this.

    +
  2. +

    Let result be the result of writing a chunk to writer given + «[ "type" → "seek", "position" → position ]».

    +
  3. +

    Release writer.

    +
  4. +

    Return result.

    +
+
+

2.6.3. The truncate() method

+
+
+
await stream . truncate(size) +
+

Resizes the file associated with stream to be size bytes long. If size is larger than + the current file size this pads the file with null bytes, otherwise it truncates the file.

+

The file cursor is updated when truncate is called. If the offset is smaller than offset, + it remains unchanged. If the offset is larger than size, the offset is set to size to + ensure that subsequent writes do not error.

+

No changes are written to the actual file until on disk until the stream has been closed. + Changes are typically written to a temporary file instead.

+
+
+
+ +
+

FileSystemWritableFileStream/truncate

+

In only one current engine.

+
+ FirefoxNoneSafariNoneChrome86+ +
+ OperaNoneEdge86+ +
+ Edge (Legacy)NoneIENone +
+ Firefox for AndroidNoneiOS SafariNoneChrome for AndroidNoneAndroid WebViewNoneSamsung InternetNoneOpera MobileNone +
+
+
+
+ The truncate(size) method, when invoked, must run these +steps: +
    +
  1. +

    Let writer be the result of getting a writer for this.

    +
  2. +

    Let result be the result of writing a chunk to writer given + «[ "type" → "truncate", "size" → size ]».

    +
  3. +

    Release writer.

    +
  4. +

    Return result.

    +
+
+

2.7. The FileSystemSyncAccessHandle interface

+
dictionary FilesystemReadWriteOptions {
+  [EnforceRange] unsigned long long at;
+}
+
+[Exposed=DedicatedWorker, SecureContext]
+interface FileSystemSyncAccessHandle {
+  unsigned long long read([AllowShared] BufferSource buffer,
+                             FilesystemReadWriteOptions options);
+  unsigned long long write([AllowShared] BufferSource buffer,
+                              FilesystemReadWriteOptions options);
+
+  Promise<undefined> truncate([EnforceRange] unsigned long long size);
+  Promise<unsigned long long> getSize();
+  Promise<undefined> flush();
+  Promise<undefined> close();
+};
+
+
+

A FileSystemSyncAccessHandle has an associated [[file]] (a file entry).

+
+

A FileSystemSyncAccessHandle is an object that is capable of reading/writing from/to, +as well as obtaining and changing the size of, a single file.

+

The read() and write() methods are synchronous. +This allows for higher performance for critical methods on contexts where asynchronous +operations come with high overhead i.e., WebAssembly.

+
+
+ To create a new FileSystemSyncAccessHandle given a file entry file in a Realm realm, perform the following steps: +
    +
  1. +

    Let handle be a new FileSystemSyncAccessHandle in realm.

    +
  2. +

    Set handle.[[file]] to file.

    +
  3. +

    Return handle.

    +
+
+

2.7.1. The read() method

+
+
+
handle . read(buffer, at: position) +
+

Reads the contents of the file associated with handle into buffer, with position as the offset.

+
+
+
+ The read(buffer, at: position) method, when invoked, must run +these steps: +

// TODO(fivedots): fill in. Does this algorithm need to check that the access handle has not been closed?

+
+

2.7.2. The write() method

+
+
+
handle . write(buffer, at: position) +
+

Writes the content of buffer into the file associated with handle with position as the offset.

+
+
+
+ The write(buffer, at: position) method, when invoked, must run +these steps: +

// TODO(fivedots): fill in. Does this algorithm need to explicitly interact with storage quota?

+
+

2.7.3. The truncate() method

+
+
+
handle . 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 truncate(size) method, when invoked, must run +these steps: +

// TODO(fivedots): fill in.

+
+

2.7.4. The getSize() method

+
+
+
handle . getSize() +
+

Returns the size of the file associated with handle in bytes.

+
+
+
+ The getSize() method, when invoked, must run +these steps: +

// TODO(fivedots): fill in.

+
+

2.7.5. The flush() method

+
+
+
handle . flush() +
+

Ensures that the contents of the file associated with handle contain all the modifications done through read() and write().

+
+
+
+ The flush() method, when invoked, must run +these steps: +

// TODO(fivedots): fill in.

+
+

2.7.6. The close() method

+
+
+
handle . close() +
+

Flushes the access handle and then closes it. Closing an access handle disables any further operations on it and releases the lock on the entry associated with handle.

+
+
+
+ The close() method, when invoked, must run +these steps: +

// TODO(fivedots): fill in.

+
+

3. Accessing Local File System

+
enum WellKnownDirectory {
+  "desktop",
+  "documents",
+  "downloads",
+  "music",
+  "pictures",
+  "videos",
+};
+
+typedef (WellKnownDirectory or FileSystemHandle) StartInDirectory;
+
+dictionary FilePickerAcceptType {
+    USVString description;
+    record<USVString, (USVString or sequence<USVString>)> accept;
+};
+
+dictionary FilePickerOptions {
+    sequence<FilePickerAcceptType> types;
+    boolean excludeAcceptAllOption = false;
+    DOMString id;
+    StartInDirectory startIn;
+};
+
+dictionary OpenFilePickerOptions : FilePickerOptions {
+    boolean multiple = false;
+};
+
+dictionary SaveFilePickerOptions : FilePickerOptions {
+    USVString? suggestedName;
+};
+
+dictionary DirectoryPickerOptions {
+    DOMString id;
+    StartInDirectory startIn;
+};
+
+[SecureContext]
+partial interface Window {
+    Promise<sequence<FileSystemFileHandle>> showOpenFilePicker(optional OpenFilePickerOptions options = {});
+    Promise<FileSystemFileHandle> showSaveFilePicker(optional SaveFilePickerOptions options = {});
+    Promise<FileSystemDirectoryHandle> showDirectoryPicker(optional DirectoryPickerOptions options = {});
+};
+
+

The showOpenFilePicker(), showSaveFilePicker() and showDirectoryPicker() methods +are together known as the local file system handle factories.

+

Note: What is referred to as the "local file system" in this spec, does not have to +strictly refer to the file system on the local device. What we call the local file system +could just as well be backed by a cloud provider. For example on Chrome OS these +file pickers will also let you pick files and directories on Google Drive.

+

In Chrome versions earlier than 85, this was implemented as a generic chooseFileSystemEntries method.

+

3.1. Local File System Permissions

+

The fact that the user picked the specific files returned by the local file system handle factories in a prompt +should be treated by the user agent as the user intending to grant read access to the website +for the returned files. As such, at the time the promise returned by one of the local file system handle factories resolves, permission state for a descriptor with handle set to the returned handle, +and mode set to "read" should be "granted".

+

Additionally for calls to showSaveFilePicker the permission state for a descriptor with handle set to the returned handle, +and mode set to readwrite should be "granted".

+
+ To verify that an environment is allowed to show a file picker, run these steps: +
    +
  1. +

    If environment’s origin is an opaque origin, + return a promise rejected with a SecurityError.

    +
  2. +

    If environment’s origin is not same origin with environment’s top-level origin, + return a promise rejected with a SecurityError.

    +
  3. +

    Let global be environment’s global object.

    +
  4. +

    If global does not have transient activation, throw a SecurityError.

    +
+
+

3.2. File picker options

+

3.2.1. Accepted file types

+
+ The showOpenFilePicker(options) and showSaveFilePicker(options) methods accept a FilePickerOptions argument, which lets the website specify the types of files +the file picker will let the user select. +

Each entry in types specifies a single user selectable option +for filtering the files displayed in the file picker.

+

Each option consists of an optional description and a number of MIME types and extensions (specified as a mapping of +MIME type to a list of extensions). If no description is provided one will be generated. +Extensions have to be strings that start with a "." and only contain valid suffix code points. +Additionally extensions are limited to a length of 16 code points.

+

In addition to complete MIME types, "*" can be used as the subtype of a MIME type to match +for example all image formats with "image/*".

+

Websites should always provide both MIME types and file +extensions for each option. On platforms that only use file extensions to describe file types +user agents can match on the extensions, while on platforms that don’t use extensions, +user agents can match on MIME type.

+

By default the file picker will also include an option to not apply any filter, +letting the user select any file. Set excludeAcceptAllOption to true to not +include this option in the file picker.

+

For example , the following options will let the user pick one of three different filters. +One for text files (either plain text or HTML), one for images, and a third one that doesn’t apply +any filter and lets the user select any file.

+
const options = {
+  types: [
+    {
+      description: 'Text Files',
+      accept: {
+        'text/plain': ['.txt', '.text'],
+        'text/html': ['.html', '.htm']
+      }
+    },
+    {
+      description: 'Images',
+      accept: {
+        'image/*': ['.png', '.gif', '.jpeg', '.jpg']
+      }
+    }
+  ],
+};
+
+

On the other hand, the following example will only let the user select SVG files. The dialog +will not show an option to not apply any filters.

+
const options = {
+  types: [
+    {
+      accept: {
+        'image/svg+xml': '.svg'
+      }
+    },
+  ],
+  excludeAcceptAllOption: true
+};
+
+
+
+ To process accept types, given FilePickerOptions options, +run these steps: +
    +
  1. +

    Let accepts options be a empty list of pairs.

    +
  2. +

    For each type of options.types:

    +
      +
    1. +

      Let description be type.description.

      +
    2. +

      For each typeStringsuffixes of type.accept:

      +
        +
      1. +

        Let parsedType be the result of parse a MIME type with typeString.

        +
      2. +

        If parsedType is failure, throw a TypeError.

        +
      3. +

        If parsedType’s parameters are not empty, throw a TypeError.

        +
      4. +

        If suffixes is a string:

        +
          +
        1. +

          Validate a suffix given suffixes.

          +
        +
      5. +

        Otherwise, for each suffix of suffixes:

        +
          +
        1. +

          Validate a suffix given suffix.

          +
        +
      +
    3. +

      Let filter be the following steps, given a filename (a string), and a type (a MIME type):

      +
        +
      1. +

        For each typeStringsuffixes of type.accept:

        +
      2. +

        Let parsedType be the result of parse a MIME type with typeString.

        +
          +
        1. +

          If parsedType’s subtype is "*":

          +
            +
          1. +

            If parsedType’s type is "*", return true.

            +
          2. +

            If parsedType’s type is type’s type, return true.

            +
          +
        2. +

          parsedType’s essence is type’s essence, return true.

          +
        3. +

          If suffixes is a string, set suffixes to « suffixes ».

          +
        4. +

          For each suffix of suffixes:

          +
            +
          1. +

            If filename ends with suffix, return true.

            +
          +
        +
      3. +

        Return false.

        +
      +
    4. +

      If description is an empty string, +set description to some user understandable string describing filter.

      +
    5. +

      Append description/filter to accepts options.

      +
    +
  3. +

    If either accepts options is empty, +or options.excludeAcceptAllOption is false:

    +
      +
    1. +

      Let description be a user understandable string describing "all files".

      +
        +
      1. +

        Let filter be an algorithm that returns true.

        +
      2. +

        Append description/filter to accepts options.

        +
      +
    +
  4. +

    If accepts options is empty, throw a TypeError.

    +
  5. +

    Return accepts options.

    +
+
+
+ To validate a suffix suffix, run the following steps: +
    +
  1. +

    If suffix does not start with ".", throw a TypeError.

    +
  2. +

    If suffix contains any code points that are not valid suffix code points, + throw a TypeError.

    +
  3. +

    If suffix ends with ".", throw a TypeError.

    +
  4. +

    If suffix’s length is more than 16, throw a TypeError.

    +
+
+

3.2.2. Starting Directory

+
+ The id and startIn fields can be specified to suggest +the directory in which the file picker opens. +

If neither of these options are specified, the user agent remembers the last directory a file +or directory was picked from, and new pickers will start out in that directory. By specifying an id the user agent can remember different directories for different IDs +(user agents will only remember directories for a limited number of IDs).

+
// If a mapping exists from this ID to a previousy picked directory, start in
+// this directory. Otherwise, a mapping will be created from this ID to the
+// directory of the resulting file picker invocation.
+const options = {
+  id: 'foo',
+};
+
+

Specifying startIn as a FileSystemFileHandle will result in the dialog +starting in the parent directory of that file, while passing in a FileSystemDirectoryHandle will result in the dialog to start in the passed in directory. These take precedence even if +an explicit id is also passed in.

+

For example, given a FileSystemDirectoryHandle project_dir, the following will show +a file picker that starts out in that directory:

+
// The picker will open to the directory of |project_dir| regardless of whether
+// 'foo' has a valid mapping.
+const options = {
+  id: 'foo',
+  startIn: |project_dir|,
+};
+
+

The id and startIn fields control only +the directory the picker opens to. In the above example, it cannot be assumed that +the id 'foo' will map to the same directory as project_dir once the file picker operation has completed.

+

Specifying startIn as a WellKnownDirectory will result in the dialog +starting in that directory, unless an explicit id was also passed +in which has a mapping to a valid directory.

+

Below is an example of specifying both an id and startIn as a WellKnownDirectory. If there is an existing +mapping from the given ID to a path, this mapping is used. Otherwise, the path suggested +via the WellKnownDirectory is used.

+
// First time specifying the ID 'foo'. It is not mapped to a directory.
+// The file picker will fall back to opening to the Downloads directory. TODO: link this.
+const options = {
+  id: 'foo',  // Unmapped.
+  startIn: "downloads",  // Start here.
+};
+
+// Later...
+
+// The ID 'foo' might or might not be mapped. For example, the mapping for this ID
+// might have been evicted.
+const options = {
+  id: 'foo',  // Maybe mapped. If so, start here.
+  startIn: "downloads",  // Otherwise, start here.
+};
+
+
+

The startIn and id options were first introduced in Chrome 91.

+

A user agent holds a recently picked directory map, which is a map of origins to path id maps.

+

A path id map is a map of valid path ids to paths.

+

A valid path id is a string where each character is ASCII alphanumeric or "_" or "-".

+

To prevent a path id map from growing without a bound, user agents should implement +some mechanism to limit how many recently picked directories will be remembered. This +can for example be done by evicting least recently used entries. +User agents should allow at least 16 entries to be stored in a path id map.

+

The WellKnownDirectory enum lets a website pick one of several well-known +directories. The exact paths the various values of this enum map to is implementation-defined (and in some cases these might not even represent actual paths on disk). +The following list describes the meaning of each of the values, and gives possible example paths on different operating systems:

+
+
+
"desktop" +
+

The user’s Desktop directory, if such a thing exists. For example this could be C:\Documents and Settings\username\Desktop, /Users/username/Desktop, or /home/username/Desktop.

+
"documents" +
+

Directory in which documents created by the user would typically be stored. + For example C:\Documents and Settings\username\My Documents, /Users/username/Documents, or /home/username/Documents.

+
"downloads" +
+

Directory where downloaded files would typically be stored. + For example C:\Documents and Settings\username\Downloads, /Users/username/Downloads, or /home/username/Downloads.

+
"music" +
+

Directory where audio files would typically be stored. + For example C:\Documents and Settings\username\My Documents\My Music, /Users/username/Music, or /home/username/Music.

+
"pictures" +
+

Directory where photos and other still images would typically be stored. + For example C:\Documents and Settings\username\My Documents\My Pictures, /Users/username/Pictures, or /home/username/Pictures.

+
"videos" +
+

Directory where videos/movies would typically be stored. + For example C:\Documents and Settings\username\My Documents\My Videos, /Users/username/Movies, or /home/username/Videos.

+
+
+ To determine the directory the picker will start in, given an optional string id, +an optional StartInDirectory startIn and an environment settings object environment, +run the following steps: +
    +
  1. +

    If id given, and is not a valid path id, throw a TypeError.

    +
  2. +

    If id’s length is more than 32, throw a TypeError.

    +
  3. +

    Let origin be environment’s origin.

    +
  4. +

    If startIn is a FileSystemHandle:

    +
      +
    1. +

      Let entry be startIn’s entry.

      +
    2. +

      If entry does not represent an entry in an origin private file system:

      +
        +
      1. +

        If entry is a file entry, and a path on the local file system + corresponding to the parent directory if entry can be determined, + then return that path.

        +
      2. +

        If entry is a directory entry, and a path on the local file system + corresponding to entry can be determined, + then return that path.

        +
      +
    +
  5. +

    If id is non-empty:

    +
      +
    1. +

      If recently picked directory map[origin] exists:

      +
        +
      1. +

        Let path map be recently picked directory map[origin].

        +
      2. +

        If path map[id] exists, then return path map[id].

        +
      +
    +
  6. +

    If startIn is a WellKnownDirectory:

    +
      +
    1. +

      Return a user agent defined path corresponding to the WellKnownDirectory value of startIn.

      +
    +
  7. +

    If id is not specified, or is an empty string:

    +
      +
    1. +

      If recently picked directory map[origin] exists:

      +
        +
      1. +

        Let path map be recently picked directory map[origin].

        +
      2. +

        If path map[""] exists, then return path map[""].

        +
      +
    +
  8. +

    Return a default path in a user agent specific manner.

    +
+
+
+ To remember a picked directory, given an optional string id, +an entry entry, and an environment settings object environment, +run the following steps: +
    +
  1. +

    Let origin be environment’s origin.

    +
  2. +

    If recently picked directory map[origin] does not exist, + then set recently picked directory map[origin] to an empty path id map.

    +
  3. +

    If id is not specified, let id be an empty string.

    +
  4. +

    Set recently picked directory map[origin][id] to the path on the local file system corresponding to entry, + if such a path can be determined.

    +
+
+

3.3. The showOpenFilePicker() method

+
+ +
+

window/showopenfilepicker

+

In only one current engine.

+
+ FirefoxNoneSafariNoneChrome86+ +
+ Opera72+Edge86+ +
+ Edge (Legacy)NoneIENone +
+ Firefox for AndroidNoneiOS SafariNoneChrome for AndroidNoneAndroid WebViewNoneSamsung InternetNoneOpera MobileNone +
+
+
+
+
+
[ handle ] = await window . showOpenFilePicker() +
[ handle ] = await window . showOpenFilePicker({ multiple: false }) +
+

Shows a file picker that lets a user select a single existing file, returning a handle for +the selected file.

+
handles = await window . showOpenFilePicker({ multiple: true }) +
+

Shows a file picker that lets a user select multiple existing files, returning handles for +the selected files.

+

Additional options can be passed to showOpenFilePicker() to indicate the types of files +the website wants the user to select and the directory in which the +file picker will open. See § 3.2 File picker options for details.

+
+
+
+ The showOpenFilePicker(options) method, when invoked, must run +these steps: +
    +
  1. +

    Let environment be this’s relevant settings object.

    +
  2. +

    Let accepts options be the result of processing accept types given options.

    +
  3. +

    Let starting directory be the result of determining the directory the picker will start in given options.id, options.startIn and environment.

    +
  4. +

    Let global be environment’s global object.

    +
  5. +

    Verify that environment is allowed to show a file picker.

    +
  6. +

    Let p be a new promise.

    +
  7. +

    Run the following steps in parallel:

    +
      +
    1. +

      Optionally, wait until any prior execution of this algorithm has terminated.

      +
    2. +

      Display a prompt to the user requesting that the user pick some files. + If options.multiple is false, there must be no more than one file selected; + otherwise any number may be selected.

      +

      The displayed prompt should let the user pick one of the accepts options to filter the list of displayed files. + Exactly how this is implemented, and what this prompt looks like is implementation-defined.

      +

      When possible, this prompt should start out showing starting directory.

      +
    3. +

      Wait for the user to have made their selection.

      +
    4. +

      If the user dismissed the prompt without making a selection, reject p with an AbortError and abort.

      +
    5. +

      Let entries be a list of file entries representing the selected files or directories.

      +
    6. +

      Let result be a empty list.

      +
    7. +

      For each entry of entries:

      +
        +
      1. +

        If entry is deemed too sensitive or dangerous to be exposed to this website by the user agent:

        +
          +
        1. +

          Inform the user that the selected files or directories can’t be exposed to this website.

          +
        2. +

          At the discretion of the user agent, + either go back to the beginning of these in parallel steps, + or reject p with an AbortError and abort.

          +
        +
      2. +

        Add a new FileSystemFileHandle associated with entry to result.

        +
      +
    8. +

      Remember a picked directory given options.id, entries[0] and environment.

      +
    9. +

      Perform the activation notification steps in global’s browsing context.

      +

      Note: This lets a website immediately perform operations on the returned handles that + might require user activation, such as requesting more permissions.

      +
    10. +

      Resolve p with result.

      +
    +
  8. +

    Return p.

    +
+
+

3.4. The showSaveFilePicker() method

+
+ +
+

window/showsavefilepicker

+

In only one current engine.

+
+ FirefoxNoneSafariNoneChrome86+ +
+ Opera72+Edge86+ +
+ Edge (Legacy)NoneIENone +
+ Firefox for AndroidNoneiOS SafariNoneChrome for AndroidNoneAndroid WebViewNoneSamsung InternetNoneOpera MobileNone +
+
+
+
+
+
handle = await window . showSaveFilePicker( options ) +
+

Shows a file picker that lets a user select a single file, returning a handle for +the selected file. The selected file does not have to exist already. If the selected +file does not exist a new empty file is created before this method returns, otherwise +the existing file is cleared before this method returned.

+
handle = await window . showSaveFilePicker({ suggestedName: "README.md" }) +
+

Shows a file picker with the suggested "README.md" file name pre-filled as the default file name to save as.

+

Additional options can be passed to showSaveFilePicker() to indicate the types of files +the website wants the user to select and the directory in which the +file picker will open. See § 3.2 File picker options for details.

+
+
+

The suggestedName option was first introduced in Chrome 91.

+
+ The showSaveFilePicker(options) method, when invoked, must run +these steps: +
    +
  1. +

    Let environment be this’s relevant settings object.

    +
  2. +

    Let accepts options be the result of processing accept types given options.

    +
  3. +

    Let starting directory be the result of determining the directory the picker will start in given options.id, options.startIn and environment.

    +
  4. +

    Let global be environment’s global object.

    +
  5. +

    Verify that environment is allowed to show a file picker.

    +
  6. +

    Let p be a new promise.

    +
  7. +

    Run the following steps in parallel:

    +
      +
    1. +

      Optionally, wait until any prior execution of this algorithm has terminated.

      +
    2. +

      Display a prompt to the user requesting that the user pick exactly one file. + The displayed prompt should let the user pick one of the accepts options to filter the list of displayed files. + Exactly how this is implemented, and what this prompt looks like is implementation-defined. + If accepts options are displayed in the UI, the selected option should also be used to suggest an extension + to append to a user provided file name, but this is not required. In particular user agents are free to ignore + potentially dangerous suffixes such as those ending in ".lnk" or ".local".

      +

      When possible, this prompt should start out showing starting directory.

      +

      If options.suggestedName is specified and not null, + the file picker prompt will be pre-filled with the options.suggestedName as the default name to save as. The interaction between the suggestedName and accepts options is implementation-defined. + If the suggestedName is deemed too dangerous, user agents should ignore or sanitize the + suggested file name, similar to the sanitization done when fetching something as a download.

      +

      Note: A user agent could for example pick whichever option in accepts options that matches suggestedName as the default filter.

      +
    3. +

      Wait for the user to have made their selection.

      +
    4. +

      If the user dismissed the prompt without making a selection, reject p with an AbortError and abort.

      +
    5. +

      Let entry be a file entry representing the selected file.

      +
    6. +

      If entry is deemed too sensitive or dangerous to be exposed to this website by the user agent:

      +
        +
      1. +

        Inform the user that the selected files or directories can’t be exposed to this website.

        +
      2. +

        At the discretion of the user agent, + either go back to the beginning of these in parallel steps, + or reject p with an AbortError and abort.

        +
      +
    7. +

      Set entry’s binary data to an empty byte sequence.

      +
    8. +

      Set result to a new FileSystemFileHandle associated with entry.

      +
    9. +

      Remember a picked directory given options.id, entry and environment.

      +
    10. +

      Perform the activation notification steps in global’s browsing context.

      +

      Note: This lets a website immediately perform operations on the returned handles that + might require user activation, such as requesting more permissions.

      +
    11. +

      Resolve p with result.

      +
    +
  8. +

    Return p.

    +
+
+

3.5. The showDirectoryPicker() method

+
+ +
+

window/showdirectorypicker

+

In only one current engine.

+
+ FirefoxNoneSafariNoneChrome86+ +
+ Opera72+Edge86+ +
+ Edge (Legacy)NoneIENone +
+ Firefox for AndroidNoneiOS SafariNoneChrome for AndroidNoneAndroid WebViewNoneSamsung InternetNoneOpera MobileNone +
+
+
+
+
+
handle = await window . showDirectoryPicker() +
+

Shows a directory picker that lets the user select a single directory, returning a handle for +the selected directory.

+
+

The id and startIn fields behave + identically to the id and startIn fields, respectively. + See § 3.2.2 Starting Directory for details on how to use these fields.

+
+
+ The showDirectoryPicker(options) method, when invoked, must run +these steps: +
    +
  1. +

    Let environment be this’s relevant settings object.

    +
  2. +

    Let starting directory be the result of determining the directory the picker will start in given options.id, options.startIn and environment.

    +
  3. +

    Let global be environment’s global object.

    +
  4. +

    Verify that environment is allowed to show a file picker.

    +
  5. +

    Let p be a new promise.

    +
  6. +

    Run the following steps in parallel:

    +
      +
    1. +

      Optionally, wait until any prior execution of this algorithm has terminated.

      +
    2. +

      Display a prompt to the user requesting that the user pick a directory.

      +

      When possible, this prompt should start out showing starting directory.

      +
    3. +

      Wait for the user to have made their selection.

      +
    4. +

      If the user dismissed the prompt without making a selection, reject p with an AbortError and abort.

      +
    5. +

      Let entry be a directory entry representing the selected directory.

      +
    6. +

      If entry is deemed too sensitive or dangerous to be exposed to this website by the user agent:

      +
        +
      1. +

        Inform the user that the selected files or directories can’t be exposed to this website.

        +
      2. +

        At the discretion of the user agent, + either go back to the beginning of these in parallel steps, + or reject p with an AbortError and abort.

        +
      +
    7. +

      Set result to a new FileSystemDirectoryHandle associated with entry.

      +
    8. +

      Remember a picked directory given options.id, entry and environment.

      +
    9. +

      Perform the activation notification steps in global’s browsing context.

      +

      Note: This lets a website immediately perform operations on the returned handles that + might require user activation, such as requesting more permissions.

      +

      Rather than requiring the website to prompt separately for a writable directory, + we should provide some kind of API to request a writable directory in one step. [Issue #89]

      +
    10. +

      Resolve p with result.

      +
    +
  7. +

    Return p.

    +
+
+

3.6. Drag and Drop

+
partial interface DataTransferItem {
+    Promise<FileSystemHandle?> getAsFileSystemHandle();
+};
+
+

During a drag-and-drop operation, dragged file and +directory items are associated with file entries and directory entries respectively.

+
+
+
handle = await item . getAsFileSystemHandle() +
+

Returns a FileSystemFileHandle object if the dragged item is a file and a FileSystemDirectoryHandle object if the dragged item is a directory.

+
+
+
+
+ +
+

DataTransferItem/getAsFileSystemHandle

+

In only one current engine.

+
+ FirefoxNoneSafariNoneChrome86+ +
+ Opera72+Edge86+ +
+ Edge (Legacy)NoneIENone +
+ Firefox for AndroidNoneiOS SafariNoneChrome for AndroidNoneAndroid WebViewNoneSamsung InternetNoneOpera MobileNone +
+
+
+

The getAsFileSystemHandle() method steps are:

+
    +
  1. +

    If the DataTransferItem object is not in the read/write + mode or the read-only mode, return a promise resolved with null.

    +
  2. +

    If the the drag data item kind is not File, + then return a promise resolved with null.

    +
  3. +

    Let p be a new promise.

    +
  4. +

    Run the following steps in parallel:

    +
      +
    1. +

      Let entry be the entry representing the dragged file or directory.

      +
    2. +

      If entry is a file entry:

      +
        +
      1. +

        Let handle be a FileSystemFileHandle associated with entry.

        +
      +
    3. +

      Else if entry is a directory entry:

      +
        +
      1. +

        Let handle be a FileSystemDirectoryHandle associated with entry.

        +
      +
    4. +

      Resolve p with entry.

      +
    +
  5. +

    Return p.

    +
+
+
+ Handling drag and drop of files and directories: +
elem.addEventListener('dragover', (e) => {
+  // Prevent navigation.
+  e.preventDefault();
+});
+elem.addEventListener('drop', async (e) => {
+  // Prevent navigation.
+  e.preventDefault();
+
+  // Process all of the items.
+  for (const item of e.dataTransfer.items) {
+    // kind will be 'file' for file/directory entries.
+    if (item.kind === 'file') {
+      const entry = await item.getAsFileSystemHandle();
+      if (entry.kind === 'file') {
+        handleFileEntry(entry);
+      } else if (entry.kind === 'directory') {
+        handleDirectoryEntry(entry);
+      }
+    }
+  }
+});
+
+
+

This currently does not block access to too sensitive or dangerous directories, to +be consistent with other APIs that give access to dropped files and directories. This is inconsistent +with the local file system handle factories though, so we might want to reconsider this.

+

4. Accessing the Origin Private File System

+

The origin private file system is a storage endpoint whose identifier is "fileSystem", types are « "local" », +and quota is null.

+

Storage endpoints should be defined in [storage] itself, rather +than being defined here. So merge this into the table there.

+

Note: While user agents will typically implement this by persisting the contents of this origin private file system to disk, it is not intended that the contents are easily +user accessible. Similarly there is no expectation that files or directories with names +matching the names of children of the origin private file system exist.

+
[SecureContext]
+partial interface StorageManager {
+  Promise<FileSystemDirectoryHandle> getDirectory();
+};
+
+

In Chrome this functionality was previously exposed as FileSystemDirectoryHandle.getSystemDirectory({type: "sandbox"}). +This new method is available as of Chrome 85.

+
+
+
directoryHandle = await navigator . storage . getDirectory() +
+

Returns the root directory of the origin private file system.

+
+
+
+ The getDirectory() method, when +invoked, must run these steps: +
    +
  1. +

    Let environment be the current settings object.

    +
  2. +

    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.

    +
  3. +

    If map["root"] does not exist:

    +
      +
    1. +

      Let dir be a new directory entry.

      +
    2. +

      Set dir’s name to "".

      +
    3. +

      Set dir’s children to an empty set.

      +
    4. +

      Set map["root"] to dir.

      +
    +
  4. +

    Return a promise resolved with a new FileSystemDirectoryHandle, + whose associated entry is map["root"].

    +
+
+

Note: In Chrome the directory entry returned by the above algorithm refers to the same storage as the temporary +file system as used to be defined in File API: Directories and System.

+

5. Accessibility Considerations

+

This section is non-normative.

+

When this specification is used to present information in the user interface, +implementors will want to follow the OS level accessibility guidelines for the platform.

+

6. Privacy Considerations

+

This section is non-normative.

+

This API does not give websites any more read access to data than the existing <input type=file> and <input type=file webkitdirectory> APIs already do. Furthermore similarly to those APIs, all +access to files and directories is explicitly gated behind a file or directory picker.

+

There are however several major privacy risks with this new API:

+

6.1. Users giving access to more, or more sensitive files than they intended.

+

This isn’t a new risk with this API, but user agents should try to make sure that users are aware +of what exactly they’re giving websites access to. This is particularly important when giving +access to a directory, where it might not be immediately clear to a user just how many files +actually exist in that directory.

+

A related risk is having a user give access to particularly sensitive data. This +could include some of a user agent’s configuration data, network cache or cookie store, +or operating system configuration data such as password files. To protect against this, user agents +are encouraged to restrict which directories a user is allowed to select in a directory picker, +and potentially even restrict which files the user is allowed to select. This will make it much +harder to accidentally give access to a directory that contains particularly sensitive data. Care +must be taken to strike the right balance between restricting what the API can access while still +having the API be useful. After all, this API intentionally lets the user use websites to interact +with some of their most private personal data.

+

Examples of directories that user agents might want to restrict as being too sensitive or dangerous include:

+
    +
  • +

    The directory or directories containing the user agent itself.

    +
  • +

    Directories where the user agent stores website storage.

    +
  • +

    Directories containing system files (such as C:\Windows on Windows).

    +
  • +

    Directories such as /dev/, /sys, and /proc on Linux that would give access to low-level devices.

    +
  • +

    A user’s entire "home" directory. +Individual files and directories inside the home directory should still be allowed, +but user agents should not generally let users give blanket access to the entire directory.

    +
  • +

    The default directory for downloads, if the user agent has such a thing. +Individual files inside the directory again should be allowed, but the whole directory would risk leaking more data than a user realizes.

    +
  • +

    Files with names that end in .lnk, when selecting a file to write to. Writing to +these files on Windows is similar to creating symlinks on other operating systems, +and as such can be used to attempt to trick users into giving access to files they didn’t intend to expose.

    +
  • +

    Files with names that end in .local, when selecting a file to write to. +Windows uses these files to decide what DLLs to load, and as such writing to +these files could be used to cause code to be executed.

    +
+

6.2. Websites trying to use this API for tracking.

+

This API could be used by websites to track the user across clearing browsing +data. This is because, in contrast with existing file access APIs, user agents are +able to grant persistent access to files or directories and can re-prompt. In +combination with the ability to write to files, websites will be able to persist an +identifier on the users' disk. Clearing browsing data will not affect those files +in any way, making these identifiers persist through those actions.

+

This risk is somewhat mitigated by the fact that clearing browsing data will clear all handles +that a website had persisted (for example in IndexedDB), +so websites won’t have any handles to re-prompt for permission after browsing data was cleared. +Furthermore user agents are encouraged to make it clear what files and directories a website has +access to, and to automatically expire permission grants except for particularly well trusted +origins (for example persistent permissions could be limited to "installed" web applications).

+

User agents also are encouraged to provide a way for users to revoke permissions granted. +Clearing browsing data is expected to revoke all permissions as well.

+

6.3. First-party vs third-party contexts.

+

In third-party contexts (e.g. an iframe whose origin does not match that of the top-level frame) +websites can’t gain access to data they don’t already have access to. This includes both getting +access to new files or directories via the local file system handle factories, as well as requesting +more permissions to existing handles via the requestPermission API.

+

Handles can also only be post-messaged to same-origin destinations. Attempts to send a handle to +a cross-origin destination will result in a messageerror event.

+

7. Security Considerations

+

This section is non-normative.

+

This API gives websites the ability to modify existing files on disk, as well as write to new +files. This has a couple of important security considerations:

+

7.1. Malware

+

This API could be used by websites to try to store and/or execute malware on the users system. +To mitigate this risk, this API does not provide any way to mark files as executable (on the other +hand files that are already executable likely remain that way, even after the files are modified +through this API). Furthermore user agents are encouraged to apply things like Mark-of-the-Web to +files created or modified by this API.

+

Finally, user agents are encouraged to verify the contents of files modified by this API via malware +scans and safe browsing checks, unless some kind of external strong trust relation already exists. +This of course has effects on the performance characteristics of this API.

+

7.2. Ransomware attacks

+

Another risk factor is that of ransomware attacks. The limitations described above regarding +blocking access to certain sensitive directories helps limit the damage such an attack can do. +Additionally user agents can grant write access to files at whatever granularity they deem +appropriate.

+

7.3. Filling up a users disk

+

Other than files in the origin private file system, files written by this API are not subject +to storage quota. As such websites can fill up a users disk without being limited by +quota, which could leave a users device in a bad state (do note that even with storage that is +subject to storage quota it is still possible to fill up, or come close to filling up, a users +disk, since storage quota in general is not dependent on the amount of available disk +space).

+

Without this API websites can write data to disk not subject to quota limitations already +by triggering downloads of large files (potentially created client side, to not incur any network +overhead). While the presence of truncate() and writing at a +potentially really large offset past the end of a file makes it much easier and lower cost to +create large files, on most file systems such files should not actually take up as much disk space as +most commonly used file systems support sparse files (and thus wouldn’t actually store the NUL +bytes generated by resizing a file or seeking past the end of it).

+

Whatever mitigations user agents use to guard against websites filling up a disk via either +quota managed storage or the existing downloads mechanism should also be employed when websites +use this API to write to disk.

+
+
+
+

Conformance

+

Document conventions

+

Conformance requirements are expressed + with a combination of descriptive assertions + and RFC 2119 terminology. + The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” + in the normative parts of this document + are to be interpreted as described in RFC 2119. + However, for readability, + these words do not appear in all uppercase letters in this specification.

+

All of the text of this specification is normative + except sections explicitly marked as non-normative, examples, and notes. [RFC2119]

+

Examples in this specification are introduced with the words “for example” + or are set apart from the normative text + with class="example", + like this:

+
+ +

This is an example of an informative example.

+
+

Informative notes begin with the word “Note” + and are set apart from the normative text + with class="note", + like this:

+

Note, this is an informative note.

+

Conformant Algorithms

+

Requirements phrased in the imperative as part of algorithms + (such as "strip any leading space characters" + or "return false and abort these steps") + are to be interpreted with the meaning of the key word + ("must", "should", "may", etc) + used in introducing the algorithm.

+

Conformance requirements phrased as algorithms or specific steps + can be implemented in any manner, + so long as the end result is equivalent. + In particular, the algorithms defined in this specification + are intended to be easy to understand + and are not intended to be performant. + Implementers are encouraged to optimize.

+
+ +

Index

+

Terms defined by this specification

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Terms defined by reference

+
    +
  • + [ECMASCRIPT] defines the following terms: +
      +
    • realm +
    +
  • + [ENCODING] defines the following terms: +
      +
    • utf-8 encode +
    +
  • + [FILE-API] defines the following terms: +
      +
    • Blob +
    • File +
    • lastModified +
    • name +
    • read operation +
    • snapshot state +
    • type +
    • unix epoch +
    +
  • + [HTML] defines the following terms: +
      +
    • DataTransferItem +
    • Serializable +
    • Window +
    • activation notification +
    • as a download +
    • browsing context +
    • current settings object +
    • deserialization steps +
    • environment settings object +
    • global object +
    • in parallel +
    • messageerror +
    • opaque origin +
    • origin (for environment settings object) +
    • read-only mode +
    • read/write mode +
    • relevant realm +
    • relevant settings object +
    • same origin +
    • serializable objects +
    • serialization steps +
    • the drag data item kind +
    • top-level origin +
    • transient activation +
    +
  • + [INFRA] defines the following terms: +
      +
    • append (for set) +
    • ascii alphanumeric +
    • assert +
    • byte sequence +
    • code point +
    • contain +
    • convert +
    • empty +
    • exist +
    • for each (for map) +
    • implementation-defined +
    • is empty +
    • length (for string) +
    • list +
    • map +
    • prepend +
    • remove +
    • set +
    • starts with +
    • string +
    +
  • + [MIMESNIFF] defines the following terms: +
      +
    • essence +
    • mime type +
    • parameters +
    • parse a mime type +
    • subtype +
    • type +
    +
  • + [permissions] defines the following terms: +
      +
    • PermissionDescriptor +
    • PermissionState +
    • PermissionStatus +
    • create a permissionstatus +
    • granted +
    • name +
    • permission descriptor type +
    • permission state +
    • permission state constraints +
    • permissions +
    • powerful feature +
    • prompt +
    • query() +
    • request permission to use +
    • state +
    +
  • + [PERMISSIONS-REQUEST] defines the following terms: +
      +
    • permission request algorithm +
    +
  • + [storage] defines the following terms: +
      +
    • StorageManager +
    • identifier +
    • obtain a local storage bottle map +
    • quota +
    • storage +
    • storage endpoint +
    • storage quota +
    • types +
    +
  • + [STREAMS] defines the following terms: +
      +
    • ReadableStream +
    • WritableStream +
    • WritableStreamDefaultWriter +
    • closealgorithm +
    • getWriter() +
    • getting a writer +
    • highwatermark +
    • release +
    • set up +
    • sizealgorithm +
    • write() +
    • writealgorithm +
    • writing a chunk +
    +
  • + [WEBIDL] defines the following terms: +
      +
    • AbortError +
    • AllowShared +
    • BufferSource +
    • DOMString +
    • DataCloneError +
    • EnforceRange +
    • Exposed +
    • InvalidModificationError +
    • InvalidStateError +
    • NoModificationAllowedError +
    • NotAllowedError +
    • NotFoundError +
    • Promise +
    • QuotaExceededError +
    • SecureContext +
    • SecurityError +
    • TypeError +
    • TypeMismatchError +
    • USVString +
    • a new promise +
    • a promise rejected with +
    • a promise resolved with +
    • asynchronous iterator initialization steps +
    • boolean +
    • get a copy of the buffer source +
    • get the next iteration result +
    • new +
    • record +
    • reject +
    • resolve +
    • sequence +
    • this +
    • undefined +
    • unsigned long long +
    • upon fulfillment +
    • wait for all +
    +
+

References

+

Normative References

+
+
[ECMASCRIPT] +
ECMAScript Language Specification. URL: https://tc39.es/ecma262/multipage/ +
[ENCODING] +
Anne van Kesteren. Encoding Standard. Living Standard. URL: https://encoding.spec.whatwg.org/ +
[FILE-API] +
Marijn Kruisselbrink; Arun Ranganathan. File API. 4 June 2021. WD. URL: https://www.w3.org/TR/FileAPI/ +
[HTML] +
Anne van Kesteren; et al. HTML Standard. Living Standard. URL: https://html.spec.whatwg.org/multipage/ +
[INFRA] +
Anne van Kesteren; Domenic Denicola. Infra Standard. Living Standard. URL: https://infra.spec.whatwg.org/ +
[MIMESNIFF] +
Gordon P. Hemsley. MIME Sniffing Standard. Living Standard. URL: https://mimesniff.spec.whatwg.org/ +
[PERMISSIONS] +
Marcos Caceres; Mike Taylor; Jeffrey Yasskin. Permissions. 28 October 2021. WD. URL: https://www.w3.org/TR/permissions/ +
[PERMISSIONS-REQUEST] +
Requesting Permissions. cg-draft. URL: https://wicg.github.io/permissions-request/ +
[RFC2119] +
S. Bradner. Key words for use in RFCs to Indicate Requirement Levels. March 1997. Best Current Practice. URL: https://datatracker.ietf.org/doc/html/rfc2119 +
[STORAGE] +
Anne van Kesteren. Storage Standard. Living Standard. URL: https://storage.spec.whatwg.org/ +
[STREAMS] +
Adam Rice; et al. Streams Standard. Living Standard. URL: https://streams.spec.whatwg.org/ +
[WEBIDL] +
Edgar Chen; Timothy Gu. Web IDL Standard. Living Standard. URL: https://webidl.spec.whatwg.org/ +
+

Informative References

+
+
[ENTRIES-API] +
File and Directory Entries API. cg-draft. URL: https://wicg.github.io/entries-api/ +
[FILE-SYSTEM-API] +
Eric Uhrhane. File API: Directories and System. 24 April 2014. NOTE. URL: https://www.w3.org/TR/file-system-api/ +
+

IDL Index

+
enum FileSystemPermissionMode {
+  "read",
+  "readwrite"
+};
+
+dictionary FileSystemPermissionDescriptor : PermissionDescriptor {
+  required FileSystemHandle handle;
+  FileSystemPermissionMode mode = "read";
+};
+
+dictionary FileSystemHandlePermissionDescriptor {
+  FileSystemPermissionMode mode = "read";
+};
+
+enum FileSystemHandleKind {
+  "file",
+  "directory",
+};
+
+[Exposed=(Window,Worker), SecureContext, Serializable]
+interface FileSystemHandle {
+  readonly attribute FileSystemHandleKind kind;
+  readonly attribute USVString name;
+
+  Promise<boolean> isSameEntry(FileSystemHandle other);
+
+  Promise<PermissionState> queryPermission(optional FileSystemHandlePermissionDescriptor descriptor = {});
+  Promise<PermissionState> requestPermission(optional FileSystemHandlePermissionDescriptor descriptor = {});
+};
+
+dictionary FileSystemCreateWritableOptions {
+  boolean keepExistingData = false;
+};
+
+enum AccessHandleMode { "in-place" };
+
+dictionary FileSystemFileHandleCreateAccessHandleOptions {
+  required AccessHandleMode mode;
+};
+
+[Exposed=(Window,Worker), SecureContext, Serializable]
+interface FileSystemFileHandle : FileSystemHandle {
+  Promise<File> getFile();
+  Promise<FileSystemWritableFileStream> createWritable(optional FileSystemCreateWritableOptions options = {});
+  [Exposed=DedicatedWorker]
+  Promise<FileSystemSyncAccessHandle> createSyncAccessHandle(optional FileSystemFileHandleCreateAccessHandleOptions options = {});
+};
+
+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);
+};
+
+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);
+};
+
+dictionary FilesystemReadWriteOptions {
+  [EnforceRange] unsigned long long at;
+}
+
+[Exposed=DedicatedWorker, SecureContext]
+interface FileSystemSyncAccessHandle {
+  unsigned long long read([AllowShared] BufferSource buffer,
+                             FilesystemReadWriteOptions options);
+  unsigned long long write([AllowShared] BufferSource buffer,
+                              FilesystemReadWriteOptions options);
+
+  Promise<undefined> truncate([EnforceRange] unsigned long long size);
+  Promise<unsigned long long> getSize();
+  Promise<undefined> flush();
+  Promise<undefined> close();
+};
+
+
+enum WellKnownDirectory {
+  "desktop",
+  "documents",
+  "downloads",
+  "music",
+  "pictures",
+  "videos",
+};
+
+typedef (WellKnownDirectory or FileSystemHandle) StartInDirectory;
+
+dictionary FilePickerAcceptType {
+    USVString description;
+    record<USVString, (USVString or sequence<USVString>)> accept;
+};
+
+dictionary FilePickerOptions {
+    sequence<FilePickerAcceptType> types;
+    boolean excludeAcceptAllOption = false;
+    DOMString id;
+    StartInDirectory startIn;
+};
+
+dictionary OpenFilePickerOptions : FilePickerOptions {
+    boolean multiple = false;
+};
+
+dictionary SaveFilePickerOptions : FilePickerOptions {
+    USVString? suggestedName;
+};
+
+dictionary DirectoryPickerOptions {
+    DOMString id;
+    StartInDirectory startIn;
+};
+
+[SecureContext]
+partial interface Window {
+    Promise<sequence<FileSystemFileHandle>> showOpenFilePicker(optional OpenFilePickerOptions options = {});
+    Promise<FileSystemFileHandle> showSaveFilePicker(optional SaveFilePickerOptions options = {});
+    Promise<FileSystemDirectoryHandle> showDirectoryPicker(optional DirectoryPickerOptions options = {});
+};
+
+partial interface DataTransferItem {
+    Promise<FileSystemHandle?> getAsFileSystemHandle();
+};
+
+[SecureContext]
+partial interface StorageManager {
+  Promise<FileSystemDirectoryHandle> getDirectory();
+};
+
+
+

Issues Index

+
+
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.
+
TODO: Explain better how entries map to files on disk (multiple entries can map to the same file or +directory on disk but an entry doesn’t have to map to any file on disk).
+
Ideally this user activation requirement would be defined upstream. [Issue #WICG/permissions-request#2]
+
Currently FileSystemPermissionMode can only be "read" or "readwrite". In +the future we might want to add a "write" mode as well to support write-only handles. [Issue #119]
+
The reading and snapshotting behavior needs to be better specified in the [FILE-API] spec, + for now this is kind of hand-wavy.
+
There has been some discussion around and desire for a "inPlace" mode for createWritable +(where changes will be written to the actual underlying file as they are written to the writer, for +example to support in-place modification of large files or things like databases). This is not +currently implemented in Chrome. Implementing this is currently blocked on figuring out how to +combine the desire to run malware checks with the desire to let websites make fast in-place +modifications to existing large files. [Issue #67]
+
In the future we might want to add arguments to the async iterable declaration to +support for example recursive iteration. [Issue #173]
+
Better specify what possible exceptions this could throw. [Issue #68]
+
Better specify what possible exceptions this could throw. [Issue #68]
+
Better specify what possible exceptions this could throw. [Issue #68]
+
Rather than requiring the website to prompt separately for a writable directory, + we should provide some kind of API to request a writable directory in one step. [Issue #89]
+
This currently does not block access to too sensitive or dangerous directories, to +be consistent with other APIs that give access to dropped files and directories. This is inconsistent +with the local file system handle factories though, so we might want to reconsider this.
+
Storage endpoints should be defined in [storage] itself, rather +than being defined here. So merge this into the table there.
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file From f48edf2046ff33fd8378b510764b35b2db56335c Mon Sep 17 00:00:00 2001 From: Thomas Steiner Date: Fri, 11 Feb 2022 22:20:36 +0100 Subject: [PATCH 12/19] Fix drag and drop example (#357) * Fix drag and drop example Fixes #356 * Update index.bs --- index.bs | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/index.bs b/index.bs index b5a391d..b605258 100644 --- a/index.bs +++ b/index.bs @@ -2035,20 +2035,19 @@ elem.addEventListener('dragover', (e) => { // Prevent navigation. e.preventDefault(); }); + elem.addEventListener('drop', async (e) => { - // Prevent navigation. e.preventDefault(); - // Process all of the items. - for (const item of e.dataTransfer.items) { - // kind will be 'file' for file/directory entries. - if (item.kind === 'file') { - const entry = await item.getAsFileSystemHandle(); - if (entry.kind === 'file') { - handleFileEntry(entry); - } else if (entry.kind === 'directory') { - handleDirectoryEntry(entry); - } + const fileHandlesPromises = [...e.dataTransfer.items] + .filter(item => item.kind === 'file') + .map(item => item.getAsFileSystemHandle()); + + for await (const handle of fileHandlesPromises) { + if (handle.kind === 'directory') { + console.log(`Directory: ${handle.name}`); + } else { + console.log(`File: ${handle.name}`); } } }); From 3c94fea3b81ca0dbf12ef12841a1fc8ddaace1cd Mon Sep 17 00:00:00 2001 From: Thomas Steiner Date: Sat, 12 Feb 2022 00:45:57 +0100 Subject: [PATCH 13/19] Rename my library in README (#346) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2ae7d11..745e145 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ Today, if a web site wants to create experiences involving local files (document - Clean up if necessary (`URL.revokeObjectURL(a.href)`) This is also the approach taken in the - [browser-nativefs](https://github.com/GoogleChromeLabs/browser-nativefs) + [browser-fs-access](https://github.com/GoogleChromeLabs/browser-fs-access) support library. - Setting `window.location` to `'data:application/octet-stream' + data_stream`. - Hidden Flash controls to display a “save as” dialog. From 91fbb6d61d86d3830f305ded0a56b2b7c836e2e9 Mon Sep 17 00:00:00 2001 From: Emanuel Krivoy Date: Tue, 1 Mar 2022 00:38:53 +0100 Subject: [PATCH 14/19] Fill in read, write, close --- index.bs | 88 +++++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 71 insertions(+), 17 deletions(-) diff --git a/index.bs b/index.bs index b605258..79340a1 100644 --- a/index.bs +++ b/index.bs @@ -539,8 +539,6 @@ currently implemented in Chrome. Implementing this is currently blocked on figur combine the desire to run malware checks with the desire to let websites make fast in-place modifications to existing large files. -// TODO(fivedots): release lock once the stream is closed. -
The createWritable(|options|) method, when invoked, must run these steps: @@ -1038,13 +1036,17 @@ in a [=/Realm=] |realm|, perform the following steps: Note: It is expected that this atomically updates the contents of the file on disk being written to. + 1. [=file entry/lock/release|Release the lock=] on |stream|.[=FileSystemWritableFileStream/[[file]]=]. 1. [=/Resolve=] |closeResult| with `undefined`. 1. Return |closeResult|. +1. Let |abortAlgorithm| be the following step: + 1. [=file entry/lock/release|Release the lock=] on |stream|.[=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|. @@ -1246,7 +1248,7 @@ steps: dictionary FilesystemReadWriteOptions { - [EnforceRange] unsigned long long at; + [EnforceRange] required unsigned long long at; } [Exposed=DedicatedWorker, SecureContext] @@ -1264,7 +1266,11 @@ interface FileSystemSyncAccessHandle { -A {{FileSystemSyncAccessHandle}} has an associated \[[file]] (a [=file entry=]). +A {{FileSystemSyncAccessHandle}} has an associated \[[file]] +(a [=file entry=]). + +A {{FileSystemSyncAccessHandle}} has an associated \[[state]], +a string that may exclusively be "open" or "closed".
@@ -1283,6 +1289,7 @@ in a [=/Realm=] |realm|, perform the following steps: 1. Let |handle| be a [=new=] {{FileSystemSyncAccessHandle}} in |realm|. 1. Set |handle|.[=FileSystemSyncAccessHandle/[[file]]=] to |file|. +1. Set |handle|.[=FileSystemSyncAccessHandle/[[state]]=] to "open". 1. Return |handle|.
@@ -1290,30 +1297,73 @@ in a [=/Realm=] |realm|, perform the following steps: ### The {{FileSystemSyncAccessHandle/read()}} method ### {#api-filesystemsyncaccesshandle-read}
- : |handle| . {{FileSystemSyncAccessHandle/read()|read}}(|buffer|, {{FilesystemReadWriteOptions/at}}: |position|) - :: Reads the contents of the file associated with |handle| into |buffer|, with |position| as the offset. + : |handle| . {{FileSystemSyncAccessHandle/read()|read}}(|buffer|, {{FilesystemReadWriteOptions}}: |options| + :: Reads the contents of the file associated with |handle| into |buffer|, with |options|.{{FilesystemReadWriteOptions/at}} as the offset.
+// TODO(fivedots): Is there a more formal way to describe the resutl of partial reads from the OS?
-The read(|buffer|, {{FilesystemReadWriteOptions/at}}: |position|) method, when invoked, must run +The read(|buffer|, {{FilesystemReadWriteOptions}}: |options|) method, when invoked, must run these steps: -// TODO(fivedots): fill in. Does this algorithm need to check that the access handle has not been closed? +1. If [=this=].[=[[state]]=] is "closed", throw a {{InvalidStateError}}. +1. Let |readPosition| be |options|.{{FilesystemReadWriteOptions/at}}. +1. Let |size| be |buffer|'s [=byte length=]. +1. Let |result| be |size|. +1. Let |bytes| be a [=byte sequence=] containing the bytes from |readPosition| + to |readPosition| + (|size| - 1) of [=this=].[=[[file]]=]'s [=file entry/binary data=]. +1. If the operations reading from [=this=].[=[[file]]=]'s [=file entry/binary data=] in the previous steps + failed: + 1. If there were partial reads and the number of modified bytes in |bytes| is known, + set |result| to the number of modified bytes. + 1. Else set |result| to 0. +1. Let |arrayBuffer| be |buffer|'s [=underlying buffer=]. +1. [=write|Write=] |bytes| into |arrayBuffer|. +1. Return |result|.
### The {{FileSystemSyncAccessHandle/write()}} method ### {#api-filesystemsyncaccesshandle-write}
- : |handle| . {{FileSystemSyncAccessHandle/write()|write}}(|buffer|, {{FilesystemReadWriteOptions/at}}: |position|) - :: Writes the content of |buffer| into the file associated with |handle| with |position| as the offset. + : |handle| . {{FileSystemSyncAccessHandle/write()|write}}(|buffer|, {{FilesystemReadWriteOptions}}: |options|) + :: Writes the content of |buffer| into the file associated with |handle| with |options|.{{FilesystemReadWriteOptions/at}} as the offset.
+// TODO(fivedots): Is there a more formal way to describe the resutl of partial writes from the OS? + +// TODO(fivedots): Figure out mechanism to prevent concurrent IO operations e.g. doing write() while a truncate() is executing in parallel.
-The write(|buffer|, {{FilesystemReadWriteOptions/at}}: |position|) method, when invoked, must run +The write(|buffer|, {{FilesystemReadWriteOptions}}: |options|) method, when invoked, must run these steps: -// TODO(fivedots): fill in. Does this algorithm need to explicitly interact with storage quota? +1. If [=this=].[=[[state]]=] is "closed", throw a {{InvalidStateError}}. +1. Let |writePosition| be |options|.{{FilesystemReadWriteOptions/at}}. +1. Let |fileContents| be a copy of [=this=].[=[[file]]=]'s [=file entry/binary data=]. +1. Let |oldSize| be |fileContents|'s [=byte sequence/length=]. +1. Let |data| be [=get a copy of the buffer source|a copy of=] |buffer|. +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| + |data|.[=byte sequence/length=] is smaller than |oldSize|: + 1. Let |tail| be a [=byte sequence=] containing the last + |oldSize| - (|writePosition| + |data|.[=byte sequence/length=]) bytes of |fileContents|. +1. Let |newSize| be the [=byte sequence/length=] of the concatenation of |head|, |data| and |tail|. +1. If |newSize| - |oldSize| exceeds the available [=storage quota=], throw a {{QuotaExceededError}}. +1. Set [=this=].[=[[file]]=]'s [=file entry/binary data=] to the concatenation of |head|, |data| and |tail|. +1. If the operations modifying the [=this=].[=[[file]]=]'s [=file entry/binary data=] in the previous steps + failed: + 1. If there were partial modifications and the number of modified bytes is known, + return the number of modified bytes. + 1. Else return 0. +1. Return |data|.[=byte sequence/length=].
@@ -1324,6 +1374,7 @@ these steps: :: 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.
+//TODO(fivedots):Make sure negative lengths are not allowed.
The truncate(|size|) method, when invoked, must run these steps: @@ -1351,7 +1402,7 @@ these steps:
: |handle| . {{FileSystemSyncAccessHandle/flush()}} - :: Ensures that the contents of the file associated with |handle| contain all the modifications done through {{FileSystemSyncAccessHandle/read()}} and {{FileSystemSyncAccessHandle/write()}}. + :: Ensures that the contents of the file associated with |handle| contain all the modifications done through {{FileSystemSyncAccessHandle/write()}}.
@@ -1364,19 +1415,22 @@ these steps: ### The {{FileSystemSyncAccessHandle/close()}} method ### {#api-filesystemsyncaccesshandle-close} -// TODO(fivedots): |fileHandle| is not properly defined here, consider adding an attribute to |handle|. -
: |handle| . {{FileSystemSyncAccessHandle/close()}} :: Flushes the access handle and then closes it. Closing an access handle disables any further operations on it and - [=file entry/lock/release|releases the lock=] on the [=FileSystemHandle/entry=] associated with |fileHandle|. + [=file entry/lock/release|releases the lock=] on the [=FileSystemHandle/entry=] associated with |handle|.
+//TODO(fivedots): Figure out language to describe flushing the file at the OS level before closing the handle.
The close() method, when invoked, must run these steps: -// TODO(fivedots): fill in. +1. Let |p| be [=a new promise=]. +1. Run the following steps [=in parallel=]: + 1. Set [=this=].[=[[state]]=] to "closed". + 1. [=/Resolve=] |p|. +1. Return |p|
From 008de226b102c5666d5b41f85d1c30659b71edef Mon Sep 17 00:00:00 2001 From: Emanuel Krivoy Date: Thu, 17 Mar 2022 16:29:29 +0100 Subject: [PATCH 15/19] Add SyncAccessHandle truncate/getSize --- index.bs | 37 ++- index.html | 677 ++++++++++++++++++++++++++++++++++------------------- 2 files changed, 468 insertions(+), 246 deletions(-) diff --git a/index.bs b/index.bs index 79340a1..fc1cf25 100644 --- a/index.bs +++ b/index.bs @@ -1258,7 +1258,7 @@ interface FileSystemSyncAccessHandle { unsigned long long write([AllowShared] BufferSource buffer, FilesystemReadWriteOptions options); - Promise truncate([EnforceRange] unsigned long long size); + Promise truncate([EnforceRange] unsigned long long newSize); Promise getSize(); Promise flush(); Promise close(); @@ -1370,16 +1370,31 @@ these steps: ### The {{FileSystemSyncAccessHandle/truncate()}} method ### {#api-filesystemsyncaccesshandle-truncate}
- : |handle| . {{FileSystemSyncAccessHandle/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. + : |handle| . {{FileSystemSyncAccessHandle/truncate()|truncate}}(|newSize|) + :: Resizes the file associated with stream to be |newSize| bytes long. If size is larger than the current file size this pads the file with null bytes, otherwise it truncates the file.
-//TODO(fivedots):Make sure negative lengths are not allowed.
-The truncate(|size|) method, when invoked, must run +The truncate(|newSize|) method, when invoked, must run these steps: -// TODO(fivedots): fill in. +1. If [=this=].[=[[state]]=] is "closed", throw a {{InvalidStateError}}. +1. If |newSize| is less than 0, throw a {{InvalidStateError}}. +1. Let |fileContents| be a copy of [=this=].[=[[file]]=]'s [=file entry/binary data=]. +1. Let |p| be [=a new promise=]. +1. Run the following steps [=in parallel=]: + 1. Let |oldSize| be the [=byte sequence/length=] of [=this=].[=[[file]]=]'s [=file entry/binary data=]. + 1. If |newSize| is larger than |oldSize|: + 1. If |newSize| - |oldSize| exceeds the available [=storage quota=], [=/reject=] |p| + with a {{QuotaExceededError}} and abort. + 1. Set [=this=].[=[[file]]=]'s to a [=byte sequence=] formed by concating + |fileContents| with a [=byte sequence=] + containing |newSize|-|oldSize| `0x00` bytes. + 1. Else if |newSize| is smaller than |oldSize|: + 1. Set [=this=].[=[[file]]=]'s to a [=byte sequence=] containing the first |newSize| bytes + in |fileContents|. + 1. [=/Resolve=] |p|. +1. Return |p|
@@ -1394,7 +1409,13 @@ these steps: The getSize() method, when invoked, must run these steps: -// TODO(fivedots): fill in. +1. If [=this=].[=[[state]]=] is "closed", throw a {{InvalidStateError}}. +1. Let |p| be [=a new promise=]. +1. Run the following steps [=in parallel=]: + 1. Let |size| be the [=byte sequence/length=] of [=this=].[=[[file]]=]'s [=file entry/binary data=]. + 1. [=/Resolve=] |p| with |size|. +1. Return |p| +
@@ -1409,7 +1430,7 @@ these steps: The flush() method, when invoked, must run these steps: -// TODO(fivedots): fill in. +// TODO(fivedots): Fill in, after figuring out language to describe flushing at the OS level.
diff --git a/index.html b/index.html index a4bb2a7..9eaefa8 100644 --- a/index.html +++ b/index.html @@ -3,8 +3,8 @@ File System Access - - + +
-

+

File System Access

-

Draft Community Group Report,

+

Draft Community Group Report,

This version: @@ -675,7 +675,7 @@

-
@@ -845,7 +845,7 @@

2 majority of file extensions are purely alphanumeric, but compound extensions (such as .tar.gz) and extensions such as .c++ for C++ source code are also fairly common, hence the inclusion of + and . as allowed code points.

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) -and a lock. A lock is a string that may exclusively be "open", "taken-exclusive" and "taken-shared".

+and a lock. A lock is a string that may exclusively be "open", "taken-exclusive", and "taken-shared".

To take a lock with a value of "exclusive" or "shared", for a given file, run the following steps: @@ -906,7 +906,6 @@

2

Set lock to "open".

- //TODO(fivedots): Define algorithm for releasing a lock.

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 entries. Each member is either a file or a directory.

An entry entry should be contained in the children of at most one directory entry, and that directory entry is also known as entry’s parent. @@ -997,20 +996,20 @@

handle's entry.

  • If entry represents an entry in an origin private file system, -this descriptor’s permission state must always be "granted".

    +this descriptor’s permission state must always be "granted".

  • -

    Otherwise, if entry’s parent is not null, this descriptor’s permission state must be -equal to the permission state for a descriptor with the same mode, +

    Otherwise, if entry’s parent is not null, this descriptor’s permission state must be +equal to the permission state for a descriptor with the same mode, and a handle representing entry’s parent.

  • Otherwise, if desc.mode is "readwrite":

    1. -

      Let read state be the permission state for a descriptor +

      Let read state be the permission state for a descriptor with the same handle, but mode = "read".

    2. -

      If read state is not "granted", this descriptor’s permission state must be equal to read state.

      +

      If read state is not "granted", this descriptor’s permission state must be equal to read state.

  • @@ -1055,7 +1054,7 @@

    mode to mode.

  • -

    Return desc’s permission state.

    +

    Return desc’s permission state.

  • @@ -1074,25 +1073,24 @@

    permission request algorithm for the "file-system" feature, given desc and status.

  • -

    Return desc’s permission state.

    +

    Return desc’s permission state.

  • Currently FileSystemPermissionMode can only be "read" or "readwrite". In the future we might want to add a "write" mode as well to support write-only handles. [Issue #119]

    2.3. The FileSystemHandle interface

    - +

    FileSystemHandle

    -

    In only one current engine.

    - FirefoxNoneSafariNoneChrome86+ + FirefoxNoneSafari15.2+Chrome86+
    Opera72+Edge86+
    Edge (Legacy)NoneIENone
    - Firefox for AndroidNoneiOS SafariNoneChrome for Android86+Android WebViewNoneSamsung Internet14.0+Opera MobileNone + Firefox for AndroidNoneiOS Safari15.2+Chrome for Android86+Android WebViewNoneSamsung Internet14.0+Opera MobileNone
    @@ -1145,18 +1143,17 @@

    - +

    FileSystemHandle/kind

    -

    In only one current engine.

    - FirefoxNoneSafariNoneChrome86+ + FirefoxNoneSafari15.2+Chrome86+
    Opera72+Edge86+
    Edge (Legacy)NoneIENone
    - Firefox for AndroidNoneiOS SafariNoneChrome for Android86+Android WebViewNoneSamsung Internet14.0+Opera MobileNone + Firefox for AndroidNoneiOS Safari15.2+Chrome for Android86+Android WebViewNoneSamsung Internet14.0+Opera MobileNone
    @@ -1168,18 +1165,17 @@

    - +

    FileSystemHandle/name

    -

    In only one current engine.

    - FirefoxNoneSafariNoneChrome86+ + FirefoxNoneSafari15.2+Chrome86+
    Opera72+Edge86+
    Edge (Legacy)NoneIENone
    - Firefox for AndroidNoneiOS SafariNoneChrome for Android86+Android WebViewNoneSamsung Internet14.0+Opera MobileNone + Firefox for AndroidNoneiOS Safari15.2+Chrome for Android86+Android WebViewNoneSamsung Internet14.0+Opera MobileNone
    @@ -1195,18 +1191,17 @@

    entry.

    2.3.1. The isSameEntry() method

    - +

    FileSystemHandle/isSameEntry

    -

    In only one current engine.

    - FirefoxNoneSafariNoneChrome86+ + FirefoxNoneSafari15.2+Chrome86+
    Opera72+Edge86+
    Edge (Legacy)NoneIENone
    - Firefox for AndroidNoneiOS SafariNoneChrome for Android86+Android WebViewNoneSamsung Internet14.0+Opera MobileNone + Firefox for AndroidNoneiOS Safari15.2+Chrome for Android86+Android WebViewNoneSamsung Internet14.0+Opera MobileNone
    @@ -1348,18 +1343,17 @@

    2.4. The FileSystemFileHandle interface

    - +
    -

    FileSystemFileHandle

    -

    In only one current engine.

    +

    FileSystemFileHandle

    - FirefoxNoneSafariNoneChrome86+ + FirefoxNoneSafari15.2+Chrome86+
    Opera72+Edge86+
    Edge (Legacy)NoneIENone
    - Firefox for AndroidNoneiOS SafariNoneChrome for Android86+Android WebViewNoneSamsung Internet14.0+Opera MobileNone + Firefox for AndroidNoneiOS Safari15.2+Chrome for Android86+Android WebViewNoneSamsung Internet14.0+Opera MobileNone
    @@ -1378,7 +1372,7 @@

    Promise<File> getFile(); Promise<FileSystemWritableFileStream> createWritable(optional FileSystemCreateWritableOptions options = {}); [Exposed=DedicatedWorker] - Promise<FileSystemSyncAccessHandle> createSyncAccessHandle(optional FileSystemFileHandleCreateAccessHandleOptions options = {}); + Promise<FileSystemSyncAccessHandle> createSyncAccessHandle(FileSystemFileHandleCreateAccessHandleOptions options); };

    A FileSystemFileHandle's associated entry must be a file entry.

    @@ -1387,18 +1381,17 @@

    2.4.1. The getFile() method

    - +

    FileSystemFileHandle/getFile

    -

    In only one current engine.

    - FirefoxNoneSafariNoneChrome86+ + FirefoxNoneSafari15.2+Chrome86+
    Opera72+Edge86+
    Edge (Legacy)NoneIENone
    - Firefox for AndroidNoneiOS SafariNoneChrome for Android86+Android WebViewNoneSamsung Internet14.0+Opera MobileNone + Firefox for AndroidNoneiOS Safari15.2+Chrome for Android86+Android WebViewNoneSamsung Internet14.0+Opera MobileNone
    @@ -1448,12 +1441,11 @@

    2.4.2. The createWritable() method

    - +

    FileSystemFileHandle/createWritable

    -

    In only one current engine.

    - FirefoxNoneSafariNoneChrome86+ + FirefoxNoneSafaripreview+Chrome86+
    Opera72+Edge86+
    @@ -1486,7 +1478,6 @@

    [Issue #67]

    -

    // TODO(fivedots): release lock once the stream is closed.

    The createWritable(options) method, when invoked, must run these steps:
      @@ -1534,14 +1525,14 @@

      FileSystemSyncAccessHandle takes an exclusive lock on the entry associated with fileHandle. This prevents the creation of further FileSystemSyncAccessHandles or FileSystemWritableFileStreams for the entry, until the access handle is closed.

      The returned FileSystemSyncAccessHandle offers synchronous read() and write() methods. This allows for higher performance for critical methods on -contexts where asynchronous operations come with high overhead i.e., WebAssembly.

      +contexts where asynchronous operations come with high overhead, i.e., WebAssembly.

      For the time being, this method will only succeed when the fileHandle belongs to the origin private file system. Also temporarily, the mode parameter must be set to "in-place". This prevents us from setting a default behavior and allows us to bring access handles to other filesytems in the future.

    - The createSyncAccessHandle(options) method, when invoked, must run these steps: + The createSyncAccessHandle(options) method, when invoked, must run these steps:
    1. Let result be a new promise.

      @@ -1575,18 +1566,17 @@

      2.5. The FileSystemDirectoryHandle interface

      - +

      FileSystemDirectoryHandle

      -

      In only one current engine.

      - FirefoxNoneSafariNoneChrome86+ + FirefoxNoneSafari15.2+Chrome86+
      Opera72+Edge86+
      Edge (Legacy)NoneIENone
      - Firefox for AndroidNoneiOS SafariNoneChrome for Android86+Android WebViewNoneSamsung Internet14.0+Opera MobileNone + Firefox for AndroidNoneiOS Safari15.2+Chrome for Android86+Android WebViewNoneSamsung Internet14.0+Opera MobileNone
      @@ -1702,18 +1692,17 @@

      2.5.2. The getFileHandle() method

      - +

      FileSystemDirectoryHandle/getFileHandle

      -

      In only one current engine.

      - FirefoxNoneSafariNoneChrome86+ + FirefoxNoneSafari15.2+Chrome86+
      Opera72+Edge86+
      Edge (Legacy)NoneIENone
      - Firefox for AndroidNoneiOS SafariNoneChrome for Android86+Android WebViewNoneSamsung Internet14.0+Opera MobileNone + Firefox for AndroidNoneiOS Safari15.2+Chrome for Android86+Android WebViewNoneSamsung Internet14.0+Opera MobileNone
      @@ -1810,18 +1799,17 @@

      2.5.3. The getDirectoryHandle() method

      - +

      FileSystemDirectoryHandle/getDirectoryHandle

      -

      In only one current engine.

      - FirefoxNoneSafariNoneChrome86+ + FirefoxNoneSafari15.2+Chrome86+
      Opera72+Edge86+
      Edge (Legacy)NoneIENone
      - Firefox for AndroidNoneiOS SafariNoneChrome for Android86+Android WebViewNoneSamsung Internet14.0+Opera MobileNone + Firefox for AndroidNoneiOS Safari15.2+Chrome for Android86+Android WebViewNoneSamsung Internet14.0+Opera MobileNone
      @@ -1914,18 +1902,17 @@

      2.5.4. The removeEntry() method

      - +

      FileSystemDirectoryHandle/removeEntry

      -

      In only one current engine.

      - FirefoxNoneSafariNoneChrome86+ + FirefoxNoneSafari15.2+Chrome86+
      Opera72+Edge86+
      Edge (Legacy)NoneIENone
      - Firefox for AndroidNoneiOS SafariNoneChrome for Android86+Android WebViewNoneSamsung Internet14.0+Opera MobileNone + Firefox for AndroidNoneiOS Safari15.2+Chrome for Android86+Android WebViewNoneSamsung Internet14.0+Opera MobileNone
      @@ -2002,18 +1989,17 @@

      2.5.5. The resolve() method

      - +

      FileSystemDirectoryHandle/resolve

      -

      In only one current engine.

      - FirefoxNoneSafariNoneChrome86+ + FirefoxNoneSafari15.2+Chrome86+
      Opera72+Edge86+
      Edge (Legacy)NoneIENone
      - Firefox for AndroidNoneiOS SafariNoneChrome for Android86+Android WebViewNoneSamsung Internet14.0+Opera MobileNone + Firefox for AndroidNoneiOS Safari15.2+Chrome for Android86+Android WebViewNoneSamsung Internet14.0+Opera MobileNone
      @@ -2147,7 +2133,7 @@
    +
  • +

    Let abortAlgorithm be the following step:

    +
      +
    1. +

      Release the lock on stream.[[file]].

      +
  • Let highWaterMark be 1.

  • Let sizeAlgorithm be an algorithm that returns 1.

  • -

    Set up stream with writeAlgorithm set to writeAlgorithm, closeAlgorithm set to closeAlgorithm, highWaterMark set to highWaterMark, and sizeAlgorithm set to sizeAlgorithm.

    +

    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.

  • Return stream.

    @@ -2190,7 +2184,7 @@
  • -
    -
    handle = await window . showDirectoryPicker() -
    -

    Shows a directory picker that lets the user select a single directory, returning a handle for +

    handle = await window . showDirectoryPicker() +
    +

    Shows a directory picker that lets the user select a single directory, returning a handle for the selected directory.

    -
    +

    The id and startIn fields behave identically to the id and startIn fields, respectively. See § 3.2.2 Starting Directory for details on how to use these fields.

    @@ -3203,7 +3334,7 @@

  • -

    Let environment be this’s relevant settings object.

    +

    Let environment be this’s relevant settings object.

  • Let starting directory be the result of determining the directory the picker will start in given options.id, options.startIn and environment.

  • @@ -3211,9 +3342,9 @@

    Verify that environment is allowed to show a file picker.

  • -

    Let p be a new promise.

    +

    Let p be a new promise.

  • -

    Run the following steps in parallel:

    +

    Run the following steps in parallel:

    1. Optionally, wait until any prior execution of this algorithm has terminated.

      @@ -3223,7 +3354,7 @@

      Wait for the user to have made their selection.

    2. -

      If the user dismissed the prompt without making a selection, reject p with an AbortError and abort.

      +

      If the user dismissed the prompt without making a selection, reject p with an AbortError and abort.

    3. Let entry be a directory entry representing the selected directory.

    4. @@ -3233,8 +3364,8 @@

      Inform the user that the selected files or directories can’t be exposed to this website.

    5. At the discretion of the user agent, - either go back to the beginning of these in parallel steps, - or reject p with an AbortError and abort.

      + either go back to the beginning of these in parallel steps, + or reject p with an AbortError and abort.

  • Set result to a new FileSystemDirectoryHandle associated with entry.

    @@ -3247,7 +3378,7 @@

    Rather than requiring the website to prompt separately for a writable directory, we should provide some kind of API to request a writable directory in one step. [Issue #89]

  • -

    Resolve p with result.

    +

    Resolve p with result.

  • Return p.

    @@ -3261,11 +3392,9 @@

    file entries and directory entries respectively.

    -
    -
    handle = await item . getAsFileSystemHandle() -
    -

    Returns a FileSystemFileHandle object if the dragged item is a file and a FileSystemDirectoryHandle object if the dragged item is a directory.

    -
    +
    handle = await item . getAsFileSystemHandle() +
    +

    Returns a FileSystemFileHandle object if the dragged item is a file and a FileSystemDirectoryHandle object if the dragged item is a directory.

    @@ -3293,9 +3422,9 @@

    the drag data item kind is not File, then return a promise resolved with null.

  • -

    Let p be a new promise.

    +

    Let p be a new promise.

  • -

    Run the following steps in parallel:

    +

    Run the following steps in parallel:

    1. Let entry be the entry representing the dragged file or directory.

      @@ -3312,7 +3441,7 @@

      FileSystemDirectoryHandle associated with entry.

  • -

    Resolve p with entry.

    +

    Resolve p with entry.

  • Return p.

    @@ -3324,20 +3453,19 @@

    In Chrome this functionality was previously exposed as FileSystemDirectoryHandle.getSystemDirectory({type: "sandbox"}). This new method is available as of Chrome 85.

    -
    -
    directoryHandle = await navigator . storage . getDirectory() -
    -

    Returns the root directory of the origin private file system.

    -
    +
    directoryHandle = await navigator . storage . getDirectory() +
    +

    Returns the root directory of the origin private file system.

    The getDirectory() method, when @@ -3487,10 +3613,10 @@

    7.3. Filling up a users disk

    Other than files in the origin private file system, files written by this API are not subject -to storage quota. As such websites can fill up a users disk without being limited by +to storage quota. As such websites can fill up a users disk without being limited by quota, which could leave a users device in a bad state (do note that even with storage that is -subject to storage quota it is still possible to fill up, or come close to filling up, a users -disk, since storage quota in general is not dependent on the amount of available disk +subject to storage quota it is still possible to fill up, or come close to filling up, a users +disk, since storage quota in general is not dependent on the amount of available disk space).

    Without this API websites can write data to disk not subject to quota limitations already by triggering downloads of large files (potentially created client side, to not incur any network @@ -3545,7 +3671,7 @@

    - +

    Index

    Terms defined by this specification

  • StartInDirectory, in § 3 +
  • [[state]], in § 2.7
  • suggestedName, in § 3
  • take, in § 2.1
  • the same as, in § 2.1
  • too sensitive or dangerous, in § 6.1
  • "truncate", in § 2.6 -
  • - truncate(size) - +
  • truncate(newSize), in § 2.7.3 +
  • truncate(size), in § 2.6.3
  • type, in § 2.6
  • types, in § 3
  • validate a suffix, in § 3.2.1 @@ -3720,7 +3842,7 @@

    WellKnownDirectory, in § 3.2.2
  • "write", in § 2.6
  • write a chunk, in § 2.6 -
  • write(buffer, at: position), in § 2.7.2 +
  • write(buffer, FilesystemReadWriteOptions: options), in § 2.7.2
  • write(buffer, options), in § 2.7
  • WriteCommandType, in § 2.6
  • write(data), in § 2.6.1 @@ -3875,10 +3997,13 @@

    2.5.3. The getDirectoryHandle() method
  • 2.5.4. The removeEntry() method
  • 2.6. The FileSystemWritableFileStream interface (2) -
  • 3.3. The showOpenFilePicker() method (2) -
  • 3.4. The showSaveFilePicker() method (2) -
  • 3.5. The showDirectoryPicker() method (2) -
  • 3.6. Drag and Drop +
  • 2.7.3. The truncate() method +
  • 2.7.4. The getSize() method +
  • 2.7.6. The close() method +
  • 3.3. The showOpenFilePicker() method (2) +
  • 3.4. The showSaveFilePicker() method (2) +
  • 3.5. The showDirectoryPicker() method (2) +
  • 3.6. Drag and Drop - + - - -