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

Interfejs File System Access API pozwala aplikacjom internetowym odczytywać lub zapisywać zmiany bezpośrednio w plikach i folderach na urządzeniu użytkownika.

Czym jest interfejs File System Access API?

Interfejs File System Access API (dawniej Native File System API, ale wcześniej nosił nazwę „Writeable Files API”) umożliwia programistom tworzenie zaawansowanych aplikacji internetowych, które współpracują z plikami na lokalnym urządzeniu użytkownika. Należą do nich IDE, edytory zdjęć i filmów, edytory tekstu i inne. Gdy użytkownik przyzna dostęp aplikacji internetowej, ten interfejs API umożliwia mu odczytywanie lub zapisywanie zmian bezpośrednio w plikach i folderach na urządzeniu użytkownika. Interfejs File System Access API to nie tylko odczyt i zapis plików, ale też umożliwia otwarcie katalogu i wyliczenie jego zawartości.

Jeśli masz już doświadczenie z czytaniem i pisaniem plików, wiele z tych rzeczy, które teraz udostępnię, będzie Ci już znane. Mimo to zachęcam do zapoznania się z jego treścią, ponieważ nie wszystkie systemy są takie same.

Interfejs File System Access API jest obecnie obsługiwany w większości przeglądarek Chromium w systemach Windows, macOS, ChromeOS i Linux. Ważnym wyjątkiem jest Brave, która jest obecnie dostępna tylko po oznaczeniu flagi. Od wersji Chromium 109 Android obsługuje część prywatnego systemu plików źródła w interfejsie API. Obecnie nie planujemy zastosowania metod selektora, ale możesz śledzić potencjalne postępy, oznaczając gwiazdką crbug.com/1011535.

Korzystanie z interfejsu File System Access API

Aby pokazać możliwości i przydatność interfejsu File System Access API, napisałam edytor tekstu z jednym plikiem. Pozwala otworzyć plik tekstowy, edytować go, zapisać z powrotem na dysku lub uruchomić nowy plik i zapisać zmiany na dysku. Nie jest to nic wymyślnego, ale wystarczająco dużo, aby pomóc w zrozumieniu poszczególnych koncepcji.

Obsługiwane przeglądarki

Obsługa przeglądarek

  • 86
  • 86
  • x
  • x

Źródło

Wypróbuj

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

Odczyt pliku z lokalnego systemu plików

Pierwszym przypadkiem użycia, z jakim chcę się zapoznać, jest proszenie użytkownika o wybranie pliku, a następnie otwarcie go i odczytanie go z dysku.

Poproś użytkownika o wybranie pliku do przeczytania

Punkt wejścia do interfejsu File System Access API to window.showOpenFilePicker(). Po wywołaniu wyświetla się okno wyboru plików z prośbą o wybranie pliku. Po wybraniu pliku interfejs API zwraca tablicę uchwytów pliku. Opcjonalny parametr options pozwala wpływać na działanie selektora plików, np. umożliwiając użytkownikowi wybranie wielu plików, katalogów lub różnych typów plików. Jeśli nie określono żadnych opcji, selektor plików umożliwia użytkownikowi wybranie pojedynczego pliku. Idealna dla edytora tekstu.

Podobnie jak wiele innych zaawansowanych interfejsów API, wywołanie showOpenFilePicker() musi odbywać się w bezpiecznym kontekście i może być wywoływane gestem 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, showOpenFilePicker() zwraca tablicę uchwytów, w tym przypadku jednoelementową tablica z jednym elementem FileSystemFileHandle, który zawiera właściwości i metody wymagane do interakcji z plikiem.

Warto mieć odniesienie do uchwytu pliku, aby móc go później użyć. Konieczne jest zapisanie zmian w pliku lub wykonanie innych operacji.

Odczyt pliku z systemu plików

Po utworzeniu nicka dla pliku możesz uzyskać dostęp do jego właściwości lub uzyskać dostęp do samego pliku. Na razie przeczytam jego zawartość. Wywołanie handle.getFile() zwraca obiekt File, który zawiera obiekt blob. Aby pobrać dane z obiektu blob, wywołaj jedną z jej metod (slice(), stream(), text() lub arrayBuffer()).

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

