API File System Access: simplifier l'accès aux fichiers locaux

L'API File System Access permet aux applications Web de lire ou d'enregistrer les modifications directement dans les fichiers et les dossiers de l'appareil de l'utilisateur.

Qu'est-ce que l'API File System Access ?

L'API File System Access (anciennement appelée API Native File System et avant cela appelée "Writeable Files") permet aux développeurs de créer des applications Web performantes qui interagissent avec les fichiers de l'appareil local de l'utilisateur, telles que des IDE, des éditeurs de photos et de vidéos, des éditeurs de texte, etc. Une fois qu'un utilisateur a accordé l'accès à une application Web, cette API lui permet de lire ou d'enregistrer les modifications directement dans les fichiers et les dossiers de son appareil. En plus de lire et d'écrire des fichiers, l'API File System Access permet d'ouvrir un répertoire et d'en énumérer le contenu.

Si vous avez déjà travaillé avec la lecture et l'écriture de fichiers, une grande partie de ce que je vais vous partager vous sera familière. Je vous encourage quand même à le lire, car tous les systèmes ne se ressemblent pas.

L'API File System Access est actuellement compatible avec la plupart des navigateurs Chromium sous Windows, macOS, ChromeOS et Linux. Une exception notable est Brave, où il n'est actuellement disponible que derrière un drapeau. Android est compatible avec la partie système de fichiers privés d'origine de l'API à partir de la version 109 de Chromium. Il n'y a actuellement aucun plan pour les méthodes de sélection, mais vous pouvez suivre leur progression potentielle en ajoutant crbug.com/1011535 aux favoris.

Utiliser l'API File System Access

Pour montrer la puissance et l'utilité de l'API File System Access, j'ai écrit un seul éditeur de texte pour les fichiers. Elle vous permet d'ouvrir un fichier texte, de le modifier, d'enregistrer les modifications sur le disque, ou de démarrer un nouveau fichier et d'enregistrer les modifications sur le disque. Il n'est pas sophistiqué, mais fournit suffisamment de données pour vous aider à comprendre les concepts.

Prise en charge des navigateurs

Navigateurs pris en charge

  • 86
  • 86
  • x
  • x

Source

Essayer

Observez l'API File System Access en action dans la démonstration de l'éditeur de texte.

Lire un fichier à partir du système de fichiers local

Le premier cas d'utilisation que je souhaite aborder consiste à demander à l'utilisateur de choisir un fichier, puis de l'ouvrir et de le lire à partir du disque.

Demandez à l'utilisateur de choisir un fichier à lire

Le point d'entrée de l'API File System Access est window.showOpenFilePicker(). Lorsqu'il est appelé, il affiche une boîte de dialogue de sélection de fichier et invite l'utilisateur à sélectionner un fichier. Après avoir sélectionné un fichier, l'API affiche un tableau de handle de fichiers. Un paramètre options facultatif vous permet d'influencer le comportement du sélecteur de fichier, par exemple en permettant à l'utilisateur de sélectionner plusieurs fichiers, répertoires ou différents types de fichiers. Si aucune option n'est spécifiée, le sélecteur de fichier permet à l'utilisateur de sélectionner un seul fichier. C'est parfait pour un éditeur de texte.

Comme de nombreuses autres API puissantes, l'appel de showOpenFilePicker() doit être effectué dans un contexte sécurisé et depuis un geste de l'utilisateur.

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

Une fois que l'utilisateur a sélectionné un fichier, showOpenFilePicker() renvoie un tableau de poignées, dans ce cas un tableau à un élément avec un FileSystemFileHandle contenant les propriétés et les méthodes nécessaires pour interagir avec le fichier.

Il est utile de conserver une référence à l'identifiant du fichier afin de pouvoir l'utiliser ultérieurement. Vous en aurez besoin pour enregistrer les modifications apportées au fichier ou pour effectuer d'autres opérations sur le fichier.

