A API File System Access permite que apps da Web leiam ou salvem mudanças diretamente em arquivos e pastas no dispositivo do usuário.
Publicado em: 19 de agosto de 2024
A API File System Access permite que os desenvolvedores criem apps da Web avançados que interagem com arquivos no dispositivo local do usuário, como IDEs, editores de fotos e vídeos, editores de texto e muito mais. Depois que um usuário concede acesso a um web app, essa API permite que ele leia ou salve mudanças diretamente em arquivos e pastas no dispositivo do usuário. Além de ler e gravar arquivos, a API File System Access permite abrir um diretório e enumerar o conteúdo dele.
Se você já trabalhou com leitura e gravação de arquivos, muito do que vou compartilhar será familiar. Recomendamos que você leia mesmo assim, porque nem todos os sistemas são iguais.
A API File System Access é compatível com a maioria dos navegadores Chromium no Windows, macOS, ChromeOS, Linux e Android. Uma exceção notável é o Brave, que atualmente só está disponível por trás de uma flag.
Usar a API File System Access
Para mostrar o poder e a utilidade da API File System Access, escrevi um editor de texto de arquivo único. Ele permite abrir um arquivo de texto, editá-lo, salvar as mudanças de volta no disco ou iniciar um novo arquivo e salvar as mudanças no disco. Não é nada sofisticado, mas oferece o suficiente para ajudar você a entender os conceitos.
Suporte ao navegador
Detecção de recursos
Para saber se a API File System Access é compatível, verifique se o método de seletor de seu interesse existe.
if ('showOpenFilePicker' in self) {
// The `showOpenFilePicker()` method of the File System Access API is supported.
}
Testar
Confira a API File System Access em ação na demonstração do editor de texto.
Ler um arquivo do sistema de arquivos local
O primeiro caso de uso que quero abordar é pedir ao usuário para escolher um arquivo e depois abrir e ler esse arquivo do disco.
Pedir que o usuário escolha um arquivo para ler
O ponto de entrada da API File System Access é window.showOpenFilePicker(). Quando chamado, ele mostra uma caixa de diálogo de seleção de arquivos
e pede que o usuário selecione um arquivo. Depois que o usuário seleciona um arquivo, a API retorna uma matriz de identificadores de arquivo. Um parâmetro options opcional permite influenciar o comportamento do seletor de arquivos, por exemplo, permitindo que o usuário selecione vários arquivos, diretórios ou tipos de arquivos diferentes.
Sem opções especificadas, o seletor de arquivos permite que o usuário selecione um único arquivo. Isso é
perfeito para um editor de texto.
Assim como muitas outras APIs avançadas, a chamada de showOpenFilePicker() precisa ser feita em um contexto seguro e de dentro de um gesto do usuário.
let fileHandle;
butOpenFile.addEventListener('click', async () => {
// Destructure the one-element array.
[fileHandle] = await window.showOpenFilePicker();
// Do something with the file handle.
});
Quando o usuário seleciona um arquivo, showOpenFilePicker() retorna uma matriz de identificadores, neste caso, uma matriz de um elemento com um FileSystemFileHandle que contém as propriedades e os métodos necessários para interagir com o arquivo.
É útil manter uma referência ao identificador de arquivo para que ele possa ser usado mais tarde. Ele será necessário para salvar as mudanças no arquivo ou realizar outras operações.
Ler um arquivo do sistema de arquivos
Agora que você tem um identificador de arquivo, é possível acessar as propriedades dele ou o próprio arquivo.
Por enquanto, vou ler o conteúdo. Chamar handle.getFile() retorna um objeto File
que contém um blob. Para receber os dados do blob, chame um dos métodos
dele (slice(),
stream(),
text() ou
arrayBuffer()).
const file = await fileHandle.getFile();
const contents = await file.text();
O objeto File retornado por FileSystemFileHandle.getFile() só pode ser lido enquanto o arquivo
subjacente no disco não for alterado. Se o arquivo no disco for modificado, o objeto File ficará
ilegível, e você precisará chamar getFile() novamente para receber um novo objeto File e ler os dados
alterados.
Como tudo funciona em conjunto
Quando os usuários clicam no botão Abrir, o navegador mostra um seletor de arquivos. Depois que um arquivo é selecionado, o
app lê o conteúdo e o coloca em um <textarea>.
let fileHandle;
butOpenFile.addEventListener('click', async () => {
[fileHandle] = await window.showOpenFilePicker();
const file = await fileHandle.getFile();
const contents = await file.text();
textArea.value = contents;
});
Grave o arquivo no sistema de arquivos local.
No editor de texto, há duas maneiras de salvar um arquivo: Salvar e Salvar como. Save grava as mudanças de volta no arquivo original usando o identificador de arquivo recuperado anteriormente. Mas Salvar como cria um novo arquivo e, portanto, exige um novo identificador de arquivo.
Criar um novo arquivo
Para salvar um arquivo, chame showSaveFilePicker(), que mostra o seletor de arquivos
no modo "salvar", permitindo que o usuário escolha um novo arquivo para salvar. Para o editor de texto, também queria que ele adicionasse automaticamente uma extensão .txt, então forneci alguns parâmetros adicionais.
async function getNewFileHandle() {
const options = {
types: [
{
description: 'Text Files',
accept: {
'text/plain': ['.txt'],
},
},
],
};
const handle = await window.showSaveFilePicker(options);
return handle;
}
Salvar alterações no disco
Todo o código para salvar mudanças em um arquivo está na demonstração do meu editor de texto no
GitHub. As principais interações do sistema de arquivos estão em
fs-helpers.js. Na forma mais simples, o processo se parece com o código a seguir.
Vou explicar cada etapa.
// 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();
}
A gravação de dados no disco usa um objeto FileSystemWritableFileStream, uma subclasse de WritableStream. Crie o fluxo chamando createWritable() no objeto de
manipulador de arquivo. Quando createWritable() é chamado, o navegador primeiro verifica se o usuário concedeu
permissão de gravação ao arquivo. Se a permissão de gravação não tiver sido concedida, o navegador vai pedir
autorização ao usuário. Se a permissão não for concedida, createWritable() vai gerar uma
DOMException, e o app não poderá gravar no arquivo. No editor de texto, os objetos
DOMException são processados no método saveFile().
O método write() usa uma string, que é o que um editor de texto precisa. Mas também pode usar uma BufferSource ou um Blob. Por exemplo, é possível canalizar um stream diretamente para ele:
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.
}
Você também pode seek() ou truncate() no stream para atualizar o
arquivo em uma posição específica ou redimensioná-lo.
Especificar um nome de arquivo e um diretório inicial sugeridos
Em muitos casos, talvez você queira que o app sugira um nome de arquivo ou local padrão. Por exemplo, um editor de texto pode sugerir um nome de arquivo padrão de Untitled Text.txt em vez de Untitled. Para isso, transmita uma propriedade suggestedName como parte das opções showSaveFilePicker.
const fileHandle = await self.showSaveFilePicker({
suggestedName: 'Untitled Text.txt',
types: [{
description: 'Text documents',
accept: {
'text/plain': ['.txt'],
},
}],
});
O mesmo vale para o diretório inicial padrão. Se você estiver criando um editor de texto, talvez queira
iniciar a caixa de diálogo de salvar ou abrir arquivo na pasta documents padrão. Já para um editor de
imagens, talvez queira iniciar na pasta pictures padrão. Você pode sugerir um diretório de início padrão transmitindo uma propriedade startIn para os métodos showSaveFilePicker, showDirectoryPicker() ou showOpenFilePicker, assim:
const fileHandle = await self.showOpenFilePicker({
startIn: 'pictures'
});
A lista de diretórios de sistema conhecidos é:
desktop: o diretório da área de trabalho do usuário, se existir.documents: diretório em que os documentos criados pelo usuário normalmente são armazenados.downloads: diretório onde os arquivos baixados normalmente são armazenados.music: diretório onde os arquivos de áudio normalmente são armazenados.pictures: diretório onde fotos e outras imagens estáticas normalmente são armazenadas.videos: diretório onde vídeos ou filmes normalmente são armazenados.
Além dos diretórios de sistema conhecidos, também é possível transmitir um arquivo ou um identificador de diretório como
um valor para startIn. A caixa de diálogo será aberta no mesmo diretório.
// Assume `directoryHandle` is a handle to a previously opened directory.
const fileHandle = await self.showOpenFilePicker({
startIn: directoryHandle
});
Especificar a finalidade de diferentes seletores de arquivos
Às vezes, os aplicativos têm seletores diferentes para fins distintos. Por exemplo, um editor de texto
formatado pode permitir que o usuário abra arquivos de texto, mas também importe imagens. Por padrão, cada seletor de arquivos
seria aberto no último local lembrado. Para contornar isso, armazene valores id para cada tipo de seletor. Se um id for especificado, a implementação do seletor de arquivos vai lembrar um
diretório separado usado por último para esse id.
const fileHandle1 = await self.showSaveFilePicker({
id: 'openText',
});
const fileHandle2 = await self.showSaveFilePicker({
id: 'importImage',
});
Armazenar identificadores de arquivos ou diretórios no IndexedDB
Os identificadores de arquivos e diretórios são serializáveis, o que significa que você pode salvar um identificador de arquivo ou
diretório no IndexedDB ou chamar postMessage() para enviá-los entre a mesma origem
de nível superior.
Salvar arquivos ou diretórios no IndexedDB significa que você pode armazenar o estado ou lembrar em quais arquivos ou diretórios um usuário estava trabalhando. Isso permite manter uma lista de arquivos abertos ou editados recentemente, oferecer a reabertura do último arquivo quando o app é aberto, restaurar o diretório de trabalho anterior e muito mais. No editor de texto, armazeno uma lista dos cinco arquivos mais recentes que o usuário abriu, permitindo que ele acesse esses arquivos novamente.
O exemplo de código a seguir mostra como armazenar e recuperar um identificador de arquivo e um identificador de diretório. Você pode ver isso em ação no Glitch. (Uso a biblioteca idb-keyval para simplificar.)
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);
}
});
Permissões e identificadores de arquivos ou diretórios armazenados
Como as permissões nem sempre são mantidas entre as sessões, verifique se o usuário concedeu permissão ao arquivo ou diretório usando queryPermission(). Se não tiverem, ligue para
requestPermission() e (re)solicite. Isso funciona da mesma forma para identificadores de arquivos e diretórios. Você
precisa executar fileOrDirectoryHandle.requestPermission(descriptor) ou
fileOrDirectoryHandle.queryPermission(descriptor), respectivamente.
No editor de texto, criei um método verifyPermission() que verifica se o usuário já
concedeu permissão e, se necessário, faz a solicitação.
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;
}
Ao solicitar permissão de gravação com a solicitação de leitura, reduzi o número de solicitações de permissão. O usuário vê uma solicitação ao abrir o arquivo e concede permissão para ler e gravar nele.
Abrir um diretório e enumerar o conteúdo dele
Para enumerar todos os arquivos em um diretório, chame showDirectoryPicker(). O usuário
seleciona um diretório em um seletor. Depois disso, um FileSystemDirectoryHandle é
retornado, permitindo enumerar e acessar os arquivos do diretório. Por padrão, você terá acesso de leitura aos arquivos no diretório, mas se precisar de acesso de gravação, transmita { mode: 'readwrite' } ao método.
butDir.addEventListener('click', async () => {
const dirHandle = await window.showDirectoryPicker();
for await (const entry of dirHandle.values()) {
console.log(entry.kind, entry.name);
}
});
Se você também precisar acessar cada arquivo usando getFile() para, por exemplo, obter os tamanhos individuais dos arquivos, não use await em cada resultado sequencialmente. Em vez disso, processe todos os arquivos em paralelo, por exemplo, usando 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));
});
Criar ou acessar arquivos e pastas em um diretório
Em um diretório, é possível criar ou acessar arquivos e pastas usando o método
getFileHandle() ou getDirectoryHandle(), respectivamente. Ao transmitir um objeto options opcional com uma chave de create e um valor booleano de
true ou false, é possível determinar se um novo arquivo ou pasta precisa ser criado caso não exista.
// 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 });
Como resolver o caminho de um item em um diretório
Ao trabalhar com arquivos ou pastas em um diretório, pode ser útil resolver o caminho do item
em questão. Isso pode ser feito com o método resolve(). Para resolução, o item pode ser um filho direto ou indireto do diretório.
// 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"]
Excluir arquivos e pastas em um diretório
Se você tiver acesso a um diretório, poderá excluir os arquivos e pastas contidos nele com o método
removeEntry(). Para pastas, a exclusão pode ser recursiva e incluir todas as subpastas e os arquivos contidos nelas.
// Delete a file.
await directoryHandle.removeEntry('Abandoned Projects.txt');
// Recursively delete a folder.
await directoryHandle.removeEntry('Old Stuff', { recursive: true });
Excluir um arquivo ou uma pasta diretamente
Se você tiver acesso a um identificador de arquivo ou diretório, chame remove() em um FileSystemFileHandle ou
FileSystemDirectoryHandle para remover.
// Delete a file.
await fileHandle.remove();
// Delete a directory.
await directoryHandle.remove();
Renomear e mover arquivos e pastas
Para renomear ou mover arquivos e pastas para um novo local, chame move() na interface FileSystemHandle. FileSystemHandle tem as interfaces filhas FileSystemFileHandle e FileSystemDirectoryHandle. O método move() usa um ou dois parâmetros. O primeiro pode ser uma string com o novo nome ou um FileSystemDirectoryHandle para a pasta de destino. No último caso, o segundo parâmetro opcional é uma string com o novo nome. Assim, a movimentação e a mudança de nome podem acontecer em uma única etapa.
// 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');
Integração de arrastar e soltar
As interfaces de arrastar e soltar HTML permitem que aplicativos da Web aceitem arquivos arrastados e soltos em uma página da Web. Durante uma operação de arrastar e soltar, os itens de arquivo e diretório arrastados são associados
a entradas de arquivo e entradas de diretório, respectivamente. O método DataTransferItem.getAsFileSystemHandle()
retorna uma promessa com um objeto FileSystemFileHandle se o item arrastado for um arquivo e uma
promessa com um objeto FileSystemDirectoryHandle se o item arrastado for um diretório. A listagem a seguir mostra isso em ação. A interface de arrastar e soltar DataTransferItem.kind é "file" para arquivos e diretórios, enquanto FileSystemHandle.kind da API File System Access é "file" para arquivos e "directory" para diretórios.
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}`);
}
}
});
Como acessar o sistema de arquivos privado de origem
O sistema de arquivos privado de origem é um endpoint de armazenamento que, como o nome sugere, é particular à origem da página. Embora os navegadores geralmente implementem isso persistindo o conteúdo desse
sistema de arquivos privado de origem em algum lugar do disco, não é esperado que o conteúdo seja acessível
ao usuário. Da mesma forma, não há expectativa de que existam arquivos ou diretórios com nomes correspondentes aos nomes de filhos do sistema de arquivos privados de origem. Embora o navegador possa fazer parecer que há arquivos, internamente, como esse é um sistema de arquivos privado de origem, o navegador pode armazenar esses "arquivos" em um banco de dados ou qualquer outra estrutura de dados. Basicamente, se você usar essa API, não espere encontrar os arquivos criados correspondendo um a um em algum lugar do disco rígido. Você pode operar normalmente no
sistema de arquivos privado de origem depois de ter acesso ao FileSystemDirectoryHandle raiz.
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 });
Acessar arquivos otimizados para performance do sistema de arquivos privado de origem
O sistema de arquivos privado de origem oferece acesso opcional a um tipo especial de arquivo altamente otimizado para desempenho, por exemplo, oferecendo acesso de gravação exclusivo e no local ao conteúdo de um arquivo. No Chromium 102 e versões mais recentes, há um método adicional no sistema de arquivos particular da origem para
simplificar o acesso a arquivos: createSyncAccessHandle() (para operações de leitura e gravação síncronas).
Ele é exposto em FileSystemFileHandle, mas exclusivamente em
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 });
Polyfilling
Não é possível fazer um polyfill completo dos métodos da API File System Access.
- O método
showOpenFilePicker()pode ser aproximado com um elemento<input type="file">. - O método
showSaveFilePicker()pode ser simulado com um elemento<a download="file_name">, embora isso acione um download programático e não permita a substituição de arquivos existentes. - O método
showDirectoryPicker()pode ser emulado com o elemento<input type="file" webkitdirectory>não padrão.
Desenvolvemos uma biblioteca chamada browser-fs-access (link em inglês) que usa a API File System Access sempre que possível e que volta para essas próximas melhores opções em todos os outros casos.
Segurança e permissões
A equipe do Chrome projetou e implementou a API File System Access usando os princípios básicos definidos em Controlling Access to Powerful Web Platform Features (em inglês), incluindo controle e transparência do usuário, além de ergonomia.
Abrir ou salvar um arquivo
Ao abrir um arquivo, o usuário dá permissão para ler um arquivo ou diretório usando o seletor de arquivos.
O seletor de arquivos aberto só pode ser mostrado usando um gesto do usuário quando veiculado em um contexto seguro. Se os usuários mudarem de ideia, poderão cancelar a seleção no seletor de arquivos, e o site não terá acesso a nada. Esse é o mesmo comportamento do elemento
<input type="file">.
Da mesma forma, quando um web app quer salvar um novo arquivo, o navegador mostra o seletor de arquivos para salvar, permitindo que o usuário especifique o nome e o local do novo arquivo. Como eles estão salvando um novo arquivo no dispositivo (em vez de substituir um arquivo existente), o seletor de arquivos concede ao app permissão para gravar no arquivo.
Pastas restritas
Para ajudar a proteger os usuários e os dados deles, o navegador pode limitar a capacidade de salvar em determinadas pastas, por exemplo, pastas principais do sistema operacional, como Windows e macOS Library. Quando isso acontece, o navegador mostra uma solicitação e pede que o usuário escolha outra pasta.
Modificar um arquivo ou diretório
Um app da Web não pode modificar um arquivo no disco sem permissão explícita do usuário.
Solicitação de permissão
Se uma pessoa quiser salvar mudanças em um arquivo a que concedeu acesso de leitura, o navegador vai mostrar uma solicitação de permissão para que o site grave as mudanças no disco. A solicitação de permissão só pode ser acionada por um gesto do usuário, por exemplo, clicando em um botão "Salvar".
Como alternativa, um app da Web que edita vários arquivos, como um ambiente de desenvolvimento integrado (IDE, na sigla em inglês), também pode pedir permissão para salvar mudanças no momento da abertura.
Se o usuário escolher "Cancelar" e não conceder acesso de gravação, o web app não poderá salvar as mudanças no arquivo local. Ele precisa oferecer um método alternativo para o usuário salvar os dados, por exemplo, oferecendo uma maneira de fazer o download do arquivo ou salvar os dados na nuvem.
Transparência
Depois que um usuário concede permissão a um web app para salvar um arquivo local, o navegador mostra um ícone na barra de endereço. Ao clicar no ícone, um pop-over é aberto mostrando a lista de arquivos a que o usuário deu acesso. O usuário pode revogar esse acesso a qualquer momento.
Permanência de permissões
O web app pode continuar salvando as mudanças no arquivo sem pedir permissão até que todas as guias da origem sejam fechadas. Depois que uma guia é fechada, o site perde todo o acesso. Na próxima vez que o usuário usar o app da Web, ele vai precisar conceder acesso aos arquivos novamente.
Feedback
Queremos saber sobre suas experiências com a API File System Access.
Fale sobre o design da API
Há algo na API que não funciona como esperado? Ou há métodos ou propriedades ausentes que você precisa implementar sua ideia? Tem uma dúvida ou um comentário sobre o modelo de segurança?
- Registre um problema de especificação no repositório do GitHub de acesso ao sistema de arquivos do WICG ou adicione suas ideias a um problema existente.
Problemas com a implementação?
Você encontrou um bug na implementação do Chrome? Ou a implementação é diferente da especificação?
- Registre um bug em https://new.crbug.com. Inclua o máximo de detalhes possível, instruções para reprodução e defina Componentes como
Blink>Storage>FileSystem.
Você planeja usar a API?
Você planeja usar a API File System Access no seu site? Seu apoio público nos ajuda a priorizar recursos e mostra a outros fornecedores de navegadores a importância de oferecer suporte a eles.
- Compartilhe como você planeja usar isso na thread do WICG Discourse.
- Envie um tweet para @ChromiumDev usando a hashtag
#FileSystemAccesse conte para nós onde e como você está usando.
Links úteis
- Explicação para o público
- Especificação de acesso ao sistema de arquivos e especificação de arquivo
- Bug de rastreamento
- Entrada do ChromeStatus.com
- Definições do TypeScript
- API File System Access: modelo de segurança do Chromium
- Componente Blink:
Blink>Storage>FileSystem
Agradecimentos
A especificação da API File System Access foi escrita por Marijn Kruisselbrink.