v151 · Permissions Policy · Security
Production Recipe
Three complete app flows — chat embed, media player, and widget host — showing where to allow focus-without-user-activation in iframes, where to deny it, and why. Each recipe includes HTTP headers, a meta-equivalent, the iframe allow attribute, live detection code, and a try-it sandbox.
Scenario — Chat embed in a host page
Goal: A third-party chat widget embedded in an iframe should NOT be able to steal focus from the host page. The chat can receive focus when the user clicks it, but must not call
focus() autonomously.
/* HTTP header — on the HOST page's response */
Permissions-Policy: focus-without-user-activation=()
// Empty list = deny all, including same-origin frames
/* Or, allow same-origin but deny the chat iframe */
Permissions-Policy: focus-without-user-activation=(self)
/* HTML — the chat iframe gets no focus-without-user-activation */
<iframe
src="https://chat.example.com/widget"
allow="microphone; camera"
/* note: no focus-without-user-activation in allow list */
></iframe>
Live detection — host page context
—
Implementation steps
- Add
Permissions-Policy: focus-without-user-activation=(self)to the host page's HTTP response headers. - Do NOT include
focus-without-user-activationin the chat iframe'sallowattribute. - In your chat widget code, only call
element.focus()inside a direct user-event handler (click,keydown). - Test by calling
document.activeElement.focus()from asetTimeout— it should be silently ignored in the iframe.
Scenario — Media player embedding a progress widget
Goal: A video player host allows an embedded chapter-navigation iframe to programmatically move focus to the next chapter button when the current chapter ends — this is a legitimate UX need driven by media events, not user input.
/* HTTP header — host page */
Permissions-Policy: focus-without-user-activation=(self "https://chapters.example.com")
/* HTML */
<iframe
src="https://chapters.example.com/nav"
allow="focus-without-user-activation"
></iframe>
/* Inside chapters iframe — now allowed to call focus() from media events */
video.addEventListener('ended', () => {
nextChapterButton.focus(); // permitted by policy
});
Live detection — this frame
—
Implementation steps
- List the chapter iframe's origin explicitly in the policy:
focus-without-user-activation=(self "https://chapters.example.com"). - In the chapter iframe's HTML, add
allow="focus-without-user-activation". - Both conditions must be met: header grants AND iframe attribute grants.
- Log when
focus()is called from non-user-event paths so you can audit intent over time.
Scenario — Widget marketplace (multiple third-party iframes)
Goal: A dashboard allows users to add third-party widgets. Only first-party dashboard widgets get focus control; all third-party widgets are denied, preventing any widget from disrupting the user's active input.
/* HTTP header — dashboard host */
Permissions-Policy: focus-without-user-activation=(self)
/* First-party widget — same origin, implicitly allowed by (self) */
<iframe src="/widgets/calendar"></iframe>
/* Third-party widget — cross-origin, not in allowlist */
<iframe
src="https://third-party.example/widget"
/* focus-without-user-activation intentionally absent from allow */
></iframe>
/* CSP can complement this — sandbox blocks even more */
<iframe
src="https://untrusted.example/widget"
sandbox="allow-scripts allow-same-origin"
></iframe>
Policy probes
—
implementation reference
Need the exact API surface, compatibility boundaries, errors, lifecycle, and source links? Read the matching gendn reference ↗