Obiekt File zwrócony przez FileSystemFileHandle.getFile() jest czytelny tylko wtedy, gdy znajdujący się na dysku podstawowy plik nie uległ zmianie. Jeśli plik na dysku zostanie zmodyfikowany, obiekt File stanie się nieczytelny i konieczne będzie ponowne wywołanie metody getFile(), aby uzyskać nowy obiekt File umożliwiający odczytanie zmienionych danych.

Podsumowanie

Gdy użytkownik kliknie przycisk Otwórz, w przeglądarce pojawi się selektor plików. Po wybraniu pliku aplikacja odczytuje jego zawartość i umieszcza w polu <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 2 sposoby: Zapisz i Zapisz jako. Zapisz po prostu zapisuje zmiany w pierwotnym pliku przy użyciu pobranego wcześniej uchwytu pliku. Opcja Zapisz jako tworzy jednak nowy plik i wymaga nowego uchwytu.

Utwórz nowy plik

Aby zapisać plik, wywołaj funkcję showSaveFilePicker(), która wyświetla selektor plików w trybie „zapisania”, dzięki czemu użytkownik może wybrać nowy plik, którego chce użyć do zapisania. Chciałem też, aby edytor tekstu automatycznie dodawał rozszerzenie .txt, więc wprowadził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 do zapisywania zmian w pliku znajdziesz w mojej wersji demonstracyjnej edytora tekstu na GitHub. Najważniejsze interakcje z systemem plików znajdują się tutaj: fs-helpers.js. Najprościej wygląda to tak, jak kod poniżej. Przeprowadzę każdy krok i wyjaśnię, na czym polega problem.

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

Zapisywanie danych na dysku używa obiektu FileSystemWritableFileStream, który jest podklasą WritableStream. Utwórz strumień, wywołując createWritable() w obiekcie uchwytu pliku. Po wywołaniu createWritable() przeglądarka najpierw sprawdza, czy użytkownik przyznał uprawnienia do zapisu w pliku. Jeśli nie przyznano uprawnień do zapisu, przeglądarka poprosi o nie użytkownika. Jeśli nie przyznasz uprawnień, createWritable() zwróci DOMException, a aplikacja nie będzie mogła zapisywać się w pliku. W edytorze tekstu obiekty DOMException są obsługiwane w metodzie saveFile().

Metoda write() pobiera ciąg znaków, który jest wymagany przez edytor tekstu. Może też jednak pobierać obiekt BufferSource lub Blob. Możesz na przykład użyć potoku pionowo umieszczonego bezpośrednio w strumieniu:

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ć skrótu seek() lub truncate() w strumieniu, aby zaktualizować plik w określonej pozycji lub zmienić jego rozmiar.

Określanie sugerowanej nazwy pliku i katalogu początkowego

W wielu przypadkach aplikacja może sugerować domyślną nazwę pliku lub lokalizację. Na przykład edytor tekstu może zasugerować domyślną nazwę pliku Untitled Text.txt, a nie Untitled. Możesz to zrobić, przekazując właściwość suggestedName w ramach 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 początkowego. Jeśli tworzysz edytor tekstu, czasem okno zapisywania lub otwierania pliku będzie się zaczynać w domyślnym folderze documents, natomiast w przypadku edytora obrazów najlepiej zacząć od domyślnego folderu pictures. Możesz zasugerować domyślny katalog początkowy, przekazując właściwość startIn do metod showSaveFilePicker, showDirectoryPicker() lub showOpenFilePicker.

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

Lista dobrze znanych katalogów systemowych:

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

Oprócz dobrze znanych katalogów systemowych możesz też przekazywać jako wartość właściwości startIn istniejący uchwyt pliku lub katalogu. 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 przeznaczenia różnych selektora 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 także importowanie obrazów. Domyślnie każdy selektor plików otwiera się w ostatniej zapamiętanej lokalizacji. Możesz to obejść, przechowując wartości id dla każdego typu selektora. Jeśli jest określony id, implementacja selektora plików pamięta osobny katalog ostatnio używany dla tego zasobu id.

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

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

