Interfejs File System Access API: upraszcza dostęp do plików lokalnych

Interfejs File System Access API umożliwia aplikacjom internetowym odczytywanie lub zapisywanie zmian bezpośrednio w plikach i folderach na urządzeniu użytkownika.

Interfejs File System Access API umożliwia programistom tworzenie zaawansowanych aplikacji internetowych, które współdziałają z plikami na lokalnym urządzeniu użytkownika, takich jak IDE, edytory zdjęć i filmów czy edytory tekstu. Gdy użytkownik przyzna aplikacji internetowej dostęp, interfejs API pozwoli mu odczytywać lub zapisywać zmiany bezpośrednio w plikach i folderach na urządzeniu. Oprócz odczytu i zapisu plików interfejs File System Access API umożliwia otwieranie katalogów i wyliczanie ich zawartości.

Jeśli wcześniej pracowałeś/pracowałaś z czytaniem i pisaniem plików, wiele z tych informacji będzie Ci znajomych. Mimo to zachęcam do jej przeczytania, ponieważ nie wszystkie systemy są takie same.

Interfejs API dostępu do systemu plików jest obsługiwany w większości przeglądarek Chromium w systemach Windows, macOS, ChromeOS i Linux. Wyjątkiem jest przeglądarka Brave, w której obecnie ta funkcja jest dostępna tylko za pomocą flagi. Pracujemy nad obsługą Androida w ramach zgłoszenia crbug.com/1011535.

Używanie interfejsu File System Access API

Aby zaprezentować możliwości i przydatność interfejsu File System Access API, napisałem(-am) pojedynczy edytor tekstu dla pojedynczego pliku. Umożliwia otwieranie plików tekstowych, ich edytowanie i zapisywanie zmian na dysku lub tworzenie nowych plików i zapisywanie zmian na dysku. Nie jest to zbyt wymyślne, ale zawiera wystarczającą ilość informacji, aby ułatwić zrozumienie pojęć.

Obsługa przeglądarek

Obsługa przeglądarek

  • Chrome: 86.
  • Edge: 86.
  • Firefox: nieobsługiwane.
  • Safari: nieobsługiwane.

Źródło

Wykrywanie cech

Aby sprawdzić, czy interfejs File System Access API jest obsługiwany, sprawdź, czy interesująca Cię metoda selektora istnieje.

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

Wypróbuj

Zobacz, jak działa interfejs File System Access API w prezentacji edytora tekstu.

Odczytywanie pliku z lokalnego systemu plików

Pierwszą rzeczą, jaką chcę rozwiązać, jest poproszenie użytkownika o wybranie pliku, a następnie otwarcie go i odczyt z dysku.

Poproś użytkownika o wybranie pliku do odczytania

Punkt wejścia do interfejsu File System Access API to: window.showOpenFilePicker(). Gdy zostanie wywołany, wyświetla okno wyboru pliku i prosi użytkownika o wybranie pliku. Po wybraniu pliku interfejs API zwraca tablicę identyfikatorów plików. Opcjonalny parametr options pozwala wpływać na działanie selektora plików, na przykład poprzez umożliwienie użytkownikowi wybrania wielu plików, katalogów lub różnych typów plików. Bez żadnych określonych opcji selektor plików pozwala użytkownikowi wybrać jeden plik. Jest to idealne rozwiązanie dla edytora tekstu.

Podobnie jak w przypadku wielu innych zaawansowanych interfejsów API, wywołanie showOpenFilePicker() musi być wykonane w bezpiecznym kontekście i wywołane w ramach gestu użytkownika.

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

Gdy użytkownik wybierze plik, funkcja showOpenFilePicker() zwraca tablicę uchwytów. W tym przypadku jest to tablica o jednym elemencie z jednym uchwytem FileSystemFileHandle, który zawiera właściwości i metody potrzebne do interakcji z plikiem.

Warto zachować odwołanie do uchwytu pliku, aby móc go użyć później. Będzie ona potrzebna do zapisania zmian w pliku lub wykonania innych operacji na pliku.

