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.

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

L'API File System Access permet aux développeurs de créer des applications Web puissantes qui interagissent avec les fichiers de l'appareil local de l'utilisateur, tels que les IDE, les éditeurs photo et vidéo, 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 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é sur la lecture et l'écriture de fichiers, une grande partie de ce que je vais partager vous sera familière. Je vous encourage toutefois à le lire, car tous les systèmes ne se ressemblent pas.

L'API File System Access est compatible avec la plupart des navigateurs Chromium sous Windows, macOS, ChromeOS et Linux. Une exception notable est Brave, où elle n'est actuellement disponible que derrière un indicateur. La prise en charge d'Android est en cours de développement dans le cadre de crbug.com/1011535.

Utiliser l'API File System Access

Pour montrer 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 nouveau fichier et d'enregistrer les modifications sur le disque. Il n'est pas très élaboré, mais il est suffisant pour vous aider à comprendre les concepts.

Prise en charge des navigateurs

Navigateurs pris en charge

  • Chrome: 86.
  • Edge: 86.
  • Firefox: non compatible.
  • Safari: non compatible.

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é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 à l'ouvrir et à 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'il est appelé, il affiche une boîte de dialogue de sélecteur de fichiers et invite l'utilisateur à sélectionner un fichier. Une fois qu'il a sélectionné un fichier, l'API renvoie un tableau de poignées de fichiers. Un paramètre options facultatif vous permet d'influencer le comportement du sélecteur de fichiers, par exemple en permettant à l'utilisateur de sélectionner plusieurs fichiers, répertoires ou types de fichiers différents. Sans options spécifiées, 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 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 au handle de fichier afin qu'il puisse être utilisé ultérieurement. Il est nécessaire d'enregistrer les modifications apportées au fichier ou d'effectuer d'autres opérations sur le fichier.

Lire un fichier à partir du système de fichiers

Maintenant que vous disposez d'un handle de fichier, vous pouvez obtenir ses propriétés ou accéder au fichier lui-même. Pour le moment, 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 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 fichiers. Une fois qu'il a sélectionné un fichier, l'application lit son contenu et le met 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. Enregistrer réécrit les modifications dans le fichier d'origine à l'aide du gestionnaire de fichiers récupéré précédemment. Toutefois, Enregistrer sous crée un nouveau fichier et nécessite donc un nouveau gestionnaire de fichiers.

Créer un fichier

Pour enregistrer un fichier, appelez showSaveFilePicker(), qui affiche le sélecteur de fichiers en mode "Enregistrer", ce qui permet à l'utilisateur de sélectionner 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 l'intégralité du code permettant d'enregistrer les modifications apportées à un fichier dans ma démonstration de l'éditeur de texte sur GitHub. Les interactions de base du système de fichiers se trouvent dans fs-helpers.js. Dans sa forme la plus simple, le processus se présente comme suit. Je vais vous expliquer chaque étape.

// 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 un disque utilise un objet FileSystemWritableFileStream, une sous-classe de WritableStream. Créez le flux en appelant createWritable() sur l'objet de poignée de fichier. Lorsque createWritable() est appelé, le navigateur vérifie d'abord si l'utilisateur a accordé l'autorisation d'écriture au 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 peut 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. Il peut également prendre un BufferSource ou un 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 utiliser 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émarrage 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 de 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, tandis que 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 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 ou les films sont généralement stockés.

En plus des répertoires système connus, vous pouvez également transmettre un gestionnaire de fichier ou de répertoire existant comme 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

Parfois, les applications disposent de sélecteurs différents pour différents objectifs. 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 à l'emplacement précédemment enregistré. 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 fichiers mémorise un répertoire de dernier accès distinct pour ce id.

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

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

Stocker des poignées de fichiers ou de répertoires dans IndexedDB

Les poignées de fichiers et de répertoires sont sérialisables, ce qui signifie que vous pouvez enregistrer un fichier ou une poignée de répertoire dans IndexedDB, ou appeler postMessage() pour les envoyer entre la même origine de niveau supérieur.

Enregistrer des poignées de fichiers ou de répertoires dans IndexedDB vous permet de stocker l'état ou de 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 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 que l'utilisateur a ouverts, 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);
  }
});

Handles et autorisations de fichiers ou de répertoires stockés

Étant donné que les autorisations ne sont pas toujours conservées entre les sessions, vous devez vérifier si l'utilisateur a accordé une autorisation au fichier ou au répertoire à l'aide de queryPermission(). Si ce n'est pas le cas, appelez requestPermission() pour (re)demander l'accès. Le fonctionnement est le même pour les poignées 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 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 l'autorisation d'écriture avec la requête 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 le lire et d'y écrire.

