FileSystem API

The FileSystem API defines functionality on a local sandboxed file system within the same origin of the Web Application that created it. It exposes standard file system operations to Web Applications, such as creation of files and directories, and reading and writing of them (from and to disk), including other programmatic manipulation of files and directories.

Portions of this specification are in flux, and are likely to be re-written; where appropriate, these have been marked as ISSUES by the editor. Implementors are encouraged to contribute to discussions on the public-webapps@w3.org mailing list. This work uses the "File API: Directories and System" W3C Note as a starting point.

Introduction

The FileSystem API is for Web applications that have client-side storage needs not readily addressed by database APIs such as [[IndexedDB]]. Such applications need to handle common file types that end users typically link with logical directory structures in a file system, and generally involve binary data that may be shared with other applications. The API is designed to be asynchronous using Promises [[!ECMAScript]].

The FileSystem API is a virtual file system, and thus user agents are responsible for allocating space for the creation of a sandboxed file system and for imposing storage quotas on that virtual file system.

Use Cases

  1. Persistent uploader
    • When a file or directory is selected for upload, it copies it into a local sandbox and uploads a chunk at a time.
    • It can restart uploads after browser crashes, network interruptions, etc.
  2. Video game or other app with lots of media assets
    • It downloads one or several large tarballs, and expands them locally into a directory structure.
    • The same download should work on any operating system.
    • It can manage prefetching just the next-to-be-needed assets in the background, so going to the next game level or activating a new feature doesn't require waiting for a download.
    • It uses those assets directly from its local cache, by direct file reads or by handing local URLs to image or video tags, WebGL asset loaders, etc.
    • The files may be of arbitrary binary format.
  3. Audio/Photo editor with offline access or local cache for speed
    • Data here is potentially quite large, and is read-write.
    • It may want to do partial writes to files (ovewriting just the ID3/EXIF tags, for example).
    • The ability to organize project files by creating directories would be useful.
  4. Offline video viewer
    • It downloads large files (>1GB) for later viewing.
    • It needs efficient seek + streaming.
    • It must be able to hand a URL to the video tag.
    • It should enable access to partly-downloaded files e.g. to let you watch the first episode of the DVD even if your download didn't complete before you got on the plane.
    • It should be able to pull a single episode out of the middle of a download and give just that to the video tag; this can be accomplished by a URL scheme that works with a filesystem path.
  5. Offline Web Mail Client
    • Downloads attachments and stores them locally.
    • Caches user-selected attachments for later upload.
    • Needs to be able to refer to cached attachments and image thumbnails for display and upload.
    • Should be able to trigger the UA's download manager just as if talking to a server.
    • Should be able to upload an email with attachments as a multipart post, rather than sending a file at a time in an XHR.

Conformance

Everything in this specification is normative except for examples and sections marked as being informative.

The keywords “MUST”,
“MUST NOT”,
“REQUIRED”,
“SHALL”,
“SHALL NOT”,
“RECOMMENDED”,
“MAY” and
“OPTIONAL” in this document are to be interpreted as described in
Key words for use in RFCs to Indicate Requirement Levels [[!RFC2119]].

The following conformance classes are defined by this specification:

conforming user agent

A user agent is considered to be a conforming user agent if it satisfies all of the MUST-, REQUIRED- and SHALL-level criteria in this specification that apply to implementations. This specification uses the terms "conforming user agent", "user agent" and "implementation" to refer to this product class.

User agents may implement algorithms in this specifications in any way desired, so long as the end result is indistinguishable from the result that would be obtained from the specification's algorithms.

User agents that use ECMAScript to implement the APIs defined in this specification must implement them in a manner consistent with the ECMAScript Bindings defined in the Web IDL specification [[!WEBIDL]] as this specification uses that specification and terminology.

Terminology

The terms Blob, File, Unix Epoch, File Constructor Steps, read operation, termination reason, and body are as defined by the [[!FileAPI]] specification.

The terms scheme, scheme data, fragment and URL are as defined by the [[!URL]] specification.

The terms request, response, and cross-origin request are as defined in the WHATWG Fetch Specification [[!Fetch]].

The term origin is used as defined by the Web Origin Specification [[!ORIGIN]].

The term worker is defined by the WebWorkers specification [[!WEBWORKERS]].

The terms context object, strictly split a string, and document are defined by the HTML specification [[!HTML]].

Algorithmic steps in this specification return when a promise is either rejected, fulfilled, or canceled, or when the steps are explicitly terminated outside of promise fulfilment or rejection.

When this specification refers to the OS Cache, it means any underlying operating system buffers or any underlying software system buffers which the conformance class might rely on before transferring data or "flushing" it to disk; the term includes kernel buffers, buffer "cache pages" or modified in-core data, typically transferred to disk within deltas of time. The term flushing the OS Cache refers to a forced flushing from the OS Cache to disk initiated by software as an additional data reliability and persistence maneuver outside of system defaults.

Model

Directory, File, Root, and Path

A directory is a logical organizing storage unit with a distinct name which contains files and/or one or more subdirectory units (which are themselves directory units). The directory is organized by a hierarchical containment structure, which can also be represented by a string, called a path; the path string uses the U+002F SOLIDUS character ("/") to denote directories within the Directory Tree. A file is durable binary data that can persist through storage, and can be programmatically manipulated as defined in the [[!FileAPI]]; this specification introduces two new primitives for handling files, namely the FileHandle and the FileHandleWritable. Files, like directories, have distinct names which identify them on the Directory Tree. Files are treated as byte sequences for certain operations in this specification.

The top-most containing directory, which contains all directories and files, if any exist, is called the root directory, and has the following properties:

Each origin has one root directory.

The Directory Tree

User agents must maintain an internal per-origin Directory Tree of directories and files that have been written to disk, along with the affiliated path from root for each directory and file that has been written to disk; each such directory or file must correspond to a node in the Directory Tree, with the root directory being the root of the Directory Tree.

The corollary to this is that for each file or directory that has been written to disk, user agents are required to maintain the position of that file or directory on the Directory Tree.

