1. Introduction
This section is non-normative.
This API enables developers to build powerful apps that interact with other (non-Web) apps on the user’s device via the device’s file system. Prominent examples of applications where users expect this functionality are IDEs, photo and video editors, text editors, and more. After a user grants a web app access, this API allows the app to read or save changes directly to files and folders on the user’s device. Beyond reading and writing files, this API provides the ability to open a directory and enumerate its contents. Additionally, web apps can use this API to store references to files and directories they’ve been given access to, allowing the web apps to later regain access to the same content without requiring the user to select the same file again.
This
API
is
similar
to
<input
type=file>
and
<input
type=file
webkitdirectory>
[entries-api]
in
that
user
interaction
happens
through
file
and
directory
picker
dialogs.
Unlike
those
APIs,
this
API
is
currently
purely
a
javascript
API,
and
does
not
integrate
with
forms
and/or
input
elements.
Additionally
this
API
also
makes
it
possible
for
websites
to
get
access
to
some
directory
without
having
to
first
prompt
the
user
for
access.
This
enables
use
cases
where
a
website
wants
to
save
data
to
disk
before
a
user
has
picked
a
location
to
save
to,
without
forcing
the
website
to
use
a
completely
different
storage
mechanism
with
a
different
API
for
such
files.
It
also
makes
it
easier
to
write
automated
tests
for
code
using
this
API.
The
entry
point
for
this
is
the
navigator.storage.getDirectory()
method.
This
is
similar
to
the
temporary
file
system
as
defined
in
earlier
drafts
of
File
API:
Directories
and
System
.
2. Files and Directories
2.1. Concepts
An entry is either a file entry or a directory entry .
Each entry has an associated name (a string ).
A valid file name is a string that is not an empty string, is not equal to "." or "..", and does not contain '/' or any other character used as path separator on the underlying platform.
Note: This means that '\' is not allowed in names on Windows, but might be allowed on other operating systems. Additionally underlying file systems might have further restrictions on what names are or aren’t allowed, so a string merely being a valid file name is not a guarantee that creating a file or directory with that name will succeed.
We should consider having further normative restrictions on file names that will never be allowed using this API, rather than leaving it entirely up to underlying file systems.
A valid suffix code point is a code point that is ASCII alphanumeric , U+002B (+), or U+002E (.).
Note:
These
code
points
were
chosen
to
support
most
pre-existing
file
formats.
The
vast
majority
of
file
extensions
are
purely
alphanumeric,
but
compound
extensions
(such
as
.tar.gz
)
and
extensions
such
as
.c++
for
C++
source
code
are
also
fairly
common,
hence
the
inclusion
of
+
and
.
as
allowed
code
points.
A file entry additionally consists of binary data (a byte sequence ) and a modification timestamp (a number representing the number of milliseconds since the Unix Epoch ).
A directory entry additionally consists of a set of children , which are themselves entries . Each member is either a file or a directory .
An entry entry should be contained in the children of at most one directory entry , and that directory entry is also known as entry ’s parent . An entry 's parent is null if no such directory entry exists.
Note:
Two
different
entries
can
represent
the
same
file
or
directory
on
disk,
in
which
case
it
is
possible
for
both
entries
to
have
a
different
parent,
or
for
one
entry
to
have
a
parent
while
the
other
entry
does
not
have
a
parent.
Typically
an
entry
does
not
have
a
parent
if
it
was
returned
by
navigator.storage.getDirectory()
or
one
of
the
local
file
system
handle
factories
,
and
an
entry
will
have
a
parent
in
all
other
cases.
Entries can (but don’t have to) be backed by files on the host operating system’s local file system, so it is possible for the binary data , modification timestamp , and children of entries to be modified by applications outside of this specification. Exactly how external changes are reflected in the data structures defined by this specification, as well as how changes made to the data structures defined here are reflected externally is left up to individual user-agent implementations.
An entry a is the same as an entry b if a is equal to b , or if a and b are backed by the same file or directory on the local file system.
TODO: Explain better how entries map to files on disk (multiple entries can map to the same file or directory on disk but an entry doesn’t have to map to any file on disk).
-
Let result be a new promise .
-
Run the following steps in parallel :
-
If child is the same as root , resolve result with an empty list, and abort.
-
Let childPromises be « ».
-
Wait for all childPromises , with the following success steps:
-
If result hasn’t been resolved yet, resolve result with
null
.
-
-
-
Return result .
2.2. Permissions
The
"file-system"
powerful
feature
's
permission-related
algorithms
and
types
are
defined
as
follows:
- permission descriptor type
-
FileSystemPermissionDescriptor
, defined as:enum
{FileSystemPermissionMode
,"read"
};"readwrite" dictionary
:FileSystemPermissionDescriptor PermissionDescriptor {required FileSystemHandle
;handle FileSystemPermissionMode
= "read"; };mode - permission state constraints
-
To determine permission state constraints for a
FileSystemPermissionDescriptor
desc , run these steps:-
If entry represents an entry in an origin private file system , this descriptor’s permission state must always be
"granted"
. -
Otherwise, if entry ’s parent is not null, this descriptor’s permission state must be equal to the permission state for a descriptor with the same
mode
, and ahandle
representing entry ’s parent . -
Otherwise, if desc .
mode
is"readwrite"
:-
Let read state be the permission state for a descriptor with the same
handle
, butmode
="read"
. -
If read state is not
"granted"
, this descriptor’s permission state must be equal to read state .
-
- permission request algorithm
-
Given a
FileSystemPermissionDescriptor
desc and aPermissionStatus
status , run these steps:-
Run the boolean permission query algorithm on desc and status .
-
Let settings be desc .
handle
's relevant settings object . -
Let global be settings ’s global object .
-
If global is not a
Window
, throw aSecurityError
. -
If global does not have transient activation , throw a
SecurityError
. -
If settings ’s origin is not same origin with settings ’s top-level origin , throw a
SecurityError
. -
Request permission to use desc .
-
Run the boolean permission query algorithm on desc and status .
Ideally this user activation requirement would be defined upstream. [Issue #WICG/permissions-request#2]
-
FileSystemHandle
handle
and
a
FileSystemPermissionMode
mode
,
run
these
steps:
-
Let desc be a
FileSystemPermissionDescriptor
. -
Set desc .
name
to"file-system"
. -
Set desc .
handle
to handle . -
Set desc .
mode
to mode . -
Return desc ’s permission state .
FileSystemHandle
handle
and
a
FileSystemPermissionMode
mode
,
run
these
steps:
-
Let desc be a
FileSystemPermissionDescriptor
. -
Set desc .
name
to"file-system"
. -
Set desc .
handle
to handle . -
Set desc .
mode
to mode . -
Let status be the result of running create a PermissionStatus for desc .
-
Run the permission request algorithm for the
"file-system"
feature, given desc and status . -
Return desc ’s permission state .
Currently
FileSystemPermissionMode
can
only
be
"read"
or
"readwrite"
.
In
the
future
we
might
want
to
add
a
"write"
mode
as
well
to
support
write-only
handles.
[Issue
#119]
2.3.
The
FileSystemHandle
interface
dictionary {
FileSystemHandlePermissionDescriptor FileSystemPermissionMode = "read"; };
mode enum {
FileSystemHandleKind ,
"file" , }; [
"directory" Exposed =(Window ,Worker ),SecureContext ,Serializable ]interface {
FileSystemHandle readonly attribute FileSystemHandleKind kind ;readonly attribute USVString name ;Promise <boolean >isSameEntry (FileSystemHandle );
other Promise <PermissionState >queryPermission (optional FileSystemHandlePermissionDescriptor = {});
descriptor Promise <PermissionState >requestPermission (optional FileSystemHandlePermissionDescriptor = {}); };
descriptor
A
FileSystemHandle
object
represents
an
entry
.
Each
FileSystemHandle
object
is
associated
with
an
entry
(an
entry
).
Multiple
separate
objects
implementing
the
FileSystemHandle
interface
can
all
be
associated
with
the
same
entry
simultaneously.
FileSystemHandle
objects
are
serializable
objects
.
In the Origin Trial as available in Chrome 78, these objects are not yet serializable. In Chrome 82 they are.
Their serialization steps , given value , serialized and forStorage are:
-
Set serialized .[[Origin]] to value ’s relevant settings object 's origin .
-
Set serialized .[[Entry]] to value ’s entry .
-
If serialized .[[Origin]] is not same origin with value ’s relevant settings object 's origin , then throw a
DataCloneError
. -
Set value ’s entry to serialized .[[Entry]]
-
handle
.
kind
-
Returns
"file"
if handle is aFileSystemFileHandle
, or"directory"
if handle is aFileSystemDirectoryHandle
.This can be used to distinguish files from directories when iterating over the contents of a directory.
-
handle
.
name
-
Returns the name of the entry represented by handle .
The
kind
attribute
must
return
"file"
if
the
associated
entry
is
a
file
entry
,
and
return
"directory"
otherwise.
The
name
attribute
must
return
the
name
of
the
associated
entry
.
2.3.1.
The
isSameEntry()
method
-
same
=
await
handle1
.
isSameEntry
( handle2 ) -
Returns true if handle1 and handle2 represent the same file or directory.
This method is first available in Chrome 82.
isSameEntry(
other
)
method,
when
invoked,
must
run
these
steps:
-
Let realm be this 's relevant Realm .
-
Let p be a new promise in realm .
-
Run the following steps in parallel :
-
Return p .
2.3.2.
The
queryPermission()
method
FileSystemHandle/queryPermission
In only one current engine.
Opera ? Edge 86+
Edge (Legacy) ? IE None
Firefox for Android ? iOS Safari ? Chrome for Android ? Android WebView None Samsung Internet ? Opera Mobile None
-
status
=
await
handle
.
queryPermission
({mode
:"read"
})- status = await handle .
queryPermission()
- status = (await navigator.
permissions
.query
({name
: "file-system",handle
: handle })).state
- status = await handle .
-
Queries the current state of the read permission of this handle. If this returns
"prompt"
the website will have to callrequestPermission()
before any operations on the handle can be done. If this returns"denied"
any operations will reject.Usually handles returned by the local file system handle factories will initially return
"granted"
for their read permission state, however other than through the user revoking permission, a handle retrieved from IndexedDB is also likely to return"prompt"
. -
status
=
await
handle
.
queryPermission
({mode
:"readwrite"
})- status = (await navigator.
permissions
.query
({name
: "file-system",handle
: handle ,mode
:"readwrite"
}).state
- status = (await navigator.
-
Queries the current state of the write permission of this handle. If this returns
"prompt"
, attempting to modify the file or directory this handle represents will require user activation and will result in a confirmation prompt being shown to the user. However if the state of the read permission of this handle is also"prompt"
the website will need to callrequestPermission()
. There is no automatic prompting for read access when attempting to read from a file or directory.
The
integration
with
the
permissions
API’s
query()
method
is
not
yet
implemented
in
Chrome.
queryPermission(
descriptor
)
method,
when
invoked,
must
run
these
steps:
-
Let result be a new promise .
-
Run the following steps in parallel :
-
Let state be the result of querying file system permission given this and descriptor .
mode
. -
Resolve result with state .
-
-
Return result .
2.3.3.
The
requestPermission()
method
FileSystemHandle/requestPermission
In only one current engine.
Opera ? Edge 86+
Edge (Legacy) ? IE None
Firefox for Android ? iOS Safari ? Chrome for Android ? Android WebView None Samsung Internet ? Opera Mobile None
-
status
=
await
handle
.
requestPermission
({mode
:"read"
})- status = await handle .
requestPermission()
- status = await handle .
-
If the state of the read permission of this handle is anything other than
"prompt"
, this will return that state directly. If it is"prompt"
however, user activation is needed and this will show a confirmation prompt to the user. The new read permission state is then returned, depending on the user’s response to the prompt. -
status
=
await
handle
.
requestPermission
({mode
:"readwrite"
}) -
If the state of the write permission of this handle is anything other than
"prompt"
, this will return that state directly. If the status of the read permission of this handle is"denied"
this will return that.Otherwise the state of the write permission is
"prompt"
and this will show a confirmation prompt to the user. The new write permission state is then returned, depending on what the user selected.
requestPermission(
descriptor
)
method,
when
invoked,
must
run
these
steps:
-
Let result be a new promise .
-
Run the following steps in parallel :
-
Let state be the result of requesting file system permission given this and descriptor .
mode
. If that throws an exception, reject result with that exception and abort. -
Resolve result with state .
-
-
Return result .
2.4.
The
FileSystemFileHandle
interface
dictionary {
FileSystemCreateWritableOptions boolean =
keepExistingData false ; }; [Exposed =(Window ,Worker ),SecureContext ,Serializable ]interface :
FileSystemFileHandle FileSystemHandle {Promise <File >getFile ();Promise <FileSystemWritableFileStream >createWritable (optional FileSystemCreateWritableOptions = {}); };
options
A
FileSystemFileHandle
's
associated
entry
must
be
a
file
entry
.
FileSystemFileHandle
objects
are
serializable
objects
.
Their
serialization
steps
and
deserialization
steps
are
the
same
as
those
for
FileSystemHandle
.
In the Origin Trial as available in Chrome 78, these objects are not yet serializable. In Chrome 82 they are.
2.4.1.
The
getFile()
method
getFile()
method,
when
invoked,
must
run
these
steps:
-
Let result be a new promise .
-
Run the following steps in parallel :
-
Let permissionStatus be the result of querying file system permission given this and
"read"
. -
If permissionStatus is not
"granted"
, reject result with aNotAllowedError
and abort. -
Let f be a new
File
. -
Set f ’s snapshot state to the current state of entry .
-
Set f ’s underlying byte sequence to a copy of entry ’s binary data .
-
Initialize the value of f ’s
name
attribute to entry ’s name . -
Initialize the value of f ’s
lastModified
attribute to entry ’s modification timestamp . -
Initialize the value of f ’s
type
attribute to an implementation defined value, based on for example entry ’s name and/or its file extension.The reading and snapshotting behavior needs to be better specified in the [FILE-API] spec, for now this is kind of hand-wavy.
-
Resolve result with f .
-
-
Return result .
2.4.2.
The
createWritable()
method
In the Origin Trial as available in Chrome 82, createWritable replaces the createWriter method.
-
stream
=
await
fileHandle
.
createWritable()
- stream = await fileHandle .
createWritable
({keepExistingData
: true/false }) - stream = await fileHandle .
-
Returns a
FileSystemWritableFileStream
that can be used to write to the file. Any changes made through stream won’t be reflected in the file represented by fileHandle until the stream has been closed. User agents try to ensure that no partial writes happen, i.e. the file represented by fileHandle will either contain its old contents or it will contain whatever data was written through stream up until the stream has been closed.This is typically implemented by writing data to a temporary file, and only replacing the file represented by fileHandle with the temporary file when the writable filestream is closed.
If
keepExistingData
isfalse
or not specified, the temporary file starts out empty, otherwise the existing file is first copied to this temporary file.
There has been some discussion around and desire for a "inPlace" mode for createWritable (where changes will be written to the actual underlying file as they are written to the writer, for example to support in-place modification of large files or things like databases). This is not currently implemented in Chrome. Implementing this is currently blocked on figuring out how to combine the desire to run malware checks with the desire to let websites make fast in-place modifications to existing large files. [Issue #67]
createWritable(
options
)
method,
when
invoked,
must
run
these
steps:
-
Let result be a new promise .
-
Run the following steps in parallel :
-
Let permissionStatus be the result of requesting file system permission given this and
"readwrite"
. If that throws an exception, reject result with that exception and abort. -
If permissionStatus is not
"granted"
, reject result with aNotAllowedError
and abort. -
Let stream be the result of creating a new FileSystemWritableFileStream for entry in this ’s relevant realm .
-
If options .
keepExistingData
istrue
:-
Set stream . [[buffer]] to a copy of entry ’s binary data .
-
-
Resolve result with stream .
-
-
Return result .
2.5.
The
FileSystemDirectoryHandle
interface
dictionary {
FileSystemGetFileOptions boolean =
create false ; };dictionary {
FileSystemGetDirectoryOptions boolean =
create false ; };dictionary {
FileSystemRemoveOptions boolean =
recursive false ; }; [Exposed =(Window ,Worker ),SecureContext ,Serializable ]interface :
FileSystemDirectoryHandle FileSystemHandle {async iterable <USVString ,FileSystemHandle >;Promise <FileSystemFileHandle >getFileHandle (USVString ,
name optional FileSystemGetFileOptions = {});
options Promise <FileSystemDirectoryHandle >getDirectoryHandle (USVString ,
name optional FileSystemGetDirectoryOptions = {});
options Promise <undefined >removeEntry (USVString ,
name optional FileSystemRemoveOptions = {});
options Promise <sequence <USVString >?>resolve (FileSystemHandle ); };
possibleDescendant
A
FileSystemDirectoryHandle
's
associated
entry
must
be
a
directory
entry
.
FileSystemDirectoryHandle
objects
are
serializable
objects
.
Their
serialization
steps
and
deserialization
steps
are
the
same
as
those
for
FileSystemHandle
.
In the Origin Trial as available in Chrome 78, these objects are not yet serializable. In Chrome 82 they are.
In
Chrome
versions
upto
Chrome
85
getFileHandle
and
getDirectoryHandle
where
called
getFile
and
getDirectory
instead.
2.5.1. Directory iteration
-
for
await
(let
[
name
,
handle
]
of
directoryHandle
)
{}
- for await (let [ name , handle ] of directoryHandle . entries()) {}
- for await (let handle of directoryHandle . values()) {}
- for await (let name of directoryHandle . keys()) {}
- for await (let [ name , handle ] of directoryHandle . entries()) {}
-
Iterates over all entries whose parent is the entry represented by directoryHandle . Entries that are created or deleted while the iteration is in progress might or might not be included. No guarantees are given either way.
In
Chrome
this
is
currently
implemented
as
a
directoryHandle.getEntries()
method
that
can
be
used
in
a
for
await..of
loop.
This
getEntries()
method
returns
more
or
less
the
same
async
iterable
as
what
is
returned
by
values()
in
this
specification.
The
proper
async
iterable
declaration
is
not
yet
implemented.
In the future we might want to add arguments to the async iterable declaration to support for example recursive iteration. [Issue #173]
FileSystemDirectoryHandle
handle
ant
its
async
iterator
iterator
are:
-
Let permissionStatus be the result of querying file system permission given handle and
"read"
. -
If permissionStatus is not
"granted"
, throw aNotAllowedError
. -
Set iterator ’s past results to an empty set .
FileSystemDirectoryHandle
handle
and
its
async
iterator
iterator
:
-
Let promise be a new promise .
-
Let directory be handle ’s entry .
-
Let permissionStatus be the result of querying file system permission given handle and
"read"
. -
If permissionStatus is not
"granted"
, reject promise with aNotAllowedError
and return promise . -
Let child be an entry in directory ’s children , such that child ’s name is not contained in iterator ’s past results , or
null
if no such entry exists.Note: This is intentionally very vague about the iteration order. Different platforms and file systems provide different guarantees about iteration order, and we want it to be possible to efficiently implement this on all platforms. As such no guarantees are given about the exact order in which elements are returned.
-
If child is
null
, then:-
Resolve promise with
undefined
.
-
-
Otherwise:
-
Append child ’s name to iterator ’s past results .
-
If child is a file entry :
-
Let result be a new
FileSystemFileHandle
associated with child .
-
-
Otherwise:
-
Let result be a new
FileSystemDirectoryHandle
associated with child .
-
-
-
Return promise .
2.5.2.
The
getFileHandle()
method
-
fileHandle
=
await
directoryHandle
.
getFileHandle
( name )- fileHandle = await directoryHandle .
getFileHandle
( name , {create
: false }) - fileHandle = await directoryHandle .
-
Returns a handle for a file named name in the directory represented by directoryHandle . If no such file exists, this rejects.
-
fileHandle
=
await
directoryHandle
.
getFileHandle
( name , {create
: true }) -
Returns a handle for a file named name in the directory represented by directoryHandle . If no such file exists, this creates a new file. If no file with named name can be created this rejects. Creation can fail because there already is a directory with the same name, because the name uses characters that aren’t supported in file names on the underlying file system, or because the user agent for security reasons decided not to allow creation of the file.
This operation requires write permission, even if the file being returned already exists. If this handle doesn’t already have write permission, this could result in a prompt being shown to the user. To get an existing file without needing write permission, call this method with
{
.create
: false }
getFileHandle(
name
,
options
)
method,
when
invoked,
must
run
these
steps:
-
Let result be a new promise .
-
Run the following steps in parallel :
-
If name is not a valid file name , reject result with a
TypeError
and abort. -
If options .
create
istrue
:-
Let permissionStatus be the result of requesting file system permission given this and
"readwrite"
. If that throws an exception, reject result with that exception and abort.
-
-
Otherwise:
-
Let permissionStatus be the result of querying file system permission given this and
"read"
.
-
-
If permissionStatus is not
"granted"
, reject result with aNotAllowedError
and abort. -
For each child of entry ’s children :
-
If child ’s name equals name :
-
If child is a directory entry :
-
Reject result with a
TypeMismatchError
and abort.
-
-
Resolve result with a new
FileSystemFileHandle
whose entry is child and abort.
-
-
-
If options .
create
isfalse
:-
Reject result with a
NotFoundError
and abort.
-
-
Let child be a new file entry .
-
Set child ’s name to name .
-
Set child ’s binary data to an empty byte sequence .
-
Set child ’s modification timestamp to the current time.
-
If creating child in the underlying file system throws an exception, reject result with that exception and abort.
Better specify what possible exceptions this could throw. [Issue #68]
-
Resolve result with a new
FileSystemFileHandle
whose entry is child .
-
-
Return result .
2.5.3.
The
getDirectoryHandle()
method
-
subdirHandle
=
await
directoryHandle
.
getDirectoryHandle
( name )- subdirHandle = await directoryHandle .
getDirectoryHandle
( name , {create
: false }) - subdirHandle = await directoryHandle .
-
Returns a handle for a directory named name in the directory represented by directoryHandle . If no such directory exists, this rejects.
-
subdirHandle
=
await
directoryHandle
.
getDirectoryHandle
( name , {create
: true }) -
Returns a handle for a directory named name in the directory represented by directoryHandle . If no such directory exists, this creates a new directory. If creating the directory failed, this rejects. Creation can fail because there already is a file with the same name, or because the name uses characters that aren’t supported in file names on the underlying file system.
This operation requires write permission, even if the directory being returned already exists. If this handle doesn’t already have write permission, this could result in a prompt being shown to the user. To get an existing directory without needing write permission, call this method with
{
.create
: false }
getDirectoryHandle(
name
,
options
)
method,
when
invoked,
must
run
these
steps:
-
Let result be a new promise .
-
Run the following steps in parallel :
-
If name is not a valid file name , reject result with a
TypeError
and abort. -
If options .
create
istrue
:-
Let permissionStatus be the result of requesting file system permission given this and
"readwrite"
. If that throws an exception, reject result with that exception and abort.
-
-
Otherwise:
-
Let permissionStatus be the result of querying file system permission given this and
"read"
.
-
-
If permissionStatus is not
"granted"
, reject result with aNotAllowedError
and abort. -
For each child of entry ’s children :
-
If child ’s name equals name :
-
If child is a file entry :
-
Reject result with a
TypeMismatchError
and abort.
-
-
Resolve result with a new
FileSystemDirectoryHandle
whose entry is child and abort.
-
-
-
If options .
create
isfalse
:-
Reject result with a
NotFoundError
and abort.
-
-
Let child be a new directory entry .
-
Set child ’s name to name .
-
If creating child in the underlying file system throws an exception, reject result with that exception and abort.
Better specify what possible exceptions this could throw. [Issue #68]
-
Resolve result with a new
FileSystemDirectoryHandle
whose entry is child .
-
-
Return result .
2.5.4.
The
removeEntry()
method
-
await
directoryHandle
.
removeEntry
( name )- await directoryHandle .
removeEntry
( name , {recursive
: false }) - await directoryHandle .
-
If the directory represented by directoryHandle contains a file named name , or an empty directory named name , this will attempt to delete that file or directory.
Attempting to delete a file or directory that does not exist is considered success, while attempting to delete a non-empty directory will result in a promise rejection.
-
await
directoryHandle
.
removeEntry
( name , {recursive
: true }) -
Removes the entry named name in the directory represented by directoryHandle . If that entry is a directory, its contents will also be deleted recursively. recursively.
Attempting to delete a file or directory that does not exist is considered success.
removeEntry(
name
,
options
)
method,
when
invoked,
must
run
these
steps:
-
Let result be a new promise .
-
Run the following steps in parallel :
-
If name is not a valid file name , reject result with a
TypeError
and abort. -
Let permissionStatus be the result of requesting file system permission given this and
"readwrite"
. If that throws an exception, reject result with that exception and abort. -
If permissionStatus is not
"granted"
, reject result with aNotAllowedError
and abort. -
For each child of entry ’s children :
-
If child ’s name equals name :
-
If child is a directory entry :
-
If child ’s children is not empty and options .
recursive
isfalse
:-
Reject result with an
InvalidModificationError
and abort.
-
-
-
If removing child in the underlying file system throws an exception, reject result with that exception and abort.
Note: If
recursive
istrue
, the removal can fail non-atomically. Some files or directories might have been removed while other files or directories still exist.Better specify what possible exceptions this could throw. [Issue #68]
-
Resolve result with
undefined
.
-
-
-
Reject result with a
NotFoundError
.
-
-
Return result .
2.5.5.
The
resolve()
method
-
path
=
await
directory
.
resolve
( child ) -
If child is equal to directory , path will be an empty array.
If child is a direct child of directory , path will be an array containing child ’s name.
If child is a descendant of directory , path will be an array containing the names of all the intermediate directories and child ’s name as last element. For example if directory represents
/home/user/project
and child represents/home/user/project/foo/bar
, this will return['foo', 'bar']
.Otherwise ( directory and child are not related), path will be null.
This functionality can be useful if a web application shows a directory listing to highlight a file opened through a file picker in that directory listing.
This method is first available in Chrome 82.
// Assume we at some point got a valid directory handle. const dir_ref= current_project_dir; if ( ! dir_ref) return ; // Now get a file reference by showing a file picker: const file_ref= await self. showOpenFilePicker(); if ( ! file_ref) { // User cancelled, or otherwise failed to open a file. return ; } // Check if file_ref exists inside dir_ref: const relative_path= await dir_ref. resolve( file_ref); if ( relative_path=== null ) { // Not inside dir_ref } else { // relative_path is an array of names, giving the relative path // from dir_ref to the file that is represented by file_ref: assert relative_path. pop() === file_ref. name; let entry= dir_ref; for ( const nameof relative_path) { entry= await entry. getDirectory( name); } entry= await entry. getFile( file_ref. name); // Now |entry| will represent the same file on disk as |file_ref|. assertawait entry. isSameEntry( file_ref) === true ; }
resolve(
possibleDescendant
)
method,
when
invoked,
must
return
the
result
of
resolving
possibleDescendant
’s
entry
relative
to
this
's
entry
.
2.6.
The
FileSystemWritableFileStream
interface
enum {
WriteCommandType ,
"write" ,
"seek" , };
"truncate" dictionary {
WriteParams required WriteCommandType ;
type unsigned long long ?;
size unsigned long long ?; (
position BufferSource or Blob or USVString )?; };
data typedef (BufferSource or Blob or USVString or WriteParams ); [
FileSystemWriteChunkType Exposed =(Window ,Worker ),SecureContext ]interface :
FileSystemWritableFileStream WritableStream {Promise <undefined >write (FileSystemWriteChunkType );
data Promise <undefined >seek (unsigned long long );
position Promise <undefined >truncate (unsigned long long ); };
size
A
FileSystemWritableFileStream
has
an
associated
[[file]]
(a
file
entry
).
A
FileSystemWritableFileStream
has
an
associated
[[buffer]]
(a
byte
sequence
).
It
is
initially
empty.
Note: This buffer can get arbitrarily large, so it is expected that implementations will not keep this in memory, but instead use a temporary file for this. All access to [[buffer]] is done in promise returning methods and algorithms, so even though operations on it seem sync, implementations can implement them async.
A
FileSystemWritableFileStream
has
an
associated
[[seekOffset]]
(a
number).
It
is
initially
0.
FileSystemWritableFileStream
object
is
a
WritableStream
object
with
additional
convenience
methods,
which
operates
on
a
single
file
on
disk.
Upon creation, an underlying sink will have been created and the stream will be usable. All operations executed on the stream are queuable and producers will be able to respond to backpressure.
The
underlying
sink’s
write
method,
and
therefore
WritableStreamDefaultWriter’s
write()
method,
will
accept
byte-like
data
or
WriteParams
as
input.
The
FileSystemWritableFileStream
has
a
file
position
cursor
initialized
at
byte
offset
0
from
the
top
of
the
file.
When
using
write()
or
by
using
WritableStream
capabilities
through
the
WritableStreamDefaultWriter’s
write()
method,
this
position
will
be
advanced
based
on
the
number
of
bytes
written
through
the
stream
object.
Similarly,
when
piping
a
ReadableStream
into
a
FileSystemWritableFileStream
object,
this
position
is
updated
with
the
number
of
bytes
that
passed
through
the
stream.
getWriter()
returns
an
instance
of
WritableStreamDefaultWriter
.
-
Let stream be a new
FileSystemWritableFileStream
in realm . -
Set stream . [[file]] to file .
-
Let writeAlgorithm be an algorithm which takes a chunk argument and returns the result of running the write a chunk algorithm with stream and chunk .
-
Let closeAlgorithm be the following steps:
-
Let closeResult be a new promise .
-
Run the following steps in parallel :
-
Let permissionStatus be the permission state for a
FileSystemPermissionDescriptor
withhandle
representing stream . [[file]] , andmode
="readwrite"
. -
If permissionStatus is not
"granted"
, reject closeResult with aNotAllowedError
and abort. -
Perform user agent-specific malware scans and safe browsing checks . If these checks fail, reject closeResult with an
AbortError
and abort. -
Set stream . [[file]] 's binary data to stream . [[buffer]] . If that throws an exception, reject closeResult with that exception and abort.
Note: It is expected that this atomically updates the contents of the file on disk being written to.
-
Resolve closeResult with
undefined
.
-
-
Return closeResult .
-
-
Let highWaterMark be 1.
-
Let sizeAlgorithm be an algorithm that returns
1
. -
Set up stream with writeAlgorithm set to writeAlgorithm , closeAlgorithm set to closeAlgorithm , highWaterMark set to highWaterMark , and sizeAlgorithm set to sizeAlgorithm .
-
Return stream .
FileSystemWritableFileStream
stream
and
chunk
,
runs
these
steps:
-
Let input be the result of converting chunk to a
FileSystemWriteChunkType
. If this throws an exception, then return a promise rejected with that exception. -
Let p be a new promise .
-
Run the following steps in parallel :
-
Let permissionStatus be the permission state for a
FileSystemPermissionDescriptor
withhandle
representing stream . [[file]] , andmode
="readwrite"
. -
If permissionStatus is not
"granted"
, reject p with aNotAllowedError
and abort. -
Let command be input .
type
if input is aWriteParams
, and"write"
otherwise. -
If command is
"write"
:-
Let data be input .
data
if input is aWriteParams
, and input otherwise. -
If data is
undefined
, reject p with aTypeError
and abort. -
Let writePosition be stream . [[seekOffset]] .
-
If input is a
WriteParams
and input .position
is notundefined
, set writePosition to input .position
. -
Let oldSize be stream . [[buffer]] 's length .
-
If data is a
BufferSource
, let dataBytes be a copy of data . -
Else if data is a
Blob
:-
Let dataBytes be the result of performing the read operation on data . If this throws an exception, reject p with that exception and abort.
-
-
Else:
-
Let dataBytes be the result of UTF-8 encoding data .
-
If writePosition is larger than oldSize , append writePosition - oldSize
0x00
(NUL) bytes to the end of stream . [[buffer]] .Note: Implementations are expected to behave as if the skipped over file contents are indeed filled with NUL bytes. That doesn’t mean these bytes have to actually be written to disk and take up disk space. Instead most file systems support so called sparse files, where these NUL bytes don’t take up actual disk space.
-
Let head be a byte sequence containing the first writePosition bytes of stream . [[buffer]] .
-
Let tail be an empty byte sequence .
-
If writePosition + data . length is smaller than oldSize :
-
Let tail be a byte sequence containing the last oldSize - ( writePosition + data . length ) bytes of stream . [[buffer]] .
-
-
Set stream . [[buffer]] to the concatenation of head , data and tail .
-
If the operations modifying stream . [[buffer]] in the previous steps failed due to exceeding the storage quota , reject p with a
QuotaExceededError
and abort, leaving stream . [[buffer]] unmodified.Note: Storage quota only applies to files stored in the origin private file system . However this operation could still fail for other files, for example if the disk being written to runs out of disk space.
-
Set stream . [[seekOffset]] to writePosition + data . length .
-
Resolve p .
-
-
Else if command is
"seek"
: -
Else if command is
"truncate"
:-
If chunk .
size
isundefined
, reject p with aTypeError
and abort. -
Let newSize be chunk .
size
. -
Let oldSize be stream . [[buffer]] 's length .
-
If newSize is larger than oldSize :
-
Set stream . [[buffer]] to a byte sequence formed by concating stream . [[buffer]] with a byte sequence containing newSize - oldSize
0x00
bytes. -
If the operation in the previous step failed due to exceeding the storage quota , reject p with a
QuotaExceededError
and abort, leaving stream . [[buffer]] unmodified.Note: Storage quota only applies to files stored in the origin private file system . However this operation could still fail for other files, for example if the disk being written to runs out of disk space.
-
-
Else if newSize is smaller than oldSize :
-
Set stream . [[buffer]] to a byte sequence containing the first newSize bytes in stream . [[buffer]] .
-
-
If stream . [[seekOffset]] is bigger than newSize , set stream . [[seekOffset]] to newSize .
-
Resolve p .
-
-
-
Return p .
2.6.1.
The
write()
method
-
await
stream
.
write
( data )- await stream .
write
({type
:"write"
,data
: data }) - await stream .
-
Writes the content of data into the file associated with stream at the current file cursor offset.
No changes are written to the actual file on disk until the stream has been closed. Changes are typically written to a temporary file instead.
-
await
stream
.
write
({type
:"write"
,position
: position ,data
: data }) -
Writes the content of data into the file associated with stream at position bytes from the top of the file. Also updates the current file cursor offset to the end of the written data.
No changes are written to the actual file on disk until the stream has been closed. Changes are typically written to a temporary file instead.
-
await
stream
.
write
({type
:"seek"
,position
: position }) -
Updates the current file cursor offset the position bytes from the top of the file.
-
await
stream
.
write
({type
:"truncate"
,size
: size }) -
Resizes the file associated with stream to be size bytes long. If size is larger than the current file size this pads the file with null bytes, otherwise it truncates the file.
The file cursor is updated when
truncate
is called. If the offset is smaller than offset, it remains unchanged. If the offset is larger than size , the offset is set to size to ensure that subsequent writes do not error.No changes are written to the actual file until on disk until the stream has been closed. Changes are typically written to a temporary file instead.
write(
data
)
method,
when
invoked,
must
run
these
steps:
-
Let writer be the result of getting a writer for this .
-
Let result be the result of writing a chunk to writer given data .
-
Release writer .
-
Return result .
2.6.2.
The
seek()
method
-
await
stream
.
seek
( position ) -
Updates the current file cursor offset the position bytes from the top of the file.
seek(
position
)
method,
when
invoked,
must
run
these
steps:
-
Let writer be the result of getting a writer for this .
-
Let result be the result of writing a chunk to writer given «[ "
type
" →"seek"
, "position
" → position ]». -
Release writer .
-
Return result .
2.6.3.
The
truncate()
method
-
await
stream
.
truncate
( size ) -
Resizes the file associated with stream to be size bytes long. If size is larger than the current file size this pads the file with null bytes, otherwise it truncates the file.
The file cursor is updated when
truncate
is called. If the offset is smaller than offset, it remains unchanged. If the offset is larger than size , the offset is set to size to ensure that subsequent writes do not error.No changes are written to the actual file until on disk until the stream has been closed. Changes are typically written to a temporary file instead.
truncate(
size
)
method,
when
invoked,
must
run
these
steps:
-
Let writer be the result of getting a writer for this .
-
Let result be the result of writing a chunk to writer given «[ "
type
" →"truncate"
, "size
" → size ]». -
Release writer .
-
Return result .
3. Accessing Local File System
enum WellKnownDirectory {"desktop" ,"documents" ,"downloads" ,"music" ,"pictures" ,"videos" , };typedef (WellKnownDirectory or FileSystemHandle );
StartInDirectory dictionary {
FilePickerAcceptType USVString ;
description record <USVString , (USVString or sequence <USVString >)>; };
accept dictionary {
FilePickerOptions sequence <FilePickerAcceptType >;
types boolean =
excludeAcceptAllOption false ;DOMString ;
id StartInDirectory ; };
startIn dictionary :
OpenFilePickerOptions FilePickerOptions {boolean =
multiple false ; };dictionary :
SaveFilePickerOptions FilePickerOptions {USVString ?; };
suggestedName dictionary {
DirectoryPickerOptions DOMString ;
id StartInDirectory ;
startIn FileSystemPermissionMode = "read"; }; [
mode SecureContext ]partial interface Window {Promise <sequence <FileSystemFileHandle >>showOpenFilePicker (optional OpenFilePickerOptions = {});
options Promise <FileSystemFileHandle >showSaveFilePicker (optional SaveFilePickerOptions = {});
options Promise <FileSystemDirectoryHandle >showDirectoryPicker (optional DirectoryPickerOptions = {}); };
options
The
showOpenFilePicker()
,
showSaveFilePicker()
and
showDirectoryPicker()
methods
are
together
known
as
the
local
file
system
handle
factories
.
Note: What is referred to as the "local file system" in this spec, does not have to strictly refer to the file system on the local device. What we call the local file system could just as well be backed by a cloud provider. For example on Chrome OS these file pickers will also let you pick files and directories on Google Drive.
In
Chrome
versions
earlier
than
85,
this
was
implemented
as
a
generic
chooseFileSystemEntries
method.
3.1. Local File System Permissions
The
fact
that
the
user
picked
the
specific
files
returned
by
the
local
file
system
handle
factories
in
a
prompt
should
be
treated
by
the
user
agent
as
the
user
intending
to
grant
read
access
to
the
website
for
the
returned
files.
As
such,
at
the
time
the
promise
returned
by
one
of
the
local
file
system
handle
factories
resolves,
permission
state
for
a
descriptor
with
handle
set
to
the
returned
handle,
and
mode
set
to
"read"
should
be
"granted"
.
Additionally
for
calls
to
showSaveFilePicker
the
permission
state
for
a
descriptor
with
handle
set
to
the
returned
handle,
and
mode
set
to
readwrite
should
be
"granted"
.
-
If environment ’s origin is an opaque origin , return a promise rejected with a
SecurityError
. -
If environment ’s origin is not same origin with environment ’s top-level origin , return a promise rejected with a
SecurityError
. -
Let global be environment ’s global object .
-
If global does not have transient activation , throw a
SecurityError
.
3.2. File picker options
3.2.1. Accepted file types
showOpenFilePicker(options)
and
showSaveFilePicker(options)
methods
accept
a
FilePickerOptions
argument,
which
lets
the
website
specify
the
types
of
files
the
file
picker
will
let
the
user
select.
Each
entry
in
types
specifies
a
single
user
selectable
option
for
filtering
the
files
displayed
in
the
file
picker.
Each
option
consists
of
an
optional
description
and
a
number
of
MIME
types
and
extensions
(specified
as
a
mapping
of
MIME
type
to
a
list
of
extensions).
If
no
description
is
provided
one
will
be
generated.
Extensions
have
to
be
strings
that
start
with
a
"."
and
only
contain
valid
suffix
code
points
.
Additionally
extensions
are
limited
to
a
length
of
16
code
points.
In addition to complete MIME types, "*" can be used as the subtype of a MIME type to match for example all image formats with "image/*".
Websites should always provide both MIME types and file extensions for each option. On platforms that only use file extensions to describe file types user agents can match on the extensions, while on platforms that don’t use extensions, user agents can match on MIME type.
By
default
the
file
picker
will
also
include
an
option
to
not
apply
any
filter,
letting
the
user
select
any
file.
Set
excludeAcceptAllOption
to
true
to
not
include
this
option
in
the
file
picker.
For example , the following options will let the user pick one of three different filters. One for text files (either plain text or HTML), one for images, and a third one that doesn’t apply any filter and lets the user select any file.
const options= { types
: [ { description
: 'Text Files' , accept
: { 'text/plain' : [ '.txt' , '.text' ], 'text/html' : [ '.html' , '.htm' ] } }, { description
: 'Images' , accept
: { 'image/*' : [ '.png' , '.gif' , '.jpeg' , '.jpg' ] } } ], };
On the other hand, the following example will only let the user select SVG files. The dialog will not show an option to not apply any filters.
const options= { types
: [ { accept
: { 'image/svg+xml' : '.svg' } }, ], excludeAcceptAllOption
: true };
FilePickerOptions
options
,
run
these
steps:
-
For each type of options .
types
:-
Let description be type .
description
. -
For each typeString → suffixes of type .
accept
:-
Let parsedType be the result of parse a MIME type with typeString .
-
If parsedType is failure, throw a
TypeError
. -
If parsedType ’s parameters are not empty, throw a
TypeError
. -
If suffixes is a string:
-
Validate a suffix given suffixes .
-
-
Otherwise, for each suffix of suffixes :
-
Validate a suffix given suffix .
-
-
-
Let filter be the following steps, given a filename (a string ), and a type (a MIME type ):
-
Let parsedType be the result of parse a MIME type with typeString .
-
Return
false
.
-
If description is an empty string, set description to some user understandable string describing filter .
-
Append description / filter to accepts options .
-
-
If either accepts options is empty , or options .
excludeAcceptAllOption
isfalse
:-
Let description be a user understandable string describing "all files".
-
Let filter be an algorithm that returns
true
. -
Append description / filter to accepts options .
-
-
-
If accepts options is empty, throw a
TypeError
. -
Return accepts options .
-
If suffix does not start with ".", throw a
TypeError
. -
If suffix contains any code points that are not valid suffix code points , throw a
TypeError
. -
If suffix ends with ".", throw a
TypeError
.
3.2.2. Starting Directory
id
and
startIn
fields
can
be
specified
to
suggest
the
directory
in
which
the
file
picker
opens.
If
neither
of
these
options
are
specified,
the
user
agent
remembers
the
last
directory
a
file
or
directory
was
picked
from,
and
new
pickers
will
start
out
in
that
directory.
By
specifying
an
id
the
user
agent
can
remember
different
directories
for
different
IDs
(user
agents
will
only
remember
directories
for
a
limited
number
of
IDs).
// If a mapping exists from this ID to a previousy picked directory, start in // this directory. Otherwise, a mapping will be created from this ID to the // directory of the resulting file picker invocation. const options= { id
: 'foo' , };
Specifying
startIn
as
a
FileSystemFileHandle
will
result
in
the
dialog
starting
in
the
parent
directory
of
that
file,
while
passing
in
a
FileSystemDirectoryHandle
will
result
in
the
dialog
to
start
in
the
passed
in
directory.
These
take
precedence
even
if
an
explicit
id
is
also
passed
in.
For
example,
given
a
FileSystemDirectoryHandle
project_dir
,
the
following
will
show
a
file
picker
that
starts
out
in
that
directory:
// The picker will open to the directory of |project_dir| regardless of whether // 'foo' has a valid mapping. const options= { id
: 'foo' , startIn
: | project_dir| , };
The
id
and
startIn
fields
control
only
the
directory
the
picker
opens
to.
In
the
above
example,
it
cannot
be
assumed
that
the
id
'foo'
will
map
to
the
same
directory
as
project_dir
once
the
file
picker
operation
has
completed.
Specifying
startIn
as
a
WellKnownDirectory
will
result
in
the
dialog
starting
in
that
directory,
unless
an
explicit
id
was
also
passed
in
which
has
a
mapping
to
a
valid
directory.
Below
is
an
example
of
specifying
both
an
id
and
startIn
as
a
WellKnownDirectory
.
If
there
is
an
existing
mapping
from
the
given
ID
to
a
path,
this
mapping
is
used.
Otherwise,
the
path
suggested
via
the
WellKnownDirectory
is
used.
// First time specifying the ID 'foo'. It is not mapped to a directory. // The file picker will fall back to opening to the Downloads directory. TODO: link this. const options= { id
: 'foo' , // Unmapped. startIn
:
"downloads" , // Start here. }; // Later... // The ID 'foo' might or might not be mapped. For example, the mapping for this ID // might have been evicted. const options= { id
: 'foo' , // Maybe mapped. If so, start here. startIn
:
"downloads" , // Otherwise, start here. };
The
startIn
and
id
options
were
first
introduced
in
Chrome
91.
A user agent holds a recently picked directory map , which is a map of origins to path id maps .
A path id map is a map of valid path ids to paths.
A valid path id is a string where each character is ASCII alphanumeric or "_" or "-".
To prevent a path id map from growing without a bound, user agents should implement some mechanism to limit how many recently picked directories will be remembered. This can for example be done by evicting least recently used entries. User agents should allow at least 16 entries to be stored in a path id map .
The
WellKnownDirectory
enum
lets
a
website
pick
one
of
several
well-known
directories.
The
exact
paths
the
various
values
of
this
enum
map
to
is
implementation-defined
(and
in
some
cases
these
might
not
even
represent
actual
paths
on
disk).
The
following
list
describes
the
meaning
of
each
of
the
values,
and
gives
possible
example
paths
on
different
operating
systems:
-
"desktop"
-
The user’s Desktop directory, if such a thing exists. For example this could be
C:\Documents and Settings\username\Desktop
,/Users/username/Desktop
, or/home/username/Desktop
. -
"documents"
-
Directory in which documents created by the user would typically be stored. For example
C:\Documents and Settings\username\My Documents
,/Users/username/Documents
, or/home/username/Documents
. -
"downloads"
-
Directory where downloaded files would typically be stored. For example
C:\Documents and Settings\username\Downloads
,/Users/username/Downloads
, or/home/username/Downloads
. -
"music"
-
Directory where audio files would typically be stored. For example
C:\Documents and Settings\username\My Documents\My Music
,/Users/username/Music
, or/home/username/Music
. -
"pictures"
-
Directory where photos and other still images would typically be stored. For example
C:\Documents and Settings\username\My Documents\My Pictures
,/Users/username/Pictures
, or/home/username/Pictures
. -
"videos"
-
Directory where videos/movies would typically be stored. For example
C:\Documents and Settings\username\My Documents\My Videos
,/Users/username/Movies
, or/home/username/Videos
. -
If id given, and is not a valid path id , throw a
TypeError
. -
Let origin be environment ’s origin .
-
If startIn is a
FileSystemHandle
:-
Let entry be startIn ’s entry .
-
If entry does not represent an entry in an origin private file system :
-
If entry is a file entry , and a path on the local file system corresponding to the parent directory if entry can be determined, then return that path.
-
If entry is a directory entry , and a path on the local file system corresponding to entry can be determined, then return that path.
-
-
-
If id is non-empty:
-
If recently picked directory map [ origin ] exists :
-
Let path map be recently picked directory map [ origin ].
-
If path map [ id ] exists , then return path map [ id ].
-
-
-
If startIn is a
WellKnownDirectory
:-
Return a user agent defined path corresponding to the
WellKnownDirectory
value of startIn .
-
-
If id is not specified, or is an empty string:
-
If recently picked directory map [ origin ] exists :
-
Let path map be recently picked directory map [ origin ].
-
If path map [""] exists , then return path map [""].
-
-
-
Return a default path in a user agent specific manner.
-
Let origin be environment ’s origin .
-
If recently picked directory map [ origin ] does not exist , then set recently picked directory map [ origin ] to an empty path id map .
-
If id is not specified, let id be an empty string.
-
Set recently picked directory map [ origin ][ id ] to the path on the local file system corresponding to entry , if such a path can be determined.
-
[
handle
]
=
await
window
.
showOpenFilePicker()
- [ handle ] = await window .
showOpenFilePicker
({multiple
: false }) - [ handle ] = await window .
-
Shows a file picker that lets a user select a single existing file, returning a handle for the selected file.
-
handles
=
await
window
.
showOpenFilePicker
({multiple
: true }) -
Shows a file picker that lets a user select multiple existing files, returning handles for the selected files.
Additional options can be passed to
showOpenFilePicker()
to indicate the types of files the website wants the user to select and the directory in which the file picker will open. See § 3.2 File picker options for details. -
Let environment be this ’s relevant settings object .
-
Let accepts options be the result of processing accept types given options .
-
Let starting directory be the result of determining the directory the picker will start in given options .
id
, options .startIn
and environment . -
Let global be environment ’s global object .
-
Verify that environment is allowed to show a file picker .
-
Let p be a new promise .
-
Run the following steps in parallel :
-
Optionally, wait until any prior execution of this algorithm has terminated.
-
Display a prompt to the user requesting that the user pick some files. If options .
multiple
is false, there must be no more than one file selected; otherwise any number may be selected.The displayed prompt should let the user pick one of the accepts options to filter the list of displayed files. Exactly how this is implemented, and what this prompt looks like is implementation-defined .
When possible, this prompt should start out showing starting directory .
-
Wait for the user to have made their selection.
-
If the user dismissed the prompt without making a selection, reject p with an
AbortError
and abort. -
Let entries be a list of file entries representing the selected files or directories.
-
Let result be a empty list .
-
For each entry of entries :
-
If entry is deemed too sensitive or dangerous to be exposed to this website by the user agent:
-
Inform the user that the selected files or directories can’t be exposed to this website.
-
At the discretion of the user agent, either go back to the beginning of these in parallel steps, or reject p with an
AbortError
and abort.
-
-
Add a new
FileSystemFileHandle
associated with entry to result .
-
-
Remember a picked directory given options .
id
, entries [0] and environment . -
Perform the activation notification steps in global ’s browsing context .
Note: This lets a website immediately perform operations on the returned handles that might require user activation, such as requesting more permissions.
-
Resolve p with result .
-
-
Return p .
-
handle
=
await
window
.
showSaveFilePicker
( options ) -
Shows a file picker that lets a user select a single file, returning a handle for the selected file. The selected file does not have to exist already. If the selected file does not exist a new empty file is created before this method returns, otherwise the existing file is cleared before this method returned.
-
handle
=
await
window
.
showSaveFilePicker
({suggestedName
: "README.md" }) -
Shows a file picker with the suggested "README.md" file name pre-filled as the default file name to save as.
Additional options can be passed to
showSaveFilePicker()
to indicate the types of files the website wants the user to select and the directory in which the file picker will open. See § 3.2 File picker options for details. -
Let environment be this ’s relevant settings object .
-
Let accepts options be the result of processing accept types given options .
-
Let starting directory be the result of determining the directory the picker will start in given options .
id
, options .startIn
and environment . -
Let global be environment ’s global object .
-
Verify that environment is allowed to show a file picker .
-
Let p be a new promise .
-
Run the following steps in parallel :
-
Optionally, wait until any prior execution of this algorithm has terminated.
-
Display a prompt to the user requesting that the user pick exactly one file. The displayed prompt should let the user pick one of the accepts options to filter the list of displayed files. Exactly how this is implemented, and what this prompt looks like is implementation-defined . If accepts options are displayed in the UI, the selected option should also be used to suggest an extension to append to a user provided file name, but this is not required. In particular user agents are free to ignore potentially dangerous suffixes such as those ending in
".lnk"
or".local"
.When possible, this prompt should start out showing starting directory .
If options .
suggestedName
is specified and not null, the file picker prompt will be pre-filled with the options .suggestedName
as the default name to save as. The interaction between thesuggestedName
and accepts options is implementation-defined . If thesuggestedName
is deemed too dangerous, user agents should ignore or sanitize the suggested file name, similar to the sanitization done when fetching something as a download . Restricted extensions for the suggested file name should match the restrictions fortypes
.Note: A user agent could for example pick whichever option in accepts options that matches
suggestedName
as the default filter. -
Wait for the user to have made their selection.
-
If the user dismissed the prompt without making a selection, reject p with an
AbortError
and abort. -
Let entry be a file entry representing the selected file.
-
If entry is deemed too sensitive or dangerous to be exposed to this website by the user agent:
-
Inform the user that the selected files or directories can’t be exposed to this website.
-
At the discretion of the user agent, either go back to the beginning of these in parallel steps, or reject p with an
AbortError
and abort.
-
-
Set entry ’s binary data to an empty byte sequence .
-
Set result to a new
FileSystemFileHandle
associated with entry . -
Remember a picked directory given options .
id
, entry and environment . -
Perform the activation notification steps in global ’s browsing context .
Note: This lets a website immediately perform operations on the returned handles that might require user activation, such as requesting more permissions.
-
Resolve p with result .
-
-
Return p .
-
handle
=
await
window
.
showDirectoryPicker()
- handle = await window .
showDirectoryPicker()
({mode
: 'read' }) - handle = await window .
-
Shows a directory picker that lets the user select a single directory, returning a handle for the selected directory if the user grants read permission.
-
handles
=
await
window
.
showDirectoryPicker()
({mode
: 'readwrite' }) -
Shows a directory picker that lets the user select a single directory, returning a handle for the selected directory. The user agent can combine read and write permission requests on this handle into one subsequent prompt.
-
Let environment be this ’s relevant settings object .
-
Let starting directory be the result of determining the directory the picker will start in given options .
id
, options .startIn
and environment . -
Let global be environment ’s global object .
-
Verify that environment is allowed to show a file picker .
-
Let p be a new promise .
-
Run the following steps in parallel :
-
Optionally, wait until any prior execution of this algorithm has terminated.
-
Display a prompt to the user requesting that the user pick a directory.
When possible, this prompt should start out showing starting directory .
-
Wait for the user to have made their selection.
-
If the user dismissed the prompt without making a selection, reject p with an
AbortError
and abort. -
Let entry be a directory entry representing the selected directory.
-
If entry is deemed too sensitive or dangerous to be exposed to this website by the user agent:
-
Inform the user that the selected files or directories can’t be exposed to this website.
-
At the discretion of the user agent, either go back to the beginning of these in parallel steps, or reject p with an
AbortError
and abort.
-
-
Set result to a new
FileSystemDirectoryHandle
associated with entry . -
Remember a picked directory given options .
id
, entry and environment . -
Let desc be a
FileSystemPermissionDescriptor
with entry ashandle
and options .mode
asmode
. -
Let status be the result of running create a PermissionStatus for desc .
-
Perform the activation notification steps in global ’s browsing context .
-
Request permission to use desc .
-
Run the boolean permission query algorithm on desc and status .
-
If status is not
"granted"
, reject result with aAbortError
and abort. -
Perform the activation notification steps in global ’s browsing context .
-
Resolve p with result .
-
-
Return p .
-
handle
=
await
item
.
getAsFileSystemHandle()
-
Returns a
FileSystemFileHandle
object if the dragged item is a file and aFileSystemDirectoryHandle
object if the dragged item is a directory. -
If the
DataTransferItem
object is not in the read/write mode or the read-only mode , return a promise resolved withnull
. -
If the the drag data item kind is not File , then return a promise resolved with
null
. -
Let p be a new promise .
-
Run the following steps in parallel :
-
Let entry be the entry representing the dragged file or directory.
-
If entry is a file entry :
-
Let handle be a
FileSystemFileHandle
associated with entry .
-
-
Else if entry is a directory entry :
-
Let handle be a
FileSystemDirectoryHandle
associated with entry .
-
-
Resolve p with entry .
-
-
Return p .
-
directoryHandle
=
await
navigator
.
storage
.
getDirectory()
-
Returns the root directory of the origin private file system.
-
Let environment be the current settings object .
-
Let map be the result of running obtain a local storage bottle map with environment and
"fileSystem"
. If this returns failure, return a promise rejected with aSecurityError
. -
If map ["root"] does not exist :
-
Let dir be a new directory entry .
-
Set dir ’s name to
""
. -
Set map ["root"] to dir .
-
-
Return a promise resolved with a new
FileSystemDirectoryHandle
, whose associated entry is map ["root"]. -
The directory or directories containing the user agent itself.
-
Directories where the user agent stores website storage .
-
Directories containing system files (such as
C:\Windows
on Windows). -
Directories such as
/dev/
,/sys
, and/proc
on Linux that would give access to low-level devices. -
A user’s entire "home" directory. Individual files and directories inside the home directory should still be allowed, but user agents should not generally let users give blanket access to the entire directory.
-
The default directory for downloads, if the user agent has such a thing. Individual files inside the directory again should be allowed, but the whole directory would risk leaking more data than a user realizes.
-
Files with names that end in
.lnk
, when selecting a file to write to. Writing to these files on Windows is similar to creating symlinks on other operating systems, and as such can be used to attempt to trick users into giving access to files they didn’t intend to expose. -
Files with names that end in
.local
, when selecting a file to write to. Windows uses these files to decide what DLLs to load, and as such writing to these files could be used to cause code to be executed.
StartInDirectory
startIn
and
an
environment
settings
object
environment
,
run
the
following
steps:
3.3.
The
showOpenFilePicker()
method
In only one current engine.
Opera ? Edge 86+
Edge (Legacy) ? IE None
Firefox for Android ? iOS Safari ? Chrome for Android None Android WebView ? Samsung Internet ? Opera Mobile ?
showOpenFilePicker(
options
)
method,
when
invoked,
must
run
these
steps:
3.4.
The
showSaveFilePicker()
method
In only one current engine.
Opera ? Edge 86+
Edge (Legacy) ? IE None
Firefox for Android ? iOS Safari ? Chrome for Android None Android WebView ? Samsung Internet ? Opera Mobile ?
The
suggestedName
option
was
first
introduced
in
Chrome
91.
showSaveFilePicker(
options
)
method,
when
invoked,
must
run
these
steps:
3.5.
The
showDirectoryPicker()
method
In only one current engine.
Opera ? Edge 86+
Edge (Legacy) ? IE None
Firefox for Android ? iOS Safari ? Chrome for Android None Android WebView ? Samsung Internet ? Opera Mobile ?
The
id
and
startIn
fields
behave
identically
to
the
id
and
startIn
fields,
respectively.
See
§ 3.2.2
Starting
Directory
for
details
on
how
to
use
these
fields.
showDirectoryPicker(
options
)
method,
when
invoked,
must
run
these
steps:
3.6. Drag and Drop
partial interface DataTransferItem {Promise <FileSystemHandle ?>getAsFileSystemHandle (); };
During a drag-and-drop operation , dragged file and directory items are associated with file entries and directory entries respectively.
DataTransferItem/getAsFileSystemHandle
In only one current engine.
Opera ? Edge 86+
Edge (Legacy) ? IE None
Firefox for Android ? iOS Safari ? Chrome for Android None Android WebView ? Samsung Internet ? Opera Mobile ?
The
getAsFileSystemHandle()
method
steps
are:
elem. addEventListener( 'dragover' , ( e) => { // Prevent navigation. e. preventDefault(); }); elem. addEventListener( 'drop' , async ( e) => { e. preventDefault(); const fileHandlesPromises= [... e. dataTransfer. items] . filter( item=> item. kind=== 'file' ) . map( item=> item. getAsFileSystemHandle()); for await ( const handleof fileHandlesPromises) { if ( handle. kind=== 'directory' ) { console. log( < code data- opaque bs- autolink- syntax= '`Directory: ${handle.name}`' > Directory: ${ handle. name} < /code>);} else { console. log( < code data- opaque bs- autolink- syntax= '`File: ${handle.name}`' > File: ${ handle. name} < /code>);} } });
This currently does not block access to too sensitive or dangerous directories, to be consistent with other APIs that give access to dropped files and directories. This is inconsistent with the local file system handle factories though, so we might want to reconsider this.
4. Accessing the Origin Private File System
The
origin
private
file
system
is
a
storage
endpoint
whose
identifier
is
"fileSystem"
,
types
are
«
"local"
»
,
and
quota
is
null.
Storage endpoints should be defined in [storage] itself, rather than being defined here. So merge this into the table there.
Note: While user agents will typically implement this by persisting the contents of this origin private file system to disk, it is not intended that the contents are easily user accessible. Similarly there is no expectation that files or directories with names matching the names of children of the origin private file system exist.
[SecureContext ]partial interface StorageManager {Promise <FileSystemDirectoryHandle >getDirectory (); };
In
Chrome
this
functionality
was
previously
exposed
as
FileSystemDirectoryHandle.getSystemDirectory({type:
"sandbox"})
.
This
new
method
is
available
as
of
Chrome
85.
getDirectory()
method,
when
invoked,
must
run
these
steps:
Note: In Chrome the directory entry returned by the above algorithm refers to the same storage as the temporary file system as used to be defined in File API: Directories and System .
5. Accessibility Considerations
This section is non-normative.
When this specification is used to present information in the user interface, implementors will want to follow the OS level accessibility guidelines for the platform.
6. Privacy Considerations
This section is non-normative.
This
API
does
not
give
websites
any
more
read
access
to
data
than
the
existing
<input
type=file>
and
<input
type=file
webkitdirectory>
APIs
already
do.
Furthermore
similarly
to
those
APIs,
all
access
to
files
and
directories
is
explicitly
gated
behind
a
file
or
directory
picker.
There are however several major privacy risks with this new API:
6.1. Users giving access to more, or more sensitive files than they intended.
This isn’t a new risk with this API, but user agents should try to make sure that users are aware of what exactly they’re giving websites access to. This is particularly important when giving access to a directory, where it might not be immediately clear to a user just how many files actually exist in that directory.
A related risk is having a user give access to particularly sensitive data. This could include some of a user agent’s configuration data, network cache or cookie store, or operating system configuration data such as password files. To protect against this, user agents are encouraged to restrict which directories a user is allowed to select in a directory picker, and potentially even restrict which files the user is allowed to select. This will make it much harder to accidentally give access to a directory that contains particularly sensitive data. Care must be taken to strike the right balance between restricting what the API can access while still having the API be useful. After all, this API intentionally lets the user use websites to interact with some of their most private personal data.
Examples of directories that user agents might want to restrict as being too sensitive or dangerous include:
6.2. Websites trying to use this API for tracking.
This API could be used by websites to track the user across clearing browsing data. This is because, in contrast with existing file access APIs, user agents are able to grant persistent access to files or directories and can re-prompt. In combination with the ability to write to files, websites will be able to persist an identifier on the users' disk. Clearing browsing data will not affect those files in any way, making these identifiers persist through those actions.
This risk is somewhat mitigated by the fact that clearing browsing data will clear all handles that a website had persisted (for example in IndexedDB), so websites won’t have any handles to re-prompt for permission after browsing data was cleared. Furthermore user agents are encouraged to make it clear what files and directories a website has access to, and to automatically expire permission grants except for particularly well trusted origins (for example persistent permissions could be limited to "installed" web applications).
User agents also are encouraged to provide a way for users to revoke permissions granted. Clearing browsing data is expected to revoke all permissions as well.
6.3. First-party vs third-party contexts.
In
third-party
contexts
(e.g.
an
iframe
whose
origin
does
not
match
that
of
the
top-level
frame)
websites
can’t
gain
access
to
data
they
don’t
already
have
access
to.
This
includes
both
getting
access
to
new
files
or
directories
via
the
local
file
system
handle
factories
,
as
well
as
requesting
more
permissions
to
existing
handles
via
the
requestPermission
API.
Handles
can
also
only
be
post-messaged
to
same-origin
destinations.
Attempts
to
send
a
handle
to
a
cross-origin
destination
will
result
in
a
messageerror
event.
7. Security Considerations
This section is non-normative.
This API gives websites the ability to modify existing files on disk, as well as write to new files. This has a couple of important security considerations:
7.1. Malware
This API could be used by websites to try to store and/or execute malware on the users system. To mitigate this risk, this API does not provide any way to mark files as executable (on the other hand files that are already executable likely remain that way, even after the files are modified through this API). Furthermore user agents are encouraged to apply things like Mark-of-the-Web to files created or modified by this API.
Finally, user agents are encouraged to verify the contents of files modified by this API via malware scans and safe browsing checks , unless some kind of external strong trust relation already exists. This of course has effects on the performance characteristics of this API.
7.2. Ransomware attacks
Another risk factor is that of ransomware attacks. The limitations described above regarding blocking access to certain sensitive directories helps limit the damage such an attack can do. Additionally user agents can grant write access to files at whatever granularity they deem appropriate.
7.3. Filling up a users disk
Other than files in the origin private file system , files written by this API are not subject to storage quota . As such websites can fill up a users disk without being limited by quota, which could leave a users device in a bad state (do note that even with storage that is subject to storage quota it is still possible to fill up, or come close to filling up, a users disk, since storage quota in general is not dependent on the amount of available disk space).
Without
this
API
websites
can
write
data
to
disk
not
subject
to
quota
limitations
already
by
triggering
downloads
of
large
files
(potentially
created
client
side,
to
not
incur
any
network
overhead).
While
the
presence
of
truncate()
and
writing
at
a
potentially
really
large
offset
past
the
end
of
a
file
makes
it
much
easier
and
lower
cost
to
create
large
files,
on
most
file
systems
such
files
should
not
actually
take
up
as
much
disk
space
as
most
commonly
used
file
systems
support
sparse
files
(and
thus
wouldn’t
actually
store
the
NUL
bytes
generated
by
resizing
a
file
or
seeking
past
the
end
of
it).
Whatever mitigations user agents use to guard against websites filling up a disk via either quota managed storage or the existing downloads mechanism should also be employed when websites use this API to write to disk.