Estimar o espaço de armazenamento disponível

tl;dr

O Chrome 61, com mais navegadores em breve, agora mostra uma estimativa de quanto armazenamento um app da Web está usando e quanto está disponível por meio de:

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

Apps da Web e armazenamento de dados modernos

As necessidades de armazenamento de um aplicativo da Web moderno ajudam a dividir o que está sendo armazenado em duas categorias: os principais dados necessários para carregar o aplicativo da Web e os dados necessários para uma interação significativa do usuário depois que o aplicativo é carregado.

O primeiro tipo de dado, que é necessário para carregar seu app da Web, consiste em HTML, JavaScript, CSS e, talvez, algumas imagens. Os service workers, junto com a API Cache Storage, fornecem a infraestrutura necessária para salvar esses recursos principais e, em seguida, usá-los mais tarde para carregar rapidamente seu app da Web, de preferência ignorando completamente a rede. Ferramentas que se integram ao processo de build do seu app da Web, como as novas bibliotecas do Workbox ou o sw-precache mais antigo, podem automatizar totalmente o processo de armazenamento, atualização e uso desse tipo de dados.

Mas e quanto ao outro tipo de dados? Esses são recursos que não são necessários para carregar seu app da Web, mas que podem desempenhar um papel crucial na experiência geral do usuário. Se você estiver criando um app da Web de edição de imagens, por exemplo, convém salvar uma ou mais cópias locais de uma imagem, permitindo que os usuários alternem entre as revisões e desfaçam o trabalho. Ou, se você estiver desenvolvendo uma experiência de reprodução de mídia off-line, salvar arquivos de áudio ou vídeo localmente seria um recurso essencial. Todo app da Web que pode ser personalizado precisa salvar alguns tipos de informações de estado. Como saber quanto espaço está disponível para esse tipo de armazenamento em tempo de execução e o que acontece quando você fica sem espaço?

No passado: window.webkitStorageInfo e navigator.webkitTemporaryStorage

Historicamente, os navegadores oferecem suporte a esse tipo de introspecção usando interfaces prefixadas, como a antiga window.webkitStorageInfo (e descontinuada) window.webkitStorageInfo e a navigator.webkitTemporaryStorage não tão antiga, mas que ainda não é padrão. Embora essas interfaces forneçam informações úteis, elas não têm um futuro como padrões da Web.

É aí que entra o Padrão de armazenamento WhatWG.

O futuro: navigator.storage

Como parte do trabalho em andamento no Storage Living Standard (link em inglês), algumas APIs úteis chegaram à interface StorageManager, que é exposta aos navegadores como navigator.storage. Como muitas outras APIs da Web mais recentes, navigator.storage está disponível apenas em origens seguras (veiculadas por HTTPS ou localhost).

No ano passado, apresentamos o método navigator.storage.persist(), que permite que seu aplicativo da Web solicite que o armazenamento seja isento da limpeza automática.

Ela agora é unida ao método navigator.storage.estimate(), que serve como uma substituição moderna para navigator.webkitTemporaryStorage.queryUsageAndQuota(). estimate() retorna informações semelhantes, mas expõe uma interface baseada em promessa, que é compatível com outras APIs assíncronas modernas. A promessa que estimate() retorna é resolvida com um objeto que contém duas propriedades: usage, que representa o número de bytes usados no momento, e quota, que representa o máximo de bytes que podem ser armazenados pela origem atual. Como tudo mais relacionado ao armazenamento, a cota é aplicada em toda a origem.

Se um aplicativo da Web tentar armazenar (por exemplo, IndexedDB ou a API Cache Storage) dados grandes o suficiente para fazer com que uma determinada origem exceda a cota disponível, a solicitação falhará com uma exceção QuotaExceededError.

Estimativas de armazenamento em ação

A forma como você usa estimate() depende do tipo de dados que seu app precisa armazenar. Por exemplo, é possível atualizar um controle na interface para que os usuários saibam quanto espaço está sendo usado após a conclusão de cada operação de armazenamento. O ideal é fornecer uma interface que permita aos usuários limpar manualmente os dados que não são mais necessários. Você pode escrever o código nas linhas de:

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

Qual é a precisão da estimativa?

É difícil perder o fato de que os dados que você recebe da função são apenas uma estimativa do espaço que uma origem está usando. Está ali no nome da função. Os valores usage e quota não precisam ser estáveis. Portanto, é recomendável considerar o seguinte:

  • usage reflete quantos bytes uma determinada origem está usando efetivamente para dados de mesma origem, que, por sua vez, podem ser afetados por técnicas de compactação interna, blocos de alocação de tamanho fixo que podem incluir espaço não utilizado e a presença de registros"tombstone" que podem ser criados temporariamente após uma exclusão. Para evitar o vazamento de informações de tamanho exatas, recursos opacos de origem cruzada salvos localmente podem contribuir com mais bytes de padding para o valor geral de usage.
  • quota reflete a quantidade de espaço atualmente reservado para uma origem. O valor depende de alguns fatores constantes, como o tamanho geral do armazenamento, mas também de vários fatores potencialmente voláteis, incluindo a quantidade de espaço de armazenamento que não está sendo usado no momento. Assim, à medida que outros aplicativos em um dispositivo gravam ou excluem dados, a quantidade de espaço que o navegador está disposto a dedicar à origem do seu app da Web provavelmente mudará.

Presente: detecção de recursos e substitutos

estimate() é ativado por padrão a partir do Chrome 61. O Firefox está testando o navigator.storage, mas, desde agosto de 2017, ele não é ativado por padrão. É necessário ativar a preferência dom.storageManager.enabled para testá-la.

Ao trabalhar com funcionalidades que ainda não são compatíveis com todos os navegadores, a detecção de recursos é essencial. É possível combinar a detecção de recursos com um wrapper baseado em promessa sobre os métodos navigator.webkitTemporaryStorage mais antigos para fornecer uma interface consistente nestas linhas:

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