From 4d3958a046228d339cdf1cdfcf3904916bc1e927 Mon Sep 17 00:00:00 2001 From: Austin Sullivan Date: Wed, 18 Jan 2023 01:04:41 +0000 Subject: [PATCH 01/15] Define a parallel queue for lock operations Fixes #74 --- index.bs | 44 +++++++++++++++++++++++++------------------- 1 file changed, 25 insertions(+), 19 deletions(-) diff --git a/index.bs b/index.bs index 737bc5b..7f94682 100644 --- a/index.bs +++ b/index.bs @@ -94,24 +94,29 @@ A file entry additionally consists of a lock (a string that may exclusively be "`open`", "`taken-exclusive`" or "`taken-shared`") and a shared lock count (a number representing the number shared locks that are taken at a given point in time). +The file system lock queue is a +[=parallel queue=] to be used for all tasks involving a +[=file entry/lock=]. +
To take a [=file entry/lock=] with a |value| of "`exclusive`" or "`shared`" on a given [=file entry=] |file|: 1. Let |lock| be the |file|'s [=file entry/lock=]. -1. Let |count| be the |file|'s [=file entry/shared lock count=]. -1. If |value| is "`exclusive`": - 1. If |lock| is "`open`": - 1. Set lock to "`taken-exclusive`". - 1. Return true. -1. If |value| is "`shared`": - 1. If |lock| is "`open`": - 1. Set |lock| to "`taken-shared`". - 1. Set |count| to 1. - 1. Return true. - 1. Otherwise, if |lock| is "`taken-shared`": - 1. Increase |count| by one. - 1. Return true. -1. Return false. +1. [=Enqueue the following steps=] to the [=file system lock queue=]: + 1. Let |count| be the |file|'s [=file entry/shared lock count=]. + 1. If |value| is "`exclusive`": + 1. If |lock| is "`open`": + 1. Set lock to "`taken-exclusive`". + 1. Return true. + 1. If |value| is "`shared`": + 1. If |lock| is "`open`": + 1. Set |lock| to "`taken-shared`". + 1. Set |count| to 1. + 1. Return true. + 1. Otherwise, if |lock| is "`taken-shared`": + 1. Increase |count| by one. + 1. Return true. + 1. Return false.
@@ -120,11 +125,12 @@ To release a [=file entry/lock=] on a given [=f run these steps: 1. Let |lock| be the |file|'s associated [=file entry/lock=]. -1. Let |count| be the |file|'s [=file entry/shared lock count=]. -1. If |lock| is "`taken-shared`": - 1. Decrease |count| by one. - 1. If |count| is 0, set |lock| to "`open`". -1. Otherwise, set |lock| to "`open`". +1. [=Enqueue the following steps=] to the [=file system lock queue=]: + 1. Let |count| be the |file|'s [=file entry/shared lock count=]. + 1. If |lock| is "`taken-shared`": + 1. Decrease |count| by one. + 1. If |count| is 0, set |lock| to "`open`". + 1. Otherwise, set |lock| to "`open`". From 3844605492e1d8023714e03f54335940b2bdd062 Mon Sep 17 00:00:00 2001 From: Austin Sullivan Date: Wed, 18 Jan 2023 01:13:35 +0000 Subject: [PATCH 02/15] nit --- index.bs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/index.bs b/index.bs index 7f94682..26a081e 100644 --- a/index.bs +++ b/index.bs @@ -95,8 +95,7 @@ a lock (a string that may exclusively be "`open`", " and a shared lock count (a number representing the number shared locks that are taken at a given point in time). The file system lock queue is a -[=parallel queue=] to be used for all tasks involving a -[=file entry/lock=]. +[=parallel queue=] to be used for all [=tasks=] involving a [=file entry/lock=].
To take a [=file entry/lock=] with a |value| of "`exclusive`" or "`shared`" on a given [=file entry=] |file|: From 7d637974165d2c302c19a8d5ab247ac38c661a1f Mon Sep 17 00:00:00 2001 From: Austin Sullivan Date: Wed, 25 Jan 2023 23:06:09 +0000 Subject: [PATCH 03/15] run task if lock taken - sketch --- index.bs | 53 +++++++++++++++++++++++++++++++---------------------- 1 file changed, 31 insertions(+), 22 deletions(-) diff --git a/index.bs b/index.bs index 26a081e..14ff181 100644 --- a/index.bs +++ b/index.bs @@ -94,42 +94,51 @@ A file entry additionally consists of a lock (a string that may exclusively be "`open`", "`taken-exclusive`" or "`taken-shared`") and a shared lock count (a number representing the number shared locks that are taken at a given point in time). -The file system lock queue is a -[=parallel queue=] to be used for all [=tasks=] involving a [=file entry/lock=]. +A user agent has an associated file system lock queue which is the +result of [=starting a new parallel queue=]. This queue is to be used for all +[=tasks=] involving a [=file entry/lock=].
-To take a [=file entry/lock=] with a |value| of "`exclusive`" or "`shared`" on a given [=file entry=] |file|: +To take a [=file entry/lock=] with a |value| of +"`exclusive`" or "`shared`" on a given [=file entry=] |file|, with promise |p|, +algorithm |onLockTakenAlgorithm|, and [=task source=] |replyTaskSource|: -1. Let |lock| be the |file|'s [=file entry/lock=]. 1. [=Enqueue the following steps=] to the [=file system lock queue=]: + 1. Let |lock| be the |file|'s [=file entry/lock=]. 1. Let |count| be the |file|'s [=file entry/shared lock count=]. 1. If |value| is "`exclusive`": 1. If |lock| is "`open`": 1. Set lock to "`taken-exclusive`". - 1. Return true. + 1. [=Queue a task=] to |replyTaskSource| to run |onLockTakenAlgorithm| + and return. 1. If |value| is "`shared`": 1. If |lock| is "`open`": 1. Set |lock| to "`taken-shared`". 1. Set |count| to 1. - 1. Return true. + 1. [=Queue a task=] to |replyTaskSource| to run |onLockTakenAlgorithm| + and return. 1. Otherwise, if |lock| is "`taken-shared`": 1. Increase |count| by one. - 1. Return true. - 1. Return false. + 1. [=Queue a task=] to |replyTaskSource| to run |onLockTakenAlgorithm| + and return. + 1. [=Reject=] |p| with a "{{NoModificationAllowedError}}" {{DOMException}}.
-To release a [=file entry/lock=] on a given [=file entry=] |file|, -run these steps: +To release a [=file entry/lock=] on a given +[=file entry=] |file| with algorithm |onLockReleasedAlgorithm| and +[=task source=] |replyTaskSource|: -1. Let |lock| be the |file|'s associated [=file entry/lock=]. 1. [=Enqueue the following steps=] to the [=file system lock queue=]: + 1. Let |lock| be the |file|'s associated [=file entry/lock=]. 1. Let |count| be the |file|'s [=file entry/shared lock count=]. 1. If |lock| is "`taken-shared`": 1. Decrease |count| by one. 1. If |count| is 0, set |lock| to "`open`". 1. Otherwise, set |lock| to "`open`". + 1. [=Queue a task=] to |replyTaskSource| to run |onLockReleasedAlgorithm| + and return.
@@ -506,18 +515,17 @@ The createWritable(|options|) method If that throws an exception, [=reject=] |result| with that exception and abort. 1. If |access| is not "{{PermissionState/granted}}", [=reject=] |result| with a "{{NotAllowedError}}" {{DOMException}} and abort. - 1. If |entry| is `null`, [=/reject=] |result| with a "{{NotFoundError}}" {{DOMException}} and abort. 1. [=Assert=]: |entry| is a [=file entry=]. - - 1. Let |lockResult| be the result of [=file entry/lock/take|taking a lock=] with "`shared`" on |entry|. - 1. If |lockResult| is false, [=reject=] |result| with a "{{NoModificationAllowedError}}" {{DOMException}} and abort. - 1. Let |stream| be the result of creating a new `FileSystemWritableFileStream` - for |entry| in [=this=]'s [=relevant realm=]. - 1. If |options|.{{FileSystemCreateWritableOptions/keepExistingData}} is true: - 1. Set |stream|.[=[[buffer]]=] to a copy of |entry|'s [=file entry/binary data=]. - 1. [=/Resolve=] |result| with |stream|. + 1. Let |onLockTakenAlgorithm| be these steps: + 1. Let |stream| be the result of creating a new `FileSystemWritableFileStream` + for |entry| in [=this=]'s [=relevant realm=]. + 1. If |options|.{{FileSystemCreateWritableOptions/keepExistingData}} is true: + 1. Set |stream|.[=[[buffer]]=] to a copy of |entry|'s [=file entry/binary data=]. + 1. [=/Resolve=] |result| with |stream|. + 1. [=file entry/lock/take|Take a lock=] with "`shared`" on |entry| with + |result|, |onLockTakenAlgorithm|, and [=storage task source=]. 1. Return |result|.
@@ -1055,8 +1063,9 @@ given a [=file entry=] |file| in a [=/Realm=] |realm|: 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. Let |onLockReleasedAlgorithm| be this step: [=/Resolve=] |closeResult| with `undefined`. + 1. [=file entry/lock/release|Release the lock=] on |stream|'s [=FileSystemWritableFileStream/[[file]]=] + with |onLockReleasedAlgorithm|, and the currently running [=task=]'s' [=task source|source=]. 1. Return |closeResult|. 1. Let |abortAlgorithm| be this step: [=file entry/lock/release|release the lock=] on |stream|.[=FileSystemWritableFileStream/[[file]]=]. From 3bb5521b93102aeb2f8d8508e43f1c72dc66a2c0 Mon Sep 17 00:00:00 2001 From: Austin Sullivan Date: Sat, 28 Jan 2023 03:54:05 +0000 Subject: [PATCH 04/15] pass only one algorithm --- index.bs | 51 +++++++++++++++++++++++++++++++++------------------ 1 file changed, 33 insertions(+), 18 deletions(-) diff --git a/index.bs b/index.bs index 14ff181..ba60c12 100644 --- a/index.bs +++ b/index.bs @@ -100,8 +100,8 @@ result of [=starting a new parallel queue=]. This queue is to be used for all
To take a [=file entry/lock=] with a |value| of -"`exclusive`" or "`shared`" on a given [=file entry=] |file|, with promise |p|, -algorithm |onLockTakenAlgorithm|, and [=task source=] |replyTaskSource|: +"`exclusive`" or "`shared`" on a given [=file entry=] |file| and with +algorithm |resultAlgorithm|: 1. [=Enqueue the following steps=] to the [=file system lock queue=]: 1. Let |lock| be the |file|'s [=file entry/lock=]. @@ -109,26 +109,26 @@ algorithm |onLockTakenAlgorithm|, and [=task source=] |replyTaskSource|: 1. If |value| is "`exclusive`": 1. If |lock| is "`open`": 1. Set lock to "`taken-exclusive`". - 1. [=Queue a task=] to |replyTaskSource| to run |onLockTakenAlgorithm| - and return. + 1. [=Queue a global task=] on the [=storage task source=] to run + |resultAlgorithm| with a value of "`success`" and return. 1. If |value| is "`shared`": 1. If |lock| is "`open`": 1. Set |lock| to "`taken-shared`". 1. Set |count| to 1. - 1. [=Queue a task=] to |replyTaskSource| to run |onLockTakenAlgorithm| - and return. + 1. [=Queue a global task=] on the [=storage task source=] to run + |resultAlgorithm| with a value of "`success`" and return. 1. Otherwise, if |lock| is "`taken-shared`": 1. Increase |count| by one. - 1. [=Queue a task=] to |replyTaskSource| to run |onLockTakenAlgorithm| - and return. - 1. [=Reject=] |p| with a "{{NoModificationAllowedError}}" {{DOMException}}. + 1. [=Queue a global task=] on the [=storage task source=] to run + |resultAlgorithm| with a value of "`success`" and return. + 1. [=Queue a global task=] on the [=storage task source=] to run + |resultAlgorithm| with a value of "`failure`" and return.
To release a [=file entry/lock=] on a given -[=file entry=] |file| with algorithm |onLockReleasedAlgorithm| and -[=task source=] |replyTaskSource|: +[=file entry=] |file| and with an optional algorithm |onLockReleasedAlgorithm|: 1. [=Enqueue the following steps=] to the [=file system lock queue=]: 1. Let |lock| be the |file|'s associated [=file entry/lock=]. @@ -137,8 +137,8 @@ To release a [=file entry/lock=] on a given 1. Decrease |count| by one. 1. If |count| is 0, set |lock| to "`open`". 1. Otherwise, set |lock| to "`open`". - 1. [=Queue a task=] to |replyTaskSource| to run |onLockReleasedAlgorithm| - and return. + 1. If |onLockReleasedAlgorithm| was given, [=queue a global task=] on the + [=storage task source=] to run |onLockReleasedAlgorithm|.
@@ -518,14 +518,17 @@ The createWritable(|options|) method 1. If |entry| is `null`, [=/reject=] |result| with a "{{NotFoundError}}" {{DOMException}} and abort. 1. [=Assert=]: |entry| is a [=file entry=]. - 1. Let |onLockTakenAlgorithm| be these steps: + 1. Let |lockResultAlgorithm| be an algorithm which takes a |lockResult| and + runs these steps: + 1. If |lockResult| is "`failure`", [=reject=] |result| with a + "{{NoModificationAllowedError}}" {{DOMException}} and abort. 1. Let |stream| be the result of creating a new `FileSystemWritableFileStream` for |entry| in [=this=]'s [=relevant realm=]. 1. If |options|.{{FileSystemCreateWritableOptions/keepExistingData}} is true: 1. Set |stream|.[=[[buffer]]=] to a copy of |entry|'s [=file entry/binary data=]. 1. [=/Resolve=] |result| with |stream|. - 1. [=file entry/lock/take|Take a lock=] with "`shared`" on |entry| with - |result|, |onLockTakenAlgorithm|, and [=storage task source=]. + 1. [=file entry/lock/take|Take a lock=] with "`shared`" on |entry| and with + |lockResultAlgorithm|. 1. Return |result|. @@ -571,11 +574,23 @@ The createSyncAccessHandle() method s 1. If |entry| does not represent a [=/file system entry=] in an [=origin private file system=], [=reject=] |result| with an "{{InvalidStateError}}" {{DOMException}} and abort. +<<<<<<< HEAD 1. Let |lockResult| be the result of [=file entry/lock/take|taking a lock=] with "`exclusive`" on |entry|. 1. If |lockResult| is false, [=reject=] |result| with a "{{NoModificationAllowedError}}" {{DOMException}} and abort. 1. Let |handle| be the result of creating a new `FileSystemSyncAccessHandle` for |entry| in [=this=]'s [=relevant realm=]. 1. [=/Resolve=] |result| with |handle|. +======= + 1. Let |lockResultAlgorithm| be an algorithm which takes a |lockResult| and + runs these steps: + 1. If |lockResult| is "`failure`", [=reject=] |result| with a + "{{NoModificationAllowedError}}" {{DOMException}} and abort. + 1. Let |handle| be the result of creating a new `FileSystemSyncAccessHandle` + for |entry| in [=this=]'s [=relevant realm=]. + 1. [=/Resolve=] |result| with |handle|. + 1. Let |lockResult| be the result of [=file entry/lock/take|taking a lock=] + with "`exclusive`" on |entry| and with |lockResultAlgorithm|. +>>>>>>> f840b90 (pass only one algorithm) 1. Return |result|. @@ -1064,8 +1079,8 @@ given a [=file entry=] |file| in a [=/Realm=] |realm|: being written to. 1. Let |onLockReleasedAlgorithm| be this step: [=/Resolve=] |closeResult| with `undefined`. - 1. [=file entry/lock/release|Release the lock=] on |stream|'s [=FileSystemWritableFileStream/[[file]]=] - with |onLockReleasedAlgorithm|, and the currently running [=task=]'s' [=task source|source=]. + 1. [=file entry/lock/release|Release the lock=] on |stream|'s + [=FileSystemWritableFileStream/[[file]]=] with |onLockReleasedAlgorithm|. 1. Return |closeResult|. 1. Let |abortAlgorithm| be this step: [=file entry/lock/release|release the lock=] on |stream|.[=FileSystemWritableFileStream/[[file]]=]. From 346b7d69e064ed7232ccc40b9158c9d49fe3f9be Mon Sep 17 00:00:00 2001 From: Austin Sullivan Date: Sat, 28 Jan 2023 04:17:07 +0000 Subject: [PATCH 05/15] wait for lock release on close --- index.bs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/index.bs b/index.bs index ba60c12..b35fe47 100644 --- a/index.bs +++ b/index.bs @@ -1519,8 +1519,10 @@ The close() method steps are: 1. If [=this=]'s [=[[state]]=] is "`closed`", return. 1. Set [=this=]'s [=[[state]]=] to "`closed`". +1. Let |onLockReleasedAlgorithm| be an algorithm that does nothing. 1. [=file entry/lock/release|Release the lock=] on - [=this=]'s [=FileSystemSyncAccessHandle/[[file]]=]. + [=this=]'s [=FileSystemSyncAccessHandle/[[file]]=] with |onLockReleasedAlgorithm|. +1. Wait for |onLockReleasedAlgorithm| to run. Note: This method does not guarantee that all file modifications will be immediately reflected in the underlying storage device. Call the From f727e379dc42dd614528239e333cd0f587c64f7a Mon Sep 17 00:00:00 2001 From: Austin Sullivan Date: Wed, 15 Feb 2023 00:25:23 +0000 Subject: [PATCH 06/15] queue a storage task --- index.bs | 32 ++++++++++++++++++-------------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/index.bs b/index.bs index b35fe47..d1a2700 100644 --- a/index.bs +++ b/index.bs @@ -109,20 +109,23 @@ algorithm |resultAlgorithm|: 1. If |value| is "`exclusive`": 1. If |lock| is "`open`": 1. Set lock to "`taken-exclusive`". - 1. [=Queue a global task=] on the [=storage task source=] to run - |resultAlgorithm| with a value of "`success`" and return. + 1. [=Queue a storage task=] given |file|'s [=relevant global object=] and + |resultAlgorithm| with a value of "`success`". + 1. Return. 1. If |value| is "`shared`": 1. If |lock| is "`open`": 1. Set |lock| to "`taken-shared`". 1. Set |count| to 1. - 1. [=Queue a global task=] on the [=storage task source=] to run - |resultAlgorithm| with a value of "`success`" and return. + 1. [=Queue a storage task=] given |file|'s [=relevant global object=] and + |resultAlgorithm| with a value of "`success`". + 1. Return. 1. Otherwise, if |lock| is "`taken-shared`": - 1. Increase |count| by one. - 1. [=Queue a global task=] on the [=storage task source=] to run - |resultAlgorithm| with a value of "`success`" and return. - 1. [=Queue a global task=] on the [=storage task source=] to run - |resultAlgorithm| with a value of "`failure`" and return. + 1. Increase |count| by 1. + 1. [=Queue a storage task=] given |file|'s [=relevant global object=] and + |resultAlgorithm| with a value of "`success`". + 1. Return. + 1. [=Queue a storage task=] given |file|'s [=relevant global object=] and + |resultAlgorithm| with a value of "`failure`". @@ -134,11 +137,11 @@ To release a [=file entry/lock=] on a given 1. Let |lock| be the |file|'s associated [=file entry/lock=]. 1. Let |count| be the |file|'s [=file entry/shared lock count=]. 1. If |lock| is "`taken-shared`": - 1. Decrease |count| by one. + 1. Decrease |count| by 1. 1. If |count| is 0, set |lock| to "`open`". 1. Otherwise, set |lock| to "`open`". - 1. If |onLockReleasedAlgorithm| was given, [=queue a global task=] on the - [=storage task source=] to run |onLockReleasedAlgorithm|. + 1. If |onLockReleasedAlgorithm| was given, [=queue a storage task=] given + |file|'s [=relevant global object=] and |onLockReleasedAlgorithm|. @@ -1519,10 +1522,11 @@ The close() method steps are: 1. If [=this=]'s [=[[state]]=] is "`closed`", return. 1. Set [=this=]'s [=[[state]]=] to "`closed`". -1. Let |onLockReleasedAlgorithm| be an algorithm that does nothing. +1. Set |lockReleased| to false. +1. Let |onLockReleasedAlgorithm| be this step: Set |lockReleased| to true. 1. [=file entry/lock/release|Release the lock=] on [=this=]'s [=FileSystemSyncAccessHandle/[[file]]=] with |onLockReleasedAlgorithm|. -1. Wait for |onLockReleasedAlgorithm| to run. +1. [=Pause=] until |lockReleased| is set to true. Note: This method does not guarantee that all file modifications will be immediately reflected in the underlying storage device. Call the From 9694ad1277aca40fa601635738c1a9bea1edf4cf Mon Sep 17 00:00:00 2001 From: Austin Sullivan Date: Wed, 15 Feb 2023 01:55:09 +0000 Subject: [PATCH 07/15] simplify lock-taking in createSAH --- index.bs | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/index.bs b/index.bs index d1a2700..54f0830 100644 --- a/index.bs +++ b/index.bs @@ -577,13 +577,6 @@ The createSyncAccessHandle() method s 1. If |entry| does not represent a [=/file system entry=] in an [=origin private file system=], [=reject=] |result| with an "{{InvalidStateError}}" {{DOMException}} and abort. -<<<<<<< HEAD - 1. Let |lockResult| be the result of [=file entry/lock/take|taking a lock=] with "`exclusive`" on |entry|. - 1. If |lockResult| is false, [=reject=] |result| with a "{{NoModificationAllowedError}}" {{DOMException}} and abort. - 1. Let |handle| be the result of creating a new `FileSystemSyncAccessHandle` - for |entry| in [=this=]'s [=relevant realm=]. - 1. [=/Resolve=] |result| with |handle|. -======= 1. Let |lockResultAlgorithm| be an algorithm which takes a |lockResult| and runs these steps: 1. If |lockResult| is "`failure`", [=reject=] |result| with a @@ -591,9 +584,8 @@ The createSyncAccessHandle() method s 1. Let |handle| be the result of creating a new `FileSystemSyncAccessHandle` for |entry| in [=this=]'s [=relevant realm=]. 1. [=/Resolve=] |result| with |handle|. - 1. Let |lockResult| be the result of [=file entry/lock/take|taking a lock=] - with "`exclusive`" on |entry| and with |lockResultAlgorithm|. ->>>>>>> f840b90 (pass only one algorithm) + 1. [=file entry/lock/take|Take a lock=] with "`exclusive`" on |entry| and with + |lockResultAlgorithm|. 1. Return |result|. From 0c515c2877ee0e3162893efd7bf18f942d4c187b Mon Sep 17 00:00:00 2001 From: Austin Sullivan Date: Thu, 23 Feb 2023 23:59:46 +0000 Subject: [PATCH 08/15] use parallel queue for all FS operations --- index.bs | 180 +++++++++++++++++++++++++++++-------------------------- 1 file changed, 95 insertions(+), 85 deletions(-) diff --git a/index.bs b/index.bs index 54f0830..9378a0f 100644 --- a/index.bs +++ b/index.bs @@ -94,54 +94,46 @@ A file entry additionally consists of a lock (a string that may exclusively be "`open`", "`taken-exclusive`" or "`taken-shared`") and a shared lock count (a number representing the number shared locks that are taken at a given point in time). -A user agent has an associated file system lock queue which is the +A user agent has an associated file system queue which is the result of [=starting a new parallel queue=]. This queue is to be used for all -[=tasks=] involving a [=file entry/lock=]. +file sytem operations.
To take a [=file entry/lock=] with a |value| of -"`exclusive`" or "`shared`" on a given [=file entry=] |file| and with -algorithm |resultAlgorithm|: - -1. [=Enqueue the following steps=] to the [=file system lock queue=]: - 1. Let |lock| be the |file|'s [=file entry/lock=]. - 1. Let |count| be the |file|'s [=file entry/shared lock count=]. - 1. If |value| is "`exclusive`": - 1. If |lock| is "`open`": - 1. Set lock to "`taken-exclusive`". - 1. [=Queue a storage task=] given |file|'s [=relevant global object=] and - |resultAlgorithm| with a value of "`success`". - 1. Return. - 1. If |value| is "`shared`": - 1. If |lock| is "`open`": - 1. Set |lock| to "`taken-shared`". - 1. Set |count| to 1. - 1. [=Queue a storage task=] given |file|'s [=relevant global object=] and - |resultAlgorithm| with a value of "`success`". - 1. Return. - 1. Otherwise, if |lock| is "`taken-shared`": - 1. Increase |count| by 1. - 1. [=Queue a storage task=] given |file|'s [=relevant global object=] and - |resultAlgorithm| with a value of "`success`". - 1. Return. - 1. [=Queue a storage task=] given |file|'s [=relevant global object=] and - |resultAlgorithm| with a value of "`failure`". +"`exclusive`" or "`shared`" on a given [=file entry=] |file|: + +1. Let |lock| be the |file|'s [=file entry/lock=]. +1. Let |count| be the |file|'s [=file entry/shared lock count=]. +1. If |value| is "`exclusive`": + 1. If |lock| is "`open`": + 1. Set lock to "`taken-exclusive`". + 1. Return a value of "`success`". +1. If |value| is "`shared`": + 1. If |lock| is "`open`": + 1. Set |lock| to "`taken-shared`". + 1. Set |count| to 1. + 1. Return a value of "`success`". + 1. Otherwise, if |lock| is "`taken-shared`": + 1. Increase |count| by 1. + 1. Return a value of "`success`". +1. Return a value of "`failure`". + +Note: These steps must be run on the [=file system queue=].
To release a [=file entry/lock=] on a given -[=file entry=] |file| and with an optional algorithm |onLockReleasedAlgorithm|: - -1. [=Enqueue the following steps=] to the [=file system lock queue=]: - 1. Let |lock| be the |file|'s associated [=file entry/lock=]. - 1. Let |count| be the |file|'s [=file entry/shared lock count=]. - 1. If |lock| is "`taken-shared`": - 1. Decrease |count| by 1. - 1. If |count| is 0, set |lock| to "`open`". - 1. Otherwise, set |lock| to "`open`". - 1. If |onLockReleasedAlgorithm| was given, [=queue a storage task=] given - |file|'s [=relevant global object=] and |onLockReleasedAlgorithm|. +[=file entry=] |file|: + +1. Let |lock| be the |file|'s associated [=file entry/lock=]. +1. Let |count| be the |file|'s [=file entry/shared lock count=]. +1. If |lock| is "`taken-shared`": + 1. Decrease |count| by 1. + 1. If |count| is 0, set |lock| to "`open`". +1. Otherwise, set |lock| to "`open`". + +Note: These steps must be run on the [=file system queue=].
@@ -511,27 +503,32 @@ The createWritable(|options|) method 1. Let |result| be [=a new promise=]. 1. Let |locator| be [=this=]'s [=FileSystemHandle/locator=]. -1. Run these steps [=in parallel=]: +1. Let |realm| be [=this=]'s [=relevant Realm=]. +1. [=Enqueue the following steps=] to the [=file system queue=]: 1. Let |entry| be the result of [=locating an entry=] given |locator|. 1. Let |access| be the result of running |entry|'s [=file system entry/request access=] given "`readwrite`". - If that throws an exception, [=reject=] |result| with that exception and abort. + If that throws an exception, [=reject=] |result| with that exception and + abort these steps. 1. If |access| is not "{{PermissionState/granted}}", - [=reject=] |result| with a "{{NotAllowedError}}" {{DOMException}} and abort. + [=reject=] |result| with a "{{NotAllowedError}}" {{DOMException}} and + abort these steps. + 1. If |entry| is `null`, [=/reject=] |result| with a "{{NotFoundError}}" {{DOMException}} and abort. 1. [=Assert=]: |entry| is a [=file entry=]. - 1. Let |lockResultAlgorithm| be an algorithm which takes a |lockResult| and - runs these steps: - 1. If |lockResult| is "`failure`", [=reject=] |result| with a - "{{NoModificationAllowedError}}" {{DOMException}} and abort. - 1. Let |stream| be the result of creating a new `FileSystemWritableFileStream` - for |entry| in [=this=]'s [=relevant realm=]. - 1. If |options|.{{FileSystemCreateWritableOptions/keepExistingData}} is true: - 1. Set |stream|.[=[[buffer]]=] to a copy of |entry|'s [=file entry/binary data=]. - 1. [=/Resolve=] |result| with |stream|. - 1. [=file entry/lock/take|Take a lock=] with "`shared`" on |entry| and with - |lockResultAlgorithm|. + + 1. Let |lockResult| be the result of [=file entry/lock/take|taking a lock=] + with "`shared`" on |entry|. + 1. If |lockResult| is "`failure`", [=reject=] |result| with a + "{{NoModificationAllowedError}}" {{DOMException}} and abort these steps. + + 1. Let |stream| be the result of creating a new `FileSystemWritableFileStream` + for |entry| in |realm|. + 1. If |options|'s {{FileSystemCreateWritableOptions/keepExistingData}} is true: + 1. Set |stream|'s [=[[buffer]]=] to a copy of |entry|'s [=file entry/binary data=]. + 1. [=/Resolve=] |result| with |stream|. + 1. Return |result|. @@ -563,29 +560,33 @@ The createSyncAccessHandle() method s 1. Let |result| be [=a new promise=]. 1. Let |locator| be [=this=]'s [=FileSystemHandle/locator=]. -1. Run these steps [=in parallel=]: +1. Let |realm| be [=this=]'s [=relevant Realm=]. +1. [=Enqueue the following steps=] to the [=file system queue=]: 1. Let |entry| be the result of [=locating an entry=] given |locator|. 1. Let |access| be the result of running |entry|'s [=file system entry/request access=] given "`readwrite`". - If that throws an exception, [=reject=] |result| with that exception and abort. - 1. If |access| is not "{{PermissionState/granted}}", - [=reject=] |result| with a "{{NotAllowedError}}" {{DOMException}} and abort. + If that throws an exception, [=reject=] |result| with that exception and + abort these steps. + 1. If |access| is not "{{PermissionState/granted}}", [=reject=] |result| with + a "{{NotAllowedError}}" {{DOMException}} and abort these steps. 1. If |entry| is `null`, [=/reject=] |result| with a "{{NotFoundError}}" {{DOMException}} and abort. 1. [=Assert=]: |entry| is a [=file entry=]. 1. If |entry| does not represent a [=/file system entry=] in an [=origin private file system=], - [=reject=] |result| with an "{{InvalidStateError}}" {{DOMException}} and abort. - 1. Let |lockResultAlgorithm| be an algorithm which takes a |lockResult| and - runs these steps: - 1. If |lockResult| is "`failure`", [=reject=] |result| with a - "{{NoModificationAllowedError}}" {{DOMException}} and abort. - 1. Let |handle| be the result of creating a new `FileSystemSyncAccessHandle` - for |entry| in [=this=]'s [=relevant realm=]. - 1. [=/Resolve=] |result| with |handle|. - 1. [=file entry/lock/take|Take a lock=] with "`exclusive`" on |entry| and with - |lockResultAlgorithm|. + [=reject=] |result| with an "{{InvalidStateError}}" {{DOMException}} and + abort these steps. + + 1. Let |lockResult| be the result of [=file entry/lock/take|taking a lock=] + with "`exclusive`" on |entry|. + 1. If |lockResult| is "`failure`", [=reject=] |result| with a + "{{NoModificationAllowedError}}" {{DOMException}} and abort these steps. + + 1. Let |handle| be the result of creating a new `FileSystemSyncAccessHandle` + for |entry| in |realm|. + 1. [=/Resolve=] |result| with |handle|. + 1. Return |result|. @@ -1055,30 +1056,38 @@ To given a [=file entry=] |file| in a [=/Realm=] |realm|: 1. Let |stream| be a [=new=] {{FileSystemWritableFileStream}} in |realm|. -1. Set |stream|.[=FileSystemWritableFileStream/[[file]]=] to |file|. +1. Set |stream|'s [=FileSystemWritableFileStream/[[file]]=] to |file|. 1. Let |writeAlgorithm| be an algorithm which takes a |chunk| argument and returns the result of running the [=write a chunk=] algorithm with |stream| and |chunk|. 1. Let |closeAlgorithm| be these steps: 1. Let |closeResult| be [=a new promise=]. - 1. Run these steps [=in parallel=]: - 1. Let |access| be the result of running |file|'s [=file system entry/query access=] given "`readwrite`". + 1. [=Enqueue the following steps=] to the [=file system queue=]: + 1. Let |access| be the result of running |file|'s + [=file system entry/query access=] given "`readwrite`". 1. If |access| is not "{{PermissionState/granted}}", [=/reject=] |closeResult| with a "{{NotAllowedError}}" {{DOMException}} - and abort. + and abort these steps. + 1. Run [=implementation-defined=] malware scans and safe browsing checks. - If these checks fail, [=/reject=] |closeResult| with an "{{AbortError}}" {{DOMException}} and abort. - 1. Set |stream|.[=FileSystemWritableFileStream/[[file]]=]'s [=file entry/binary data=] to |stream|.[=[[buffer]]=]. - If that throws an exception, [=/reject=] |closeResult| with that exception and abort. + If these checks fail, [=/reject=] |closeResult| with an + "{{AbortError}}" {{DOMException}} and abort these steps. + 1. Set |stream|'s [=FileSystemWritableFileStream/[[file]]=]'s + [=file entry/binary data=] to |stream|'s [=[[buffer]]=]. + If that throws an exception, [=/reject=] |closeResult| with that + exception and abort these steps. + + Note: It is expected that this atomically updates the contents of the + file on disk being written to. - 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|'s [=FileSystemWritableFileStream/[[file]]=]. + 1. [=/Resolve=] |closeResult| with `undefined`. - 1. Let |onLockReleasedAlgorithm| be this step: [=/Resolve=] |closeResult| with `undefined`. - 1. [=file entry/lock/release|Release the lock=] on |stream|'s - [=FileSystemWritableFileStream/[[file]]=] with |onLockReleasedAlgorithm|. 1. Return |closeResult|. -1. Let |abortAlgorithm| be this step: [=file entry/lock/release|release the lock=] on - |stream|.[=FileSystemWritableFileStream/[[file]]=]. +1. Let |abortAlgorithm| be these steps: + 1. [=enqueue steps|Enqueue this step=] to the [=file system queue=]: + 1. [=file entry/lock/release|release the lock=] on + |stream|'s [=FileSystemWritableFileStream/[[file]]=]. 1. Let |highWaterMark| be 1. 1. Let |sizeAlgorithm| be an algorithm that returns `1`. 1. [=WritableStream/Set up=] |stream| with close() method steps are: 1. If [=this=]'s [=[[state]]=] is "`closed`", return. 1. Set [=this=]'s [=[[state]]=] to "`closed`". 1. Set |lockReleased| to false. -1. Let |onLockReleasedAlgorithm| be this step: Set |lockReleased| to true. -1. [=file entry/lock/release|Release the lock=] on - [=this=]'s [=FileSystemSyncAccessHandle/[[file]]=] with |onLockReleasedAlgorithm|. -1. [=Pause=] until |lockReleased| is set to true. +1. Let |file| be [=this=]'s [=FileSystemSyncAccessHandle/[[file]]=]. +1. [=Enqueue the following steps=] to the [=file system queue=]: + 1. [=file entry/lock/release|Release the lock=] on |file|. + 1. Set |lockReleased| to true. +1. [=Pause=] until |lockReleased| is true. Note: This method does not guarantee that all file modifications will be immediately reflected in the underlying storage device. Call the From 334d4e6dbb88a7f885c065497d55370b5a92c5ca Mon Sep 17 00:00:00 2001 From: Austin Sullivan Date: Thu, 2 Mar 2023 00:22:37 +0000 Subject: [PATCH 09/15] queue a task to enqueue steps --- index.bs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/index.bs b/index.bs index 9378a0f..7a93fd5 100644 --- a/index.bs +++ b/index.bs @@ -107,18 +107,18 @@ To take a [=file entry/lock=] with a |value| of 1. If |value| is "`exclusive`": 1. If |lock| is "`open`": 1. Set lock to "`taken-exclusive`". - 1. Return a value of "`success`". + 1. Return "`success`". 1. If |value| is "`shared`": 1. If |lock| is "`open`": 1. Set |lock| to "`taken-shared`". 1. Set |count| to 1. - 1. Return a value of "`success`". + 1. Return "`success`". 1. Otherwise, if |lock| is "`taken-shared`": 1. Increase |count| by 1. - 1. Return a value of "`success`". -1. Return a value of "`failure`". + 1. Return "`success`". +1. Return "`failure`". -Note: These steps must be run on the [=file system queue=]. +Note: These steps have to be run on the [=file system queue=]. @@ -133,7 +133,7 @@ To release a [=file entry/lock=] on a given 1. If |count| is 0, set |lock| to "`open`". 1. Otherwise, set |lock| to "`open`". -Note: These steps must be run on the [=file system queue=]. +Note: These steps have to be run on the [=file system queue=]. @@ -504,7 +504,8 @@ The createWritable(|options|) method 1. Let |result| be [=a new promise=]. 1. Let |locator| be [=this=]'s [=FileSystemHandle/locator=]. 1. Let |realm| be [=this=]'s [=relevant Realm=]. -1. [=Enqueue the following steps=] to the [=file system queue=]: +1. [=Queue a storage task=] with [=this=]'s [=relevant global object=] to + [=enqueue the following steps=] to the [=file system queue=]: 1. Let |entry| be the result of [=locating an entry=] given |locator|. 1. Let |access| be the result of running |entry|'s [=file system entry/request access=] given "`readwrite`". From f2d6b04856275c4a369df92edc69da570f77335e Mon Sep 17 00:00:00 2001 From: Austin Sullivan Date: Tue, 4 Apr 2023 04:38:36 +0000 Subject: [PATCH 10/15] rebase + make access checks not throwable --- index.bs | 149 ++++++++++++++++++++++++++++++++----------------------- 1 file changed, 88 insertions(+), 61 deletions(-) diff --git a/index.bs b/index.bs index 7a93fd5..85791f5 100644 --- a/index.bs +++ b/index.bs @@ -61,16 +61,31 @@ different storage mechanism with a different API for such files. The entry point A file system entry is either a [=file entry=] or a [=directory entry=]. -Each [=/file system entry=] has an associated query access algorithm, which takes "`read`" -or "`readwrite`" mode and returns a {{PermissionState}}. Unless specified -otherwise it returns "{{PermissionState/denied}}". The algorithm is allowed to throw. - -Each [=/file system entry=] has an associated request access algorithm, which takes -"`read`" or "`readwrite`" mode and returns a {{PermissionState}}. Unless specified -otherwise it returns "{{PermissionState/denied}}". The algorithm is allowed to throw. - -Note: Implementations that only implement this specification and not dependent specifications do not -need to bother implementing [=/file system entry=]'s [=file system entry/query access=] and [=file system entry/request access=]. +Each [=/file system entry=] has an associated +query access +algorithm, which takes "`read`" or "`readwrite`" mode and +returns either a {{PermissionState}} or an [=exception/error name=] that must be +listed in the [=error names table=]. +Unless specified otherwise it returns "{{PermissionState/denied}}". + +Each [=/file system entry=] has an associated +request access +algorithm, which takes "`read`" or "`readwrite`" mode and +returns either a {{PermissionState}} or an [=exception/error name=] that must be +listed in the [=error names table=]. +Unless specified otherwise it returns "{{PermissionState/denied}}". + +

Dependent specifications may consider this API a +[=powerful feature=]. However, unlike other [=powerful features=] whose +[=permission request algorithm=] may throw, [=/file system entry=]'s +[=file system entry/query access=] and [=file system entry/request access=] +algorithms must run [=in parallel=] on the [=file system queue=] and are +therefore not allowed to throw. Instead, the caller is expected to [=/reject=] +as appropriate should these algorithms return an [=exception/error name=]. + +Note: Implementations that only implement this specification and not dependent +specifications do not need to bother implementing [=/file system entry=]'s +[=file system entry/query access=] and [=file system entry/request access=]. Issue(101): Make access check algorithms associated with a FileSystemHandle. @@ -504,31 +519,33 @@ The createWritable(|options|) method 1. Let |result| be [=a new promise=]. 1. Let |locator| be [=this=]'s [=FileSystemHandle/locator=]. 1. Let |realm| be [=this=]'s [=relevant Realm=]. -1. [=Queue a storage task=] with [=this=]'s [=relevant global object=] to - [=enqueue the following steps=] to the [=file system queue=]: +1. Let |global| be [=this=]'s [=relevant global object=]. +1. [=Enqueue the following steps=] to the [=file system queue=]: 1. Let |entry| be the result of [=locating an entry=] given |locator|. 1. Let |access| be the result of running |entry|'s [=file system entry/request access=] given "`readwrite`". - If that throws an exception, [=reject=] |result| with that exception and - abort these steps. - 1. If |access| is not "{{PermissionState/granted}}", - [=reject=] |result| with a "{{NotAllowedError}}" {{DOMException}} and - abort these steps. - - 1. If |entry| is `null`, [=/reject=] |result| with a - "{{NotFoundError}}" {{DOMException}} and abort. + 1. If |access| is not "{{PermissionState/granted}}": + 1. Set |requestAccessError| to |access| if |access| is an + [=exception/error name=]; otherwise, "{{NotAllowedError}}". + 1. [=Queue a storage task=] with |global| to [=/reject=] |result| with a + |requestAccessError| {{DOMException}} and abort these steps. + + 1. If |entry| is `null`, [=queue a storage task=] with |global| to [=/reject=] + |result| with a "{{NotFoundError}}" {{DOMException}} and abort these steps. 1. [=Assert=]: |entry| is a [=file entry=]. 1. Let |lockResult| be the result of [=file entry/lock/take|taking a lock=] with "`shared`" on |entry|. - 1. If |lockResult| is "`failure`", [=reject=] |result| with a + 1. If |lockResult| is "`failure`", [=queue a storage task=] with |global| to + [=/reject=] |result| with a "{{NoModificationAllowedError}}" {{DOMException}} and abort these steps. - 1. Let |stream| be the result of creating a new `FileSystemWritableFileStream` - for |entry| in |realm|. - 1. If |options|'s {{FileSystemCreateWritableOptions/keepExistingData}} is true: - 1. Set |stream|'s [=[[buffer]]=] to a copy of |entry|'s [=file entry/binary data=]. - 1. [=/Resolve=] |result| with |stream|. + 1. [=Queue a storage task=] with |global| to run these steps: + 1. Let |stream| be the result of creating a new `FileSystemWritableFileStream` + for |entry| in |realm|. + 1. If |options|'s {{FileSystemCreateWritableOptions/keepExistingData}} is true: + 1. Set |stream|'s [=[[buffer]]=] to a copy of |entry|'s [=file entry/binary data=]. + 1. [=/Resolve=] |result| with |stream|. 1. Return |result|. @@ -562,31 +579,36 @@ The createSyncAccessHandle() method s 1. Let |result| be [=a new promise=]. 1. Let |locator| be [=this=]'s [=FileSystemHandle/locator=]. 1. Let |realm| be [=this=]'s [=relevant Realm=]. +1. Let |global| be [=this=]'s [=relevant global object=]. 1. [=Enqueue the following steps=] to the [=file system queue=]: 1. Let |entry| be the result of [=locating an entry=] given |locator|. 1. Let |access| be the result of running |entry|'s [=file system entry/request access=] given "`readwrite`". - If that throws an exception, [=reject=] |result| with that exception and - abort these steps. - 1. If |access| is not "{{PermissionState/granted}}", [=reject=] |result| with - a "{{NotAllowedError}}" {{DOMException}} and abort these steps. - - 1. If |entry| is `null`, [=/reject=] |result| with a - "{{NotFoundError}}" {{DOMException}} and abort. + 1. If |access| is not "{{PermissionState/granted}}": + 1. Set |requestAccessError| to |access| if |access| is an + [=exception/error name=]; otherwise, "{{NotAllowedError}}". + 1. [=Queue a storage task=] with |global| to [=/reject=] |result| with a + |requestAccessError| {{DOMException}} and abort these steps. + + 1. If |entry| is `null`, [=queue a storage task=] with |global| to [=/reject=] + |result| with a "{{NotFoundError}}" {{DOMException}} and abort these steps. 1. [=Assert=]: |entry| is a [=file entry=]. - 1. If |entry| does not represent a [=/file system entry=] in an [=origin private file system=], - [=reject=] |result| with an "{{InvalidStateError}}" {{DOMException}} and + 1. If |entry| does not represent a [=/file system entry=] in an + [=origin private file system=], [=queue a storage task=] with |global| to + [=/reject=] |result| with an "{{InvalidStateError}}" {{DOMException}} and abort these steps. 1. Let |lockResult| be the result of [=file entry/lock/take|taking a lock=] with "`exclusive`" on |entry|. - 1. If |lockResult| is "`failure`", [=reject=] |result| with a + 1. If |lockResult| is "`failure`", [=queue a storage task=] with |global| to + [=/reject=] |result| with a "{{NoModificationAllowedError}}" {{DOMException}} and abort these steps. - 1. Let |handle| be the result of creating a new `FileSystemSyncAccessHandle` - for |entry| in |realm|. - 1. [=/Resolve=] |result| with |handle|. + 1. [=Queue a storage task=] with |global| to run these steps: + 1. Let |handle| be the result of creating a new `FileSystemSyncAccessHandle` + for |entry| in |realm|. + 1. [=/Resolve=] |result| with |handle|. 1. Return |result|. @@ -707,7 +729,7 @@ and its async iterator |iterator|: [=file system entry/query access=] given "`read`". 1. If |access| is not "{{PermissionState/granted}}", - [=reject=] |promise| with a "{{NotAllowedError}}" {{DOMException}} and + [=/reject=] |promise| with a "{{NotAllowedError}}" {{DOMException}} and return |promise|. 1. Let |child| be a [=/file system entry=] in |directory|'s [=directory entry/children=], @@ -774,7 +796,7 @@ The getFileHandle(|name|, |options|)getDirectoryHandle(|name|, |option 1. If |options|.{{FileSystemGetDirectoryOptions/create}} is true: 1. Let |access| be the result of running |entry|'s [=file system entry/request access=] given "`readwrite`". - If that throws an exception, [=reject=] |result| with that exception and abort. + If that throws an exception, [=/reject=] |result| with that exception and abort. 1. Otherwise: 1. Let |access| be the result of running |entry|'s [=file system entry/query access=] given "`read`". @@ -915,7 +937,7 @@ The removeEntry(|name|, |options|) Date: Fri, 2 Jun 2023 04:58:34 +0000 Subject: [PATCH 11/15] apply pattern everywhere --- index.bs | 538 +++++++++++++++++++++++++++++++++---------------------- 1 file changed, 319 insertions(+), 219 deletions(-) diff --git a/index.bs b/index.bs index 85791f5..6197312 100644 --- a/index.bs +++ b/index.bs @@ -64,16 +64,31 @@ A file system entry is either a [=file entry=] or a Each [=/file system entry=] has an associated query access algorithm, which takes "`read`" or "`readwrite`" mode and -returns either a {{PermissionState}} or an [=exception/error name=] that must be -listed in the [=error names table=]. -Unless specified otherwise it returns "{{PermissionState/denied}}". +returns a [=/file system access result=]. +Unless specified otherwise it returns a [=/file system access result=] with a +[=file system access result/permission state=] of "{{PermissionState/denied}}" +and with a [=file system access result/error name=] of « the empty string ». Each [=/file system entry=] has an associated request access algorithm, which takes "`read`" or "`readwrite`" mode and -returns either a {{PermissionState}} or an [=exception/error name=] that must be -listed in the [=error names table=]. -Unless specified otherwise it returns "{{PermissionState/denied}}". +returns a [=/file system access result=]. +Unless specified otherwise it returns a [=/file system access result=] with a +[=file system access result/permission state=] of "{{PermissionState/denied}}" +and with a [=file system access result/error name=] of « the empty string ». + +A file system access result is a [=struct=] encapsulating the +result of [=file system entry/query access|querying=] or +[=file system entry/request access|requesting=] access to the file system. +It has the following [=struct/items=]: + +: permission state +:: A {{PermissionState}} +: error name +:: A [=string=] which must be either « the empty string » or an + [=exception/error name=] listed in the [=error names table=]. + If [=file system access result/permission state=] is + "{{PermissionState/granted}}" this must be « the empty string »

Dependent specifications may consider this API a [=powerful feature=]. However, unlike other [=powerful features=] whose @@ -81,7 +96,8 @@ Unless specified otherwise it returns "{{PermissionState/denied}}". [=file system entry/query access=] and [=file system entry/request access=] algorithms must run [=in parallel=] on the [=file system queue=] and are therefore not allowed to throw. Instead, the caller is expected to [=/reject=] -as appropriate should these algorithms return an [=exception/error name=]. +as appropriate should these algorithms return an +[=file system access result/error name=] other than « the empty string ». Note: Implementations that only implement this specification and not dependent specifications do not need to bother implementing [=/file system entry=]'s @@ -159,7 +175,7 @@ A directory entry additionally consists of a [=/s children, which are themselves [=/file system entries=]. Each member is either a [=/file entry=] or a [=/directory entry=]. -A [=/file system entry=] |entry| should be [=list/contained=] in the [=children=] of at most one +A [=/file system entry=] |entry| should be [=list/contained=] in the [=directory entry/children=] of at most one [=directory entry=], and that directory entry is also known as |entry|'s parent. A [=/file system entry=]'s [=file system entry/parent=] is null if no such directory entry exists. @@ -170,7 +186,7 @@ parent while the other entry does not have a parent. [=/File system entries=] can (but don't have to) be backed by files on the host operating system's local file system, so it is possible for the [=binary data=], [=modification timestamp=], -and [=children=] of entries to be modified by applications outside of this specification. +and [=directory entry/children=] of entries to be modified by applications outside of this specification. Exactly how external changes are reflected in the data structures defined by this specification, as well as how changes made to the data structures defined here are reflected externally is left up to individual user-agent implementations. @@ -185,22 +201,22 @@ To resolve a [=/file system locator=] |child| relative to a [=directory locator=] |root|: 1. Let |result| be [=a new promise=]. -1. Run these steps [=in parallel=]: +1. [=Enqueue the following steps=] to the [=file system queue=]: 1. If |child|'s [=FileSystemHandle/locator=]'s [=file system locator/root=] is not |root|'s [=FileSystemHandle/locator=]'s [=file system locator/root=], - [=/resolve=] |result| with null, and abort. + [=/resolve=] |result| with null, and abort these steps. 1. Let |childPath| be |child|'s [=FileSystemHandle/locator=]'s [=file system locator/path=]. 1. Let |rootPath| be |root|'s [=FileSystemHandle/locator=]'s [=file system locator/path=]. 1. If |childPath| is [=the same path as=] |rootPath|, - [=/resolve=] |result| with « », and abort. + [=/resolve=] |result| with « », and abort these steps. 1. If |rootPath|'s [=list/size=] is greater than |childPath|'s [=list/size=], - [=/resolve=] |result| with null, and abort. + [=/resolve=] |result| with null, and abort these steps. 1. [=list/For each=] |index| of |rootPath|'s [=list/indices=]: 1. If |rootPath|.\[[|index|]] is not |childPath|.\[[|index|]], then - [=/resolve=] |result| with null, and abort. + [=/resolve=] |result| with null, and abort these steps. 1. Let |relativePath| be « ». 1. [=list/For each=] |index| of [=the range=] from |rootPath|'s [=list/size=] @@ -373,7 +389,7 @@ The isSameEntry(|other|) method steps are 1. Let |realm| be [=this=]'s [=relevant Realm=]. 1. Let |p| be [=a new promise=] in |realm|. -1. Run these steps [=in parallel=]: +1. [=Enqueue the following steps=] to the [=file system queue=]: 1. If [=this=]'s [=FileSystemHandle/locator=] is [=the same locator as=] |other|'s [=FileSystemHandle/locator=], [=/resolve=] |p| with true. @@ -452,27 +468,35 @@ The getFile() method steps are: 1. Let |result| be [=a new promise=]. 1. Let |locator| be [=this=]'s [=FileSystemHandle/locator=]. -1. Run these steps [=in parallel=]: +1. Let |global| be [=this=]'s [=relevant global object=]. +1. [=Enqueue the following steps=] to the [=file system queue=]: 1. Let |entry| be the result of [=locating an entry=] given |locator|. - 1. Let |access| be the result of running |entry|'s + 1. Let |accessResult| be the result of running |entry|'s [=file system entry/query access=] given "`read`". - 1. If |access| is not "{{PermissionState/granted}}", - [=/reject=] |result| with a "{{NotAllowedError}}" {{DOMException}} and abort. - 1. If |entry| is null, [=/reject=] |result| with a - "{{NotFoundError}}" {{DOMException}} and abort. - 1. [=Assert=]: |entry| is a [=file entry=]. - - 1. Let |f| be a new {{File}}. - 1. Set |f|'s snapshot state to the current state of |entry|. - 1. Set |f|'s underlying byte sequence to a copy of |entry|'s [=binary data=]. - 1. Set |f|.{{File/name}} to |entry|'s [=file system entry/name=]. - 1. Set |f|.{{File/lastModified}} to |entry|'s [=file entry/modification timestamp=]. - 1. Set |f|.{{Blob/type}} to an [=implementation-defined=] value, based on for example |entry|'s [=file system entry/name=] or its file extension. - - Issue: The reading and snapshotting behavior needs to be better specified in the [[FILE-API]] spec, - for now this is kind of hand-wavy. - 1. [=/Resolve=] |result| with |f|. + 1. [=Queue a storage task=] with |global| to run these steps: + 1. If |accessResult|'s [=file system access result/permission state=] + is not "{{PermissionState/granted}}": + 1. Set |accessErrorName| to |accessResult|'s + [=file system access result/error name=] if it is not + « the empty string »; otherwise "{{NotAllowedError}}". + 1. [=/Reject=] |result| with an |accessErrorName| {{DOMException}} and + abort these steps. + + 1. If |entry| is null, [=/reject=] |result| with a + "{{NotFoundError}}" {{DOMException}} and abort these steps. + 1. [=Assert=]: |entry| is a [=file entry=]. + + 1. Let |f| be a new {{File}}. + 1. Set |f|'s snapshot state to the current state of |entry|. + 1. Set |f|'s underlying byte sequence to a copy of |entry|'s [=binary data=]. + 1. Set |f|.{{File/name}} to |entry|'s [=file system entry/name=]. + 1. Set |f|.{{File/lastModified}} to |entry|'s [=file entry/modification timestamp=]. + 1. Set |f|.{{Blob/type}} to an [=implementation-defined=] value, based on for example |entry|'s [=file system entry/name=] or its file extension. + + Issue: The reading and snapshotting behavior needs to be better specified in the [[FILE-API]] spec, + for now this is kind of hand-wavy. + 1. [=/Resolve=] |result| with |f|. 1. Return |result|. @@ -522,13 +546,15 @@ The createWritable(|options|) method 1. Let |global| be [=this=]'s [=relevant global object=]. 1. [=Enqueue the following steps=] to the [=file system queue=]: 1. Let |entry| be the result of [=locating an entry=] given |locator|. - 1. Let |access| be the result of running |entry|'s + 1. Let |accessResult| be the result of running |entry|'s [=file system entry/request access=] given "`readwrite`". - 1. If |access| is not "{{PermissionState/granted}}": - 1. Set |requestAccessError| to |access| if |access| is an - [=exception/error name=]; otherwise, "{{NotAllowedError}}". - 1. [=Queue a storage task=] with |global| to [=/reject=] |result| with a - |requestAccessError| {{DOMException}} and abort these steps. + 1. If |accessResult|'s [=file system access result/permission state=] + is not "{{PermissionState/granted}}": + 1. Set |accessErrorName| to |accessResult|'s + [=file system access result/error name=] if it is not + « the empty string »; otherwise "{{NotAllowedError}}". + 1. [=Queue a storage task=] with |global| to [=/reject=] |result| with an + |accessErrorName| {{DOMException}} and abort these steps. 1. If |entry| is `null`, [=queue a storage task=] with |global| to [=/reject=] |result| with a "{{NotFoundError}}" {{DOMException}} and abort these steps. @@ -536,11 +562,11 @@ The createWritable(|options|) method 1. Let |lockResult| be the result of [=file entry/lock/take|taking a lock=] with "`shared`" on |entry|. - 1. If |lockResult| is "`failure`", [=queue a storage task=] with |global| to - [=/reject=] |result| with a - "{{NoModificationAllowedError}}" {{DOMException}} and abort these steps. 1. [=Queue a storage task=] with |global| to run these steps: + 1. If |lockResult| is "`failure`", [=/reject=] |result| with a + "{{NoModificationAllowedError}}" {{DOMException}} and abort these steps. + 1. Let |stream| be the result of creating a new `FileSystemWritableFileStream` for |entry| in |realm|. 1. If |options|'s {{FileSystemCreateWritableOptions/keepExistingData}} is true: @@ -582,13 +608,15 @@ The createSyncAccessHandle() method s 1. Let |global| be [=this=]'s [=relevant global object=]. 1. [=Enqueue the following steps=] to the [=file system queue=]: 1. Let |entry| be the result of [=locating an entry=] given |locator|. - 1. Let |access| be the result of running |entry|'s + 1. Let |accessResult| be the result of running |entry|'s [=file system entry/request access=] given "`readwrite`". - 1. If |access| is not "{{PermissionState/granted}}": - 1. Set |requestAccessError| to |access| if |access| is an - [=exception/error name=]; otherwise, "{{NotAllowedError}}". - 1. [=Queue a storage task=] with |global| to [=/reject=] |result| with a - |requestAccessError| {{DOMException}} and abort these steps. + 1. If |accessResult|'s [=file system access result/permission state=] + is not "{{PermissionState/granted}}": + 1. Set |accessErrorName| to |accessResult|'s + [=file system access result/error name=] if it is not + « the empty string »; otherwise "{{NotAllowedError}}". + 1. [=Queue a storage task=] with |global| to [=/reject=] |result| with an + |accessErrorName| {{DOMException}} and abort these steps. 1. If |entry| is `null`, [=queue a storage task=] with |global| to [=/reject=] |result| with a "{{NotFoundError}}" {{DOMException}} and abort these steps. @@ -601,11 +629,11 @@ The createSyncAccessHandle() method s 1. Let |lockResult| be the result of [=file entry/lock/take|taking a lock=] with "`exclusive`" on |entry|. - 1. If |lockResult| is "`failure`", [=queue a storage task=] with |global| to - [=/reject=] |result| with a - "{{NoModificationAllowedError}}" {{DOMException}} and abort these steps. 1. [=Queue a storage task=] with |global| to run these steps: + 1. If |lockResult| is "`failure`", [=/reject=] |result| with a + "{{NoModificationAllowedError}}" {{DOMException}} and abort these steps. + 1. Let |handle| be the result of creating a new `FileSystemSyncAccessHandle` for |entry| in |realm|. 1. [=/Resolve=] |result| with |handle|. @@ -701,13 +729,17 @@ support for example recursive iteration. The [=asynchronous iterator initialization steps=] for a {{FileSystemDirectoryHandle}} |handle| and its async iterator |iterator| are: -1. Let |entry| be the result of [=locating an entry=] - given |handle|'s [=FileSystemHandle/locator=]. -1. Let |access| be the result of running |entry|'s - [=file system entry/query access=] given "`read`". - -1. If |access| is not "{{PermissionState/granted}}", - [=throw=] a "{{NotAllowedError}}" {{DOMException}}. +1. [=Enqueue the following steps=] to the [=file system queue=]: + 1. Let |entry| be the result of [=locating an entry=] + given |handle|'s [=FileSystemHandle/locator=]. + 1. Let |accessResult| be the result of running |entry|'s + [=file system entry/query access=] given "`read`". + 1. If |accessResult|'s [=file system access result/permission state=] + is not "{{PermissionState/granted}}": + 1. Set |accessErrorName| to |accessResult|'s + [=file system access result/error name=] if it is not + « the empty string »; otherwise "{{NotAllowedError}}". + 1. [=Throw=] an |accessErrorName| {{DOMException}}. 1. Set |iterator|'s past results to an empty [=/set=]. @@ -718,43 +750,54 @@ To [=get the next iteration result=] for a {{FileSystemDirectoryHandle}} |handle and its async iterator |iterator|: 1. Let |promise| be [=a new promise=]. +1. [=Enqueue the following steps=] to the [=file system queue=]: + 1. Let |directory| be the result of [=locating an entry=] + given |handle|'s [=FileSystemHandle/locator=]. + 1. Let |accessResult| be the result of running |directory|'s + [=file system entry/query access=] given "`read`". -1. Let |directory| be the result of [=locating an entry=] - given |handle|'s [=FileSystemHandle/locator=]. -1. If |directory| is `null`, [=/reject=] |result| with a - "{{NotFoundError}}" {{DOMException}} and abort. - 1. [=Assert=]: |directory| is a [=directory entry=]. - -1. Let |access| be the result of running |directory|'s - [=file system entry/query access=] given "`read`". - -1. If |access| is not "{{PermissionState/granted}}", - [=/reject=] |promise| with a "{{NotAllowedError}}" {{DOMException}} and - return |promise|. - -1. Let |child| be a [=/file system entry=] in |directory|'s [=directory entry/children=], - such that |child|'s [=file system entry/name=] is not contained in |iterator|'s [=past results=], - or `null` if no such entry exists. - - Note: This is intentionally very vague about the iteration order. Different platforms - and file systems provide different guarantees about iteration order, and we want it to - be possible to efficiently implement this on all platforms. As such no guarantees are given - about the exact order in which elements are returned. - -1. If |child| is `null`, then: - 1. [=/Resolve=] |promise| with `undefined`. - -1. Otherwise: - 1. [=set/Append=] |child|'s [=file system entry/name=] to |iterator|'s [=past results=]. - 1. If |child| is a [=file entry=]: - 1. Let |result| be the result of creating a child `FileSystemFileHandle` - with |handle|'s [=FileSystemHandle/locator=] and - |child|'s [=file system entry/name=] in |handle|'s [=relevant Realm=]. - 1. Otherwise: - 1. Let |result| be the result of creating a child `FileSystemDirectoryHandle` - with |handle|'s [=FileSystemHandle/locator=] and - |child|'s [=file system entry/name=] in |handle|'s [=relevant Realm=]. - 1. [=/Resolve=] |promise| with (|child|'s [=file system entry/name=], |result|). + 1. [=Queue a storage task=] with |handle|'s [=relevant global object=] to + run these steps: + 1. If |accessResult|'s [=file system access result/permission state=] + is not "{{PermissionState/granted}}": + 1. Set |accessErrorName| to |accessResult|'s + [=file system access result/error name=] if it is not + « the empty string »; otherwise "{{NotAllowedError}}". + 1. [=/Reject=] |promise| with an |accessErrorName| {{DOMException}} + and return |promise|. + + 1. If |directory| is `null`, [=/reject=] |result| with a + "{{NotFoundError}}" {{DOMException}} and abort these steps. + 1. [=Assert=]: |directory| is a [=directory entry=]. + + 1. Let |child| be a [=/file system entry=] in + |directory|'s [=directory entry/children=], such that + |child|'s [=file system entry/name=] is not contained in + |iterator|'s [=past results=], or `null` if no such entry exists. + + Note: This is intentionally very vague about the iteration order. + Different platforms and file systems provide different guarantees about + iteration order, and we want it to be possible to efficiently implement + this on all platforms. As such no guarantees are given about the exact + order in which elements are returned. + + 1. If |child| is `null`, [=/resolve=] |promise| with `undefined` and + abort these steps. + + 1. [=set/Append=] |child|'s [=file system entry/name=] to + |iterator|'s [=past results=]. + 1. If |child| is a [=file entry=]: + 1. Let |result| be the result of + creating a child `FileSystemFileHandle` with + |handle|'s [=FileSystemHandle/locator=] and + |child|'s [=file system entry/name=] in |handle|'s [=relevant Realm=]. + 1. Otherwise: + 1. Let |result| be the result of + creating a child `FileSystemDirectoryHandle` with + |handle|'s [=FileSystemHandle/locator=] and + |child|'s [=file system entry/name=] in |handle|'s [=relevant Realm=]. + 1. [=/Resolve=] |promise| with + (|child|'s [=file system entry/name=], |result|). 1. Return |promise|. @@ -789,47 +832,58 @@ The getFileHandle(|name|, |options|)creating a child `FileSystemFileHandle` with |locator| and - |child|'s [=file system entry/name=] in |realm| and abort. - 1. If |options|.{{FileSystemGetFileOptions/create}} is false: - 1. [=/Reject=] |result| with a "{{NotFoundError}}" {{DOMException}} and abort. - 1. Let |child| be a new [=file entry=] whose [=query access=] and [=request access=] algorithms - are those of |entry|. - 1. Set |child|'s [=file system entry/name=] to |name|. - 1. Set |child|'s [=binary data=] to an empty [=byte sequence=]. - 1. Set |child|'s [=modification timestamp=] to the current time. - 1. [=set/Append=] |child| to |entry|'s [=directory entry/children=]. - 1. If creating |child| in the underlying file system throws an exception, - [=/reject=] |result| with that exception and abort. - - Issue(11): Better specify what possible exceptions this could throw. - 1. [=/Resolve=] |result| with the result of - creating a child `FileSystemFileHandle` with |locator| and - |child|'s [=file system entry/name=] in |realm|. + + 1. [=Queue a storage task=] with |global| to run these steps: + 1. If |accessResult|'s [=file system access result/permission state=] + is not "{{PermissionState/granted}}": + 1. Set |accessErrorName| to |accessResult|'s + [=file system access result/error name=] if it is not + « the empty string »; otherwise "{{NotAllowedError}}". + 1. [=/Reject=] |result| with an |accessErrorName| {{DOMException}} and + abort these steps. + + 1. If |entry| is `null`, [=/reject=] |result| with a + "{{NotFoundError}}" {{DOMException}} and abort these steps. + 1. [=Assert=]: |entry| is a [=directory entry=]. + + 1. [=set/For each=] |child| of |entry|'s [=directory entry/children=]: + 1. If |child|'s [=file system entry/name=] equals |name|: + 1. If |child| is a [=directory entry=]: + 1. [=/Reject=] |result| with a + "{{TypeMismatchError}}" {{DOMException}} and abort these steps. + 1. [=/Resolve=] |result| with the result of + creating a child `FileSystemFileHandle` with |locator| and + |child|'s [=file system entry/name=] in |realm| and + abort these steps. + 1. If |options|.{{FileSystemGetFileOptions/create}} is false: + 1. [=/Reject=] |result| with a "{{NotFoundError}}" {{DOMException}} and + abort these steps. + 1. Let |child| be a new [=file entry=] whose [=query access=] and + [=request access=] algorithms are those of |entry|. + 1. Set |child|'s [=file system entry/name=] to |name|. + 1. Set |child|'s [=binary data=] to an empty [=byte sequence=]. + 1. Set |child|'s [=modification timestamp=] to the current time. + 1. [=set/Append=] |child| to |entry|'s [=directory entry/children=]. + 1. If creating |child| in the underlying file system throws an exception, + [=/reject=] |result| with that exception and abort these steps. + + Issue(11): Better specify what possible exceptions this could throw. + 1. [=/Resolve=] |result| with the result of + creating a child `FileSystemFileHandle` with |locator| and + |child|'s [=file system entry/name=] in |realm|. 1. Return |result|. @@ -863,45 +917,57 @@ The getDirectoryHandle(|name|, |option 1. Let |result| be [=a new promise=]. 1. Let |realm| be [=this=]'s [=relevant Realm=]. 1. Let |locator| be [=this=]'s [=FileSystemHandle/locator=]. -1. Run these steps [=in parallel=]: - 1. If |name| is not a [=valid file name=], [=/reject=] |result| with a {{TypeError}} and abort. +1. Let |global| be [=this=]'s [=relevant global object=]. +1. [=Enqueue the following steps=] to the [=file system queue=]: + 1. If |name| is not a [=valid file name=], [=queue a storage task=] with + |global| to [=/reject=] |result| with a {{TypeError}} and + abort these steps. 1. Let |entry| be the result of [=locating an entry=] given |locator|. 1. If |options|.{{FileSystemGetDirectoryOptions/create}} is true: - 1. Let |access| be the result of running |entry|'s + 1. Let |accessResult| be the result of running |entry|'s [=file system entry/request access=] given "`readwrite`". - If that throws an exception, [=/reject=] |result| with that exception and abort. 1. Otherwise: - 1. Let |access| be the result of running |entry|'s + 1. Let |accessResult| be the result of running |entry|'s [=file system entry/query access=] given "`read`". - 1. If |access| is not "{{PermissionState/granted}}", - [=/reject=] |result| with a "{{NotAllowedError}}" {{DOMException}} and abort. - - 1. If |entry| is `null`, [=/reject=] |result| with a - "{{NotFoundError}}" {{DOMException}} and abort. - 1. [=Assert=]: |entry| is a [=directory entry=]. - - 1. [=set/For each=] |child| of |entry|'s [=directory entry/children=]: - 1. If |child|'s [=file system entry/name=] equals |name|: - 1. If |child| is a [=file entry=]: - 1. [=/Reject=] |result| with a "{{TypeMismatchError}}" {{DOMException}} and abort. - 1. [=/Resolve=] |result| with the result of - creating a child `FileSystemDirectoryHandle` with - |locator| and |child|'s [=file system entry/name=] in |realm| and abort. - 1. If |options|.{{FileSystemGetFileOptions/create}} is false: - 1. [=/Reject=] |result| with a "{{NotFoundError}}" {{DOMException}} and abort. - 1. Let |child| be a new [=directory entry=] whose [=query access=] and [=request access=] - algorithms are those of |entry|. - 1. Set |child|'s [=file system entry/name=] to |name|. - 1. Set |child|'s [=directory entry/children=] to an empty [=/set=]. - 1. [=set/Append=] |child| to |entry|'s [=directory entry/children=]. - 1. If creating |child| in the underlying file system throws an exception, - [=/reject=] |result| with that exception and abort. - - Issue(11): Better specify what possible exceptions this could throw. - 1. [=/Resolve=] |result| with the result of - creating a child `FileSystemDirectoryHandle` with - |locator| and |child|'s [=file system entry/name=] in |realm|. + + 1. [=Queue a storage task=] with |global| to run these steps: + 1. If |accessResult|'s [=file system access result/permission state=] + is not "{{PermissionState/granted}}": + 1. Set |accessErrorName| to |accessResult|'s + [=file system access result/error name=] if it is not + « the empty string »; otherwise "{{NotAllowedError}}". + 1. [=/Reject=] |result| with an |accessErrorName| {{DOMException}} and + abort these steps. + + 1. If |entry| is `null`, [=/reject=] |result| with a + "{{NotFoundError}}" {{DOMException}} and abort these steps. + 1. [=Assert=]: |entry| is a [=directory entry=]. + + 1. [=set/For each=] |child| of |entry|'s [=directory entry/children=]: + 1. If |child|'s [=file system entry/name=] equals |name|: + 1. If |child| is a [=file entry=]: + 1. [=/Reject=] |result| with a + "{{TypeMismatchError}}" {{DOMException}} and abort these steps. + 1. [=/Resolve=] |result| with the result of + creating a child `FileSystemDirectoryHandle` with + |locator| and |child|'s [=file system entry/name=] in |realm| and + abort these steps. + 1. If |options|.{{FileSystemGetFileOptions/create}} is false: + 1. [=/Reject=] |result| with a "{{NotFoundError}}" {{DOMException}} and + abort these steps. + 1. Let |child| be a new [=directory entry=] whose [=query access=] and + [=request access=] algorithms are those of |entry|. + 1. Set |child|'s [=file system entry/name=] to |name|. + 1. Set |child|'s [=directory entry/children=] to an empty [=/set=]. + 1. [=set/Append=] |child| to |entry|'s [=directory entry/children=]. + 1. If creating |child| in the underlying file system throws an exception, + [=/reject=] |result| with that exception and abort these steps. + + Issue(11): Better specify what possible exceptions this could throw. + 1. [=/Resolve=] |result| with the result of + creating a child `FileSystemDirectoryHandle` with + |locator| and |child|'s [=file system entry/name=] in |realm|. 1. Return |result|. @@ -931,36 +997,50 @@ The removeEntry(|name|, |options|) @@ -1085,17 +1165,19 @@ given a [=file entry=] |file| in a [=/Realm=] |realm|: 1. Let |closeAlgorithm| be these steps: 1. Let |closeResult| be [=a new promise=]. 1. [=Enqueue the following steps=] to the [=file system queue=]: - 1. Let |access| be the result of running |file|'s + 1. Let |accessResult| be the result of running |file|'s [=file system entry/query access=] given "`readwrite`". - 1. If |access| is not "{{PermissionState/granted}}": - 1. Set |requestAccessError| to |access| if |access| is an - [=exception/error name=]; otherwise, "{{NotAllowedError}}". - 1. [=Queue a storage task=] with |file|'s [=relevant global object=] to - [=/reject=] |closeResult| with a - |requestAccessError| {{DOMException}} and abort these steps. 1. [=Queue a storage task=] with |file|'s [=relevant global object=] to run these steps: + 1. If |accessResult|'s [=file system access result/permission state=] + is not "{{PermissionState/granted}}": + 1. Set |accessErrorName| to |accessResult|'s + [=file system access result/error name=] if it is not + « the empty string »; otherwise "{{NotAllowedError}}". + 1. [=/Reject=] |closeResult| with an + |accessErrorName| {{DOMException}} and abort these steps. + 1. Run [=implementation-defined=] malware scans and safe browsing checks. If these checks fail, [=/reject=] |closeResult| with an "{{AbortError}}" {{DOMException}} and abort these steps. @@ -1107,14 +1189,16 @@ given a [=file entry=] |file| in a [=/Realm=] |realm|: 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|'s [=FileSystemWritableFileStream/[[file]]=]. - 1. [=/Resolve=] |closeResult| with `undefined`. + 1. [=Enqueue the following steps=] to the [=file system queue=]: + 1. [=file entry/lock/release|Release the lock=] on + |stream|'s [=FileSystemWritableFileStream/[[file]]=]. + 1. [=Queue a storage task=] with |file|'s [=relevant global object=] + to [=/resolve=] |closeResult| with `undefined`. 1. Return |closeResult|. 1. Let |abortAlgorithm| be these steps: 1. [=enqueue steps|Enqueue this step=] to the [=file system queue=]: - 1. [=file entry/lock/release|release the lock=] on + 1. [=file entry/lock/release|Release the lock=] on |stream|'s [=FileSystemWritableFileStream/[[file]]=]. 1. Let |highWaterMark| be 1. 1. Let |sizeAlgorithm| be an algorithm that returns `1`. @@ -1136,18 +1220,28 @@ runs these steps: 1. Let |input| be the result of [=converted to an IDL value|converting=] |chunk| to a {{FileSystemWriteChunkType}}. If this throws an exception, then return [=a promise rejected with=] that exception. 1. Let |p| be [=a new promise=]. -1. Run these steps [=in parallel=]: - 1. Let |access| be the result of running |stream|'s [=FileSystemWritableFileStream/[[file]]=]'s - [=file system entry/query access=] given "`readwrite`". - 1. If |access| is not "{{PermissionState/granted}}", - [=/reject=] |p| with a "{{NotAllowedError}}" {{DOMException}} and abort. - 1. Let |command| be |input|.{{WriteParams/type}} if |input| is a {{WriteParams}}, - and {{WriteCommandType/"write"}} otherwise. - 1. If |command| is {{WriteCommandType/"write"}}: +1. [=Enqueue the following steps=] to the [=file system queue=]: + 1. Let |accessResult| be the result of running + |stream|'s [=FileSystemWritableFileStream/[[file]]=]'s + [=file system entry/query access=] given "`readwrite`". + + 1. [=Queue a storage task=] with |stream|'s [=relevant global object=] to + run these steps: + 1. If |accessResult|'s [=file system access result/permission state=] + is not "{{PermissionState/granted}}": + 1. Set |accessErrorName| to |accessResult|'s + [=file system access result/error name=] if it is not + « the empty string »; otherwise "{{NotAllowedError}}". + 1. [=/Reject=] |p| with an |accessErrorName| {{DOMException}} and + abort these steps. + + 1. Let |command| be |input|.{{WriteParams/type}} if |input| is a {{WriteParams}}, + and {{WriteCommandType/"write"}} otherwise. + 1. If |command| is {{WriteCommandType/"write"}}: 1. Let |data| be |input|.{{WriteParams/data}} if |input| is a {{WriteParams}}, and |input| otherwise. 1. If |data| is `undefined`, - [=/reject=] |p| with a {{TypeError}} and abort. + [=/reject=] |p| with a {{TypeError}} and abort these steps. 1. Let |writePosition| be |stream|.[=[[seekOffset]]=]. 1. If |input| is a {{WriteParams}} and |input|.{{WriteParams/position}} is not `undefined`, set |writePosition| to |input|.{{WriteParams/position}}. @@ -1157,7 +1251,8 @@ runs these steps: 1. Otherwise, if |data| is a {{Blob}}: 1. Let |dataBytes| be the result of performing the read operation on |data|. - If this throws an exception, [=/reject=] |p| with that exception and abort. + If this throws an exception, [=/reject=] |p| with that exception + and abort these steps. 1. Otherwise: 1. [=Assert=]: |data| is a {{USVString}}. 1. Let |dataBytes| be the result of [=UTF-8 encoding=] |data|. @@ -1176,7 +1271,8 @@ runs these steps: |oldSize| - (|writePosition| + |data|.[=byte sequence/length=]) bytes of |stream|.[=[[buffer]]=]. 1. Set |stream|.[=[[buffer]]=] to the concatenation of |head|, |data| and |tail|. 1. If the operations modifying |stream|.[=[[buffer]]=] in the previous steps failed - due to exceeding the [=storage quota=], [=/reject=] |p| with a "{{QuotaExceededError}}" {{DOMException}} and abort, + due to exceeding the [=storage quota=], [=/reject=] |p| with a + "{{QuotaExceededError}}" {{DOMException}} and abort these steps, leaving |stream|.[=[[buffer]]=] unmodified. Note: [=Storage quota=] only applies to files stored in the [=origin private file system=]. @@ -1184,22 +1280,22 @@ runs these steps: to runs out of disk space. 1. Set |stream|.[=[[seekOffset]]=] to |writePosition| + |data|.[=byte sequence/length=]. 1. [=/Resolve=] |p|. - 1. Otherwise, if |command| is {{WriteCommandType/"seek"}}: + 1. Otherwise, if |command| is {{WriteCommandType/"seek"}}: 1. If |chunk|.{{WriteParams/position}} is `undefined`, - [=/reject=] |p| with a {{TypeError}} and abort. + [=/reject=] |p| with a {{TypeError}} and abort these steps. 1. Set |stream|.[=[[seekOffset]]=] to |chunk|.{{WriteParams/position}}. 1. [=/Resolve=] |p|. - 1. Otherwise, if |command| is {{WriteCommandType/"truncate"}}: + 1. Otherwise, if |command| is {{WriteCommandType/"truncate"}}: 1. If |chunk|.{{WriteParams/size}} is `undefined`, - [=/reject=] |p| with a {{TypeError}} and abort. + [=/reject=] |p| with a {{TypeError}} and abort these steps. 1. Let |newSize| be |chunk|.{{WriteParams/size}}. 1. Let |oldSize| be |stream|.[=[[buffer]]=]'s [=byte sequence/length=]. 1. If |newSize| is larger than |oldSize|: 1. Set |stream|.[=[[buffer]]=] to a [=byte sequence=] formed by concating |stream|.[=[[buffer]]=] with a [=byte sequence=] containing |newSize|-|oldSize| `0x00` bytes. 1. If the operation in the previous step failed due to exceeding the [=storage quota=], - [=/reject=] |p| with a "{{QuotaExceededError}}" {{DOMException}} and abort, - leaving |stream|.[=[[buffer]]=] unmodified. + [=/reject=] |p| with a "{{QuotaExceededError}}" {{DOMException}} and + abort these steps, 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 @@ -1603,8 +1699,12 @@ The getDirectory() method steps are: return [=a promise rejected with=] a "{{SecurityError}}" {{DOMException}}. 1. If |map|["root"] does not [=map/exist=]: - 1. Let |dir| be a new [=directory entry=] whose [=query access=] and [=request access=] algorithms - always return "{{PermissionState/granted}}". + 1. Let |dir| be a new [=directory entry=] whose [=query access=] and + [=request access=] algorithms always return + a [=/file system access result=] + with a [=file system access result/permission state=] + of "{{PermissionState/granted}}" and + with a [=file system access result/error name=] of « the empty string ». 1. Set |dir|'s [=file system entry/name=] to the empty string. 1. Set |dir|'s [=directory entry/children=] to an empty [=/set=]. 1. Set |map|["root"] to |dir|. From c20e4bdde8d2b81fc09222175ebc8aab5aa70d03 Mon Sep 17 00:00:00 2001 From: Austin Sullivan Date: Fri, 2 Jun 2023 05:04:27 +0000 Subject: [PATCH 12/15] update warning --- index.bs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/index.bs b/index.bs index 6197312..96d824a 100644 --- a/index.bs +++ b/index.bs @@ -95,9 +95,10 @@ It has the following [=struct/items=]: [=permission request algorithm=] may throw, [=/file system entry=]'s [=file system entry/query access=] and [=file system entry/request access=] algorithms must run [=in parallel=] on the [=file system queue=] and are -therefore not allowed to throw. Instead, the caller is expected to [=/reject=] -as appropriate should these algorithms return an -[=file system access result/error name=] other than « the empty string ». +therefore not allowed to throw. Instead, the caller is expected to +[=queue a storage task=] to [=/reject=], as appropriate, +should these algorithms return an [=file system access result/error name=] +other than « the empty string ». Note: Implementations that only implement this specification and not dependent specifications do not need to bother implementing [=/file system entry=]'s From 5a7d095f2ef95ebec02e5ba70b2f2d9d3f149423 Mon Sep 17 00:00:00 2001 From: Austin Sullivan Date: Fri, 2 Jun 2023 14:50:21 +0000 Subject: [PATCH 13/15] assume error name is returned if not granted --- index.bs | 116 ++++++++++++++++++++----------------------------------- 1 file changed, 41 insertions(+), 75 deletions(-) diff --git a/index.bs b/index.bs index 96d824a..98ec3f1 100644 --- a/index.bs +++ b/index.bs @@ -67,7 +67,7 @@ algorithm, which takes "`read`" or "`readwrite`" mode and returns a [=/file system access result=]. Unless specified otherwise it returns a [=/file system access result=] with a [=file system access result/permission state=] of "{{PermissionState/denied}}" -and with a [=file system access result/error name=] of « the empty string ». +and with a [=file system access result/error name=] of the empty string. Each [=/file system entry=] has an associated request access @@ -75,7 +75,7 @@ algorithm, which takes "`read`" or "`readwrite`" mode and returns a [=/file system access result=]. Unless specified otherwise it returns a [=/file system access result=] with a [=file system access result/permission state=] of "{{PermissionState/denied}}" -and with a [=file system access result/error name=] of « the empty string ». +and with a [=file system access result/error name=] of the empty string. A file system access result is a [=struct=] encapsulating the result of [=file system entry/query access|querying=] or @@ -85,10 +85,13 @@ It has the following [=struct/items=]: : permission state :: A {{PermissionState}} : error name -:: A [=string=] which must be either « the empty string » or an +:: A [=string=] which must be either the empty string or an [=exception/error name=] listed in the [=error names table=]. - If [=file system access result/permission state=] is - "{{PermissionState/granted}}" this must be « the empty string » + Iff [=file system access result/permission state=] is + "{{PermissionState/granted}}" this will be the empty string. + It is expected that in most cases when + [=file system access result/permission state=] is not + "{{PermissionState/granted}}", this will be "{{NotAllowedError}}".

Dependent specifications may consider this API a [=powerful feature=]. However, unlike other [=powerful features=] whose @@ -98,7 +101,7 @@ algorithms must run [=in parallel=] on the [=file system queue=] and are therefore not allowed to throw. Instead, the caller is expected to [=queue a storage task=] to [=/reject=], as appropriate, should these algorithms return an [=file system access result/error name=] -other than « the empty string ». +other than the empty string. Note: Implementations that only implement this specification and not dependent specifications do not need to bother implementing [=/file system entry=]'s @@ -477,12 +480,9 @@ The getFile() method steps are: 1. [=Queue a storage task=] with |global| to run these steps: 1. If |accessResult|'s [=file system access result/permission state=] - is not "{{PermissionState/granted}}": - 1. Set |accessErrorName| to |accessResult|'s - [=file system access result/error name=] if it is not - « the empty string »; otherwise "{{NotAllowedError}}". - 1. [=/Reject=] |result| with an |accessErrorName| {{DOMException}} and - abort these steps. + is not "{{PermissionState/granted}}", [=/reject=] |result| with a + {{DOMException}} of |accessResult|'s + [=file system access result/error name=] and abort these steps. 1. If |entry| is null, [=/reject=] |result| with a "{{NotFoundError}}" {{DOMException}} and abort these steps. @@ -550,12 +550,10 @@ The createWritable(|options|) method 1. Let |accessResult| be the result of running |entry|'s [=file system entry/request access=] given "`readwrite`". 1. If |accessResult|'s [=file system access result/permission state=] - is not "{{PermissionState/granted}}": - 1. Set |accessErrorName| to |accessResult|'s - [=file system access result/error name=] if it is not - « the empty string »; otherwise "{{NotAllowedError}}". - 1. [=Queue a storage task=] with |global| to [=/reject=] |result| with an - |accessErrorName| {{DOMException}} and abort these steps. + is not "{{PermissionState/granted}}", [=queue a storage task=] with + |global| to [=/reject=] |result| with a {{DOMException}} of + |accessResult|'s [=file system access result/error name=] and + abort these steps. 1. If |entry| is `null`, [=queue a storage task=] with |global| to [=/reject=] |result| with a "{{NotFoundError}}" {{DOMException}} and abort these steps. @@ -612,12 +610,10 @@ The createSyncAccessHandle() method s 1. Let |accessResult| be the result of running |entry|'s [=file system entry/request access=] given "`readwrite`". 1. If |accessResult|'s [=file system access result/permission state=] - is not "{{PermissionState/granted}}": - 1. Set |accessErrorName| to |accessResult|'s - [=file system access result/error name=] if it is not - « the empty string »; otherwise "{{NotAllowedError}}". - 1. [=Queue a storage task=] with |global| to [=/reject=] |result| with an - |accessErrorName| {{DOMException}} and abort these steps. + is not "{{PermissionState/granted}}", [=queue a storage task=] with + |global| to [=/reject=] |result| with a {{DOMException}} of + |accessResult|'s [=file system access result/error name=] and + abort these steps. 1. If |entry| is `null`, [=queue a storage task=] with |global| to [=/reject=] |result| with a "{{NotFoundError}}" {{DOMException}} and abort these steps. @@ -727,21 +723,10 @@ Issue(15): In the future we might want to add arguments to the async iterable de support for example recursive iteration.

-The [=asynchronous iterator initialization steps=] for a {{FileSystemDirectoryHandle}} |handle| +The [=asynchronous iterator initialization steps=] for a +{{FileSystemDirectoryHandle}} handle and its async iterator |iterator| are: -1. [=Enqueue the following steps=] to the [=file system queue=]: - 1. Let |entry| be the result of [=locating an entry=] - given |handle|'s [=FileSystemHandle/locator=]. - 1. Let |accessResult| be the result of running |entry|'s - [=file system entry/query access=] given "`read`". - 1. If |accessResult|'s [=file system access result/permission state=] - is not "{{PermissionState/granted}}": - 1. Set |accessErrorName| to |accessResult|'s - [=file system access result/error name=] if it is not - « the empty string »; otherwise "{{NotAllowedError}}". - 1. [=Throw=] an |accessErrorName| {{DOMException}}. - 1. Set |iterator|'s past results to an empty [=/set=].
@@ -760,12 +745,9 @@ and its async iterator |iterator|: 1. [=Queue a storage task=] with |handle|'s [=relevant global object=] to run these steps: 1. If |accessResult|'s [=file system access result/permission state=] - is not "{{PermissionState/granted}}": - 1. Set |accessErrorName| to |accessResult|'s - [=file system access result/error name=] if it is not - « the empty string »; otherwise "{{NotAllowedError}}". - 1. [=/Reject=] |promise| with an |accessErrorName| {{DOMException}} - and return |promise|. + is not "{{PermissionState/granted}}", [=/reject=] |promise| with a + {{DOMException}} of |accessResult|'s + [=file system access result/error name=] and abort these steps.: 1. If |directory| is `null`, [=/reject=] |result| with a "{{NotFoundError}}" {{DOMException}} and abort these steps. @@ -849,12 +831,9 @@ The getFileHandle(|name|, |options|)getDirectoryHandle(|name|, |option 1. [=Queue a storage task=] with |global| to run these steps: 1. If |accessResult|'s [=file system access result/permission state=] - is not "{{PermissionState/granted}}": - 1. Set |accessErrorName| to |accessResult|'s - [=file system access result/error name=] if it is not - « the empty string »; otherwise "{{NotAllowedError}}". - 1. [=/Reject=] |result| with an |accessErrorName| {{DOMException}} and - abort these steps. + is not "{{PermissionState/granted}}", [=/reject=] |result| with a + {{DOMException}} of |accessResult|'s + [=file system access result/error name=] and abort these steps. 1. If |entry| is `null`, [=/reject=] |result| with a "{{NotFoundError}}" {{DOMException}} and abort these steps. @@ -1010,12 +986,9 @@ The removeEntry(|name|, |options|)getDirectory() method steps are: a [=/file system access result=] with a [=file system access result/permission state=] of "{{PermissionState/granted}}" and - with a [=file system access result/error name=] of « the empty string ». + with an [=file system access result/error name=] of the empty string. 1. Set |dir|'s [=file system entry/name=] to the empty string. 1. Set |dir|'s [=directory entry/children=] to an empty [=/set=]. 1. Set |map|["root"] to |dir|. From a189311bc1295cbac798de56896ba96f35747b43 Mon Sep 17 00:00:00 2001 From: Austin Sullivan Date: Tue, 13 Jun 2023 12:21:40 -0400 Subject: [PATCH 14/15] address nits --- index.bs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/index.bs b/index.bs index 98ec3f1..29787b4 100644 --- a/index.bs +++ b/index.bs @@ -67,7 +67,7 @@ algorithm, which takes "`read`" or "`readwrite`" mode and returns a [=/file system access result=]. Unless specified otherwise it returns a [=/file system access result=] with a [=file system access result/permission state=] of "{{PermissionState/denied}}" -and with a [=file system access result/error name=] of the empty string. +and with an [=file system access result/error name=] of the empty string. Each [=/file system entry=] has an associated request access @@ -75,7 +75,7 @@ algorithm, which takes "`read`" or "`readwrite`" mode and returns a [=/file system access result=]. Unless specified otherwise it returns a [=/file system access result=] with a [=file system access result/permission state=] of "{{PermissionState/denied}}" -and with a [=file system access result/error name=] of the empty string. +and with an [=file system access result/error name=] of the empty string. A file system access result is a [=struct=] encapsulating the result of [=file system entry/query access|querying=] or @@ -85,13 +85,13 @@ It has the following [=struct/items=]: : permission state :: A {{PermissionState}} : error name -:: A [=string=] which must be either the empty string or an +:: A [=string=] which must be the empty string if + [=file system access result/permission state=] is + "{{PermissionState/granted}}"; otherwise an [=exception/error name=] listed in the [=error names table=]. - Iff [=file system access result/permission state=] is - "{{PermissionState/granted}}" this will be the empty string. It is expected that in most cases when [=file system access result/permission state=] is not - "{{PermissionState/granted}}", this will be "{{NotAllowedError}}". + "{{PermissionState/granted}}", this should be "{{NotAllowedError}}".

Dependent specifications may consider this API a [=powerful feature=]. However, unlike other [=powerful features=] whose From ba5a29532f622dcdb789212cb8886fb4f626c546 Mon Sep 17 00:00:00 2001 From: Austin Sullivan Date: Tue, 13 Jun 2023 09:31:47 -0700 Subject: [PATCH 15/15] update error name ref prompted by https://github.com/whatwg/webidl/commit/fcbaa0921a03ca1dafdaf5300492e1b377f694f3 --- index.bs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.bs b/index.bs index 29787b4..cc5dfa8 100644 --- a/index.bs +++ b/index.bs @@ -88,7 +88,7 @@ It has the following [=struct/items=]: :: A [=string=] which must be the empty string if [=file system access result/permission state=] is "{{PermissionState/granted}}"; otherwise an - [=exception/error name=] listed in the [=error names table=]. + [=DOMException/name=] listed in the [=`DOMException` names table=]. It is expected that in most cases when [=file system access result/permission state=] is not "{{PermissionState/granted}}", this should be "{{NotAllowedError}}".