-Title: File System Access -Shortname: file-system-access -Abstract: This document defines a web platform API that enables developers to build - powerful web apps that interact with files on the user's local device. - It builds on [[FILE-API|File API]] for file reading capabilities, and adds new API - surface to enable modifying files, as well as working with directories. -Status: CG-DRAFT -ED: https://wicg.github.io/file-system-access/ -Level: 1 -Editor: Marijn Kruisselbrink, Google, mek@chromium.org, w3cid 72440 -Group: WICG -Repository: wicg/file-system-access +Group: WHATWG +H1: File System +Shortname: fs +Text Macro: TWITTER whatfilesystem +Abstract: File System defines infrastructure for file systems as well as their API. Indent: 2 -Complain About: accidental-2119 yes, missing-example-ids yes Markup Shorthands: css no, markdown yes@@ -21,7 +13,7 @@ spec:webidl; type:dfn; text:resolve
-urlPrefix: https://tc39.github.io/ecma262/; spec: ECMA-262 +urlPrefix: https://tc39.es/ecma262/; spec: ECMA-262 type: dfn; text: realm; url: realm urlPrefix: https://storage.spec.whatwg.org/; spec: storage type: dfn; text: storage; url: site-storage @@ -47,44 +39,37 @@ urlPrefix: https://storage.spec.whatwg.org/; spec: storage } + # Introduction # {#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 `` -and `` -[[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 -{{StorageManager/getDirectory()|navigator.storage.getDirectory()}} method. This -is similar to the temporary file system as defined in earlier drafts of -[[file-system-api|File API: Directories and System]]. +This document defines fundamental infrastructure for file system APIs. In addition, it defines an +API that makes it possible for websites to get access to a file system directory without having to +first prompt the user for access. This enables use cases where a website wants to save data to disk +before a user has picked a location to save to, without forcing the website to use a completely +different storage mechanism with a different API for such files. The entry point for this is the +{{StorageManager/getDirectory()|navigator.storage.getDirectory()}} method. + # Files and Directories # {#files-and-directories} ## Concepts ## {#concepts} An entry is either a [=file entry=] or a [=directory entry=]. + + +Each [=/entry=] has an associated query access algorithm, which takes "`read`" +or "`readwrite`" mode and returns a {{PermissionState}}. Unless specified +otherwise it returns "{{PermissionState/denied}}". The algorithm is allowed to throw. + +Each [=/entry=] has an associated request access algorithm, which takes +"`read`" or "`readwrite`" mode and returns a {{PermissionState}}. Unless specified +otherwise it returns "{{PermissionState/denied}}". The algorithm is allowed to throw. + +Note: Implementations that only implement this specification and not dependent specifications do not +need to bother implementing [=/entry=]'s [=entry/query access=] and [=entry/request access=]. Each [=/entry=] has an associated name (a [=string=]). @@ -100,14 +85,6 @@ Issue: We should consider having further normative restrictions on file names th 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). @@ -121,10 +98,7 @@ An [=/entry=]'s [=entry/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 {{StorageManager/getDirectory()|navigator.storage.getDirectory()}} -or one of the [=local file system handle factories=], -and an entry will have a parent in all other cases. +parent while the other entry does not have a parent. [=/Entries=] can (but don't have to) be backed by files on the host operating system's local file system, so it is possible for the [=binary data=], [=modification timestamp=], @@ -161,97 +135,9 @@ run the following steps: -## Permissions ## {#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 mode = "read"; - }; - - -: [=permission state constraints=] -::- To determine [=permission state constraints=] for a {{FileSystemPermissionDescriptor}} |desc|, - run these steps: - - 1. Let |entry| be |desc|.{{FileSystemPermissionDescriptor/handle}}'s [=FileSystemHandle/entry=]. - 1. If |entry| represents an [=/entry=] in an [=origin private file system=], - this descriptor's [=permission state=] must always be {{PermissionState/"granted"}}. - 1. Otherwise, if |entry|'s [=entry/parent=] is not null, this descriptor's [=permission state=] must be - equal to the [=permission state=] for a descriptor with the same {{FileSystemPermissionDescriptor/mode}}, - and a {{FileSystemPermissionDescriptor/handle}} representing |entry|'s [=entry/parent=]. - 1. Otherwise, if |desc|.{{FileSystemPermissionDescriptor/mode}} is {{"readwrite"}}: - 1. Let |read state| be the [=permission state=] for a descriptor - with the same {{FileSystemPermissionDescriptor/handle}}, - but {{FileSystemPermissionDescriptor/mode}} = {{"read"}}. - 1. If |read state| is not {{PermissionState/"granted"}}, this descriptor's [=permission state=] - must be equal to |read state|. - -: [=permission request algorithm=] -::- Given a {{FileSystemPermissionDescriptor}} |desc| and a {{PermissionStatus}} |status|, - run these steps: - - 1. Run the [=boolean permission query algorithm=] on |desc| and |status|. - 1. If |status|.{{PermissionStatus/state}} is not {{PermissionState/"prompt"}}, abort. - 1. Let |settings| be |desc|.{{FileSystemPermissionDescriptor/handle}}'s [=relevant settings object=]. - 1. Let |global| be |settings|'s [=environment settings object/global object=]. - 1. If |global| is not a {{Window}}, throw a {{SecurityError}}. - 1. If |global| does not have [=transient activation=], throw a {{SecurityError}}. - 1. If |settings|'s [=environment settings object/origin=] is not [=same origin=] with |settings|'s [=top-level origin=], - throw a {{SecurityError}}. - 1. [=Request permission to use=] |desc|. - 1. Run the [=boolean permission query algorithm=] on |desc| and |status|. - - Issue(WICG/permissions-request#2): Ideally this user activation requirement would be defined upstream. - --To query file system permission -given a {{FileSystemHandle}} |handle| and a {{FileSystemPermissionMode}} |mode|, run these steps: - -1. Let |desc| be a {{FileSystemPermissionDescriptor}}. -1. Set |desc|.{{PermissionDescriptor/name}} to {{"file-system"}}. -1. Set |desc|.{{FileSystemPermissionDescriptor/handle}} to |handle|. -1. Set |desc|.{{FileSystemPermissionDescriptor/mode}} to |mode|. -1. Return |desc|'s [=permission state=]. - -- --To request file system permission -given a {{FileSystemHandle}} |handle| and a {{FileSystemPermissionMode}} |mode|, run these steps: - -1. Let |desc| be a {{FileSystemPermissionDescriptor}}. -1. Set |desc|.{{PermissionDescriptor/name}} to {{"file-system"}}. -1. Set |desc|.{{FileSystemPermissionDescriptor/handle}} to |handle|. -1. Set |desc|.{{FileSystemPermissionDescriptor/mode}} to |mode|. -1. Let |status| be the result of running create a PermissionStatus for |desc|. -1. Run the [=permission request algorithm=] for the {{"file-system"}} feature, given |desc| and |status|. -1. Return |desc|'s [=permission state=]. - -- -Issue(119): 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. - ## The {{FileSystemHandle}} interface ## {#api-filesystemhandle}-dictionary FileSystemHandlePermissionDescriptor { - FileSystemPermissionMode mode = "read"; -}; - enum FileSystemHandleKind { "file", "directory", @@ -263,9 +149,6 @@ interface FileSystemHandle { readonly attribute USVString name; Promise @@ -276,9 +159,6 @@ the {{FileSystemHandle}} interface can all be associated with the same [=/entry=isSameEntry(FileSystemHandle other); - - Promise queryPermission(optional FileSystemHandlePermissionDescriptor descriptor = {}); - Promise requestPermission(optional FileSystemHandlePermissionDescriptor descriptor = {}); }; {{FileSystemHandle}} objects are [=serializable objects=]. -Advisement: 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: 1. Set |serialized|.\[[Origin]] to |value|'s [=relevant settings object=]'s [=environment settings object/origin=]. @@ -322,8 +202,6 @@ associated [=FileSystemHandle/entry=]. :: Returns true if |handle1| and |handle2| represent the same file or directory.-Advisement: This method is first available in Chrome 82. -The isSameEntry(|other|) method, when invoked, must run these steps: @@ -337,77 +215,6 @@ The isSameEntry(|other|) method, when inv-### The {{FileSystemHandle/queryPermission()}} method ### {#api-filesystemhandle-querypermission} - -- : |status| = await |handle| . {{FileSystemHandle/queryPermission()|queryPermission}}({ {{FileSystemHandlePermissionDescriptor/mode}} : {{"read"}} }) - : |status| = await |handle| . {{FileSystemHandle/queryPermission()}} - : |status| = (await navigator.{{Navigator/permissions}}.{{Permissions/query()|query}}({ {{PermissionDescriptor/name}} : "file-system", {{FileSystemPermissionDescriptor/handle}} : |handle| })).{{PermissionStatus/state}} - :: Queries the current state of the read permission of this handle. If this returns `"prompt"` - the website will have to call {{FileSystemHandle/requestPermission()}} 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| . {{FileSystemHandle/queryPermission()|queryPermission}}({ {{FileSystemHandlePermissionDescriptor/mode}} : {{"readwrite"}} }) - : |status| = (await navigator.{{Navigator/permissions}}.{{Permissions/query()|query}}({ {{PermissionDescriptor/name}} : "file-system", {{FileSystemPermissionDescriptor/handle}} : |handle|, {{FileSystemPermissionDescriptor/mode}} : {{"readwrite"}}}).{{PermissionStatus/state}} - :: 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 call - {{FileSystemHandle/requestPermission()}}. There is no automatic prompting for read access when - attempting to read from a file or directory. -- -Advisement: The integration with the permissions API's {{Permissions/query()}} method is not yet implemented in Chrome. - --The queryPermission(|descriptor|) method, when invoked, must run these steps: - -1. Let |result| be [=a new promise=]. -1. Run the following steps [=in parallel=]: - 1. Let |state| be the result of [=querying file system permission=] - given [=this=] and |descriptor|.{{FileSystemHandlePermissionDescriptor/mode}}. - 1. [=/Resolve=] |result| with |state|. -1. Return |result|. - -- -### The {{FileSystemHandle/requestPermission()}} method ### {#api-filesystemhandle-requestpermission} - -- : |status| = await |handle| . {{FileSystemHandle/requestPermission()|requestPermission}}({ {{FileSystemHandlePermissionDescriptor/mode}} : {{"read"}} }) - : |status| = await |handle| . {{FileSystemHandle/requestPermission()}} - :: 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| . {{FileSystemHandle/requestPermission()|requestPermission}}({ {{FileSystemHandlePermissionDescriptor/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. -- --The requestPermission(|descriptor|) method, when invoked, must run these steps: - -1. Let |result| be [=a new promise=]. -1. Run the following steps [=in parallel=]: - 1. Let |state| be the result of [=requesting file system permission=] - given [=this=] and |descriptor|.{{FileSystemHandlePermissionDescriptor/mode}}. - If that throws an exception, [=reject=] |result| with that exception and abort. - 1. [=/Resolve=] |result| with |state|. -1. Return |result|. - -- ## The {{FileSystemFileHandle}} interface ## {#api-filesystemfilehandle}@@ -427,9 +234,6 @@ A {{FileSystemFileHandle}}'s associated [=FileSystemHandle/entry=] must be a [=f {{FileSystemFileHandle}} objects are [=serializable objects=]. Their [=serialization steps=] and [=deserialization steps=] are the same as those for {{FileSystemHandle}}. -Advisement: In the Origin Trial as available in Chrome 78, these objects are not yet serializable. -In Chrome 82 they are. - ### The {{FileSystemFileHandle/getFile()}} method ### {#api-filesystemfilehandle-getfile} @@ -444,17 +248,17 @@ The getFile() method, when invoked, m 1. Let |result| be [=a new promise=]. 1. Run the following steps [=in parallel=]: - 1. Let |permissionStatus| be the result of [=querying file system permission=] - given [=this=] and {{"read"}}. - 1. If |permissionStatus| is not {{PermissionState/"granted"}}, + 1. Let |access| be the result of running [=this=]'s [=FileSystemHandle/entry=]'s + [=entry/query access=] given "`read`". + 1. If |access| is not "{{PermissionState/granted}}", reject |result| with a {{NotAllowedError}} and abort. - 1. Let |entry| be [=this=]'s [=FileSystemHandle/entry=]. + 1. Let |entry| be [=this=]'s [=FileSystemHandle/entry=]. 1. Let |f| be a new {{File}}. 1. Set |f|'s snapshot state to the current state of |entry|. 1. Set |f|'s underlying byte sequence to a copy of |entry|'s [=binary data=]. 1. Initialize the value of |f|'s {{File/name}} attribute to |entry|'s [=entry/name=]. 1. Initialize the value of |f|'s {{File/lastModified}} attribute to |entry|'s [=file entry/modification timestamp=]. - 1. Initialize the value of |f|'s {{Blob/type}} attribute to an implementation defined value, based on for example |entry|'s [=entry/name=] and/or its file extension. + 1. Initialize the value of |f|'s {{Blob/type}} attribute to an [=implementation-defined=] value, based on for example |entry|'s [=entry/name=] or its file extension. Issue: The reading and snapshotting behavior needs to be better specified in the [[FILE-API]] spec, for now this is kind of hand-wavy. @@ -465,8 +269,6 @@ The getFile() method, when invoked, m ### The {{FileSystemFileHandle/createWritable()}} method ### {#api-filesystemfilehandle-createwritable} -Advisement: In the Origin Trial as available in Chrome 82, createWritable replaces the createWriter method. -: |stream| = await |fileHandle| . {{FileSystemFileHandle/createWritable()}} : |stream| = await |fileHandle| . {{FileSystemFileHandle/createWritable()|createWritable}}({ {{FileSystemCreateWritableOptions/keepExistingData}}: true/false }) @@ -496,14 +298,14 @@ The createWritable(|options|) method, 1. Let |result| be [=a new promise=]. 1. Run the following steps [=in parallel=]: - 1. Let |permissionStatus| be the result of [=requesting file system permission=] - given [=this=] and {{"readwrite"}}. + 1. Let |access| be the result of running [=this=]'s [=FileSystemHandle/entry=]'s + [=entry/request access=] given "`readwrite`". If that throws an exception, [=reject=] |result| with that exception and abort. - 1. If |permissionStatus| is not {{PermissionState/"granted"}}, + 1. If |access| is not "{{PermissionState/granted}}", reject |result| with a {{NotAllowedError}} and abort. - 1. Let |entry| be [=this=]'s [=FileSystemHandle/entry=]. + 1. Let |entry| be [=this=]'s [=FileSystemHandle/entry=]. 1. Let |stream| be the result of [=create a new FileSystemWritableFileStream|creating a new FileSystemWritableFileStream=] - for |entry| in [=this=]'s [=relevant realm=]. + for |entry| in [=this=]'s [=relevant realm=]. 1. If |options|.{{FileSystemCreateWritableOptions/keepExistingData}} is `true`: 1. Set |stream|.[=[[buffer]]=] to a copy of |entry|'s [=file entry/binary data=]. 1. [=/Resolve=] |result| with |stream|. @@ -544,12 +346,6 @@ A {{FileSystemDirectoryHandle}}'s associated [=FileSystemHandle/entry=] must be {{FileSystemDirectoryHandle}} objects are [=serializable objects=]. Their [=serialization steps=] and [=deserialization steps=] are the same as those for {{FileSystemHandle}}. -Advisement: In the Origin Trial as available in Chrome 78, these objects are not yet serializable. -In Chrome 82 they are. - -Advisement: In Chrome versions upto Chrome 85 `getFileHandle` and `getDirectoryHandle` where -called `getFile` and `getDirectory` instead. - ### Directory iteration ### {#api-filesystemdirectoryhandle-asynciterable}-Advisement: This method is first available in Chrome 82. -@@ -562,10 +358,6 @@ called `getFile` and `getDirectory` instead. No guarantees are given either way.-Advisement: 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. - Issue(173): In the future we might want to add arguments to the async iterable declaration to support for example recursive iteration. @@ -573,10 +365,10 @@ support for example recursive iteration. The [=asynchronous iterator initialization steps=] for a {{FileSystemDirectoryHandle}} |handle| ant its async iterator |iterator| are: -1. Let |permissionStatus| be the result of [=querying file system permission=] - given |handle| and {{"read"}}. +1. Let |access| be the result of running |handle|'s [=FileSystemHandle/entry=]'s + [=entry/query access=] given "`read`". -1. If |permissionStatus| is not {{PermissionState/"granted"}}, +1. If |access| is not "{{PermissionState/granted}}", throw a {{NotAllowedError}}. 1. Set |iterator|'s past results to an empty [=/set=]. @@ -591,10 +383,10 @@ and its async iterator |iterator|: 1. Let |directory| be |handle|'s [=FileSystemHandle/entry=]. -1. Let |permissionStatus| be the result of [=querying file system permission=] - given |handle| and {{"read"}}. +1. Let |access| be the result of running |handle|'s [=FileSystemHandle/entry=]'s + [=entry/query access=] given "`read`". -1. If |permissionStatus| is not {{PermissionState/"granted"}}, +1. If |access| is not "{{PermissionState/granted}}", reject |promise| with a {{NotAllowedError}} and return |promise|. 1. Let |child| be an [=/entry=] in |directory|'s [=directory entry/children=], @@ -650,15 +442,15 @@ must run these steps: 1. Run the following steps [=in parallel=]: 1. If |name| is not a [=valid file name=], [=/reject=] |result| with a {{TypeError}} and abort. - 1. Let |entry| be [=this=]'s [=FileSystemHandle/entry=]. + 1. Let |entry| be [=this=]'s [=FileSystemHandle/entry=]. 1. If |options|.{{FileSystemGetFileOptions/create}} is `true`: - 1. Let |permissionStatus| be the result of [=requesting file system permission=] - given [=this=] and {{"readwrite"}}. + 1. Let |access| be the result of running [=this=]'s [=FileSystemHandle/entry=]'s + [=entry/request access=] given "`readwrite`". If that throws an exception, [=reject=] |result| with that exception and abort. 1. Otherwise: - 1. Let |permissionStatus| be the result of [=querying file system permission=] - given [=this=] and {{"read"}}. - 1. If |permissionStatus| is not {{PermissionState/"granted"}}, + 1. Let |access| be the result of running [=this=]'s [=FileSystemHandle/entry=]'s + [=entry/query access=] given "`read`". + 1. If |access| is not "{{PermissionState/granted}}", reject |result| with a {{NotAllowedError}} and abort. 1. [=set/For each=] |child| of |entry|'s [=directory entry/children=]: @@ -668,7 +460,8 @@ must run these steps: 1. [=/Resolve=] |result| with a new {{FileSystemFileHandle}} whose [=FileSystemHandle/entry=] is |child| and abort. 1. If |options|.{{FileSystemGetFileOptions/create}} is `false`: 1. [=/Reject=] |result| with a {{NotFoundError}} and abort. - 1. Let |child| be a new [=file entry=]. + 1. Let |child| be a new [=file entry=] whose [=query access=] and [=request access=] algorithms + are those of |entry|. 1. Set |child|'s [=entry/name=] to |name|. 1. Set |child|'s [=binary data=] to an empty [=byte sequence=]. 1. Set |child|'s [=modification timestamp=] to the current time. @@ -711,15 +504,15 @@ invoked, must run these steps: 1. Run the following steps [=in parallel=]: 1. If |name| is not a [=valid file name=], [=/reject=] |result| with a {{TypeError}} and abort. - 1. Let |entry| be [=this=]'s [=FileSystemHandle/entry=]. + 1. Let |entry| be [=this=]'s [=FileSystemHandle/entry=]. 1. If |options|.{{FileSystemGetDirectoryOptions/create}} is `true`: - 1. Let |permissionStatus| be the result of [=requesting file system permission=] - given [=this=] and {{"readwrite"}}. + 1. Let |access| be the result of running [=this=]'s [=FileSystemHandle/entry=]'s + [=entry/request access=] given "`readwrite`". If that throws an exception, [=reject=] |result| with that exception and abort. 1. Otherwise: - 1. Let |permissionStatus| be the result of [=querying file system permission=] - given [=this=] and {{"read"}}. - 1. If |permissionStatus| is not {{PermissionState/"granted"}}, + 1. Let |access| be the result of running [=this=]'s [=FileSystemHandle/entry=]'s + [=entry/query access=] given "`read`". + 1. If |access| is not "{{PermissionState/granted}}", reject |result| with a {{NotAllowedError}} and abort. 1. [=set/For each=] |child| of |entry|'s [=directory entry/children=]: @@ -729,7 +522,8 @@ invoked, must run these steps: 1. [=/Resolve=] |result| with a new {{FileSystemDirectoryHandle}} whose [=FileSystemHandle/entry=] is |child| and abort. 1. If |options|.{{FileSystemGetFileOptions/create}} is `false`: 1. [=/Reject=] |result| with a {{NotFoundError}} and abort. - 1. Let |child| be a new [=directory entry=]. + 1. Let |child| be a new [=directory entry=] whose [=query access=] and [=request access=] + algorithms are those of |entry|. 1. Set |child|'s [=entry/name=] to |name|. 1. Set |child|'s [=directory entry/children=] to an empty [=/set=]. 1. [=set/Append=] |child| to |entry|'s [=directory entry/children=]. @@ -769,11 +563,11 @@ these steps: 1. Run the following steps [=in parallel=]: 1. If |name| is not a [=valid file name=], [=/reject=] |result| with a {{TypeError}} and abort. - 1. Let |entry| be [=this=]'s [=FileSystemHandle/entry=]. - 1. Let |permissionStatus| be the result of [=requesting file system permission=] - given [=this=] and {{"readwrite"}}. + 1. Let |entry| be [=this=]'s [=FileSystemHandle/entry=]. + 1. Let |access| be the result of running [=this=]'s [=FileSystemHandle/entry=]'s + [=entry/request access=] given "`readwrite`". If that throws an exception, [=reject=] |result| with that exception and abort. - 1. If |permissionStatus| is not {{PermissionState/"granted"}}, + 1. If |access| is not "{{PermissionState/granted}}", reject |result| with a {{NotAllowedError}} and abort. 1. [=set/For each=] |child| of |entry|'s [=directory entry/children=]: @@ -814,8 +608,6 @@ these steps: listing to highlight a file opened through a file picker in that directory listing.-# Accessing Local File System # {#local-filesystem} - -// Assume we at some point got a valid directory handle. @@ -926,12 +718,10 @@ in a [=/Realm=] |realm|, perform the following steps: 1. Let |closeAlgorithm| be the following steps: 1. Let |closeResult| be [=a new promise=]. 1. Run the following steps [=in parallel=]: - 1. Let |permissionStatus| be the [=permission state=] for a {{FileSystemPermissionDescriptor}} - with {{FileSystemPermissionDescriptor/handle}} representing |stream|.[=[[file]]=], - and {{FileSystemPermissionDescriptor/mode}} = {{"readwrite"}}. - 1. If |permissionStatus| is not {{PermissionState/"granted"}}, + 1. Let |access| be the result of running |file|'s [=entry/query access=] given "`readwrite`". + 1. If |access| is not "{{PermissionState/granted}}", reject |closeResult| with a {{NotAllowedError}} and abort. - 1. Perform user agent-specific [=malware scans and safe browsing checks=]. + 1. Perform [=implementation-defined=] malware scans and safe browsing checks. If these checks fail, [=/reject=] |closeResult| with an {{AbortError}} and abort. 1. Set |stream|.[=[[file]]=]'s [=file entry/binary data=] to |stream|.[=[[buffer]]=]. If that throws an exception, [=/reject=] |closeResult| with that exception and abort. @@ -960,10 +750,9 @@ runs these steps: If this throws an exception, then return [=a promise rejected with=] that exception. 1. Let |p| be [=a new promise=]. 1. Run the following steps [=in parallel=]: - 1. Let |permissionStatus| be the [=permission state=] for a {{FileSystemPermissionDescriptor}} - with {{FileSystemPermissionDescriptor/handle}} representing |stream|.[=[[file]]=], - and {{FileSystemPermissionDescriptor/mode}} = {{"readwrite"}}. - 1. If |permissionStatus| is not {{PermissionState/"granted"}}, + 1. Let |access| be the result of running |stream|'s [=FileSystemWritableFileStream/[[file]]=]'s + [=entry/query access=] given "`readwrite`". + 1. If |access| is not "{{PermissionState/granted}}", reject |p| with a {{NotAllowedError}} and abort. 1. Let |command| be |input|.{{WriteParams/type}} if |input| is a {{WriteParams}}, and {{WriteCommandType/"write"}} otherwise. @@ -1141,684 +930,6 @@ steps: -enum WellKnownDirectory { - "desktop", - "documents", - "downloads", - "music", - "pictures", - "videos", -}; - -typedef (WellKnownDirectory or FileSystemHandle) StartInDirectory; - -dictionary FilePickerAcceptType { - USVString description; - record - -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. - -Advisement: In Chrome versions earlier than 85, this was implemented as a generic `chooseFileSystemEntries` method. - -## Local File System Permissions ## {#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 {{FileSystemPermissionDescriptor/handle}} set to the returned handle, -and {{FileSystemPermissionDescriptor/mode}} set to {{"read"}} -should be {{PermissionState/"granted"}}. - -Additionally for calls to {{showSaveFilePicker}} -the [=permission state=] for a descriptor with {{FileSystemPermissionDescriptor/handle}} set to the returned handle, -and {{FileSystemPermissionDescriptor/mode}} set to {{readwrite}} -should be {{PermissionState/"granted"}}. - -)> accept; -}; - -dictionary FilePickerOptions { - sequence 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; -}; - -[SecureContext] -partial interface Window { - Promise > showOpenFilePicker(optional OpenFilePickerOptions options = {}); - Promise showSaveFilePicker(optional SaveFilePickerOptions options = {}); - Promise showDirectoryPicker(optional DirectoryPickerOptions options = {}); -}; - -To verify that an |environment| is allowed to show a file picker, run these steps: - -1. If |environment|'s [=environment settings object/origin=] is an [=opaque origin=], - return [=a promise rejected with=] a {{SecurityError}}. - -1. If |environment|'s [=environment settings object/origin=] is not [=same origin=] with - |environment|'s [=top-level origin=], - return [=a promise rejected with=] a {{SecurityError}}. - -1. Let |global| be |environment|'s [=environment settings object/global object=]. - -1. If |global| does not have [=transient activation=], throw a {{SecurityError}}. - -- -## File picker options ## {#api-filepickeroptions} - -### Accepted file types ### {#api-filepickeroptions-types} - --The {{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 {{FilePickerOptions/types}} specifies a single user selectable option -for filtering the files displayed in the file picker. - -Each option consists of an optional {{FilePickerAcceptType/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 = { -- -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. - -{{FilePickerOptions/types}} : [ - { -{{FilePickerAcceptType/description}} : 'Text Files', -{{FilePickerAcceptType/accept}} : { - 'text/plain': ['.txt', '.text'], - 'text/html': ['.html', '.htm'] - } - }, - { -{{FilePickerAcceptType/description}} : 'Images', -{{FilePickerAcceptType/accept}} : { - 'image/*': ['.png', '.gif', '.jpeg', '.jpg'] - } - } - ], -}; --const options = { -- -{{FilePickerOptions/types}} : [ - { -{{FilePickerAcceptType/accept}} : { - 'image/svg+xml': '.svg' - } - }, - ], -{{FilePickerOptions/excludeAcceptAllOption}} : true -}; --To process accept types, given {{FilePickerOptions}} |options|, -run these steps: - -1. Let |accepts options| be a empty [=/list=] of [=pairs=]. -1. [=list/For each=] |type| of |options|.{{FilePickerOptions/types}}: - 1. Let |description| be |type|.{{FilePickerAcceptType/description}}. - 1. [=map/For each=] |typeString| → |suffixes| of |type|.{{FilePickerAcceptType/accept}}: - 1. Let |parsedType| be the result of [=parse a MIME type=] with |typeString|. - 1. If |parsedType| is failure, throw a {{TypeError}}. - 1. If |parsedType|'s [=MIME type/parameters=] are not empty, throw a {{TypeError}}. - 1. If |suffixes| is a string: - 1. [=Validate a suffix=] given |suffixes|. - 1. Otherwise, [=list/for each=] |suffix| of |suffixes|: - 1. [=Validate a suffix=] given |suffix|. - - 1. Let |filter| be the following steps, given a |filename| (a [=string=]), and a |type| (a [=MIME type=]): - 1. [=map/For each=] |typeString| → |suffixes| of |type|.{{FilePickerAcceptType/accept}}: - 1. Let |parsedType| be the result of [=parse a MIME type=] with |typeString|. - 1. If |parsedType|'s [=MIME type/subtype=] is "*": - 1. If |parsedType|'s [=MIME type/type=] is "*", return `true`. - 1. If |parsedType|'s [=MIME type/type=] is |type|'s [=MIME type/type=], return `true`. - 1. |parsedType|'s [=MIME type/essence=] is |type|'s [=MIME type/essence=], return `true`. - 1. If |suffixes| is a string, set |suffixes| to « |suffixes| ». - 1. [=list/For each=] |suffix| of |suffixes|: - 1. If |filename| ends with |suffix|, return `true`. - 1. Return `false`. - - 1. If |description| is an empty string, - set |description| to some user understandable string describing |filter|. - - 1. [=list/Append=] |description|/|filter| to |accepts options|. - -1. If either |accepts options| is [=list/empty=], - or |options|.{{FilePickerOptions/excludeAcceptAllOption}} is `false`: - 1. Let |description| be a user understandable string describing "all files". - 1. Let |filter| be an algorithm that returns `true`. - 1. [=list/Append=] |description|/|filter| to |accepts options|. - -1. If |accepts options| is empty, throw a {{TypeError}}. - -1. Return |accepts options|. - -- --To validate a suffix |suffix|, run the following steps: - -1. If |suffix| does not [=string/starts with|start with=] ".", throw a {{TypeError}}. -1. If |suffix| contains any [=code points=] that are not [=valid suffix code points=], - throw a {{TypeError}}. -1. If |suffix| ends with ".", throw a {{TypeError}}. -1. If |suffix|'s [=string/length=] is more than 16, throw a {{TypeError}}. - -- -### Starting Directory ### {#api-filepickeroptions-starting-directory} - --The {{FilePickerOptions/id}} and {{FilePickerOptions/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 -{{FilePickerOptions/id}} the user agent can remember different directories for different IDs -(user agents will only remember directories for a limited number of IDs). - -- -Advisement: The {{FilePickerOptions/startIn}} and {{FilePickerOptions/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: - --// 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 = { -- -Specifying {{FilePickerOptions/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 {{FilePickerOptions/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: - -{{FilePickerOptions/id}} : 'foo', -}; --// The picker will open to the directory of |project_dir| regardless of whether -// 'foo' has a valid mapping. -const options = { -- -The {{FilePickerOptions/id}} and {{FilePickerOptions/startIn}} fields control only -the directory the picker opens to. In the above example, it cannot be assumed that -the {{FilePickerOptions/id}} 'foo' will map to the same directory as |project_dir| -once the file picker operation has completed. - -Specifying {{FilePickerOptions/startIn}} as a {{WellKnownDirectory}} will result in the dialog -starting in that directory, unless an explicit {{FilePickerOptions/id}} was also passed -in which has a mapping to a valid directory. - -Below is an example of specifying both an {{FilePickerOptions/id}} and -{{FilePickerOptions/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. - -{{FilePickerOptions/id}} : 'foo', -{{FilePickerOptions/startIn}} : |project_dir|, -}; --// 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 = { -- -{{FilePickerOptions/id}} : 'foo', // Unmapped. -{{FilePickerOptions/startIn}} :{{WellKnownDirectory/"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 = { -{{FilePickerOptions/id}} : 'foo', // Maybe mapped. If so, start here. -{{FilePickerOptions/startIn}} :{{WellKnownDirectory/"downloads"}} , // Otherwise, start here. -}; --: "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`. - -
-To determine the directory the picker will start in, given an optional [=string=] |id|, -an optional {{StartInDirectory}} |startIn| and an [=environment settings object=] |environment|, -run the following steps: - -1. If |id| given, and is not a [=valid path id=], throw a {{TypeError}}. -1. If |id|'s [=string/length=] is more than 32, throw a {{TypeError}}. - -1. Let |origin| be |environment|'s [=environment settings object/origin=]. - -1. If |startIn| is a {{FileSystemHandle}}: - 1. Let |entry| be |startIn|'s [=FileSystemHandle/entry=]. - 1. If |entry| does not represent an [=/entry=] in an [=origin private file system=]: - 1. 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. - 1. If |entry| is a [=directory entry=], and a path on the local file system - corresponding to |entry| can be determined, - then return that path. - -1. If |id| is non-empty: - 1. If [=recently picked directory map=][|origin|] [=map/exists=]: - 1. Let |path map| be [=recently picked directory map=][|origin|]. - 1. If |path map|[|id|] [=map/exists=], then return |path map|[|id|]. - -1. If |startIn| is a {{WellKnownDirectory}}: - 1. Return a user agent defined path corresponding to the {{WellKnownDirectory}} value of |startIn|. - -1. If |id| is not specified, or is an empty string: - 1. If [=recently picked directory map=][|origin|] [=map/exists=]: - 1. Let |path map| be [=recently picked directory map=][|origin|]. - 1. If |path map|[""] [=map/exists=], then return |path map|[""]. - -1. Return a default path in a user agent specific manner. - -- --To remember a picked directory, given an optional [=string=] |id|, -an [=/entry=] |entry|, and an [=environment settings object=] |environment|, -run the following steps: - -1. Let |origin| be |environment|'s [=environment settings object/origin=]. -1. If [=recently picked directory map=][|origin|] does not [=map/exist=], - then set [=recently picked directory map=][|origin|] to an empty [=path id map=]. -1. If |id| is not specified, let |id| be an empty string. -1. 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. - -- -## The {{showOpenFilePicker()}} method ## {#api-showopenfilepicker} - -- : [ |handle| ] = await window . {{showOpenFilePicker()}} - : [ |handle| ] = await window . {{showOpenFilePicker()|showOpenFilePicker}}({ {{OpenFilePickerOptions/multiple}}: false }) - :: Shows a file picker that lets a user select a single existing file, returning a handle for - the selected file. - - : handles = await window . {{showOpenFilePicker()|showOpenFilePicker}}({ {{OpenFilePickerOptions/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 [[#api-filepickeroptions]] for details. -- --The showOpenFilePicker(|options|) method, when invoked, must run -these steps: - -1. Let |environment| be [=this=]'s [=relevant settings object=]. - -1. Let |accepts options| be the result of [=process accept types|processing accept types=] given |options|. -1. Let |starting directory| be the result of [=determine the directory the picker will start in|determining the directory the picker will start in=] - given |options|.{{FilePickerOptions/id}}, |options|.{{FilePickerOptions/startIn}} and |environment|. - -1. Let |global| be |environment|'s [=environment settings object/global object=]. -1. Verify that |environment| [=is allowed to show a file picker=]. - -1. Let |p| be [=a new promise=]. -1. Run the following steps [=in parallel=]: - - 1. Optionally, wait until any prior execution of this algorithm has terminated. - - 1. Display a prompt to the user requesting that the user pick some files. - If |options|.{{OpenFilePickerOptions/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|. - - 1. Wait for the user to have made their selection. - - 1. If the user dismissed the prompt without making a selection, - [=/reject=] |p| with an {{AbortError}} and abort. - - 1. Let |entries| be a [=/list=] of [=file entries=] representing the selected files or directories. - 1. Let |result| be a empty [=/list=]. - - 1. [=list/For each=] |entry| of |entries|: - 1. If |entry| is deemed [=too sensitive or dangerous=] to be exposed to this website by the user agent: - 1. Inform the user that the selected files or directories can't be exposed to this website. - 1. 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. - - 1. Add a new {{FileSystemFileHandle}} associated with |entry| to |result|. - - 1. [=Remember a picked directory=] given |options|.{{FilePickerOptions/id}}, |entries|[0] and |environment|. - - 1. Perform the activation notification steps in |global|'s [=Window/browsing context=]. - - Note: This lets a website immediately perform operations on the returned handles that - might require user activation, such as requesting more permissions. - - 1. [=/Resolve=] |p| with |result|. - -1. Return |p|. - -- -## The {{Window/showSaveFilePicker()}} method ## {#api-showsavefilepicker} - -- : |handle| = await window . {{showSaveFilePicker()|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()|showSaveFilePicker}}({ {{SaveFilePickerOptions/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 [[#api-filepickeroptions]] for details. -- -Advisement: The {{SaveFilePickerOptions/suggestedName}} option was first introduced in Chrome 91. - --The showSaveFilePicker(|options|) method, when invoked, must run -these steps: - -1. Let |environment| be [=this=]'s [=relevant settings object=]. - -1. Let |accepts options| be the result of [=process accept types|processing accept types=] given |options|. -1. Let |starting directory| be the result of [=determine the directory the picker will start in|determining the directory the picker will start in=] - given |options|.{{FilePickerOptions/id}}, |options|.{{FilePickerOptions/startIn}} and |environment|. - -1. Let |global| be |environment|'s [=environment settings object/global object=]. -1. Verify that |environment| [=is allowed to show a file picker=]. - -1. Let |p| be [=a new promise=]. -1. Run the following steps [=in parallel=]: - - 1. Optionally, wait until any prior execution of this algorithm has terminated. - - 1. 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|.{{SaveFilePickerOptions/suggestedName}} is specified and not null, - the file picker prompt will be pre-filled with the - |options|.{{SaveFilePickerOptions/suggestedName}} as the default name to save as. The interaction between the - {{SaveFilePickerOptions/suggestedName}} and |accepts options| is [=implementation-defined=]. - If the {{SaveFilePickerOptions/suggestedName}} 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. - - Note: A user agent could for example pick whichever option in |accepts options| that matches - {{SaveFilePickerOptions/suggestedName}} as the default filter. - - 1. Wait for the user to have made their selection. - - 1. If the user dismissed the prompt without making a selection, - [=/reject=] |p| with an {{AbortError}} and abort. - - 1. Let |entry| be a [=file entry=] representing the selected file. - - 1. If |entry| is deemed [=too sensitive or dangerous=] to be exposed to this website by the user agent: - 1. Inform the user that the selected files or directories can't be exposed to this website. - 1. 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. - - 1. Set |entry|'s [=binary data=] to an empty [=byte sequence=]. - - 1. Set |result| to a new {{FileSystemFileHandle}} associated with |entry|. - - 1. [=Remember a picked directory=] given |options|.{{FilePickerOptions/id}}, |entry| and |environment|. - - 1. Perform the activation notification steps in |global|'s [=Window/browsing context=]. - - Note: This lets a website immediately perform operations on the returned handles that - might require user activation, such as requesting more permissions. - - 1. [=/Resolve=] |p| with |result|. - -1. Return |p|. - -- -## The {{Window/showDirectoryPicker()}} method ## {#api-showdirectorypicker} - -- : |handle| = await window . {{Window/showDirectoryPicker()}} - :: Shows a directory picker that lets the user select a single directory, returning a handle for - the selected directory. - - The {{DirectoryPickerOptions/id}} and {{DirectoryPickerOptions/startIn}} fields behave - identically to the {{FilePickerOptions/id}} and {{FilePickerOptions/startIn}} fields, respectively. - See [[#api-filepickeroptions-starting-directory]] for details on how to use these fields. -- --The showDirectoryPicker(options) method, when invoked, must run -these steps: - -1. Let |environment| be [=this=]'s [=relevant settings object=]. - -1. Let |starting directory| be the result of [=determine the directory the picker will start in|determining the directory the picker will start in=] - given |options|.{{DirectoryPickerOptions/id}}, |options|.{{DirectoryPickerOptions/startIn}} and |environment|. - -1. Let |global| be |environment|'s [=environment settings object/global object=]. -1. Verify that |environment| [=is allowed to show a file picker=]. - -1. Let |p| be [=a new promise=]. -1. Run the following steps [=in parallel=]: - - 1. Optionally, wait until any prior execution of this algorithm has terminated. - - 1. Display a prompt to the user requesting that the user pick a directory. - - When possible, this prompt should start out showing |starting directory|. - - 1. Wait for the user to have made their selection. - - 1. If the user dismissed the prompt without making a selection, - [=/reject=] |p| with an {{AbortError}} and abort. - - 1. Let |entry| be a [=directory entry=] representing the selected directory. - - 1. If |entry| is deemed [=too sensitive or dangerous=] to be exposed to this website by the user agent: - 1. Inform the user that the selected files or directories can't be exposed to this website. - 1. 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. - - 1. Set |result| to a new {{FileSystemDirectoryHandle}} associated with |entry|. - - 1. [=Remember a picked directory=] given |options|.{{DirectoryPickerOptions/id}}, |entry| and |environment|. - - 1. Perform the activation notification steps in |global|'s [=Window/browsing context=]. - - Note: This lets a website immediately perform operations on the returned handles that - might require user activation, such as requesting more permissions. - - Issue(89): Rather than requiring the website to prompt separately for a writable directory, - we should provide some kind of API to request a writable directory in one step. - - 1. [=/Resolve=] |p| with |result|. - -1. Return |p|. - -- -## Drag and Drop ## {#drag-and-drop} - --partial interface DataTransferItem { - Promise - -During a drag-and-drop operation, dragged file and -directory items are associated with [=file entries=] and [=directory entries=] -respectively. - -getAsFileSystemHandle(); -}; - - : |handle| = await item . {{getAsFileSystemHandle()}} - :: Returns a {{FileSystemFileHandle}} object if the dragged item is a file and a {{FileSystemDirectoryHandle}} object if the dragged item is a directory. -- -- -The getAsFileSystemHandle() method steps are: - -1. If the {{DataTransferItem}} object is not in the read/write - mode or the read-only mode, return - [=a promise resolved with=] `null`. - -1. If the the drag data item kind is not File, - then return [=a promise resolved with=] `null`. - -1. Let |p| be [=a new promise=]. - -1. Run the following steps [=in parallel=]: - - 1. Let |entry| be the [=/entry=] representing the dragged file or directory. - - 1. If |entry| is a [=file entry=]: - - 1. Let |handle| be a {{FileSystemFileHandle}} associated with |entry|. - - 1. Else if |entry| is a [=directory entry=]: - - 1. Let |handle| be a {{FileSystemDirectoryHandle}} associated with |entry|. - - 1. [=/Resolve=] |p| with |entry|. - -1. Return |p|. - -- --Handling drag and drop of files and directories: -- -Issue: 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. # Accessing the Origin Private File System # {#sandboxed-filesystem} @@ -1842,9 +953,6 @@ partial interface StorageManager { }; -Advisement: In Chrome this functionality was previously exposed as `FileSystemDirectoryHandle.getSystemDirectory({type: "sandbox"})`. -This new method is available as of Chrome 85. --elem.addEventListener('dragover', (e) => { - // Prevent navigation. - e.preventDefault(); -}); -elem.addEventListener('drop', async (e) => { - // Prevent navigation. - e.preventDefault(); - - // Process all of the items. - for (const item of e.dataTransfer.items) { - // kind will be 'file' for file/directory entries. - if (item.kind === 'file') { - const entry = await item.getAsFileSystemHandle(); - if (entry.kind === 'file') { - handleFileEntry(entry); - } else if (entry.kind === 'directory') { - handleDirectoryEntry(entry); - } - } - } -}); - -: |directoryHandle| = await navigator . storage . {{StorageManager/getDirectory()}} :: Returns the root directory of the origin private file system. @@ -1861,8 +969,9 @@ invoked, must run these steps: return [=a promise rejected with=] a {{SecurityError}}. 1. If |map|["root"] does not [=map/exist=]: - 1. Let |dir| be a new [=directory entry=]. - 1. Set |dir|'s [=entry/name=] to `""`. + 1. Let |dir| be a new [=directory entry=] whose [=query access=] and [=request access=] algorithms + always return "{{PermissionState/granted}}". + 1. Set |dir|'s [=entry/name=] to the empty string. 1. Set |dir|'s [=directory entry/children=] to an empty [=/set=]. 1. Set |map|["root"] to |dir|. @@ -1871,136 +980,14 @@ 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-system-api|File API: Directories and System]]. - -# Accessibility Considerations # {#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. - - -# Privacy Considerations # {#privacy-considerations} - -*This section is non-normative.* - -This API does not give websites any more read access to data than the existing `` -and `` 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: - -## Users giving access to more, or more sensitive files than they intended. ## {#privacy-wide-access} - -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: - -* The directory or directories containing the user agent itself. -* Directories where the user agent stores [=storage|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. - -## Websites trying to use this API for tracking. ## {#privacy-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. - -## First-party vs third-party contexts. ## {#privacy-third-party} - -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 {{MessagePort/messageerror}} event. - -# Security Considerations # {#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: - -## Malware ## {#security-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. - -## Ransomware attacks ## {#security-ransomware} - -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. -## Filling up a users disk ## {#filling-up-disk} +Acknowledgments
-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). +This standard is written by Marijn Kruisselbrink +(Google, mek@chromium.org). -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 {{FileSystemWritableFileStream/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. +
This Living Standard includes material copied from W3C WICG's +File System Access, which is +available under the +W3C Software and Document License. diff --git a/security-privacy-questionnaire.md b/security-privacy-questionnaire.md deleted file mode 100644 index 008ce9c..0000000 --- a/security-privacy-questionnaire.md +++ /dev/null @@ -1,77 +0,0 @@ -https://www.w3.org/TR/2019/NOTE-security-privacy-questionnaire-20190523/ - -### 2.1. What information might this feature expose to Web sites or other parties, and for what purposes is that exposure necessary? - -This feature exposes files and directories the user explicitly selects to share with web sites with those web sites. This feature doesn't expose any more information than is already exposed via `` and `` today. - -However this feature does make it possible for browsers to extend the time web sites have access to this information. I.e. permission grants and handles received through this API could be persisted giving web sites access to the same files/directories on disk when a user later returns to the website (or when a service worker for the origin is processing an event). At least for the Chrome implementation, we're only planning on having these grants persist for installed PWAs. On the drive-by web, access will only be as long as the web site is open, requiring re-prompting in subsequent visits. - -### 2.2. Is this specification exposing the minimum amount of information necessary to power the feature? - -Yes, we're only exposing files and directories explicitly selected by the user. Of course, web sites could ask for access to a directory when all it needs is access to some files, but the same is already true today. At least as far as exposing information to web sites is concerned, this API doesn't expose any more information than existing APIs do today. - -### 2.3. How does this specification deal with personal information or personally-identifiable information or information derived thereof? - -No data is exposed without the user explicitly choosing what files or directories to expose to the web site. - -### 2.4. How does this specification deal with sensitive information? - -No data is exposed without the user explicitly choosing what files or directories to expose to the web site, so only sensitive data that the user explicitly decides to share via this API will be shared. Furthermore, this API is only exposed in secure contexts, and third-party iframes (i.e. iframes that are cross origin from the top-level frame) won't be able to show pickers or permission prompts and can only access data they were already granted access to from a top-level same origin frame. - -### 2.5. Does this specification introduce new state for an origin that persists across browsing sessions? - -Yes, this specification lets websites store handles they've gotten access to (via a file or directory picker) in IndexedDB. User agents could also persist the permission grants that go with these handles, but at least in the Chrome implementation, these permission grants will only be persistent for installed PWAs. The drive-by web will only have enough state to allow it to re-prompt for access, but the access itself won't be persistent. - -Furthermore, the user will be able to clear storage (storage is file handles in IndexedDB) and/or revoke permissions to clear the state that was persisted, similarly to how other permissions work. - -Websites can also store any state they like in files they get write access to via this API. Since files written to using this API are considered to be data owned by the user, not by the application, this state would not be cleared when clearing browser data. However access to this state would be removed. If a user later picks the same files or directories again to give the website access to them, the websites will regain access to whatever state they persisted. - -Additionally, user agents could also choose to persist the last directory a file was picked from using this API on a per origin (and per purpose via the `FilePickerOption.id` option) basis. This state will not be exposed to the website, it only changes the UI that is presented to the user. A website will have no way of telling if a user picked a file in a certain directory because of this state or because the user manually navigated to the directory. - -### 2.6. What information from the underlying platform, e.g. configuration data, is exposed by this specification to an origin? - -Anything that exists on disk in files could be exposed by the user to the web. However, user agents are encouraged to maintain a block list of certain directories with particularly sensitive files, and thus somewhat restrict which files and directories the user is allowed to select. For example, things like Chrome's "Profile" directory, and other platform configuration data directories are likely going to be on this block list. - -### 2.7. Does this specification allow an origin access to sensors on a user’s device - -No, unless a device exposes such sensors as files or directories. User agents are encouraged to block access to such files or directories (for example `/dev` on linux like systems). - -### 2.8. What data does this specification expose to an origin? Please also document what data is identical to data exposed by other features, in the same or different contexts. - -The data this specification lets a user expose to an origin is identical to the data exposed via `` and ``. The differences are in giving the origins the ability to re-prompt for access to files they previously had access to (if handles were stored in IndexedDB), and in the ability for origins to write back to the files (after explicit permission is granted for that). - -### 2.9. Does this specification enable new script execution/loading mechanisms? - -No. - -### 2.10. Does this specification allow an origin to access other devices? - -Not really. The exception would be devices that are exposed as files or directories by the platform. I.e. network shares or cloud storage sync clients could expose data on other devices in a way that looks like regular files or directories. The user agent could let the user pick these files or directories, thereby giving an origin implicit access to this other device. This API doesn't have any functionality to let a website enumerate all network shares on the local network, only explicitly selected files or directories can be accessed by an origin. - -### 2.11. Does this specification allow an origin some measure of control over a user agent’s native UI? - -The origin can pop up native file or directory pickers, and have some control over what appears inside that native UI (e.g. accepted file types, starting directory and suggested file names), but that control is very limited. The spec does put limitations on what is allowed as an accepted file type and suggested file name to limit the security impact of allowing websites to control the native UI. User agents are expected to employ similar mechanisms to sanitize the sugessted file names as are used to sanitize for example suggested file names in `` today. - -### 2.12. What temporary identifiers might this this specification create or expose to the web? - -None. - -### 2.13. How does this specification distinguish between behavior in first-party and third-party contexts? - -It is expected that user agents do not allow third-party contexts to prompt for any kind of access using this API. I.e. third-party contexts can potentially access files or directories that their origin was already granted access to in a first-party context (by sharing handles via IndexedDB or postMessage), but can't trigger any new file/directory pickers or permission requests. - -### 2.14. How does this specification work in the context of a user agent’s Private Browsing or "incognito" mode? - -The feature will work mostly the same as in regular mode, except no handles or permission grants will be persistent. Web sites can use this API to store data to disk even in private browsing mode, but to later be able to read this data again (either from private browsing or regular mode), the user would have to explicitly re-pick the same file or directory. - -### 2.15. Does this specification have a "Security Considerations" and "Privacy Considerations" section? - -Yes. - -### 2.16. Does this specification allow downgrading default security characteristics? - -No. - -### 2.17. What should this questionnaire have asked? - -Perhaps something about how a feature might impact privacy and security in a bigger picture. Particularly this questionnaire focuses on all the ways it might make privacy or security worse. And while that is important, and while adding new capabilities like this looks scary, from a higher level perspective we do believe that this actually makes things better. Adding these capabilities to the web will lead to Web replacements for one-off native apps, resulting in a net benefit for user security & privacy. diff --git a/w3c.json b/w3c.json deleted file mode 100644 index e63486d..0000000 --- a/w3c.json +++ /dev/null @@ -1,5 +0,0 @@ - { - "group": [80485] -, "contacts": ["marcoscaceres"] -, "repo-type": "cg-report" -}