diff --git a/index.bs b/index.bs index 737bc5b..cc5dfa8 100644 --- a/index.bs +++ b/index.bs @@ -61,16 +61,51 @@ 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 a [=/file system access result=]. +Unless specified otherwise it returns a [=/file system access result=] with a +[=file system access result/permission state=] of "{{PermissionState/denied}}" +and with an [=file system access result/error name=] of the empty string. + +Each [=/file system entry=] has an associated +request access +algorithm, which takes "`read`" or "`readwrite`" mode and +returns a [=/file system access result=]. +Unless specified otherwise it returns a [=/file system access result=] with a +[=file system access result/permission state=] of "{{PermissionState/denied}}" +and with an [=file system access result/error name=] of the empty string. + +A file system access result is a [=struct=] encapsulating the +result of [=file system entry/query access|querying=] or +[=file system entry/request access|requesting=] access to the file system. +It has the following [=struct/items=]: + +: permission state +:: A {{PermissionState}} +: error name +:: A [=string=] which must be the empty string if + [=file system access result/permission state=] is + "{{PermissionState/granted}}"; otherwise an + [=DOMException/name=] listed in the [=`DOMException` names table=]. + It is expected that in most cases when + [=file system access result/permission state=] is not + "{{PermissionState/granted}}", this should be "{{NotAllowedError}}". + +

Dependent specifications may consider this API a +[=powerful feature=]. However, unlike other [=powerful features=] whose +[=permission request algorithm=] may throw, [=/file system entry=]'s +[=file system entry/query access=] and [=file system entry/request access=] +algorithms must run [=in parallel=] on the [=file system queue=] and are +therefore not allowed to throw. Instead, the caller is expected to +[=queue a storage task=] to [=/reject=], as appropriate, +should these algorithms return an [=file system access result/error name=] +other than the empty string. + +Note: Implementations that only implement this specification and not dependent +specifications do not need to bother implementing [=/file system entry=]'s +[=file system entry/query access=] and [=file system entry/request access=]. Issue(101): Make access check algorithms associated with a FileSystemHandle. @@ -94,38 +129,47 @@ 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 queue which is the +result of [=starting a new parallel queue=]. This queue is to be used for all +file sytem operations. +

