File an issue about the selected text

File System

Living Standard — Last Updated

Participate:
GitHub whatwg/fs (new issue, open issues)
Chat on Matrix
Commits:
GitHub whatwg/fs/commits
Snapshot as of this commit
@whatfilesystem
Tests:
web-platform-tests fs/ (ongoing work)
Not Ready For Implementation

This spec is not yet ready for implementation. It exists in this repository to record the ideas and promote discussion.

Before attempting to implement this spec, please contact the editors.

Abstract

File System defines infrastructure for file systems as well as their API.

1. 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 navigator.storage.getDirectory() method.

2. Files and Directories

2.1. Concepts

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

Each entry has an associated query access algorithm, which takes "read" or "readwrite" mode and returns a PermissionState. Unless specified otherwise it returns "denied". The algorithm is allowed to throw.

Each entry has an associated request access algorithm, which takes "read" or "readwrite" mode and returns a PermissionState. Unless specified otherwise it returns "denied". The algorithm is allowed to throw.

Note: Implementations that only implement this specification and not dependent specifications do not need to bother implementing entry's query access and request access.

Each entry has an associated name (a string).

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

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

We should consider having further normative restrictions on file names that will never be allowed using this API, rather than leaving it entirely up to underlying file systems.

A file entry additionally consists of binary data (a byte sequence) and a modification timestamp (a number representing the number of milliseconds since the Unix Epoch).

A directory entry additionally consists of a set of children, which are themselves entries. Each member is either a file or a directory.

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

Note: Two different entries can represent the same file or directory on disk, in which case it is possible for both entries to have a different parent, or for one entry to have a parent while the other entry does not have a parent.

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

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

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

To resolve an entry child relative to a directory entry root, run the following steps:
  1. Let result be a new promise.

  2. Run the following steps in parallel:

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

    2. Let childPromises be « ».

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

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

      2. Append p to childPromises.

      3. Upon fulfillment of p with value path:

        1. If path is not null:

          1. Prepend entry’s name to path.

          2. Resolve result with path.

    4. Wait for all childPromises, with the following success steps:

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

  3. Return result.

2.2. The FileSystemHandle interface

enum FileSystemHandleKind {
  "file",
  "directory",
};

[Exposed=(Window,Worker), SecureContext, Serializable]
interface FileSystemHandle {
  readonly attribute FileSystemHandleKind kind;
  readonly attribute USVString name;

  Promise<boolean> isSameEntry(FileSystemHandle other);
};

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

FileSystemHandle objects are serializable objects.

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

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

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

Their deserialization steps, given serialized and value are:
  1. If serialized.[[Origin]] is not same origin with value’s relevant settings object's origin, then throw a DataCloneError.

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

handle . kind

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

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

handle . name

Returns the name of the entry represented by handle.

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

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

2.2.1. The isSameEntry() method

same = await handle1 . isSameEntry( handle2 )

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

The isSameEntry(other) method, when invoked, must run these steps:
  1. Let realm be this's relevant Realm.

  2. Let p be a new promise in realm.

  3. Run the following steps in parallel:

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

    2. Else resolve p with false.

  4. Return p.

2.3. The FileSystemFileHandle interface

dictionary FileSystemCreateWritableOptions {
  boolean keepExistingData = false;
};

[Exposed=(Window,Worker), SecureContext, Serializable]
interface FileSystemFileHandle : FileSystemHandle {
  Promise<File> getFile();
  Promise<FileSystemWritableFileStream> createWritable(optional FileSystemCreateWritableOptions options = {});
};

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

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

2.3.1. The getFile() method

file = await fileHandle . getFile()

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

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

  2. Run the following steps in parallel:

    1. Let access be the result of running this's entry's query access given "read".

    2. If access is not "granted", reject result with a NotAllowedError and abort.

    3. Let entry be this's entry.

    4. Let f be a new File.

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

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

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

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

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

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

    10. Resolve result with f.

  3. Return result.

2.3.2. The createWritable() method

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

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

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

