diff --git a/.github/ISSUE_TEMPLATE/0-new-issue.yml b/.github/ISSUE_TEMPLATE/0-new-issue.yml
new file mode 100644
index 0000000..5509cf1
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/0-new-issue.yml
@@ -0,0 +1,17 @@
+name: New issue
+description: File a new issue against the File System Standard.
+body:
+ - type: markdown
+ attributes:
+ value: |
+ Before filling out this form, please familiarize yourself with the [Code of Conduct](https://whatwg.org/code-of-conduct). You might also find the [FAQ](https://whatwg.org/faq) and [Working Mode](https://whatwg.org/working-mode) useful.
+
+ If at any point you have questions, please reach out to us on [Chat](https://whatwg.org/chat).
+ - type: textarea
+ attributes:
+ label: "What is the issue with the File System Standard?"
+ validations:
+ required: true
+ - type: markdown
+ attributes:
+ value: "Thank you for taking the time to improve the File System Standard!"
diff --git a/.github/ISSUE_TEMPLATE/1-new-feature.yml b/.github/ISSUE_TEMPLATE/1-new-feature.yml
new file mode 100644
index 0000000..9189920
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/1-new-feature.yml
@@ -0,0 +1,27 @@
+name: New feature
+description: Request a new feature in the File System Standard.
+labels: ["addition/proposal", "needs implementer interest"]
+body:
+ - type: markdown
+ attributes:
+ value: |
+ Before filling out this form, please familiarize yourself with the [Code of Conduct](https://whatwg.org/code-of-conduct), [FAQ](https://whatwg.org/faq), and [Working Mode](https://whatwg.org/working-mode). They help with setting expectations and making sure you know what is required. The FAQ ["How should I go about proposing new features to WHATWG standards?"](https://whatwg.org/faq#adding-new-features) is especially relevant.
+
+ If at any point you have questions, please reach out to us on [Chat](https://whatwg.org/chat).
+ - type: textarea
+ attributes:
+ label: "What problem are you trying to solve?"
+ validations:
+ required: true
+ - type: textarea
+ attributes:
+ label: "What solutions exist today?"
+ - type: textarea
+ attributes:
+ label: "How would you solve it?"
+ - type: textarea
+ attributes:
+ label: "Anything else?"
+ - type: markdown
+ attributes:
+ value: "Thank you for taking the time to improve the File System Standard!"
diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
new file mode 100644
index 0000000..70e8d0d
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/config.yml
@@ -0,0 +1,8 @@
+blank_issues_enabled: false
+contact_links:
+ - name: Chat
+ url: https://whatwg.org/chat
+ about: Please do reach out with questions and feedback!
+ - name: Stack Overflow
+ url: https://stackoverflow.com/
+ about: If you're having trouble building a web page, this is not the right repository. Consider asking your question on Stack Overflow instead.
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 445136a..af3476e 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -9,15 +9,14 @@ on:
jobs:
build:
name: Build
- runs-on: ubuntu-20.04
+ runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 2
- # Note: `python` will also be this version, which various scripts depend on.
- - uses: actions/setup-python@v3
+ - uses: actions/setup-python@v4
with:
- python-version: "3.10"
+ python-version: "3.11"
# Note: `make deploy` will do a deploy dry run on PRs.
- run: make deploy
env:
diff --git a/PULL_REQUEST_TEMPLATE.md b/PULL_REQUEST_TEMPLATE.md
index 543574a..f292b8d 100644
--- a/PULL_REQUEST_TEMPLATE.md
+++ b/PULL_REQUEST_TEMPLATE.md
@@ -1,16 +1,19 @@
- [ ] At least two implementers are interested (and none opposed):
* …
* …
- [ ] [Tests](https://github.com/web-platform-tests/wpt) are written and can be reviewed and commented upon at:
- * …
+ * …
- [ ] [Implementation bugs](https://github.com/whatwg/meta/blob/main/MAINTAINERS.md#handling-pull-requests) are filed:
* Chromium: …
* Gecko: …
* WebKit: …
- [ ] [MDN issue](https://github.com/whatwg/meta/blob/main/MAINTAINERS.md#handling-pull-requests) is filed: …
+- [ ] The top of this comment includes a [clear commit message](https://github.com/whatwg/meta/blob/main/COMMITTING.md) to use.
(See [WHATWG Working Mode: Changes](https://whatwg.org/working-mode#changes) for more details.)
diff --git a/README.md b/README.md
index 5897eef..0a0a96b 100644
--- a/README.md
+++ b/README.md
@@ -1,42 +1,33 @@
-# File System Standard
-
-This repository hosts the
-[File System Standard](https://fs.spec.whatwg.org/).
+This repository hosts the [File System Standard](https://fs.spec.whatwg.org/).
## Code of conduct
-We are committed to providing a friendly, safe, and welcoming environment for all. Please read and
-respect the [WHATWG Code of Conduct](https://whatwg.org/code-of-conduct).
+We are committed to providing a friendly, safe, and welcoming environment for all. Please read and respect the [Code of Conduct](https://whatwg.org/code-of-conduct).
## Contribution opportunities
-Folks notice minor and larger issues with the File System Standard all the time and we'd love your
-help fixing those. Pull requests for typographical and grammar errors are also most welcome.
+Folks notice minor and larger issues with the File System Standard all the time and we'd love your help fixing those. Pull requests for typographical and grammar errors are also most welcome.
+
+Issues labeled ["good first issue"](https://github.com/whatwg/fs/labels/good%20first%20issue) are a good place to get a taste for editing the File System Standard. Note that we don't assign issues and there's no reason to ask for availability either, just provide a pull request.
-We'd be happy to mentor you through this process. If you're interested and need help getting
-started, leave a comment on the issue or ask around [on chat](https://whatwg.org/chat).
+If you are thinking of suggesting a new feature, read through the [FAQ](https://whatwg.org/faq) and [Working Mode](https://whatwg.org/working-mode) documents to get yourself familiarized with the process.
+
+We'd be happy to help you with all of this [on Chat](https://whatwg.org/chat).
## Pull requests
-In short, change `index.bs` and submit your patch, with a
-[good commit message](https://github.com/whatwg/meta/blob/main/COMMITTING.md). Consider
-reading through the [WHATWG FAQ](https://whatwg.org/faq) if you are new here.
+In short, change `index.bs` and submit your patch, with a [good commit message](https://github.com/whatwg/meta/blob/main/COMMITTING.md).
-Please add your name to the Acknowledgments section in your first pull request, even for trivial
-fixes. The names are sorted lexicographically.
+Please add your name to the Acknowledgments section in your first pull request, even for trivial fixes. The names are sorted lexicographically.
-## Building "locally"
+To ensure your patch meets all the necessary requirements, please also see the [Contributor Guidelines](https://github.com/whatwg/meta/blob/main/CONTRIBUTING.md). Editors of the File System Standard are expected to follow the [Maintainer Guidelines](https://github.com/whatwg/meta/blob/main/MAINTAINERS.md).
-For quick local iteration, run `make`. To verify your changes locally, run `make deploy`. See more
-in the
-[WHATWG Contributor Guidelines](https://github.com/whatwg/meta/blob/main/CONTRIBUTING.md#building).
+## Tests
-## Merge policy
+Tests are an essential part of the standardization process and will need to be created or adjusted as changes to the standard are made. Tests for the File System Standard can be found in the `fs/` directory of [`web-platform-tests/wpt`](https://github.com/web-platform-tests/wpt).
-If you can commit to this repository, see the
-[WHATWG Maintainer Guidelines](https://github.com/whatwg/meta/blob/main/MAINTAINERS.md).
+A dashboard showing the tests running against browser engines can be seen at [wpt.fyi/results/fs](https://wpt.fyi/results/fs).
-## Tests
+## Building "locally"
-Tests can be found in the `fs/` directory of
-[web-platform-tests/wpt](https://github.com/web-platform-tests/wpt).
+For quick local iteration, run `make`; this will use a web service to build the standard, so that you don't have to install anything. See more in the [Contributor Guidelines](https://github.com/whatwg/meta/blob/main/CONTRIBUTING.md#building).
diff --git a/index.bs b/index.bs
index 889d9c2..d3171eb 100644
--- a/index.bs
+++ b/index.bs
@@ -3,8 +3,9 @@ Group: WHATWG
H1: File System
Shortname: fs
Text Macro: TWITTER whatfilesystem
-Text Macro: LATESTRD 2023-03
+Text Macro: LATESTRD 2023-09
Abstract: File System defines infrastructure for file systems as well as their API.
+Translation: ja https://triple-underscore.github.io/fs-ja.html
Indent: 2
Markup Shorthands: css no, markdown yes
diff --git a/proposals/FileSystemObserver.md b/proposals/FileSystemObserver.md
index ec73b8f..3d424bc 100644
--- a/proposals/FileSystemObserver.md
+++ b/proposals/FileSystemObserver.md
@@ -68,7 +68,7 @@ A `FileSystemObserver` allows changes to the file to be observed with much more
```javascript
// Same as above, but using the proposed FileSystemObserver
-const callback = (records, observer) => {
+const callback = async (records, observer) => {
// Will be run when the observed file changes.
// The change record includes a handle detailing which file has
diff --git a/review-drafts/2023-09.bs b/review-drafts/2023-09.bs
new file mode 100644
index 0000000..4ca9348
--- /dev/null
+++ b/review-drafts/2023-09.bs
@@ -0,0 +1,1780 @@
+
+Group: WHATWG
+Status: RD
+Date: 2023-09-18
+H1: File System
+Shortname: fs
+Text Macro: TWITTER whatfilesystem
+Text Macro: LATESTRD 2023-09
+Abstract: File System defines infrastructure for file systems as well as their API.
+Indent: 2
+Markup Shorthands: css no, markdown yes
+
+
+
+spec:webidl; type:dfn; text:resolve
+
+
+
+urlPrefix: https://tc39.es/ecma262/; spec: ECMA-262
+ type: dfn; text: current realm; url: current-realm
+ type: dfn; text: realm; url: realm
+urlPrefix: https://storage.spec.whatwg.org/; spec: storage
+ type: dfn; text: storage; url: site-storage
+ type: dfn; text: storage bucket; url: storage-bucket
+
+
+
+
+
+# Introduction # {#introduction}
+
+*This section is non-normative.*
+
+This document defines fundamental infrastructure for file system APIs. In addition, it defines an
+API that makes it possible for websites to get access to a file system directory without having to
+first prompt the user for access. This enables use cases where a website wants to save data to disk
+before a user has picked a location to save to, without forcing the website to use a completely
+different storage mechanism with a different API for such files. The entry point for this is the
+{{StorageManager/getDirectory()|navigator.storage.getDirectory()}} method.
+
+
+# Files and Directories # {#files-and-directories}
+
+## Concepts ## {#concepts}
+
+A file system entry is either a [=file entry=] or a [=directory entry=].
+
+Each [=/file system entry=] has an associated
+query access
+algorithm, which takes "`read`" or "`readwrite`" mode and
+returns a [=/file system access result=].
+Unless specified otherwise it returns a [=/file system access result=] with a
+[=file system access result/permission state=] of "{{PermissionState/denied}}"
+and with an [=file system access result/error name=] of the empty string.
+
+Each [=/file system entry=] has an associated
+request access
+algorithm, which takes "`read`" or "`readwrite`" mode and
+returns a [=/file system access result=].
+Unless specified otherwise it returns a [=/file system access result=] with a
+[=file system access result/permission state=] of "{{PermissionState/denied}}"
+and with an [=file system access result/error name=] of the empty string.
+
+A file system access result is a [=struct=] encapsulating the
+result of [=file system entry/query access|querying=] or
+[=file system entry/request access|requesting=] access to the file system.
+It has the following [=struct/items=]:
+
+: permission state
+:: A {{PermissionState}}
+: error name
+:: A [=string=] which must be the empty string if
+ [=file system access result/permission state=] is
+ "{{PermissionState/granted}}"; otherwise an
+ [=DOMException/name=] listed in the `DOMException` names table.
+ It is expected that in most cases when
+ [=file system access result/permission state=] is not
+ "{{PermissionState/granted}}", this should be "{{NotAllowedError}}".
+
+ Dependent specifications may consider this API a
+[=powerful feature=]. However, unlike other [=powerful features=] whose
+[=permission request algorithm=] may throw, [=/file system entry=]'s
+[=file system entry/query access=] and [=file system entry/request access=]
+algorithms must run [=in parallel=] on the [=file system queue=] and are
+therefore not allowed to throw. Instead, the caller is expected to
+[=queue a storage task=] to [=/reject=], as appropriate,
+should these algorithms return an [=file system access result/error name=]
+other than the empty string.
+
+Note: Implementations that only implement this specification and not dependent
+specifications do not need to bother implementing [=/file system entry=]'s
+[=file system entry/query access=] and [=file system entry/request access=].
+
+Issue(101): Make access check algorithms associated with a FileSystemHandle.
+
+Each [=/file system entry=] has an associated name (a [=string=]).
+
+A valid file name is a [=string=] that is not an empty string, is not equal to "." or "..",
+and does not contain '/' or any other character used as path separator on the underlying platform.
+
+Note: This means that '\' is not allowed in names on Windows, but might be allowed on
+other operating systems. Additionally underlying file systems might have further restrictions
+on what names are or aren't allowed, so a string merely being a [=valid file name=] is not
+a guarantee that creating a file or directory with that name will succeed.
+
+Issue: We should consider having further normative restrictions on file names that will
+never be allowed using this API, rather than leaving it entirely up to underlying file
+systems.
+
+A file entry additionally consists of
+binary data (a [=byte sequence=]), a
+modification timestamp (a number representing the number of milliseconds since the Unix Epoch),
+a lock (a string that may exclusively be "`open`", "`taken-exclusive`" or "`taken-shared`")
+and a shared lock count (a number representing the number shared locks that are taken at a given point in time).
+
+A user agent has an associated file system queue which is the
+result of [=starting a new parallel queue=]. This queue is to be used for all
+file system operations.
+
+
+To take a [=file entry/lock=] with a |value| of
+"`exclusive`" or "`shared`" on a given [=file entry=] |file|:
+
+1. Let |lock| be the |file|'s [=file entry/lock=].
+1. Let |count| be the |file|'s [=file entry/shared lock count=].
+1. If |value| is "`exclusive`":
+ 1. If |lock| is "`open`":
+ 1. Set lock to "`taken-exclusive`".
+ 1. Return "`success`".
+1. If |value| is "`shared`":
+ 1. If |lock| is "`open`":
+ 1. Set |lock| to "`taken-shared`".
+ 1. Set |count| to 1.
+ 1. Return "`success`".
+ 1. Otherwise, if |lock| is "`taken-shared`":
+ 1. Increase |count| by 1.
+ 1. Return "`success`".
+1. Return "`failure`".
+
+Note: These steps have to be run on the [=file system queue=].
+
+
+
+
+To release a [=file entry/lock=] on a given
+[=file entry=] |file|:
+
+1. Let |lock| be the |file|'s associated [=file entry/lock=].
+1. Let |count| be the |file|'s [=file entry/shared lock count=].
+1. If |lock| is "`taken-shared`":
+ 1. Decrease |count| by 1.
+ 1. If |count| is 0, set |lock| to "`open`".
+1. Otherwise, set |lock| to "`open`".
+
+Note: These steps have to be run on the [=file system queue=].
+
+
+
+Note: Locks help prevent concurrent modifications to a file. A {{FileSystemWritableFileStream}}
+requires a shared lock, while a {{FileSystemSyncAccessHandle}} requires an exclusive one.
+
+A directory entry additionally consists of a [=/set=] of
+children, which are themselves [=/file system entries=].
+Each member is either a [=/file entry=] or a [=/directory entry=].
+
+A [=/file system entry=] |entry| should be [=list/contained=] in the [=directory entry/children=] of at most one
+[=directory entry=], and that directory entry is also known as |entry|'s
+parent.
+A [=/file system entry=]'s [=file system entry/parent=] is null if no such directory entry exists.
+
+Note: Two different [=/file system entries=] can represent the same file or directory on disk, in which
+case it is possible for both entries to have a different parent, or for one entry to have a
+parent while the other entry does not have a parent.
+
+[=/File system entries=] can (but don't have to) be backed by files on the host operating system's local file system,
+so it is possible for the [=binary data=], [=modification timestamp=],
+and [=directory entry/children=] of entries to be modified by applications outside of this specification.
+Exactly how external changes are reflected in the data structures defined by this specification,
+as well as how changes made to the data structures defined here are reflected externally
+is left up to individual user-agent implementations.
+
+A [=/file system entry=] |a| is the same entry as
+a [=/file system entry=] |b| if |a| is equal to |b|, or
+if |a| and |b| are backed by the same file or directory on the local file system.
+
+
+
+To resolve a
+[=/file system locator=] |child| relative to a [=directory locator=] |root|:
+
+1. Let |result| be [=a new promise=].
+1. [=Enqueue the following steps=] to the [=file system queue=]:
+ 1. If |child|'s [=FileSystemHandle/locator=]'s [=file system locator/root=]
+ is not |root|'s [=FileSystemHandle/locator=]'s [=file system locator/root=],
+ [=/resolve=] |result| with null, and abort these steps.
+
+ 1. Let |childPath| be |child|'s [=FileSystemHandle/locator=]'s [=file system locator/path=].
+ 1. Let |rootPath| be |root|'s [=FileSystemHandle/locator=]'s [=file system locator/path=].
+ 1. If |childPath| is [=the same path as=] |rootPath|,
+ [=/resolve=] |result| with « », and abort these steps.
+
+ 1. If |rootPath|'s [=list/size=] is greater than |childPath|'s [=list/size=],
+ [=/resolve=] |result| with null, and abort these steps.
+
+ 1. [=list/For each=] |index| of |rootPath|'s [=list/indices=]:
+ 1. If |rootPath|.\[[|index|]] is not |childPath|.\[[|index|]], then
+ [=/resolve=] |result| with null, and abort these steps.
+
+ 1. Let |relativePath| be « ».
+ 1. [=list/For each=] |index| of [=the range=] from |rootPath|'s [=list/size=]
+ to |rootPath|'s [=list/size=], exclusive,
+ [=list/append=] |childPath|.\[[|index|]] to |relativePath|.
+
+ 1. [=/Resolve=] |result| with |relativePath|.
+
+1. Return |result|.
+
+
+
+A file system locator represents a potential location of a
+[=/file system entry=]. A [=/file system locator=] is either a [=file locator=]
+or a [=directory locator=].
+
+Each [=/file system locator=] has an associated path (a [=/file system path=]),
+a kind (a {{FileSystemHandleKind}}), and
+a root (a [=file system root=]).
+
+Issue(109): Consider giving each locator a [=storage bucket=].
+
+A file locator is a [=/file system locator=] whose
+[=file system locator/kind=] is "{{FileSystemHandleKind/file}}".
+A directory locator is a [=/file system locator=] whose
+[=file system locator/kind=] is "{{FileSystemHandleKind/directory}}".
+
+A file system root is an opaque [=string=] whose value is
+[=implementation-defined=].
+
+For a [=/file system locator=] |locator|
+whichs [=locate an entry|locates to=] a [=file entry=] |entry| that conceptually
+exists at the path `data/drafts/example.txt` relative to the root directory of
+a [=/bucket file system=],
+|locator|'s [=file system locator/kind=] has to be "{{FileSystemHandleKind/file}}",
+|locator|'s [=file system locator/path=] has to be « "`data`", "`drafts`", "`example.txt`" », and
+|locator|'s [=file system locator/root=] might include relevant identifying
+information such as the [=storage bucket=] and the disk drive.
+
+A [=/file system locator=] |a| is the same locator as
+a [=/file system locator=] |b| if
+|a|'s [=file system locator/kind=] is |b|'s [=file system locator/kind=],
+|a|'s [=file system locator/root=] is |b|'s [=file system locator/root=], and
+|a|'s [=file system locator/path=] is [=the same path as=] |b|'s [=file system locator/path=].
+
+
+The locate an entry algorithm given a
+[=/file system locator=] |locator| runs an [=implementation-defined=] series of steps adhering to
+these constraints:
+
+- If |locator| is a [=file locator=], they return a [=file entry=] or null.
+- If |locator| is a [=directory locator=], they return a [=directory entry=] or null.
+- If these steps return a non-null |entry|, then:
+ - [=Getting the locator=] with |entry| returns |locator|,
+ provided no intermediate file system operations were run.
+ - |entry|'s [=file system entry/name=] is the last [=list/item=] of |locator|'s
+ [=file system locator/path=].
+
+
+
+
+The get the locator algorithm given
+[=/file system entry=] |entry| runs an [=implementation-defined=] series of steps adhering to these
+constraints:
+
+- If |entry| is a [=file entry=], they return a [=file locator=].
+- If |entry| is a [=directory entry=], they return a [=directory locator=].
+- If these steps return |locator|, then:
+ - [=Locating an entry=] with |locator| returns |entry|,
+ provided no intermediate file system operations were run.
+ - |entry|'s [=file system entry/name=] is the last [=list/item=] of |locator|'s
+ [=file system locator/path=].
+
+
+
+A file system path is a [=/list=] of one or more [=strings=].
+This may be a virtual path that is mapped to real location on disk or in memory,
+may correspond directly to a path on the local file system, or may not
+correspond to any file on disk at all. The actual physical location of the
+corresponding [=/file system entry=] is [=implementation-defined=].
+
+Let |path| be the [=/list=]
+« "`data`", "`drafts`", "`example.txt`" ».
+There is no expectation that a file named `example.txt` exists anywhere on disk.
+
+A [=/file system path=] |a| is the same path as
+a [=/file system path=] |b| if
+|a|'s [=list/size=] is the same as |b|'s [=list/size=] and
+[=list/for each=] |index| of |a|'s [=list/indices=]
+|a|.\[[|index|]] is |b|.\[[|index|]].
+
+
The contents of a [=/file system locator=], including its
+[=file system locator/path=], are not expected to be shared in their entirety
+with the website process. The [=/file system path=] might contain components
+which are not known to the website unless the [=/file system locator=] is later
+[=file system locator/resolved=] relative to a parent [=directory locator=].
+
+## The {{FileSystemHandle}} interface ## {#api-filesystemhandle}
+
+
+enum FileSystemHandleKind {
+ "file",
+ "directory",
+};
+
+[Exposed=(Window,Worker), SecureContext, Serializable]
+interface FileSystemHandle {
+ readonly attribute FileSystemHandleKind kind;
+ readonly attribute USVString name;
+
+ Promise isSameEntry(FileSystemHandle other);
+};
+
+
+A {{FileSystemHandle}} object is associated with a locator (a
+[=/file system locator=]).
+
+Note: Multiple {{FileSystemHandle}} objects can have
+[=the same locator as|the same=] [=/file system locator=].
+
+A {{FileSystemHandle}}
+is in a bucket file system
+if the first [=list/item=] of its [=FileSystemHandle/locator=]'s
+[=file system locator/path=] is the empty string.
+
+Note: This is a bit magical, but it works since only the root directory of a
+[=/bucket file system=] can have a [=file system locator/path=] which
+[=list/contains=] an empty string. See {{StorageManager/getDirectory()}}.
+All other [=list/item=]s of a [=file system locator/path=] will be a
+[=valid file name=].
+
+Issue(109): Consider improving this situation by giving each locator a
+[=storage bucket=].
+
+
+{{FileSystemHandle}} objects are [=serializable objects=].
+
+Their [=serialization steps=], given |value|, |serialized| and forStorage are:
+
+1. Set |serialized|.\[[Origin]] to |value|'s [=relevant settings object=]'s [=environment settings object/origin=].
+1. Set |serialized|.\[[Locator]] to |value|'s [=FileSystemHandle/locator=].
+
+
+
+
+Their [=deserialization steps=], given |serialized| and |value| are:
+
+1. If |serialized|.\[[Origin]] is not [=same origin=] with
+ |value|'s [=relevant settings object=]'s [=environment settings object/origin=],
+ then [=throw=] a "{{DataCloneError}}" {{DOMException}}.
+1. Set |value|'s [=FileSystemHandle/locator=] to |serialized|.\[[Locator]].
+
+
+
+
+ : |handle| . {{FileSystemHandle/kind}}
+ :: Returns "{{FileSystemHandleKind/file}}" if |handle| is a {{FileSystemFileHandle}},
+ or "{{FileSystemHandleKind/directory}}" if |handle| is a {{FileSystemDirectoryHandle}}.
+
+ This can be used to distinguish files from directories when iterating over the contents
+ of a directory.
+
+ : |handle| . {{FileSystemHandle/name}}
+ :: Returns the last path component of |handle|'s
+ [=FileSystemHandle/locator=]'s [=file system locator/path=].
+
+
+The kind getter steps are to return
+[=this=]'s [=FileSystemHandle/locator=]'s [=file system locator/kind=].
+
+The name getter steps are to return
+the last [=list/item=] (a [=string=]) of
+[=this=]'s [=FileSystemHandle/locator=]'s [=file system locator/path=].
+
+### The {{FileSystemHandle/isSameEntry()}} method ### {#api-filesystemhandle-issameentry}
+
+
+ : same = await |handle1| . {{FileSystemHandle/isSameEntry()|isSameEntry}}( |handle2| )
+ :: Returns true if |handle1| and |handle2| represent the same file or directory.
+
+
+
+The isSameEntry(|other|) method steps are:
+
+1. Let |realm| be [=this=]'s [=relevant Realm=].
+1. Let |p| be [=a new promise=] in |realm|.
+1. [=Enqueue the following steps=] to the [=file system queue=]:
+ 1. If [=this=]'s [=FileSystemHandle/locator=] is
+ [=the same locator as=] |other|'s [=FileSystemHandle/locator=],
+ [=/resolve=] |p| with true.
+ 1. Otherwise [=/resolve=] |p| with false.
+1. Return |p|.
+
+
+
+## The {{FileSystemFileHandle}} interface ## {#api-filesystemfilehandle}
+
+
+dictionary FileSystemCreateWritableOptions {
+ boolean keepExistingData = false;
+};
+
+[Exposed=(Window,Worker), SecureContext, Serializable]
+interface FileSystemFileHandle : FileSystemHandle {
+ Promise getFile();
+ Promise createWritable(optional FileSystemCreateWritableOptions options = {});
+ [Exposed=DedicatedWorker]
+ Promise createSyncAccessHandle();
+};
+
+
+Note: A {{FileSystemFileHandle}}'s associated [=FileSystemHandle/locator=]'s
+[=file system locator/kind=] is "{{FileSystemHandleKind/file}}".
+
+
+To
+create a child `FileSystemFileHandle`
+given a [=directory locator=] |parentLocator| and a string |name| in a [=/Realm=] |realm|:
+
+1. Let |handle| be a [=new=] {{FileSystemFileHandle}} in |realm|.
+1. Let |childType| be "{{FileSystemHandleKind/file}}".
+1. Let |childRoot| be a copy of |parentLocator|'s [=file system locator/root=].
+1. Let |childPath| be the result of [=list/clone|cloning=] |parentLocator|'s
+ [=file system locator/path=] and [=list/append|appending=] |name|.
+1. Set |handle|'s [=FileSystemHandle/locator=] to a [=/file system locator=] whose
+ [=file system locator/kind=] is |childType|,
+ [=file system locator/root=] is |childRoot|, and
+ [=file system locator/path=] is |childPath|.
+1. Return |handle|.
+
+
+
+
+To
+create a new `FileSystemFileHandle`
+given a [=/file system root=] |root| and a [=/file system path=] |path|
+in a [=/Realm=] |realm|:
+
+1. Let |handle| be a [=new=] {{FileSystemFileHandle}} in |realm|.
+1. Set |handle|'s [=FileSystemHandle/locator=] to a [=/file system locator=] whose
+ [=file system locator/kind=] is "{{FileSystemHandleKind/file}}",
+ [=file system locator/root=] is |root|, and
+ [=file system locator/path=] is |path|.
+1. Return |handle|.
+
+
+
+{{FileSystemFileHandle}} objects are [=serializable objects=]. Their [=serialization steps=] and
+[=deserialization steps=] are the same as those for {{FileSystemHandle}}.
+
+### The {{FileSystemFileHandle/getFile()}} method ### {#api-filesystemfilehandle-getfile}
+
+
+ : file = await |fileHandle| . {{FileSystemFileHandle/getFile()}}
+ :: Returns a {{File}} representing the state on disk of the [=file entry=]
+ [=locate an entry|locatable=] by |handle|'s [=FileSystemHandle/locator=].
+ If the file on disk changes or is removed after this method is called, the returned
+ {{File}} object will likely be no longer readable.
+
+
+
+The
getFile() method steps are:
+
+1. Let |result| be [=a new promise=].
+1. Let |locator| be [=this=]'s [=FileSystemHandle/locator=].
+1. Let |global| be [=this=]'s [=relevant global object=].
+1. [=Enqueue the following steps=] to the [=file system queue=]:
+ 1. Let |entry| be the result of [=locating an entry=] given |locator|.
+ 1. Let |accessResult| be the result of running |entry|'s
+ [=file system entry/query access=] given "`read`".
+
+ 1. [=Queue a storage task=] with |global| to run these steps:
+ 1. If |accessResult|'s [=file system access result/permission state=]
+ is not "{{PermissionState/granted}}", [=/reject=] |result| with a
+ {{DOMException}} of |accessResult|'s
+ [=file system access result/error name=] and abort these steps.
+
+ 1. If |entry| is null, [=/reject=] |result| with a
+ "{{NotFoundError}}" {{DOMException}} and abort these steps.
+ 1. [=Assert=]: |entry| is a [=file entry=].
+
+ 1. Let |f| be a new {{File}}.
+ 1. Set |f|'s
snapshot state to the current state of |entry|.
+ 1. Set |f|'s underlying byte sequence to a copy of |entry|'s [=binary data=].
+ 1. Set |f|'s {{File/name}} to |entry|'s [=file system entry/name=].
+ 1. Set |f|'s {{File/lastModified}} to |entry|'s [=file entry/modification timestamp=].
+ 1. Set |f|'s {{Blob/type}} to an [=implementation-defined=] value, based on
+ for example |entry|'s [=file system entry/name=] or its file extension.
+
+ Issue: The reading and snapshotting behavior needs to be better specified in the [[FILE-API]] spec,
+ for now this is kind of hand-wavy.
+ 1. [=/Resolve=] |result| with |f|.
+1. Return |result|.
+
+
+
+### The {{FileSystemFileHandle/createWritable()}} method ### {#api-filesystemfilehandle-createwritable}
+
+
+ : |stream| = await |fileHandle| . {{FileSystemFileHandle/createWritable()}}
+ : |stream| = await |fileHandle| . {{FileSystemFileHandle/createWritable()|createWritable}}({ {{FileSystemCreateWritableOptions/keepExistingData}}: true/false })
+ :: Returns a {{FileSystemWritableFileStream}} that can be used to write to the file. Any changes made through
+ |stream| won't be reflected in the [=file entry=] [=locate an entry|locatable=] by
+ |fileHandle|'s [=FileSystemHandle/locator=] until the stream has been closed.
+ User agents try to ensure that no partial writes happen, i.e. the file
+ will either contain its old contents or it will contain whatever data was written
+ through |stream| up until the stream has been closed.
+
+ This is typically implemented by writing data to a temporary file, and only replacing the
+ [=file entry=] [=locate an entry|locatable=] by |fileHandle|'s [=FileSystemHandle/locator=]
+ with the temporary file when the writable filestream is closed.
+
+ If {{FileSystemCreateWritableOptions/keepExistingData}} is false or not specified,
+ the temporary file starts out empty,
+ otherwise the existing file is first copied to this temporary file.
+
+ Creating a {{FileSystemWritableFileStream}} [=file entry/lock/take|takes a shared lock=] on the
+ [=file entry=] [=locate an entry|locatable=] with |fileHandle|'s [=FileSystemHandle/locator=].
+ This prevents the creation of {{FileSystemSyncAccessHandle|FileSystemSyncAccessHandles}}
+ for the entry, until the stream is closed.
+
+
+See WICG/file-system-access issue #67
+for discussion around and desire for a "inPlace" mode for createWritable (where
+changes will be written to the actual underlying file as they are written to the
+writer, for example to support in-place modification of large files or things
+like databases). This is not currently implemented in Chrome. Implementing this
+is currently blocked on figuring out how to combine the desire to run malware
+checks with the desire to let websites make fast in-place modifications to
+existing large files. In-place writes are available for files in a
+[=/bucket file system=] via the {{FileSystemSyncAccessHandle}} interface.
+
+
+The
createWritable(|options|) method steps are:
+
+1. Let |result| be [=a new promise=].
+1. Let |locator| be [=this=]'s [=FileSystemHandle/locator=].
+1. Let |realm| be [=this=]'s [=relevant Realm=].
+1. Let |global| be [=this=]'s [=relevant global object=].
+1. [=Enqueue the following steps=] to the [=file system queue=]:
+ 1. Let |entry| be the result of [=locating an entry=] given |locator|.
+ 1. Let |accessResult| be the result of running |entry|'s
+ [=file system entry/request access=] given "`readwrite`".
+ 1. If |accessResult|'s [=file system access result/permission state=]
+ is not "{{PermissionState/granted}}", [=queue a storage task=] with
+ |global| to [=/reject=] |result| with a {{DOMException}} of
+ |accessResult|'s [=file system access result/error name=] and
+ abort these steps.
+
+ 1. If |entry| is `null`, [=queue a storage task=] with |global| to [=/reject=]
+ |result| with a "{{NotFoundError}}" {{DOMException}} and abort these steps.
+ 1. [=Assert=]: |entry| is a [=file entry=].
+
+ 1. Let |lockResult| be the result of [=file entry/lock/take|taking a lock=]
+ with "`shared`" on |entry|.
+
+ 1. [=Queue a storage task=] with |global| to run these steps:
+ 1. If |lockResult| is "`failure`", [=/reject=] |result| with a
+ "{{NoModificationAllowedError}}" {{DOMException}} and abort these steps.
+
+ 1. Let |stream| be the result of
creating a new `FileSystemWritableFileStream`
+ for |entry| in |realm|.
+ 1. If |options|["{{FileSystemCreateWritableOptions/keepExistingData}}"]
+ is true:
+ 1. Set |stream|'s [=[[buffer]]=] to a copy of |entry|'s
+ [=file entry/binary data=].
+ 1. [=/Resolve=] |result| with |stream|.
+
+1. Return |result|.
+
+
+
+### The {{FileSystemFileHandle/createSyncAccessHandle()}} method ### {#api-filesystemfilehandle-createsyncaccesshandle}
+
+
+ : |handle| = await |fileHandle| . {{FileSystemFileHandle/createSyncAccessHandle()|createSyncAccessHandle}}()
+ :: Returns a {{FileSystemSyncAccessHandle}} that can be used to read from/write to the file.
+ Changes made through |handle| might be immediately reflected in the
+ [=file entry=] [=locate an entry|locatable=] by |fileHandle|'s [=FileSystemHandle/locator=].
+ To ensure the changes are reflected in this file, the handle can be flushed.
+
+ Creating a {{FileSystemSyncAccessHandle}} [=file entry/lock/take|takes an exclusive lock=] on the
+ [=file entry=] [=locate an entry|locatable=] with |fileHandle|'s [=FileSystemHandle/locator=].
+ This prevents the creation of further {{FileSystemSyncAccessHandle|FileSystemSyncAccessHandles}}
+ or {{FileSystemWritableFileStream|FileSystemWritableFileStreams}}
+ for the entry, until the access handle is closed.
+
+ The returned {{FileSystemSyncAccessHandle}} offers synchronous methods. This allows for higher performance
+ on contexts where asynchronous operations come with high overhead, e.g., WebAssembly.
+
+ For the time being, this method will only succeed when the |fileHandle|
+ [=FileSystemHandle/is in a bucket file system=].
+
+
+
+The
createSyncAccessHandle() method steps are:
+
+1. Let |result| be [=a new promise=].
+1. Let |locator| be [=this=]'s [=FileSystemHandle/locator=].
+1. Let |realm| be [=this=]'s [=relevant Realm=].
+1. Let |global| be [=this=]'s [=relevant global object=].
+1. Let |isInABucketFileSystem| be true if
+ [=this=] [=FileSystemHandle/is in a bucket file system=];
+ otherwise false.
+1. [=Enqueue the following steps=] to the [=file system queue=]:
+ 1. Let |entry| be the result of [=locating an entry=] given |locator|.
+ 1. Let |accessResult| be the result of running |entry|'s
+ [=file system entry/request access=] given "`readwrite`".
+ 1. If |accessResult|'s [=file system access result/permission state=]
+ is not "{{PermissionState/granted}}", [=queue a storage task=] with
+ |global| to [=/reject=] |result| with a {{DOMException}} of
+ |accessResult|'s [=file system access result/error name=] and
+ abort these steps.
+
+ 1. If |isInABucketFileSystem| is false,
+ [=queue a storage task=] with |global| to
+ [=/reject=] |result| with an "{{InvalidStateError}}" {{DOMException}} and
+ abort these steps.
+
+ 1. If |entry| is `null`, [=queue a storage task=] with |global| to [=/reject=]
+ |result| with a "{{NotFoundError}}" {{DOMException}} and abort these steps.
+ 1. [=Assert=]: |entry| is a [=file entry=].
+
+ 1. Let |lockResult| be the result of [=file entry/lock/take|taking a lock=]
+ with "`exclusive`" on |entry|.
+
+ 1. [=Queue a storage task=] with |global| to run these steps:
+ 1. If |lockResult| is "`failure`", [=/reject=] |result| with a
+ "{{NoModificationAllowedError}}" {{DOMException}} and abort these steps.
+
+ 1. Let |handle| be the result of
creating a new `FileSystemSyncAccessHandle`
+ for |entry| in |realm|.
+ 1. [=/Resolve=] |result| with |handle|.
+
+1. Return |result|.
+
+
+
+## The {{FileSystemDirectoryHandle}} interface ## {#api-filesystemdirectoryhandle}
+
+
+dictionary FileSystemGetFileOptions {
+ boolean create = false;
+};
+
+dictionary FileSystemGetDirectoryOptions {
+ boolean create = false;
+};
+
+dictionary FileSystemRemoveOptions {
+ boolean recursive = false;
+};
+
+[Exposed=(Window,Worker), SecureContext, Serializable]
+interface FileSystemDirectoryHandle : FileSystemHandle {
+ async iterable;
+
+ Promise getFileHandle(USVString name, optional FileSystemGetFileOptions options = {});
+ Promise getDirectoryHandle(USVString name, optional FileSystemGetDirectoryOptions options = {});
+
+ Promise removeEntry(USVString name, optional FileSystemRemoveOptions options = {});
+
+ Promise?> resolve(FileSystemHandle possibleDescendant);
+};
+
+
+Note: A {{FileSystemDirectoryHandle}}'s associated [=FileSystemHandle/locator=]'s
+[=file system locator/kind=] is "{{FileSystemHandleKind/directory}}".
+
+
+To
+create a child `FileSystemDirectoryHandle`
+given a [=directory locator=] |parentLocator| and a string |name| in a [=/Realm=] |realm|:
+
+1. Let |handle| be a [=new=] {{FileSystemDirectoryHandle}} in |realm|.
+1. Let |childType| be "{{FileSystemHandleKind/directory}}".
+1. Let |childRoot| be a copy of |parentLocator|'s [=file system locator/root=].
+1. Let |childPath| be the result of [=list/clone|cloning=] |parentLocator|'s
+ [=file system locator/path=] and [=list/append|appending=] |name|.
+1. Set |handle|'s [=FileSystemHandle/locator=] to a [=/file system locator=] whose
+ [=file system locator/kind=] is |childType|,
+ [=file system locator/root=] is |childRoot|, and
+ [=file system locator/path=] is |childPath|.
+1. Return |handle|.
+
+
+
+
+To
+create a new `FileSystemDirectoryHandle`
+given a [=/file system root=] |root| and a [=/file system path=] |path|
+in a [=/Realm=] |realm|:
+
+1. Let |handle| be a [=new=] {{FileSystemDirectoryHandle}} in |realm|.
+1. Set |handle|'s [=FileSystemHandle/locator=] to a [=/file system locator=] whose
+ [=file system locator/kind=] is "{{FileSystemHandleKind/directory}}",
+ [=file system locator/root=] is |root|, and
+ [=file system locator/path=] is |path|.
+1. Return |handle|.
+
+
+
+{{FileSystemDirectoryHandle}} objects are [=serializable objects=]. Their [=serialization steps=] and
+[=deserialization steps=] are the same as those for {{FileSystemHandle}}.
+
+### Directory iteration ### {#api-filesystemdirectoryhandle-asynciterable}
+
+
+ : for await (let [|name|, |handle|] of |directoryHandle|) {}
+ : for await (let [|name|, |handle|] of |directoryHandle| . entries()) {}
+ : for await (let |handle| of |directoryHandle| . values()) {}
+ : for await (let |name| of |directoryHandle| . keys()) {}
+ :: Iterates over all entries whose parent is the [=directory entry=]
+ [=locate an entry|locatable=] by |directoryHandle|'s [=FileSystemHandle/locator=].
+ Entries that are created or deleted while the iteration is in progress
+ might or might not be included. No guarantees are given either way.
+
+
+Issue(15): In the future we might want to add arguments to the async iterable declaration to
+support for example recursive iteration.
+
+
+The [=asynchronous iterator initialization steps=] for a
+{{FileSystemDirectoryHandle}} handle
+and its async iterator |iterator| are:
+
+1. Set |iterator|'s past results to an empty [=/set=].
+
+
+
+
+To [=get the next iteration result=] for a {{FileSystemDirectoryHandle}} |handle|
+and its async iterator |iterator|:
+
+1. Let |promise| be [=a new promise=].
+1. [=Enqueue the following steps=] to the [=file system queue=]:
+ 1. Let |directory| be the result of [=locating an entry=]
+ given |handle|'s [=FileSystemHandle/locator=].
+ 1. Let |accessResult| be the result of running |directory|'s
+ [=file system entry/query access=] given "`read`".
+
+ 1. [=Queue a storage task=] with |handle|'s [=relevant global object=] to
+ run these steps:
+ 1. If |accessResult|'s [=file system access result/permission state=]
+ is not "{{PermissionState/granted}}", [=/reject=] |promise| with a
+ {{DOMException}} of |accessResult|'s
+ [=file system access result/error name=] and abort these steps.:
+
+ 1. If |directory| is `null`, [=/reject=] |result| with a
+ "{{NotFoundError}}" {{DOMException}} and abort these steps.
+ 1. [=Assert=]: |directory| is a [=directory entry=].
+
+ 1. Let |child| be a [=/file system entry=] in
+ |directory|'s [=directory entry/children=], such that
+ |child|'s [=file system entry/name=] is not contained in
+ |iterator|'s [=past results=], or `null` if no such entry exists.
+
+ Note: This is intentionally very vague about the iteration order.
+ Different platforms and file systems provide different guarantees about
+ iteration order, and we want it to be possible to efficiently implement
+ this on all platforms. As such no guarantees are given about the exact
+ order in which elements are returned.
+
+ 1. If |child| is `null`, [=/resolve=] |promise| with `undefined` and
+ abort these steps.
+
+ 1. [=set/Append=] |child|'s [=file system entry/name=] to
+ |iterator|'s [=past results=].
+ 1. If |child| is a [=file entry=]:
+ 1. Let |result| be the result of
+
creating a child `FileSystemFileHandle` with
+ |handle|'s [=FileSystemHandle/locator=] and
+ |child|'s [=file system entry/name=] in |handle|'s [=relevant Realm=].
+ 1. Otherwise:
+ 1. Let |result| be the result of
+
creating a child `FileSystemDirectoryHandle` with
+ |handle|'s [=FileSystemHandle/locator=] and
+ |child|'s [=file system entry/name=] in |handle|'s [=relevant Realm=].
+ 1. [=/Resolve=] |promise| with
+ (|child|'s [=file system entry/name=], |result|).
+
+1. Return |promise|.
+
+
+
+### The {{FileSystemDirectoryHandle/getFileHandle()}} method ### {#api-filesystemdirectoryhandle-getfilehandle}
+
+
+ : |fileHandle| = await |directoryHandle| . {{FileSystemDirectoryHandle/getFileHandle()|getFileHandle}}(|name|)
+ : |fileHandle| = await |directoryHandle| . {{FileSystemDirectoryHandle/getFileHandle()|getFileHandle}}(|name|, { {{FileSystemGetFileOptions/create}}: false })
+ :: Returns a handle for a file named |name| in the [=directory entry=]
+ [=locate an entry|locatable=] by |directoryHandle|'s [=FileSystemHandle/locator=].
+ If no such file exists, this rejects.
+
+ : |fileHandle| = await |directoryHandle| . {{FileSystemDirectoryHandle/getFileHandle()|getFileHandle}}(|name|, { {{FileSystemGetFileOptions/create}}: true })
+ :: Returns a handle for a file named |name| in the [=directory entry=]
+ [=locate an entry|locatable=] by |directoryHandle|'s [=FileSystemHandle/locator=].
+ If no such file exists, this creates a new file. If no file with named |name| can be created this
+ rejects. Creation can fail because there already is a directory with the same name, because the
+ name uses characters that aren't supported in file names on the underlying file system, or
+ because the user agent for security reasons decided not to allow creation of the file.
+
+ This operation requires write permission, even if the file being returned already exists. If
+ this handle doesn't already have write permission, this could result in a prompt being shown to
+ the user. To get an existing file without needing write permission, call this method
+ with { {{FileSystemGetFileOptions/create}}: false }
.
+
+
+
+The
getFileHandle(|name|, |options|) method steps are:
+
+1. Let |result| be [=a new promise=].
+1. Let |realm| be [=this=]'s [=relevant Realm=].
+1. Let |locator| be [=this=]'s [=FileSystemHandle/locator=].
+1. Let |global| be [=this=]'s [=relevant global object=].
+1. [=Enqueue the following steps=] to the [=file system queue=]:
+ 1. If |name| is not a [=valid file name=], [=queue a storage task=] with
+ |global| to [=/reject=] |result| with a {{TypeError}} and
+ abort these steps.
+
+ 1. Let |entry| be the result of [=locating an entry=] given |locator|.
+ 1. If |options|["{{FileSystemGetFileOptions/create}}"] is true:
+ 1. Let |accessResult| be the result of running |entry|'s
+ [=file system entry/request access=] given "`readwrite`".
+ 1. Otherwise:
+ 1. Let |accessResult| be the result of running |entry|'s
+ [=file system entry/query access=] given "`read`".
+
+ 1. [=Queue a storage task=] with |global| to run these steps:
+ 1. If |accessResult|'s [=file system access result/permission state=]
+ is not "{{PermissionState/granted}}", [=/reject=] |result| with a
+ {{DOMException}} of |accessResult|'s
+ [=file system access result/error name=] and abort these steps.
+
+ 1. If |entry| is `null`, [=/reject=] |result| with a
+ "{{NotFoundError}}" {{DOMException}} and abort these steps.
+ 1. [=Assert=]: |entry| is a [=directory entry=].
+
+ 1. [=set/For each=] |child| of |entry|'s [=directory entry/children=]:
+ 1. If |child|'s [=file system entry/name=] equals |name|:
+ 1. If |child| is a [=directory entry=]:
+ 1. [=/Reject=] |result| with a
+ "{{TypeMismatchError}}" {{DOMException}} and abort these steps.
+ 1. [=/Resolve=] |result| with the result of
+
creating a child `FileSystemFileHandle` with |locator| and
+ |child|'s [=file system entry/name=] in |realm| and
+ abort these steps.
+ 1. If |options|["{{FileSystemGetFileOptions/create}}"] is false:
+ 1. [=/Reject=] |result| with a "{{NotFoundError}}" {{DOMException}} and
+ abort these steps.
+ 1. Let |child| be a new [=file entry=] whose [=query access=] and
+ [=request access=] algorithms are those of |entry|.
+ 1. Set |child|'s [=file system entry/name=] to |name|.
+ 1. Set |child|'s [=binary data=] to an empty [=byte sequence=].
+ 1. Set |child|'s [=modification timestamp=] to the current time.
+ 1. [=set/Append=] |child| to |entry|'s [=directory entry/children=].
+ 1. If creating |child| in the underlying file system throws an exception,
+ [=/reject=] |result| with that exception and abort these steps.
+
+ Issue(11): Better specify what possible exceptions this could throw.
+ 1. [=/Resolve=] |result| with the result of
+
creating a child `FileSystemFileHandle` with |locator| and
+ |child|'s [=file system entry/name=] in |realm|.
+1. Return |result|.
+
+
+
+### The {{FileSystemDirectoryHandle/getDirectoryHandle()}} method ### {#api-filesystemdirectoryhandle-getdirectoryhandle}
+
+
+ : |subdirHandle| = await |directoryHandle| . {{FileSystemDirectoryHandle/getDirectoryHandle()|getDirectoryHandle}}(|name|)
+ : |subdirHandle| = await |directoryHandle| . {{FileSystemDirectoryHandle/getDirectoryHandle()|getDirectoryHandle}}(|name|, { {{FileSystemGetDirectoryOptions/create}}: false })
+ :: Returns a handle for a directory named |name| in the [=directory entry=]
+ [=locate an entry|locatable=] by |directoryHandle|'s [=FileSystemHandle/locator=].
+ If no such directory exists, this rejects.
+
+ : |subdirHandle| = await |directoryHandle| . {{FileSystemDirectoryHandle/getDirectoryHandle()|getDirectoryHandle}}(|name|, { {{FileSystemGetDirectoryOptions/create}}: true })
+ :: Returns a handle for a directory named |name| in the [=directory entry=]
+ [=locate an entry|locatable=] by |directoryHandle|'s [=FileSystemHandle/locator=] .
+ If no such directory exists, this creates a new directory. If creating the
+ directory failed, this rejects. Creation can fail because there already is a file with the same
+ name, or because the name uses characters that aren't supported in file names on the underlying
+ file system.
+
+ This operation requires write permission, even if the directory being returned already exists.
+ If this handle doesn't already have write permission, this could result in a prompt being shown
+ to the user. To get an existing directory without needing write permission, call this method
+ with { {{FileSystemGetDirectoryOptions/create}}: false }
.
+
+
+
+The
getDirectoryHandle(|name|, |options|) method steps are:
+
+1. Let |result| be [=a new promise=].
+1. Let |realm| be [=this=]'s [=relevant Realm=].
+1. Let |locator| be [=this=]'s [=FileSystemHandle/locator=].
+1. Let |global| be [=this=]'s [=relevant global object=].
+1. [=Enqueue the following steps=] to the [=file system queue=]:
+ 1. If |name| is not a [=valid file name=], [=queue a storage task=] with
+ |global| to [=/reject=] |result| with a {{TypeError}} and
+ abort these steps.
+
+ 1. Let |entry| be the result of [=locating an entry=] given |locator|.
+ 1. If |options|["{{FileSystemGetDirectoryOptions/create}}"] is true:
+ 1. Let |accessResult| be the result of running |entry|'s
+ [=file system entry/request access=] given "`readwrite`".
+ 1. Otherwise:
+ 1. Let |accessResult| be the result of running |entry|'s
+ [=file system entry/query access=] given "`read`".
+
+ 1. [=Queue a storage task=] with |global| to run these steps:
+ 1. If |accessResult|'s [=file system access result/permission state=]
+ is not "{{PermissionState/granted}}", [=/reject=] |result| with a
+ {{DOMException}} of |accessResult|'s
+ [=file system access result/error name=] and abort these steps.
+
+ 1. If |entry| is `null`, [=/reject=] |result| with a
+ "{{NotFoundError}}" {{DOMException}} and abort these steps.
+ 1. [=Assert=]: |entry| is a [=directory entry=].
+
+ 1. [=set/For each=] |child| of |entry|'s [=directory entry/children=]:
+ 1. If |child|'s [=file system entry/name=] equals |name|:
+ 1. If |child| is a [=file entry=]:
+ 1. [=/Reject=] |result| with a
+ "{{TypeMismatchError}}" {{DOMException}} and abort these steps.
+ 1. [=/Resolve=] |result| with the result of
+
creating a child `FileSystemDirectoryHandle` with
+ |locator| and |child|'s [=file system entry/name=] in |realm| and
+ abort these steps.
+ 1. If |options|["{{FileSystemGetFileOptions/create}}"] is false:
+ 1. [=/Reject=] |result| with a "{{NotFoundError}}" {{DOMException}} and
+ abort these steps.
+ 1. Let |child| be a new [=directory entry=] whose [=query access=] and
+ [=request access=] algorithms are those of |entry|.
+ 1. Set |child|'s [=file system entry/name=] to |name|.
+ 1. Set |child|'s [=directory entry/children=] to an empty [=/set=].
+ 1. [=set/Append=] |child| to |entry|'s [=directory entry/children=].
+ 1. If creating |child| in the underlying file system throws an exception,
+ [=/reject=] |result| with that exception and abort these steps.
+
+ Issue(11): Better specify what possible exceptions this could throw.
+ 1. [=/Resolve=] |result| with the result of
+
creating a child `FileSystemDirectoryHandle` with
+ |locator| and |child|'s [=file system entry/name=] in |realm|.
+1. Return |result|.
+
+
+
+### The {{FileSystemDirectoryHandle/removeEntry()}} method ### {#api-filesystemdirectoryhandle-removeentry}
+
+
+ : await |directoryHandle| . {{FileSystemDirectoryHandle/removeEntry()|removeEntry}}(|name|)
+ : await |directoryHandle| . {{FileSystemDirectoryHandle/removeEntry()|removeEntry}}(|name|, { {{FileSystemRemoveOptions/recursive}}: false })
+ :: If the [=directory entry=] [=locate an entry|locatable=] by |directoryHandle|'s
+ [=FileSystemHandle/locator=] contains a file named |name|, or an empty
+ directory named |name|, this will attempt to delete that file or directory.
+
+ Attempting to delete a file or directory that does not exist is considered success,
+ while attempting to delete a non-empty directory will result in a promise rejection.
+
+ : await |directoryHandle| . {{FileSystemDirectoryHandle/removeEntry()|removeEntry}}(|name|, { {{FileSystemRemoveOptions/recursive}}: true })
+ :: Removes the [=/file system entry=] named |name| in the [=directory entry=]
+ [=locate an entry|locatable=] by |directoryHandle|'s [=FileSystemHandle/locator=].
+ If that entry is a directory, its contents will also be deleted recursively.
+
+ Attempting to delete a file or directory that does not exist is considered success.
+
+
+
+The removeEntry(|name|, |options|) method steps are:
+
+1. Let |result| be [=a new promise=].
+1. Let |locator| be [=this=]'s [=FileSystemHandle/locator=].
+1. Let |global| be [=this=]'s [=relevant global object=].
+1. [=Enqueue the following steps=] to the [=file system queue=]:
+ 1. If |name| is not a [=valid file name=], [=queue a storage task=] with
+ |global| to [=/reject=] |result| with a {{TypeError}} and
+ abort these steps.
+
+ 1. Let |entry| be the result of [=locating an entry=] given |locator|.
+ 1. Let |accessResult| be the result of running |entry|'s
+ [=file system entry/request access=] given "`readwrite`".
+
+ 1. [=Queue a storage task=] with |global| to run these steps:
+ 1. If |accessResult|'s [=file system access result/permission state=]
+ is not "{{PermissionState/granted}}", [=/reject=] |result| with a
+ {{DOMException}} of |accessResult|'s
+ [=file system access result/error name=] and abort these steps.
+
+ 1. If |entry| is `null`, [=/reject=] |result| with a
+ "{{NotFoundError}}" {{DOMException}} and abort these steps.
+ 1. [=Assert=]: |entry| is a [=directory entry=].
+
+ 1. [=set/For each=] |child| of |entry|'s [=directory entry/children=]:
+ 1. If |child|'s [=file system entry/name=] equals |name|:
+ 1. If |child| is a [=directory entry=]:
+ 1. If |child|'s [=directory entry/children=] is not
+ [=set/is empty|empty=] and
+ |options|["{{FileSystemRemoveOptions/recursive}}"] is false:
+ 1. [=/Reject=] |result| with an
+ "{{InvalidModificationError}}" {{DOMException}} and
+ abort these steps.
+ 1. [=set/Remove=] |child| from |entry|'s [=directory entry/children=].
+ 1. If removing |child| in the underlying file system throws an
+ exception, [=/reject=] |result| with that exception and
+ abort these steps.
+
+ Note: If {{FileSystemRemoveOptions/recursive}} is true, the removal can fail
+ non-atomically. Some files or directories might have been removed while other files
+ or directories still exist.
+
+ Issue(11): Better specify what possible exceptions this could throw.
+ 1. [=/Resolve=] |result| with `undefined`.
+ 1. [=/Reject=] |result| with a "{{NotFoundError}}" {{DOMException}}.
+1. Return |result|.
+
+
+
+### The {{FileSystemDirectoryHandle/resolve()}} method ### {#api-filesystemdirectoryhandle-resolve}
+
+
+ : |path| = await |directory| . {{FileSystemDirectoryHandle/resolve()|resolve}}( |child| )
+ :: If |child| is equal to |directory|, |path| will be an empty array.
+ :: If |child| is a direct child of |directory|, |path| will be an array containing |child|'s name.
+ :: If |child| is a descendant of |directory|, |path| will be an array containing the names of
+ all the intermediate directories and |child|'s name as last element.
+ For example if |directory| represents `/home/user/project`
+ and |child| represents `/home/user/project/foo/bar`, this will return
+ `['foo', 'bar']`.
+ :: Otherwise (|directory| and |child| are not related), |path| will be null.
+
+
+
+
+// Assume we at some point got a valid directory handle.
+const dir_ref = current_project_dir;
+if (!dir_ref) return;
+
+// Now get a file reference:
+const file_ref = await dir_ref.getFileHandle(filename, { create: true });
+
+// Check if file_ref exists inside dir_ref:
+const relative_path = await dir_ref.resolve(file_ref);
+if (relative_path === null) {
+ // Not inside dir_ref.
+} else {
+ // relative_path is an array of names, giving the relative path
+ // from dir_ref to the file that is represented by file_ref:
+ assert relative_path.pop() === file_ref.name;
+
+ let entry = dir_ref;
+ for (const name of relative_path) {
+ entry = await entry.getDirectory(name);
+ }
+ entry = await entry.getFile(file_ref.name);
+
+ // Now |entry| will represent the same file on disk as |file_ref|.
+ assert await entry.isSameEntry(file_ref) === true;
+}
+
+
+
+
+The resolve(|possibleDescendant|) method steps are
+to return the result of [=file system locator/resolving=]
+|possibleDescendant|'s [=FileSystemHandle/locator=]
+relative to [=this=]'s [=FileSystemHandle/locator=].
+
+
+
+## The {{FileSystemWritableFileStream}} interface ## {#api-filesystemwritablefilestream}
+
+
+enum WriteCommandType {
+ "write",
+ "seek",
+ "truncate",
+};
+
+dictionary WriteParams {
+ required WriteCommandType type;
+ unsigned long long? size;
+ unsigned long long? position;
+ (BufferSource or Blob or USVString)? data;
+};
+
+typedef (BufferSource or Blob or USVString or WriteParams) FileSystemWriteChunkType;
+
+[Exposed=(Window,Worker), SecureContext]
+interface FileSystemWritableFileStream : WritableStream {
+ Promise write(FileSystemWriteChunkType data);
+ Promise seek(unsigned long long position);
+ Promise truncate(unsigned long long size);
+};
+
+
+A {{FileSystemWritableFileStream}} has an associated \[[file]] (a [=file entry=]).
+
+A {{FileSystemWritableFileStream}} has an associated \[[buffer]] (a [=byte sequence=]).
+It is initially empty.
+
+Note: This buffer can get arbitrarily large, so it is expected that implementations will not keep this in memory,
+but instead use a temporary file for this. All access to \[[buffer]] is done in promise returning methods and
+algorithms, so even though operations on it seem sync, implementations can implement them async.
+
+A {{FileSystemWritableFileStream}} has an associated \[[seekOffset]] (a number).
+It is initially 0.
+
+
+A {{FileSystemWritableFileStream}} object is a {{WritableStream}} object with additional
+convenience methods, which operates on a single file on disk.
+
+Upon creation, an underlying sink will have been created and the stream will be usable.
+All operations executed on the stream are queuable and producers will be able to respond to backpressure.
+
+The underlying sink's write method, and therefore {{WritableStreamDefaultWriter/write()|WritableStreamDefaultWriter's write()}}
+method, will accept byte-like data or {{WriteParams}} as input.
+
+The {{FileSystemWritableFileStream}} has a file position cursor initialized at byte offset 0 from the top of the file.
+When using {{FileSystemWritableFileStream/write()|write()}} or by using WritableStream capabilities through the {{WritableStreamDefaultWriter/write()|WritableStreamDefaultWriter's write()}} method, this position will be advanced based on the number of bytes written through the stream object.
+
+Similarly, when piping a {{ReadableStream}} into a {{FileSystemWritableFileStream}} object, this position is updated with the number of bytes that passed through the stream.
+
+{{WritableStream/getWriter()|getWriter()}} returns an instance of {{WritableStreamDefaultWriter}}.
+
+
+
+To
+
create a new `FileSystemWritableFileStream`
+given a [=file entry=] |file| in a [=/Realm=] |realm|:
+
+1. Let |stream| be a [=new=] {{FileSystemWritableFileStream}} in |realm|.
+1. Set |stream|'s [=FileSystemWritableFileStream/[[file]]=] to |file|.
+1. Let |writeAlgorithm| be an algorithm which takes a |chunk| argument
+ and returns the result of running the [=write a chunk=] algorithm with |stream| and |chunk|.
+1. Let |closeAlgorithm| be these steps:
+ 1. Let |closeResult| be [=a new promise=].
+ 1. [=Enqueue the following steps=] to the [=file system queue=]:
+ 1. Let |accessResult| be the result of running |file|'s
+ [=file system entry/query access=] given "`readwrite`".
+
+ 1. [=Queue a storage task=] with |file|'s [=relevant global object=]
+ to run these steps:
+ 1. If |accessResult|'s [=file system access result/permission state=]
+ is not "{{PermissionState/granted}}", [=/reject=] |closeResult|
+ with a {{DOMException}} of |accessResult|'s
+ [=file system access result/error name=] and abort these steps.
+ 1. Run [=implementation-defined=] malware scans and safe browsing checks.
+ If these checks fail, [=/reject=] |closeResult| with an
+ "{{AbortError}}" {{DOMException}} and abort these steps.
+ 1. Set |stream|'s [=FileSystemWritableFileStream/[[file]]=]'s
+ [=file entry/binary data=] to |stream|'s [=[[buffer]]=].
+ If that throws an exception, [=/reject=] |closeResult| with that
+ exception and abort these steps.
+
+ Note: It is expected that this atomically updates the contents of the
+ file on disk being written to.
+
+ 1. [=Enqueue the following steps=] to the [=file system queue=]:
+ 1. [=file entry/lock/release|Release the lock=] on
+ |stream|'s [=FileSystemWritableFileStream/[[file]]=].
+ 1. [=Queue a storage task=] with |file|'s [=relevant global object=]
+ to [=/resolve=] |closeResult| with `undefined`.
+
+ 1. Return |closeResult|.
+1. Let |abortAlgorithm| be these steps:
+ 1. [=enqueue steps|Enqueue this step=] to the [=file system queue=]:
+ 1. [=file entry/lock/release|Release the lock=] on
+ |stream|'s [=FileSystemWritableFileStream/[[file]]=].
+1. Let |highWaterMark| be 1.
+1. Let |sizeAlgorithm| be an algorithm that returns `1`.
+1. [=WritableStream/Set up=] |stream| with
writeAlgorithm set to |writeAlgorithm|,
closeAlgorithm set to |closeAlgorithm|,
abortAlgorithm set to |abortAlgorithm|,
highWaterMark set to |highWaterMark|, and
sizeAlgorithm set to |sizeAlgorithm|.
+1. Return |stream|.
+
+
+
+
+The
write a chunk algorithm,
+given a {{FileSystemWritableFileStream}} |stream| and |chunk|,
+runs these steps:
+
+1. Let |input| be the result of [=converted to an IDL value|converting=] |chunk| to a {{FileSystemWriteChunkType}}.
+ If this throws an exception, then return [=a promise rejected with=] that exception.
+1. Let |p| be [=a new promise=].
+1. [=Enqueue the following steps=] to the [=file system queue=]:
+ 1. Let |accessResult| be the result of running
+ |stream|'s [=FileSystemWritableFileStream/[[file]]=]'s
+ [=file system entry/query access=] given "`readwrite`".
+
+ 1. [=Queue a storage task=] with |stream|'s [=relevant global object=] to
+ run these steps:
+ 1. If |accessResult|'s [=file system access result/permission state=]
+ is not "{{PermissionState/granted}}", [=/reject=] |p| with a
+ {{DOMException}} of |accessResult|'s
+ [=file system access result/error name=] and abort these steps.
+
+ 1. Let |command| be |input|["{{WriteParams/type}}"] if
+ |input| is a [=/dictionary=]; otherwise "{{WriteCommandType/write}}".
+ 1. If |command| is "{{WriteCommandType/write}}":
+ 1. If |input| is `undefined` or |input| is a [=/dictionary=] and
+ |input|["{{WriteParams/data}}"] does not [=map/exists|exist=],
+ [=/reject=] |p| with a {{TypeError}} and abort these steps.
+ 1. Let |data| be |input|["{{WriteParams/data}}"] if
+ |input| is a [=/dictionary=]; otherwise |input|.
+ 1. Let |writePosition| be |stream|'s [=[[seekOffset]]=].
+ 1. If |input| is a [=/dictionary=] and |input|["{{WriteParams/position}}"]
+ [=map/exists=], set |writePosition| to
+ |input|["{{WriteParams/position}}"].
+ 1. Let |oldSize| be |stream|'s [=[[buffer]]=]'s [=byte sequence/length=].
+ 1. If |data| is a {{BufferSource}},
+ let |dataBytes| be [=get a copy of the buffer source|a copy of=] |data|.
+ 1. Otherwise, if |data| is a {{Blob}}:
+ 1. Let |dataBytes| be the result of performing the
+
read operation on |data|.
+ If this throws an exception, [=/reject=] |p| with that exception
+ and abort these steps.
+ 1. Otherwise:
+ 1. [=Assert=]: |data| is a {{USVString}}.
+ 1. Let |dataBytes| be the result of [=UTF-8 encoding=] |data|.
+ 1. If |writePosition| is larger than |oldSize|,
+ append |writePosition| - |oldSize| 0x00 (NUL) bytes to the end of
+ |stream|'s [=[[buffer]]=].
+
+ Note: Implementations are expected to behave as if the skipped over file contents
+ are indeed filled with NUL bytes. That doesn't mean these bytes have to actually be
+ written to disk and take up disk space. Instead most file systems support so called
+ sparse files, where these NUL bytes don't take up actual disk space.
+
+ 1. Let |head| be a [=byte sequence=] containing the first |writePosition|
+ bytes of |stream|'s[=[[buffer]]=].
+ 1. Let |tail| be an empty [=byte sequence=].
+ 1. If |writePosition| + |data|'s [=byte sequence/length=] is smaller than |oldSize|:
+ 1. Let |tail| be a [=byte sequence=] containing the last
+ |oldSize| - (|writePosition| + |data|'s [=byte sequence/length=])
+ bytes of |stream|'s [=[[buffer]]=].
+ 1. Set |stream|'s [=[[buffer]]=] to the concatenation of
+ |head|, |data| and |tail|.
+ 1. If the operations modifying |stream|'s [=[[buffer]]=] in the
+ previous steps failed due to exceeding the [=storage quota=],
+ [=/reject=] |p| with a "{{QuotaExceededError}}" {{DOMException}}
+ and abort these steps, leaving |stream|'s [=[[buffer]]=] unmodified.
+
+ Note: [=Storage quota=] only applies to files stored in a
+ [=/bucket file system=].
+ However this operation could still fail for other files,
+ for example if the disk being written to runs out of disk space.
+ 1. Set |stream|'s [=[[seekOffset]]=] to |writePosition| + |data|'s
+ [=byte sequence/length=].
+ 1. [=/Resolve=] |p|.
+ 1. Otherwise, if |command| is "{{WriteCommandType/seek}}":
+ 1. [=Assert=]: |chunk| is a [=/dictionary=].
+ 1. If |chunk|["{{WriteParams/position}}"] does not [=map/exists|exist=],
+ [=/reject=] |p| with a {{TypeError}} and abort these steps.
+ 1. Set |stream|'s [=[[seekOffset]]=] to
+ |chunk|["{{WriteParams/position}}"].
+ 1. [=/Resolve=] |p|.
+ 1. Otherwise, if |command| is "{{WriteCommandType/truncate}}":
+ 1. [=Assert=]: |chunk| is a [=/dictionary=].
+ 1. If |chunk|["{{WriteParams/size}}"] does not [=map/exists|exist=],
+ [=/reject=] |p| with a {{TypeError}} and abort these steps.
+ 1. Let |newSize| be |chunk|["{{WriteParams/size}}"].
+ 1. Let |oldSize| be |stream|'s [=[[buffer]]=]'s [=byte sequence/length=].
+ 1. If |newSize| is larger than |oldSize|:
+ 1. Set |stream|'s [=[[buffer]]=] to a [=byte sequence=] formed by
+ concating |stream|'s [=[[buffer]]=] with a [=byte sequence=]
+ containing |newSize|-|oldSize| `0x00` bytes.
+ 1. If the operation in the previous step failed due to exceeding the [=storage quota=],
+ [=/reject=] |p| with a "{{QuotaExceededError}}" {{DOMException}} and
+ abort these steps, leaving |stream|'s [=[[buffer]]=] unmodified.
+
+ Note: [=Storage quota=] only applies to files stored in a
+ [=/bucket file system=].
+ However this operation could still fail for other files,
+ for example if the disk being written to runs out of disk space.
+ 1. Otherwise, if |newSize| is smaller than |oldSize|:
+ 1. Set |stream|'s [=[[buffer]]=] to a [=byte sequence=] containing the
+ first |newSize| bytes in |stream|'s [=[[buffer]]=].
+ 1. If |stream|'s [=[[seekOffset]]=] is bigger than |newSize|,
+ set |stream|'s [=[[seekOffset]]=] to |newSize|.
+ 1. [=/Resolve=] |p|.
+1. Return |p|.
+
+
+
+### The {{FileSystemWritableFileStream/write()}} method ### {#api-filesystemwritablefilestream-write}
+
+
+ : await |stream| . {{FileSystemWritableFileStream/write()|write}}(|data|)
+ : await |stream| . {{FileSystemWritableFileStream/write()|write}}({
+ {{WriteParams/type}}: "{{WriteCommandType/write}}",
+ {{WriteParams/data}}: |data| })
+ :: Writes the content of |data| into the file associated with |stream| at the current file
+ cursor offset.
+
+ No changes are written to the actual file on disk until the stream has been closed.
+ Changes are typically written to a temporary file instead.
+
+ : await |stream| . {{FileSystemWritableFileStream/write()|write}}({
+ {{WriteParams/type}}: "{{WriteCommandType/write}}",
+ {{WriteParams/position}}: |position|,
+ {{WriteParams/data}}: |data| })
+ :: Writes the content of |data| into the file associated with |stream| at |position|
+ bytes from the top of the file. Also updates the current file cursor offset to the
+ end of the written data.
+
+ No changes are written to the actual file on disk until the stream has been closed.
+ Changes are typically written to a temporary file instead.
+
+ : await |stream| . {{FileSystemWritableFileStream/write()|write}}({
+ {{WriteParams/type}}: "{{WriteCommandType/seek}}",
+ {{WriteParams/position}}: |position| })
+ :: Updates the current file cursor offset the |position| bytes from the top of the file.
+
+ : await |stream| . {{FileSystemWritableFileStream/write()|write}}({
+ {{WriteParams/type}}: "{{WriteCommandType/truncate}}",
+ {{WriteParams/size}}: |size| })
+ :: Resizes the file associated with |stream| to be |size| bytes long. If |size| is larger than
+ the current file size this pads the file with null bytes, otherwise it truncates the file.
+
+ The file cursor is updated when {{truncate}} is called. If the cursor is smaller than |size|,
+ it remains unchanged. If the cursor is larger than |size|, it is set to |size| to
+ ensure that subsequent writes do not error.
+
+ No changes are written to the actual file until on disk until the stream has been closed.
+ Changes are typically written to a temporary file instead.
+
+
+
+The write(|data|) method steps are:
+
+1. Let |writer| be the result of [=WritableStream/getting a writer=] for [=this=].
+1. Let |result| be the result of [=WritableStreamDefaultWriter/writing a chunk=] to |writer| given
+ |data|.
+1. [=WritableStreamDefaultWriter/Release=] |writer|.
+1. Return |result|.
+
+
+
+### The {{FileSystemWritableFileStream/seek()}} method ### {#api-filesystemwritablefilestream-seek}
+
+
+ : await |stream| . {{FileSystemWritableFileStream/seek()|seek}}(|position|)
+ :: Updates the current file cursor offset the |position| bytes from the top of the file.
+
+
+
+The seek(|position|) method steps are:
+
+1. Let |writer| be the result of [=WritableStream/getting a writer=] for [=this=].
+1. Let |result| be the result of [=WritableStreamDefaultWriter/writing a chunk=] to |writer| given
+ «[ "{{WriteParams/type}}" → "{{WriteCommandType/seek}}", "{{WriteParams/position}}" →
+ |position| ]».
+1. [=WritableStreamDefaultWriter/Release=] |writer|.
+1. Return |result|.
+
+
+
+### The {{FileSystemWritableFileStream/truncate()}} method ### {#api-filesystemwritablefilestream-truncate}
+
+
+ : await |stream| . {{FileSystemWritableFileStream/truncate()|truncate}}(|size|)
+ :: Resizes the file associated with |stream| to be |size| bytes long. If |size| is larger than
+ the current file size this pads the file with null bytes, otherwise it truncates the file.
+
+ The file cursor is updated when {{truncate}} is called. If the cursor is smaller than |size|,
+ it remains unchanged. If the cursor is larger than |size|, it is set to |size| to
+ ensure that subsequent writes do not error.
+
+ No changes are written to the actual file until on disk until the stream has been closed.
+ Changes are typically written to a temporary file instead.
+
+
+
+The truncate(|size|) method steps are:
+
+1. Let |writer| be the result of [=WritableStream/getting a writer=] for [=this=].
+1. Let |result| be the result of [=WritableStreamDefaultWriter/writing a chunk=] to |writer| given
+ «[ "{{WriteParams/type}}" → "{{WriteCommandType/truncate}}", "{{WriteParams/size}}" →
+ |size| ]».
+1. [=WritableStreamDefaultWriter/Release=] |writer|.
+1. Return |result|.
+
+
+
+## The {{FileSystemSyncAccessHandle}} interface ## {#api-filesystemsyncaccesshandle}
+
+
+
+dictionary FileSystemReadWriteOptions {
+ [EnforceRange] unsigned long long at;
+};
+
+[Exposed=DedicatedWorker, SecureContext]
+interface FileSystemSyncAccessHandle {
+ unsigned long long read(AllowSharedBufferSource buffer,
+ optional FileSystemReadWriteOptions options = {});
+ unsigned long long write(AllowSharedBufferSource buffer,
+ optional FileSystemReadWriteOptions options = {});
+
+ undefined truncate([EnforceRange] unsigned long long newSize);
+ unsigned long long getSize();
+ undefined flush();
+ undefined close();
+};
+
+
+
+A {{FileSystemSyncAccessHandle}} has an associated \[[file]]
+(a [=file entry=]).
+
+A {{FileSystemSyncAccessHandle}} has an associated \[[state]],
+a string that may exclusively be "`open`" or "`closed`".
+
+A {{FileSystemSyncAccessHandle}} is an object that is capable of reading from/writing to,
+as well as obtaining and changing the size of, a single file.
+
+A {{FileSystemSyncAccessHandle}} offers synchronous methods. This allows for higher performance on
+contexts where asynchronous operations come with high overhead, e.g., WebAssembly.
+
+A {{FileSystemSyncAccessHandle}} has a file position cursor initialized at byte offset 0 from the top of the file.
+
+
+To
+create a new `FileSystemSyncAccessHandle`
+given a [=file entry=] |file| in a [=/Realm=] |realm|:
+
+1. Let |handle| be a [=new=] {{FileSystemSyncAccessHandle}} in |realm|.
+1. Set |handle|'s [=FileSystemSyncAccessHandle/[[file]]=] to |file|.
+1. Set |handle|'s [=FileSystemSyncAccessHandle/[[state]]=] to "`open`".
+1. Return |handle|.
+
+
+
+### The {{FileSystemSyncAccessHandle/read()}} method ### {#api-filesystemsyncaccesshandle-read}
+
+
+ : |handle| . {{FileSystemSyncAccessHandle/read()|read}}(|buffer|)
+ : |handle| . {{FileSystemSyncAccessHandle/read()|read}}(|buffer|, { {{FileSystemReadWriteOptions/at}} })
+ :: Reads the contents of the file associated with |handle| into |buffer|, optionally at a given offset.
+ :: The file cursor is updated when {{FileSystemSyncAccessHandle/read()}} is called to point to the byte after the last byte read.
+
+
+Issue(35): Specify how Access Handles should react when reading from a file that has been modified externally.
+
+
+The read(|buffer|, {{FileSystemReadWriteOptions}}: |options|) method steps are:
+
+1. If [=this=]'s [=[[state]]=] is "`closed`",
+ [=throw=] an "{{InvalidStateError}}" {{DOMException}}.
+1. Let |bufferSize| be |buffer|'s [=byte length=].
+1. Let |fileContents| be [=this=]'s [=FileSystemSyncAccessHandle/[[file]]=]'s [=file entry/binary data=].
+1. Let |fileSize| be |fileContents|'s [=byte sequence/length=].
+1. Let |readStart| be |options|["{{FileSystemReadWriteOptions/at}}"] if
+ |options|["{{FileSystemReadWriteOptions/at}}"] [=map/exists=]; otherwise
+ [=this=]'s [=FileSystemSyncAccessHandle/file position cursor=].
+1. If the underlying file system does not support reading from a file offset of
+ |readStart|, [=throw=] a {{TypeError}}.
+1. If |readStart| is larger than |fileSize|:
+ 1. Set [=this=]'s [=FileSystemSyncAccessHandle/file position cursor=] to |fileSize|.
+ 1. Return 0.
+1. Let |readEnd| be |readStart| + (|bufferSize| − 1).
+1. If |readEnd| is larger than |fileSize|, set |readEnd| to |fileSize|.
+1. Let |bytes| be a [=byte sequence=] containing the bytes from |readStart| to |readEnd| of |fileContents|.
+1. Let |result| be |bytes|'s [=byte sequence/length=].
+1. If the operations reading from |fileContents| in the previous steps failed:
+ 1. If there were partial reads and the number of bytes that were read into |bytes| is known,
+ set |result| to the number of read bytes.
+ 1. Otherwise set |result| to 0.
+1. Let |arrayBuffer| be |buffer|'s [=underlying buffer=].
+1. [=ArrayBuffer/write|Write=] |bytes| into |arrayBuffer|.
+1. Set [=this=]'s [=FileSystemSyncAccessHandle/file position cursor=] to |readStart| + |result|.
+1. Return |result|.
+
+
+
+### The {{FileSystemSyncAccessHandle/write()}} method ### {#api-filesystemsyncaccesshandle-write}
+
+
+ : |handle| . {{FileSystemSyncAccessHandle/write()|write}}(|buffer|)
+ : |handle| . {{FileSystemSyncAccessHandle/write()|write}}(|buffer|, { {{FileSystemReadWriteOptions/at}} })
+ :: Writes the content of |buffer| into the file associated with |handle|, optionally at a given offset, and returns the number of written bytes.
+ Checking the returned number of written bytes allows callers to detect and handle errors and partial writes.
+ :: The file cursor is updated when {{write}} is called to point to the byte after the last byte written.
+
+
+
+
+The write(|buffer|, {{FileSystemReadWriteOptions}}: |options|) method steps are:
+
+1. If [=this=]'s [=[[state]]=] is "`closed`",
+ [=throw=] an "{{InvalidStateError}}" {{DOMException}}.
+1. Let |writePosition| be |options|["{{FileSystemReadWriteOptions/at}}"] if
+ |options|["{{FileSystemReadWriteOptions/at}}"] [=map/exists=]; otherwise
+ [=this=]'s [=FileSystemSyncAccessHandle/file position cursor=].
+1. If the underlying file system does not support writing to a file offset of
+ |writePosition|, [=throw=] a {{TypeError}}.
+1. Let |fileContents| be a copy of [=this=]'s
+ [=FileSystemSyncAccessHandle/[[file]]=]'s [=file entry/binary data=].
+1. Let |oldSize| be |fileContents|'s [=byte sequence/length=].
+1. Let |bufferSize| be |buffer|'s [=byte length=].
+1. If |writePosition| is larger than |oldSize|,
+ append |writePosition| − |oldSize| 0x00 (NUL) bytes to the end of |fileContents|.
+
+ Note: Implementations are expected to behave as if the skipped over file contents
+ are indeed filled with NUL bytes. That doesn't mean these bytes have to actually be
+ written to disk and take up disk space. Instead most file systems support so called
+ sparse files, where these NUL bytes don't take up actual disk space.
+
+1. Let |head| be a [=byte sequence=] containing the first |writePosition| bytes of |fileContents|.
+1. Let |tail| be an empty [=byte sequence=].
+1. If |writePosition| + |bufferSize| is smaller than |oldSize|:
+ 1. Set |tail| to a [=byte sequence=] containing the last
+ |oldSize| − (|writePosition| + |bufferSize|) bytes of |fileContents|.
+1. Let |newSize| be |head|'s [=byte sequence/length=] + |bufferSize| + |tail|'s [=byte sequence/length=].
+1. If |newSize| − |oldSize| exceeds the available [=storage quota=],
+ [=throw=] a "{{QuotaExceededError}}" {{DOMException}}.
+1. Set [=this=]'s [=FileSystemSyncAccessHandle/[[file]]=]'s
+ [=file entry/binary data=] to the concatenation of
+ |head|, the contents of |buffer| and |tail|.
+
+ Note: The mechanism used to access buffer's contents is left purposely vague.
+ It is likely that implementations will choose to focus on performance by issuing
+ direct write calls to the host operating system (instead of creating a copy of buffer),
+ which prevents a detailed specification of the write order and the results of partial writes.
+
+1. If the operations modifying the [=this=]'s[=FileSystemSyncAccessHandle/[[file]]=]'s
+ [=file entry/binary data=] in the previous steps failed:
+ 1. If there were partial writes and the number of bytes that were written from |buffer| is known:
+ 1. Let |bytesWritten| be the number of bytes that were written from |buffer|.
+ 1. Set [=this=]'s [=FileSystemSyncAccessHandle/file position cursor=] to |writePosition| + |bytesWritten|.
+ 1. Return |bytesWritten|.
+ 1. Otherwise [=throw=] an "{{InvalidStateError}}" {{DOMException}}.
+1. Set [=this=]'s [=FileSystemSyncAccessHandle/file position cursor=] to |writePosition| + |bufferSize|.
+1. Return |bufferSize|.
+
+
+
+### The {{FileSystemSyncAccessHandle/truncate()}} method ### {#api-filesystemsyncaccesshandle-truncate}
+
+
+ : |handle| . {{FileSystemSyncAccessHandle/truncate()|truncate}}(|newSize|)
+ :: Resizes the file associated with |handle| to be |newSize| bytes long. If |newSize| is larger than the current file size this pads the file with null bytes; otherwise it truncates the file.
+
+ :: The file cursor is updated when {{truncate}} is called. If the cursor is smaller than |newSize|, it remains unchanged. If the cursor is larger than |newSize|, it is set to |newSize|.
+
+
+
+The truncate(|newSize|) method steps are:
+
+1. If [=this=]'s [=[[state]]=] is "`closed`",
+ [=throw=] an "{{InvalidStateError}}" {{DOMException}}.
+1. Let |fileContents| be a copy of [=this=]'s
+ [=FileSystemSyncAccessHandle/[[file]]=]'s [=file entry/binary data=].
+1. Let |oldSize| be the [=byte sequence/length=] of [=this=]'s
+ [=FileSystemSyncAccessHandle/[[file]]=]'s [=file entry/binary data=].
+1. If the underlying file system does not support setting a file's size to
+ |newSize|, [=throw=] a {{TypeError}}.
+1. If |newSize| is larger than |oldSize|:
+ 1. If |newSize| − |oldSize| exceeds the available [=storage quota=],
+ [=throw=] a "{{QuotaExceededError}}" {{DOMException}}.
+ 1. Set [=this=]'s [=FileSystemSyncAccessHandle/[[file]]=]'s to a
+ [=byte sequence=] formed by concatenating |fileContents| with a
+ [=byte sequence=] containing |newSize| − |oldSize| 0x00 bytes.
+ 1. If the operations modifying the [=this=]'s
+ [=FileSystemSyncAccessHandle/[[file]]=]'s [=file entry/binary data=]
+ in the previous steps failed,
+ [=throw=] an "{{InvalidStateError}}" {{DOMException}}.
+1. Otherwise, if |newSize| is smaller than |oldSize|:
+ 1. Set [=this=]'s [=FileSystemSyncAccessHandle/[[file]]=]'s to a
+ [=byte sequence=] containing the first |newSize| bytes in |fileContents|.
+ 1. If the operations modifying the [=this=]'s
+ [=FileSystemSyncAccessHandle/[[file]]=]'s [=file entry/binary data=]
+ in the previous steps failed,
+ [=throw=] an "{{InvalidStateError}}" {{DOMException}}.
+1. If [=this=]'s [=FileSystemSyncAccessHandle/file position cursor=] is greater than |newSize|, then set [=FileSystemSyncAccessHandle/file position cursor=] to |newSize|.
+
+
+
+### The {{FileSystemSyncAccessHandle/getSize()}} method ### {#api-filesystemsyncaccesshandle-getsize}
+
+
+ : |handle| . {{FileSystemSyncAccessHandle/getSize()}}
+ :: Returns the size of the file associated with |handle| in bytes.
+
+
+
+The getSize() method steps are:
+
+1. If [=this=]'s [=[[state]]=] is "`closed`",
+ [=throw=] an "{{InvalidStateError}}" {{DOMException}}.
+1. Return [=this=]'s [=FileSystemSyncAccessHandle/[[file]]=]'s
+ [=file entry/binary data=]'s [=byte sequence/length=].
+
+
+
+### The {{FileSystemSyncAccessHandle/flush()}} method ### {#api-filesystemsyncaccesshandle-flush}
+
+
+ : |handle| . {{FileSystemSyncAccessHandle/flush()}}
+ :: Ensures that the contents of the file associated with |handle| contain all the modifications done through {{FileSystemSyncAccessHandle/write()}}.
+
+
+
+The flush() method steps are:
+
+1. If [=this=]'s [=[[state]]=] is "`closed`",
+ [=throw=] an "{{InvalidStateError}}" {{DOMException}}.
+1. Attempt to transfer all cached modifications of the file's content to the
+ file system's underlying storage device.
+
+ Note: This is also known as flushing. This may be a no-op on some file
+ systems, such as in-memory file systems, which do not have a "disk" to flush
+ to.
+
+
+
+### The {{FileSystemSyncAccessHandle/close()}} method ### {#api-filesystemsyncaccesshandle-close}
+
+
+ : |handle| . {{FileSystemSyncAccessHandle/close()}}
+ :: Closes the access handle or no-ops if the access handle is already closed.
+ This disables any further operations on it and
+ [=file entry/lock/release|releases the lock=] on the
+ [=FileSystemSyncAccessHandle/[[file]]=] associated with |handle|.
+
+
+
+The close() method steps are:
+
+1. If [=this=]'s [=[[state]]=] is "`closed`", return.
+1. Set [=this=]'s [=[[state]]=] to "`closed`".
+1. Set |lockReleased| to false.
+1. Let |file| be [=this=]'s [=FileSystemSyncAccessHandle/[[file]]=].
+1. [=Enqueue the following steps=] to the [=file system queue=]:
+ 1. [=file entry/lock/release|Release the lock=] on |file|.
+ 1. Set |lockReleased| to true.
+1. [=Pause=] until |lockReleased| is true.
+
+Note: This method does not guarantee that all file modifications will be
+immediately reflected in the underlying storage device. Call the
+{{FileSystemSyncAccessHandle/flush()}} method first if you require this
+guarantee.
+
+
+
+
+# Accessing the Bucket File System # {#sandboxed-filesystem}
+
+The bucket file system is a
+[=storage endpoint=] whose
+identifier is `"fileSystem"`,
+types are `« "local" »`,
+and quota is null.
+
+Issue: Storage endpoints should be defined in [[storage]] itself, rather
+than being defined here. So merge this into the table there.
+
+Note: While user agents will typically implement this by persisting the contents of a
+[=/bucket file system=] to disk, it is not intended that the contents are easily
+user accessible. Similarly there is no expectation that files or directories with names
+matching the names of children of a [=/bucket file system=] exist.
+
+
+[SecureContext]
+partial interface StorageManager {
+ Promise getDirectory();
+};
+
+
+
+ : |directoryHandle| = await navigator . storage . {{StorageManager/getDirectory()}}
+ :: Returns the root directory of the [=/bucket file system=].
+
+
+
+The
getDirectory() method steps are:
+
+1. Let |environment| be the [=current settings object=].
+
+1. Let |map| be the result of running [=obtain a local storage bottle map=]
+ with |environment| and `"fileSystem"`. If this returns failure,
+ return [=a promise rejected with=] a "{{SecurityError}}" {{DOMException}}.
+
+1. If |map|["root"] does not [=map/exist=]:
+ 1. Let |dir| be a new [=directory entry=] whose [=query access=] and
+ [=request access=] algorithms always return
+ a [=/file system access result=]
+ with a [=file system access result/permission state=]
+ of "{{PermissionState/granted}}" and
+ with an [=file system access result/error name=] of the empty string.
+ 1. Set |dir|'s [=file system entry/name=] to the empty string.
+ 1. Set |dir|'s [=directory entry/children=] to an empty [=/set=].
+ 1. Set |map|["root"] to |dir|.
+
+1. Let |root| be an [=implementation-defined=] opaque [=string=].
+1. Let |path| be « the empty string ».
+1. Let |handle| be the result of
creating a new `FileSystemDirectoryHandle`.
+ given |root| and |path| in the [=current realm=].
+
+ Note: |root| might include relevant identifying information such as the
+ [=storage bucket=].
+
+1. Assert: [=locating an entry=] given |handle|'s [=FileSystemHandle/locator=]
+ returns a [=directory entry=] that is [=the same entry as=] |map|["root"].
+
+1. Return [=a promise resolved with=] |handle|.
+
+
+
+
+Acknowledgments
+
+Many thanks to
+Alex Danilo,
+Anne van Kesteren,
+Anoesj Sadraee,
+Austin Sullivan,
+Chase Phillips,
+Daseul Lee,
+Dru Knox,
+Edgar Chen,
+Emanuel Krivoy,
+Hazim Mohamed,
+Ingvar Stepanyan,
+Jari Jalkanen,
+Joshua Bell,
+Kagami Sascha Rosylight,
+Marcos Cáceres,
+Martin Thomson,
+Olivier Yiptong,
+Philip Jägenstedt,
+Randell Jesup,
+Richard Stotz,
+Ruth John,
+Sid Vishnoi,
+Sihui Liu,
+Stefan Sauer,
+Thomas Steiner,
+Victor Costan, and
+Youenn Fablet
+for being awesome!
+
+
This standard is written by Marijn Kruisselbrink
+(Google, mek@chromium.org).
+
+
+
This Living Standard includes material copied from W3C WICG's
+File System Access, which is
+available under the
+W3C Software and Document License.