要約
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 Worker と Cache Storage API は、これらのコア リソースを保存し、後でウェブアプリをすばやく読み込むために必要なインフラストラクチャを提供します。理想的には、ネットワークを完全にバイパスします。(新しい Workbox ライブラリや古い sw-precache
など、ウェブアプリのビルドプロセスと統合するツールを使用すると、このタイプのデータの保存、更新、使用のプロセスを完全に自動化できます)。
では、他の種類のデータはどうでしょうか。これらは、ウェブアプリの読み込みには必要ありませんが、全体的なユーザー エクスペリエンスにおいて重要な役割を果たすリソースです。たとえば、画像編集ウェブアプリを作成する場合、画像のローカルコピーを 1 つ以上保存して、ユーザーがリビジョンを切り替えたり、作業を元に戻したりできるようにします。オフライン メディア再生機能を開発している場合は、音声ファイルや動画ファイルをローカルに保存することが重要な機能になります。パーソナライズ可能なウェブアプリはすべて、なんらかの状態情報を保存する必要があります。このタイプのランタイム ストレージに使用可能な容量と、容量が不足した場合の影響を確認するにはどうすればよいですか?
過去: window.webkitStorageInfo
、navigator.webkitTemporaryStorage
ブラウザは、非常に古い(非推奨)window.webkitStorageInfo
や、それほど古くないものの標準ではない navigator.webkitTemporaryStorage
などの接頭辞付きインターフェースを介して、このタイプのイントロスペクションをサポートしてきました。これらのインターフェースは有用な情報を提供しましたが、ウェブ標準としての将来はありません。
ここで、WHATWG Storage Standard が登場します。
将来: navigator.storage
Storage Living Standard に関する継続的な作業の一環として、いくつかの便利な API が StorageManager
インターフェースに追加されました。このインターフェースは、navigator.storage
としてブラウザに公開されます。他の多くの新しいウェブ API と同様に、navigator.storage
は安全な(HTTPS または localhost 経由で提供される)オリジンでのみ使用できます。
昨年、navigator.storage.persist()
メソッドが導入されました。このメソッドを使用すると、ウェブ アプリケーションでストレージの自動クリーンアップを免除するようリクエストできます。
navigator.storage.estimate()
メソッドが追加されました。これは navigator.webkitTemporaryStorage.queryUsageAndQuota()
の最新の代替手段です。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;
}
}
推定値の精度はどの程度ですか?
この関数から返されるデータは、オリジンが使用しているスペースの推定値にすぎないことは明らかです。関数名に含まれています。usage
値も quota
値も安定性を意図したものではないため、次の点を考慮することをおすすめします。
usage
は、特定のオリジンが同じオリジンのデータに実際に使用しているバイト数を反映します。これは、内部圧縮手法、未使用のスペースを含む可能性のある固定サイズの割り当てブロック、削除後に一時的に作成される「Tombstone」レコードの存在によって影響を受ける可能性があります。正確なサイズ情報の漏洩を防ぐため、ローカルに保存されたクロスオリジンの不透明リソースは、usage
値全体に追加のパディング バイトを追加する場合があります。quota
は、オリジンに現在予約されているスペースの量を反映します。この値は、ストレージの総サイズなどの一定の要因だけでなく、現在使用されていないストレージ容量など、不安定な要因にも依存します。そのため、デバイス上の他のアプリがデータを書き込んだり削除したりすると、ブラウザがウェブアプリのオリジンに割り当てるスペースの量が変更される可能性があります。
現在: 機能の検出とフォールバック
estimate()
は Chrome 61 以降、デフォルトで有効になっています。Firefox では navigator.storage
を試験運用していますが、2017 年 8 月時点ではデフォルトで有効になっていません。テストするには、dom.storageManager.enabled
設定を有効にする必要があります。
一部のブラウザでまだサポートされていない機能を扱う場合は、機能検出が必須です。以前の navigator.webkitTemporaryStorage
メソッドの上にプロミスベースのラッパーを追加して、次のような一貫したインターフェースを提供できます。
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});
}