Lire un fichier à partir du système de fichiers

Maintenant que vous disposez d'un handle vers un fichier, vous pouvez obtenir les propriétés du fichier ou accéder au fichier lui-même. Pour l’instant, je vais simplement lire son contenu. L'appel de handle.getFile() renvoie un objet File contenant un blob. Pour obtenir les données du blob, appelez l'une de ses méthodes (slice(), stream(), text() ou arrayBuffer()).

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

L'objet File renvoyé par FileSystemFileHandle.getFile() n'est lisible que tant que le fichier sous-jacent sur le disque n'a pas changé. Si le fichier sur le disque est modifié, l'objet File devient illisible et vous devez appeler à nouveau getFile() pour obtenir un nouvel objet File afin de lire les données modifiées.

Synthèse

Lorsque les utilisateurs cliquent sur le bouton Ouvrir, le navigateur affiche un sélecteur de fichier. Une fois le fichier sélectionné, l'application lit le contenu et le place dans un <textarea>.

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

Écrire le fichier dans le système de fichiers local

Dans l'éditeur de texte, vous pouvez enregistrer un fichier de deux façons: Enregistrer et Enregistrer sous. La commande Save (Enregistrer) réécrit simplement les modifications dans le fichier d'origine à l'aide de la poignée de fichier récupérée précédemment. En revanche, Save As (Enregistrer sous) crée un fichier et nécessite donc un nouveau handle de fichier.

Créer un fichier

Pour enregistrer un fichier, appelez showSaveFilePicker(). Le sélecteur de fichier s'affiche alors en mode "Enregistrer", ce qui permet à l'utilisateur de choisir un nouveau fichier qu'il souhaite enregistrer. Pour l'éditeur de texte, je voulais également qu'il ajoute automatiquement une extension .txt. J'ai donc fourni quelques paramètres supplémentaires.

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

Enregistrer les modifications sur le disque

Vous trouverez tout le code permettant d'enregistrer des modifications dans un fichier dans ma démo de l'éditeur de texte sur GitHub. Les principales interactions du système de fichiers se trouvent dans fs-helpers.js. Dans sa forme la plus simple, le processus ressemble à l'exemple de code ci-dessous. Je vais passer en revue chaque étape et vous l'expliquer.

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

L'écriture de données sur le disque utilise un objet FileSystemWritableFileStream, une sous-classe de WritableStream. Créez le flux en appelant createWritable() sur l'objet de handle de fichier. Lorsque createWritable() est appelé, le navigateur vérifie d'abord si l'utilisateur a accordé une autorisation d'écriture sur le fichier. Si l'autorisation d'écriture n'a pas été accordée, le navigateur invite l'utilisateur à le faire. Si l'autorisation n'est pas accordée, createWritable() génère une erreur DOMException, et l'application ne pourra pas écrire dans le fichier. Dans l'éditeur de texte, les objets DOMException sont gérés dans la méthode saveFile().

La méthode write() utilise une chaîne, ce qui est nécessaire pour un éditeur de texte. Toutefois, elle peut également utiliser un objet BufferSource ou Blob. Par exemple, vous pouvez diriger un flux directement vers celui-ci:

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

Vous pouvez également seek() ou truncate() dans le flux pour mettre à jour le fichier à une position spécifique, ou redimensionner le fichier.

Spécifier un nom de fichier suggéré et un répertoire de départ

Dans de nombreux cas, vous souhaiterez peut-être que votre application suggère un nom ou un emplacement par défaut pour les fichiers. Par exemple, un éditeur de texte peut suggérer un nom de fichier par défaut Untitled Text.txt au lieu de Untitled. Pour ce faire, transmettez une propriété suggestedName dans les options showSaveFilePicker.

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