If keepExistingData is false or not specified, the temporary file starts out empty, otherwise the existing file is first copied to this temporary file.

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

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

  2. Run the following steps in parallel:

    1. Let access be the result of running this's entry's request access given "readwrite". If that throws an exception, reject result with that exception and abort.

    2. If access is not "granted", reject result with a NotAllowedError and abort.

    3. Let entry be this's entry.

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

    5. If options.keepExistingData is true:

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

    6. Resolve result with stream.

  3. Return result.

2.4. The FileSystemDirectoryHandle interface

dictionary FileSystemGetFileOptions {
  boolean create = false;
};

dictionary FileSystemGetDirectoryOptions {
  boolean create = false;
};

dictionary FileSystemRemoveOptions {
  boolean recursive = false;
};

[Exposed=(Window,Worker), SecureContext, Serializable]
interface FileSystemDirectoryHandle : FileSystemHandle {
  async iterable<USVString, FileSystemHandle>;

  Promise<FileSystemFileHandle> getFileHandle(USVString name, optional FileSystemGetFileOptions options = {});
  Promise<FileSystemDirectoryHandle> getDirectoryHandle(USVString name, optional FileSystemGetDirectoryOptions options = {});

  Promise<undefined> removeEntry(USVString name, optional FileSystemRemoveOptions options = {});

  Promise<sequence<USVString>?> resolve(FileSystemHandle possibleDescendant);
};

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

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

2.4.1. Directory iteration

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

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

In 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 ant its async iterator iterator are:
  1. Let access be the result of running handle’s entry's query access given "read".

  2. If access is not "granted", throw a NotAllowedError.

  3. Set iterator’s past results to an empty set.

To get the next iteration result for a FileSystemDirectoryHandle handle and its async iterator iterator:
  1. Let promise be a new promise.

  2. Let directory be handle’s entry.

  3. Let access be the result of running handle’s entry's query access given "read".

  4. If access is not "granted", reject promise with a NotAllowedError and return promise.

  5. Let child be an entry in directory’s children, such that child’s name is not contained in iterator’s past results, or null if no such entry exists.

    Note: This is intentionally very vague about the iteration order. Different platforms and file systems provide different guarantees about iteration order, and we want it to be possible to efficiently implement this on all platforms. As such no guarantees are given about the exact order in which elements are returned.

  6. If child is null, then:

    1. Resolve promise with undefined.

  7. Otherwise:

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

    2. If child is a file entry:

      1. Let result be a new FileSystemFileHandle associated with child.

    3. Otherwise:

      1. Let result be a new FileSystemDirectoryHandle associated with child.

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

  8. Return promise.

2.4.2. The getFileHandle() method

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

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

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

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

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

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

  2. Run the following steps in parallel:

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

    2. Let entry be this's entry.

    3. If options.create is true:

      1. Let access be the result of running this's entry's request access given "readwrite". If that throws an exception, reject result with that exception and abort.

    4. Otherwise:

      1. Let access be the result of running this's entry's query access given "read".

    5. If access is not "granted", reject result with a NotAllowedError and abort.

    6. For each child of entry’s children:

      1. If child’s name equals name:

        1. If child is a directory entry:

          1. Reject result with a TypeMismatchError and abort.

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

    7. If options.create is false:

      1. Reject result with a NotFoundError and abort.

    8. Let child be a new file entry whose query access and request access algorithms are those of entry.

    9. Set child’s name to name.

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

    11. Set child’s modification timestamp to the current time.

    12. Append child to entry’s children.

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

      Better specify what possible exceptions this could throw.

    14. Resolve result with a new FileSystemFileHandle whose entry is child.

  3. Return result.

2.4.3. The getDirectoryHandle() method

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

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

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

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

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

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

  2. Run the following steps in parallel:

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

    2. Let entry be this's entry.

    3. If options.create is true:

      1. Let access be the result of running this's entry's request access given "readwrite". If that throws an exception, reject result with that exception and abort.

    4. Otherwise:

      1. Let access be the result of running this's entry's query access given "read".

    5. If access is not "granted", reject result with a NotAllowedError and abort.

    6. For each child of entry’s children:

      1. If child’s name equals name:

        1. If child is a file entry:

          1. Reject result with a TypeMismatchError and abort.

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

    7. If options.create is false:

      1. Reject result with a NotFoundError and abort.

    8. Let child be a new directory entry whose query access and request access algorithms are those of entry.

    9. Set child’s name to name.

    10. Set child’s children to an empty set.

    11. Append child to entry’s children.

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

      Better specify what possible exceptions this could throw.

    13. Resolve result with a new FileSystemDirectoryHandle whose entry is child.

  3. Return result.

