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

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 つのカテゴリに分けることができます。ウェブ アプリケーションの読み込みに必要なコアデータと、アプリケーションの読み込まれた後の有意義なユーザー操作に必要なデータの 2 つです。

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

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

過去: window.webkitStorageInfonavigator.webkitTemporaryStorage

ブラウザはこれまで、非常に古い(非推奨の)window.webkitStorageInfo や、まだ古いものの標準ではない navigator.webkitTemporaryStorage など、接頭辞付きインターフェースを介してこのタイプのイントロスペクションをサポートしてきました。これらのインターフェースは有用な情報を提供しましたが、ウェブ標準としての将来はありません。

そこで出番となるのが、WHATWG Storage Standard です。

今後: navigator.storage

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

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

これは navigator.storage.estimate() メソッドで結合され、navigator.webkitTemporaryStorage.queryUsageAndQuota() の最新の代わりとして機能します。estimate() は同様の情報を返しますが、Promise ベースのインターフェースを公開します。これは他の最新の非同期 API と整合しています。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});
}