API доступа к файловой системе: упрощение доступа к локальным файлам

API доступа к файловой системе позволяет веб-приложениям считывать или сохранять изменения непосредственно в файлах и папках на устройстве пользователя.

Что такое API доступа к файловой системе?

API доступа к файловой системе позволяет разработчикам создавать мощные веб-приложения, взаимодействующие с файлами на локальном устройстве пользователя, такие как IDE, фото- и видеоредакторы, текстовые редакторы и многое другое. После того, как пользователь предоставляет доступ к веб-приложению, этот API позволяет ему читать или сохранять изменения непосредственно в файлах и папках на устройстве пользователя. Помимо чтения и записи файлов, API доступа к файловой системе предоставляет возможность открывать каталог и перечислять его содержимое.

Если вы уже работали с чтением и записью файлов, многое из того, чем я собираюсь поделиться, будет вам знакомо. Я рекомендую вам прочитать это в любом случае, потому что не все системы одинаковы.

File System Access API поддерживается большинством браузеров Chromium на Windows, macOS, ChromeOS, Linux и Android. Заметным исключением является Brave, где он в настоящее время доступен только за флагом .

Использование API доступа к файловой системе

Чтобы продемонстрировать мощь и полезность API доступа к файловой системе, я написал текстовый редактор для одного файла. Он позволяет открывать текстовый файл, редактировать его, сохранять изменения на диск или начинать новый файл и сохранять изменения на диск. Ничего особенного, но дает достаточно, чтобы помочь вам понять концепции.

Поддержка браузера

Browser Support

  • Хром: 86.
  • Край: 86.
  • Firefox: не поддерживается.
  • Safari: не поддерживается.

Source

Обнаружение особенностей

Чтобы узнать, поддерживается ли API доступа к файловой системе, проверьте, существует ли интересующий вас метод выбора.

if ('showOpenFilePicker' in self) {
  // The `showOpenFilePicker()` method of the File System Access API is supported.
}

Попробуй это

Посмотрите на API доступа к файловой системе в действии в демонстрационной версии текстового редактора .

Прочитать файл из локальной файловой системы

Первый вариант использования, который я хочу рассмотреть, — это попросить пользователя выбрать файл, а затем открыть и прочитать этот файл с диска.

Попросите пользователя выбрать файл для чтения.

Точкой входа в API доступа к файловой системе является window.showOpenFilePicker() . При вызове он показывает диалоговое окно выбора файлов и предлагает пользователю выбрать файл. После выбора файла API возвращает массив дескрипторов файлов. Необязательный параметр options позволяет влиять на поведение выбора файлов, например, позволяя пользователю выбирать несколько файлов, каталогов или различных типов файлов. Если не указаны никакие параметры, выбор файлов позволяет пользователю выбрать один файл. Это идеально подходит для текстового редактора.

Как и многие другие мощные API, вызов showOpenFilePicker() должен выполняться в безопасном контексте и должен быть вызван из жеста пользователя.

let fileHandle;
butOpenFile.addEventListener('click', async () => {
  // Destructure the one-element array.
  [fileHandle] = await window.showOpenFilePicker();
  // Do something with the file handle.
});

После того как пользователь выбирает файл, showOpenFilePicker() возвращает массив дескрипторов, в данном случае одноэлементный массив с одним FileSystemFileHandle , который содержит свойства и методы, необходимые для взаимодействия с файлом.

Полезно сохранить ссылку на дескриптор файла, чтобы его можно было использовать позже. Он понадобится для сохранения изменений в файле или для выполнения любых других операций с файлом.

Прочитать файл из файловой системы

Теперь, когда у вас есть дескриптор файла, вы можете получить свойства файла или получить доступ к самому файлу. Сейчас я прочитаю его содержимое. Вызов handle.getFile() возвращает объект File , содержащий blob. Чтобы получить данные из blob, вызовите один из его методов ( slice() , stream() , text() или arrayBuffer() ).

const file = await fileHandle.getFile();
const contents = await file.text();

Объект File , возвращаемый FileSystemFileHandle.getFile() , доступен для чтения только до тех пор, пока не изменился базовый файл на диске. Если файл на диске изменен, объект File становится нечитаемым, и вам нужно будет снова вызвать getFile() , чтобы получить новый объект File для чтения измененных данных.

Собираем все вместе

