Cross-Origin-Opener-Policy: same-origin vs same-origin-allow-popups vs unsafe-none (tested)
COOP decides whether a page shares a browsing context group with the windows it opens or was opened by. When the link is cut, window.opener becomes null and the opener's handle reports the popup as closed. We tried every combination.
1. Did the opener and popup stay connected?
"Kept" = popup saw window.opener and the opener's w.closed was false. "Cut" = window.opener === null and w.closed === true while the popup was still open.
| Opener COOP ↓ / popup COOP → | (none) or unsafe-none | same-origin-allow-popups | same-origin |
|---|---|---|---|
| (none) | Kept | Cut | Cut |
same-origin-allow-popups | Kept | Kept if same origin, else cut | Cut |
same-origin | Cut | Cut | Kept if same origin, else cut |
Same site on another port counted as cross-origin every time: COOP compares origins, not sites (unlike CORP same-site). A popup sending unsafe-none behaved exactly like one sending no header.
2. Why your OAuth / payment popup broke
- Your page sends
COOP: same-originand opens a sign-in or checkout popup on another origin: the link is cut, so the popup can'tpostMessageback viawindow.opener, and your "is the popup closed?" polling seesclosed === trueimmediately. - Fix on the opener:
same-origin-allow-popups. In our test it kept the link to popups that send no COOP (most providers) and protected the page from being opened by others with a handle. - The provider's page itself sending
same-origincuts the link no matter what you send. Nothing on your side fixes that; use a redirect flow instead.
# nginx: pages that open third-party popups
add_header Cross-Origin-Opener-Policy same-origin-allow-popups always;
# pages that open nothing cross-origin (strongest isolation)
add_header Cross-Origin-Opener-Policy same-origin always;
Why send it at all: cutting the opener link stops other sites from keeping a handle to your window (tab-nabbing, window.frames.length-style leaks), and same-origin plus Cross-Origin-Embedder-Policy is what unlocks SharedArrayBuffer. Build it with the rest in the free security headers generator.
Frequently asked questions
Why is window.opener null in my popup?
One side sends Cross-Origin-Opener-Policy that cuts the link: in our Chrome test any same-origin COOP, or a popup sending same-origin-allow-popups to a cross-origin opener, made window.opener null.
Why does popup.closed return true while the popup is open?
Same cause. When COOP cuts the link, the opener's handle reports closed === true immediately; we saw it in every 'cut' combination.
What is the difference between COOP same-origin and same-origin-allow-popups?
same-origin cut the link to every cross-origin popup and to popups without COOP; same-origin-allow-popups kept popups that send no COOP (or unsafe-none) connected.
Does COOP same-site exist?
No. In our test another port on the same host was treated as cross-origin; there is no same-site value.
Is COOP unsafe-none the same as no header?
Yes, sent by the popup it behaved identically to no header in all 9 combinations we tried.
Written with AI assistance. Measured on 2026-10-01 in Chromium 153 (full build), fresh context per row: a page on localhost:8992 sent each Cross-Origin-Opener-Policy value and opened a popup with window.open() to the same origin, the same site on another port (localhost:8993) and another site (127.0.0.1:8993), each popup sending each COOP value; we checked window.opener in the popup and w.closed in the opener. 36 combinations. The tab-nabbing / cross-window leak and cross-origin-isolation (COEP, SharedArrayBuffer) remarks are from the spec and Chrome docs, not this test. PlainHeaders is our extension; the other methods are free.