Odczytywanie pliku z systemu plików

Teraz, gdy masz uchwyt pliku, możesz uzyskać jego właściwości lub uzyskać dostęp do samego pliku. Na razie przeczytam jego treść. Wywołanie handle.getFile() zwraca obiekt File, który zawiera blob. Aby uzyskać dane z bloba, wywołaj jedną z metod (slice(), stream(), text() lub arrayBuffer()).

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

Obiekt File zwracany przez funkcję FileSystemFileHandle.getFile() jest czytelny tylko wtedy, gdy podstawowy plik na dysku się nie zmienił. Jeśli plik na dysku zostanie zmodyfikowany, obiekt File stanie się nieczytelny i będzie trzeba ponownie wywołać funkcję getFile(), aby uzyskać nowy obiekt File, który odczyta zmienione dane.

Podsumowanie

Gdy użytkownicy klikną przycisk Otwórz, przeglądarka wyświetli selektor plików. Po wybraniu pliku aplikacja odczytuje jego zawartość i przekaże ją do <textarea>.

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

Zapisz plik w lokalnym systemie plików

W edytorze tekstu możesz zapisać plik na dwa sposoby: Zapisz lub Zapisz jako. Kliknięcie Zapisz powoduje zapisanie zmian w oryginalnym pliku z użyciem pobranego wcześniej uchwytu pliku. Opcja Zapisz jako tworzy jednak nowy plik i dlatego wymaga nowego uchwytu.

Utwórz nowy plik

Aby zapisać plik, wywołaj funkcję showSaveFilePicker(), która wyświetla selektor plików w trybie „zapisuj”, umożliwiając użytkownikowi wybranie nowego pliku do zapisania. W przypadku edytora tekstu chciałem też, aby automatycznie dodawał rozszerzenie .txt, więc podałem kilka dodatkowych parametrów.

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

Zapisz zmiany na dysku

Cały kod służący do zapisywania zmian w pliku znajdziesz w mojej wersji demonstracyjnej edytora tekstu na GitHub. Interakcje z głównym systemem plików znajdują się w pliku fs-helpers.js. Najprościej wygląda to tak, jak pokazano poniżej. Omówię każdy krok i wyjaśnię to.

// 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();
}

Do zapisywania danych na dysku wykorzystywany jest obiekt FileSystemWritableFileStream, podklasa klasy WritableStream. Utwórz strumień, wywołując funkcję createWritable() obiektu filehandle. Gdy wywoływana jest funkcja createWritable(), przeglądarka najpierw sprawdza, czy użytkownik przyznał uprawnienia do zapisu do pliku. Jeśli nie przyznano uprawnień do zapisu, przeglądarka poprosi użytkownika o je przyznanie. Jeśli nie przyznasz uprawnień, createWritable() rzuci błąd DOMException, a aplikacja nie będzie mogła zapisać pliku. W edytorze tekstu obiekty DOMException są obsługiwane w metodzie saveFile().

Metoda write() przyjmuje ciąg znaków, który jest potrzebny edytorowi tekstu. Może też jednak obejmować BufferSource lub Blob. Możesz na przykład przesłać dane bezpośrednio do:

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

Możesz też użyć seek() lub truncate() w strumieniu, aby zaktualizować plik w określonej pozycji lub zmienić jego rozmiar.

Podawanie sugerowanej nazwy pliku i katalogu początkowego

W wielu przypadkach może być konieczne, aby aplikacja sugerowała domyślną nazwę pliku lub domyślną lokalizację. Edytor tekstu może na przykład zaproponować domyślną nazwę pliku Untitled Text.txt, a nie Untitled. Aby to zrobić, możesz przekazać właściwość suggestedName jako część opcji showSaveFilePicker.

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