Il en va de même pour le répertoire de démarrage par défaut. Si vous créez un éditeur de texte, vous pouvez lancer la boîte de dialogue d'enregistrement ou d'ouverture de fichier dans le dossier documents par défaut, tandis que pour un éditeur d'images, vous pouvez commencer dans le dossier pictures par défaut. Vous pouvez suggérer un répertoire de départ par défaut en transmettant une propriété startIn aux méthodes showSaveFilePicker, showDirectoryPicker() ou showOpenFilePicker, comme ceci.

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

La liste des répertoires système connus est la suivante:

  • desktop: répertoire du bureau de l'utilisateur, le cas échéant.
  • documents: répertoire dans lequel les documents créés par l'utilisateur sont généralement stockés.
  • downloads: répertoire dans lequel les fichiers téléchargés sont généralement stockés.
  • music: répertoire dans lequel les fichiers audio sont généralement stockés.
  • pictures: répertoire dans lequel les photos et autres images fixes sont généralement stockées.
  • videos: répertoire dans lequel les vidéos/films seraient généralement stockés.

Outre les répertoires système connus, vous pouvez également transmettre un gestionnaire de fichiers ou de répertoires existant en tant que valeur pour startIn. La boîte de dialogue s'ouvrira alors dans le même répertoire.

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

Spécifier la finalité des différents sélecteurs de fichiers

Parfois, les applications ont différents sélecteurs pour des objectifs différents. Par exemple, un éditeur de texte enrichi peut permettre à l'utilisateur d'ouvrir des fichiers texte, mais également d'importer des images. Par défaut, chaque sélecteur de fichier s'ouvre au dernier emplacement mémorisé. Vous pouvez contourner ce problème en stockant des valeurs id pour chaque type de sélecteur. Si un id est spécifié, l'implémentation du sélecteur de fichier mémorise un dernier répertoire distinct utilisé pour ce id.

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

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

Stocker des handle de fichier ou de répertoire dans IndexedDB

Les handle de fichier et de répertoire sont sérialisables, ce qui signifie que vous pouvez enregistrer un handle de fichier ou de répertoire dans IndexedDB, ou appeler postMessage() pour les envoyer entre la même origine de premier niveau.

Enregistrer des identifiants de fichiers ou de répertoires dans IndexedDB signifie que vous pouvez stocker l'état ou vous souvenir des fichiers ou des répertoires sur lesquels un utilisateur travaillait. Cela permet de conserver une liste des fichiers récemment ouverts ou modifiés, de proposer de rouvrir le dernier fichier à l'ouverture de l'application, de restaurer le répertoire de travail précédent, etc. Dans l'éditeur de texte, je stocke une liste des cinq fichiers les plus récents que l'utilisateur a ouverts, ce qui permet d'y accéder facilement.

L'exemple de code ci-dessous montre comment stocker et récupérer un handle de fichier et un handle de répertoire. Vous pouvez voir cela en action sur Glitch. (J'utilise la bibliothèque idb-keyval par souci de concision.)

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

Poignées et autorisations de fichiers ou de répertoires stockés

Étant donné que les autorisations ne sont actuellement pas conservées entre les sessions, vous devez vérifier si l'utilisateur a accordé l'autorisation d'accéder au fichier ou au répertoire à l'aide de queryPermission(). Si ce n'est pas le cas, appelez requestPermission() pour en (re)demander. Cela fonctionne de la même manière pour les identifiants de fichiers et de répertoires. Vous devez exécuter respectivement fileOrDirectoryHandle.requestPermission(descriptor) ou fileOrDirectoryHandle.queryPermission(descriptor).

Dans l'éditeur de texte, j'ai créé une méthode verifyPermission() qui vérifie si l'utilisateur a déjà accordé l'autorisation et, si nécessaire, effectue la requête.

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

En demandant une autorisation d'écriture avec la requête de lecture, j'ai réduit le nombre d'invites d'autorisation. L'utilisateur ne voit qu'une invite à l'ouverture du fichier, et l'accorde à la fois pour le lire et l'écrire.