Когда пользователи нажимают кнопку «Открыть» , браузер показывает средство выбора файлов. После выбора файла приложение считывает его содержимое и помещает его в <textarea> .

let fileHandle;
butOpenFile.addEventListener('click', async () => {
  [fileHandle] = await window.showOpenFilePicker();
  const file = await fileHandle.getFile();
  const contents = await file.text();
  textArea.value = contents;
});

Записать файл в локальную файловую систему

В текстовом редакторе есть два способа сохранить файл: Save и Save As . Save записывает изменения обратно в исходный файл, используя полученный ранее дескриптор файла. Но Save As создает новый файл и, следовательно, требует нового дескриптора файла.

Создать новый файл

Чтобы сохранить файл, вызовите showSaveFilePicker() , который показывает средство выбора файлов в режиме «сохранить», позволяя пользователю выбрать новый файл, который он хочет использовать для сохранения. Для текстового редактора я также хотел, чтобы он автоматически добавлял расширение .txt , поэтому я предоставил некоторые дополнительные параметры.

async function getNewFileHandle() {
  const options = {
    types: [
      {
        description: 'Text Files',
        accept: {
          'text/plain': ['.txt'],
        },
      },
    ],
  };
  const handle = await window.showSaveFilePicker(options);
  return handle;
}

Сохранить изменения на диске

Вы можете найти весь код для сохранения изменений в файле в моей демонстрации текстового редактора на GitHub . Основные взаимодействия с файловой системой находятся в fs-helpers.js . В простейшем случае процесс выглядит как следующий код. Я пройдусь по каждому шагу и объясню его.

// fileHandle is an instance of FileSystemFileHandle..
async function writeFile(fileHandle, contents) {
  // Create a FileSystemWritableFileStream to write to.
  const writable = await fileHandle.createWritable();
  // Write the contents of the file to the stream.
  await writable.write(contents);
  // Close the file and write the contents to disk.
  await writable.close();
}

Запись данных на диск использует объект FileSystemWritableFileStream , подкласс WritableStream . Создайте поток, вызвав createWritable() для объекта дескриптора файла. При вызове createWritable() браузер сначала проверяет, предоставил ли пользователь разрешение на запись в файл. Если разрешение на запись не было предоставлено, браузер запрашивает у пользователя разрешение. Если разрешение не предоставлено, createWritable() выдает исключение DOMException , и приложение не сможет записать данные в файл. В текстовом редакторе объекты DOMException обрабатываются в методе saveFile() .

Метод write() принимает строку, которая нужна для текстового редактора. Но он также может принимать BufferSource или Blob . Например, вы можете напрямую передать ему поток:

async function writeURLToFile(fileHandle, url) {
  // Create a FileSystemWritableFileStream to write to.
  const writable = await fileHandle.createWritable();
  // Make an HTTP request for the contents.
  const response = await fetch(url);
  // Stream the response into the file.
  await response.body.pipeTo(writable);
  // pipeTo() closes the destination pipe by default, no need to close it.
}

Вы также можете seek() или truncate() внутри потока, чтобы обновить файл в определенной позиции или изменить размер файла.

Указание предлагаемого имени файла и начального каталога

Во многих случаях вы можете захотеть, чтобы ваше приложение предлагало имя файла или местоположение по умолчанию. Например, текстовый редактор может захотеть предложить имя файла по умолчанию Untitled Text.txt вместо Untitled . Вы можете добиться этого, передав свойство suggestedName как часть параметров showSaveFilePicker .

const fileHandle = await self.showSaveFilePicker({
  suggestedName: 'Untitled Text.txt',
  types: [{
    description: 'Text documents',
    accept: {
      'text/plain': ['.txt'],
    },
  }],
});

То же самое касается и начального каталога по умолчанию. Если вы создаете текстовый редактор, вы можете захотеть запустить диалог сохранения или открытия файла в папке documents по умолчанию, тогда как для редактора изображений вы можете захотеть запустить в папке pictures по умолчанию. Вы можете предложить начальный каталог по умолчанию, передав свойство startIn методам showSaveFilePicker , showDirectoryPicker() или showOpenFilePicker следующим образом.

const fileHandle = await self.showOpenFilePicker({
  startIn: 'pictures'
});

