Estimer l'espace de stockage disponible

tl;dr

Chrome 61, suivi d'autres navigateurs, affiche désormais une estimation de l'espace de stockage utilisé par une application Web et de la quantité d'espace disponible via:

if ('storage' in navigator && 'estimate' in navigator.storage) {
  navigator.storage.estimate().then(({usage, quota}) => {
    console.log(`Using ${usage} out of ${quota} bytes.`);
  });
}

Applications Web modernes et stockage de données

Lorsque vous pensez aux besoins de stockage d'une application Web moderne, il est utile de diviser ce qui est stocké en deux catégories: les données de base nécessaires au chargement de l'application Web et les données nécessaires à une interaction utilisateur significative une fois l'application chargée.

Le premier type de données, qui est nécessaire pour charger votre application Web, comprend le code HTML, JavaScript, CSS et éventuellement certaines images. Les service workers et l'API Cache Storage fournissent l'infrastructure nécessaire pour enregistrer ces ressources principales. Vous pouvez ensuite les utiliser ultérieurement pour charger rapidement votre application Web, idéalement en contournant complètement le réseau. (Les outils qui s'intègrent au processus de compilation de votre application Web, tels que les nouvelles bibliothèques Workbox ou l'ancienne version de sw-precache, peuvent automatiser entièrement le processus de stockage, de mise à jour et d'utilisation de ce type de données.)

Mais qu'en est-il des autres types de données ? Il s'agit de ressources qui ne sont pas nécessaires pour charger votre application Web, mais qui peuvent jouer un rôle crucial dans votre expérience utilisateur globale. Par exemple, si vous développez une application Web de retouche d'images, vous pouvez enregistrer une ou plusieurs copies locales d'une image pour permettre aux utilisateurs de passer d'une révision à l'autre et d'annuler leur travail. Si vous développez une expérience de lecture de contenus multimédias hors connexion, l'enregistrement des fichiers audio ou vidéo en local est une fonctionnalité essentielle. Chaque application Web personnalisable finit par devoir enregistrer une sorte d'informations d'état. Comment connaître l'espace disponible pour ce type de stockage d'exécution, et que se passe-t-il lorsque vous manquez d'espace ?

Passé: window.webkitStorageInfo et navigator.webkitTemporaryStorage

Les navigateurs ont toujours pris en charge ce type d'introspection via des interfaces précédées, comme l'ancienne (obsolète) window.webkitStorageInfo et la version pas comme l'ancienne, mais toujours non standard, navigator.webkitTemporaryStorage. Bien que ces interfaces fournissent des informations utiles, elles n'ont pas d'avenir en tant que normes Web.

C'est là que la norme de stockage WhatWG entre en jeu.

L'avenir: navigator.storage

Dans le cadre des travaux continus effectués sur Storage Living Standard, quelques API utiles ont été intégrées à l'interface StorageManager, qui est exposée aux navigateurs en tant que navigator.storage. Comme de nombreuses autres API Web plus récentes, navigator.storage n'est disponible que sur des origines sécurisées (diffusées via HTTPS ou localhost).

L'année dernière, nous avons lancé la méthode navigator.storage.persist(), qui permet à votre application Web de demander l'exemption de son espace de stockage du nettoyage automatique.

Il est désormais joint par la méthode navigator.storage.estimate(), qui remplace moderne de navigator.webkitTemporaryStorage.queryUsageAndQuota(). estimate() renvoie des informations similaires, mais expose une interface basée sur des promesses, qui est conforme aux autres API asynchrones modernes. La promesse renvoyée par estimate() se résout avec un objet contenant deux propriétés: usage, qui représente le nombre d'octets actuellement utilisés, et quota, qui représente le nombre maximal d'octets pouvant être stockés par l'origine actuelle. (Comme pour tout ce qui concerne le stockage, le quota s'applique à l'ensemble d'une origine.)

Si une application Web tente de stocker, à l'aide d'IndexedDB ou de l'API Cache Storage, par exemple, des données suffisamment volumineuses pour dépasser le quota disponible d'une origine, la requête échoue et génère une exception QuotaExceededError.

Les estimations de l'espace de stockage en action

