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 des modifications directement dans les fichiers et dossiers de l'appareil de l'utilisateur.

Publié le 19 août 2024

L'API File System Access permet aux développeurs de créer des applications Web puissantes qui interagissent avec les fichiers sur l'appareil local de l'utilisateur, comme les IDE, les éditeurs de photos et de vidéos, les é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 des modifications directement dans les fichiers et dossiers de son appareil. En plus de la lecture et de l'écriture de fichiers, l'API File System Access permet d'ouvrir un répertoire et d'énumérer son contenu.

Si vous avez déjà travaillé avec des fichiers en lecture et en écriture, une grande partie de ce que je vais vous présenter vous sera familière. Je vous encourage tout de même à le lire, car tous les systèmes ne sont pas identiques.

L'API File System Access est compatible avec la plupart des navigateurs Chromium sur Windows, macOS, ChromeOS, Linux et Android. Une exception notable est Brave, où cette fonctionnalité n'est actuellement disponible qu'avec un indicateur.

Utiliser l'API File System Access

Pour illustrer la puissance et l'utilité de l'API File System Access, j'ai écrit un éditeur de texte à fichier unique. Il vous permet d'ouvrir un fichier texte, de le modifier, d'enregistrer les modifications sur le disque, ou de créer un fichier et d'enregistrer les modifications sur le disque. Il n'a rien d'extraordinaire, mais il vous fournit suffisamment d'informations pour vous aider à comprendre les concepts.

Prise en charge des navigateurs

Browser Support

  • Chrome: 86.
  • Edge: 86.
  • Firefox: not supported.
  • Safari: not supported.

Source

Détection de fonctionnalités

Pour savoir si l'API File System Access est compatible, vérifiez si la méthode de sélecteur qui vous intéresse existe.

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

Essayer

Découvrez l'API File System Access en action dans la démo 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.

Demander à l'utilisateur de choisir un fichier à lire

Le point d'entrée de l'API File System Access est window.showOpenFilePicker(). Lorsqu'elle est appelée, elle affiche une boîte de dialogue de sélection de fichier et invite l'utilisateur à sélectionner un fichier. Une fois qu'il a sélectionné un fichier, l'API renvoie un tableau de handles de fichier. Un paramètre options facultatif vous permet d'influencer le comportement du sélecteur de fichiers, par exemple en autorisant l'utilisateur à sélectionner plusieurs fichiers ou répertoires, ou différents types de fichiers. Sans aucune option spécifiée, le sélecteur de fichiers 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 à partir d'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 handles. Dans ce cas, il s'agit d'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 au descripteur de fichier pour pouvoir l'utiliser ultérieurement. Il sera nécessaire pour enregistrer les modifications apportées au fichier ou pour effectuer toute autre opération sur le fichier.

Lire un fichier à partir du système de fichiers

Maintenant que vous disposez d'un handle pour un fichier, vous pouvez obtenir les propriétés du fichier ou accéder au fichier lui-même. Pour l'instant, je vais lire son contenu. L'appel de handle.getFile() renvoie un objet File, qui contient 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 devrez 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 fichiers. Une fois le fichier sélectionné, l'application lit son 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 manières : Enregistrer et Enregistrer sous. Save (Enregistrer) réécrit les modifications dans le fichier d'origine à l'aide du descripteur de fichier récupéré précédemment. Toutefois, l'option Enregistrer sous crée un fichier, et nécessite donc un nouveau descripteur de fichier.

Créer un fichier

Pour enregistrer un fichier, appelez showSaveFilePicker(), qui affiche le sélecteur de fichiers en mode "Enregistrer". L'utilisateur peut ainsi choisir un nouveau fichier à utiliser pour l'enregistrement. Pour l'éditeur de texte, je voulais également qu'il ajoute automatiquement une extension .txt. J'ai donc fourni des 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 les modifications apportées à un fichier dans ma démo d'éditeur de texte sur GitHub. Les interactions de base avec le système de fichiers se trouvent dans fs-helpers.js. Dans sa forme la plus simple, le processus ressemble au code suivant. Je vais vous guider à 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 gestion de fichier. Lorsque createWritable() est appelé, le navigateur vérifie d'abord si l'utilisateur a accordé l'autorisation d'écriture pour le fichier. Si l'autorisation d'écriture n'a pas été accordée, le navigateur invite l'utilisateur à l'accorder. Si l'autorisation n'est pas accordée, createWritable() génère une 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() accepte une chaîne, ce qui est nécessaire pour un éditeur de texte. mais il peut également prendre une BufferSource ou un Blob. Par exemple, vous pouvez rediriger 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 le redimensionner.

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