Список известных системных каталогов:

  • desktop : Каталог рабочего стола пользователя, если таковой существует.
  • documents : Каталог, в котором обычно хранятся документы, созданные пользователем.
  • downloads : Каталог, в котором обычно хранятся загруженные файлы.
  • music : Каталог, в котором обычно хранятся аудиофайлы.
  • pictures : Каталог, в котором обычно хранятся фотографии и другие неподвижные изображения.
  • videos : Каталог, где обычно хранятся видео или фильмы.

Помимо известных системных каталогов, вы также можете передать существующий файл или дескриптор каталога в качестве значения для startIn . Диалоговое окно затем откроется в том же каталоге.

// Assume `directoryHandle` is a handle to a previously opened directory.
const fileHandle = await self.showOpenFilePicker({
  startIn: directoryHandle
});

Указание назначения различных средств выбора файлов

Иногда приложения имеют разные средства выбора для разных целей. Например, редактор форматированного текста может позволить пользователю открывать текстовые файлы, а также импортировать изображения. По умолчанию каждое средство выбора файлов будет открываться в последнем запомненном месте. Вы можете обойти это, сохранив значения id для каждого типа средства выбора. Если указан id , реализация средства выбора файлов запоминает отдельный последний использованный каталог для этого id .

const fileHandle1 = await self.showSaveFilePicker({
  id: 'openText',
});

const fileHandle2 = await self.showSaveFilePicker({
  id: 'importImage',
});

Хранение дескрипторов файлов или каталогов в IndexedDB

Дескрипторы файлов и каталогов являются сериализуемыми, что означает, что вы можете сохранить дескриптор файла или каталога в IndexedDB или вызвать postMessage() для их отправки между одним и тем же источником верхнего уровня.

Сохранение дескрипторов файлов или каталогов в IndexedDB означает, что вы можете сохранять состояние или запоминать, с какими файлами или каталогами работал пользователь. Это позволяет сохранять список недавно открытых или отредактированных файлов, предлагать повторное открытие последнего файла при открытии приложения, восстанавливать предыдущий рабочий каталог и многое другое. В текстовом редакторе я сохраняю список из пяти последних файлов, которые открывал пользователь, что позволяет снова получить к ним доступ.

Следующий пример кода показывает сохранение и извлечение дескриптора файла и дескриптора каталога. Вы можете увидеть это в действии на Glitch. (Для краткости я использую библиотеку idb-keyval .)

import { get, set } from 'https://unpkg.com/[email protected]/dist/esm/index.js';

const pre1 = document.querySelector('pre.file');
const pre2 = document.querySelector('pre.directory');
const button1 = document.querySelector('button.file');
const button2 = document.querySelector('button.directory');

// File handle
button1.addEventListener('click', async () => {
  try {
    const fileHandleOrUndefined = await get('file');
    if (fileHandleOrUndefined) {
      pre1.textContent = `Retrieved file handle "${fileHandleOrUndefined.name}" from IndexedDB.`;
      return;
    }
    const [fileHandle] = await window.showOpenFilePicker();
    await set('file', fileHandle);
    pre1.textContent = `Stored file handle for "${fileHandle.name}" in IndexedDB.`;
  } catch (error) {
    alert(error.name, error.message);
  }
});

// Directory handle
button2.addEventListener('click', async () => {
  try {
    const directoryHandleOrUndefined = await get('directory');
    if (directoryHandleOrUndefined) {
      pre2.textContent = `Retrieved directroy handle "${directoryHandleOrUndefined.name}" from IndexedDB.`;
      return;
    }
    const directoryHandle = await window.showDirectoryPicker();
    await set('directory', directoryHandle);
    pre2.textContent = `Stored directory handle for "${directoryHandle.name}" in IndexedDB.`;
  } catch (error) {
    alert(error.name, error.message);
  }
});

Сохраненные дескрипторы файлов или каталогов и разрешения

Поскольку разрешения не всегда сохраняются между сеансами , вам следует проверить, предоставил ли пользователь разрешение на файл или каталог с помощью queryPermission() . Если нет, вызовите requestPermission() чтобы (повторно) запросить его. Это работает одинаково для дескрипторов файлов и каталогов. Вам нужно запустить fileOrDirectoryHandle.requestPermission(descriptor) или fileOrDirectoryHandle.queryPermission(descriptor) соответственно.

В текстовом редакторе я создал метод verifyPermission() , который проверяет, предоставил ли пользователь уже разрешение, и при необходимости делает запрос.

