Concept in Storage
Eviction and best-effort storage
Published
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.
How it works
Section titled “How it works”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.
Safari’s seven-day rule
Section titled “Safari’s seven-day rule”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 browsing
Section titled “Private browsing”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.
Observed behaviour
Section titled “Observed behaviour”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 reportsusagenear zero for the origin whilequotais unchanged, because the quota is a share of the disk and the bucket is simply empty.caches.keys()resolves to[]andindexedDB.databases()to[](BCDapi.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 nextactivate.- Safari’s seven-day deletion also removes the service worker registration, so a visit after the
deadline starts with
navigator.serviceWorker.controller === nulland theinstallevent 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.
See also
Section titled “See also”- Storage Standard: buckets and eviction (whatwg.org)
- Updates to Storage Policy (webkit.org)
- Full Third-Party Cookie Blocking and More (webkit.org)
- StorageManager.persist() and persisted()
- StorageManager.estimate()
- Cache Storage API
Specifications
| Specification | Status |
|---|---|
| Persistent Storage | W3C |
- Legend
- Yes
- Partial
- Flag
- No
- Unknown
| Browser / Platform | Support | Versions | Confidence | Source | Notes |
|---|---|---|---|---|---|
| Chrome (Desktop) | Yes | 55 | high | source | — |
| Chrome (Android) | Yes | 55 | high | source | 1 |
| Edge (Desktop) | Yes | 79 | high | source | 2 |
| Firefox (Desktop) | Yes | 57 | high | source | — |
| Firefox (Android) | Yes | 57 | high | source | 3 |
| Safari (macOS) | Yes | 15.2 | high | source | — |
| Safari (iOS) | Yes | 15.2 | high | source | 4 |
| Samsung Internet | Yes | 6.0 | high | source | 5 |
| WebView (Android) | Yes | 55 | high | source | 6 |
- Derived by browser-compat-data mirroring from Chrome.
- Derived by browser-compat-data mirroring from Chrome.
- Derived by browser-compat-data mirroring from Firefox.
- Derived by browser-compat-data mirroring from Safari.
- Derived by browser-compat-data mirroring from Chrome Android.
- Derived by browser-compat-data mirroring from Chrome Android.