A Directory Pointer is a pointer to a position in the Directory Tree; unless specified otherwise, it initally points to the root of the Directory Tree.

The present working directory is the node on the Directory Tree that represents the current directory from which an operation is taking place. In this specification, this is the context object from which a Directory operation is called.

A path to a given directory from the root directory is said to be a path from root and always begins with a leading U+002F SOLIDUS ("/"). A path that is relative only from the present working directory is said to be a relative path, and does not begin with a U+002F SOLIDUS ("/").

The present working directory may also be the root Directory, in which case the path does not need to begin with a leading U+002F SOLIDUS ("/"). If the present working Directory is the root directory, then all paths to other directories or files can be represented by relative paths.

var path = "/music/genres/jazz"; // path from root to jazz Directory

// set genres directory to the present working Directory:

var pathToJazz = "jazz"; // relative path from within genres to jazz Directory 

Currently paths of the sort "../.../myDir/yourDir" which are a type of relative path are not permitted. We only allow forward path navigation; allowing "../../" will allow backward path navigation, which we don't mention in this specification.

A child is a node on the Directory Tree with a position that is below a given position on the Directory Tree for a given directory; a subdirectory of the present working directory is a child of the present working directory; a subdirectory of that subdirectory is also a child of the present working directory. An immediate child corresponds to a node on the Directory Tree that is either a subdirectory that is the next position on the path, or a file that is next on the path. A parent is a directory that contains a file or another directory; in particular, the directory that contains the present working directory is its parent. The root directory is the parent of all other nodes in the Directory Tree.

When this specification says to add d to the Directory Tree, where d is either a file or a directory, a user agent must add d to the Directory Tree, along with its path from root.

Adding to the Directory Tree involves allocating storage for a new directory or a file, but not necessarily allocating storage for a pre-existing one, which can be added to the Directory Tree through a transfer operation. Failure to allocate storage owing to disk quota issues must cause promises in this specification that add to the Directory Tree to get rejected with a QuotaExceededError.

When this specification says to remove d from the Directory Tree, where d is either a file or a directory, a user agent must remove d from the Directory Tree; in particular, this removes the path that d had and removes data associated with d. If d has child nodes such as files or subdirectories, these must also be recursively removed.

User agents should determine if the storage can be reallocated for subsequent operations.

When this specification says to remove the path for a particular node, which is either a file or a directory, a user agent must remove the path from root for that node, without removing that node's data. This includes recursively removing the path from root for any child of that node, which applies only to directories.

When this specification says to transfer e to directory d, where e is either a file or a directory, the following steps must be followed, taking care to check for illegal transfers:

  1. If e and d are identical, then return failure, unless this is a renaming operation, in which case run the renaming operation and end these steps.

  2. Otherwise, remove the path for e.

  3. If there is already a file or directory o with the identical name as e in d, run the following substeps:

    1. Remove o from the Directory Tree.

      This includes recursively removing any child nodes of o.

    2. Add e to the Directory Tree such that e is an immediate child of d. If e is directory, it becomes a subdirectory of d. If e is a file, it becomes a file contained by d.

  4. Otherwise add e to the Directory Tree such that e is an immediate child of d. If e is a directory, it becomes a subdirectory of d. If e is a file, it becomes a file contained by d.

Since transfers add to the Directory Tree, a transfer may generate storage quota errors which reject promises that invoke a transfer operation with a QuotaExceededError.

The set of illegal transfers are given below:

For any illegal transfer, promises invoking a transfer must be rejected with an InvalidStateError.

When this specification says to do a renaming operation with a new name on a node e on the Directory Tree, where e is either a file or a directory, user agents must change the name of e to the new name. If a transfer is invoked on two nodes, e and d, that identify the same node on the Directory Tree, unless d is supplied with a new name, the transfer is considered an illegal transfer. If the method invoking the transfer is supplied a new name, then it can perform a renaming operation.

The change to the underlying model's node e will result in a new object, or invalidate existing File and Directory objects referring to the underlying resource with the old name, which no longer exists.

When this specification says to crawl the entire path for a given path p, the following steps must be run:

  1. Let input be set to path, position be a pointer to input, initially set to the first character of input, and tokens be a list of tokens, initially empty. If input is a single U+002F SOLIDUS character, set the Directory Pointer to the root directory and terminate this algorithm.

  2. If the character at position is a U+002F SOLIDUS character ("/"), set the Directory Pointer to root; this represents a path from root. Otherwise, set it to the present working directory. Strictly split input on the U+002F SOLIDUS character.

  3. For each token in tokens, if the next position of the Directory Pointer strictly matches the name of a directory or a file, advance the Directory Pointer to that directory or file. If it does not strictly match, return failure.

When this specification says to crawl the path until node n for a given path p and value for n, the following steps must be run:

  1. Let input be set to path, position be a pointer to input, initially set to the first character of input, and tokens be a list of tokens, initially empty. If input is a single U+002F SOLIDUS character, set the Directory Pointer to the root directory and terminate this algorithm.

  2. If the character at position is a U+002F SOLIDUS character ("/"), set the Directory Pointer to root; this represents a path from root. Otherwise, set it to the present working directory. Strictly split input on the U+002F SOLIDUS character.

  3. If n ≥ the number of tokens in tokens, run the crawl the entire path algorithm and terminate this algorithm.

  4. Otherwise, for each token in tokens up to the nth token, if the next position of the Directory Pointer strictly matches the name of a directory, advance the Directory Pointer to that directory. If it does not strictly match, return failure.

Extensions to Existing Objects

The FileSystem API is exposed to the Web via an extension to the window.navigator object [[!HTML]]. A URL-generating method for individual files within the virtualized filesystem is generated via an extension to the window.URL object [[!URL]].

Extension to Navigator

Promise<Directory?> getFileSystem()

The getFileSystem method returns a Promise that is either fulfilled with the root Directory object or fulfilled with null if the root Directory object cannot be returned. The getFileSystem method must act as follows:

  1. Let promise be a new promise and run the next steps asynchronously.

  2. Resolve or reject promise according to the Directory Creation Steps with no value given for path.

  3. Return promise.

