使用可能な保存容量の見積もり

tl;dr

Chrome 61(今後対応ブラウザを増やす予定)では、ウェブアプリが使用しているストレージの容量と利用可能な容量の推定値が次のように表示されるようになりました。

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

最新のウェブアプリとデータ ストレージ

最新のウェブ アプリケーションのストレージのニーズについて考えると、保存されるものを 2 つのカテゴリに分けることができます。ウェブ アプリケーションの読み込みに必要なコアデータと、アプリケーションの読み込み後にユーザーの有意義な操作に必要となるデータです。

最初のデータタイプは、ウェブアプリの読み込みに必要なもので、HTML、JavaScript、CSS、画像で構成されます。Service WorkerCache Storage API は、コアリソースを保存し、後でそのリソースを使用してウェブアプリをすばやく読み込み、ネットワークを完全にバイパスするために必要なインフラストラクチャを提供します。(新しいワークボックス ライブラリや古い sw-precache など、ウェブアプリのビルドプロセスと連携するツールは、このタイプのデータの保存、更新、使用のプロセスを完全に自動化できます)。

しかし、他の種類のデータについてはどうでしょうか。これらのリソースは、ウェブアプリの読み込みには必要ありませんが、全体的なユーザー エクスペリエンスにおいて重要な役割を果たします。たとえば、画像編集ウェブアプリを作成している場合、画像のローカルコピーを 1 つ以上保存して、ユーザーが複数のリビジョンを切り替えて作業を元に戻すことができるようにしたい場合があります。また、オフラインのメディア再生エクスペリエンスを開発する場合は、音声ファイルまたは動画ファイルをローカルに保存することは非常に重要です。パーソナライズ可能なウェブアプリでは、最終的にはなんらかの状態情報を保存する必要があります。このタイプのランタイム ストレージで使用可能な容量と、容量が足りなくなるとどうなるでしょうか。

過去: window.webkitStorageInfonavigator.webkitTemporaryStorage

ブラウザはこれまで、この種のイントロスペクションをプレフィックス付きのインターフェースを介してサポートしてきました。たとえば、非常に古い(非推奨の)window.webkitStorageInfo や、従来型ではないが依然として標準ではない navigator.webkitTemporaryStorage などです。これらのインターフェースは有用な情報を提供しますが、ウェブ標準のような未来はありません。

ここで登場するのが WHATWG ストレージ標準です。

未来: navigator.storage

Storage Living Standard に関する継続的な取り組みの一環として、いくつかの便利な API が StorageManager インターフェースに適用されました。このインターフェースは navigator.storage としてブラウザに公開されます。他の多くの新しいウェブ API と同様に、navigator.storage安全なオリジン(HTTPS または localhost 経由で提供)でのみ利用できます。

昨年、navigator.storage.persist() メソッドを導入しました。これにより、ウェブ アプリケーションからストレージを自動クリーンアップから除外するようにリクエストできます。

現在は、navigator.webkitTemporaryStorage.queryUsageAndQuota() の最新の代替機能である navigator.storage.estimate() メソッドで結合されています。estimate() も同様の情報を返しますが、他の最新の非同期 API との整合性のある Promise ベースのインターフェースを公開しています。estimate() が返す Promise は、usage(現在使用されているバイト数)と quota(現在のオリジンで保存できる最大バイト数)の 2 つのプロパティを含むオブジェクトで解決されます。(ストレージに関連する他のすべてのサービスと同様に、割り当てはオリジン全体に適用されます)。

ウェブ アプリケーションが(IndexedDB や Cache Storage API などを使用して)特定のオリジンを使用可能な割り当てを超えるのに十分な大きさのデータを保存しようとすると、リクエストは QuotaExceededError 例外で失敗します。

ストレージ見積もりの実例

estimate() の正確な使用方法は、アプリが保存する必要があるデータの種類によって異なります。たとえば、インターフェースのコントロールを更新して、各ストレージ オペレーションの完了後に、ユーザーが使用している容量を把握できます。不要になったデータをユーザーが手動でクリーンアップできるインターフェースを提供するのが理想的です。次のような行に沿ってコードを記述できます。

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

この見積もりはどの程度正確ですか?

この関数から返されるデータは、オリジンが使用しているスペースの推定にすぎないことは忘れがちです。関数名の中にあります。usagequota の値は安定性を意図したものではないため、次の点を考慮することをおすすめします。

  • usage は、特定のオリジンが同一オリジンデータに効果的に使用しているバイト数を表します。このバイト数は、内部圧縮技術、未使用スペースを含む可能性がある固定サイズ割り当てブロック、削除後に一時的に作成される「tombstone」レコードの存在の影響を受ける可能性があります。正確なサイズ情報の漏洩を防ぐため、ローカルに保存されたクロスオリジンや不透明リソースにより、usage 値全体にパディング バイトが追加されることがあります。
  • quota は、オリジンに現在予約されているスペースの量を表します。この値は、全体的なストレージ サイズなどの一定の要因によって異なりますが、現在使用されていないストレージ容量など、不安定な可能性があるさまざまな要因にも依存します。したがって、デバイス上の他のアプリケーションがデータの書き込みや削除を行うと、ブラウザがウェブアプリの送信元に費やすことのできる容量が変わる可能性があります。

現在: 機能の検出とフォールバック

Chrome 61 以降、estimate() はデフォルトで有効になります。Firefox では navigator.storage を試験運用していますが、2017 年 8 月の時点では、デフォルトでは有効になっていません。テストするには、dom.storageManager.enabled 設定を有効にする必要があります。

すべてのブラウザでまだサポートされていない機能を使用する場合、機能の検出は必須です。以前の navigator.webkitTemporaryStorage メソッドに加えて、機能検出と Promise ベースのラッパーを組み合わせることで、以下の行に沿って一貫したインターフェースを提供できます。

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