tl;dr
O Chrome 61, e mais navegadores no futuro, agora expõe uma estimativa da quantidade de armazenamento que um app da Web está usando e quanto está disponível por:
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
Ao pensar nas necessidades de armazenamento de um aplicativo da Web moderno, é útil dividir o que está sendo armazenado em duas categorias: os dados principais necessários para carregar o aplicativo da Web e os dados necessários para uma interação significativa do usuário após o carregamento.
O primeiro tipo de dados, 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 usá-los
posteriormente 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
Workbox ou as
sw-precache
mais antigas,
podem automatizar totalmente o processo de armazenamento, atualização e uso desse tipo de
dados.
Mas e o 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. Por exemplo, se você estiver criando um app da Web de edição de imagens, salve uma ou mais cópias locais de uma imagem, permitindo que os usuários alternem entre 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 será um recurso essencial. Todo app da Web que pode ser personalizado acaba precisando salvar algum tipo de informações de estado. Como você sabe quanto espaço está disponível para esse tipo de armazenamento no ambiente de execução e o que acontece quando fica sem espaço?
Passado: window.webkitStorageInfo
e navigator.webkitTemporaryStorage
Historicamente, os navegadores oferecem suporte a esse tipo de introspecção por meio de interfaces
prefixadas, como a muito antiga (e descontinuada)
window.webkitStorageInfo
e a navigator.webkitTemporaryStorage
não tão antiga, mas 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 (link em inglês).
O futuro: navigator.storage
Como parte do trabalho contínuo no Storage Living Standard, 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
(exibidas via HTTPS ou localhost).
No ano passado, apresentamos o
método navigator.storage.persist()
, que permite que seu aplicativo da Web solicite que o armazenamento dele seja
isento da limpeza automática.
Agora ele é unido pelo 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 promessas (em inglês), 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.
Assim como tudo o que está relacionado ao armazenamento, a cota é aplicada a uma origem inteira.
Se um aplicativo da Web tentar armazenar (por exemplo, o IndexedDB ou a
API Cache Storage) dados grandes o suficiente para exceder a
cota disponível em uma determinada origem, a solicitação falhará com uma exceção
QuotaExceededError
.
Estimativas de armazenamento em ação
A forma exata como você usa estimate()
depende do tipo de dados que seu app precisa
armazenar. Por exemplo, atualize um controle na interface que informe aos usuários 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 código assim:
// 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á bem 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 da mesma origem, que, por sua vez, podem ser afetados por técnicas de compactação internas, 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 exato, os recursos opacos de origem cruzada salvos localmente podem contribuir com mais bytes de padding para o valorusage
geral.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 não utilizado no momento. Assim, como 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á.
O presente: detecção de recursos e substitutos
O estimate()
está ativado por padrão a partir do Chrome 61. O Firefox
está fazendo experimentos com 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. Você pode combinar a detecção de recursos com um
wrapper baseado em promessa, além dos métodos navigator.webkitTemporaryStorage
mais antigos
para fornecer uma interface consistente com estas linhas de:
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});
}