Uchwyty plików przechowywania lub uchwyty katalogów w IndexedDB

Nicki plików i uchwyty katalogów można serializować, co oznacza, że możesz zapisać uchwyt pliku lub katalogu w IndexedDB albo wywołać postMessage(), aby wysłać je między tym samym źródłem najwyższego poziomu.

Zapisanie nicków plików lub katalogów w IndexedDB oznacza, że możesz przechowywać stan lub pamiętać, nad którymi plikami lub katalogami pracował użytkownik. Dzięki temu można między innymi przechowywać listę ostatnio otwieranych lub edytowanych plików, zaproponować ponowne otwarcie ostatniego pliku po otwarciu aplikacji czy przywrócić poprzedni katalog roboczy. W edytorze tekstu zapisuję listę 5 ostatnich plików otwartych przez użytkownika, co ułatwia powrót do nich.

Przykładowy kod poniżej pokazuje przechowywanie i pobieranie uchwytu pliku oraz uchwytu katalogu. Możesz zobaczyć to w praktyce w Glitchu. (dla zwięzłości używam biblioteki 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);
  }
});

Uchwyty i uprawnienia przechowywanych plików lub katalogów

Ponieważ uprawnienia nie są obecnie zachowywane między sesjami, należy sprawdzić, czy użytkownik przyznał uprawnienia do pliku lub katalogu za pomocą funkcji queryPermission(). Jeśli nie, zadzwoń pod numer requestPermission(), aby (ponownie) poprosić o dostęp. Działa to tak samo w przypadku uchwytów plików i katalogów. Musisz uruchomić odpowiednio fileOrDirectoryHandle.requestPermission(descriptor) lub fileOrDirectoryHandle.queryPermission(descriptor).

Utworzyłem w edytorze tekstu metodę verifyPermission(), która sprawdza, czy użytkownik udzielił już odpowiednich 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;
}

Wysyłając prośbę o uprawnienia do zapisu razem z prośbą o odczyt, zmniejszyłem liczbę próśb o przyznanie uprawnień. Po otwarciu pliku użytkownik widzi jeden komunikat i przyznaje mu uprawnienia zarówno do odczytu, jak i zapisu.

Otwieranie katalogu i wyliczenie jego zawartości

Aby wymienić wszystkie pliki w katalogu, wywołaj funkcję showDirectoryPicker(). Użytkownik wybiera w selektorze katalog, po czym zwracany jest element FileSystemDirectoryHandle, który pozwala wyliczyć pliki katalogu i uzyskać do nich dostęp. Domyślnie będziesz mieć uprawnienia do odczytu plików w katalogu, ale jeśli potrzebujesz uprawnień do zapisu, możesz przekazać metody { mode: 'readwrite' }.

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

Jeśli musisz dodatkowo uzyskać dostęp do każdego pliku za pomocą getFile(), aby np. uzyskać dostęp do poszczególnych rozmiarów plików, nie używaj pola await w przypadku każdego wyniku po kolei, ale przetwarzaj wszystkie pliki równolegle, np. za pomocą Promise.all().

const butDir = document.getElementById('butDirectory');
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 oraz uzyskiwanie do nich dostępu;

Z poziomu katalogu możesz tworzyć pliki i foldery oraz uzyskiwać do nich dostęp za pomocą metody getFileHandle() lub odpowiednio metody getDirectoryHandle(). Przekazując opcjonalny obiekt options z kluczem o wartości create i wartością logiczną true lub false, możesz określić, czy nowy plik lub folder nie istnieje.

// 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ć pomocne znalezienie ścieżki do odpowiedniego elementu. Użyj do tego metody resolve() o odpowiedniej nazwie. Element może być bezpośrednim lub pośrednim elementem podrzędnym 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 z katalogu

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

// 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() na FileSystemFileHandle lub FileSystemDirectoryHandle, aby go usunąć.

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

zmienianie nazw oraz przenoszenie plików i folderów;