async function verifyPermission(fileHandle, readWrite) {
  const options = {};
  if (readWrite) {
    options.mode = 'readwrite';
  }
  // Check if permission was already granted. If so, return true.
  if ((await fileHandle.queryPermission(options)) === 'granted') {
    return true;
  }
  // Request permission. If the user grants permission, return true.
  if ((await fileHandle.requestPermission(options)) === 'granted') {
    return true;
  }
  // The user didn't grant permission, so return false.
  return false;
}

Запросив разрешение на запись вместе с запросом на чтение, я сократил количество запросов на разрешение; пользователь видит один запрос при открытии файла и предоставляет разрешение как на чтение, так и на запись в него.

Открытие каталога и перечисление его содержимого

Чтобы перечислить все файлы в каталоге, вызовите showDirectoryPicker() . Пользователь выбирает каталог в Picker, после чего возвращается FileSystemDirectoryHandle , который позволяет перечислить и получить доступ к файлам каталога. По умолчанию у вас будет доступ на чтение к файлам в каталоге, но если вам нужен доступ на запись, вы можете передать методу { mode: 'readwrite' } .

butDir.addEventListener('click', async () => {
  const dirHandle = await window.showDirectoryPicker();
  for await (const entry of dirHandle.values()) {
    console.log(entry.kind, entry.name);
  }
});

Если вам дополнительно необходимо получить доступ к каждому файлу с помощью getFile() , например, чтобы получить размеры отдельных файлов, не используйте await для каждого результата последовательно, а вместо этого обрабатывайте все файлы параллельно, например, с помощью Promise.all() .

butDir.addEventListener('click', async () => {
  const dirHandle = await window.showDirectoryPicker();
  const promises = [];
  for await (const entry of dirHandle.values()) {
    if (entry.kind !== 'file') {
      continue;
    }
    promises.push(entry.getFile().then((file) => `${file.name} (${file.size})`));
  }
  console.log(await Promise.all(promises));
});

Создание или доступ к файлам и папкам в каталоге

Из каталога вы можете создавать или получать доступ к файлам и папкам с помощью метода getFileHandle() или, соответственно, метода getDirectoryHandle() . Передавая необязательный объект options с ключом create и логическим значением true или false , вы можете определить, следует ли создавать новый файл или папку, если они не существуют.

// In an existing directory, create a new directory named "My Documents".
const newDirectoryHandle = await existingDirectoryHandle.getDirectoryHandle('My Documents', {
  create: true,
});
// In this new directory, create a file named "My Notes.txt".
const newFileHandle = await newDirectoryHandle.getFileHandle('My Notes.txt', { create: true });

Определение пути к элементу в каталоге

При работе с файлами или папками в каталоге может быть полезно разрешить путь к рассматриваемому элементу. Это можно сделать с помощью метко названного метода resolve() . Для разрешения элемент может быть прямым или косвенным потомком каталога.

// Resolve the path of the previously created file called "My Notes.txt".
const path = await newDirectoryHandle.resolve(newFileHandle);
// `path` is now ["My Documents", "My Notes.txt"]

Удаление файлов и папок в каталоге

Если вы получили доступ к каталогу, вы можете удалить содержащиеся в нем файлы и папки с помощью метода removeEntry() . Для папок удаление может быть опционально рекурсивным и включать все подпапки и содержащиеся в них файлы.

// Delete a file.
await directoryHandle.removeEntry('Abandoned Projects.txt');
// Recursively delete a folder.
await directoryHandle.removeEntry('Old Stuff', { recursive: true });

Удаление файла или папки напрямую

Если у вас есть доступ к дескриптору файла или каталога, вызовите remove() для FileSystemFileHandle или FileSystemDirectoryHandle , чтобы удалить его.

// Delete a file.
await fileHandle.remove();
// Delete a directory.
await directoryHandle.remove();

Переименование и перемещение файлов и папок

Файлы и папки можно переименовывать или перемещать в новое место, вызывая move() в интерфейсе FileSystemHandle . FileSystemHandle имеет дочерние интерфейсы FileSystemFileHandle и FileSystemDirectoryHandle . Метод move() принимает один или два параметра. Первый может быть либо строкой с новым именем, либо FileSystemDirectoryHandle для папки назначения. В последнем случае необязательный второй параметр — это строка с новым именем, поэтому перемещение и переименование могут происходить за один шаг.

