Estimer l'espace de stockage disponible

tl;dr

Chrome 61 (plus de navigateurs à venir) fournit désormais une estimation de l'espace de stockage utilisé par une application Web et de l'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 essentielles nécessaires au chargement de l'application Web et les données nécessaires à une interaction significative de l'utilisateur une fois l'application chargée.

Le premier type de données, qui est nécessaire pour charger votre application Web, comprend du code HTML, JavaScript, CSS et éventuellement des images. Les service workers et l'API Cache Storage fournissent l'infrastructure nécessaire pour enregistrer ces ressources principales, puis les utiliser ultérieurement pour charger rapidement votre application Web, idéalement en contournant entièrement 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 les anciennes 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 de l'autre type 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 écrivez une application Web de retouche d'images, vous pouvez enregistrer une ou plusieurs copies locales d'une image, ce qui permet 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 serait une fonctionnalité essentielle. Chaque application Web pouvant être personnalisée finit par avoir besoin d'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 n'avez plus d'espace ?

Passée: window.webkitStorageInfo et navigator.webkitTemporaryStorage

Les navigateurs ont historiquement accepté ce type d'introspection via des interfaces avec préfixe, telles que window.webkitStorageInfo, très ancienne (et obsolète) et navigator.webkitTemporaryStorage non standard, mais toujours non standard. 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.

À venir: navigator.storage

Dans le cadre des travaux en cours sur Storage Living Standard, quelques API utiles ont été ajouté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 pour les 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 à ce que son espace de stockage soit exempté du nettoyage automatique.

Elle est désormais jointe par la méthode navigator.storage.estimate(), qui remplace modernement navigator.webkitTemporaryStorage.queryUsageAndQuota(). estimate() renvoie des informations similaires, mais expose une interface basée sur une promesse, comme les 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, par exemple, d'IndexedDB ou de l'API Cache Storage) des données suffisamment volumineuses pour dépasser son quota disponible pour une origine donnée, la requête échoue et génère une exception QuotaExceededError.

Estimations de l'espace de stockage en action

La manière exacte 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é une fois chaque opération de stockage terminée. Idéalement, vous devriez alors 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;
  }
}

Quel est le degré de précision de l'estimation ?

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

  • usage reflète le nombre d'octets utilisés efficacement par une origine donnée pour les données de même origine, lesquelles 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" pouvant être créés temporairement après une suppression. Pour éviter la fuite d'informations de taille exacte, les ressources opaques multi-origines enregistrées en local peuvent contribuer à ajouter des octets de marge intérieure à la valeur usage globale.
  • quota reflète la quantité d'espace actuellement réservée à une origine. La valeur dépend de certains facteurs constants, tels que la taille globale de l'espace de stockage, mais aussi d'un certain nombre de facteurs potentiellement volatils, y compris la quantité d'espace de stockage actuellement inutilisée. Ainsi, lorsque d'autres applications d'un appareil écrivent ou suppriment des données, la quantité d'espace que le navigateur est prête à consacrer à l'origine de votre application Web est susceptible de changer.

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

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

Lorsque vous travaillez avec des fonctionnalités qui ne sont pas encore compatibles avec tous les navigateurs, la détection de fonctionnalités est essentielle. Vous pouvez combiner la détection de caractéristiques avec un wrapper basé sur une 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});
}