Aby zmienić nazwę plików i folderów lub przenieść je do nowej lokalizacji, wywołaj move() w interfejsie FileSystemHandle. FileSystemHandle ma interfejsy podrzędne FileSystemFileHandle i FileSystemDirectoryHandle. Metoda move() przyjmuje 1 lub 2 parametry. Pierwszym z nich może być ciąg o nowej nazwie lub FileSystemDirectoryHandle prowadzący do folderu docelowego. W drugim przypadku opcjonalny drugi parametr to ciąg znaków z nową nazwą, więc przeniesienie i zmianę nazwy może 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 metodą „przeciągnij i upuść”

Interfejsy HTML typu „przeciągnij i upuść” umożliwiają aplikacjom internetowym akceptowanie przeciąganych i upuszczonych plików na stronie internetowej. Podczas przeciągania i upuszczania przeciągnięte elementy pliku i katalogu są powiązane odpowiednio z wpisami pliku i katalogu. Metoda DataTransferItem.getAsFileSystemHandle() zwraca obietnicę z obiektem FileSystemFileHandle, jeśli przeciągnięty element jest plikiem, oraz obietnicę z obiektem FileSystemDirectoryHandle, jeśli przeciągnięty element jest katalogiem. Poniższy wykaz pokazuje, jak to działa. Pamiętaj, że interfejs DataTransferItem.kind interfejsu typu „przeciągnij i upuść” ma wartość "file" dla plików oraz katalogów, podczas gdy interfejs File System Access API FileSystemHandle.kind to "file" dla plików i "directory" dla 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ła

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. Przeglądarki zazwyczaj implementują to, przechowując zawartość prywatnego systemu plików źródła na dysku, ale nie zapewnia to, że treści są łatwo dostępne dla użytkowników. Analogicznie nie oczekuje się, że istnieją pliki lub katalogi o nazwach odpowiadających nazwom podrzędnymi w prywatnym systemie plików źródła. Przeglądarka może sprawiać wrażenie, że istnieją pliki, ale ponieważ jest to prywatny system plików źródła, przeglądarka może przechowywać te „pliki” w bazie danych lub w innej strukturze danych. Jeśli używasz tego interfejsu API, nie spodziewaj się, że utworzone pliki będą dopasowane do jednego miejsca na dysku twardym. Gdy będziesz mieć dostęp do katalogu głównego FileSystemDirectoryHandle, możesz normalnie działać w prywatnym systemie plików origin.

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

  • 86
  • 86
  • 111
  • 15.2

Źródło

Uzyskiwanie dostępu do plików zoptymalizowanych pod kątem wydajności z prywatnego systemu plików źródła

Prywatny system plików źródła zapewnia opcjonalny dostęp do specjalnego rodzaju pliku, który jest wysoce zoptymalizowany pod kątem wydajności, np. przez lokalny i wyłączny dostęp do zawartości pliku. W Chromium 102 i nowszych dostępna jest dodatkowa metoda w prywatnym systemie plików źródła, która ułatwia dostęp do plików: createSyncAccessHandle() (do synchronicznych operacji odczytu i zapisu). Widoczny w FileSystemFileHandle, ale tylko w sektorach roboczych.

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

Polyfill

Nie można całkowicie wypełnić metod interfejsu File System Access API.

  • Metoda showOpenFilePicker() jest dostępna w przybliżeniu za pomocą elementu <input type="file">.
  • Metodę showSaveFilePicker() można symulować za pomocą elementu <a download="file_name">, ale wywołuje to automatyczne pobieranie i nie pozwala na zastąpienie istniejących plików.
  • Metoda showDirectoryPicker() może być nieco emulowana przez niestandardowy element <input type="file" webkitdirectory>.

Opracowaliśmy bibliotekę o nazwie browser-fs-access, która wszędzie tam, gdzie jest to możliwe, korzysta z interfejsu File System Access API. W pozostałych przypadkach korzysta ona z tych najlepszych opcji.

Zabezpieczenia i uprawnienia

Zespół Chrome zaprojektował i wdrożył interfejs File System Access API zgodnie z podstawowymi zasadami określonymi w artykule Kontrolowanie dostępu do zaawansowanych funkcji platformy internetowej, w tym z kontrolą i przejrzystością działania użytkowników oraz ergonomią użytkownika.

