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.
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.
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:
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.
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.
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:
It is not contained by any other directory in the Directory Tree; it contains all others.
It is represented by a path that is equal to the string "/" (a single U+002F SOLIDUS character).
It has the name "root" (that is, the Unicode characters U+0072, U+006F, U+006F, U=0074).
"root" is not intended to be a reserved word. Other subdirectories can be created that are called "root."
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:
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.
Otherwise, remove the path for e.
If there is already a file or directory o with the identical name as e in d, run the following substeps:
Remove o from the Directory Tree.
This includes recursively removing any child nodes of o.
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.
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:
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.
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.
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:
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.
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.
If n ≥ the number of tokens in tokens, run the crawl the entire path algorithm and terminate this algorithm.
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]].
When the getFileSystemURL(file)
method is called, the user agent must run the steps below:
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
.
Otherwise, return the FileSystem URL returned by the URL generation steps.
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.
The origin of a Directory
is the same as the origin of the document
or worker
. Each origin has one root
Directory
.
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:
If the method was invoked without a path argument, run the following root
Directory
substeps:
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
.
root
Directory
.
Otherwise the method was invoked with a path argument. Run the following Directory
creation for a path argument steps:
If the path argument is the empty string, then reject p with an InvalidStateError
.
If the path argument is a single U+002F SOLIDUS character ("/") then resolve p with the root
Directory
.
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 ("/").
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:
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. 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
.
Otherwise, resolve p with a new Directory
d with the following properties:
d.name
is set to the last token in tokens; this must correspond to the last directory added to the Directory Tree.d is set to be the present working directory.
Subsequent Directory
operations on d that use a relative path will set the Directory Pointer to d.
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. |
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.
On getting, this must be the name of the underlying directory.
When the createFile(path, options)
method is run, the user agents must run the steps below:
Let p be a new promise.
Run the following steps asynchronously:
Strictly split the path
argument on the U+002F SOLIDUS character. Let n be the total number of resulting tokens.
If crawling the path until node n-1 for the path
argument returns failure, reject p with an InvalidStateError
.
If the nth token in tokens identifies a directory, reject p with an
InvalidStateError
.
The path
argument must identify a file.
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:
If the CreateIfExistsMode
ifExists
dictionary member of the options
argument is "fail" then reject p with a NoModificationAllowedError
.
If the CreateIfExistsMode
ifExists
dictionary member of the options
argument is "replace" then run the following substeps:
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]]).
Remove the file at the at the next position of the Directory Pointer which strictly matches the name of the nth token in tokens.
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.
Otherwise, resolve p with f.
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:
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]).
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
.Otherwise, resolve p with f.
Return p
When the createDirectory(path)
method is called, the user agent must run the steps below:
When the get(path)
method is called, the user agent must run the steps below:
Let p be a new promise.
Run the following steps asynchronously:
If crawling the entire path represented by the path
argument returns failure, reject p with an InvalidStateError
.
Otherwise, set d to the node at the position the Directory Pointer is pointing to, and run the following substeps:
Return p.
When the move(path, dest)
method is called, the user agent must run the steps below:
Let p be a new CancelablePromise.
The web does not have a CancelablePromise
type yet.
Run the following steps asynchronously:
If path
is a DOMString
, then crawl the entire path represented by path
. If it returns failure, then reject p with an InvalidStateError
.
If dest
is a DOMString
, then run the following substeps:
dest
as a path. If it returns failure, then reject p with an InvalidStateError
.dest
is a File
, reject p with an InvalidStateError
.
If the node that corresponds to dest
is a child of path
, then reject p with an
InvalidStateError
.
This is an illegal transfer.
If path
and dest
both represent directories and are at the identical position on the Directory Tree, run the following substeps:
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.
Otherwise:
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
resolve p with undefined.
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.
Otherwise:
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.
If there were no errors, resolve p with undefined.
Return p.
When the remove(path)
method is called, the user agent must run the steps below:
Let p be a new promise.
Run the following steps asynchronously:
If path
is a DOMString
crawl the entire path. If it returns failure, resolve p with false
.
If path
is a File
then run the following substeps:
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.
Resolve p with true
.
If path
represents a directory run the following substeps:
If path
has one or more child nodes, resolve p with false
.
Otherwise, run the following substeps:
Remove from the Directory Tree the node identified by the path
argument.
Resolve p with true
.
When the removeDeep(path)
method is called, the user agent must run the steps below:
Let p be a new promise.
Run the following steps asynchronously:
If path
is a DOMString
crawl the entire path. If it returns failure, resolve p with false
.
If path
is a File
then run the following substeps:
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.
Resolve p with true
.
If path
represents a directory run the following substeps:
If path
has no child nodes, run the following substeps:
path
argument.
true
If path
has one or more child nodes, run the following substeps:
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.
Resolve p with true
.
When the openRead(path)
method is called, the user agent must run the steps below:
Run the following steps asynchronously:
If path
is a DOMString
, run the following substeps:
InvalidStateError
.path
represents a directory node and not a file node, reject p with an InvalidStateError
.
Otherwise, run the following substeps:
If the I/O Transaction cannot be initiated according to the lifetime rules, reject p with an InvalidStateError
.
Otherwise, let f be a new FileHandle
set to refer to the file at path
. Set the FileOpenMode
enum property on f to readonly
.
Initiate the readonly I/O Transaction associated with f according to the lifetime rules.
Resolve p with f.
When the openWrite(path, options)
method is called, the user agent must run the steps below:
Run the following steps asynchronously:
If the path
argument is a DOMString, then run the substeps below:
Strictly split the path
argument on the U+002F SOLIDUS character. Let n be the total number of resulting tokens.
If crawling the path until node n-1 for the path
argument returns failure, reject p with an InvalidStateError
.
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.
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.
If path
is a File
then set filename to path.name
.
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.
If the optional OpenWriteOptions
dictionary argument is used, and the OpenIfExistsMode
ifExists
member is "fail" then reject p with an InvalidStateError
.
Otherwise, if the OpenIfExistsMode
ifExists
dictionary member is "open" then run the substeps below:
This enum value is set to "open" by default.
If the Flush
dictionary member is set to true
, run the following substeps:
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
.
Initiate the readwrite I/O Transaction associated with f according to the lifetime rules.
Resolve p with f.
If the Flush
dictionary member is set to false
, run the following substeps:
FileHandleWritable
with the FileOpenMode
enum set to readwrite
, and set to the scope file corresponding to path
. Set f.active
to true
.Initiate the readwrite I/O Transaction associated with f according to the lifetime rules.
Resolve p with f.
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:
If the optional OpenWriteOptions
dictionary argument is used, and the OpenIfNotExistsMode
ifNotExists
member is "fail" then reject p with an InvalidStateError
.
Otherwise, if the OpenIfNotExistsMode
ifNotExists
dictionary member is "create" then run the substeps below:
This enum value is set to "create" by default.
If the Flush
dictionary member is set to true
, run the following substeps:
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
.
Initiate the readwrite I/O Transaction associated with f according to the lifetime rules.
Resolve p with f.
If the Flush
dictionary member is set to false
, run the following substeps:
FileHandleWritable
with the FileOpenMode
enum set to readwrite
, and set to the scope file corresponding to path
. Set f.active
to true
.Initiate the readwrite I/O Transaction associated with f according to the lifetime rules.
Resolve p with f.
When the getFilesAndDirectories()
method is called, the user agent must run the steps below:
Let p be a new promise and s a sequence, initially set to an empty sequence.
Run the following steps asynchronously:
If the present working directory has no files or directories, resolve p with s, which is still an empty sequence.
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.
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.
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.
Return p.
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:
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
.
Observable
's rejection path with an InvalidStateError
.File
corresponding to the file; when done, invoke this Observable
's final state.File
or Directory
) until done; when done, invoke this Observable
's final state.When the enumerateDeep(path)
method is called, the user agent must run the steps below:
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
.
If any immediate child is a directory, run the following substeps for all child nodes of that directory:
File
object for any immediate child nodes that are files.When done, invoke this Observable
's final state.
If a parameter is provided, and crawling the entire path returns failure, invoke the Observable
's rejection path with an InvalidStateError
.
If crawling the entire path above does not return failure, run the substeps below:
If the Directory Pointer points to a file, emit a File
corresponding to the file; when done, invoke this Observable
's final state.
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
.
If any immediate child is a directory, run the following substeps for all child nodes of that directory:
File
object for any immediate child nodes that are files.When done, invoke this Observable
's final state.
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 FileHandle
s. 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:
An I/O Transaction is initiated in one of two modes, defined below:
A readonly I/O Transaction must only be allowed to read data from the scope file, and is initiated by a fulfilled promise on the openRead()
operation, called on a Directory
object. This type of I/O Transaction belongs to a FileHandle
object, on which write operations cannot take place on the scope file; only read requests are allowed. Any number of readonly I/O Transactions may run at the same time on the same scope file, which is an advantage of this mode. If two concurrent readonly I/O Transactions are running, the data that the implementation returns through read operations on each I/O Transaction MUST remain constant. That is, two concurrent requests to read the same piece of data MUST yield the same result both for the case when data is found and the result is that data, and for the case when data is not found and a lack of data is indicated.
There are a number of ways that an implementation ensures this. The implementation may prevent any readwrite I/O Transaction, whose scope file is the same as the scope file of the readonly I/O Transaction, from starting until the readonly I/O Transaction finishes. Or the implementation can allow the readonly I/O Transaction to see a snapshot of the contents of the scope file which is taken when the readonly I/O Transaction started.
A readwrite I/O Transaction is allowed to read and write data from the scope file, and is initiated by a fulfilled promise on the openWrite()
operation, called on a Directory
object. This type of I/O Transaction belongs to a FileHandleWritable
object, on which both read and write requests can take place on the scope file. Multiple readWrite I/O Transactions must NOT run at the same time on the same scope file, since otherwise that may mean that they would modify each other's data. Implementations must ensure that another readwrite I/O Transaction does not modify the scope file while the present readwrite I/O Transaction is active. If multiple readwrite I/O Transactions are attempting to write to the same scope file, the readwrite I/O Transaction that is first gets access to the scope file first.
Implementations must reject openWrite()
promises that violate the concurrency condition for this mode with an InvalidStateError
DOMException
.
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
.
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.
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
.
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
.
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:
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
.
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.
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.
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
.
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.
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.
When the getFile()
method is called, user agents must run the steps below:
Let p be a new promise. Run the following steps asynchronously.
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.
If any errors occur from the Directory Promise Rejection Error Table, reject p with the most appropriate error.
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.
When the read(size)
method is called, the user agent must run the steps below:
If the I/O Transaction is not active, reject p with a TransactionInactive
DOMException
.
If offset
is null
, reject p with an InvalidStateError
DOMException
.
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.
Start the I/O Transaction and set this object's active
to false
.
The I/O Transaction now is no longer active.
Let b be a byte position pointer into the scope file. Advance b to the byte order position at offset
.
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.
Resolve p with a new ArrayBuffer
set to the body returned by the read operation and set this object's active
to true
.
The I/O Transaction is now active again.
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.
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.
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:
The I/O Transaction must not be active.
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.
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.
When the write(value)
method is called, the user agent must run the steps below:
If the I/O Transaction is not active, reject p with a TransactionInactive
DOMException
.
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.
Start the I/O Transaction and set this object's active
to false
.
The I/O Transaction now is no longer active.
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.
Convert value
to a byte sequence. If value
cannot be converted to a byte sequence, reject p with an InvalidStateError
.
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.
Otherwise, if no errors have occured, resolve p with undefined and set this object's active
to true
.
The I/O Transaction is now active again.
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.
When the setSize(size)
method is called, the user agent must run the steps below:
Let p be a new promise. Run the following steps asynchronously:
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.
If the value of size
causes a storage quota error, reject p with a QuotaExceededError
.
Otherwise, the user agent must allocate the bytes specified by size
in the Directory Tree, and resolve p with undefined.
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.
This enum value indicates whether to write over a pre-existing file or to fail the attempt to write over a pre-existing file.
This is the raw data of the file.
This value, if provided, means an implementation must create a scope file if it does not exist in the Directory Tree.
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
.
This value, if set outside the default to true
, creates a flushing FileHandleWritable
, which has specific closure steps; .
This value, if set, replaces a File
by removing it and writing a new File
over it.
This is the default; by default, a file cannot be replaced.
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
.
This value means the operation to open an existing file should return in rejection.
If a file does not exist, this value means it should be created.
This value means the operation to open a non-existing file should return with rejection.
This value must be a Directory
object signifying the directory for the move()
method, which may include a renaming operation.
The name of the directory; normative conditions exist as part of the move()
method.
This must only be associated with a FileHandle
, and indicates a readonly I/O Transaction.
This must only be associated with a FileHandleWritable
, and indicates a readwrite I/O Transaction.
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.
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]]
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.
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".
When this specification says to follow the URL generation steps for a File
object f, the following steps must be run:
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.
Append the ":" (U+003A COLON) character to result.
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.
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.
Otherwise, convert p to a Unicode string and append it to result.
Return result.
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]:
This response is used if the request has succeeded, and no network error is generated.
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]].
Responses that do not succeed with a 200 OK act as if a network error has occurred [[!Fetch]]. Network errors are used when:
Any request other than GET is used.
A cross-origin request is made on a FileSystem URL.
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.
Jonas Sicking, Mounir Lamouri, Ben Turner, Jan Varga, Andrea Marchesini, Doug Turner, Maciej Stachowiak