Dans de nombreux cas, vous souhaiterez peut-être que votre application suggère un nom de fichier ou un emplacement par défaut. Par exemple, un éditeur de texte peut suggérer un nom de fichier par défaut tel que Untitled Text.txt plutôt que 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 démarrer la boîte de dialogue d'enregistrement ou d'ouverture de fichier dans le dossier documents par défaut. En revanche, pour un éditeur d'images, vous pouvez démarrer dans le dossier pictures par défaut. Vous pouvez suggérer un répertoire de démarrage par défaut en transmettant une propriété startIn aux méthodes showSaveFilePicker, showDirectoryPicker() ou showOpenFilePicker comme suit.

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

Voici la liste des répertoires système connus :

  • desktop : répertoire de bureau de l'utilisateur, s'il existe.
  • 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 ou les films sont généralement stockés.

En plus des répertoires système connus, vous pouvez également transmettre un fichier ou un répertoire existant en tant que valeur pour startIn. La boîte de dialogue s'ouvre 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 l'objectif des différents sélecteurs de fichiers

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

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

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

Stocker des descripteurs de fichiers ou de répertoires dans IndexedDB

Les handles 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 handles de fichiers ou de répertoires dans IndexedDB signifie que vous pouvez stocker l'état ou vous souvenir des fichiers ou 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 lorsque l'application est ouverte, 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 ouverts par l'utilisateur, ce qui lui permet d'y accéder à nouveau.

L'exemple de code suivant montre comment stocker et récupérer un descripteur de fichier et un descripteur de répertoire. Vous pouvez voir une démonstration 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);
  }
});

Autorisations et identifiants de fichiers ou de répertoires stockés

Étant donné que les autorisations ne sont pas toujours conservées d'une session à l'autre, 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 le requestPermission() pour le (re)demander. Le fonctionnement est le même pour les handles de fichiers et de répertoires. Vous devez exécuter fileOrDirectoryHandle.requestPermission(descriptor) ou fileOrDirectoryHandle.queryPermission(descriptor), respectivement.

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, envoie la demande.

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 l'autorisation d'écriture avec la demande de lecture, j'ai réduit le nombre d'invites d'autorisation. L'utilisateur voit une invite lorsqu'il ouvre le fichier et accorde l'autorisation de lecture et d'écriture.

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 FileSystemDirectoryHandle est renvoyé, ce qui vous permet d'énumérer les fichiers du répertoire et d'y accéder. Par défaut, vous aurez 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.

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 à l'aide de getFile() (par exemple, pour obtenir la taille de chaque fichier), n'utilisez pas await sur chaque résultat de manière séquentielle, mais traitez plutôt tous les fichiers en parallèle, par exemple à l'aide de 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));
});

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

À 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 getDirectoryHandle(), respectivement. En transmettant un objet options facultatif avec une clé create et une valeur booléenne true ou false, vous pouvez déterminer si un nouveau fichier ou 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 d'un élément dans un répertoire

Lorsque vous travaillez avec 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(). Pour la résolution, l'élément peut être un enfant direct ou indirect du répertoire.

// 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 ê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 descripteur de fichier ou de répertoire, appelez remove() sur un FileSystemFileHandle ou 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

Vous pouvez renommer ou déplacer des fichiers et des dossiers en appelant move() sur l'interface FileSystemHandle. FileSystemHandle possède les interfaces enfants FileSystemFileHandle et FileSystemDirectoryHandle. La méthode move() comporte un ou deux paramètres. La première peut être une chaîne avec le nouveau nom ou un FileSystemDirectoryHandle vers le dossier de destination. Dans ce dernier cas, le deuxième paramètre facultatif est une chaîne contenant le nouveau nom. Il est donc possible de déplacer et de renommer un fichier 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 les fichiers glissés et 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 déplacés sont associés respectivement à des entrées de fichier et de répertoire. La méthode DataTransferItem.getAsFileSystemHandle() renvoie une promesse avec un objet FileSystemFileHandle si l'élément déplacé est un fichier, et une promesse avec un objet FileSystemDirectoryHandle si l'élément déplacé est un répertoire. La liste suivante illustre ce processus. Notez que DataTransferItem.kind de l'interface de 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és de l'origine