Extension to URL

static DOMString? getFileSystemURL(File file)

When the getFileSystemURL(file) method is called, the user agent must run the steps below:

  1. Run the URL generation steps for file. If they return with failure, return null.

    If the File parameter does not refer to a node in the Directory Tree, this method will return null.

  2. Otherwise, return the FileSystem URL returned by the URL generation steps.

The Directory Interface

Directory objects represent a directory in the Directory Tree, and allow for API operations on that directory or on other parts of the Directory Tree, which may affect other directories. The root Directory object represents the root directory. Each Directory object must refer to the present working directory, unless any method called on that Directory uses a path argument that refers to another directory or file.

Origin of a Directory

The origin of a Directory is the same as the origin of the document or worker. Each origin has one root Directory.

The origin of the root Directory is not affected by changes to document.domain.

Resolving or Rejecting a Directory-Creating Promise

When this specification says to resolve or reject a promise p according to the Directory Creation Steps, a conforming implementation must run the following steps within the asynchronous block that invoked this algorithm, and return control back to the invoking algorithm after resolving or rejecting p:

  1. If the method was invoked without a path argument, run the following root Directory substeps:

    1. If the root Directory cannot be returned by the implementation, resolve p with null.

      The invocation without a path argument occurs from navigator.getFileSystem(). In practice, there are few reasons for a user agent to not return a root Directory object; various errors may arise from Directory operations on the root Directory, but failure to create a root Directory should fulfill a Promise with null.

    2. Otherwise, resolve p with the root Directory.
  2. Otherwise the method was invoked with a path argument. Run the following Directory creation for a path argument steps:

    1. If the path argument is the empty string, then reject p with an InvalidStateError.

    2. If the path argument is a single U+002F SOLIDUS character ("/") then resolve p with the root Directory.

    3. Otherwise let input be set to the path argument, and let position be a pointer to input, initially pointing to the first character of input. Let dirname initially be set to the empty string, and let tokens be an ordered list of tokens, initally empty. If the character at position is a U+002F SOLIDUS character ("/"), set the Directory Pointer to the root of the Directory Tree, and advance position by one character. If not, set the Directory Pointer to the present working directory. Strictly split input on the U+002F SOLIDUS character ("/").

    4. While there are tokens in tokens, set dirname to initially equal the first token in tokens, moving to the next one at each iteration, and run the following substeps:

      1. If dirname strictly matches a subdirectory name that is the next position of the Directory Pointer, advance the Directory Pointer to the position on the Directory Tree that corresponds to the directory named dirname and reset dirname to the empty string.
      2. If dirname does not strictly match the next position of the Directory Pointer, then add to the Directory Tree a Directory called dirname and advance the Directory Pointer to the newly created Directory and reset dirname to the empty string. If adding to the Directory Tree raises any error, reject p with the most appropriate error from the Directory Promise Rejection Error Table.

    5. If all values of dirname strictly match the name of nodes on the Directory Tree, and no new directories are added to the Directory Tree, reject p with an InvalidModificationError.

    6. Otherwise, resolve p with a new Directory d with the following properties:

Directory Promise Rejection Error Table

Rejection of Directory-creation promises and promises with I/O Transactions occur with the errors and reasons shown in the Directory Promise Rejection Error Table below.

Type Description and Failure Reason
QuotaExceededError

If no storage space exists for a directory creation operation.

SecurityError

If the user agent determines that the directory creation operation causes a security risk.

InvalidStateError

If the user agent determines that an I/O Transaction cannot be initiated due to lifetime rules, or if parameter inconsistencies exist (e.g. referring to a non-existent file or directory for a given operation) then this error must be used.

This includes mode constraints on an I/O Transaction as a type of lifetime error condition -- no two readwrite I/O Transactions can run concurrently.

TransactionInactiveError

If a file read or write request takes place on an I/O Transaction that is not in the active state, this error must be used.

Sequence Type for Directory Content Iteration

This specification uses this type for the getFilesAndDirectories() method.

It is expected that this sequence parameterized type will be replaced or obviated with different and more programmatically efficient constructs such as AsyncIterator or Observable when they are added to the platform.

Directory WebIDL

readonly attribute DOMString name

On getting, this must be the name of the underlying directory.

Promise<File> createFile(DOMString path, CreateFileOptions options)