Ouvrir un répertoire et énumérer son contenu

Pour énumérer tous les fichiers d'un répertoire, appelez showDirectoryPicker(). L'utilisateur sélectionne un répertoire dans un sélecteur, après quoi un élément FileSystemDirectoryHandle est renvoyé, ce qui vous permet d'énumérer les fichiers du répertoire et d'y accéder. Par défaut, vous disposez d'un accès en lecture aux fichiers du répertoire, mais si vous avez besoin d'un accès en écriture, vous pouvez transmettre { mode: 'readwrite' } à la méthode.

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

Si vous devez également accéder à chaque fichier via getFile() pour, par exemple, obtenir les tailles de fichiers individuelles, n'utilisez pas await pour chaque résultat de manière séquentielle, mais traitez plutôt tous les fichiers en parallèle (par exemple, via 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));
});

Créer ou accéder à des fichiers et des dossiers dans un répertoire

À partir d'un répertoire, vous pouvez créer des fichiers et des dossiers ou y accéder à l'aide de la méthode getFileHandle() ou, respectivement, de la méthode getDirectoryHandle(). En transmettant un objet options facultatif avec une clé create et une valeur booléenne true ou false, vous pouvez déterminer si un fichier ou un dossier doit être créé s'il n'existe pas.

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

Résoudre le chemin d'accès à un élément d'un répertoire

Lorsque vous utilisez des fichiers ou des dossiers dans un répertoire, il peut être utile de résoudre le chemin d'accès de l'élément en question. Pour ce faire, utilisez la méthode resolve(), qui est bien nommée. Pour la résolution, l'élément peut être un enfant direct ou indirect de l'annuaire.

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

Supprimer des fichiers et des dossiers dans un répertoire

Si vous avez obtenu l'accès à un répertoire, vous pouvez supprimer les fichiers et dossiers qu'il contient à l'aide de la méthode removeEntry(). Pour les dossiers, la suppression peut éventuellement être récursive et inclure tous les sous-dossiers et les fichiers qu'ils contiennent.

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

Supprimer directement un fichier ou un dossier

Si vous avez accès à un handle de fichier ou de répertoire, appelez remove() sur un FileSystemFileHandle ou un FileSystemDirectoryHandle pour le supprimer.

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

Renommer et déplacer des fichiers et des dossiers

Les fichiers et les dossiers peuvent être renommés ou déplacés vers un nouvel emplacement en appelant move() sur l'interface FileSystemHandle. FileSystemHandle comporte les interfaces enfants FileSystemFileHandle et FileSystemDirectoryHandle. La méthode move() utilise un ou deux paramètres. Le premier peut être une chaîne avec le nouveau nom ou un élément FileSystemDirectoryHandle dans le dossier de destination. Dans ce dernier cas, le deuxième paramètre facultatif est une chaîne avec le nouveau nom. Le déplacement et le changement de nom peuvent donc se produire en une seule étape.

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

Intégration par glisser-déposer

Les interfaces HTML de glisser-déposer permettent aux applications Web d'accepter des fichiers glissés-déposés sur une page Web. Lors d'une opération de glisser-déposer, les éléments de fichier et de répertoire glissés sont associés respectivement aux entrées de fichier et de répertoire. La méthode DataTransferItem.getAsFileSystemHandle() renvoie une promesse avec un objet FileSystemFileHandle si l'élément glissé est un fichier, et une promesse avec un objet FileSystemDirectoryHandle si l'élément glissé est un répertoire. La liste ci-dessous illustre ce processus. Notez que le DataTransferItem.kind de l'interface Glisser-déposer est "file" pour les fichiers et les répertoires, tandis que FileSystemHandle.kind de l'API File System Access est "file" pour les fichiers et "directory" pour les répertoires.

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

Accéder au système de fichiers privé d'origine