Le système de fichiers privés d'origine est un point de terminaison de stockage qui, comme son nom l'indique, est privé à 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 quelque part, il n'est pas prévu que le contenu soit accessible à l'utilisateur. De même, il n'est pas prévu que des fichiers ou des répertoires dont les noms correspondent à ceux des enfants du système de fichiers privés d'origine existent. Bien que le navigateur puisse donner l'impression qu'il existe des fichiers, en interne, comme il s'agit d'un système de fichiers privés d'origine, le navigateur peut stocker ces "fichiers" dans une base de données ou toute autre structure de données. En d'autres termes, si vous utilisez cette API, ne vous attendez pas à trouver les fichiers créés correspondant un à un quelque part sur le disque dur. Vous pouvez fonctionner normalement 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 });

Browser Support

  • Chrome: 86.
  • Edge: 86.
  • Firefox: 111.
  • Safari: 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 privés d'origine offre un accès facultatif à un type de fichier spécial qui est hautement optimisé pour les performances, par exemple en offrant un accès en écriture exclusif et sur place au contenu d'un fichier. Dans Chromium 102 et versions ultérieures, une méthode supplémentaire est disponible sur le système de fichiers privé 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 les 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

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

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

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 options les plus adaptées dans tous les autres cas.

Sécurité et autorisations

L'équipe Chrome a conçu et implémenté l'API File System Access en utilisant les principes de base définis dans Controlling Access to Powerful Web Platform Features, y compris le contrôle et la transparence pour l'utilisateur, ainsi que l'ergonomie pour l'utilisateur.

Ouvrir un fichier ou en enregistrer un nouveau

Sélecteur de fichier pour ouvrir un fichier en lecture
Sélecteur de fichier utilisé pour ouvrir un fichier existant en lecture.

Lorsqu'il ouvre un fichier, l'utilisateur autorise la lecture d'un fichier ou d'un répertoire à l'aide du sélecteur de fichiers. Le sélecteur de fichiers ouverts ne peut être affiché qu'à l'aide d'un geste de l'utilisateur 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 fichiers, et le site n'a accès à rien. Ce comportement est identique à celui de l'élément <input type="file">.

Sélecteur de fichiers permettant d&#39;enregistrer un fichier sur le disque.
Sélecteur de fichiers 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 de fichier d'enregistrement, ce qui permet à l'utilisateur de spécifier le nom et l'emplacement du nouveau fichier. Étant donné qu'ils enregistrent un nouveau fichier sur l'appareil (au lieu d'écraser un fichier existant), le sélecteur de fichiers accorde à l'application l'autorisation d'écrire dans le fichier.

Dossiers à accès restreint

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

Modifier un fichier ou un répertoire existant

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

Invite d'autorisation

Si une personne souhaite enregistrer des modifications dans un fichier auquel elle a précédemment accordé un accès en lecture, le navigateur affiche une invite d'autorisation, demandant l'autorisation pour le site 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".

Message d&#39;autorisation affiché avant l&#39;enregistrement d&#39;un fichier.
Invite affichée aux utilisateurs avant que le navigateur ne soit autorisé à écrire dans 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 l'accès en écriture, l'application Web ne peut pas enregistrer les modifications apportées au fichier local. Il doit fournir à l'utilisateur une autre méthode pour enregistrer ses données, par exemple en lui permettant de télécharger le fichier ou d'enregistrer les données dans le cloud.

Transparence

Icône de l&#39;omnibox
Icône de la barre d'adresse indiquant que l'utilisateur a autorisé le site Web à enregistrer 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. Lorsque l'utilisateur clique sur l'icône, un pop-up s'ouvre et affiche la liste des fichiers auxquels il a accordé l'accès. L'utilisateur peut toujours 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 y être invitée, jusqu'à ce que tous les onglets de son origine soient fermés. Une fois un onglet fermé, le site perd tout accès. La prochaine fois que l'utilisateur utilisera l'application Web, il sera de nouveau invité à autoriser l'accès aux fichiers.

Commentaires

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

Parlez-nous de la conception de l'API

Y a-t-il quelque chose dans l'API qui ne fonctionne pas comme prévu ? Ou bien 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é ?

Vous rencontrez un problème d'implémentation ?

Avez-vous trouvé un bug dans l'implémentation de Chrome ? Ou l'implémentation est-elle différente de la spécification ?

  • Signalez un bug sur https://new.crbug.com. Veillez à inclure autant de détails que possible, des instructions pour reproduire le problème et définissez Composants sur Blink>Storage>FileSystem.

Vous prévoyez d'utiliser l'API ?

Vous prévoyez d'utiliser l'API File System Access sur votre site ? Votre soutien public 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é rédigée par Marijn Kruisselbrink.