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.
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.)
| frame | allow attribute | allowsFeature("haptics") | allowsFeature("fullscreen") |
|---|---|---|---|
| A | (none — inherits self) | waiting… | waiting… |
| B | sandbox="allow-scripts" → opaque origin, outside self | waiting… | waiting… |
<!-- once WebHaptics ships, cross-origin embeds opt in exactly like this -->
<iframe src="https://cross-origin.example/widget" allow="haptics"></iframe>