To samo dotyczy domyślnego katalogu startowego. Jeśli tworzysz edytor tekstu, okno zapisywania lub otwierania pliku najlepiej otwierać się w domyślnym folderze documents, a w przypadku edytora obrazów – w domyślnym folderze pictures. Możesz zasugerować domyślny katalog startowy, przekazując właściwość startIn do metod showSaveFilePicker, showDirectoryPicker() lub showOpenFilePicker.

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

Lista znanych katalogów systemowych:

  • desktop: katalog na komputerze użytkownika, jeśli istnieje.
  • documents: katalog, w którym zwykle przechowywane są dokumenty utworzone przez użytkownika.
  • downloads: katalog, w którym zwykle są przechowywane pobrane pliki.
  • music: katalog, w którym zwykle przechowywane są pliki audio.
  • pictures: katalog, w którym zwykle przechowywane są zdjęcia i inne obrazy.
  • videos: katalog, w którym zwykle przechowywane są filmy.

Oprócz znanych katalogów systemowych możesz też przekazać istniejący plik lub uchwyt katalogu jako wartość parametru startIn. Okno otworzy się w tym samym katalogu.

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

Określanie celu różnych selektorów plików

Czasami aplikacje mają różne selektory do różnych celów. Na przykład edytor tekstu sformatowanego może umożliwiać użytkownikowi otwieranie plików tekstowych, ale też importowanie obrazów. Domyślnie każdy selektor plików otwiera się w ostatnio zapamiętanej lokalizacji. Możesz to obejść, przechowując wartości id dla każdego typu selektora. Jeśli podano wartość id, implementacja selektora plików zapamięta osobny ostatnio używany katalog dla tego id.

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

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

przechowywanie uchwytów plików lub katalogów w IndexedDB;

Identyfikatory plików i katalogów można serializować, co oznacza, że możesz zapisać identyfikator pliku lub katalogu w IndexedDB albo wywołać funkcję postMessage(), aby przesłać je między tymi samymi źródłami najwyższego poziomu.

Zapisywanie uchwytów plików lub katalogów w IndexedDB umożliwia przechowywanie stanu lub zapamiętywanie informacji o tym, nad jakimi plikami lub katalogami pracował użytkownik. Dzięki temu możesz zachować listę ostatnio otwieranych lub edytowanych plików, zaoferować ponowne otwarcie ostatniego pliku po uruchomieniu aplikacji, przywrócić poprzedni katalog roboczy i wykonywać inne czynności. W edytorze tekstu przechowuję listę 5 najnowszych plików, które użytkownik otworzył, dzięki czemu można do nich ponownie uzyskać dostęp.

Poniższy przykład kodu pokazuje przechowywanie i pobieranie uchwytu pliku i uchwytu katalogu. Możesz sprawdzić, jak to działa na stronie Glitch. Dla zachowania zwięzłości stosuję bibliotekę 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);
  }
});

Złożone uchwyty i uprawnienia do pliku lub katalogu

Ponieważ uprawnienia nie są zawsze zachowywane między sesjami, należy sprawdzić, czy użytkownik przyznał uprawnienia do pliku lub katalogu za pomocą queryPermission(). Jeśli nie, zadzwoń pod numer requestPermission(), aby poprosić o jego ponowne wysłanie. To samo działa w przypadku uchwytów plików i katalogów. W tym celu musisz uruchomić odpowiednio fileOrDirectoryHandle.requestPermission(descriptor) lub fileOrDirectoryHandle.queryPermission(descriptor).

W edytorze tekstu utworzyłem metodę verifyPermission(), która sprawdza, czy użytkownik już udzielił uprawnień, i w razie potrzeby wysyła żądanie.

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;
}

Poprzez wysłanie żądania uprawnień do zapisu wraz z żądaniem odczytu udało mi się zmniejszyć liczbę wyświetlanych próśb o przyznanie uprawnień. Użytkownik widzi jedno takie żądanie podczas otwierania pliku i przyznaje uprawnienia do odczytu i zapisu.

otwieranie katalogu i wyliczanie jego zawartości.