When the createFile(path, options) method is run, the user agents must run the steps below:

  1. Let p be a new promise.

  2. Run the following steps asynchronously:

    1. Strictly split the path argument on the U+002F SOLIDUS character. Let n be the total number of resulting tokens.

    2. If crawling the path until node n-1 for the path argument returns failure, reject p with an InvalidStateError.

    3. If the nth token in tokens identifies a directory, reject p with an InvalidStateError.

      The path argument must identify a file.

    4. Otherwise, if the nth token in tokens strictly matches the name of a pre-existing file at the next position of the Directory Pointer, run the following substeps:

      1. If the CreateIfExistsMode ifExists dictionary member of the options argument is "fail" then reject p with a NoModificationAllowedError.

      2. If the CreateIfExistsMode ifExists dictionary member of the options argument is "replace" then run the following substeps:

        1. Let f be a new File constructed according to running the File Constructor Steps with the following modifications:

          • Treat the data member of the options dictionary as if it was a single element in the fileBits sequence, and follow the step for that type from the File Constructor Steps.

          • Replace the fileName parameter with the nth token in tokens, and normalize it according to the File Constructor Steps.

          • Set f.lastModifiedDate to the current date and time represented as the number of milliseconds since the Unix Epoch (which is the equivalent of Date.now() [[!ECMAScript]]).

        2. Remove the file at the at the next position of the Directory Pointer which strictly matches the name of the nth token in tokens.

        3. Add the file represented by f to the next position of the Directory Pointer and advance the Directory Pointer to point to f. If adding to the Directory Tree raises any error, reject p with the most appropriate error from the Directory Promise Rejection Error Table.

        4. Otherwise, resolve p with f.

    5. If the nth token does not strictly match the name of a pre-existing file at the next position of the Directory Pointer run the following substeps:

      1. Let f be a new File constructed according to the File Constructor Steps with the following modifications:

        • Treat the data member of the options dictionary as if it was a single element in the fileBits sequence, and follow the step for that type from the File Constructor Steps.

        • Replace the fileName parameter with the nth token in tokens, and normalize it according to the File Constructor Steps.

        • Set f.lastModifiedDate to the current date and time represented as the number of milliseconds since the Unix Epoch (which is the equivalent of Date.now() [[!ECMAScript]).

      2. Add the file represented by f to the next position of the Directory Pointer and advance the Directory Pointer to point to f. If adding to the Directory Tree raises any error, reject p with the most appropriate error from the Directory Promise Rejection Error Table

        .
      3. Otherwise, resolve p with f.

  3. Return p

Promise<Directory> createDirectory(DOMString path)

When the createDirectory(path) method is called, the user agent must run the steps below:

  1. Let p be a new promise and run the next step asynchronously.
  2. Resolve or reject p according to the Directory Creation Steps, using the path argument.
  3. Return p
DOMString path
This parameter represents the path; user agents must interpret it as a path from root or a relative path.
Promise<(File or Directory)> get(DOMString path)

When the get(path) method is called, the user agent must run the steps below:

  1. Let p be a new promise.

  2. Run the following steps asynchronously:

    1. If crawling the entire path represented by the path argument returns failure, reject p with an InvalidStateError.

    2. Otherwise, set d to the node at the position the Directory Pointer is pointing to, and run the following substeps:

      1. If d is a file, resolve p with a new File representing d.

      2. If d is a directory, resolve p with a new Directory representing d.

  3. Return p.

CancelablePromise<void> move((DOMString or File or Directory) path, (DOMString or Directory or DestinationDict) dest)

When the move(path, dest) method is called, the user agent must run the steps below:

  1. Let p be a new CancelablePromise.

    The web does not have a CancelablePromise type yet.

  2. Run the following steps asynchronously:

    1. If path is a DOMString, then crawl the entire path represented by path. If it returns failure, then reject p with an InvalidStateError.

    2. If dest is a DOMString, then run the following substeps:

      1. Crawl the entire path represented by dest as a path. If it returns failure, then reject p with an InvalidStateError.

      2. If the path represented by dest is a File, reject p with an InvalidStateError.

        It is an error to move a directory or a file to the location of another file; dest must represent a directory, either through a valid path string, a Directory object, or the DestinationDict dictionary argument.

    3. If the node that corresponds to dest is a child of path, then reject p with an InvalidStateError.

      This is an illegal transfer.

    4. If path and dest both represent directories and are at the identical position on the Directory Tree, run the following substeps:

      1. If dest is not a DestinationDict with a name member that has a value different than the name of the directory represented by path, then reject p with an InvalidStateError.

        This is an illegal transfer; only a DestinationDict allows for a renaming operation.

      2. Otherwise:

        1. if dest is a DestinationDict with a name member that has a value different than the name of the directory represented by path, conduct a renaming operation on the directory identified by path

        2. resolve p with undefined.

        This may have implications for Directory objects that exist with the old name of the underlying directory.

    5. If path is a child of dest and dest has an immediate child which is a parent of path with the same name as the node identified by path, then reject p with an InvalidStateError.

      This is an illegal transfer.

    6. Otherwise:

      1. transfer path to dest. If there were errors in the transfer, reject p with the most appropriate error from the Directory Promise Rejection Error Table.

      2. If there were no errors, resolve p with undefined.

  3. Return p.

Promise<boolean> remove((DOMString or File or Directory) path)

When the remove(path) method is called, the user agent must run the steps below:

  1. Let p be a new promise.

  2. Run the following steps asynchronously:

    1. If path is a DOMString crawl the entire path. If it returns failure, resolve p with false.

    2. If path is a File then run the following substeps:

      1. Remove from the Directory Tree the node identified by the path argument.

        Any request on this file as a scope file must immediately be rejected with an InvalidStateError, and the I/O Transaction must close.

      2. Resolve p with true.

    3. If path represents a directory run the following substeps:

      1. If path has one or more child nodes, resolve p with false.

      2. Otherwise, run the following substeps:

        1. Remove from the Directory Tree the node identified by the path argument.

        2. Resolve p with true.

  3. Return p.
Promise<boolean> removeDeep((DOMString or File or Directory) path)

When the removeDeep(path) method is called, the user agent must run the steps below:

  1. Let p be a new promise.

  2. Run the following steps asynchronously:

    1. If path is a DOMString crawl the entire path. If it returns failure, resolve p with false.

    2. If path is a File then run the following substeps:

      1. Remove from the Directory Tree the node identified by the path argument.

        Any request on this file as a scope file must immediately be rejected with an InvalidStateError, and the I/O Transaction must close.

      2. Resolve p with true.

    3. If path represents a directory run the following substeps:

      1. If path has no child nodes, run the following substeps:

        1. remove from the Directory Tree the node identified by the path argument.
        2. Resolve p with true
      2. If path has one or more child nodes, run the following substeps:

        1. Recursively remove from the Directory Tree the node identified by the path argument, including all child nodes.

          Any request on file(s) as a scope file(s) must immediately be rejected with an InvalidStateError, and the affiliated I/O Transaction(s) must close.

        2. Resolve p with true.

  3. Return p.
Promise<FileHandle> openRead((DOMString or File) path)

When the openRead(path) method is called, the user agent must run the steps below:

  1. Let p be a new promise.
  2. Run the following steps asynchronously:

    1. If path is a DOMString, run the following substeps:

      1. Crawl the entire path. If it returns failure, then reject p with an InvalidStateError.

      2. If path represents a directory node and not a file node, reject p with an InvalidStateError.
      3. Otherwise, run the following substeps:

        1. If the I/O Transaction cannot be initiated according to the lifetime rules, reject p with an InvalidStateError.

        2. Otherwise, let f be a new FileHandle set to refer to the file at path. Set the FileOpenMode enum property on f to readonly.

        3. Initiate the readonly I/O Transaction associated with f according to the lifetime rules.

        4. Resolve p with f.

    2. Return p.
Promise<FileHandleWritable> openWrite((DOMString or File) path, OpenWriteOptions options)

When the openWrite(path, options) method is called, the user agent must run the steps below:

  1. Let p be a new promise, and filename a string, initially empty.
  2. Run the following steps asynchronously:

    1. If the path argument is a DOMString, then run the substeps below:

      1. Strictly split the path argument on the U+002F SOLIDUS character. Let n be the total number of resulting tokens.

      2. If crawling the path until node n-1 for the path argument returns failure, reject p with an InvalidStateError.

      3. If the node identified by the token at n is a directory, reject p with an InvalidStateError.

        If a DOMString is used for path, it must point to a file in the Directory Tree, or be a the name of a potential new file.

      4. If an error occurs from the Directory Promise Rejection Error Table, reject p with the most appropriate rejection error.

        Multiple genres of error may take place. Writing to given file in the Directory Tree may raise quota errors. Additionally, lifetime rules on I/O Transactions don't allow for two readwrite I/O Transactions to operate on the same scope file concurrently; if this operation violates the mode restriction, p must be rejected the most appropriate error from the Directory Promise Rejection Error Table.

      5. Otherwise, set filename to the nth token.
    2. If path is a File then set filename to path.name.

    3. If path is a File and filename corresponds to an existing file's name in the present working directory OR path is a DOMString and filename corresponds to a file's name in the directory identified by the n-1th token, then run the following substeps:

      If path is a File, then this method applies to the present working directory; if path is a DOMString, then this method applies to the directory identified by path's n-1th token.

      1. If the optional OpenWriteOptions dictionary argument is used, and the OpenIfExistsMode ifExists member is "fail" then reject p with an InvalidStateError.

      2. Otherwise, if the OpenIfExistsMode ifExists dictionary member is "open" then run the substeps below:

        This enum value is set to "open" by default.

        1. If the Flush dictionary member is set to true, run the following substeps:

          1. Let f be a flushing FileHandleWritable with the FileOpenMode enum set to readwrite, and set to the scope file corresponding to path, and with f.active set to true.

          2. Initiate the readwrite I/O Transaction associated with f according to the lifetime rules.

          3. Resolve p with f.

        2. If the Flush dictionary member is set to false, run the following substeps:

          1. Let f be a non-flushing FileHandleWritable with the FileOpenMode enum set to readwrite, and set to the scope file corresponding to path. Set f.active to true.

          2. Initiate the readwrite I/O Transaction associated with f according to the lifetime rules.

          3. Resolve p with f.

      3. Otherwise, if path is a File and filename does NOT correspond to an existing file's name in the present working directory OR path is a DOMString and filename does NOT correspond to a file's name in the directory identified by the n-1th token, then run the following substeps:

        1. If the optional OpenWriteOptions dictionary argument is used, and the OpenIfNotExistsMode ifNotExists member is "fail" then reject p with an InvalidStateError.

        2. Otherwise, if the OpenIfNotExistsMode ifNotExists dictionary member is "create" then run the substeps below:

          This enum value is set to "create" by default.

          1. If the Flush dictionary member is set to true, run the following substeps:

            1. Let f be a flushing FileHandleWritable with the FileOpenMode enum set to readwrite, and set to the scope file corresponding to path, and with f.active set to true.

            2. Initiate the readwrite I/O Transaction associated with f according to the lifetime rules.

            3. Resolve p with f.

          2. If the Flush dictionary member is set to false, run the following substeps:

            1. Let f be a non-flushing FileHandleWritable with the FileOpenMode enum set to readwrite, and set to the scope file corresponding to path. Set f.active to true.

            2. Initiate the readwrite I/O Transaction associated with f according to the lifetime rules.

            3. Resolve p with f.

  3. Return p.
Promise<FileOrDirectorySequence> getFilesAndDirectories()

When the getFilesAndDirectories() method is called, the user agent must run the steps below:

  1. Let p be a new promise and s a sequence, initially set to an empty sequence.

  2. Run the following steps asynchronously:

    1. If the present working directory has no files or directories, resolve p with s, which is still an empty sequence.

    2. Otherwise, for each immediate child of the present working directory, add the corresponding node to s as a File or Directory, depending on the type of the immediate child.

    3. If there are any problems retrieving any immediate child, reject p with an InvalidStateError.

      Promise rejection occurs for any problem crawling the present working directory.

    4. Once all the immediate child nodes of the present working directory have been added to s as a File or Directory, resolve p with s.

  3. Return p.

Observable<(File or Directory)> enumerate(optional DOMString path)

The Observable async generator and semantics around it are not well-defined yet. The more useful underyling primitive might be an AsyncIterator. This is a general platform problem; Observable and AsyncIterator are currently proposals within TC-39, and this should be considered "placeholder" semantics till the proposal becomes a language formalism. See also Issue 4 with this specification.

When the enumerate(path) method is called, the user agent must run the steps below:

  1. If no parameter is provided, set the Directory Pointer to the present working directory. For each immediate child, emit a corresponding object (File or Directory) until done; when done, invoke this Observable's final state.

    If there are no immediate child nodes, this becomes the empty Observable.

  2. If a parameter is provided, and crawling the entire path returns failure, invoke the Observable's rejection path with an InvalidStateError.
  3. If crawling the entire path does not return failure, run the substeps below:
    1. If the Directory Pointer points to a file, emit a File corresponding to the file; when done, invoke this Observable's final state.
    2. If the Directory Pointer points to a directory, for each immediate child, emit a corresponding object (File or Directory) until done; when done, invoke this Observable's final state.
Observable<(File)> enumerateDeep(optional DOMString path)

When the enumerateDeep(path) method is called, the user agent must run the steps below:

  1. If no parameter is provided, set the Directory Pointer to the present working directory and run the following substeps:
    1. If there are immediate child nodes and they are files, emit a corresponding File object.

      If there are no immediate child nodes, this becomes the empty Observable.

    2. If any immediate child is a directory, run the following substeps for all child nodes of that directory:

      1. Set the Directory Pointer to the directory and emit a corresponding File object for any immediate child nodes that are files.

      2. If any immediate child nodes are directories, run the step above on them.
    3. When done, invoke this Observable's final state.

  2. If a parameter is provided, and crawling the entire path returns failure, invoke the Observable's rejection path with an InvalidStateError.

  3. If crawling the entire path above does not return failure, run the substeps below:

    1. If the Directory Pointer points to a file, emit a File corresponding to the file; when done, invoke this Observable's final state.

    2. If the Directory Pointer points to a directory, run the following substeps:
      1. If there are immediate child nodes and they are files, emit a corresponding File object.

        If there are no immediate child nodes, this becomes the empty Observable.

      2. If any immediate child is a directory, run the following substeps for all child nodes of that directory:

        1. Set the Directory Pointer to the directory and emit a corresponding File object for any immediate child nodes that are files.

        2. If any immediate child nodes are directories, run the step above on them.
      3. When done, invoke this Observable's final state.

The FileHandle interface

File References and I/O Transactions

The FileHandle family of objects includes the FileHandle object and the FileHandleWritable object, and are references to individual files in the Directory Tree or to File objects not yet written to the Directory Tree. Since a FileHandleWritable inherits from a FileHandle, this specification refers to both as FileHandles. Each FileHandle is said to have a single scope file which is the file in the Directory Tree that the FileHandle object refers to, or is the byte stream that will be added to the Directory Tree. A FileHandle object can be used to read a file, and a FileHandleWritable object can be used to both read from and write to a scope file.

Each FileHandle initiates and eventually closes an I/O Transaction, which reads or writes to a given scope file, depending on mode. Each individual read or write operation on an I/O Transaction is called a request. I/O Transactions are short-lived and allow for multiple atomic read or write requests. Each I/O Transaction has a mode which lasts for the entirety of its lifetime. Each I/O Transaction has an active flag; when set, it means the I/O Transaction can start requests on the scope file, depending on its mode. I/O Transactions must maintain a request list of requests, which are put in the request list in order of creation according to the callback sequence.

When active, an I/O Transaction can read or write data, depending on mode.

Each I/O Transaction is subject to the following lifetime rules:

  1. An I/O Transaction is initiated in one of two modes, defined below:

  2. Once an I/O Transaction is initiated, its active flag is set to true. Implementations must allow read or write operations, depending on the mode, against an I/O Transaction when the active flag is set to true.

    This also sets the active boolean property on the FileHandle or the FileHandleWritable to true.

  3. Once an implementation is able to enforce the constraints defined for the mode, the I/O Transaction can be asynchronously started by the read() or write() method.

    The timing for when this happens is affected by the mode of the I/O Transaction and the scope file in question.

  4. Once an I/O Transaction has been started, it can read or write from a scope file, depending on mode, and on whether read() or write() has been called. At the time an individual read() or write() operation is processing, the active flag must be set to false. Implementations must NOT allow read or write operations, depending on the mode, against an I/O Transaction when the active flag is set to false.

    This also sets the active boolean property on the FileHandle or the FileHandleWritable to false.

  5. Once a read or write request is terminated, the I/O Transaction must become active again if it has further requests to fulfill from the request list. Implementations must process requests from the request list, depending on the mode, against an I/O Transaction when the active flag is set to true.

  6. Once all read and write operations in the request list have resulted in termination, the I/O Transaction is considered closed and user agents must run the closure steps. An I/O Transaction can also be forcibly closed through user action, such as window closure; in this case, it must NOT be active and must NOT handle further requests.

    A closed I/O Transaction has implications for flushing FileHandleWritable objects.

Individual calls to read() or write() made on a FileHandle or a FileHandleWritable are called requests. To process requests from the request list, the user agent must run the steps below:

  1. Requests must only placed in the request list if the I/O Transaction is active. If it is not active, the promise that initiated the request -- either the read() or write() call -- must be rejected with a TransactionInactive DOMException.

  2. After awaiting for the I/O Transaction to become active according to the lifetime rules, user agents must asynchronously run the next request in the request list according to the steps for read() or write(), at which point the request is removed from the request list.

  3. A request must terminate when the request returns; that is, in promise fulfilment, rejection, or cancellation, which must make the I/O Transaction active. Repeat the step above till all requests have terminated. When an I/O Transaction is forcibly terminated, such as upon window closure, this also forces any request to terminate.

    1. The cancellation of an individual read() or write() request depends on the use of the CancelablePromise type's cancel(), around which no consensus exist currently.

    2. The "forced" closure scenario may necessitate an abort state for I/O Transactions, which this specification doesn't currently have.

FileHandle WebIDL

readonly attribute FileOpenMode mode

Corresponds to the mode of the I/O Transaction. For a FileHandleWritable, on getting, the mode must be set to readwrite. For a FileHandle, on getting, the mode must be set to readonly.

readonly attribute boolean active

Corresponds to the active state of the I/O Transaction affiliated with this FileHandle. On getting, if true, implementations must allow calls to read() or write() to be placed in the request list for the I/O Transaction initiated for this object. On getting, if false, implementations must reject the read() or write() operation with a TransactionInactiveError DOMException.

Implementations must allow developers to check for active state of an I/O Transaction initiated by this object using this property.

attribute long long? offset

Denotes the start position of a read() or write() request in terms of byte order in the byte sequence. On getting, a value of 0 must be interpreted as the start of the file, corresponding to byte order position 0. On getting, a value of null must be interpreted as the end of the file -- that is, one byte position past the last byte in the byte order. All other values must be interpreted as a byte order position from which a request starts.

Promise<File> getFile()

When the getFile() method is called, user agents must run the steps below:

  1. Let p be a new promise. Run the following steps asynchronously.

    1. If this FileHandle has initiated a readwrite I/O Transaction and it is NOT active for this object's scope file, reject p with a TransactionInactive DOMException.

      If a FileHandleWritable is currently running the steps to process request, the file must not be returned as a File till the FileHandleWritable is active again.

    2. If any errors occur from the Directory Promise Rejection Error Table, reject p with the most appropriate error.

    3. Otherwise resolve p with a new File f representing the scope file belonging to this object.

      f.name should be set to the name of the scope file; f.size to the size of the scope file.

  2. Return p.
CancelablePromise<ArrayBuffer> read(unsigned long long size)

When the read(size) method is called, the user agent must run the steps below:

  1. Let p be a new promise. Run the following steps asynchronously:
    1. If the I/O Transaction is not active, reject p with a TransactionInactive DOMException.

    2. If offset is null, reject p with an InvalidStateError DOMException.

    3. Otherwise, once the lifetime conditions for the I/O Transaction are met, run the following substeps:

      This read request is being processed from the request list.

      1. Start the I/O Transaction and set this object's active to false.

        The I/O Transaction now is no longer active.

      2. Let b be a byte position pointer into the scope file. Advance b to the byte order position at offset.

      3. Starting with the byte at b, perform a read operation with the synchronous flag unset on the scope file, stopping after reading size total bytes. Advance b with each byte read. If any errors occur when performing the read operation, reject p with the error that corresponds to the termination reason.

      4. Resolve p with a new ArrayBuffer set to the body returned by the read operation and set this object's active to true.

  2. If no errors have occured, set offset to the byte order position that is at b+1.

    The implementation must set offset to the very next byte available to a subsequent read operation, which will start at that value of offset as byte order position.

  3. Return p.
unsigned long long size

The total number of bytes to be read. If size > the total size of the scope file, implementations must set size to be the size of the scope file.

The FileHandleWritable Interface

Writing to Files and Flushing

FileHandleWritable objects support the same features as a FileHandle, but in addition allow write requests on a scope file using a readwrite I/O Transaction. A single write request is a write operation, which appends bytes to a byte sequence -- that is, the scope file -- and writes them to disk; a write operation is the byte appending variant of adding to the Directory Tree and incurs the same potential errors and quota limitations.

A flushing FileHandleWritable flushes the OS Cache when the I/O Transaction is closed. A non-flushing FileHandleWritable does NOT flush the OS Cache.

The closure steps for a FileHandle and FileHandleWritable occur when the FileHandle or FileHandleWritable is closed, and must be as below:

  1. The I/O Transaction must not be active.

    I/O Transactions that are closed have processed all requests in the request list, meaning that the end of the callback sequence has been reached; they must have no pending requests, nor can any new request be made against them.

  2. For a flushing FileHandleWritable, flush the OS Cache. If the OS Cache cannot be flushed, user agents should write an error to the console.

    No promise rejection occurs on failed flush, since this occurs outside of the callback sequence; one could throw here, but that isn't a best practice for promise-based APIs. In practice, a failed flush might not be commonplace, so writing to the console might be ok for failure signaling.

    This is a potentially expensive operation, and is coined from within the openWrite call that initiates the I/O Transaction associated with this FileHandleWritable. Implementations may issue messages to users, particularly on browser close, if a flushing operation is underway, including writing to console.

  3. Implementations must not re-open a closed FileHandle object.

    A corollary to this is that implementations may garbage collect a closed FileHandle object, but this is an implementation detail.

FileHandleWritable WebIDL

CancelablePromise<void> write((DOMString or ArrayBuffer or ArrayBufferView or Blob) value)

When the write(value) method is called, the user agent must run the steps below:

  1. Let p be a new promise. Run the following steps asynchronously:
    1. If the I/O Transaction is not active, reject p with a TransactionInactive DOMException.

    2. Otherwise, once the lifetime conditions for the I/O Transaction are met, run the following substeps:

      This write request is being processed from the request list.

      1. Start the I/O Transaction and set this object's active to false.

        The I/O Transaction now is no longer active.

      2. Let b be a byte position pointer into the scope file. Advance b to the byte order position at offset. If offset is null, advance b to the end of the scope file -- that is, one past the last byte in the scope file.

      3. Convert value to a byte sequence. If value cannot be converted to a byte sequence, reject p with an InvalidStateError.

      4. Starting with the byte at b, perform a write operation on the scope file, stopping after writing the total number of bytes in value OR to the size set by a fulfilled setSize call, whichever is less. Advance b with each byte written. If any errors occur when performing the write operation, reject p with the error that corresponds to the most appropriate error from the Directory Promise Rejection Error Table and set this object's active to true.

        If setSize() is rejected, the write operation must also be rejected.

        The I/O Transaction is now active again.

      5. Otherwise, if no errors have occured, resolve p with undefined and set this object's active to true.

  2. If no errors have occured, set offset to the byte order position that is at b+1, unless offset is null. If offset is null, let offset remain null.

    The implementation must set offset to the very next byte available to a subsequent write operation, which will start at that value of offset as byte order position. If null, implementations must treat this as the end of the file.

  3. Return p.
Promise<void> setSize(optional unsigned long long size)

When the setSize(size) method is called, the user agent must run the steps below:

  1. Let p be a new promise. Run the following steps asynchronously:

    1. If this I/O Transaction is not active, reject p with a TransactionInactiveError.

      setSize() is not allowed on an I/O Transaction that is not active.

    2. If the value of size causes a storage quota error, reject p with a QuotaExceededError.

    3. Otherwise, the user agent must allocate the bytes specified by size in the Directory Tree, and resolve p with undefined.

  2. Return p.
optional unsigned long long size

The total number of bytes to be allocated for write operations. The value of size may be greater than the size of the total size of the scope file. If the optional size parameter is NOT included, user agents must allocate storage in the Directory Tree equivalent to the total number of bytes in the scope file. This argument may be used to truncate the size of a write operation; implementations should allow it to both truncate and increase file size.

It is not necessary to setSize() to write beyond the end of a scope file; user agents must permit writing beyond the end of a scope file, unless quota error(s) are generated.

FileSystem Configuration Parameters

CreateIfExistsMode ifExists = "fail"

This enum value indicates whether to write over a pre-existing file or to fail the attempt to write over a pre-existing file.

(DOMString or Blob or ArrayBuffer or ArrayBufferView) data

This is the raw data of the file.

OpenIfNotExistsMode ifNotExists = "create"

This value, if provided, means an implementation must create a scope file if it does not exist in the Directory Tree.

OpenIfExistsMode ifExists = "open"

This value, if provided and set by default, means an implementation must open a scope file that exists in the Directory Tree for a readwrite I/O Transaction associated with the FileHandleWritable.

boolean Flush = false

This value, if set outside the default to true, creates a flushing FileHandleWritable, which has specific closure steps; .

replace

This value, if set, replaces a File by removing it and writing a new File over it.

fail

This is the default; by default, a file cannot be replaced.

open

This value, if provided and set by default in the OpenWriteOptions dictionary, means an implementation must open a scope file that exists in the Directory Tree for a readwrite I/O Transaction associated with the FileHandleWritable.

fail

This value means the operation to open an existing file should return in rejection.

create

If a file does not exist, this value means it should be created.

fail

This value means the operation to open a non-existing file should return with rejection.

Directory dir

This value must be a Directory object signifying the directory for the move() method, which may include a renaming operation.

DOMString name

The name of the directory; normative conditions exist as part of the move() method.

readonly

This must only be associated with a FileHandle, and indicates a readonly I/O Transaction.

readwrite

This must only be associated with a FileHandleWritable, and indicates a readwrite I/O Transaction.

FileSystem URLs

Format and Origin of FileSystem URLs

A FileSystem URL must consist of the filesystem: scheme followed by scheme data which must consist of a string comprising the origin of the FileSystem URL, and the path from root of the file resource in the Directory Tree. A FileSystem URL may contain an optional fragment.

ABNF for FileSystem URL

scheme ":" origin path-from-root [fragIdentifier]
scheme = "filesystem"
; scheme is always "filesystem"
; origin is a string representation of the FileSystem URL's origin
; path-from-root always begins with a leading U+0024 SOLIDUS character
; fragIdentifier is optional and is as defined in [[!URL]]

Origin of a FileSystem URL

This area is currently TBD -- see the affiliated issue below:

Origin may be redefined in terms of Script Realm; open web platform bugs include 27203, 27204, and 26603.

The origin of a FileSystem URL, when serialized as a string, must conform to the Web Origin Specification's Unicode Serialization of an Origin algorithm [[!ORIGIN]]. Cross-origin requests on FileSystem URLs must return a network error.

Fragment Discussion

The fragment's resolution and processing directives depend on the media type [[RFC2046]] of a potentially retrieved representation, even though such a retrieval is only performed if the FileSystem URL is dereferenced. For example, in an HTML file [[!HTML]] the fragment could be used to refer to an anchor within the file. If the user agent does not recognize the media type of the resource, OR if a fragment is not meaningful within the resource, it must ignore the fragment.

A valid FileSystem URL reference could look like: filesystem:http://example.org:8080/music/disco/swedes/abba.html#bjorn where "#bjorn" might be an HTML fragment identifier referring to an element with an id attribute of "bjorn".

URL Generation Steps

When this specification says to follow the URL generation steps for a File object f, the following steps must be run:

  1. Let result be the empty string. Append the string "filesystem" (that is, the Unicode code point sequence U+0066, U+0069, U+006C, U+0065, U+0073, U+0079, U+0073, U+0074, U+0065, U+006D) to result.

  2. Append the ":" (U+003A COLON) character to result.

  3. Let O be the origin of the FileSystem URL. If the Unicode Serialization of an Origin algorithm [[!ORIGIN]] on O returns null, user agents may substitute an implementation defined value for the return value of the Unicode Serialization of an Origin algorithm. Append the result of the Unicode Serialization of an Origin algorithm for O [[!ORIGIN]] to result.

  4. Let p be the path from root of f. If there is no path from root for f, return failure and end these steps.

    Having no path from root might be because the file isn't found in the Directory Tree, or the File object reference is invalid.

  5. Otherwise, convert p to a Unicode string and append it to result.

  6. Return result.

Dereferencing Model for FileSystem URLs

The URL [[URL]] and Fetch [[!Fetch]] specifications should be considered normative when they get around to dealing with FileSystem URLs. This section provides guidance.

FileSystem URLs are dereferenced when the user agent retrieves the resource identified by the FileSystem URL and returns it to the requesting entity. This section provides guidance on requests and responses.

Only requests with GET [[!RFC7231]] are supported. Specifically, responses are only a subset of the following from HTTP [RFC7231]:

200 OK

This response is used if the request has succeeded, and no network error is generated.

Response Headers

Along with 200 OK responses, user agents use a Content-Type header [[!RFC7231]] that is equal to the value of the File's type attribute, if it is not the empty string.

Along with 200 OK responses, user agents use a Content-Length header [[!RFC7230]] that is equal to the value of the File's size attribute.

If a Content-Type header [[!RFC7231]] is provided, then user agents obtain and process that media type in a manner consistent with the Mime Sniffing specification [[!MIMESniff]].

User agents should use the File's name attribute, as if the response had a Content-Disposition header with the filename parameter set to the File's name attribute [[!RFC6266]].

Network Errors

Responses that do not succeed with a 200 OK act as if a network error has occurred [[!Fetch]]. Network errors are used when:

  1. Any request other than GET is used.

  2. A cross-origin request is made on a FileSystem URL.

  3. The underlying file in the Directory Tree is no longer in the location identified by the FileSystem URL's path from root in the scheme data.

Acknowledgements

Jonas Sicking, Mounir Lamouri, Ben Turner, Jan Varga, Andrea Marchesini, Doug Turner, Maciej Stachowiak