Ouvrir un répertoire et en énumérer le 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 et d'accéder aux fichiers du répertoire. 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.

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() pour obtenir, par exemple, les tailles de fichiers individuelles, n'utilisez pas await de manière séquentielle sur chaque résultat, mais plutôt traitez 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 ou accéder à des fichiers et des dossiers dans un répertoire

À partir d'un répertoire, vous pouvez créer ou accéder à des fichiers et des dossiers à 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 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 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 de l'élément en question. Pour ce faire, utilisez la méthode resolve(), qui porte un nom bien choisi. 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 les 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 gestionnaire de fichiers ou de répertoires, 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

Vous pouvez renommer des fichiers et des dossiers ou les déplacer vers un nouvel emplacement en appelant move() sur l'interface FileSystemHandle. FileSystemHandle possède les interfaces enfants FileSystemFileHandle et FileSystemDirectoryHandle. La méthode move() prend un ou deux paramètres. Le premier peut être une chaîne avec le nouveau nom ou un FileSystemDirectoryHandle vers le dossier de destination. Dans le 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 être effectués 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-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 à des entrées de fichier et de répertoire, respectivement. 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 suivante illustre ce point. Notez que l'interface de glisser-déposer DataTransferItem.kind 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 privé 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 un disque quelque part, le contenu n'est pas destiné à être accessible par 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é 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é d'origine, il peut stocker ces "fichiers" dans une base de données ou toute autre structure de données. En substance, si vous utilisez cette API, ne vous attendez pas à trouver les fichiers créés mis en correspondance un à un quelque part sur le disque dur. Une fois que vous avez accès à la racine FileSystemDirectoryHandle, vous pouvez utiliser le système de fichiers privé d'origine comme d'habitude.

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

  • 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é d'origine fournit un accès facultatif à un type de fichier spécial hautement optimisé pour les performances, par exemple en offrant un accès en écriture exclusif et en 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">, bien que cela déclenche un téléchargement programmatique et n'autorise pas l'écrasement des fichiers existants.
  • La méthode showDirectoryPicker() peut être quelque peu é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 utilise ces options de substitution dans tous les autres cas.

Sécurité et autorisations

L'équipe Chrome a conçu et implémenté l'API File System Access en suivant les principes de base définis dans Controlling Access to Powerful Web Platform Features (Contrôler l'accès aux fonctionnalités puissantes de la plate-forme Web), y compris le contrôle et la transparence de l'utilisateur, ainsi que l'ergonomie.

Ouvrir un fichier ou en enregistrer un

Sélecteur de fichiers pour ouvrir un fichier à lire
Sélecteur de fichiers permettant d'ouvrir un fichier existant pour le lire.

Lorsque l'utilisateur ouvre un fichier, il autorise la lecture du fichier ou du 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 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 pour enregistrer un fichier sur le disque.
Sélecteur de fichiers permettant d'enregistrer un fichier sur un disque.

De même, lorsqu'une application Web souhaite enregistrer un nouveau fichier, le navigateur affiche le sélecteur de fichier d'enregistrement, qui permet à l'utilisateur de spécifier le nom et l'emplacement du nouveau fichier. Étant donné qu'il enregistre un nouveau fichier sur l'appareil (plutôt que de remplacer un fichier existant), le sélecteur de fichiers accorde à l'application l'autorisation d'écrire dans le fichier.

Dossiers restreints

Pour protéger les utilisateurs et leurs données, le navigateur peut limiter leur possibilité d'enregistrer des fichiers dans certains dossiers, par exemple les dossiers principaux du système d'exploitation comme Windows et les dossiers de 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 un disque sans l'autorisation explicite de l'utilisateur.

Invite d'autorisation

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

Invite d&#39;autorisation affichée avant l&#39;enregistrement d&#39;un fichier.
Invite présentée aux utilisateurs avant que le navigateur ne reçoive l'autorisation d'écrire 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. Il doit fournir une autre méthode permettant à l'utilisateur d'enregistrer ses données, par exemple en lui permettant de "télécharger" le fichier ou d'enregistrer des données dans le cloud.

Transparence

Icône de l&#39;Omnibox
Icone de la barre d'adresse indiquant que l'utilisateur a autorisé le site Web à enregistrer dans un fichier local.

Une fois qu'un utilisateur a accordé à une application Web l'autorisation d'enregistrer un fichier local, le navigateur affiche une icône dans la barre d'adresse. Cliquez sur l'icône pour ouvrir un pop-up affichant la liste des fichiers auxquels l'utilisateur 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 invite 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é à accéder aux fichiers.

Commentaires

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

Parlez-nous de la conception de l'API

L'API ne fonctionne-t-elle pas comme prévu ? Ou manque-t-il des méthodes ou des propriétés dont vous avez besoin pour implémenter votre idée ? Vous avez une question ou un commentaire sur le modèle de sécurité ?

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 Components sur Blink>Storage>FileSystem. Glitch est idéal pour partager des reproductions rapides.

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.