-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|: 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. 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 true. + 1. Return "`success`". 1. Otherwise, if |lock| is "`taken-shared`": - 1. Increase |count| by one. - 1. Return true. -1. Return false. + 1. Increase |count| by 1. + 1. Return "`success`". +1. Return "`failure`". + +Note: These steps have to be run on the [=file system queue=].
-To release a [=file entry/lock=] on a given [=file entry=] |file|, -run these steps: +To release a [=file entry/lock=] on a given +[=file entry=] |file|: 1. Let |lock| be the |file|'s associated [=file entry/lock=]. 1. Let |count| be the |file|'s [=file entry/shared lock count=]. 1. If |lock| is "`taken-shared`": - 1. Decrease |count| by one. + 1. Decrease |count| by 1. 1. If |count| is 0, set |lock| to "`open`". 1. Otherwise, set |lock| to "`open`". +Note: These steps have to be run on the [=file system queue=]. +
Note: Locks help prevent concurrent modifications to a file. A {{FileSystemWritableFileStream}} @@ -135,7 +179,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. @@ -146,7 +190,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. @@ -161,22 +205,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=] @@ -349,7 +393,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. @@ -428,27 +472,32 @@ 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}}", [=/reject=] |result| with a + {{DOMException}} of |accessResult|'s + [=file system access result/error name=] and abort these steps. + + 1. If |entry| is null, [=/reject=] |result| with a + "{{NotFoundError}}" {{DOMException}} and abort these steps. + 1. [=Assert=]: |entry| is a [=file entry=]. + + 1. Let |f| be a new {{File}}. + 1. Set |f|'s snapshot state to the current state of |entry|. + 1. Set |f|'s underlying byte sequence to a copy of |entry|'s [=binary data=]. + 1. Set |f|.{{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|. @@ -494,25 +543,35 @@ 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. 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`". - 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. If |accessResult|'s [=file system access result/permission state=] + is not "{{PermissionState/granted}}", [=queue a storage task=] with + |global| to [=/reject=] |result| with a {{DOMException}} of + |accessResult|'s [=file system access result/error name=] and + abort these steps. + + 1. If |entry| is `null`, [=queue a storage task=] with |global| to [=/reject=] + |result| with a "{{NotFoundError}}" {{DOMException}} and abort these steps. 1. [=Assert=]: |entry| is a [=file entry=]. - 1. Let |lockResult| be the result of [=file entry/lock/take|taking a lock=] with "`shared`" on |entry|. - 1. 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 |lockResult| be the result of [=file entry/lock/take|taking a lock=] + with "`shared`" on |entry|. + + 1. [=Queue a storage task=] with |global| to run these steps: + 1. If |lockResult| is "`failure`", [=/reject=] |result| with a + "{{NoModificationAllowedError}}" {{DOMException}} and abort these steps. + + 1. Let |stream| be the result of creating a new `FileSystemWritableFileStream` + for |entry| in |realm|. + 1. If |options|'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|. @@ -544,25 +603,38 @@ 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. 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`". - 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. If |accessResult|'s [=file system access result/permission state=] + is not "{{PermissionState/granted}}", [=queue a storage task=] with + |global| to [=/reject=] |result| with a {{DOMException}} of + |accessResult|'s [=file system access result/error name=] and + abort these steps. + + 1. If |entry| is `null`, [=queue a storage task=] with |global| to [=/reject=] + |result| with a "{{NotFoundError}}" {{DOMException}} and abort these steps. 1. [=Assert=]: |entry| is a [=file entry=]. - 1. 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 |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. 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. [=Queue a storage task=] with |global| to run these steps: + 1. If |lockResult| is "`failure`", [=/reject=] |result| with a + "{{NoModificationAllowedError}}" {{DOMException}} and abort these steps. + + 1. Let |handle| be the result of creating a new `FileSystemSyncAccessHandle` + for |entry| in |realm|. + 1. [=/Resolve=] |result| with |handle|. + 1. Return |result|. @@ -651,17 +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. 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. Set |iterator|'s past results to an empty [=/set=].
@@ -671,43 +736,51 @@ 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}}", [=/reject=] |promise| with a + {{DOMException}} of |accessResult|'s + [=file system access result/error name=] and abort these steps.: + + 1. If |directory| is `null`, [=/reject=] |result| with a + "{{NotFoundError}}" {{DOMException}} and abort these steps. + 1. [=Assert=]: |directory| is a [=directory entry=]. + + 1. Let |child| be a [=/file system entry=] in + |directory|'s [=directory entry/children=], such that + |child|'s [=file system entry/name=] is not contained in + |iterator|'s [=past results=], or `null` if no such entry exists. + + Note: This is intentionally very vague about the iteration order. + Different platforms and file systems provide different guarantees about + iteration order, and we want it to be possible to efficiently implement + this on all platforms. As such no guarantees are given about the exact + order in which elements are returned. + + 1. If |child| is `null`, [=/resolve=] |promise| with `undefined` and + abort these steps. + + 1. [=set/Append=] |child|'s [=file system entry/name=] to + |iterator|'s [=past results=]. + 1. If |child| is a [=file entry=]: + 1. Let |result| be the result of + creating a child `FileSystemFileHandle` with + |handle|'s [=FileSystemHandle/locator=] and + |child|'s [=file system entry/name=] in |handle|'s [=relevant Realm=]. + 1. Otherwise: + 1. Let |result| be the result of + creating a child `FileSystemDirectoryHandle` with + |handle|'s [=FileSystemHandle/locator=] and + |child|'s [=file system entry/name=] in |handle|'s [=relevant Realm=]. + 1. [=/Resolve=] |promise| with + (|child|'s [=file system entry/name=], |result|). 1. Return |promise|. @@ -742,47 +815,55 @@ 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}}", [=/reject=] |result| with a + {{DOMException}} of |accessResult|'s + [=file system access result/error name=] and abort these steps. + + 1. If |entry| is `null`, [=/reject=] |result| with a + "{{NotFoundError}}" {{DOMException}} and abort these steps. + 1. [=Assert=]: |entry| is a [=directory entry=]. + + 1. [=set/For each=] |child| of |entry|'s [=directory entry/children=]: + 1. If |child|'s [=file system entry/name=] equals |name|: + 1. If |child| is a [=directory entry=]: + 1. [=/Reject=] |result| with a + "{{TypeMismatchError}}" {{DOMException}} and abort these steps. + 1. [=/Resolve=] |result| with the result of + creating a child `FileSystemFileHandle` with |locator| and + |child|'s [=file system entry/name=] in |realm| and + abort these steps. + 1. If |options|.{{FileSystemGetFileOptions/create}} is false: + 1. [=/Reject=] |result| with a "{{NotFoundError}}" {{DOMException}} and + abort these steps. + 1. Let |child| be a new [=file entry=] whose [=query access=] and + [=request access=] algorithms are those of |entry|. + 1. Set |child|'s [=file system entry/name=] to |name|. + 1. Set |child|'s [=binary data=] to an empty [=byte sequence=]. + 1. Set |child|'s [=modification timestamp=] to the current time. + 1. [=set/Append=] |child| to |entry|'s [=directory entry/children=]. + 1. If creating |child| in the underlying file system throws an exception, + [=/reject=] |result| with that exception and abort these steps. + + Issue(11): Better specify what possible exceptions this could throw. + 1. [=/Resolve=] |result| with the result of + creating a child `FileSystemFileHandle` with |locator| and + |child|'s [=file system entry/name=] in |realm|. 1. Return |result|. @@ -816,45 +897,54 @@ 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}}", [=/reject=] |result| with a + {{DOMException}} of |accessResult|'s + [=file system access result/error name=] and abort these steps. + + 1. If |entry| is `null`, [=/reject=] |result| with a + "{{NotFoundError}}" {{DOMException}} and abort these steps. + 1. [=Assert=]: |entry| is a [=directory entry=]. + + 1. [=set/For each=] |child| of |entry|'s [=directory entry/children=]: + 1. If |child|'s [=file system entry/name=] equals |name|: + 1. If |child| is a [=file entry=]: + 1. [=/Reject=] |result| with a + "{{TypeMismatchError}}" {{DOMException}} and abort these steps. + 1. [=/Resolve=] |result| with the result of + creating a child `FileSystemDirectoryHandle` with + |locator| and |child|'s [=file system entry/name=] in |realm| and + abort these steps. + 1. If |options|.{{FileSystemGetFileOptions/create}} is false: + 1. [=/Reject=] |result| with a "{{NotFoundError}}" {{DOMException}} and + abort these steps. + 1. Let |child| be a new [=directory entry=] whose [=query access=] and + [=request access=] algorithms are those of |entry|. + 1. Set |child|'s [=file system entry/name=] to |name|. + 1. Set |child|'s [=directory entry/children=] to an empty [=/set=]. + 1. [=set/Append=] |child| to |entry|'s [=directory entry/children=]. + 1. If creating |child| in the underlying file system throws an exception, + [=/reject=] |result| with that exception and abort these steps. + + Issue(11): Better specify what possible exceptions this could throw. + 1. [=/Resolve=] |result| with the result of + creating a child `FileSystemDirectoryHandle` with + |locator| and |child|'s [=file system entry/name=] in |realm|. 1. Return |result|. @@ -884,36 +974,47 @@ The removeEntry(|name|, |options|) @@ -1032,29 +1133,43 @@ 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. If |access| is not "{{PermissionState/granted}}", - [=/reject=] |closeResult| with a "{{NotAllowedError}}" {{DOMException}} - and abort. - 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. - - 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. [=Enqueue the following steps=] to the [=file system queue=]: + 1. Let |accessResult| be the result of running |file|'s + [=file system entry/query access=] given "`readwrite`". + + 1. [=Queue a storage task=] with |file|'s [=relevant global object=] + to run these steps: + 1. If |accessResult|'s [=file system access result/permission state=] + is not "{{PermissionState/granted}}", [=/reject=] |closeResult| + with a {{DOMException}} of |accessResult|'s + [=file system access result/error name=] and abort these steps. + 1. Run [=implementation-defined=] malware scans and safe browsing checks. + If these checks fail, [=/reject=] |closeResult| with an + "{{AbortError}}" {{DOMException}} and abort these steps. + 1. Set |stream|'s [=FileSystemWritableFileStream/[[file]]=]'s + [=file entry/binary data=] to |stream|'s [=[[buffer]]=]. + If that throws an exception, [=/reject=] |closeResult| with that + exception and abort these steps. + + Note: It is expected that this atomically updates the contents of the + file on disk being written to. + + 1. [=Enqueue the following steps=] to the [=file system queue=]: + 1. [=file entry/lock/release|Release the lock=] on + |stream|'s [=FileSystemWritableFileStream/[[file]]=]. + 1. [=Queue a storage task=] with |file|'s [=relevant global object=] + to [=/resolve=] |closeResult| with `undefined`. + 1. Return |closeResult|. -1. Let |abortAlgorithm| be 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 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|. @@ -1115,7 +1238,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=]. @@ -1123,22 +1247,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 @@ -1490,8 +1614,12 @@ The close() method steps are: 1. If [=this=]'s [=[[state]]=] is "`closed`", return. 1. Set [=this=]'s [=[[state]]=] to "`closed`". -1. [=file entry/lock/release|Release the lock=] on - [=this=]'s [=FileSystemSyncAccessHandle/[[file]]=]. +1. Set |lockReleased| to false. +1. Let |file| be [=this=]'s [=FileSystemSyncAccessHandle/[[file]]=]. +1. [=Enqueue the following steps=] to the [=file system queue=]: + 1. [=file entry/lock/release|Release the lock=] on |file|. + 1. Set |lockReleased| to true. +1. [=Pause=] until |lockReleased| is true. Note: This method does not guarantee that all file modifications will be immediately reflected in the underlying storage device. Call the @@ -1538,8 +1666,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 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|.