Skip to content

Concept in Storage

Eviction and best-effort storage

Published

Widely available since 2021-12W3C

Every origin’s storage bucket is best-effort until the browser grants persistence, and a best-effort bucket may be deleted whole when the disk runs low, when the browser’s overall ceiling is reached, or, in Safari, when the user has not interacted with the site for seven days of browser use. The unit of deletion is the bucket, so an evicted origin loses its caches, databases, and service worker registration together.

The Storage Standard defines two bucket modes, best-effort and persistent, and requires that a bucket, when cleared, is cleared in its entirety (Storage Standard, whatwg.org). It leaves the trigger and the order to the browser, which is where the engines diverge.

Pressure-driven eviction in Chromium and Firefox

Section titled “Pressure-driven eviction in Chromium and Firefox”

Chromium begins evicting when its overall ceiling, 80 % of total disk size, is exceeded, and Firefox when the disk fills up. Both apply a least-recently-used order: the best-effort origin visited longest ago is cleared first, then the next, until usage is back under the limit (Storage quotas and eviction criteria, developer.mozilla.org). An origin can therefore be evicted while far below its own quota: the trigger is the combined total, and the selection is recency, not size. Persistent buckets are skipped.

WebKit evicts on three triggers: exceeding the overall quota (80 % of disk for a browser app, 20 % for other apps that embed web content), system storage pressure, and Intelligent Tracking Prevention (Updates to Storage Policy, webkit.org). The ITP trigger, introduced in iOS 13.4 and Safari 13.1, deletes an origin’s script-writable storage after seven days of Safari use without user interaction on the site. The clock counts days on which Safari was used, not calendar days, and covers IndexedDB, localStorage, sessionStorage, media keys, and service worker registrations and caches (Full Third-Party Cookie Blocking and More, webkit.org). An origin is excluded from eviction while it has an open page or while its bucket is persistent.

Web apps added to the Home Screen run outside Safari and keep their own counter of days of use, so an installed app that is opened resets its clock; Apple states it does not expect first-party data in such an app to be deleted.

Private windows are a separate lifecycle. Chrome’s Incognito reduces the origin quota to about 5 % of disk and discards the data when the last Incognito window closes (Storage for the web, web.dev); Firefox and Safari likewise clear private-session storage when the session ends. None of this is LRU eviction, and persist() does not change it.

Eviction leaves no event in the page. The only signals are indirect, and each one is checkable without special tooling.

  • navigator.storage.estimate() after eviction reports usage near zero for the origin while quota is unchanged, because the quota is a share of the disk and the bucket is simply empty.
  • caches.keys() resolves to [] and indexedDB.databases() to [] (BCD api.IDBFactory.databases: Chrome 72, Firefox 126, Safari 14) for an origin whose bucket was cleared, which is how a service worker can detect that a precache it installed is gone and rebuild it on the next activate.
  • Safari’s seven-day deletion also removes the service worker registration, so a visit after the deadline starts with navigator.serviceWorker.controller === null and the install event runs again.

The check below runs on page load and distinguishes “evicted” from “first visit” by a marker in localStorage, which Safari clears in the same sweep but Chromium and Firefox leave alone when they evict the quota-managed bucket.

async function detectEviction() {
if (!('caches' in self) || !('storage' in navigator)) {
return 'unknown'; // no quota-managed storage here: nothing to detect
}
const names = await caches.keys();
const seenBefore = localStorage.getItem('shell-installed') === '1';
if (names.length === 0 && seenBefore) {
return 'evicted'; // bucket cleared under pressure; rebuild the precache
}
localStorage.setItem('shell-installed', '1');
return names.length === 0 ? 'first-visit' : 'intact';
}

An 'evicted' result is the moment to re-run the precache and, if the app has earned it, to call navigator.storage.persist() so the next sweep skips this origin.

Specifications

SpecificationStatus
Persistent StorageW3C
  • Legend
  • Yes
  • Partial
  • Flag
  • No
  • Unknown
Browser / PlatformSupportVersionsConfidenceSourceNotes
Chrome (Desktop)Yes55highsource—
Chrome (Android)Yes55highsource1
Edge (Desktop)Yes79highsource2
Firefox (Desktop)Yes57highsource—
Firefox (Android)Yes57highsource3
Safari (macOS)Yes15.2highsource—
Safari (iOS)Yes15.2highsource4
Samsung InternetYes6.0highsource5
WebView (Android)Yes55highsource6
  1. Derived by browser-compat-data mirroring from Chrome.
  2. Derived by browser-compat-data mirroring from Chrome.
  3. Derived by browser-compat-data mirroring from Firefox.
  4. Derived by browser-compat-data mirroring from Safari.
  5. Derived by browser-compat-data mirroring from Chrome Android.
  6. Derived by browser-compat-data mirroring from Chrome Android.

Source data: /compatibility/persistent-storage.json · Global usage: 93 % (StatCounter 2026-05)

Source: spec · MDN · Last verified 2026-10-03 · Confidence: high (computed from sources)