// Rename the file.
await file.move('new_name');
// Move the file to a new directory.
await file.move(directory);
// Move the file to a new directory and rename it.
await file.move(directory, 'newer_name');

Интеграция с функцией перетаскивания

Интерфейсы HTML Drag and Drop позволяют веб-приложениям принимать перетаскиваемые файлы на веб-странице. Во время операции перетаскивания перетаскиваемые элементы файлов и каталогов связываются с записями файлов и записями каталогов соответственно. Метод DataTransferItem.getAsFileSystemHandle() возвращает обещание с объектом FileSystemFileHandle , если перетаскиваемый элемент является файлом, и обещание с объектом FileSystemDirectoryHandle , если перетаскиваемый элемент является каталогом. Следующий листинг показывает это в действии. Обратите внимание, что DataTransferItem.kind интерфейса Drag and Drop — это "file" как для файлов, так и для каталогов, тогда как FileSystemHandle.kind API доступа к файловой системе — это "file" для файлов и "directory" для каталогов.

elem.addEventListener('dragover', (e) => {
  // Prevent navigation.
  e.preventDefault();
});

elem.addEventListener('drop', async (e) => {
  e.preventDefault();

  const fileHandlesPromises = [...e.dataTransfer.items]
    .filter((item) => item.kind === 'file')
    .map((item) => item.getAsFileSystemHandle());

  for await (const handle of fileHandlesPromises) {
    if (handle.kind === 'directory') {
      console.log(`Directory: ${handle.name}`);
    } else {
      console.log(`File: ${handle.name}`);
    }
  }
});

Доступ к исходной частной файловой системе

Частная файловая система origin — это конечная точка хранения, которая, как следует из названия, является частной для источника страницы. Хотя браузеры обычно реализуют это, сохраняя содержимое этой частной файловой системы origin на диске где-то, не предполагается, что содержимое будет доступно пользователю. Аналогично, не ожидается, что файлы или каталоги с именами, совпадающими с именами дочерних элементов частной файловой системы origin, существуют. Хотя браузер может создавать видимость наличия файлов, внутренне — поскольку это частная файловая система origin — браузер может хранить эти «файлы» в базе данных или любой другой структуре данных. По сути, если вы используете этот API, не ожидайте найти созданные файлы, совпадающие один к одному, где-то на жестком диске. Вы можете работать как обычно с частной файловой системой origin, как только у вас будет доступ к корневому FileSystemDirectoryHandle .

const root = await navigator.storage.getDirectory();
// Create a new file handle.
const fileHandle = await root.getFileHandle('Untitled.txt', { create: true });
// Create a new directory handle.
const dirHandle = await root.getDirectoryHandle('New Folder', { create: true });
// Recursively remove a directory.
await root.removeEntry('Old Stuff', { recursive: true });

Browser Support

  • Хром: 86.
  • Край: 86.
  • Firefox: 111.
  • Сафари: 15.2.

Source

Доступ к файлам, оптимизированным для производительности, из исходной частной файловой системы

Частная файловая система origin обеспечивает необязательный доступ к особому виду файлов, который высоко оптимизирован для производительности, например, предлагая доступ на месте и эксклюзивный доступ для записи к содержимому файла. В Chromium 102 и более поздних версиях в частной файловой системе origin есть дополнительный метод для упрощения доступа к файлам: createSyncAccessHandle() (для синхронных операций чтения и записи). Он представлен в FileSystemFileHandle , но исключительно в Web Workers .

// (Read and write operations are synchronous,
// but obtaining the handle is asynchronous.)
// Synchronous access exclusively in Worker contexts.
const accessHandle = await fileHandle.createSyncAccessHandle();
const writtenBytes = accessHandle.write(buffer);
const readBytes = accessHandle.read(buffer, { at: 1 });

Полифиллинг

Полностью полифиллить методы API доступа к файловой системе невозможно.

  • Метод showOpenFilePicker() можно аппроксимировать с помощью элемента <input type="file"> .
  • Метод showSaveFilePicker() можно смоделировать с помощью элемента <a download="file_name"> , хотя это запускает программную загрузку и не позволяет перезаписывать существующие файлы.
  • Метод showDirectoryPicker() можно в некоторой степени эмулировать с помощью нестандартного элемента <input type="file" webkitdirectory> .

Мы разработали библиотеку под названием browser-fs-access , которая использует API доступа к файловой системе везде, где это возможно, и прибегает к этим следующим лучшим вариантам во всех остальных случаях.

