Service Worker Popup Capability Delegation
Demonstrates how a Service Worker handling a notification click can delegate transient user activation to an existing window client via client.postMessage(message, {delegate: 'popup'}), enabling the client to open auxiliary popup windows without being blocked by the popup blocker.
1. Setup & Prerequisites / Enablement
Requirements: Chrome 157+ with chrome://flags/#enable-experimental-web-platform-features (or --enable-features=CapabilityDelegationPopup).
Service Worker: Registering...
Notification permission: Checking... Request permission
Popup delegation support: Checking...
2. Primary Demo
Delay notification by 5.2s so the button click's 5s transient user activation expires first
Call await client.focus() before delegating
Primary Demo: Delegate Popup on Click
3. Boundary / Error Test Cases
Control: No Delegation — Notification click posts message without {delegate: 'popup'}; window.open() is blocked.
Single-Use Receiver Token — Client calls window.open() twice; 1st opens, 2nd is blocked.
Single-Use Sender Token — SW calls client.postMessage(..., {delegate: 'popup'}) twice; 2nd throws NotAllowedError.
Expired Receiver Token (1.5s delay) — Client waits 1.5s before calling window.open(); popup is blocked.
SW postMessage Without Notification Click — Calls client.postMessage(..., {delegate: 'popup'}) immediately without notification click; throws NotAllowedError.
Unsupported Capability (fullscreen) — SW calls client.postMessage(..., {delegate: 'fullscreen'}); throws NotSupportedError.
Frame window.postMessage({delegate: 'popup'}) — Calls window.postMessage from frame; throws NotSupportedError.
4. Activity Log
Clear log
Ready.
Example Code
Service Worker (service-worker.js)
self.addEventListener('notificationclick', (event) => {
event.notification.close();
event.waitUntil((async () => {
const clientList = await self.clients.matchAll({type: 'window'});
if (clientList.length > 0) {
// 1. Focus existing window (optional; AllowWindowInteraction is decoupled from popup token)
await clientList[0].focus();
// 2. Delegate popup capability token via postMessage
try {
clientList[0].postMessage(
{type: 'open-popup', payload: event.notification.data},
{delegate: 'popup'}
);
} catch (err) {
// Fallback if popup capability delegation is unsupported in the browser
if (self.clients.openWindow) {
await self.clients.openWindow('index.html');
}
}
} else if (self.clients.openWindow) {
// Fallback when no client window is currently open
await self.clients.openWindow('index.html?fallback=openWindow');
}
})());
});
Window Client (script.js)
navigator.serviceWorker.addEventListener('message', (event) => {
if (event.data?.type === 'open-popup') {
// 1. Share state directly with auxiliary window
window.lastDelegatedMessage = event.data.payload;
// 2. Open auxiliary popup without popup blocker obstruction
const popup = window.open('popup.html', '_blank', 'width=540,height=620');
}
});