2.4.4. The removeEntry() method

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

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

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

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

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

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

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

  2. Run the following steps in parallel:

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

    2. Let entry be this's entry.

    3. Let access be the result of running this's entry's request access given "readwrite". If that throws an exception, reject result with that exception and abort.

    4. If access is not "granted", reject result with a NotAllowedError and abort.

    5. For each child of entry’s children:

      1. If child’s name equals name:

        1. If child is a directory entry:

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

            1. Reject result with an InvalidModificationError and abort.

        2. Remove child from entry’s children.

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

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

          Better specify what possible exceptions this could throw.

        4. Resolve result with undefined.

    6. Reject result with a NotFoundError.

  3. Return result.

2.4.5. The resolve() method

path = await directory . resolve( child )

If child is equal to directory, path will be an empty array.

If child is a direct child of directory, path will be an array containing child’s name.

If child is a descendant of directory, path will be an array containing the names of all the intermediate directories and child’s name as last element. For example if directory represents /home/user/project and child represents /home/user/project/foo/bar, this will return ['foo', 'bar'].

Otherwise (directory and child are not related), path will be null.

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

// Assume we at some point got a valid directory handle.
const dir_ref = current_project_dir;
if (!dir_ref) return;

// Now get a file reference by showing a file picker:
const file_ref = await self.showOpenFilePicker();
if (!file_ref) {
    // User cancelled, or otherwise failed to open a file.
    return;
}

// Check if file_ref exists inside dir_ref:
const relative_path = await dir_ref.resolve(file_ref);
if (relative_path === null) {
    // Not inside dir_ref
} else {
    // relative_path is an array of names, giving the relative path
    // from dir_ref to the file that is represented by file_ref:
    assert relative_path.pop() === file_ref.name;

    let entry = dir_ref;
    for (const name of relative_path) {
        entry = await entry.getDirectory(name);
    }
    entry = await entry.getFile(file_ref.name);

    // Now |entry| will represent the same file on disk as |file_ref|.
    assert await entry.isSameEntry(file_ref) === true;
}
The resolve(possibleDescendant) method, when invoked, must return the result of resolving possibleDescendant’s entry relative to this's entry.

2.5. The FileSystemWritableFileStream interface

enum WriteCommandType {
  "write",
  "seek",
  "truncate",
};

dictionary WriteParams {
  required WriteCommandType type;
  unsigned long long? size;
  unsigned long long? position;
  (BufferSource or Blob or USVString)? data;
};

typedef (BufferSource or Blob or USVString or WriteParams) FileSystemWriteChunkType;

[Exposed=(Window,Worker), SecureContext]
interface FileSystemWritableFileStream : WritableStream {
  Promise<undefined> write(FileSystemWriteChunkType data);
  Promise<undefined> seek(unsigned long long position);
  Promise<undefined> truncate(unsigned long long size);
};

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

A FileSystemWritableFileStream has an associated [[buffer]] (a byte sequence). It is initially empty.

Note: This buffer can get arbitrarily large, so it is expected that implementations will not keep this in memory, but instead use a temporary file for this. All access to [[buffer]] is done in promise returning methods and algorithms, so even though operations on it seem sync, implementations can implement them async.

A FileSystemWritableFileStream has an associated [[seekOffset]] (a number). It is initially 0.

A FileSystemWritableFileStream object is a WritableStream object with additional convenience methods, which operates on a single file on disk.

Upon creation, an underlying sink will have been created and the stream will be usable. All operations executed on the stream are queuable and producers will be able to respond to backpressure.