Безопасность и разрешения

Команда Chrome разработала и реализовала API доступа к файловой системе, используя основные принципы, определенные в документе «Управление доступом к мощным функциям веб-платформы» , включая пользовательский контроль и прозрачность, а также эргономику пользователя.

Открытие файла или сохранение нового файла

Средство выбора файлов для открытия файла для чтения
Средство выбора файлов, используемое для открытия существующего файла для чтения.

При открытии файла пользователь предоставляет разрешение на чтение файла или каталога с помощью средства выбора файлов. Средство выбора открытых файлов может быть показано только с помощью жеста пользователя, если оно обслуживается из безопасного контекста . Если пользователи передумают, они могут отменить выбор в средстве выбора файлов, и сайт не получит доступа ни к чему. Это то же самое поведение, что и у элемента <input type="file"> .

Средство выбора файлов для сохранения файла на диск.
Средство выбора файлов, используемое для сохранения файла на диск.

Аналогично, когда веб-приложение хочет сохранить новый файл, браузер показывает средство выбора файла сохранения, позволяя пользователю указать имя и местоположение нового файла. Поскольку они сохраняют новый файл на устройстве (а не перезаписывают существующий файл), средство выбора файла предоставляет приложению разрешение на запись в файл.

Ограниченные папки

Чтобы защитить пользователей и их данные, браузер может ограничить возможность пользователя сохранять в определенных папках, например, в основных папках операционной системы, таких как Windows, папках библиотеки macOS. Когда это происходит, браузер выводит запрос и просит пользователя выбрать другую папку.

Изменение существующего файла или каталога

Веб-приложение не может изменить файл на диске без получения явного разрешения пользователя.

Запрос на разрешение

Если человек хочет сохранить изменения в файле, к которому он ранее предоставил доступ на чтение, браузер показывает запрос на разрешение, запрашивая разрешение для сайта записывать изменения на диск. Запрос на разрешение может быть вызван только жестом пользователя, например, нажатием кнопки «Сохранить».

Перед сохранением файла отображается запрос на разрешение.
Запрос, показываемый пользователям перед тем, как браузеру будет предоставлено разрешение на запись в существующий файл.

В качестве альтернативы веб-приложение, редактирующее несколько файлов, например IDE, также может запрашивать разрешение на сохранение изменений во время открытия.

Если пользователь выбирает Отмена и не предоставляет доступ на запись, веб-приложение не может сохранить изменения в локальном файле. Оно должно предоставить пользователю альтернативный метод сохранения данных, например, предоставив возможность «загрузить» файл или сохранить данные в облаке.

Прозрачность

Значок омнибокса
Значок адресной строки, указывающий на то, что пользователь предоставил веб-сайту разрешение на сохранение в локальном файле.

После того, как пользователь предоставил веб-приложению разрешение на сохранение локального файла, браузер отображает значок в адресной строке. Нажатие на значок открывает всплывающее окно со списком файлов, к которым пользователь предоставил доступ. Пользователь всегда может отозвать этот доступ, если захочет.

Сохранение разрешения

Веб-приложение может продолжать сохранять изменения в файле без запроса, пока все вкладки для его источника не будут закрыты. После закрытия вкладки сайт теряет весь доступ. В следующий раз, когда пользователь использует веб-приложение, ему будет повторно предложено предоставить доступ к файлам.

Обратная связь

Мы хотим услышать о вашем опыте работы с API доступа к файловой системе.

Расскажите нам о дизайне API

Есть ли что-то в API, что работает не так, как вы ожидали? Или отсутствуют методы или свойства, которые вам нужны для реализации вашей идеи? Есть вопрос или комментарий по модели безопасности?

Проблемы с реализацией?

Вы нашли ошибку в реализации Chrome? Или реализация отличается от спецификации?

  • Сообщите об ошибке по адресу https://new.crbug.com . Обязательно включите как можно больше подробностей, инструкции по воспроизведению и установите для компонентов значение Blink>Storage>FileSystem .

Планируете использовать API?

Планируете использовать API доступа к файловой системе на своем сайте? Ваша публичная поддержка помогает нам расставлять приоритеты функций и показывает другим поставщикам браузеров, насколько важно их поддерживать.

Полезные ссылки

Благодарности

Спецификация API доступа к файловой системе была написана Мариной Круссельбринк .