API доступа к файловой системе позволяет веб-приложениям читать или сохранять изменения непосредственно в файлах и папках на устройстве пользователя.
Что такое API доступа к файловой системе?
API доступа к файловой системе позволяет разработчикам создавать мощные веб-приложения, которые взаимодействуют с файлами на локальном устройстве пользователя, например интегрированные среды разработки, фото- и видеоредакторы, текстовые редакторы и т. д. После того как пользователь предоставляет доступ к веб-приложению, этот API позволяет ему читать или сохранять изменения непосредственно в файлах и папках на устройстве пользователя. Помимо чтения и записи файлов, API доступа к файловой системе предоставляет возможность открывать каталог и перечислять его содержимое.
Если вы раньше работали с чтением и записью файлов, многое из того, чем я собираюсь поделиться, будет вам знакомо. В любом случае я советую вам прочитать это, потому что не все системы одинаковы.
API доступа к файловой системе поддерживается большинством браузеров Chromium в Windows, macOS, ChromeOS и Linux. Заметным исключением является Brave, где в настоящее время он доступен только за флагом . Поддержка Android разрабатывается в контексте crbug.com/1011535 .
Использование API доступа к файловой системе
Чтобы продемонстрировать мощь и полезность API доступа к файловой системе, я написал текстовый редактор для одного файла. Он позволяет вам открыть текстовый файл, отредактировать его, сохранить изменения обратно на диск или создать новый файл и сохранить изменения на диске. В этом нет ничего особенного, но его достаточно, чтобы помочь вам понять концепции.
Поддержка браузера
Обнаружение функций
Чтобы узнать, поддерживается ли 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
, содержащий большой двоичный объект. Чтобы получить данные из большого двоичного объекта, вызовите один из его методов ( 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;
});
Запишите файл в локальную файловую систему
В текстовом редакторе существует два способа сохранить файл: «Сохранить» и «Сохранить как» . Сохранить записывает изменения обратно в исходный файл, используя дескриптор файла, полученный ранее. Но команда «Сохранить как» создает новый файл и, следовательно, требует нового дескриптора файла.
Создать новый файл
Чтобы сохранить файл, вызовите 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/idb-keyval@5.0.2/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()
. Пользователь выбирает каталог в средстве выбора, после чего возвращается 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 });
Разрешение пути к элементу в каталоге
При работе с файлами или папками в каталоге может быть полезно определить путь к рассматриваемому элементу. Это можно сделать с помощью метко названного методаsolve 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
интерфейса перетаскивания является "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}`);
}
}
});
Доступ к исходной частной файловой системе
Исходная частная файловая система — это конечная точка хранилища, которая, как следует из названия, является частной по отношению к источнику страницы. Хотя браузеры обычно реализуют это, сохраняя содержимое исходной частной файловой системы где-нибудь на диске, не предполагается, что это содержимое будет доступно пользователю. Точно так же не ожидается, что существуют файлы или каталоги с именами, совпадающими с именами дочерних элементов исходной частной файловой системы. Хотя браузер может создать впечатление, что файлы существуют, внутри него (поскольку это исходная частная файловая система) браузер может хранить эти «файлы» в базе данных или любой другой структуре данных. По сути, если вы используете этот API, не ждите, что созданные файлы совпадут один к одному где-то на жестком диске. Вы можете работать как обычно с исходной частной файловой системой, как только у вас появится доступ к корневому файлу 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 });
Доступ к файлам, оптимизированным для производительности, из исходной частной файловой системы.
Частная файловая система происхождения обеспечивает дополнительный доступ к файлам особого типа, которые высоко оптимизированы для производительности, например, предлагая эксклюзивный доступ на запись к содержимому файла на месте. В Chromium 102 и более поздних версиях в исходной частной файловой системе есть дополнительный метод для упрощения доступа к файлам: 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>
.
Мы разработали библиотеку под названием браузер-fs-access , которая использует API доступа к файловой системе везде, где это возможно, и которая во всех остальных случаях обращается к следующим лучшим вариантам.
Безопасность и разрешения
Команда Chrome разработала и реализовала API доступа к файловой системе, используя основные принципы, определенные в разделе «Управление доступом к мощным функциям веб-платформы» , включая пользовательский контроль и прозрачность, а также пользовательскую эргономику.
Открытие файла или сохранение нового файла
При открытии файла пользователь предоставляет разрешение на чтение файла или каталога с помощью средства выбора файлов. Средство выбора открытого файла можно отобразить с помощью жеста пользователя только в безопасном контексте . Если пользователи передумают, они могут отменить выбор в средстве выбора файлов, и сайт ни к чему не получит доступа. Это то же поведение, что и у элемента <input type="file">
.
Аналогичным образом, когда веб-приложение хочет сохранить новый файл, браузер отображает средство выбора файла сохранения, позволяющее пользователю указать имя и местоположение нового файла. Поскольку они сохраняют новый файл на устройстве (а не перезаписывают существующий файл), средство выбора файлов предоставляет приложению разрешение на запись в файл.
Ограниченные папки
Чтобы защитить пользователей и их данные, браузер может ограничить возможность пользователя сохранять данные в определенных папках, например в папках основной операционной системы, таких как Windows, или в папках библиотеки macOS. Когда это происходит, браузер отображает подсказку и просит пользователя выбрать другую папку.
Изменение существующего файла или каталога
Веб-приложение не может изменить файл на диске без явного разрешения пользователя.
Запрос разрешения
Если пользователь хочет сохранить изменения в файле, к которому он ранее предоставил доступ для чтения, браузер отображает запрос на разрешение, запрашивая разрешение у сайта на запись изменений на диск. Запрос разрешения может быть вызван только жестом пользователя, например нажатием кнопки «Сохранить».
Альтернативно, веб-приложение, редактирующее несколько файлов, например IDE, также может запрашивать разрешение на сохранение изменений во время открытия.
Если пользователь выбирает «Отмена» и не предоставляет доступ для записи, веб-приложение не сможет сохранить изменения в локальном файле. Он должен предоставить пользователю альтернативный метод сохранения своих данных, например, предоставляя возможность «загрузить» файл или сохранить данные в облаке.
Прозрачность
Как только пользователь предоставил веб-приложению разрешение на сохранение локального файла, в адресной строке браузера отображается значок. При нажатии на значок открывается всплывающее окно со списком файлов, к которым пользователь предоставил доступ. Пользователь всегда может отозвать этот доступ, если пожелает.
Сохранение разрешений
Веб-приложение может продолжать сохранять изменения в файле без запроса, пока все вкладки его источника не будут закрыты. После закрытия вкладки сайт теряет всякий доступ. В следующий раз, когда пользователь воспользуется веб-приложением, ему будет повторно предложено предоставить доступ к файлам.
Обратная связь
Мы хотим услышать о вашем опыте работы с API доступа к файловой системе.
Расскажите нам о дизайне API
Что-то в API работает не так, как вы ожидали? Или вам не хватает методов или свойств, необходимых для реализации вашей идеи? У вас есть вопрос или комментарий по модели безопасности?
- Сообщите о проблеме спецификации в репозитории WICG File System Access на GitHub или добавьте свои мысли к существующей проблеме.
Проблема с реализацией?
Вы нашли ошибку в реализации Chrome? Или реализация отличается от спецификации?
- Сообщите об ошибке на https://new.crbug.com . Обязательно включите как можно больше подробностей, инструкции по воспроизведению и установите для параметра «Компоненты» значение
Blink>Storage>FileSystem
. Glitch отлично подходит для обмена быстрыми повторами.
Планируете использовать API?
Планируете использовать API доступа к файловой системе на своем сайте? Ваша публичная поддержка помогает нам расставлять приоритеты для функций и показывает другим поставщикам браузеров, насколько важно их поддерживать.
- Поделитесь, как вы планируете его использовать, в теме обсуждения WICG .
- Отправьте твит @ChromiumDev, используя хэштег
#FileSystemAccess
, и сообщите нам, где и как вы его используете.
Полезные ссылки
- Публичный объяснитель
- Спецификация доступа к файловой системе и спецификация файла
- Ошибка отслеживания
- Запись ChromeStatus.com
- Определения TypeScript
- API доступа к файловой системе — модель безопасности Chromium
- Компонент Blink:
Blink>Storage>FileSystem
Благодарности
Спецификация API доступа к файловой системе была написана Мариной Круссельбринк .
,API доступа к файловой системе позволяет веб-приложениям читать или сохранять изменения непосредственно в файлах и папках на устройстве пользователя.
Что такое API доступа к файловой системе?
API доступа к файловой системе позволяет разработчикам создавать мощные веб-приложения, которые взаимодействуют с файлами на локальном устройстве пользователя, например интегрированные среды разработки, фото- и видеоредакторы, текстовые редакторы и т. д. После того как пользователь предоставляет доступ к веб-приложению, этот API позволяет ему читать или сохранять изменения непосредственно в файлах и папках на устройстве пользователя. Помимо чтения и записи файлов, API доступа к файловой системе предоставляет возможность открывать каталог и перечислять его содержимое.
Если вы раньше работали с чтением и записью файлов, многое из того, чем я собираюсь поделиться, будет вам знакомо. В любом случае я советую вам прочитать это, потому что не все системы одинаковы.
API доступа к файловой системе поддерживается большинством браузеров Chromium в Windows, macOS, ChromeOS и Linux. Заметным исключением является Brave, где в настоящее время он доступен только за флагом . Поддержка Android разрабатывается в контексте crbug.com/1011535 .
Использование API доступа к файловой системе
Чтобы продемонстрировать мощь и полезность API доступа к файловой системе, я написал текстовый редактор для одного файла. Он позволяет вам открыть текстовый файл, отредактировать его, сохранить изменения обратно на диск или создать новый файл и сохранить изменения на диске. В этом нет ничего особенного, но его достаточно, чтобы помочь вам понять концепции.
Поддержка браузера
Обнаружение функций
Чтобы узнать, поддерживается ли 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
, содержащий большой двоичный объект. Чтобы получить данные из большого двоичного объекта, вызовите один из его методов ( 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;
});
Запишите файл в локальную файловую систему
В текстовом редакторе существует два способа сохранить файл: «Сохранить» и «Сохранить как» . Сохранить записывает изменения обратно в исходный файл, используя дескриптор файла, полученный ранее. Но команда «Сохранить как» создает новый файл и, следовательно, требует нового дескриптора файла.
Создать новый файл
Чтобы сохранить файл, вызовите 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/idb-keyval@5.0.2/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()
. Пользователь выбирает каталог в средстве выбора, после чего возвращается 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 });
Разрешение пути к элементу в каталоге
При работе с файлами или папками в каталоге может быть полезно определить путь к рассматриваемому элементу. Это можно сделать с помощью метко названного методаsolve 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
интерфейса перетаскивания является "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}`);
}
}
});
Доступ к исходной частной файловой системе
Исходная частная файловая система — это конечная точка хранилища, которая, как следует из названия, является частной по отношению к источнику страницы. Хотя браузеры обычно реализуют это, сохраняя содержимое исходной частной файловой системы где-нибудь на диске, не предполагается, что это содержимое будет доступно пользователю. Точно так же не ожидается, что существуют файлы или каталоги с именами, совпадающими с именами дочерних элементов исходной частной файловой системы. Хотя браузер может создать впечатление, что файлы существуют, внутри него (поскольку это исходная частная файловая система) браузер может хранить эти «файлы» в базе данных или любой другой структуре данных. По сути, если вы используете этот API, не ждите, что созданные файлы совпадут один к одному где-то на жестком диске. Вы можете работать как обычно с исходной частной файловой системой, как только у вас появится доступ к корневому файлу 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 });
Доступ к файлам, оптимизированным для производительности, из исходной частной файловой системы.
Частная файловая система происхождения обеспечивает дополнительный доступ к файлам особого типа, которые высоко оптимизированы для производительности, например, предлагая эксклюзивный доступ на запись к содержимому файла на месте. В Chromium 102 и более поздних версиях в исходной частной файловой системе есть дополнительный метод для упрощения доступа к файлам: 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>
.
Мы разработали библиотеку под названием браузер-fs-access , которая использует API доступа к файловой системе везде, где это возможно, и которая во всех остальных случаях обращается к следующим лучшим вариантам.
Безопасность и разрешения
Команда Chrome разработала и реализовала API доступа к файловой системе, используя основные принципы, определенные в разделе «Управление доступом к мощным функциям веб-платформы» , включая пользовательский контроль и прозрачность, а также пользовательскую эргономику.
Открытие файла или сохранение нового файла
При открытии файла пользователь предоставляет разрешение на чтение файла или каталога с помощью средства выбора файлов. Средство выбора открытого файла можно отобразить с помощью жеста пользователя только в безопасном контексте . Если пользователи передумают, они могут отменить выбор в средстве выбора файлов, и сайт ни к чему не получит доступа. Это то же поведение, что и у элемента <input type="file">
.
Аналогичным образом, когда веб-приложение хочет сохранить новый файл, браузер отображает средство выбора файла сохранения, позволяющее пользователю указать имя и местоположение нового файла. Поскольку они сохраняют новый файл на устройстве (а не перезаписывают существующий файл), средство выбора файлов предоставляет приложению разрешение на запись в файл.
Ограниченные папки
Чтобы защитить пользователей и их данные, браузер может ограничить возможность пользователя сохранять данные в определенных папках, например в папках основной операционной системы, таких как Windows, или в папках библиотеки macOS. Когда это происходит, браузер отображает подсказку и просит пользователя выбрать другую папку.
Изменение существующего файла или каталога
Веб-приложение не может изменить файл на диске без явного разрешения пользователя.
Запрос разрешения
Если пользователь хочет сохранить изменения в файле, к которому он ранее предоставил доступ для чтения, браузер отображает запрос на разрешение, запрашивая разрешение у сайта на запись изменений на диск. Запрос разрешения может быть вызван только жестом пользователя, например нажатием кнопки «Сохранить».
Альтернативно, веб-приложение, редактирующее несколько файлов, например IDE, также может запрашивать разрешение на сохранение изменений во время открытия.
Если пользователь выбирает «Отмена» и не предоставляет доступ для записи, веб-приложение не сможет сохранить изменения в локальном файле. Он должен предоставить пользователю альтернативный метод сохранения своих данных, например, предоставляя возможность «загрузить» файл или сохранить данные в облаке.
Прозрачность
Как только пользователь предоставил веб-приложению разрешение на сохранение локального файла, в адресной строке браузера отображается значок. При нажатии на значок открывается всплывающее окно со списком файлов, к которым пользователь предоставил доступ. Пользователь всегда может отозвать этот доступ, если пожелает.
Сохранение разрешений
Веб-приложение может продолжать сохранять изменения в файле без запроса, пока все вкладки его источника не будут закрыты. После закрытия вкладки сайт теряет всякий доступ. В следующий раз, когда пользователь воспользуется веб-приложением, ему будет повторно предложено предоставить доступ к файлам.
Обратная связь
Мы хотим услышать о вашем опыте работы с API доступа к файловой системе.
Расскажите нам о дизайне API
Что-то в API работает не так, как вы ожидали? Или вам не хватает методов или свойств, необходимых для реализации вашей идеи? У вас есть вопрос или комментарий по модели безопасности?
- Сообщите о проблеме спецификации в репозитории WICG File System Access на GitHub или добавьте свои мысли к существующей проблеме.
Проблема с реализацией?
Вы нашли ошибку в реализации Chrome? Или реализация отличается от спецификации?
- Сообщите об ошибке на https://new.crbug.com . Обязательно включите как можно больше подробностей, инструкции по воспроизведению и установите для параметра «Компоненты» значение
Blink>Storage>FileSystem
. Glitch отлично подходит для обмена быстрыми повторами.
Планируете использовать API?
Планируете использовать API доступа к файловой системе на своем сайте? Ваша публичная поддержка помогает нам расставлять приоритеты для функций и показывает другим поставщикам браузеров, насколько важно их поддерживать.
- Поделитесь, как вы планируете его использовать, в теме обсуждения WICG .
- Отправьте твит @ChromiumDev, используя хэштег
#FileSystemAccess
, и сообщите нам, где и как вы его используете.
Полезные ссылки
- Публичный объяснитель
- Спецификация доступа к файловой системе и спецификация файла
- Ошибка отслеживания
- Запись ChromeStatus.com
- Определения TypeScript
- API доступа к файловой системе — модель безопасности Chromium
- Компонент Blink:
Blink>Storage>FileSystem
Благодарности
Спецификация API доступа к файловой системе была написана Мариной Круссельбринк .