The underlying sink’s write method, and therefore WritableStreamDefaultWriter’s write() method, will accept byte-like data or WriteParams as input.

The FileSystemWritableFileStream has a file position cursor initialized at byte offset 0 from the top of the file. When using write() or by using WritableStream capabilities through the WritableStreamDefaultWriter’s write() method, this position will be advanced based on the number of bytes written through the stream object.

Similarly, when piping a ReadableStream into a FileSystemWritableFileStream object, this position is updated with the number of bytes that passed through the stream.

getWriter() returns an instance of WritableStreamDefaultWriter.

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

  2. Set stream.[[file]] to file.

  3. Let writeAlgorithm be an algorithm which takes a chunk argument and returns the result of running the write a chunk algorithm with stream and chunk.

  4. Let closeAlgorithm be the following steps:

    1. Let closeResult be a new promise.

    2. Run the following steps in parallel:

      1. Let access be the result of running file’s query access given "readwrite".

      2. If access is not "granted", reject closeResult with a NotAllowedError and abort.

      3. Perform implementation-defined malware scans and safe browsing checks. If these checks fail, reject closeResult with an AbortError and abort.

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

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

      5. Resolve closeResult with undefined.

    3. Return closeResult.

  5. Let highWaterMark be 1.

  6. Let sizeAlgorithm be an algorithm that returns 1.

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

  8. Return stream.

The write a chunk algorithm, given a FileSystemWritableFileStream stream and chunk, runs these steps:
  1. Let input be the result of converting chunk to a FileSystemWriteChunkType. If this throws an exception, then return a promise rejected with that exception.

  2. Let p be a new promise.

  3. Run the following steps in parallel:

    1. Let access be the result of running stream’s [[file]]'s query access given "readwrite".

    2. If access is not "granted", reject p with a NotAllowedError and abort.

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

    4. If command is "write":

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

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

      3. Let writePosition be stream.[[seekOffset]].

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

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

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

      7. Else if data is a Blob:

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

      8. Else:

        1. Assert: data is a USVString.

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

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

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

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

      11. Let tail be an empty byte sequence.

      12. If writePosition + data.length is smaller than oldSize:

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

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

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

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

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

      16. Resolve p.

    5. Else if command is "seek":

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

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

      3. Resolve p.

    6. Else if command is "truncate":

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

      2. Let newSize be chunk.size.

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

      4. If newSize is larger than oldSize:

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

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

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

      5. Else if newSize is smaller than oldSize:

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

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

      7. Resolve p.

  4. Return p.

2.5.1. The write() method

await stream . write(data)
await stream . write({ type: "write", data: data })

Writes the content of data into the file associated with stream at the current file cursor offset.

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

await stream . write({ type: "write", position: position, data: data })

Writes the content of data into the file associated with stream at position bytes from the top of the file. Also updates the current file cursor offset to the end of the written data.

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

await stream . write({ type: "seek", position: position })

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

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

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

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

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

The write(data) method, when invoked, must run these steps:
  1. Let writer be the result of getting a writer for this.

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

  3. Release writer.

  4. Return result.

2.5.2. The seek() method

await stream . seek(position)

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

The seek(position) method, when invoked, must run these steps:
  1. Let writer be the result of getting a writer for this.

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

  3. Release writer.

  4. Return result.

2.5.3. The truncate() method

await stream . truncate(size)

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

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

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

The truncate(size) method, when invoked, must run these steps:
  1. Let writer be the result of getting a writer for this.

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

  3. Release writer.

  4. Return result.

3. Accessing the Origin Private File System

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

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

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

[SecureContext]
partial interface StorageManager {
  Promise<FileSystemDirectoryHandle> getDirectory();
};
directoryHandle = await navigator . storage . getDirectory()

Returns the root directory of the origin private file system.

The getDirectory() method, when invoked, must run these steps:
  1. Let environment be the current settings object.

  2. Let map be the result of running obtain a local storage bottle map with environment and "fileSystem". If this returns failure, return a promise rejected with a SecurityError.

  3. If map["root"] does not exist:

    1. Let dir be a new directory entry whose query access and request access algorithms always return "granted".

    2. Set dir’s name to the empty string.

    3. Set dir’s children to an empty set.

    4. Set map["root"] to dir.

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

