In a browser page, `self.crossOriginIsolated` is false and `SharedArrayBuffer` is unavailable to your code. Which two response headers does the top-level document need in order to change that, and what typically breaks once you send them?
answer
- Post-Spectre gate on shared memory
- Two headers, opener and embedder
- One runtime flag to feature-detect
- Third-party assets must opt in
- Popups lose window.opener
basics
~10 sThe document must send Cross-Origin-Opener-Policy: same-origin and Cross-Origin-Embedder-Policy: require-corp. Then self.crossOriginIsolated becomes true and SharedArrayBuffer works. The cost: cross-origin subresources without CORP or CORS headers stop loading, and popups lose their opener link.
solid answer
~40 sYou need both `Cross-Origin-Opener-Policy: same-origin` and `Cross-Origin-Embedder-Policy: require-corp` (or `credentialless` where supported) on the top-level document. Together they make the page **cross-origin isolated**, which you can verify at runtime with `self.crossOriginIsolated`; that flag is the gate browsers put in front of `SharedArrayBuffer` and high-resolution timers after Spectre. The breakage is real and usually discovered late. Under COEP, every cross-origin subresource — images, scripts, fonts, iframes — must opt in with `Cross-Origin-Resource-Policy` or be fetched with CORS, so third-party assets that send neither simply fail to load. Under COOP, the page is severed from cross-origin popups, so `window.opener` is null and OAuth or payment windows that post back to the opener stop working. Both apply to workers too, which inherit the isolation.
code
javascript · 9 lines// Gate the shared-memory path on the flag the platform actually checks.
if (self.crossOriginIsolated) {
const sab = new SharedArrayBuffer(1024);
const counter = new Int32Array(sab);
Atomics.store(counter, 0, 1);
console.log('shared memory available', Atomics.load(counter, 0));
} else {
console.log('not isolated - fall back to transferring ArrayBuffers');
}go deeper
Know that SharedArrayBuffer is not available on an ordinary page and that unlocking it is a server header decision, not something you can switch on from JavaScript.
Name both headers and their values, explain that self.crossOriginIsolated is the flag to feature-detect, and describe why cross-origin subresources start failing under require-corp.
Walk a real rollout: report-only headers first, an inventory of third-party assets needing CORP or CORS, a plan for OAuth popups that relied on window.opener, and a fallback path for routes that lose isolation.
Own the decision itself — weigh one capability against the whole third-party embedding and popup story, decide whether transferred buffers deliver the same result with no deployment cost, and set who is accountable for keeping every HTML route isolated.
## Why there is a gate at all `SharedArrayBuffer` gives two threads a genuinely shared block of memory. Combined with a way to measure time precisely, shared memory can be turned into a very high-resolution timer, which is the ingredient speculative-execution side-channel attacks need to read memory they should not see. Browsers responded by removing `SharedArrayBuffer` from ordinary pages and re-offering it only to documents that have proved they are not sharing a process with untrusted cross-origin content. That proof is **cross-origin isolation**. So the question is never "how do I enable SharedArrayBuffer" as a flag. It is "how do I make this document isolated", and the answer is two headers on the document response. ## The two headers ``` Cross-Origin-Opener-Policy: same-origin Cross-Origin-Embedder-Policy: require-corp ``` `Cross-Origin-Opener-Policy: same-origin` cuts the document's scripting relationship with windows it opens or was opened by, unless they are same-origin and carry the same policy. `Cross-Origin-Embedder-Policy: require-corp` says the document refuses to embed cross-origin resources that have not explicitly agreed to be embedded. `credentialless` is an alternative COEP value that loads cross-origin subresources without credentials instead of demanding opt-in, which is easier to deploy but is not supported everywhere — check the browsers you must support. When both are in effect the browser sets `self.crossOriginIsolated` to `true`. That property is the one thing worth feature-detecting, because it is the actual condition the platform checks: ```js if (self.crossOriginIsolated) { const sab = new SharedArrayBuffer(1024); } ``` Isolation is per-document and it is inherited by the workers that document creates, so a dedicated worker of an isolated page can construct and receive a `SharedArrayBuffer` as well. ## What breaks, in the order teams discover it **Third-party subresources.** Under `require-corp`, any cross-origin image, script, stylesheet, font or iframe must either be fetched with CORS or send `Cross-Origin-Resource-Policy: cross-origin`. Analytics beacons, ad tags, map tiles, avatar CDNs and embedded video players commonly send neither, and they stop loading outright. You do not get a degraded experience, you get a failed fetch. Nested iframes are worse: a cross-origin frame also has to send its own COEP header to be embeddable in an isolated page, which means the third party has to cooperate. **Popups and the opener chain.** `COOP: same-origin` means a cross-origin popup you open has `window.opener === null`, and a popup opened *by* someone else cannot reach into your page. Any OAuth-in-a-popup, payment-provider window, or SSO flow that returns a result by calling `window.opener.postMessage(...)` breaks. The usual repair is to move those flows to a redirect-based handoff or to a same-origin bridge document. **Deploying it everywhere.** The headers must be on the document response, which means the server or CDN that serves HTML, including error pages and any route rendered by a different service. A single un-isolated route means `crossOriginIsolated` flips false there, and code that assumed shared memory throws. ## Rolling it out without a blind cutover Browsers offer report-only variants — `Cross-Origin-Opener-Policy-Report-Only` and `Cross-Origin-Embedder-Policy-Report-Only` — which evaluate the policy and send violation reports without enforcing it. Ship those first, collect the list of cross-origin resources that would be blocked, negotiate CORP headers or self-host the offenders, and only then enforce. Feature-detect with `self.crossOriginIsolated` in application code and keep a non-shared fallback path, because a page can lose isolation for reasons outside your control — an embedded partner drops a header, or a route is served by a system you do not own. ## The judgment part The honest tradeoff is that cross-origin isolation is a whole-document commitment made for one capability. If the only reason is a WebAssembly module that wants threads, ask first whether the single-threaded build is fast enough, or whether transferring `ArrayBuffer`s between workers gives you the throughput without any header changes at all. Transfer costs nothing to deploy and detaches instead of sharing; shared memory costs you the entire third-party embedding story. Many teams get the performance they wanted from buffer transfer and never need isolation. When you do need it — a heavy media or compute application, an emulator, a threaded Wasm runtime — budget the migration as a cross-team project, not a header change.
- How do you find out which resources will break before you enforce the policy?Deploy the report-only variants first: `Cross-Origin-Opener-Policy-Report-Only` and `Cross-Origin-Embedder-Policy-Report-Only`. The browser evaluates the policy and reports what would have been blocked without actually blocking it, giving you the inventory of cross-origin resources that need `Cross-Origin-Resource-Policy`, CORS, or self-hosting.
- Do workers created by an isolated page need their own headers?A dedicated worker inherits its owner document's isolation, so it can use SharedArrayBuffer without extra headers on the page's own script. The worker script itself is subject to COEP like any other subresource, which in practice means same-origin worker scripts are fine and cross-origin ones need CORP or CORS.
- Your only reason for wanting isolation is a threaded WebAssembly build. What would you check first?Whether the single-threaded build meets the target, and whether the work can be split across several dedicated workers that pass `ArrayBuffer`s by transfer instead of sharing memory. Transfer needs no header changes and no third-party negotiation, so it is worth measuring before committing the whole document to isolation.
- What is the runtime signal that isolation is actually in effect?`self.crossOriginIsolated` — a boolean available in both window and worker global scopes. Feature-detect on it rather than on `typeof SharedArrayBuffer`, because it is the exact condition the platform gates on, and it is the value that tells you whether a fallback path is needed on this particular route.
saying these in an interview costs you the question
- Thinking one header is enough to enable shared memory
- Believing HTTPS alone unlocks SharedArrayBuffer
- Assuming third-party embeds keep working under require-corp
- Forgetting popups lose window.opener under COOP
- Feature-detecting the constructor instead of crossOriginIsolated