Le système de fichiers d'origine privé est un point de terminaison de stockage qui, comme son nom l'indique, est réservé à l'origine de la page. Bien que les navigateurs implémentent généralement cela en conservant le contenu de ce système de fichiers privé d'origine sur le disque, il n'est pas prévu que le contenu soit facilement accessible par l'utilisateur. De même, il n'y a aucune attente quant à l'existence de fichiers ou de répertoires dont les noms correspondent aux noms des enfants du système de fichiers privé d'origine. Bien que le navigateur puisse donner l'impression qu'il existe des fichiers, en interne (puisqu'il s'agit d'un système de fichiers d'origine privé), le navigateur peut stocker ces "fichiers" dans une base de données ou toute autre structure de données. En fait, si vous utilisez cette API, ne vous attendez pas à trouver les fichiers créés avec une correspondance individuelle sur le disque dur. Vous pouvez procéder comme d'habitude sur le système de fichiers privé d'origine une fois que vous avez accès à la racine 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 });

Navigateurs pris en charge

  • 86
  • 86
  • 111
  • 15.2

Source

Accéder aux fichiers optimisés pour les performances à partir du système de fichiers privé d'origine

Le système de fichiers d'origine privé fournit un accès facultatif à un type spécial de fichier hautement optimisé pour les performances, par exemple en offrant un accès en écriture sur place et exclusif au contenu d'un fichier. À partir de la version 102 de Chromium, il existe une autre méthode sur le système de fichiers privés d'origine pour simplifier l'accès aux fichiers: createSyncAccessHandle() (pour les opérations de lecture et d'écriture synchrones). Il est exposé sur FileSystemFileHandle, mais exclusivement dans 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 });

Polyremplissage

Il n'est pas possible d'émuler complètement les méthodes de l'API File System Access.

  • La méthode showOpenFilePicker() peut être approximative avec un élément <input type="file">.
  • La méthode showSaveFilePicker() peut être simulée avec un élément <a download="file_name">, bien que cela déclenche un téléchargement programmatique et ne permet pas d'écraser des fichiers existants.
  • La méthode showDirectoryPicker() peut être émulée avec l'élément <input type="file" webkitdirectory> non standard.

Nous avons développé une bibliothèque appelée browser-fs-access qui utilise l'API File System Access dans la mesure du possible et qui se rabat sur ces meilleures options dans tous les autres cas.

Sécurité et autorisations

L'équipe Chrome a conçu et mis en œuvre l'API File System Access en s'appuyant sur les principes fondamentaux définis dans l'article Contrôler l'accès aux fonctionnalités puissantes de la plate-forme Web, dont le contrôle et la transparence pour l'utilisateur, ainsi que l'ergonomie.

Ouvrir ou enregistrer un nouveau fichier

Sélecteur de fichier pour ouvrir un fichier afin de le lire
Sélecteur de fichier utilisé pour ouvrir un fichier existant afin de le lire.

Lors de l'ouverture d'un fichier, l'utilisateur fournit l'autorisation de lire un fichier ou un répertoire via le sélecteur de fichier. Le sélecteur de fichier ouvert ne peut s'afficher par un geste que lorsqu'il est diffusé à partir d'un contexte sécurisé. Si les utilisateurs changent d'avis, ils peuvent annuler la sélection dans le sélecteur de fichier, et le site n'a accès à rien. Ce comportement est le même que celui de l'élément <input type="file">.

Sélecteur de fichier pour enregistrer un fichier sur le disque.
Sélecteur de fichier utilisé pour enregistrer un fichier sur le disque.

De même, lorsqu'une application Web souhaite enregistrer un nouveau fichier, le navigateur affiche le sélecteur d'enregistrement, permettant à l'utilisateur de spécifier le nom et l'emplacement du nouveau fichier. Étant donné qu'ils enregistrent un nouveau fichier sur l'appareil (plutôt que d'écraser un fichier existant), le sélecteur de fichier autorise l'application à écrire dans le fichier.

Dossiers restreints

Pour protéger les utilisateurs et leurs données, le navigateur peut limiter la capacité de l'utilisateur à enregistrer des fichiers dans certains dossiers, par exemple dans les dossiers principaux du système d'exploitation tels que Windows, les dossiers de la bibliothèque macOS, etc. Dans ce cas, le navigateur affiche une invite et demande à l'utilisateur de choisir un autre dossier.

Modification d'un fichier ou d'un répertoire existant

Une application Web ne peut pas modifier un fichier sur un disque sans obtenir l'autorisation explicite de l'utilisateur.

Invite d'autorisation

Si une personne souhaite enregistrer les modifications apportées à un fichier pour lequel elle a déjà accordé un accès en lecture, le navigateur affiche une invite d'autorisation, demandant au site l'autorisation d'écrire les modifications sur le disque. La demande d'autorisation ne peut être déclenchée que par un geste de l'utilisateur, par exemple en cliquant sur un bouton "Enregistrer".

Invite d&#39;autorisation affichée avant l&#39;enregistrement d&#39;un fichier.
Invite affichée avant que le navigateur ne dispose d'une autorisation en écriture sur un fichier existant.

Une application Web qui modifie plusieurs fichiers, comme un IDE, peut également demander l'autorisation d'enregistrer les modifications au moment de l'ouverture.

Si l'utilisateur choisit "Annuler" et n'accorde pas d'accès en écriture, l'application Web ne peut pas enregistrer les modifications apportées au fichier local. Elle doit proposer une autre méthode permettant à l'utilisateur d'enregistrer ses données, par exemple en lui permettant de "télécharger" le fichier, de les enregistrer dans le cloud, etc.

Transparence

Icône de l&#39;omnibox
Icône de l'omnibox indiquant que l'utilisateur a autorisé le site Web à enregistrer dans un fichier local.

Une fois qu'un utilisateur a autorisé une application Web à enregistrer un fichier local, le navigateur affiche une icône dans la barre d'adresse. En cliquant sur l'icône, un pop-up s'ouvre et affiche la liste des fichiers auxquels l'utilisateur a donné accès. L'utilisateur peut facilement révoquer cet accès s'il le souhaite.

Persistance des autorisations

L'application Web peut continuer à enregistrer les modifications apportées au fichier sans afficher d'invite tant que tous les onglets de son origine ne sont pas fermés. Une fois qu'un onglet est fermé, le site n'y a plus accès. La prochaine fois que l'utilisateur se servira de l'application Web, il sera invité à accéder aux fichiers.

Commentaires

Nous aimerions connaître votre avis sur votre expérience avec l'API File System Access.

Décrivez-nous la conception de l'API.

Y a-t-il quelque chose dans l'API qui ne fonctionne pas comme prévu ? Ou manque-t-il des méthodes ou des propriétés dont vous avez besoin pour mettre en œuvre votre idée ? Vous avez une question ou un commentaire sur le modèle de sécurité ?

Un problème d'implémentation ?

Avez-vous détecté un bug dans l'implémentation de Chrome ? Ou l'implémentation est-elle différente des spécifications ?

  • Signalez un bug à l'adresse https://new.crbug.com. Veillez à inclure autant de détails que possible, ainsi que des instructions simples pour reproduire le bug, et définissez le champ Composants sur Blink>Storage>FileSystem. Glitch est idéal pour partager des reproductions simples et rapides.

Vous prévoyez d'utiliser l'API ?

Vous prévoyez d'utiliser l'API File System Access sur votre site ? Votre assistance publique nous aide à hiérarchiser les fonctionnalités et montre aux autres fournisseurs de navigateurs à quel point il est essentiel de les prendre en charge.

Liens utiles

Remerciements

La spécification de l'API File System Access a été écrite par Marijn Kruisselbrink.