Aby wyliczyć wszystkie pliki w katalogu, wywołaj funkcję showDirectoryPicker(). Użytkownik wybiera katalog w selektorze, po czym zwracany jest element FileSystemDirectoryHandle, który umożliwia wyliczanie plików katalogu i uzyskiwanie do nich dostępu. Domyślnie masz uprawnienia tylko do odczytu plików w katalogu, ale jeśli potrzebujesz uprawnień do zapisu, możesz przekazać parametr { mode: 'readwrite' } do metody.

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

Jeśli dodatkowo potrzebujesz dostępu do każdego pliku za pomocą funkcji getFile(), aby na przykład uzyskać rozmiary poszczególnych plików, nie używaj funkcji await do poszczególnych wyników sekwencyjnie, ale przetwarzaj wszystkie pliki równolegle, na przykład za pomocą funkcji 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));
});

tworzenie plików i folderów w katalogu lub uzyskiwanie do nich dostępu.

W katalogu możesz tworzyć pliki i foldery oraz uzyskiwać do nich dostęp za pomocą metody getFileHandle() lub odpowiednio getDirectoryHandle(). Przekazując opcjonalny obiekt options z kluczem create i wartością logiczną true lub false, możesz określić, czy w przypadku braku nowego pliku lub folderu należy go utworzyć.

// 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 });

Rozpoznawanie ścieżki elementu w katalogu

Podczas pracy z plikami lub folderami w katalogu może być przydatne określenie ścieżki do danego elementu. Użyj do tego metody o małej nazwie resolve(). Podczas rozwiązywania adresu element może być bezpośrednim lub pośrednim podkatalogiem katalogu.

// 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"]

Usuwanie plików i folderów w katalogu

Jeśli masz dostęp do katalogu, możesz usunąć znajdujące się w nim pliki i foldery za pomocą metody removeEntry(). W przypadku folderów usuwanie może być opcjonalnie rekurencyjne i obejmować wszystkie podfoldery oraz pliki w nich zawarte.

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

bezpośrednie usuwanie pliku lub folderu.

Jeśli masz dostęp do uchwytu pliku lub katalogu, wywołaj remove() w FileSystemFileHandle lub FileSystemDirectoryHandle, aby go usunąć.

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

Zmienianie nazw i przenoszenie plików oraz folderów

Pliki i foldery można przenosić do nowej lokalizacji lub zmieniać ich nazwy, wywołując move() w interfejsie FileSystemHandle. FileSystemHandle ma interfejsy podrzędne FileSystemFileHandle i FileSystemDirectoryHandle. Metoda move() przyjmuje 1 lub 2 parametry. Pierwszy może być ciągiem znaków z nową nazwą lub FileSystemDirectoryHandle do folderu docelowego. W tym drugim przypadku opcjonalny drugi parametr to ciąg znaków z nową nazwą, dzięki czemu przenoszenie i zmiana nazwy mogą nastąpić w jednym kroku.

// 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');

Integracja polegająca na przeciąganiu i upuszczaniu

