File System Access

Draft Community Group Report,

This version:
https://wicg.github.io/file-system-access/
Issue Tracking:
GitHub
Inline In Spec
Editor:
(Google)
Former Editors:
(Google)
(Google)

Abstract

This document extends the API in [FS] to enable developers to build powerful web apps that interact with files on the user’s local device. It builds on File API for file reading capabilities, and adds new API surface to enable modifying files, as well as working with directories.

Status of this document

This specification was published by the Web Platform Incubator Community Group. It is not a W3C Standard nor is it on the W3C Standards Track. Please note that under the W3C Community Contributor License Agreement (CLA) there is a limited opt-out and other conditions apply. Learn more about W3C Community and Business Groups.

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.

This API extends the API in [FS], which specifies a bucket file system which websites can get access to without having to first prompt the user for access.

2. Files and Directories

2.1. Concepts

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.

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 mode = "read";
};
permission state constraints
To determine permission state constraints for a FileSystemPermissionDescriptor desc, run these steps:
  1. Let entry be desc["handle"]'s entry.

  2. If entry represents a file system entry in a bucket file system, this descriptor’s permission state must always be "granted".

  3. 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 a handle representing entry’s parent.

  4. Otherwise, if desc["mode"] is "readwrite":

    1. Let read state be the permission state for a descriptor with the same handle, but whose mode is "read".

    2. If read state is not "granted", this descriptor’s permission state must be equal to read state.

Make these checks no longer associated with an entry. [whatwg/fs Issue #101]

permission request algorithm
Given a FileSystemPermissionDescriptor desc and a PermissionStatus status, run these steps:
  1. Run the default permission query algorithm on desc and status.

  2. If status’s state is not "prompt", then abort these steps.

  3. Let settings be desc["handle"]'s relevant settings object.

  4. Let global be settings’s global object.

  5. If global is not a Window, then throw a "SecurityError" DOMException.

  6. If global does not have transient activation, then throw a "SecurityError" DOMException.

  7. If settings’s origin is not same origin with settings’s top-level origin, then throw a "SecurityError" DOMException.

  8. Request permission to use desc.

  9. Run the default permission query algorithm on desc and status.

Ideally this user activation requirement would be defined upstream. [WICG/permissions-request Issue #2]

To query file system permission given a FileSystemHandle handle and a FileSystemPermissionMode mode, run these steps:
  1. Let desc be a FileSystemPermissionDescriptor.

  2. Set desc["name"] to "file-system".

  3. Set desc["handle"] to handle.

  4. Set desc["mode"] to mode.

  5. 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.

  2. Set desc["name"] to "file-system".

  3. Set desc["handle"] to handle.

  4. Set desc["mode"] to mode.

  5. Let status be the result of running create a PermissionStatus for desc.

  6. Run the permission request algorithm for the "file-system" feature, given desc and status.

  7. 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 mode = "read";
};

[Exposed=(Window,Worker), SecureContext, Serializable]
partial interface FileSystemHandle {
  Promise<PermissionState> queryPermission(optional FileSystemHandlePermissionDescriptor descriptor = {});
  Promise<PermissionState> requestPermission(optional FileSystemHandlePermissionDescriptor descriptor = {});
};

2.3.1. The queryPermission() method

status = await handle . queryPermission({ mode : "read" })
status = await handle . queryPermission()
status = (await navigator.permissions.query({ name : "file-system", handle : handle })).state

Queries the current state of the read permission of this handle. If this returns "prompt" the website will have to call 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 . queryPermission({ mode : "readwrite" })
status = (await navigator.permissions.query({ name : "file-system", handle : handle, mode : "readwrite" }).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 requestPermission(). 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.

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

  2. Run the following steps in parallel:

    1. Let state be the result of querying file system permission given this and descriptor["mode"].

    2. Resolve result with state.

  3. Return result.

2.3.2. The requestPermission() method

status = await handle . requestPermission({ mode : "read" })
status = await handle . 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 . requestPermission({ mode : "readwrite" })