[
  {
    "path": ".github/workflows/pr-push.yml",
    "content": "name: CI\non:\n  pull_request: {}\n  push:\n    branches: [main]\n\njobs:\n  main:\n    name: Build, Validate and Deploy\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v2\n      - uses: sidvishnoi/spec-prod@v1\n        with:\n          GH_PAGES_BRANCH: gh-pages\n          VALIDATE_MARKUP: false\n"
  },
  {
    "path": ".gitignore",
    "content": "/index.html"
  },
  {
    "path": ".pr-preview.json",
    "content": "{\n    \"src_file\": \"index.bs\",\n    \"type\": \"bikeshed\",\n    \"params\": {\n        \"force\": 1\n    }\n}\n"
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "content": "# Code of Conduct\n\nAll documentation, code and communication under this repository are covered by the [W3C Code of Ethics and Professional Conduct](https://www.w3.org/Consortium/cepc/).\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# Web Platform Incubator Community Group\n\nThis repository is being used for work in the W3C Web Platform Incubator Community Group, governed by the [W3C Community License\nAgreement (CLA)](http://www.w3.org/community/about/agreements/cla/). To make substantive contributions,\nyou must join the CG.\n\nIf you are not the sole contributor to a contribution (pull request), please identify all\ncontributors in the pull request comment.\n\nTo add a contributor (other than yourself, that's automatic), mark them one per line as follows:\n\n```\n+@github_username\n```\n\nIf you added a contributor by mistake, you can remove them in a comment with:\n\n```\n-@github_username\n```\n\nIf you are making a pull request on behalf of someone else but you had no part in designing the\nfeature, you can remove yourself with the above syntax.\n"
  },
  {
    "path": "EXPLAINER.md",
    "content": "# What is all this?\n\nAt a high level what we're providing is several bits:\n\n1. A modernized version of the existing (but not really standardized)\n   [`Entry`](https://www.w3.org/TR/2012/WD-file-system-api-20120417/#idl-def-Entry)\n   API in the form of new (names TBD) `FileSystemFileHandle` and\n   `FileSystemDirectoryHandle` interfaces (see also the [wicg entries-api](https://wicg.github.io/entries-api/),\n   a read-only and slightly more standardized subset of this same API).\n2. A modernized version of the existing (and also not really standardized)\n   [`FileWriter`](https://dev.w3.org/2009/dap/file-system/file-writer.html#the-filewriter-interface)\n   interface.\n3. Various entry points to get a handle representing a limited view of the\n   local file system. I.e. either via a file picker, or to get access to\n   certain well known directories. Mimicking things such as chrome's\n   [`chrome.fileSystem.chooseEntry`](https://developer.chrome.com/apps/fileSystem#method-chooseEntry) API.\n\n## Use-Cases\n\nIn native applications, there are common file access patterns that we aim to address with this API.\n\n### Single-file Editor\n1. Open a file from the user's file system\n1. Edit the file and save the changes back to the file system\n1. Open another file in the same manner described above\n1. Auto-save any changes to the files in the browsing session\n1. The files can be opened in any native or web applications concurrently\n1. Changes to the files on disk, made in any native or other web application, are accessible\n1. Access the files with the same access in future browsing sessions\n1. Create a new file in the editor\n1. Auto-save changes to the new file in a temporary location, even before the user has picked a file name/location\n\n### Multi-file Editor\n1. Open a directory that contains many files and sub-directories, represented hierarchically\n1. Find and edit multiple files and save the changes back to the file system\n1. Auto-save any further changes to the files in the browsing session\n1. The files can be opened in any native or web applications concurrently\n1. Changes to the files on disk, made in any native or other web applications, are accessible\n1. Access the files with the same access in future browsing sessions\n1. New files in the directory tree, that were not present at the time the root directory was\n   opened, created in any native or other web application, are accessible\n\n### File Libraries\n1. Open one or more directories that contain many files and sub-directories\n1. Changes to the files on disk, made in any native or other web applications, are accessible\n1. Access the files with the same access in future browsing sessions\n1. New files in the directory tree, that were not present at the time the root directory was\n   opened, created in any native or other web application, are accessible\n1. When the user chooses to do some work, access one or more of those files\n\n## Goals\n\nThe main overarching goal here is to increase interoperability of web applications\nwith native applications, specifically where it comes to being able to operate on\nthe native file system.\n\nTraditionally the file system is how different apps collaborate and share data on\ndesktop platforms, but also on mobile there is generally at least some sort of\nconcept of a file system, although it is less prevalent there.\n\nSome example applications of the API we would like to address:\n\n* A simple \"single file\" editor. Also possible integration with a \"file-type\n  handler\" kind of API. Things like (rich) text editors, photo editors, etc.\n\n* Multi-File editors. Things like IDEs, CAD style applications, the kind of apps\n  where you work on a project consisting of multiple files, usually together in\n  the same directory.\n\n* Apps that want to work with \"libraries\" of certain types of files. I.e. photo\n  managers, music managers/media players, or even drawing/publishing apps that\n  want access to the raw font files for all fonts on the system.\n\nBut even though we'd like to design the API to eventually enable all these use\ncases, initially we'd almost certainly be shipping a very limited API surface\nwith limited capabilities.\n\nAdditionally we want to make it possible for websites to get access to some\ndirectory without having to first prompt the user for access. This enables use\ncases where a website wants to save data to disk before a user has picked a\nlocation to save to, without forcing the website to use a completely different\nstorage mechanism with a different API for such files. It also makes it easier\nto write automated tests for code using this API.\n\n## Non-goals\n\nAt least for now out of scope is access to the full file system, subscribing to\nfile change notifications, probably many things related to file metadata (i.e.\nmarking files as executable/hidden, etc). Also not yet planning to address how\nthis new API might integrate with `<input type=file>`.\n\n# Example code\n\n```javascript\n// Show a file picker to open a file.\nconst [file_ref] = await self.showOpenFilePicker({\n    multiple: false,\n    types: [{description: 'Images', accept: {'image/*': ['.jpg', '.gif', '.png']}}],\n    suggestedStartLocation: 'pictures-library'\n});\nif (!file_ref) {\n    // User cancelled, or otherwise failed to open a file.\n    return;\n}\n\n// Read the contents of the file.\nconst file_reader = new FileReader();\nfile_reader.onload = async (event) => {\n    // File contents will appear in event.target.result.  See\n    // https://developer.mozilla.org/en-US/docs/Web/API/FileReader/onload for\n    // more info.\n\n    // ...\n\n    // Write changed contents back to the file. Rejects if file reference is not\n    // writable. Note that it is not generally possible to know if a file\n    // reference is going to be writable without actually trying to write to it.\n    // For example, both the underlying filesystem level permissions for the\n    // file might have changed, or the user/user agent might have revoked write\n    // access for this website to this file after it acquired the file\n    // reference.\n    const writable = await file_ref.createWritable();\n    await writable.write(new Blob(['foobar']));\n    await writable.seek(1024);\n    await writable.write(new Blob(['bla']));\n\n    // |writable| is also a WritableStream, so you can for example pipe into it.\n    let response = await fetch('foo');\n    await response.body.pipeTo(writable);\n\n    // pipeTo by default closes the destination pipe, otherwise an explicit\n    // writable.close() call would have been needed to persist the written data.\n};\n\n// file_ref.file() method will reject if site (no longer) has access to the\n// file.\nlet file = await file_ref.file();\n\n// readAsArrayBuffer() is async and returns immediately.  |file_reader|'s onload\n// handler will be called with the result of the file read.\nfile_reader.readAsArrayBuffer(file);\n```\n\nAlso possible to store file references in IDB to re-read and write to them later.\n\n```javascript\n// Open a db instance to save file references for later sessions\nlet db;\nlet request = indexedDB.open(\"WritableFilesDemo\");\nrequest.onerror = function(e) { console.log(e); }\nrequest.onsuccess = function(e) { db = e.target.result; }\n\n// Show file picker UI.\nconst [file_ref] = await self.showOpenFilePicker();\n\nif (file_ref) {\n    // Save the reference to open the file later.\n    let transaction = db.transaction([\"filerefs\"], \"readwrite\");\n    let request = transaction.objectStore(\"filerefs\").add( file_ref );\n    request.onsuccess = function(e) { console.log(e); }\n\n    // Do other useful things with the opened file.\n};\n\n// ...\n\n// Retrieve a file you've opened before. Show's no filepicker UI, but can show\n// some other permission prompt if the browser so desires.\n// The browser can choose when to allow or not allow this open.\nlet file_id = \"123\"; // Some logic to determine which file you'd like to open\nlet transaction = db.transaction([\"filerefs\"], \"readonly\");\nlet request = transaction.objectStore(\"filerefs\").get(file_id);\nrequest.onsuccess = async function(e) {\n    let ref = e.result;\n\n    // Permissions for the handle may have expired while the handle was stored\n    // in IndexedDB. Before it is safe to use the handle we should request at\n    // least read access to the handle again.\n    if (await ref.requestPermission() != 'granted') {\n      // No longer allowed to access the handle.\n      return;\n    }\n\n    // Rejects if file is no longer readable, either because it doesn't exist\n    // anymore or because the website no longer has permission to read it.\n    let file = await ref.file();\n    // ... read from file\n\n    // Rejects if file is no longer writable, because the website no longer has\n    // permission to write to it.\n    let file_writer = await ref.createWritable();\n    // ... write to file_writer\n}\n```\n\nThe fact that handles are serializable also means you can `postMessage` them around:\n\n```javascript\n// In a service worker:\nself.addEventListener('some-hypothetical-launch-event', async (e) => {\n  // e.file is a FileSystemFileHandle representing the file this SW was launched with.\n  let win = await clients.openWindow('bla.html');\n  if (win)\n    win.postMessage({openFile: e.file});\n});\n\n// In bla.html\nnavigator.serviceWorker.addEventListener('message', e => {\n  let file_ref = e.openFile;\n  // Do something useful with the file reference.\n});\n```\n\nAlso possible to get access to an entire directory.\n\n```javascript\nconst dir_ref = await self.showDirectoryPicker();\nif (!dir_ref) {\n    // User cancelled, or otherwise failed to open a directory.\n    return;\n}\n// Read directory contents.\nfor await (const [name, entry] of dir_ref) {\n    // entry is a FileSystemFileHandle or a FileSystemDirectoryHandle.\n    // name is equal to entry.name\n}\n\n// Get a specific file.\nconst file_ref = await dir_ref.getFile('foo.js');\n// Do something useful with the file.\n\n// Get a subdirectory.\nconst subdir = await dir_ref.getDirectory('bla', {create: true});\n\n// No special API to create copies, but still possible to do so by using\n// available read and write APIs.\nconst new_file = await dir_ref.getFile('new_name', {create: true});\nconst new_file_writer = await new_file.createWritable();\nawait new_file_writer.write(await file_ref.getFile());\nawait new_file_writer.close();\n\n// Or using streams:\nconst copy2 = await dir_ref.getFile('new_name', {create: true});\n(await file_ref.getFile()).stream().pipeTo(await copy2.createWritable());\n```\n\nYou can also check if two references reference the same file or directory (or at\nleast reference the same path), as well as lookup the relative path of an entry\ninside another directory you have access to.\n\nIf for example an IDE has access to a directory, and uses that to display a tree\nview of said directory, this can be useful to be able to highlight a file in that\ntree, even if the file is opened through a new file picker by opening an existing\nfile or saving to a new file.\n\n```javascript\n// Assume we at some point got a valid directory handle.\nconst dir_ref = await self.showDirectoryPicker();\nif (!dir_ref) return;\n\n// Now get a file reference by showing another file picker:\nconst file_ref = await self.showOpenFilePicker();\nif (!file_ref) {\n    // User cancelled, or otherwise failed to open a file.\n    return;\n}\n\n// Check if file_ref exists inside dir_ref:\nconst relative_path = await dir_ref.resolve(file_ref);\nif (relative_path === null) {\n    // Not inside dir_ref\n} else {\n    // relative_path is an array of names, giving the relative path\n    // from dir_ref to the file that is represented by file_ref:\n    let entry = dir_ref;\n    for (const name of relative_path) {\n        entry = await entry.getChild(name);\n    }\n\n    // Now |entry| will represent the same file on disk as |file_ref|.\n    assert await entry.isSameEntry(file_ref) == true;\n}\n```\n\nTo get access to a writable directory without having to ask the user for access,\nwe also provide a \"sandboxed\" file system. Files in this directory are not\nexposed to native applications (or other web applications), but instead are\nprivate to the origin. Storage in this sandboxed file system is subject to\nquota restrictions and eviction measures like other web exposed storage mechanisms.\n\n```javascript\nconst sandboxed_dir = await self.getSandboxedFileSystem();\n\n// The website can freely create files and directories in this directory.\nconst cache_dir = await sandboxed_dir.getDirectory('cache', {create: true});\nfor await (const entry of cache_dir.values()) {\n    // Do something with entry.\n};\n\nconst new_file = await sandboxed_dir.getFile('Untitled 1.txt', {create: true});\nconst writer = await new_file.createWritable();\nwriter.write(\"some data\");\nawait writer.close();\n```\n\nAnd perhaps even possible to get access to certain \"well-known\" directories,\nwithout showing a file picker, i.e. to get access to all fonts, all photos, or\nsimilar. Could still include some kind of permission prompt if needed.\n\n```javascript\nconst font_dir = await FileSystemDirectoryHandle.getSystemDirectory({type: 'fonts'});\nfor await (const entry of font_dir.values()) {\n    // Use font entry.\n};\n```\n\n# Proposed security models\n\nBy far the hardest part for this API is of course going to be the security model\nto use. The API provides a lot of scary power to websites that could be abused\nin many terrible ways. There are both major privacy risks (websites getting\naccess to private data they weren't supposed to have access to) as well as\nsecurity risks (websites modifying executables, installing viruses, encrypting\nthe users data and demanding ransoms, etc). So great care will have to be taken\nto limit how much damage a website can do, and make sure a user understands what\nthey are giving a website access to. Persistent access to a file could also be\nused as some form of super-cookie (but of course all access to files should be\nrevoked when cookies/storage are cleared, so this shouldn't be too bad).\n\nThe primary entry point for this API is a file picker (i.e. a chooser). As such\nthe user always is in full control over what files and directories a website has\naccess to. Furthermore every access to the file (either reading or writing)\nafter a website has somehow gotten a handle is done through an asynchronous API,\nso browser could include more prompting and/or permission checking at those\npoints. This last bit is particularly important when it comes to persisting\nhandles in IndexedDB. When a handle is retrieved later a user agent might want\nto re-prompt to allow access to the file or directory.\n\nOther parts that can contribute to making this API as safe as possible for users\ninclude:\n\n## Limiting access to certain directories\n\nFor example it is probably a good idea for a user agent to not allow the user\nto select things like the root of a filesystem, certain system directories,\nthe users entire home directory, or even their entire downloads directory.\n\n## Limiting write access to certain file types\n\nNot allowing websites to write to certain file types such as executables will\nlimit the possible attack surface.\n\n## Other things user agents come up with\n\n# Staged implementation\n\nAt least in chrome we're not planning on implementing and shipping all this at\nonce. Quite likely an initial implementation will for example not include any of\nthe transferability/serializability and thus retainability of references. We do\nwant to add those feature in later iterations, so we're designing the API to\nsupport them and hope to come up with a security model that can be adapted to\nsupport them, but explicitly not supporting everything initially should make\nthings slightly less scary/dangerous and give more time to figure out how to\nexpose the really powerful bits.\n"
  },
  {
    "path": "LICENSE.md",
    "content": "All Reports in this Repository are licensed by Contributors\nunder the\n[W3C Software and Document License](http://www.w3.org/Consortium/Legal/2015/copyright-software-and-document).\n\nContributions to Specifications are made under the\n[W3C CLA](https://www.w3.org/community/about/agreements/cla/).\n\nContributions to Test Suites are made under the\n[W3C 3-clause BSD License](https://www.w3.org/Consortium/Legal/2008/03-bsd-license.html)\n\n"
  },
  {
    "path": "README.md",
    "content": "# File System Access API\nView proposals in the [EXPLAINER](EXPLAINER.md) and the [spec](https://wicg.github.io/file-system-access/).\n\nSee [changes.md](changes.md) for a list of changes that were made to the API surface between what was available as an Origin Trial in Chrome 83 through Chrome 85, and what will be shipped in Chrome 86.\n\n## Problem\nToday, if a web site wants to create experiences involving local files (document editor, image compressor, etc.) they are at a disadvantage to native apps. A web site must ask the user to reopen a file every time they want to edit it. After opening, the site can only save changes by re downloading the file to the Downloads folder. A native app, by comparison, can maintain a most recently used list, auto save, and save files anywhere the user wants.\n\n## Use cases\n- Open local file to read\n- Open local file to edit and then save\n- Open local file to edit with auto save\n- Create and save a new file\n- Delete an existing file\n- Read meta data about files\n\n## Workarounds\n- [FileSaver.js](https://github.com/eligrey/FileSaver.js/) polyfills `saveAs()` from the [W3C File API](https://www.w3.org/TR/FileAPI/), but files open in a new window instead of downloading on Safari 6.1+ and iOS.\n- In Edge, Firefox, and Chrome developers can:\n\t- Create a fake anchor element (`var a = document.createElement('a')`)\n\t- Set `download` to the desired filename (`a.download = 'file.txt'`)\n\t- Set `href` to a data URI or Blob URL (`a.href = URL.createObjectURL(blob)`)\n\t- Fake a click on the anchor element (`a.click()`)\n\t- Clean up if necessary (`URL.revokeObjectURL(a.href)`)\n\n  This is also the approach taken in the\n  [browser-fs-access](https://github.com/GoogleChromeLabs/browser-fs-access)\n  support library.\n- Setting `window.location` to `'data:application/octet-stream' + data_stream`.\n- Hidden Flash controls to display a “save as” dialog.\n\nThese methods are clunky and only support “save as” (and depending on the UA may automatically appear in Downloads without prompting the user for location). They do not support most recently used lists, auto save, save, or deleting a file.\n"
  },
  {
    "path": "changes.md",
    "content": "This file enumerates the changes that were made to the API surface between the Origin Trial as shipped in Chrome 83,\nand what is or will be available in Chrome 86.\n\n## File Picker API entry points\n\n**Before (in Origin Trial)**\n```javascript\nlet file1 = await window.chooseFileSystemEntries(\n    {type: 'open-file'});\nlet files = await window.chooseFileSystemEntries(\n    {type: 'open-file', multiple: true});\nlet file2 = await window.chooseFileSystemEntries(\n    {type: 'save-file'});\nlet dir = window.chooseFileSystemEntries(\n    {type: 'open-directory'});\n```\n\n**After (in Chrome M86)**\n```javascript\nlet [file1] = await window.showOpenFilePicker();\nlet files = await window.showOpenFilePicker({multiple: true});\nlet file2 = await window.showSaveFilePicker();\nlet dir = await window.showDirectoryPicker();\n```\n\n## Specifying accepted file types\n\n**Before (in Origin Trial)**\n```javascript\nawait window.chooseFileSystemEntries({\n  accepts: [\n    {\n      description: 'Text Files',\n      mimeTypes: ['text/plain', 'text/html'],\n      extensions: ['txt', 'text', 'html', 'htm']\n    },\n    {\n      description: 'Images',\n      mimeTypes: ['image/*'],\n      extensions: ['png', 'gif', 'jpeg', 'jpg']\n    }\n  ]\n});\n```\n\n**After (in Chrome M86)**\n```javascript\nawait window.showOpenFilePicker({\n  types: [\n    {\n      description: 'Text Files',\n      accept: {\n        'text/plain': ['.txt', '.text'],\n        'text/html': ['.html', '.htm']\n      }\n    },\n    {\n      description: 'Images',\n      accept: {\n        'image/*': ['.png', '.gif', '.jpeg', '.jpg']\n      }\n    }\n  ]\n});\n```\n\n## Determining if a handle is a file or directory\n\n**Before (in Origin Trial)**\n```javascript\nif (handle.isFile) {\n  // handle is a file\n} else if (handle.isDirectory) {\n  // handle is a directory\n} else {\n  // can't happen\n}\n```\n\n**After (in Chrome M86)**\n```javascript\nswitch (handle.kind) {\n  case 'file':\n    // handle is a file\n    break;\n  case 'directory':\n    // handle is a directory\n    break;\n  default:\n    // can't happen\n}\n```\n\n## Getting children of a directory\n\n**Before (in Origin Trial)**\n```javascript\nlet file1 = parent.getFile('name');\nlet file2 = parent.getFile('name2', {create: true});\nlet dir1 = parent.getDirectory('dir1');\nlet dir2 = parent.getDirectory('dir2', {create: true});\n```\n\n**After (in Chrome M86)**\n```javascript\nlet file1 = parent.getFileHandle('name');\nlet file2 = parent.getFileHandle('name2', {create: true});\nlet dir1 = parent.getDirectoryHandle('dir1');\nlet dir2 = parent.getDirectoryHandle('dir2', {create: true});\n```\n\n## Directory iteration\n\n**Before (in Origin Trial)**\n```javascript\nfor await (let handle of parent.getEntries()) {\n  // Use handle and/or handle.name\n}\n```\n\n**After (in Chrome M86)**\n```javascript\nfor await (let handle of parent.values()) { /* ... */ }\nfor await (let [name, handle] of parent) { /* ... */ }\nfor await (let [name, handle] of parent.entries()) { /* ... */ }\nfor await (let name of parent.keys()) { /* ... */ }\n```\n\n## Changes to permissions\n\n**Before (in Origin Trial)**\n```javascript\nawait handle.queryPermission();\nawait handle.queryPermission({writable: false});\nawait handle.requestPermission();\nawait handle.requestPermission({writable: false});\n```\n\n**After (in Chrome M86)**\n```javascript\nawait handle.queryPermission();\nawait handle.queryPermission({mode: 'read'});\nawait handle.requestPermission();\nawait handle.requestPermission({mode: 'read'});\n```\n\n**Before (in Origin Trial)**\n```javascript\nawait handle.queryPermission({writable: true});\nawait handle.requestPermission({writable: true});\n```\n\n**After (in Chrome M86)**\n```javascript\nawait handle.queryPermission({mode: 'readwrite'});\nawait handle.requestPermission({mode: 'readwrite'});\n```\n\n## Origin Private/Sandboxed File System\n\n**Before (in Origin Trial)**\n```javascript\nlet root = await FileSystemDirectoryHandle.getSystemDirectory(type: 'sandbox');\n```\n\n**After (in Chrome M86)**\n```javascript\nlet root = await navigator.storage.getDirectory();\n```\n"
  },
  {
    "path": "index.bs",
    "content": "<pre class=metadata>\nTitle: File System Access\nShortname: file-system-access\nAbstract: This document extends the API in [[FS]] to enable developers to build\n  powerful web apps that interact with files on the user's local device.\n  It builds on [[FILE-API|File API]] for file reading capabilities, and adds new API\n  surface to enable modifying files, as well as working with directories.\nStatus: CG-DRAFT\nED: https://wicg.github.io/file-system-access/\nLevel: 1\nEditor: Ming-Ying Chung, Google, mych@chromium.org, w3cid 139196\nFormer Editor: Austin Sullivan, Google, asully@chromium.org, w3cid 126126\nFormer Editor: Marijn Kruisselbrink, Google, mek@chromium.org, w3cid 72440\nGroup: WICG\nRepository: wicg/file-system-access\nIndent: 2\nComplain About: accidental-2119 yes, missing-example-ids yes\nMarkup Shorthands: css no, markdown yes\n</pre>\n\n<pre class=link-defaults>\nspec:webidl; type:dfn; text:resolve\n</pre>\n\n<pre class=anchors>\nurlPrefix: https://tc39.github.io/ecma262/; spec: ECMA-262\n  type: dfn; text: realm; url: realm\nurlPrefix: https://storage.spec.whatwg.org/; spec: storage\n  type: dfn; text: storage; url: site-storage\nurlPrefix: https://www.w3.org/TR/webdriver-bidi/; spec: webdriver-bidi\n  type: dfn; text: WebDriver BiDi file dialog opened; url: webdriver-bidi-file-dialog-opened\n</pre>\n\n<style>\n.domintro dt {\n    font-family: Menlo, Consolas, \"DejaVu Sans Mono\", Monaco, monospace;\n\n    padding-top: 0.5em;\n    padding-bottom: 1em;\n}\n.domintro dt a {\n    color: inherit; border-bottom-style: none;\n}\n.domintro dt code {\n    font-size: inherit;\n}\n.domintro::before {\n    content: 'For web developers (non-normative)';\n    text-transform: initial;\n\n}\n</style>\n\n# Introduction # {#introduction}\n\n*This section is non-normative.*\n\nThis API enables developers to build powerful apps that interact with other\n(non-Web) apps on the user's device via the device's file system. Prominent\nexamples of applications where users expect this functionality are IDEs,\nphoto and video editors, text editors, and more.\nAfter a user grants a web app access, this API allows the app to read or save\nchanges directly to files and folders on the user's device. Beyond reading and\nwriting files, this API provides the ability to open a directory and enumerate\nits contents. Additionally, web apps can use this API to store references to\nfiles and directories they've been given access to, allowing the web apps to\nlater regain access to the same content without requiring the user to select the\nsame file again.\n\nThis API is similar to <a href=\"https://html.spec.whatwg.org/multipage/input.html#file-upload-state-(type=file)\">`<input type=file>`</a>\nand <a href=\"https://wicg.github.io/entries-api/#html-forms\">`<input type=file webkitdirectory>`</a>\n[[entries-api]]\nin that user interaction happens through file and directory picker dialogs.\nUnlike those APIs, this API is currently purely a javascript API, and\ndoes not integrate with forms and/or input elements.\n\nThis API extends the API in [[FS]], which specifies a [=/bucket file system=]\nwhich websites can get access to without having to first prompt the user for access.\n\n# Files and Directories # {#files-and-directories}\n\n## Concepts ## {#concepts}\n\nA <dfn>valid suffix code point</dfn> is a [=code point=] that is [=ASCII alphanumeric=],\nU+002B (+), or U+002E (.).\n\nNote: These code points were chosen to support most pre-existing file formats. The vast\nmajority of file extensions are purely alphanumeric, but compound extensions (such as\n`.tar.gz`) and extensions such as `.c++` for C++ source code are also fairly common,\nhence the inclusion of + and . as allowed code points.\n\n## Permissions ## {#permissions}\n\nThe <dfn for=PermissionName enum-value>\"file-system\"</dfn> [=powerful feature=]'s\npermission-related algorithms and types are defined as follows:\n\n: [=permission descriptor type=]\n:: {{FileSystemPermissionDescriptor}}, defined as:\n  <xmp class=idl>\n  enum FileSystemPermissionMode {\n    \"read\",\n    \"readwrite\"\n  };\n\n  dictionary FileSystemPermissionDescriptor : PermissionDescriptor {\n    required FileSystemHandle handle;\n    FileSystemPermissionMode mode = \"read\";\n  };\n  </xmp>\n\n: [=permission state constraints=]\n:: <div algorithm=\"permission state constraints\">\n  To determine [=permission state constraints=] for a {{FileSystemPermissionDescriptor}} |desc|,\n  run these steps:\n\n  1. Let |entry| be |desc|[\"{{FileSystemPermissionDescriptor/handle}}\"]'s\n     [=FileSystemHandle/entry=].\n  1. If |entry| represents a [=/file system entry=] in a [=/bucket file system=],\n     this descriptor's [=permission state=] must always be\n     \"{{PermissionState/granted}}\".\n  1. Otherwise, if |entry|'s [=file system entry/parent=] is not null, this descriptor's [=permission state=] must be\n     equal to the [=permission state=] for a descriptor with the same {{FileSystemPermissionDescriptor/mode}},\n     and a {{FileSystemPermissionDescriptor/handle}} representing |entry|'s [=file system entry/parent=].\n  1. Otherwise, if |desc|[\"{{FileSystemPermissionDescriptor/mode}}\"] is\n     \"{{FileSystemPermissionMode/readwrite}}\":\n    1. Let |read state| be the [=permission state=] for a descriptor\n       with the same {{FileSystemPermissionDescriptor/handle}},\n       but whose {{FileSystemPermissionDescriptor/mode}} is\n       \"{{FileSystemPermissionMode/read}}\".\n    1. If |read state| is not \"{{PermissionState/granted}}\", this descriptor's [=permission state=]\n       must be equal to |read state|.\n\nIssue(whatwg/fs#101): Make these checks no longer associated with an entry.\n\n: [=permission request algorithm=]\n:: <div algorithm=\"permission request algorithm\">\n  Given a {{FileSystemPermissionDescriptor}} |desc| and a {{PermissionStatus}} |status|,\n  run these steps:\n\n  1. Run the [=default permission query algorithm=] on |desc| and |status|.\n  1. If |status|'s {{PermissionStatus/state}} is not\n     \"{{PermissionState/prompt}}\", then abort these steps.\n  1. Let |settings| be |desc|[\"{{FileSystemPermissionDescriptor/handle}}\"]'s\n     [=relevant settings object=].\n  1. Let |global| be |settings|'s [=environment settings object/global object=].\n  1. If |global| is not a {{Window}}, then\n     [=throw=] a \"{{SecurityError}}\" {{DOMException}}.\n  1. If |global| does not have [=transient activation=], then\n     [=throw=] a \"{{SecurityError}}\" {{DOMException}}.\n  1. If |settings|'s [=environment settings object/origin=]\n     is not [=same origin=] with |settings|'s [=top-level origin=], then\n     [=throw=] a \"{{SecurityError}}\" {{DOMException}}.\n  1. [=Request permission to use=] |desc|.\n  1. Run the [=default permission query algorithm=] on |desc| and |status|.\n\n  Issue(WICG/permissions-request#2): Ideally this user activation requirement would be defined upstream.\n\n<div algorithm>\nTo <dfn lt=\"querying file system permission|query file system permission\">query file system permission</dfn>\ngiven a {{FileSystemHandle}} |handle| and a {{FileSystemPermissionMode}} |mode|, run these steps:\n\n1. Let |desc| be a {{FileSystemPermissionDescriptor}}.\n1. Set |desc|[\"{{PermissionDescriptor/name}}\"] to\n   \"{{PermissionName/file-system}}\".\n1. Set |desc|[\"{{FileSystemPermissionDescriptor/handle}}\"] to |handle|.\n1. Set |desc|[\"{{FileSystemPermissionDescriptor/mode}}\"] to |mode|.\n1. Return |desc|'s [=permission state=].\n\n</div>\n\n<div algorithm>\nTo <dfn lt=\"requesting file system permission|request file system permission\">request file system permission</dfn>\ngiven a {{FileSystemHandle}} |handle| and a {{FileSystemPermissionMode}} |mode|, run these steps:\n\n1. Let |desc| be a {{FileSystemPermissionDescriptor}}.\n1. Set |desc|[\"{{PermissionDescriptor/name}}\"] to\n   \"{{PermissionName/file-system}}\".\n1. Set |desc|[\"{{FileSystemPermissionDescriptor/handle}}\"] to |handle|.\n1. Set |desc|[\"{{FileSystemPermissionDescriptor/mode}}\"] to |mode|.\n1. Let |status| be the result of running <a spec=permissions>create a PermissionStatus</a> for |desc|.\n1. Run the [=permission request algorithm=] for the\n   \"{{PermissionName/file-system}}\" feature, given |desc| and |status|.\n1. Return |desc|'s [=permission state=].\n\n</div>\n\nIssue(119): Currently {{FileSystemPermissionMode}} can only be\n\"{{FileSystemPermissionMode/read}}\" or \"{{FileSystemPermissionMode/readwrite}}\".\nIn the future we might want to add a \"write\" mode as well to support write-only\nhandles.\n\n## The {{FileSystemHandle}} interface ## {#api-filesystemhandle}\n\n<xmp class=idl>\ndictionary FileSystemHandlePermissionDescriptor {\n  FileSystemPermissionMode mode = \"read\";\n};\n\n[Exposed=(Window,Worker), SecureContext, Serializable]\npartial interface FileSystemHandle {\n  Promise<PermissionState> queryPermission(optional FileSystemHandlePermissionDescriptor descriptor = {});\n  Promise<PermissionState> requestPermission(optional FileSystemHandlePermissionDescriptor descriptor = {});\n};\n</xmp>\n\n### The {{FileSystemHandle/queryPermission()}} method ### {#api-filesystemhandle-querypermission}\n\n<div class=\"note domintro\">\n  : |status| = await |handle| . {{FileSystemHandle/queryPermission()|queryPermission}}({ {{FileSystemHandlePermissionDescriptor/mode}} : \"{{FileSystemPermissionMode/read}}\" })\n  : |status| = await |handle| . {{FileSystemHandle/queryPermission()}}\n  : |status| = (await navigator.{{Navigator/permissions}}.{{Permissions/query()|query}}({ {{PermissionDescriptor/name}} : \"{{PermissionName/file-system}}\", {{FileSystemPermissionDescriptor/handle}} : |handle| })).{{PermissionStatus/state}}\n  :: Queries the current state of the read permission of this handle.\n     If this returns \"{{PermissionState/prompt}}\" the website will have to call\n     {{FileSystemHandle/requestPermission()}} before any operations on the\n     handle can be done.\n     If this returns \"{{PermissionState/denied}}\" any operations will reject.\n\n     Usually handles returned by the [=local file system handle factories=] will\n     initially return \"{{PermissionState/granted}}\" for their read permission\n     state, however other than through the user revoking permission, a handle\n     retrieved from IndexedDB is also likely to return\n     \"{{PermissionState/prompt}}\".\n\n  : |status| = await |handle| . {{FileSystemHandle/queryPermission()|queryPermission}}({ {{FileSystemHandlePermissionDescriptor/mode}} : \"{{FileSystemPermissionMode/readwrite}}\" })\n  : |status| = (await navigator.{{Navigator/permissions}}.{{Permissions/query()|query}}({ {{PermissionDescriptor/name}} : \"{{PermissionName/file-system}}\", {{FileSystemPermissionDescriptor/handle}} : |handle|, {{FileSystemPermissionDescriptor/mode}} : \"{{FileSystemPermissionMode/readwrite}}\" }).{{PermissionStatus/state}}\n  :: Queries the current state of the write permission of this handle.\n     If this returns \"{{PermissionState/prompt}}\", attempting to modify the\n     file or directory this handle represents will require user activation\n     and will result in a confirmation prompt being shown to the user.\n     However if the state of the read permission of this handle is also\n     \"{{PermissionState/prompt}}\" the website will need to call\n     {{FileSystemHandle/requestPermission()}}.\n     There is no automatic prompting for read access when attempting to\n     read from a file or directory.\n</div>\n\nAdvisement: The integration with the permissions API's {{Permissions/query()}} method is not yet implemented in Chrome.\n\n<div algorithm>\nThe <dfn method for=FileSystemHandle>queryPermission(|descriptor|)</dfn> method, when invoked, must run these steps:\n\n1. Let |result| be [=a new promise=].\n1. Run the following steps [=in parallel=]:\n  1. Let |state| be the result of [=querying file system permission=]\n     given <b>[=this=]</b> and\n     |descriptor|[\"{{FileSystemHandlePermissionDescriptor/mode}}\"].\n  1. [=/Resolve=] |result| with |state|.\n1. Return |result|.\n\n</div>\n\n### The {{FileSystemHandle/requestPermission()}} method ### {#api-filesystemhandle-requestpermission}\n\n<div class=\"note domintro\">\n  : |status| = await |handle| . {{FileSystemHandle/requestPermission()|requestPermission}}({ {{FileSystemHandlePermissionDescriptor/mode}} : \"{{FileSystemPermissionMode/read}}\" })\n  : |status| = await |handle| . {{FileSystemHandle/requestPermission()}}\n  :: If the state of the read permission of this handle is anything other than\n     \"{{PermissionState/prompt}}\", this will return that state directly.\n     If it is \"{{PermissionState/prompt}}\" however, user activation is needed and\n     this will show a confirmation prompt to the user.\n     The new read permission state is then returned, depending on\n     the user's response to the prompt.\n\n  : |status| = await |handle| . {{FileSystemHandle/requestPermission()|requestPermission}}({ {{FileSystemHandlePermissionDescriptor/mode}} : \"{{FileSystemPermissionMode/readwrite}}\" })\n  :: If the state of the write permission of this handle is anything other than\n     \"{{PermissionState/prompt}}\", this will return that state directly.\n     If the status of the read permission of this handle is\n     \"{{PermissionState/denied}}\" this will return that.\n\n     Otherwise the state of the write permission is \"{{PermissionState/prompt}}\"\n     and this will show a confirmation prompt to the user.\n     The new write permission state is then returned, depending on\n     what the user selected.\n</div>\n\n<div algorithm>\nThe <dfn method for=FileSystemHandle>requestPermission(|descriptor|)</dfn> method, when invoked, must run these steps:\n\n1. Let |result| be [=a new promise=].\n1. Run the following steps [=in parallel=]:\n  1. Let |state| be the result of [=requesting file system permission=]\n     given <b>[=this=]</b> and\n     |descriptor|[\"{{FileSystemHandlePermissionDescriptor/mode}}\"].\n     If that throws an exception, [=/reject=] |result| with that exception and abort.\n  1. [=/Resolve=] |result| with |state|.\n1. Return |result|.\n\n</div>\n\n# Accessing Local File System # {#local-filesystem}\n\n<xmp class=idl>\nenum WellKnownDirectory {\n  \"desktop\",\n  \"documents\",\n  \"downloads\",\n  \"music\",\n  \"pictures\",\n  \"videos\",\n};\n\ntypedef (WellKnownDirectory or FileSystemHandle) StartInDirectory;\n\ndictionary FilePickerAcceptType {\n    USVString description = \"\";\n    record<USVString, (USVString or sequence<USVString>)> accept;\n};\n\ndictionary FilePickerOptions {\n    sequence<FilePickerAcceptType> types;\n    boolean excludeAcceptAllOption = false;\n    DOMString id;\n    StartInDirectory startIn;\n};\n\ndictionary OpenFilePickerOptions : FilePickerOptions {\n    boolean multiple = false;\n};\n\ndictionary SaveFilePickerOptions : FilePickerOptions {\n    USVString? suggestedName;\n};\n\ndictionary DirectoryPickerOptions {\n    DOMString id;\n    StartInDirectory startIn;\n    FileSystemPermissionMode mode = \"read\";\n};\n\n[SecureContext]\npartial interface Window {\n    Promise<sequence<FileSystemFileHandle>> showOpenFilePicker(optional OpenFilePickerOptions options = {});\n    Promise<FileSystemFileHandle> showSaveFilePicker(optional SaveFilePickerOptions options = {});\n    Promise<FileSystemDirectoryHandle> showDirectoryPicker(optional DirectoryPickerOptions options = {});\n};\n</xmp>\n\nThe {{showOpenFilePicker()}}, {{showSaveFilePicker()}} and {{showDirectoryPicker()}} methods\nare together known as the <dfn>local file system handle factories</dfn>.\n\nNote: What is referred to as the \"local file system\" in this spec, does not have to\nstrictly refer to the file system on the local device. What we call the local file system\ncould just as well be backed by a cloud provider. For example on Chrome OS these\nfile pickers will also let you pick files and directories on Google Drive.\n\nAdvisement: In Chrome versions earlier than 85, this was implemented as a generic `chooseFileSystemEntries` method.\n\n## Local File System Permissions ## {#local-file-system-permissions}\n\nThe fact that the user picked the specific files returned by the [=local file system handle factories=] in a prompt\nshould be treated by the user agent as the user intending to grant read access to the website\nfor the returned files. As such, at the time the promise returned by one of the [=local file system handle factories=]\nresolves, [=permission state=] for a descriptor with {{FileSystemPermissionDescriptor/handle}} set to the returned handle,\nand {{FileSystemPermissionDescriptor/mode}} set to \"{{FileSystemPermissionMode/read}}\"\nshould be \"{{PermissionState/granted}}\".\n\nAdditionally for calls to {{showSaveFilePicker}}\nthe [=permission state=] for a descriptor with {{FileSystemPermissionDescriptor/handle}} set to the returned handle,\nand {{FileSystemPermissionDescriptor/mode}} set to \"{{FileSystemPermissionMode/readwrite}}\"\nshould be \"{{PermissionState/granted}}\".\n\n<div algorithm>\nTo verify that an |environment| <dfn>is allowed to show a file picker</dfn>, run these steps:\n\n1. If |environment|'s [=environment settings object/origin=] is an [=opaque origin=],\n   return [=a promise rejected with=] a \"{{SecurityError}}\" {{DOMException}}.\n\n1. If |environment|'s [=environment settings object/origin=] is not [=same origin=] with\n   |environment|'s [=top-level origin=],\n   return [=a promise rejected with=] a \"{{SecurityError}}\" {{DOMException}}.\n\n1. Let |global| be |environment|'s [=environment settings object/global object=].\n\n1. If |global| does not have [=transient activation=], then\n   [=throw=] a \"{{SecurityError}}\" {{DOMException}}.\n\n</div>\n\n## File picker options ## {#api-filepickeroptions}\n\n### Accepted file types ### {#api-filepickeroptions-types}\n\n<div class=\"note domintro\">\nThe {{showOpenFilePicker(options)}} and {{showSaveFilePicker(options)}} methods accept a\n{{FilePickerOptions}} argument, which lets the website specify the types of files\nthe file picker will let the user select.\n\nEach entry in {{FilePickerOptions/types}} specifies a single user selectable option\nfor filtering the files displayed in the file picker.\n\nEach option consists of an <span class=allow-2119>optional</span> {{FilePickerAcceptType/description}}\nand a number of MIME types and extensions (specified as a mapping of\nMIME type to a list of extensions). If no description is provided one will be generated.\nExtensions have to be strings that start with a \".\" and only contain [=valid suffix code points=].\nAdditionally extensions are limited to a length of 16 code points.\n\nIn addition to complete MIME types, \"\\*\" can be used as the subtype of a MIME type to match\nfor example all image formats with \"image/\\*\".\n\nWebsites <span class=allow-2119>should</span> always provide both MIME types and file\nextensions for each option. On platforms that only use file extensions to describe file types\nuser agents can match on the extensions, while on platforms that don't use extensions,\nuser agents can match on MIME type.\n\nBy default the file picker will also include an option to not apply any filter,\nletting the user select any file. Set {{excludeAcceptAllOption}} to `true` to not\ninclude this option in the file picker.\n\nFor example , the following options will let the user pick one of three different filters.\nOne for text files (either plain text or HTML), one for images, and a third one that doesn't apply\nany filter and lets the user select any file.\n\n<pre class=example id=\"filepickeroptions-types-example1\" highlight=js>\nconst options = {\n  <l>{{FilePickerOptions/types}}</l>: [\n    {\n      <l>{{FilePickerAcceptType/description}}</l>: 'Text Files',\n      <l>{{FilePickerAcceptType/accept}}</l>: {\n        'text/plain': ['.txt', '.text'],\n        'text/html': ['.html', '.htm']\n      }\n    },\n    {\n      <l>{{FilePickerAcceptType/description}}</l>: 'Images',\n      <l>{{FilePickerAcceptType/accept}}</l>: {\n        'image/*': ['.png', '.gif', '.jpeg', '.jpg']\n      }\n    }\n  ],\n};\n</pre>\n\nOn the other hand, the following example will only let the user select SVG files. The dialog\nwill not show an option to not apply any filters.\n\n<pre class=example id=\"filepickeroptions-types-example2\" highlight=js>\nconst options = {\n  <l>{{FilePickerOptions/types}}</l>: [\n    {\n      <l>{{FilePickerAcceptType/accept}}</l>: {\n        'image/svg+xml': '.svg'\n      }\n    },\n  ],\n  <l>{{FilePickerOptions/excludeAcceptAllOption}}</l>: true\n};\n</pre>\n\n</div>\n\n<div algorithm>\nTo <dfn>process accept types</dfn>, given {{FilePickerOptions}} |options|,\nrun these steps:\n\n1. Let |accepts options| be a empty [=/list=] of [=tuples=]\n   consisting of a description and a filter.\n1. [=list/For each=] |type| of |options|[\"{{FilePickerOptions/types}}\"]:\n  1. [=map/For each=] |typeString| → |suffixes| of\n     |type|[\"{{FilePickerAcceptType/accept}}\"]:\n    1. Let |parsedType| be the result of [=parse a MIME type=] with |typeString|.\n    1. If |parsedType| is failure, then [=throw=] a {{TypeError}}.\n    1. If |parsedType|'s [=MIME type/parameters=] are not empty, then\n       [=throw=] a {{TypeError}}.\n    1. If |suffixes| is a string:\n      1. [=Validate a suffix=] given |suffixes|.\n    1. Otherwise, [=list/for each=] |suffix| of |suffixes|:\n      1. [=Validate a suffix=] given |suffix|.\n\n  1. Let |filter| be these steps, given a |filename| (a [=string=]) and a |type| (a [=MIME type=]):\n    1. [=map/For each=] |typeString| → |suffixes| of\n       |type|[\"{{FilePickerAcceptType/accept}}\"]:\n    1. Let |parsedType| be the result of [=parse a MIME type=] with |typeString|.\n      1. If |parsedType|'s [=MIME type/subtype=] is \"*\":\n        1. If |parsedType|'s [=MIME type/type=] is \"*\", return `true`.\n        1. If |parsedType|'s [=MIME type/type=] is |type|'s [=MIME type/type=], return `true`.\n      1. |parsedType|'s [=MIME type/essence=] is |type|'s [=MIME type/essence=], return `true`.\n      1. If |suffixes| is a string, set |suffixes| to « |suffixes| ».\n      1. [=list/For each=] |suffix| of |suffixes|:\n        1. If |filename| ends with |suffix|, return `true`.\n    1. Return `false`.\n\n  1. Let |description| be |type|[\"{{FilePickerAcceptType/description}}\"].\n  1. If |description| is an empty string,\n    set |description| to some user understandable string describing |filter|.\n\n  1. [=list/Append=] (|description|, |filter|) to |accepts options|.\n\n1. If either |accepts options| is [=list/empty=],\n  or |options|[\"{{FilePickerOptions/excludeAcceptAllOption}}\"] is `false`:\n  1. Let |description| be a user understandable string describing \"all files\".\n    1. Let |filter| be an algorithm that returns `true`.\n    1. [=list/Append=] (|description|, |filter|) to |accepts options|.\n\n1. If |accepts options| is empty, then [=throw=] a {{TypeError}}.\n\n1. Return |accepts options|.\n\n</div>\n\n<div algorithm>\nTo <dfn>validate a suffix</dfn> |suffix|, run the following steps:\n\n1. If |suffix| does not [=string/starts with|start with=] \".\", then\n   [=throw=] a {{TypeError}}.\n1. If |suffix| contains any [=code points=] that are not\n   [=valid suffix code points=], then [=throw=] a {{TypeError}}.\n1. If |suffix| ends with \".\", then [=throw=] a {{TypeError}}.\n1. If |suffix|'s [=string/length=] is more than 16, then\n   [=throw=] a {{TypeError}}.\n\n</div>\n\n### Starting Directory ### {#api-filepickeroptions-starting-directory}\n\n<div class=\"note domintro\">\nThe {{FilePickerOptions/id}} and {{FilePickerOptions/startIn}} fields can be specified to suggest\nthe directory in which the file picker opens.\n\nIf neither of these options are specified, the user agent remembers the last directory a file\nor directory was picked from, and new pickers will start out in that directory. By specifying an\n{{FilePickerOptions/id}} the user agent can remember different directories for different IDs\n(user agents will only remember directories for a limited number of IDs).\n\n<pre class=example id=\"filepickeroptions-starting-directory-example1\" highlight=js>\n// If a mapping exists from this ID to a previousy picked directory, start in\n// this directory. Otherwise, a mapping will be created from this ID to the\n// directory of the resulting file picker invocation.\nconst options = {\n  <l>{{FilePickerOptions/id}}</l>: 'foo',\n};\n</pre>\n\nSpecifying {{FilePickerOptions/startIn}} as a {{FileSystemFileHandle}} will result in the dialog\nstarting in the parent directory of that file, while passing in a {{FileSystemDirectoryHandle}}\nwill result in the dialog to start in the passed in directory. These take precedence even if\nan explicit {{FilePickerOptions/id}} is also passed in.\n\nFor example, given a {{FileSystemDirectoryHandle}} <var>project_dir</var>, the following will show\na file picker that starts out in that directory:\n\n<pre class=example id=\"filepickeroptions-starting-directory-example2\" highlight=js>\n// The picker will open to the directory of |project_dir| regardless of whether\n// 'foo' has a valid mapping.\nconst options = {\n  <l>{{FilePickerOptions/id}}</l>: 'foo',\n  <l>{{FilePickerOptions/startIn}}</l>: |project_dir|,\n};\n</pre>\n\nThe {{FilePickerOptions/id}} and {{FilePickerOptions/startIn}} fields control only\nthe directory the picker opens to. In the above example, it cannot be assumed that\nthe {{FilePickerOptions/id}} 'foo' will map to the same directory as |project_dir|\nonce the file picker operation has completed.\n\nSpecifying {{FilePickerOptions/startIn}} as a {{WellKnownDirectory}} will result in the dialog\nstarting in that directory, unless an explicit {{FilePickerOptions/id}} was also passed\nin which has a mapping to a valid directory.\n\nBelow is an example of specifying both an {{FilePickerOptions/id}} and\n{{FilePickerOptions/startIn}} as a {{WellKnownDirectory}}. If there is an existing\nmapping from the given ID to a path, this mapping is used. Otherwise, the path suggested\nvia the {{WellKnownDirectory}} is used.\n\n<pre class=example id=\"filepickeroptions-starting-directory-example3\" highlight=js>\n// First time specifying the ID 'foo'. It is not mapped to a directory.\n// The file picker will fall back to opening to the Downloads directory. TODO: link this.\nconst options = {\n  <l>{{FilePickerOptions/id}}</l>: 'foo',  // Unmapped.\n  <l>{{FilePickerOptions/startIn}}</l>: \"<l>{{WellKnownDirectory/downloads}}</l>\",  // Start here.\n};\n\n// Later...\n\n// The ID 'foo' might or might not be mapped. For example, the mapping for this ID\n// might have been evicted.\nconst options = {\n  <l>{{FilePickerOptions/id}}</l>: 'foo',  // Maybe mapped. If so, start here.\n  <l>{{FilePickerOptions/startIn}}</l>: \"<l>{{WellKnownDirectory/downloads}}</l>\",  // Otherwise, start here.\n};\n</pre>\n\n</div>\n\nAdvisement: The {{FilePickerOptions/startIn}} and {{FilePickerOptions/id}} options were first introduced in Chrome 91.\n\nA user agent holds a <dfn>recently picked directory map</dfn>, which is a\n[=map=] of [=/origins=] to [=path id maps=].\n\nA <dfn>path id map</dfn> is a [=map=] of [=valid path ids=] to paths.\n\nA <dfn>valid path id</dfn> is a [=string=] where each character is [=ASCII alphanumeric=] or \"_\" or \"-\".\n\nTo prevent a [=path id map=] from growing without a bound, user agents should implement\nsome mechanism to limit how many recently picked directories will be remembered. This\ncan for example be done by evicting least recently used entries.\nUser agents should allow at least 16 entries to be stored in a [=path id map=].\n\nThe <dfn enum>WellKnownDirectory</dfn> enum lets a website pick one of several well-known\ndirectories. The exact paths the various values of this enum map to is [=implementation-defined=]\n(and in some cases these might not even represent actual paths on disk).\nThe following list describes the meaning of each of the values, and gives possible example paths on different operating systems:\n\n<dl dfn-for=WellKnownDirectory>\n: <dfn enum-value>\"desktop\"</dfn>\n:: The user's Desktop directory, if such a thing exists. For example this could be\n   `C:\\Documents and Settings\\username\\Desktop`, `/Users/username/Desktop`, or `/home/username/Desktop`.\n: <dfn enum-value>\"documents\"</dfn>\n:: Directory in which documents created by the user would typically be stored.\n   For example `C:\\Documents and Settings\\username\\My Documents`, `/Users/username/Documents`, or `/home/username/Documents`.\n: <dfn enum-value>\"downloads\"</dfn>\n:: Directory where downloaded files would typically be stored.\n   For example `C:\\Documents and Settings\\username\\Downloads`, `/Users/username/Downloads`, or `/home/username/Downloads`.\n: <dfn enum-value>\"music\"</dfn>\n:: Directory where audio files would typically be stored.\n   For example `C:\\Documents and Settings\\username\\My Documents\\My Music`, `/Users/username/Music`, or `/home/username/Music`.\n: <dfn enum-value>\"pictures\"</dfn>\n:: Directory where photos and other still images would typically be stored.\n   For example `C:\\Documents and Settings\\username\\My Documents\\My Pictures`, `/Users/username/Pictures`, or `/home/username/Pictures`.\n: <dfn enum-value>\"videos\"</dfn>\n:: Directory where videos/movies would typically be stored.\n   For example `C:\\Documents and Settings\\username\\My Documents\\My Videos`, `/Users/username/Movies`, or `/home/username/Videos`.\n\n<div algorithm>\nTo <dfn>determine the directory the picker will start in</dfn>, given an optional [=string=] |id|,\nan optional {{StartInDirectory}} |startIn| and an [=environment settings object=] |environment|,\nrun the following steps:\n\n1. If |id| given, and is not a [=valid path id=], then\n   [=throw=] a {{TypeError}}.\n1. If |id|'s [=string/length=] is more than 32, then\n   [=throw=] a {{TypeError}}.\n\n1. Let |origin| be |environment|'s [=environment settings object/origin=].\n\n1. If |startIn| is a {{FileSystemHandle}} and |startIn| is not\n   [=FileSystemHandle/is in a bucket file system|in a bucket file system=]:\n  1. Let |entry| be the result of [=locating an entry=] given\n     |startIn|'s [=FileSystemHandle/locator=].\n  1. If |entry| is a [=file entry=], return the path of\n     |entry|'s [=file system entry/parent=] in the local file system.\n  1. If |entry| is a [=directory entry=], return\n     |entry|'s path in the local file system.\n\n1. If |id| is non-empty:\n  1. If [=recently picked directory map=][|origin|] [=map/exists=]:\n    1. Let |path map| be [=recently picked directory map=][|origin|].\n    1. If |path map|[|id|] [=map/exists=], then return |path map|[|id|].\n\n1. If |startIn| is a {{WellKnownDirectory}}:\n  1. Return a user agent defined path corresponding to the {{WellKnownDirectory}} value of |startIn|.\n\n1. If |id| is not specified, or is an empty string:\n  1. If [=recently picked directory map=][|origin|] [=map/exists=]:\n    1. Let |path map| be [=recently picked directory map=][|origin|].\n    1. If |path map|[\"\"] [=map/exists=], then return |path map|[\"\"].\n\n1. Return a default path in a user agent specific manner.\n\n</div>\n\n<div algorithm>\nTo <dfn>remember a picked directory</dfn>, given an optional [=string=] |id|,\na [=/file system entry=] |entry|, and an [=environment settings object=] |environment|,\nrun the following steps:\n\n1. Let |origin| be |environment|'s [=environment settings object/origin=].\n1. If [=recently picked directory map=][|origin|] does not [=map/exist=],\n   then set [=recently picked directory map=][|origin|] to an empty [=path id map=].\n1. If |id| is not specified, let |id| be an empty string.\n1. Set [=recently picked directory map=][|origin|][|id|] to the path on the local file system corresponding to |entry|,\n   if such a path can be determined.\n\n</div>\n\n## The {{showOpenFilePicker()}} method ## {#api-showopenfilepicker}\n\n<div class=\"note domintro\">\n  : [ |handle| ] = await window . {{showOpenFilePicker()}}\n  : [ |handle| ] = await window . {{showOpenFilePicker()|showOpenFilePicker}}({ {{OpenFilePickerOptions/multiple}}: false })\n  :: Shows a file picker that lets a user select a single existing file, returning a handle for\n    the selected file.\n\n  : handles = await window . {{showOpenFilePicker()|showOpenFilePicker}}({ {{OpenFilePickerOptions/multiple}}: true })\n  :: Shows a file picker that lets a user select multiple existing files, returning handles for\n    the selected files.\n\n    Additional options can be passed to {{showOpenFilePicker()}} to indicate the types of files\n    the website wants the user to select and the directory in which the\n    file picker will open. See [[#api-filepickeroptions]] for details.\n</div>\n\n<div algorithm>\nThe <dfn method for=Window>showOpenFilePicker(|options|)</dfn> method, when invoked, must run\nthese steps:\n\n1. Let |environment| be <b>[=this=]</b>'s [=relevant settings object=].\n\n1. Let |accepts options| be the result of [=process accept types|processing accept types=] given |options|.\n1. Let |starting directory| be the result of\n   [=determine the directory the picker will start in|determining the directory the picker will start in=]\n   given |options|[\"{{FilePickerOptions/id}}\"],\n   |options|[\"{{FilePickerOptions/startIn}}\"], and |environment|.\n\n1. Let |global| be |environment|'s [=environment settings object/global object=].\n1. Verify that |environment| [=is allowed to show a file picker=].\n\n1. Let |p| be [=a new promise=].\n1. Run the following steps [=in parallel=]:\n\n  1. Optionally, wait until any prior execution of this algorithm has terminated.\n\n  1. Let |filePickerOptions| be an empty [=/map=].\n\n  1. Set |filePickerOptions|[\"multiple\"] to |options|[\"{{OpenFilePickerOptions/multiple}}\"].\n\n  1. Let |dismissed| be the result of [=WebDriver BiDi file dialog opened=] with null and |filePickerOptions|.\n\n  1. If |dismissed| is false:\n\n    1. Display a prompt to the user requesting that the user pick some files.\n       If |filePickerOptions|[\"multiple\"] is false, there must be no more than one file selected;\n       otherwise any number may be selected.\n\n       The displayed prompt should let the user pick one of the |accepts options| to filter the list of displayed files.\n       Exactly how this is implemented, and what this prompt looks like is [=implementation-defined=].\n\n       When possible, this prompt should start out showing |starting directory|.\n\n    1. Wait for the user to have made their selection.\n\n  1. If |dismissed| is true or if the user dismissed the prompt without making a selection,\n     [=/reject=] |p| with an \"{{AbortError}}\" {{DOMException}} and abort.\n\n  1. Let |entries| be a [=/list=] of [=file entries=] representing the selected files or directories.\n  1. Let |result| be a empty [=/list=].\n\n  1. [=list/For each=] |entry| of |entries|:\n    1. If |entry| is deemed [=too sensitive or dangerous=] to be exposed to this website by the user agent:\n      1. Inform the user that the selected files or directories can't be exposed to this website.\n      1. At the discretion of the user agent,\n         either go back to the beginning of these [=in parallel=] steps,\n         or [=/reject=] |p| with an \"{{AbortError}}\" {{DOMException}} and abort.\n\n    1. Add a new {{FileSystemFileHandle}} associated with |entry| to |result|.\n\n  1. [=Remember a picked directory=] given\n     |options|[\"{{FilePickerOptions/id}}\"], |entries|[0] and |environment|.\n\n  1. Perform the <a spec=html>activation notification</a> steps in |global|'s [=Window/browsing context=].\n\n     Note: This lets a website immediately perform operations on the returned handles that\n     might require user activation, such as requesting more permissions.\n\n  1. [=/Resolve=] |p| with |result|.\n\n1. Return |p|.\n\n</div>\n\n## The {{Window/showSaveFilePicker()}} method ## {#api-showsavefilepicker}\n\n<div class=\"note domintro\">\n  : |handle| = await window . {{showSaveFilePicker()|showSaveFilePicker}}( |options| )\n  :: Shows a file picker that lets a user select a single file, returning a handle for\n    the selected file. The selected file does not have to exist already. If the selected\n    file does not exist a new empty file is created before this method returns, otherwise\n    the existing file is cleared before this method returned.\n\n  : |handle| = await window . {{showSaveFilePicker()|showSaveFilePicker}}({ {{SaveFilePickerOptions/suggestedName}}: \"README.md\" })\n  :: Shows a file picker with the suggested \"README.md\" file name pre-filled as the default file name to save as.\n\n    Additional |options| can be passed to {{showSaveFilePicker()}} to indicate the types of files\n    the website wants the user to select and the directory in which the\n    file picker will open. See [[#api-filepickeroptions]] for details.\n</div>\n\nAdvisement: The {{SaveFilePickerOptions/suggestedName}} option was first introduced in Chrome 91.\n\n<div algorithm>\nThe <dfn method for=Window>showSaveFilePicker(|options|)</dfn> method, when invoked, must run\nthese steps:\n\n1. Let |environment| be <b>[=this=]</b>'s [=relevant settings object=].\n\n1. Let |accepts options| be the result of [=process accept types|processing accept types=] given |options|.\n1. Let |starting directory| be the result of\n   [=determine the directory the picker will start in|determining the directory the picker will start in=]\n   given |options|[\"{{FilePickerOptions/id}}\"],\n   |options|[\"{{FilePickerOptions/startIn}}\"] and |environment|.\n\n1. Let |global| be |environment|'s [=environment settings object/global object=].\n1. Verify that |environment| [=is allowed to show a file picker=].\n\n1. Let |p| be [=a new promise=].\n1. Run the following steps [=in parallel=]:\n\n  1. Optionally, wait until any prior execution of this algorithm has terminated.\n\n  1. Display a prompt to the user requesting that the user pick exactly one file.\n     The displayed prompt should let the user pick one of the |accepts options| to filter the list of displayed files.\n     Exactly how this is implemented, and what this prompt looks like is [=implementation-defined=].\n     If |accepts options| are displayed in the UI, the selected option should also be used to suggest an extension\n     to append to a user provided file name, but this is not required. In particular user agents are free to ignore\n     potentially dangerous suffixes such as those ending in `\".lnk\"` or `\".local\"`.\n\n     When possible, this prompt should start out showing |starting directory|.\n\n     If |options|[\"{{SaveFilePickerOptions/suggestedName}}\"] [=map/exists=] and\n     is not null, the file picker prompt will be pre-filled with the\n     |options|[\"{{SaveFilePickerOptions/suggestedName}}\"] as the default name\n     to save as. The interaction between the\n     {{SaveFilePickerOptions/suggestedName}} and |accepts options| is [=implementation-defined=].\n     If the {{SaveFilePickerOptions/suggestedName}} is deemed too dangerous, user agents should ignore or sanitize the\n     suggested file name, similar to the sanitization done when fetching something <a spec=html>as a download</a>.\n\n     Note: A user agent could for example pick whichever option in |accepts options| that matches\n     {{SaveFilePickerOptions/suggestedName}} as the default filter.\n\n  1. Wait for the user to have made their selection.\n\n  1. If the user dismissed the prompt without making a selection,\n     [=/reject=] |p| with an \"{{AbortError}}\" {{DOMException}} and abort.\n\n  1. Let |entry| be a [=file entry=] representing the selected file.\n\n  1. If |entry| is deemed [=too sensitive or dangerous=] to be exposed to this website by the user agent:\n    1. Inform the user that the selected files or directories can't be exposed to this website.\n    1. At the discretion of the user agent,\n       either go back to the beginning of these [=in parallel=] steps,\n       or [=/reject=] |p| with an \"{{AbortError}}\" {{DOMException}} and abort.\n\n  1. Set |entry|'s [=binary data=] to an empty [=byte sequence=].\n\n  1. Set |result| to a new {{FileSystemFileHandle}} associated with |entry|.\n\n  1. [=Remember a picked directory=] given\n     |options|[\"{{FilePickerOptions/id}}\"], |entry| and |environment|.\n\n  1. Perform the <a spec=html>activation notification</a> steps in |global|'s [=Window/browsing context=].\n\n     Note: This lets a website immediately perform operations on the returned handles that\n     might require user activation, such as requesting more permissions.\n\n  1. [=/Resolve=] |p| with |result|.\n\n1. Return |p|.\n\n</div>\n\n## The {{Window/showDirectoryPicker()}} method ## {#api-showdirectorypicker}\n\n<div class=\"note domintro\">\n  : |handle| = await window . {{Window/showDirectoryPicker()}}\n  : |handle| = await window . {{Window/showDirectoryPicker()}}({ {{DirectoryPickerOptions/mode}}: 'read' })\n  :: Shows a directory picker that lets the user select a single directory, returning a handle for\n    the selected directory if the user grants read permission.\n\n  : |handle| = await window . {{Window/showDirectoryPicker()}}({ {{DirectoryPickerOptions/mode}}: 'readwrite' })\n  :: Shows a directory picker that lets the user select a single directory, returning a handle for\n    the selected directory. The user agent can combine read and write permission requests on this handle into\n    one subsequent prompt.\n\n  The {{DirectoryPickerOptions/id}} and {{DirectoryPickerOptions/startIn}} fields behave\n  identically to the {{FilePickerOptions/id}} and {{FilePickerOptions/startIn}} fields, respectively.\n  See [[#api-filepickeroptions-starting-directory]] for details on how to use these fields.\n</div>\n\n<div algorithm>\nThe <dfn method for=Window>showDirectoryPicker(<var>options</var>)</dfn> method, when invoked, must run\nthese steps:\n\n1. Let |environment| be <b>[=this=]</b>'s [=relevant settings object=].\n\n1. Let |starting directory| be the result of [=determine the directory the picker will start in|determining the directory the picker will start in=]\n   given |options|[\"{{DirectoryPickerOptions/id}}\"],\n   |options|[\"{{DirectoryPickerOptions/startIn}}\"] and |environment|.\n\n1. Let |global| be |environment|'s [=environment settings object/global object=].\n1. Verify that |environment| [=is allowed to show a file picker=].\n\n1. Let |p| be [=a new promise=].\n1. Run the following steps [=in parallel=]:\n\n  1. Optionally, wait until any prior execution of this algorithm has terminated.\n\n  1. Let |filePickerOptions| be an empty [=/map=].\n\n  1. Set |filePickerOptions|[\"multiple\"] to false.\n\n  1. Let |dismissed| be the result of [=WebDriver BiDi file dialog opened=] with null and |filePickerOptions|.\n\n  1. If |dismissed| is false:\n\n    1. Display a prompt to the user requesting that the user pick a directory.\n\n       When possible, this prompt should start out showing |starting directory|.\n\n    1. Wait for the user to have made their selection.\n\n  1. If |dismissed| is true or if the user dismissed the prompt without making a selection,\n     [=/reject=] |p| with an \"{{AbortError}}\" {{DOMException}} and abort.\n\n  1. Let |entry| be a [=directory entry=] representing the selected directory.\n\n  1. If |entry| is deemed [=too sensitive or dangerous=] to be exposed to this website by the user agent:\n    1. Inform the user that the selected files or directories can't be exposed to this website.\n    1. At the discretion of the user agent,\n       either go back to the beginning of these [=in parallel=] steps,\n       or [=/reject=] |p| with an \"{{AbortError}}\" {{DOMException}} and abort.\n\n  1. Set |result| to a new {{FileSystemDirectoryHandle}} associated with |entry|.\n\n  1. [=Remember a picked directory=] given\n     |options|[\"{{DirectoryPickerOptions/id}}\"], |entry| and |environment|.\n\n  1. Let |desc| be a {{FileSystemPermissionDescriptor}}.\n  1. Set |desc|[\"{{PermissionDescriptor/name}}\"] to\n     \"{{PermissionName/file-system}}\".\n  1. Set |desc|[\"{{FileSystemPermissionDescriptor/handle}}\"] to |result|.\n  1. Set |desc|[\"{{FileSystemPermissionDescriptor/mode}}\"] to\n     |options|[\"{{DirectoryPickerOptions/mode}}\"].\n  1. Let |status| be the result of running <a spec=permissions>create a PermissionStatus</a> for |desc|.\n\n  1. Perform the <a spec=html>activation notification</a> steps in |global|'s [=Window/browsing context=].\n\n  1. [=Request permission to use=] |desc|.\n  1. Run the [=default permission query algorithm=] on |desc| and |status|.\n  1. If |status| is not \"{{PermissionState/granted}}\",\n     [=/reject=] |result| with a \"{{AbortError}}\" {{DOMException}} and abort.\n\n  1. Perform the <a spec=html>activation notification</a> steps in |global|'s [=Window/browsing context=].\n\n  1. [=/Resolve=] |p| with |result|.\n\n1. Return |p|.\n\n</div>\n\n## Drag and Drop ## {#drag-and-drop}\n\n<xmp class=idl>\npartial interface DataTransferItem {\n    Promise<FileSystemHandle?> getAsFileSystemHandle();\n};\n</xmp>\n\nDuring a <em>drag-and-drop operation</em>, dragged file and\ndirectory items are associated with [=file entries=] and [=directory entries=]\nrespectively.\n\n<div class=\"note domintro\">\n  : |handle| = await item . {{getAsFileSystemHandle()}}\n  :: Returns a {{FileSystemFileHandle}} object if the dragged item is a file and a {{FileSystemDirectoryHandle}} object if the dragged item is a directory.\n</div>\n\n<div algorithm>\n\nThe <dfn method for=DataTransferItem>getAsFileSystemHandle()</dfn> method steps are:\n\n1. If the {{DataTransferItem}} object is not in the <a spec=html>read/write\n    mode</a> or the <a spec=html>read-only mode</a>, return\n    [=a promise resolved with=] `null`.\n\n1. If the <a spec=html>the drag data item kind</a> is not <em>File</em>,\n    then return [=a promise resolved with=] `null`.\n\n1. Let |p| be [=a new promise=].\n\n1. Run the following steps [=in parallel=]:\n\n  1. Let |entry| be the [=/file system entry=] representing the dragged file or directory.\n\n  1. If |entry| is a [=file entry=]:\n\n    1. Let |handle| be a {{FileSystemFileHandle}} associated with |entry|.\n\n  1. Else if |entry| is a [=directory entry=]:\n\n    1. Let |handle| be a {{FileSystemDirectoryHandle}} associated with |entry|.\n\n  1. [=/Resolve=] |p| with |entry|.\n\n1. Return |p|.\n\n</div>\n\n<div class=example id=draganddrop-example>\nHandling drag and drop of files and directories:\n<xmp highlight=js>\nelem.addEventListener('dragover', (e) => {\n  // Prevent navigation.\n  e.preventDefault();\n});\n\nelem.addEventListener('drop', async (e) => {\n  e.preventDefault();\n\n  const fileHandlesPromises = [...e.dataTransfer.items]\n    .filter(item => item.kind === 'file')\n    .map(item => item.getAsFileSystemHandle());\n\n  for await (const handle of fileHandlesPromises) {\n    if (handle.kind === 'directory') {\n      console.log(`Directory: ${handle.name}`);\n    } else {\n      console.log(`File: ${handle.name}`);\n    }\n  }\n});\n</xmp>\n</div>\n\nIssue: This currently does not block access to [=too sensitive or dangerous=] directories, to\nbe consistent with other APIs that give access to dropped files and directories. This is inconsistent\nwith the [=local file system handle factories=] though, so we might want to reconsider this.\n\n# Accessibility Considerations # {#accessibility-considerations}\n\n*This section is non-normative.*\n\nWhen this specification is used to present information in the user interface,\nimplementors will want to follow the OS level accessibility guidelines for the platform.\n\n\n# Privacy Considerations # {#privacy-considerations}\n\n*This section is non-normative.*\n\nThis API does not give websites any more read access to data than the existing `<input type=file>`\nand `<input type=file webkitdirectory>` APIs already do. Furthermore similarly to those APIs, all\naccess to files and directories is explicitly gated behind a file or directory picker.\n\nThere are however several major privacy risks with this new API:\n\n## Users giving access to more, or more sensitive files than they intended. ## {#privacy-wide-access}\n\nThis isn't a new risk with this API, but user agents should try to make sure that users are aware\nof what exactly they're giving websites access to. This is particularly important when giving\naccess to a directory, where it might not be immediately clear to a user just how many files\nactually exist in that directory.\n\nA related risk is having a user give access to particularly sensitive data. This\ncould include some of a user agent's configuration data, network cache or cookie store,\nor operating system configuration data such as password files. To protect against this, user agents\nare encouraged to restrict which directories a user is allowed to select in a directory picker,\nand potentially even restrict which files the user is allowed to select. This will make it much\nharder to accidentally give access to a directory that contains particularly sensitive data. Care\nmust be taken to strike the right balance between restricting what the API can access while still\nhaving the API be useful. After all, this API intentionally lets the user use websites to interact\nwith some of their most private personal data.\n\nExamples of directories that user agents might want to restrict as being\n<dfn>too sensitive or dangerous</dfn> include:\n\n* The directory or directories containing the user agent itself.\n* Directories where the user agent stores [=storage|website storage=].\n* Directories containing system files (such as `C:\\Windows` on Windows).\n* Directories such as `/dev/`, `/sys`, and `/proc` on Linux that would give access to low-level devices.\n* A user's entire \"home\" directory.\n  Individual files and directories inside the home directory should still be allowed,\n  but user agents should not generally let users give blanket access to the entire directory.\n* The default directory for downloads, if the user agent has such a thing.\n  Individual files inside the directory again should be allowed, but the whole directory would risk leaking more data than a user realizes.\n* Files with names that end in `.lnk`, when selecting a file to write to. Writing to\n  these files on Windows is similar to creating symlinks on other operating systems,\n  and as such can be used to attempt to trick users into giving access to files they didn't intend to expose.\n* Files with names that end in `.local`, when selecting a file to write to.\n  Windows uses these files to decide what DLLs to load, and as such writing to\n  these files could be used to cause code to be executed.\n\n## Websites trying to use this API for tracking. ## {#privacy-tracking}\n\nThis API could be used by websites to track the user across clearing browsing\ndata. This is because, in contrast with existing file access APIs, user agents are\nable to grant persistent access to files or directories and can re-prompt. In\ncombination with the ability to write to files, websites will be able to persist an\nidentifier on the users' disk. Clearing browsing data will not affect those files\nin any way, making these identifiers persist through those actions.\n\nThis risk is somewhat mitigated by the fact that clearing browsing data will clear all handles\nthat a website had persisted (for example in IndexedDB),\nso websites won't have any handles to re-prompt for permission after browsing data was cleared.\nFurthermore user agents are encouraged to make it clear what files and directories a website has\naccess to, and to automatically expire permission grants except for particularly well trusted\norigins (for example persistent permissions could be limited to \"installed\" web applications).\n\nUser agents also are encouraged to provide a way for users to revoke permissions granted.\nClearing browsing data is expected to revoke all permissions as well.\n\n## First-party vs third-party contexts. ## {#privacy-third-party}\n\nIn third-party contexts (e.g. an iframe whose origin does not match that of the top-level frame)\nwebsites can't gain access to data they don't already have access to. This includes both getting\naccess to new files or directories via the [=local file system handle factories=], as well as requesting\nmore permissions to existing handles via the {{requestPermission}} API.\n\nHandles can also only be post-messaged to same-origin destinations. Attempts to send a handle to\na cross-origin destination will result in a {{MessagePort/messageerror}} event.\n\n# Security Considerations # {#security-considerations}\n\n*This section is non-normative.*\n\nThis API gives websites the ability to modify existing files on disk, as well as write to new\nfiles. This has a couple of important security considerations:\n\n## Malware ## {#security-malware}\n\nThis API could be used by websites to try to store and/or execute malware on the users system.\nTo mitigate this risk, this API does not provide any way to mark files as executable (on the other\nhand files that are already executable likely remain that way, even after the files are modified\nthrough this API). Furthermore user agents are encouraged to apply things like Mark-of-the-Web to\nfiles created or modified by this API.\n\nFinally, user agents are encouraged to verify the contents of files modified by this API via malware\nscans and safe browsing checks, unless some kind of external strong trust relation already exists.\nThis of course has effects on the performance characteristics of this API.\n\n## Ransomware attacks ## {#security-ransomware}\n\nAnother risk factor is that of ransomware attacks. The limitations described above regarding\nblocking access to certain sensitive directories helps limit the damage such an attack can do.\nAdditionally user agents can grant write access to files at whatever granularity they deem\nappropriate.\n\n## Filling up a users disk ## {#filling-up-disk}\n\nOther than files in a [=/bucket file system=], files written by this API are not subject\nto [=storage quota=]. As such websites can fill up a users disk without being limited by\nquota, which could leave a users device in a bad state (do note that even with storage that is\nsubject to [=storage quota=] it is still possible to fill up, or come close to filling up, a users\ndisk, since [=storage quota=] in general is not dependent on the amount of available disk\nspace).\n\nWithout this API websites can write data to disk not subject to quota limitations already\nby triggering downloads of large files (potentially created client side, to not incur any network\noverhead). While the presence of {{FileSystemWritableFileStream/truncate()}} and writing at a\npotentially really large offset past the end of a file makes it much easier and lower cost to\ncreate large files, on most file systems such files should not actually take up as much disk space as\nmost commonly used file systems support sparse files (and thus wouldn't actually store the NUL\nbytes generated by resizing a file or seeking past the end of it).\n\nWhatever mitigations user agents use to guard against websites filling up a disk via either\nquota managed storage or the existing downloads mechanism should also be employed when websites\nuse this API to write to disk.\n"
  },
  {
    "path": "proposals/AccessHandle.md",
    "content": "# AccessHandle Proposal\n\n## Authors:\n\n* Emanuel Krivoy (fivedots@chromium.org)\n* Richard Stotz (rstz@chromium.org)\n\n## Participate\n\n* [Issue tracker](https://github.com/WICG/file-system-access/issues)\n\n## Table of Contents\n\n<!-- START doctoc generated TOC please keep comment here to allow auto update -->\n<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->\n\n- [Introduction](#introduction)\n- [Goals & Use Cases](#goals--use-cases)\n- [Non-goals](#non-goals)\n- [What makes the new surface fast?](#what-makes-the-new-surface-fast)\n- [Proposed API](#proposed-api)\n  - [New data access surface](#new-data-access-surface)\n  - [Locking semantics](#locking-semantics)\n- [Open Questions](#open-questions)\n  - [Assurances on non-awaited consistency](#assurances-on-non-awaited-consistency)\n- [Trying It Out](#trying-it-out)\n- [Appendix](#appendix)\n  - [AccessHandle IDL](#accesshandle-idl)\n- [References & acknowledgements](#references--acknowledgements)\n\n<!-- END doctoc generated TOC please keep comment here to allow auto update -->\n\n## Introduction\n\nWe propose augmenting the Origin Private File System (OPFS) with a new surface\nthat brings very performant access to data. This new surface differs from\nexisting ones by offering in-place and exclusive write access to a file’s\ncontent. This change, along with the ability to consistently read unflushed\nmodifications and the availability of a synchronous variant on dedicated\nworkers, significantly improves performance and unblocks new use cases for the\nFile System Access API.\n\nMore concretely, we would add a *createAccessHandle()* method to the\n*FileSystemFileHandle* object. It would return an *AccessHandle* that contains\na [duplex stream](https://streams.spec.whatwg.org/#other-specs-duplex) and\nauxiliary methods. The readable/writable pair in the duplex stream communicates\nwith the same backing file, allowing the user to read unflushed contents.\nAnother new method, *createSyncAccessHandle()*, would only be exposed on Worker\nthreads. This method would offer a more buffer-based surface with synchronous\nreading and writing. The creation of AccessHandle also creates a lock that\nprevents write access to the file across (and within the same) execution\ncontexts.\n\nThis proposal is part of our effort to integrate [Storage Foundation\nAPI](https://github.com/WICG/storage-foundation-api-explainer) into File System\nAccess API. For more context the origins of this proposal, and alternatives\nconsidered, please check out: [Merging Storage Foundation API and the Origin\nPrivate File\nSystem](https://docs.google.com/document/d/121OZpRk7bKSF7qU3kQLqAEUVSNxqREnE98malHYwWec),\n[Recommendation for Augmented\nOPFS](https://docs.google.com/document/d/1g7ZCqZ5NdiU7oqyCpsc2iZ7rRAY1ZXO-9VoG4LfP7fM).\n\nAlthough this proposal is the successor \"in spirit\" to the Storage Foundation\nAPI, the two APIs operate on entirely different sets of files. There exists no\nway of accessing a file stored through Storage Foundation API using the Origin\nPrivate File System, and vice versa.\n\n## Goals & Use Cases\n\nOur goal is to give developers flexibility by providing generic, simple, and\nperformant primitives upon which they can build higher-level storage\ncomponents. The new surface is particularly well suited for Wasm-based\nlibraries and applications that want to use custom storage algorithms to\nfine-tune execution speed and memory usage.\n\nA few examples of what could be done with *AccessHandles*:\n\n*   Distribute a performant Wasm port of SQLite. This gives developers the\n    ability to use a persistent and fast SQL engine without having to rely on\n    the deprecated WebSQL API.\n*   Allow a music production website to operate on large amounts of media, by\n    relying on the new surface's performance and direct buffered access to\n    offload sound segments to disk instead of holding them in memory.\n*   Provide a fast and persistent [Emscripten](https://emscripten.org/)\n    filesystem to act as generic and easily accessible storage for Wasm.\n\n## Non-goals\n\nThis proposal is focused only on additions to the [Origin Private File\nSystem](https://wicg.github.io/file-system-access/#sandboxed-filesystem), and\ndoesn't currently consider changes to the rest of File System Access API or how\nfiles in the host machine are accessed.\n\nThis proposal does not consider accessing files stored using the Storage \nFoundation API through OPFS or vice versa.\n\n## What makes the new surface fast?\n\nThere are a few design choices that primarily contribute to the performance of\nAccessHandles:\n\n*   Write operations are not guaranteed to be immediately persistent, rather\n    persistency is achieved through calls to *flush()*. At the same time, data\n    can be consistently read before flushing. This allows applications to only\n    schedule time consuming flushes when they are required for long-term data\n    storage, and not as a precondition to operate on recently written data.\n*   The exclusive write lock held by the AccessHandle saves implementations\n    from having to provide a central data access point across execution\n    contexts. In multi-process browsers, such as Chrome, this helps avoid costly\n    inter-process communication (IPCs) between renderer and browser processes.\n*   Data copies are avoided when reading or writing. In the async surface this\n    is achieved through SharedArrayBuffers and BYOB readers. In the sync\n    surface, we rely on user-allocated buffers to hold the data.\n\nFor more information on what affects the performance of similar storage APIs,\nsee [Design considerations for the Storage Foundation\nAPI](https://docs.google.com/document/d/1cOdnvuNIWWyJHz1uu8K_9DEgntMtedxfCzShI7d01cs)\n\n## Proposed API\n\n### New data access surface\n\n```javascript\n// In all contexts\nconst accessHandle = await fileHandle.createAccessHandle();\nawait accessHandle.writable.getWriter().write(buffer);\nconst reader = accessHandle.readable.getReader({ mode: \"byob\" });\n// Assumes seekable streams, and SharedArrayBuffer support are available\nawait reader.read(buffer, { at: 1 });\n\n// Only in a worker context\nconst accessHandle = await fileHandle.createSyncAccessHandle();\nconst writtenBytes = accessHandle.write(buffer);\nconst readBytes = accessHandle.read(buffer, { at: 1 });\n```\n\nAs mentioned above, a new *createAccessHandle()* method would be added to\n*FileSystemFileHandle*. Another method, *createSyncAccessHandle()*, would be\nonly exposed on Worker threads. An IDL description of the new interface can be\nfound in the [Appendix](#appendix).\n\nThe reason for offering a Worker-only synchronous interface, is that consuming\nasynchronous APIs from Wasm has severe performance implications (more details\n[here](https://docs.google.com/document/d/1lsQhTsfcVIeOW80dr467Auud_VCeAUv2ZOkC63oSyKo)).\nSince this overhead is most impactful on methods that are called often, we've\nonly made *read()* and *write()* synchronous. This allows us to keep a simpler\nmental model (where the sync and async handle are identical, except reading and\nwriting) and reduce the number of new sync methods, while avoiding the most\nimportant perfomance penalties.\n\nThis proposal assumes that [seekable\nstreams](https://github.com/whatwg/streams/issues/1128) will be available. If\nthis doesn’t happen, we can emulate the seeking behavior by extending the\ndefault reader and writer with a *seek()* method.\n\n### Locking semantics\n\n```javascript\nconst accessHandle1 = await fileHandle.createAccessHandle();\ntry {\n  const accessHandle2 = await fileHandle.createAccessHandle();\n} catch (e) {\n  // This catch will always be executed, since there is an open access handle\n}\nawait accessHandle1.close();\n// Now a new access handle may be created\n```\n\n*createAccessHandle()* would take an exclusive write lock on the file that\nprevents the creation of any other access handles or  *WritableFileStreams*.\nSimilarly *createWritable()* would take a shared write lock that blocks the\ncreation of access handles, but not of other writable streams. This prevents\nthe file from being modified from multiple contexts, while still being\nbackwards compatible with the current OPFS spec and supporting multiple\n*WritableFileStreams* at once.\n\nCreating a [File](https://www.w3.org/TR/FileAPI/#dfn-file) through *getFile()*\nwould be possible when a lock is in place. The returned File behaves as it\ncurrently does in OPFS i.e., it is invalidated if file contents are changed\nafter it was created. It is worth noting that these Files could be used to\nobserve changes done through the new API, even if a lock is still being held.\n\n## Open Questions\n\n### Assurances on non-awaited consistency\n\nIt would be possible to clearly specify the behavior of an immediate async read\noperation after a non-awaited write operation, by serializing file operations\n(as is currently done in Storage Foundation API). We should decide if this is\nconvenient, both from a specification and performance point of view.\n\n## Trying It Out\n\nA prototype of the synchronous surface (i.e., *createSyncAccessHandles()* and\nthe *FileSystemSyncAccessHandle* object) is available in Chrome. If you're\nusing version 95 or higher, you can enable it by launching Chrome with the\n`--enable-blink-features=FileSystemAccessAccessHandle` flag or enabling\n\"Experimental Web Platform features\" in \"chrome://flags\". If you're using\nversion 94, launch Chrome with the\n`--enable-features=FileSystemAccessAccessHandle` flag.\n\nSync access handles are available in an Origin Trial, starting with Chrome 95.\nSign up\n[here](https://developer.chrome.com/origintrials/#/view_trial/3378825620434714625)\nto participate.\n\nWe have also developed an Emscripten file system based on access handles.\nInstructions on how to use it can be found\n[here](https://github.com/rstz/emscripten-pthreadfs/blob/main/pthreadfs/README.md).\n\n## Appendix\n\n### AccessHandle IDL\n\n```webidl\ninterface FileSystemFileHandle : FileSystemHandle {\n  Promise<File> getFile();\n  Promise<FileSystemWritableFileStream> createWritable(optional FileSystemCreateWritableOptions options = {});\n\n  Promise<FileSystemAccessHandle> createAccessHandle();\n  [Exposed=DedicatedWorker]\n  Promise<FileSystemSyncAccessHandle> createSyncAccessHandle();\n};\n\n[SecureContext]\ninterface FileSystemAccessHandle {\n  // Assumes seekable streams are available. The\n  // Seekable extended attribute is ad-hoc notation for this proposal.\n  [Seekable] readonly attribute WritableStream writable;\n  [Seekable] readonly attribute ReadableStream readable;\n\n  // Resizes the file to be size bytes long. If size is larger than the current\n  // size the file is padded with null bytes, otherwise it is truncated.\n  Promise<undefined> truncate([EnforceRange] unsigned long long size);\n  // Returns the current size of the file.\n  Promise<unsigned long long> getSize();\n  // Persists the changes that have been written to disk\n  Promise<undefined> flush();\n  // Flushes and closes the streams, then releases the lock on the file\n  Promise<undefined> close();\n};\n\n[Exposed=DedicatedWorker, SecureContext]\ninterface FileSystemSyncAccessHandle {\n  unsigned long long read([AllowShared] BufferSource buffer,\n                             FilesystemReadWriteOptions options);\n  unsigned long long write([AllowShared] BufferSource buffer,\n                              FilesystemReadWriteOptions options);\n\n  Promise<undefined> truncate([EnforceRange] unsigned long long size);\n  Promise<unsigned long long> getSize();\n  Promise<undefined> flush();\n  Promise<undefined> close();\n};\n\ndictionary FilesystemReadWriteOptions {\n  [EnforceRange] unsigned long long at;\n};\n```\n\n## References & acknowledgements\n\nMany thanks for valuable feedback and advice from:\n\nDomenic Denicola, Marijn Kruisselbrink, Victor Costan\n"
  },
  {
    "path": "proposals/CloudIdentifier.md",
    "content": "# Cloud Identifier\n\n## Authors:\n\n* Alexander Hendrich (hendrich@chromium.org)\n\n## Participate\n\n* [Issue tracker](https://github.com/WICG/file-system-access/issues)\n\n## Table of Contents\n\n<!-- START doctoc generated TOC please keep comment here to allow auto update -->\n<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->\n\n- [Introduction](#introduction)\n  - [Sample usage](#sample-usage)\n    - [Demo page](#demo-page)\n  - [Use Cases](#use-cases)\n    - [Remote file handling](#remote-file-handling)\n      - [Before](#before)\n      - [After](#after)\n    - [De-duplication for online document editors](#de-duplication-for-online-document-editors)\n    - [Drag \\& Drop into Mail](#drag--drop-into-mail)\n  - [Non-Goals](#non-goals)\n- [Design](#design)\n  - [Web IDL](#web-idl)\n  - [Interaction with CSP client](#interaction-with-csp-client)\n    - [Registration](#registration)\n    - [Requesting a cloud identifier](#requesting-a-cloud-identifier)\n- [Security and Privacy Considerations](#security-and-privacy-considerations)\n  - [Fingerprinting](#fingerprinting)\n  - [Modification via read-only permission](#modification-via-read-only-permission)\n- [Contributors](#contributors)\n- [Stakeholder Feedback / Opposition](#stakeholder-feedback--opposition)\n\n<!-- END doctoc generated TOC please keep comment here to allow auto update -->\n\n## Introduction\nThe objective of this API is to allow web applications to detect whether a `FileSystemHandle` they have acquired (obtained via file/directory picker or as a parameter of an opening flow as a registered file handler) belongs to a cloud-synced file/directory. If so, the web application receives a “cloud identifier” so that it can directly interact with the file/directory using the cloud storage provider’s (CSP) backend APIs.\n\nA CSP can register as a local sync client to the browser, which in turn may ask the local sync client to provide a unique identifier for a given file/directory when requested.\n\n### Sample usage\n\n```javascript\nconst [fileHandle] = await window.showOpenFilePicker(pickerOpts);\nconst cloudIdentifiers = await fileHandle.getCloudIdentifiers();\n\nif(cloudIdentifiers.length === 0) {\n  // File is not synced by any CSP\n}\nfor(const cloudIdentifier of cloudIdentifiers) {\n  if(cloudIdentifier.providerName === 'drive.google.com') {\n    // retrieve/modify the file from Google Drive API using cloudIdentifier.id\n  } else if(cloudIdentifier.providerName === 'onedrive.live.com') {\n    // retrieve/modify the file from Microsoft OneDrive API using cloudIdentifier.id\n  }\n}\n```\n\n#### Demo page\nThere is a [demo page](https://cloud-file-handling.glitch.me/) where this API can be tested behind a flag on ChromeOS. Sample usage of that demo page can also be seen with [this recording](https://drive.google.com/file/d/1nrAYOwp9w6JtsKx8XxHkHAVjHyvQG53o/view?usp=sharing).\n\n### Use Cases\n\n#### Remote file handling\n\nWeb applications offering app streaming or VDI might want to make a locally synced file available to the remote virtual machine where the app/desktop is being streamed from. For example, the user runs their image editor on a remote machine, launches the file picker to open image.png from the local client device, makes some changes and then saves the file with Ctrl+S.\n\n##### Before\n\n![image](./images/cloud-identifier/remote-file-handling-before.png \"Before\")\n\n1. VDI web app requests and receives the file via `FileSystemFileHandle.getFile()`\n2. CSP client app downloads the file from CSP server (if not already on disc)\n3. VDI web app transfers the file’s content to VDI server, which creates it as local file on the server and opens the application for that file\n4. VDI server sends any changes made to the file back to the VDI web app\n5. VDI web app writes updated file contents via `FileSystemFileHandle.write()`, which is picked up by the CSP client app\n6. CSP client app synchronizes the file by uploading it to the CSP server\n\nIn this scenario all transfers (arrows in diagram) transfer the entire file and the file has to be downloaded and uploaded from the local device’s network connection.\n\n##### After\n\n![image](./images/cloud-identifier/remote-file-handling-after.png \"After\")\n\n1. VDI web app requests and receives the file’s cloud identifier via `FileSystemFileHandle.getCloudIdentifiers()`\n2. VDI web app sends the file’s cloud identifier to VDI server\n3. VDI server requests and receives the file’s content from CSP server using the cloud identifier\n4. VDI server sends any changes made to the file back to the CSP server\n5. CSP client synchronizes updated file to local device (might be metadata only)\n\nIn this scenario only transfers (3) and (4) would actually transfer the full file, all the other transfers only move the cloud identifier. These actual file transfers also don’t move across the device’s network connection, but rather use the VDI server’s network connection to the CSP server, which should likely also have higher bandwidth.\n\nThis reduces network traffic and delays (especially with large files) and prevents version drift (file being modified somewhere else while a local client device is uploading).\n\n#### De-duplication for online document editors\n\nWeb-based document editors can already open local files using existing file system APIs. In order to offer full functionality, including cross device support and some advanced editing/sharing features, the documents need to be uploaded to a cloud storage (Google Drive or Microsoft OneDrive or similar) before they can be edited. This leads to duplicate files for documents that were already stored in these locations.\n\nWith the proposed changes, the web application could check whether the given file handle is already synced by a cloud storage and generate identifiers for these without a duplicate upload.\n\n#### Drag & Drop into Mail\n\nSharing files via mail used to be done using simple file attachments. Especially for large files, it is preferable to instead upload them to a CSP and then share an access link to that file to the recipient. Google Mail’s web application, for example, already does this by prompting the user to upload a file to Google Drive when attaching a large file.\n\nWith the proposed changes, the web application could already detect that the file is synced by cloud storage and generate a share link for that file’s cloud identifier without requiring the user to manually go through these steps or prompt the user to upload the file to cloud storage again.\n\n### Non-Goals\n\nThis proposal does **not** plan to provide a way for web apps to\n* provide permission to these files/directories.\n* interact (fetch/modify/etc) with these files/directories.\n* provide additional meta-data (e.g. sync status)\n\nacross various different CSP backend APIs.\n\nThis new web API just serves as bridge between the CSP's local sync clients and the web app to retrieve a unique identifier for a file/directory. Obtaining the required permissions on that file/directory or actually reading/writing contents to it via CSP backend API is delegated to the web app's own implementation for each CSP. The `getCloudIdentifiers()` might also return `FileSystemCloudIdentifier`s for CSPs the web app does not support.\n\n## Design\n\n![image](./images/cloud-identifier/design.png \"Design\")\n\n1. Locally installed CSP client registers itself as a provider for certain directories with the browser\n2. Web app receives `FileSystemHandle` for a file/directory (via File Handling opening flow, File System Access file/directory picker or drag & drop) and requests its cloud identifier(s) via `getCloudIdentifiers()`.\n3. Browser checks whether this path has been registered as being synced by a local sync client\n   * If no, resolve promise with empty list\n4. For each registered local sync client for that path, the browser will send a request to the registered local sync client's executable with the file’s/directory’s path\n5. Local sync client requests a token for that path from the CSP\n6.  CSP can choose to generate a one-time token or stable identifier to later be used by the CSP’s backend APIs. Either way, the properties of the token are up to the CSP and completely opaque to the browser.\n7. Local sync client responds to browser with the token\n8. Browser gathers all responses from registered local sync clients. For all incoming responses, the browser will construct a cloud identifier consisting of the responding local sync client's registered identifier and the provided token. Once all registered local sync clients for that path have responded, the promise is resolved with a list of cloud identifiers. If the local sync client fails to respond within a reasonable time frame, the promise is resolved with all received cloud identifiers until then or empty list if none.\n9. Web app can freely interact with CSP’s backend APIs on that file/directory. The web application is responsible for getting the right access permissions for these web APIs.\n\n### Web IDL\n\nThis section describes the interface the web app would interact with.\n\n```idl\ndictionary FileSystemCloudIdentifier {\n  DOMString providerName;\n  DOMString id;\n};\n\npartial interface FileSystemHandle {\n  Promise<FileSystemCloudIdentifier[]> getCloudIdentifiers();\n}\n```\n\nThe new method\n* extends `FileSystemHandle`, i.e. is available for files and directories.\n* returns a list of `FileSystemCloudIdentifier`s since a single file/directory can be synced by multiple CSPs at the same time, although in most cases this list would only contain a single entry.\n* returns an empty list if the file/directory is not synced by any CSPs or no CSP client responded in time.\n\n### Interaction with CSP client\n\nThis web API requires the browser and the CSP’s local sync client to exchange information. This piece is not part of the official specification and up to individual browser’s to implement, but we can provide guidelines here as well.\n\n#### Registration\n\nThe browser needs to be aware of \n* which CSP client’s are available on the user’s computer\n* which files/directories are synced by the CSP client\n* where the CSP client executable is located \n* the CSP’s identifier (e.g. “com.google.drive”)\n\nTherefore, the CSP needs to provide a JSON file containing these details and register it with the browser at a known location (specific directory or registry key), similar to [Chrome extension’s native messaging host registration](https://developer.chrome.com/docs/apps/nativeMessaging/#native-messaging-host-location).\n\nThe provided file would look like this:\n\n```json\n{\n  \"name\": \"com.foo-company.drive\",\n  \"path\": \"C:\\\\Program Files\\\\FooCompany\\\\cloud_storage_provider_helper.exe\",\n  \"synced_paths\": [\n    \"C:\\\\path_to_synced_directory\\\\\",\n    \"C:\\\\path_to_other_synced_directory\\\\\",\n  ]\n}\n```\n\n#### Requesting a cloud identifier\n\nWhenever a web app calls `getCloudIdentifiers()`, the browser will iterate through all registered CSP clients and filter for ones that cover the file’s/directory’s path as part of their `synced_paths`.\n\nFor these, the browser will launch the executable at `path` and transfer the web app’s origin and the requested file/directory path, either via command line argument or via stdin (preceded with a 32-bit message length).\n\nThe CSP client will then respond with the token for the given file/directory via stdout (preceded with a 32-bit message length).\n\nOnce the browser has received all responses (or some reasonable timeout has expired), the browser will bundle the individual tokens and the CSPs’ names together to return a cloud identifier.\n\nIt is up to each individual browser’s implementation on how they handle dynamic registrations, i.e. whether they re-read all the CSP registration files for each request and how long or if they cache them.\n\n## Security and Privacy Considerations\n\n### Fingerprinting\n\nThe browser has no control whether the CSP will provide one-time tokens or stable identifiers as part of their cloud identifier. If the CSP provides stable identifiers, the web application could use these as a [fingerprinting](https://www.w3.org/TR/fingerprinting-guidance/#dfn-active-fingerprinting) mechanism for the files/directories it has access to.\n\nIn theory, if a web application already has access to a `FileSystemHandle` the web application could already use other mechanisms to generate fingerprinting identifiers, but these are less stable:\n* Hashing the file’s/directory’s contents -> identifier will change if file/directory content changes\n* `FileSystemHandle.getUniqueId()` [[explainer](https://github.com/whatwg/fs/pull/46)] -> clearing browsing data will reset unique IDs\n\nThe web application would need repeated access to the same `FileSystemHandle` to perform this fingerprinting though. That means the user must either re-grant access to the same file or the web app stores the file handle in an IndexedDB, which can be cleared by the user though by clearing their browsing data.\n\nIt would also be up to the CSP whether they actually provide permanent tokens or temporary tokens.\n\n### Modification via read-only permission\n\nIf a web application only has `read` [permission](https://wicg.github.io/file-system-access/#enumdef-filesystempermissionmode) to a `FileSystemHandle`, but has edit/write permission to that file via CSP backend APIs, it could still modify the cloud-stored file, which is then synced to the device and thereby modify the file. \n\nIn this case, the user has clearly granted write access to the CSP-backed file and by having a sync client, the user also allows the local files to be modified, so the change would actually not be surprising.\n\n## Contributors\n\n* Austin Sullivan (asully@chromium.org)\n* Rob Beard (rbeard@google.com)\n\n## Stakeholder Feedback / Opposition\n\n* Web developer: [positive](https://github.com/WICG/file-system-access/pull/411#issuecomment-1609676416)\n"
  },
  {
    "path": "proposals/SuggestedNameAndDir.md",
    "content": "# Suggested file name and location\n\n## Authors:\n\n* Marijn Kruisselbrink\n\n## Participate\n\n* [Issue tracker](https://github.com/WICG/file-system-access/issues)\n\n## Table of Contents\n\n<!-- START doctoc generated TOC please keep comment here to allow auto update -->\n<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->\n\n- [Introduction](#introduction)\n- [Goals and Use Cases](#goals-and-use-cases)\n  - [Save an existing file in a different directory](#save-an-existing-file-in-a-different-directory)\n  - [Save a file using a different name in the same directory](#save-a-file-using-a-different-name-in-the-same-directory)\n  - [Open a file from the same directory as a currently open file or project](#open-a-file-from-the-same-directory-as-a-currently-open-file-or-project)\n  - [Prompt a user to open a image (or video, or audio file)](#prompt-a-user-to-open-a-image-or-video-or-audio-file)\n  - [Remember different last-used directories for different purposes](#remember-different-last-used-directories-for-different-purposes)\n- [Non-goals](#non-goals)\n- [Proposed API](#proposed-api)\n  - [Specifying suggested file name to save as](#specifying-suggested-file-name-to-save-as)\n  - [Specifying starting directory based on an existing handle.](#specifying-starting-directory-based-on-an-existing-handle)\n  - [Specifying a well-known starting directory](#specifying-a-well-known-starting-directory)\n  - [Distinguishing the \"purpose\" of different file picker invocations.](#distinguishing-the-purpose-of-different-file-picker-invocations)\n- [Detailed design discussion](#detailed-design-discussion)\n  - [Interaction of `suggestedName` and accepted file types](#interaction-of-suggestedname-and-accepted-file-types)\n    - [Considered alternatives](#considered-alternatives)\n  - [Interaction between `startIn` and `id`](#interaction-between-startin-and-id)\n    - [Considered alternatives](#considered-alternatives-1)\n  - [Start in directory vs start in parent of directory](#start-in-directory-vs-start-in-parent-of-directory)\n  - [Security considerations](#security-considerations)\n- [Stakeholder Feedback / Opposition](#stakeholder-feedback--opposition)\n- [References & acknowledgements](#references--acknowledgements)\n\n<!-- END doctoc generated TOC please keep comment here to allow auto update -->\n\n## Introduction\n\nWhen initially shipping the File System Access API we shipped a bare minimum API\nsurface for showing file and directory pickers. We're now proposing adding a\ncouple of frequently requested (and commonly supported in other file picker\nAPIs) options to let websites provide a couple more opportunities to influence\nthe behavior of the file and directory pickers.\n\nIn particular this explainer addresses letting websites give suggestions for the\nname and location of files and directories that are being saved or loaded.\n\n## Goals and Use Cases\n\nThe overarching goal here is to provide websites the opportunity to provide the\nfile or directory picker implementation suggestions for both the initial\ndirectory to show in the picker, and (in the case of save dialogs) the file name\nto save a file as. Specifically to help address the following use cases:\n\n### Save an existing file in a different directory\n\n1. User opens a file from their local file system, or from some cloud provider.\n2. User then picks the \"Save As\" option from the web application.\n3. User saves the file in a different directory, but using the name the file\n   already had (without having to re-type the name).\n\n### Save a file using a different name in the same directory\n\n1. Users opens a file from their local file system.\n2. User then picks the \"Save As\" option from the web application.\n3. User saves the file using a different name, in the directory the original\n   file exists (without having to re-navigate to that directory).\n\n### Open a file from the same directory as a currently open file or project\n\n1. User is editing a file from their local file system.\n2. User then picks the \"Open\" option from the web application to open another file.\n3. User can pick files from the same directory the currently open file is in\n   without having to re-navigate to that directory.\n\n### Prompt a user to open a image (or video, or audio file)\n\n1. User picks \"Insert image\" in a web application\n2. A file picker pops up starting out in the users \"Pictures\" folder.\n\n### Remember different last-used directories for different purposes\n\n1. User opens a document in a web application from directory A.\n2. User inserts an image into the document, by selecting a file from directory B.\n3. User opens a different document, from directory A without having to navigate\n   to directory A again.\n4. Further image insertion operations should have their file pickers start out\n   in the directory of the last image insertion as well.\n\n## Non-goals\n\nAll the above functionality is intended as hints/suggestions from websites to\nthe file picker implementation to improve the end user experience. However\nimplementations should not be required to always follow these suggestions. If\nfor example a suggested file name provided by the website is deemed too\ndangerous to be allowed, implementations should be free to ignore the suggestion\n(or adjust the suggested file name to not be dangerous anymore).\n\n## Proposed API\n\n### Specifying suggested file name to save as\n\n```javascript\nconst file_ref = await self.showSaveFilePicker({\n  suggestedName: 'README.md',\n  types: [{\n    description: 'Markdown',\n    accept: {\n      'text/markdown': ['.md'],\n    },\n  }],\n});\n```\n\n### Specifying starting directory based on an existing handle.\n\nWe propose adding a `startIn` option, that can be either a file or a directory\nhandle. If the passed in handle is a file handle, the picker will start out in\nthe parent directory of the file, while if the passed in handle is a directory\nhandle the picker will start out in the passed in directory itself.\n\nAdditionally, in a save file picker, if `startIn` is a file, and no explicit\n`suggestedName` is also passed in, the name from the passed in file handle\nwill be treated as if it was passed as `suggestedName`.\n\nThis lets you use `startIn` for typical \"Save as\" UI flows:\n\n```javascript\nasync function saveFileAs(file_handle) {\n  return await self.showSaveFilePicker({\n    startIn: file_handle\n  });\n}\n```\n\nAnd is also useful for cases where you a user is likely to want to open a file\nfrom the same directory as a currently open file or directory:\n\n```javascript\nasync function openFileFromDirectory(project_dir) {\n  return await self.showOpenFilePicker({\n    startIn: project_dir\n  });\n}\n\n// Used when prompting the user to open a new file, starting out in the\n// directory containing a currently open file.\nasync function openFileFromDirectoryContainingFile(open_file) {\n  return await self.showOpenFilePicker({\n    startIn: open_file\n  });\n}\n\n// Used for example in a flow where a website wants to prompt the user to open\n// the directory containing the currently opened file (for example for file\n// formats that contain relative paths to other files in the same directory).\nasync function openDirectoryContainingFile(open_file) {\n  return await self.showDirectoryPicker({\n    startIn: open_file\n  });\n}\n```\n\n### Specifying a well-known starting directory\n\nTo support saving files in or opening files from certain well-known directories,\nwe also propose allowing passing certain string values to `startIn` to represent\nthese well-known directories. This would look something like:\n\n```javascript\nconst file_ref = await self.showOpenFilePicker({\n  startIn: 'pictures'\n});\n```\n\nThe possible values for `startIn` would be:\n\n- `desktop`: The user's Desktop directory, if such a thing exists.\n- `documents`: Directory in which documents created by the user would typically be stored.\n- `downloads`: Directory where downloaded files would typically be stored.\n- `music`: Directory where audio files would typically be stored.\n- `pictures`: Directory where photos and other still images would typically be stored.\n- `videos`: Directory where videos/movies would typically be stored.\n\n### Distinguishing the \"purpose\" of different file picker invocations.\n\nCurrently the Chrome implementation of the File System Access API remembers the\nlast used directory on a per-origin basis. To allow websites to remember\ndifferent directories for file pickers that are used for different purposes\n(i.e. opening documents, exporting to other formats, inserting images) we\npropose adding an `id` option to the file and directory picker methods. If an\n`id` is specified the file picker implementation will remember a separate last\nused directory for pickers with that same `id`:\n\n```javascript\nconst file_ref1 = await self.showSaveFilePicker({\n  id: 'fruit'\n});\n\nconst file_ref2 = await self.showSaveFilePicker({\n  id: 'flower'\n});\n\n// Picker should start out in the directory `file_ref1` was picked from.\nconst file_ref3 = await self.showOpenFilePicker({\n  id: 'fruit'\n});\n\n```\n\n## Detailed design discussion\n\n### Interaction of `suggestedName` and accepted file types\n\nIt is possible for `suggestedName` to be inconsistent with the file types a\nwebsite declared as being the accepted file types for a file picker. There are\na couple of cases worth highlighting here:\n\n1. If `suggestedName` ends in a suffix that is specified for one of the accepted\n   file types, the file picker should default to that file type.\n\n2. Otherwise, if `excludeAcceptAllOption` is false (or no explicit accepted file\n   types are provided), the \"all files\" options should be the default selected\n   file type in the file picker.\n\n3. Finally, if neither of these are the case (i.e. the suggested file name does\n   not match any of the file types accepted by the dialog), the implementation\n   should behave as if `excludeAcceptAllOption` was set to false, and default\n   to that option.\n\n#### Considered alternatives\n\nAn alternative to 3. would be to reject if no accepted file types match the\nsuggested file name.\n\nAnother alternative to 3. would be to append a suffix from the first accepted\nfile type to the suggested file name (or replace the extension from the\nsuggested file name with one that is accepted). This seems less desirable than 3\nbecause this would mean that specifying an extension in the `suggestedName` is\noptional as long as `excludeAcceptAllOption` is set to true, but then later\nchanging `excludeAcceptAllOption` to false would suddenly change behavior and\npossibly break existing API usage.\n\n### Interaction between `startIn` and `id`\n\nBoth these attributes influence what directory the file picker should start with,\nas such it isn't immediately obvious what should happen if all are provided.\n\nOur proposal is for it to not be an error to provide both `startIn` and\n`id`. If a well-known directory is provide for `startIn`, `id` will take\nprecedence. That means that if a previously recorded path for the same `id`\nexists, it will be used as starting directory, and only if no such path is\nknown will `startIn` be used.\n\nOn the other hand if `startIn` is a file or directory handle, it will take\nprecedence over `id`. In this case `startIn` specifies what directory the\nfile picker should start out in, and the ultimately picked directory will be\nrecorded as the last selected directory for the given `id`, such that future\ninvocations with that `id` but no `startIn` will use the directory.\n\n#### Considered alternatives\n\nWe could reject if both a `startIn` option and `id` are provided, to ensure only\none option controls which directory to start in. There don't see to be many\ndownsides to allowing both though, as recording the last used directory for\nfuture invocations of a file picker without a `startIn` option still seems\nuseful.\n\nWe could also have either `id` or `startIn` always take precedence over the\nother. In some ways this might be less confusing, as it would always be clear\nwhich one takes precedence, without having to know the type of what is passed to\n`startIn`. We've chosen not to do so though. Using a well-known directory only\nas a fallback mechanism for the `id` based directory matches what other similar\nAPIs do. Simultanously if a website explicitly specifies a concrete directory to\nopen the picker in by passing a file or directory handle to `startIn` we also\nwant to respect that. Hence the precedence depending on the type of the `startIn`\noption.\n\n### Start in directory vs start in parent of directory\n\nAn earlier version of this document had separate `startIn` and `startInParentOf`\noptions. This would enable websites to not only open file or directory pickers\nin the same directory as a passed in directory handle, but also in the parent\ndirectory of such a directory (without needing to have access to a handle for\nthe parent directory).\n\nHaving a single `startIn` option results in a simpler and easier to explain and\nunderstand API, whil still supporting all the major use cases. Websites will be\nable to start file or directory pickers in any directory they have a handle to,\nas well as any directory for which they have a handle to a file in said\ndirectory. The only hypothetical use case that isn't covered by this is for\ncases where the website wants the user to select a sibling directory to a\npreviously selected directory, but we're not aware of any concrete use cases\nwhere that would be beneficial.\n\n### Security considerations\n\nAs mentioned in the non-goals section, all these attributes should be considered\nsuggestions or hints. This is particularly relevant for the `suggestedName`\noption. The same concerns around writing to files ending in `.lnk` or `.local`\nas mentioned in https://wicg.github.io/file-system-access/#privacy-wide-access\nalso apply to letting the website suggest a user save files with these names.\nUser agents should do similar sanitization as to how the `download` attribute\nof `<a>` tags is processed in https://html.spec.whatwg.org/multipage/links.html#as-a-download.\n\n## Stakeholder Feedback / Opposition\n\n* Chrome : Positive, authoring this explainer\n* Gecko : No signals\n* WebKit : No signals\n* Web developers : Positive, frequently requested (See #85, #144, #94 and #80.)\n\n## References & acknowledgements\n\nMany thanks for valuable feedback and advice from:\n\nAustin Sullivan, Thomas Steiner\n"
  },
  {
    "path": "security-privacy-questionnaire.md",
    "content": "https://www.w3.org/TR/2019/NOTE-security-privacy-questionnaire-20190523/\n\n### 2.1. What information might this feature expose to Web sites or other parties, and for what purposes is that exposure necessary?\n\nThis 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 `<input type=file>` and `<input type=file webkitdirectory>` today.\n\nHowever 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.\n\n### 2.2. Is this specification exposing the minimum amount of information necessary to power the feature?\n\nYes, 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.\n\n### 2.3. How does this specification deal with personal information or personally-identifiable information or information derived thereof?\n\nNo data is exposed without the user explicitly choosing what files or directories to expose to the web site.\n\n### 2.4. How does this specification deal with sensitive information?\n\nNo 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.\n\n### 2.5. Does this specification introduce new state for an origin that persists across browsing sessions?\n\nYes, 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.\n\nFurthermore, 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.\n\nWebsites 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.\n\nAdditionally, 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.\n\nThe `getUniqueId()` method will require a user agent to persist information (e.g. a salt) to provide unique identifiers for handles which are stable across browsing sessions, but which are invalidated once the user clears storage for the site. This state will not be exposed to the website.\n\nThe `getCloudIdentifiers()` method will request identifiers for a given file/directory handle from a cloud storage provider's sync client (usually an external service/application) and forward these to the requesting website. These identifiers may be stable and cannot be invalidated as part of this API.\n\n### 2.6. What information from the underlying platform, e.g. configuration data, is exposed by this specification to an origin?\n\nAnything 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.\n\nThe `getCloudIdentifiers()` method will request identifiers for a given file/directory handle from a cloud storage provider's sync client (usually an external service/application) and forward these to the requesting website. \nTherefore, the requesting website can enumerate all those sync clients present on the user's machine that sync a file/directory the website has a handle to.\n\n### 2.7. Does this specification allow an origin access to sensors on a user’s device\n\nNo, 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).\n\n### 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.\n\nThe data this specification lets a user expose to an origin is identical to the data exposed via `<input type=file>` and `<input type=file webkitdirectory>`. 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).\n\n### 2.9. Does this specification enable new script execution/loading mechanisms?\n\nNo.\n\n### 2.10. Does this specification allow an origin to access other devices?\n\nNot 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.\n\n### 2.11. Does this specification allow an origin some measure of control over a user agent’s native UI?\n\nThe 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 `<a download=\"foo.ln\">` today.\n\n### 2.12. What temporary identifiers might this this specification create or expose to the web?\n\nThe `getUniqueId()` method will create a temporary unique identifier for a given handle. This ID will become invalid if the user clears storage for the site.\n\n### 2.13. How does this specification distinguish between behavior in first-party and third-party contexts?\n\nIt 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.\n\n### 2.14. How does this specification work in the context of a user agent’s Private Browsing or \"incognito\" mode?\n\nThe 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.\n\n### 2.15. Does this specification have a \"Security Considerations\" and \"Privacy Considerations\" section?\n\nYes.\n\n### 2.16. Does this specification allow downgrading default security characteristics?\n\nNo.\n\n### 2.17. What should this questionnaire have asked?\n\nPerhaps 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.\n"
  },
  {
    "path": "w3c.json",
    "content": " {\n    \"group\":      [80485]\n,   \"contacts\":   [\"marcoscaceres\"]\n,   \"repo-type\":  \"cg-report\"\n}\n"
  }
]