Interfejsy HTML do przeciągania i upuszczania umożliwiają aplikacjom internetowym akceptowanie przeciągniętych i upuszczonych plików na stronie internetowej. Podczas operacji przeciągania i upuszczania przeciągnięte pliki i elementy katalogu są powiązane odpowiednio z rekordami plików i rekordami katalogów. Metoda DataTransferItem.getAsFileSystemHandle() zwraca obietnicę z obiektem FileSystemFileHandle, jeśli przeciągany element jest plikiem, oraz obietnicę z obiektem FileSystemDirectoryHandle, jeśli przeciągany element jest katalogiem. Poniżej znajdziesz przykładowy kod. Pamiętaj, że interfejs przeciągania i upuszczaniaDataTransferItem.kind jest"file" zarówno w przypadku plików, jak i katalogów, a FileSystemHandle.kind interfejsu File System Access API jest"file" w przypadku plików i "directory" w przypadku katalogów.

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}`);
    }
  }
});

Dostęp do prywatnego systemu plików źródłowego

Prywatny system plików źródła to punkt końcowy pamięci masowej, który, jak sama nazwa wskazuje, jest prywatny dla źródła strony. Chociaż przeglądarki zwykle wdrażają to, zachowując w jakimś miejscu zawartość prywatnego systemu plików źródła, nie jest to wymagane, aby były one dostępne dla użytkowników. Podobnie nie oczekuje się, że istnieją pliki lub katalogi o nazwach pasujących do nazw podrzędnych elementów prywatnego systemu plików źródłowego. Chociaż przeglądarka może sprawiać wrażenie, że są to pliki, to wewnętrznie – ponieważ jest to prywatny system plików pochodzenia – przeglądarka może przechowywać te „pliki” w bazie danych lub innej strukturze danych. Jeśli korzystasz z tego interfejsu API, nie oczekuj, że utworzone pliki będą znajdować się w jednej parze na dysku twardym. Gdy masz dostęp do katalogu głównego FileSystemDirectoryHandle, możesz normalnie korzystać z prywatnego systemu plików źródłowego.

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 });

Obsługa przeglądarek

  • Chrome: 86.
  • Edge: 86.
  • Firefox: 111.
  • Safari: 15.2.

Źródło

uzyskiwać dostęp do plików zoptymalizowanych pod kątem wydajności z prywatnego systemu plików punktu początkowego;

Prywatny system plików źródła zapewnia opcjonalny dostęp do specjalnego rodzaju pliku, który jest w wysokim stopniu zoptymalizowany pod kątem wydajności, np. oferując dostęp do zapisu na miejscu i wyłącznie do zawartości pliku. W Chromium 102 i nowszych wersjach w systemie plików prywatnych pochodzenia jest dostępna dodatkowa metoda upraszczająca dostęp do plików: createSyncAccessHandle() (do synchronicznych operacji odczytu i zapisu). Jest ona dostępna na FileSystemFileHandle, ale tylko w elementach Worker.

// (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 });

wypełnianie treścią zastępczą,

Nie można całkowicie zastąpić metod interfejsu File System Access API.

  • Metodę showOpenFilePicker() można zastąpić przybliżeniem za pomocą elementu <input type="file">.
  • Metodę showSaveFilePicker() można symulować za pomocą elementu <a download="file_name">, ale powoduje to wywołanie pobierania programowego i nie pozwala na zastąpienie istniejących plików.
  • Metodę showDirectoryPicker() można w pewnym stopniu emulować za pomocą niestandardowego elementu <input type="file" webkitdirectory>.

Opracowaliśmy bibliotekę o nazwie browser-fs-access, która w miarę możliwości korzysta z interfejsu File System Access API. We wszystkich innych przypadkach korzysta z naszych najlepszych opcji.

Zabezpieczenia i uprawnienia

Zespół Chrome zaprojektował i wdrożył interfejs File System Access API, korzystając z podstawowych zasad określonych w artykule Kontrolowanie dostępu do zaawansowanych funkcji platformy internetowej, w tym kontroli i przejrzystości użytkownika oraz ergonomii.

Otwieranie pliku lub zapisywanie nowego

Okno wyboru plików do otwarcia pliku do odczytu
Wybór pliku służący do otwarcia istniejącego pliku do odczytu.

Podczas otwierania pliku użytkownik przyznaje uprawnienia do odczytu pliku lub katalogu za pomocą selektora plików. Wybór pliku do otwarcia może być wyświetlany tylko za pomocą gestu użytkownika, gdy jest wyświetlany w bezpiecznym kontekście. Jeśli użytkownik zmieni zdanie, może anulować wybór w selektorze plików, a witryna nie uzyska dostępu do niczego. Jest to takie samo działanie jak w przypadku elementu <input type="file">.

Selektor plików umożliwiający zapisanie pliku na dysku.
Selektor plików służący do zapisywania pliku na dysku.

Podobnie, gdy aplikacja internetowa chce zapisać nowy plik, przeglądarka wyświetla selektor zapisywania pliku, który umożliwia użytkownikowi określenie nazwy i lokalizacji nowego pliku. Ponieważ użytkownik zapisuje nowy plik na urządzeniu (a nie zastępuje istniejącego pliku), selektor plików przyznaje aplikacji uprawnienia do zapisu do pliku.

Foldery z ograniczeniami

Aby chronić użytkowników i ich dane, przeglądarka może ograniczyć możliwość zapisywania ich do niektórych folderów, na przykład podstawowych folderów systemu operacyjnego, takich jak Windows, czy folderów biblioteki macOS. W takim przypadku przeglądarka wyświetla prośbę o wybranie innego folderu.

Modyfikowanie dotychczasowego pliku lub katalogu

Aplikacja internetowa nie może modyfikować pliku na dysku bez wyraźnej zgody użytkownika.

Prośba o uprawnienia

Jeśli użytkownik chce zapisać zmiany w pliku, do którego wcześniej przyznał dostęp do odczytu, przeglądarka wyświetla prośbę o uprawnienia, aby strona mogła zapisać zmiany na dysku. Prośba o uprawnienia może zostać wywołana tylko przez użytkownika, na przykład przez kliknięcie przycisku Zapisz.

Prośba o zgodę jest wyświetlana przed zapisaniem pliku.
Pytanie wyświetlane użytkownikom przed przyznaniem przeglądarce uprawnień do zapisu w dotychczasowym pliku.

Aplikacja internetowa, która edytuje wiele plików, np. IDE, może też poprosić o uprawnienia do zapisywania zmian już podczas otwierania.

Jeśli użytkownik wybierze Anuluj i nie przyzna uprawnień do zapisu, aplikacja internetowa nie będzie mogła zapisać zmian w pliku lokalnym. Powinien on zawierać alternatywną metodę zapisywania danych, na przykład „pobieranie” pliku lub zapisywanie danych w chmurze.

Przejrzystość

Ikona omniboksu
ikona na pasku adresu wskazująca, że użytkownik przyznał witrynie uprawnienia do zapisywania w pliku lokalnym.

Gdy użytkownik przyzna aplikacji internetowej uprawnienia do zapisywania pliku lokalnego, przeglądarka wyświetli ikonę na pasku adresu. Kliknięcie ikony powoduje wyświetlenie wyskakującego okienka z listą plików, do których użytkownik ma dostęp. Użytkownik może w każdej chwili cofnąć ten dostęp.

Trwałość uprawnień

Aplikacja internetowa może zapisywać zmiany w pliku bez pytania do momentu zamknięcia wszystkich kart jego pochodzenia. Po zamknięciu karty witryna traci dostęp do wszystkich zasobów. Gdy użytkownik następnym razem użyje aplikacji internetowej, ponownie zostanie poproszony o dostęp do plików.

Prześlij opinię

Chcemy poznać Twoje wrażenia z korzystania z interfejsu File System Access API.

Prześlij informacje o projektowaniu interfejsu API

Czy coś w interfejsie API nie działa zgodnie z oczekiwaniami? A może brakuje metod lub właściwości, których potrzebujesz do wdrożenia swojego pomysłu? Masz pytania lub uwagi dotyczące modelu bezpieczeństwa?

Problem z implementacją?

Czy wystąpił błąd związany z implementacją Chrome? A może implementacja różni się od specyfikacji?

  • Zgłoś błąd na stronie https://new.crbug.com. Podaj jak najwięcej szczegółów, instrukcje odtwarzania błędu i ustaw wartość Components na Blink>Storage>FileSystem. Glitch świetnie sprawdza się do udostępniania szybkich reprosów.

Planujesz korzystać z interfejsu API?

Zamierzasz używać w swojej witrynie interfejsu File System Access API? Twoje publiczne wsparcie pomaga nam ustalać priorytety funkcji i pokazuje innym dostawcom przeglądarek, jak ważne jest dla nich wsparcie.

Przydatne linki

Podziękowania

Specyfikację File System Access API napisał Marijn Kruisselbrink.