Acknowledgments

This standard is written by Marijn Kruisselbrink (Google, mek@chromium.org).

Intellectual property rights

This Living Standard includes material copied from W3C WICG’s File System Access, which is available under the W3C Software and Document License.

Copyright © WHATWG (Apple, Google, Mozilla, Microsoft). This work is licensed under a Creative Commons Attribution 4.0 International License. To the extent portions of it are incorporated into source code, such portions in the source code are licensed under the BSD 3-Clause License instead.

Index

Terms defined by this specification

Terms defined by reference

References

Normative References

[ECMASCRIPT]
ECMAScript Language Specification. URL: https://tc39.es/ecma262/multipage/
[ENCODING]
Anne van Kesteren. Encoding Standard. Living Standard. URL: https://encoding.spec.whatwg.org/
[FILE-API]
Marijn Kruisselbrink; Arun Ranganathan. File API. URL: https://w3c.github.io/FileAPI/
[HTML]
Anne van Kesteren; et al. HTML Standard. Living Standard. URL: https://html.spec.whatwg.org/multipage/
[INFRA]
Anne van Kesteren; Domenic Denicola. Infra Standard. Living Standard. URL: https://infra.spec.whatwg.org/
[PERMISSIONS]
Marcos Caceres; Mike Taylor. Permissions. URL: https://w3c.github.io/permissions/
[STORAGE]
Anne van Kesteren. Storage Standard. Living Standard. URL: https://storage.spec.whatwg.org/
[STREAMS]
Adam Rice; et al. Streams Standard. Living Standard. URL: https://streams.spec.whatwg.org/
[WEBIDL]
Edgar Chen; Timothy Gu. Web IDL Standard. Living Standard. URL: https://webidl.spec.whatwg.org/

IDL Index

enum FileSystemHandleKind {
  "file",
  "directory",
};

[Exposed=(Window,Worker), SecureContext, Serializable]
interface FileSystemHandle {
  readonly attribute FileSystemHandleKind kind;
  readonly attribute USVString name;

  Promise<boolean> isSameEntry(FileSystemHandle other);
};

dictionary FileSystemCreateWritableOptions {
  boolean keepExistingData = false;
};

[Exposed=(Window,Worker), SecureContext, Serializable]
interface FileSystemFileHandle : FileSystemHandle {
  Promise<File> getFile();
  Promise<FileSystemWritableFileStream> createWritable(optional FileSystemCreateWritableOptions options = {});
};

dictionary FileSystemGetFileOptions {
  boolean create = false;
};

dictionary FileSystemGetDirectoryOptions {
  boolean create = false;
};

dictionary FileSystemRemoveOptions {
  boolean recursive = false;
};

[Exposed=(Window,Worker), SecureContext, Serializable]
interface FileSystemDirectoryHandle : FileSystemHandle {
  async iterable<USVString, FileSystemHandle>;

  Promise<FileSystemFileHandle> getFileHandle(USVString name, optional FileSystemGetFileOptions options = {});
  Promise<FileSystemDirectoryHandle> getDirectoryHandle(USVString name, optional FileSystemGetDirectoryOptions options = {});

  Promise<undefined> removeEntry(USVString name, optional FileSystemRemoveOptions options = {});

  Promise<sequence<USVString>?> resolve(FileSystemHandle possibleDescendant);
};

enum WriteCommandType {
  "write",
  "seek",
  "truncate",
};

dictionary WriteParams {
  required WriteCommandType type;
  unsigned long long? size;
  unsigned long long? position;
  (BufferSource or Blob or USVString)? data;
};

typedef (BufferSource or Blob or USVString or WriteParams) FileSystemWriteChunkType;

[Exposed=(Window,Worker), SecureContext]
interface FileSystemWritableFileStream : WritableStream {
  Promise<undefined> write(FileSystemWriteChunkType data);
  Promise<undefined> seek(unsigned long long position);
  Promise<undefined> truncate(unsigned long long size);
};

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