Otwieranie pliku lub zapisywanie nowego

Selektor plików, który umożliwia otwarcie pliku do czytania
Selektor plików używany do otwierania istniejącego pliku do odczytu.

Otwierając plik, użytkownik przyznaje uprawnienia do odczytu pliku lub katalogu za pomocą selektora plików. Otwarty selektor plików można wyświetlić za pomocą gestu użytkownika tylko w bezpiecznym kontekście. Jeśli użytkownik zmieni zdanie, może anulować wybór w selektorze plików, a witryna nie uzyska do niczego dostępu. Działa to tak samo 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, w przeglądarce pojawia się selektor zapisu, który umożliwia użytkownikowi określenie nazwy i lokalizacji nowego pliku. Ponieważ zapisuje on nowy plik na urządzeniu (a nie zastępuje istniejący plik), selektor plików przyznaje aplikacji uprawnienia do zapisu w pliku.

Foldery z ograniczeniami

Aby chronić użytkowników i ich dane, przeglądarka może ograniczać możliwość zapisywania plików do określonych 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 komunikat z prośbą o wybranie innego folderu.

Modyfikowanie istniejącego pliku lub katalogu

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

Prośba o zgodę

Jeśli użytkownik chce zapisać zmiany w pliku, do którego wcześniej przyznał jej uprawnienia do odczytu, przeglądarka wyświetli prośbę o uprawnienia z prośbą o pozwolenie witrynie na zapisywanie zmian na dysku. Prośba o uprawnienia może być aktywowana tylko gestem użytkownika, np. kliknięciem przycisku Zapisz.

Przed zapisaniem pliku wyświetla się prośba o przyznanie uprawnień.
Monit wyświetlany użytkownikom przed przyznaniem przeglądarce uprawnień do zapisu w istniejącym pliku.

Aplikacja internetowa, która edytuje wiele plików, np. IDE, może też poprosić o uprawnienia do zapisywania zmian w momencie 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. Aplikacja powinna zapewniać użytkownikowi alternatywną metodę zapisywania danych, np. „pobranie” pliku czy zapisanie danych w chmurze.

Przejrzystość

Ikona omniboksu
Ikona omniboksu wskazująca, że użytkownik przyznał witrynie uprawnienia do zapisu w pliku lokalnym.

Gdy użytkownik przyzna aplikacji internetowej uprawnienia do zapisywania pliku lokalnego, na pasku adresu przeglądarki pojawi się ikona. Kliknięcie ikony otwiera wyskakujące okienko z listą plików, do których użytkownik ma dostęp. Użytkownik może łatwo odebrać ten dostęp.

Trwałość uprawnień

Aplikacja internetowa może kontynuować zapisywanie zmian w pliku bez pytania, dopóki wszystkie karty z jej źródła nie zostaną zamknięte. Po zamknięciu karty strona całkowicie traci dostęp. Gdy następnym razem użytkownik skorzysta z aplikacji internetowej, zostanie ponownie poproszony o dostęp do plików.

Prześlij opinię

Chętnie poznamy Twoją opinię o korzystaniu z interfejsu File System Access API.

Opowiedz nam o projekcie interfejsu API

Czy jest coś, co nie działa w interfejsie API zgodnie z oczekiwaniami? A może brakuje metod lub właściwości, które potrzebujesz do realizacji swojego pomysłu? Masz pytanie lub komentarz na temat modelu bezpieczeństwa?

Problem z implementacją?

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

  • Zgłoś błąd na https://new.crbug.com. Podaj jak najwięcej szczegółów i proste instrukcje odtwarzania oraz ustaw Komponenty na Blink>Storage>FileSystem. Usterki to świetny sposób na udostępnianie szybkich i łatwych replik.

Planujesz korzystać z interfejsu API?

Planujesz używać w witrynie interfejsu File System Access API? Twoja publiczna pomoc pomoże nam nadać priorytet funkcjom i pokazać innym dostawcom przeglądarek, jak ważne jest ich wsparcie.

Przydatne linki

Podziękowania

Specyfikację interfejsu File System Access API opublikowała Marijn Kruisselbrink.