La manière dont vous utilisez estimate() dépend du type de données que votre application doit stocker. Par exemple, vous pouvez mettre à jour une commande dans votre interface pour indiquer aux utilisateurs l'espace utilisé après chaque opération de stockage. Idéalement, vous devez fournir une interface permettant aux utilisateurs de nettoyer manuellement les données qui ne sont plus nécessaires. Vous pouvez écrire du code comme suit:

// For a primer on async/await, see
// https://developers.google.com/web/fundamentals/getting-started/primers/async-functions
async function storeDataAndUpdateUI(dataUrl) {
  // Pro-tip: The Cache Storage API is available outside of service workers!
  // See https://googlechrome.github.io/samples/service-worker/window-caches/
  const cache = await caches.open('data-cache');
  await cache.add(dataUrl);

  if ('storage' in navigator && 'estimate' in navigator.storage) {
    const {usage, quota} = await navigator.storage.estimate();
    const percentUsed = Math.round(usage / quota * 100);
    const usageInMib = Math.round(usage / (1024 * 1024));
    const quotaInMib = Math.round(quota / (1024 * 1024));

    const details = `${usageInMib} out of ${quotaInMib} MiB used (${percentUsed}%)`;

    // This assumes there's a <span id="storageEstimate"> or similar on the page.
    document.querySelector('#storageEstimate').innerText = details;
  }
}

Dans quelle mesure l'estimation est-elle précise ?

Il est difficile de passer à côté du fait que les données que vous obtenez de la fonction ne sont qu'une estimation de l'espace utilisé par une origine. C’est juste là dans le nom de la fonction ! Les valeurs usage et quota n'ont pas vocation à être stables. Nous vous recommandons donc de prendre en compte les éléments suivants:

  • usage reflète le nombre d'octets qu'une origine donnée utilise efficacement pour les données de même origine, qui peuvent à leur tour être affectées par des techniques de compression internes, des blocs d'allocation de taille fixe pouvant inclure de l'espace inutilisé et la présence d'enregistrements "tombstone" qui peuvent être créés temporairement après une suppression. Pour éviter les fuites d'informations de taille exactes, les ressources opaques multi-origines enregistrées localement peuvent contribuer à des octets de remplissage supplémentaires par rapport à la valeur usage globale.
  • quota reflète la quantité d'espace actuellement réservée pour une origine. Cette valeur dépend de certains facteurs constants, tels que la taille globale de l'espace de stockage, mais également d'un certain nombre de facteurs potentiellement volatils, comme la quantité d'espace de stockage actuellement inutilisée. Ainsi, si d'autres applications sur un appareil écrivent ou suppriment des données, l'espace que le navigateur est prêt à consacrer à l'origine de votre application Web changera probablement.

Le présent: détection de fonctionnalités et solutions de secours

estimate() est activé par défaut à partir de Chrome 61. Firefox teste actuellement navigator.storage, mais depuis août 2017, il n'est plus activé par défaut. Vous devez activer la préférence dom.storageManager.enabled pour la tester.

Lorsque vous utilisez des fonctionnalités qui ne sont pas encore prises en charge dans tous les navigateurs, la détection de fonctionnalités est indispensable. Vous pouvez combiner la détection de fonctionnalités et un wrapper basé sur la promesse en plus des anciennes méthodes navigator.webkitTemporaryStorage pour fournir une interface cohérente comme suit:

function storageEstimateWrapper() {
  if ('storage' in navigator && 'estimate' in navigator.storage) {
    // We've got the real thing! Return its response.
    return navigator.storage.estimate();
  }

  if ('webkitTemporaryStorage' in navigator &&
      'queryUsageAndQuota' in navigator.webkitTemporaryStorage) {
    // Return a promise-based wrapper that will follow the expected interface.
    return new Promise(function(resolve, reject) {
      navigator.webkitTemporaryStorage.queryUsageAndQuota(
        function(usage, quota) {resolve({usage: usage, quota: quota})},
        reject
      );
    });
  }

  // If we can't estimate the values, return a Promise that resolves with NaN.
  return Promise.resolve({usage: NaN, quota: NaN});
}