A widget served from widget.example is loaded in an iframe on two different top-level sites. Modern browsers no longer let it read the same client-side storage in both. What changed, and what is the unit that quota and eviction now apply to?
answer
- origin alone is no longer the key
- top-level site is part of the identity
- one store per embedder
- quota counted per partition too
- gesture-gated escape hatch exists
basics
~20 sBrowsers now key client storage on the top-level site plus the frame's origin, not the origin alone. An embedded widget therefore gets a separate store, a separate quota and a separate eviction fate on every site that embeds it, and cannot carry state between embedders.
solid answer
~50 sThis is third-party storage partitioning. The identity that storage hangs off is no longer just the origin; it is a storage key formed from the top-level site plus the frame's origin. So `widget.example` inside a frame on `siteA.example` and the same widget on `siteB.example` address two entirely separate stores — different IndexedDB databases, different `CacheStorage`, different `localStorage`, and separate quota accounting and eviction for each. Nothing written in one is visible in the other, and the widget loaded as a top-level page sees a third store again. Chromium shipped this in Chrome 115; Firefox and Safari partition by default under their own tracking-protection models. The practical consequence is that cross-site identity and cross-site caching by an embedded third party stopped working by design. The Storage Access API is the sanctioned escape hatch, but it needs a user gesture and a browser grant, and its coverage beyond cookies is still uneven.
go deeper
Know that an iframe from a third-party origin no longer shares storage with the same origin elsewhere: what it can read depends on which site it is embedded in.
Be ready to explain that the storage key combines the top-level site with the frame's origin, and to list what that covers — Web Storage, IndexedDB, the Cache API, service workers and BroadcastChannel.
Show the consequences: quota and eviction are accounted per partition, cross-embedder continuity has to move server-side, and the Storage Access API is a gesture-gated grant with uneven coverage rather than a switch you can rely on.
Own the strategy for an embeddable product: what identity means without shared client state, what the per-embedder cold start costs, and how to migrate customers off assumptions that partitioning has already invalidated.
## The identity storage hangs off changed For most of the web's history, client-side storage was keyed on origin alone. An iframe from `widget.example` opened the same IndexedDB database no matter which page embedded it, which is exactly what made cross-site tracking through storage possible: write an identifier once, read it back everywhere the widget appears. Modern browsers changed the key. Storage is now addressed by a **storage key**, which combines the frame's origin with the **top-level site** it is embedded under. The same origin in two different embedding contexts gets two unrelated stores. So for a widget from `widget.example`: - embedded on `siteA.example` → one store - embedded on `siteB.example` → a second, entirely separate store - loaded directly as a top-level page → a third The frame code is identical in all three; the storage it reaches is not. ## What is partitioned Partitioning is not limited to one API. It covers the script-visible storage surface an embedded frame can use, including `localStorage` and `sessionStorage`, IndexedDB, `CacheStorage`, service worker registrations, and coordination primitives such as `BroadcastChannel` and the Web Locks API. Cookies are partitioned too, but under their own separate rules and attributes. Note what that means for messaging: two frames of the same origin on two different top-level sites cannot see each other's storage and cannot reach each other through a `BroadcastChannel` either. The isolation is deliberate and fairly complete. ## Quota and eviction follow the key This is the second half of the question and the part candidates usually miss. Because everything is keyed by the storage key, quota and eviction are accounted per key as well: - Each partition has its own usage and its own share of the ceiling. `navigator.storage.estimate()` called inside the frame reports the partition's numbers, not a global total for `widget.example`. - Filling the widget's storage on one embedding site does not consume the budget it has on another. - Eviction of one partition leaves the others alone: the widget can be wiped on `siteA.example` while its data on `siteB.example` survives untouched. One embedded copy of your widget being evicted therefore says nothing about the others, and there is no single number that describes "how much disk this widget uses across the web". ## Browser status Chromium shipped third-party storage partitioning in Chrome 115. Firefox arrived at the same place through Total Cookie Protection and state partitioning, and Safari partitions or blocks third-party state by default under its tracking-prevention rules. The details differ, but the design conclusion is the same everywhere: an embedded third party cannot assume shared state across embedders. ## The escape hatch, and its limits The Storage Access API exists for the legitimate cases — an embedded document that is the same organisation as the top-level site, a federated widget where the user genuinely expects continuity: ```js if (await document.hasStorageAccess() === false) { await document.requestStorageAccess(); // requires a user gesture } ``` `document.requestStorageAccess()` must be called from a user gesture inside the embedded frame, and the browser decides whether to grant it — a grant is not guaranteed and is not permanent. Historically it restores unpartitioned **cookie** access; extensions that cover other storage types are newer and support differs across browsers, so do not architect on the assumption that IndexedDB or `localStorage` will be un-partitioned on request. ## Designing under partitioning The honest design response is to stop treating the client as the place where cross-site identity lives. **Move continuity to the server.** If the same user should be recognised on two embedding sites, that recognition belongs to an authenticated server-side session, not a value cached in an iframe. **Expect a cold start per embedder.** Any bootstrap the widget caches will be rebuilt once per top-level site. Keep it small, and lean on the HTTP cache — which is a separate mechanism from quota-managed storage — for the static assets themselves. **Budget per partition.** A widget that assumed one shared quota across the web now has one budget per embedder. That is usually more total room, but each individual partition is subject to its own eviction. **Test in an embedded context.** The most common way this defect ships is developing the widget as a top-level page, where nothing is partitioned, and discovering the behaviour only once a real customer embeds it.
- Does navigator.storage.estimate() inside a partitioned iframe report the widget's total usage across all sites?No. Quota accounting follows the storage key, so the estimate describes only that partition — the widget's storage under this one top-level site. There is no API that aggregates a third party's usage across every embedder, and one partition being close to its ceiling says nothing about the others.
- Two iframes of the same origin sit on two different top-level sites. Can they coordinate through BroadcastChannel?No. BroadcastChannel is partitioned along with the rest of storage, so channels in different partitions never see each other's messages. Coordination across embedders has to go through a server, or through explicit postMessage to a shared opener or parent that both sides already trust.
- When is the Storage Access API an appropriate answer, and what does it require?When the embedded document and the top-level site are genuinely related and the user expects continuity, such as a first-party service embedded under a partner domain. It requires calling document.requestStorageAccess() from a user gesture inside the frame, and the browser may refuse. Grants are not permanent, and coverage beyond cookies is uneven, so keep a working fallback.
saying these in an interview costs you the question
- Assumes the same origin always reaches the same storage
- Thinks only cookies are partitioned, not other storage
- Believes one quota is shared across all embedding sites
- Expects requestStorageAccess to work without a user gesture
- Tests the widget only as a top-level page