v156 · web haptics · edge & failure model

Activation & policy gates

Every playHaptics() call runs a gauntlet the design doc spells out — fenced-frame check, the "haptics" permissions policy, sticky user activation, intensity normalization — and every early return is silent: no throw, no rejected promise, no signal. This bench reads each gate's real state in your browser and shows exactly where a call would stop today.

The gate pipeline, read live

Gate 1 — fenced frame

Haptics are suppressed inside fenced frames, like navigator.vibrate(). The real probe: window.fence only exists inside a fenced frame.

reading…

Gate 2 — "haptics" permissions policy (default allowlist self)

Registered in permissions_policy_features.json5 with depends_on: ["WebHaptics"] — so the policy name only exists once the flag is on. Probed via the real document.featurePolicy introspection API.

reading…

Gate 3 — sticky user activation

The imperative API requires sticky activation: navigator.userActivation.hasBeenActive, which flips true on your first interaction with the page and stays true. Transient activation (isActive) expires after a few seconds; sticky does not.

reading…

Gate 4 — intensity normalization

The IDL takes optional double intensity = 1. WebIDL double rejects non-finite values with a TypeError at the binding; finite out-of-range values pass the binding and are clamped to [0, 1] in both the renderer and the browser process.

Type a value (try 2.5, -1, NaN, Infinity) and evaluate.

Not run yet.

Delegation: how allow="haptics" will behave

A default allowlist of self means same-origin iframes inherit access and cross-origin iframes must be delegated with allow="haptics". The haptics policy isn't registered until the flag ships, so to prove the mechanism for real, the two iframes below also report a policy that is registered everywhere today (fullscreen). Frame A is plain same-origin and inherits self; frame B is sandboxed into an opaque origin, which falls outside every default-self allowlist — the very same denial an undelegated cross-origin iframe gets — and you can watch the introspection flip. (An explicit allow="fullscreen 'none'" flips it too, but Chrome logs report-only violation notices for it, so the quiet opaque-origin route demos better.)

Live reports from the two iframes (via postMessage)
frameallow attributeallowsFeature("haptics")allowsFeature("fullscreen")
A(none — inherits self)waiting…waiting…
Bsandbox="allow-scripts" → opaque origin, outside selfwaiting…waiting…
<!-- once WebHaptics ships, cross-origin embeds opt in exactly like this -->
<iframe src="https://cross-origin.example/widget" allow="haptics"></iframe>

see also