demo · v137

SPC Error Probe with User Activation

The v137 rename (SecurityErrorNotAllowedError) only fires on the missing-activation path. This probe adds the third lane from the spec matrix: credentials.create({ payment }) called with a fresh user gesture. Both pre-v137 and v137+ agree here: the result is either a credential or a NotAllowedError — never a SecurityError. Run both lanes to confirm activation is the key variable.

ScenarioSpec outcomePre-v137v137+
same-origin, no activation NotAllowedError NotAllowedError NotAllowedError
cross-origin iframe, no activation NotAllowedError SecurityError ← bug NotAllowedError ✓ v137 fix
cross-origin iframe, with activation resolves / NotAllowedError (dismiss) NotAllowedError NotAllowedError this demo

Lane A — no user activation

Runs immediately on click — the handler itself consumes the gesture, so create() is called without an associated activation. This is the path the v137 rename targets.

click to run…
Expected: SecurityError (pre-v137 cross-origin iframe) | NotAllowedError (v137+ or same-origin)

Lane B — with fresh user activation new lane

Your click IS the user gesture. create() is called synchronously inside the click handler with a fresh activation. Dismiss the browser dialog — you will always see NotAllowedError, never SecurityError.

click to run with a fresh user gesture…
Expected: NotAllowedError (dismiss) or success — same in all Chrome versions
// Lane A — no activation (the v137 fix targets this path):
//   cross-origin iframe pre-v137: SecurityError  ← wrong
//   cross-origin iframe v137+:    NotAllowedError ← correct
//   same-origin (any version):    NotAllowedError
//
// Lane B — with activation (this demo):
//   any origin, any version: resolves | NotAllowedError (user dismiss)
//   → SecurityError NEVER fires on the with-activation path
//
// Key takeaway: if your error handler checks `e.name === 'SecurityError'`
// you were only catching the cross-origin + no-activation path.
// Upgrade to NotAllowedError to handle all affected branches:
//
try {
  const cred = await navigator.credentials.create({ publicKey });
} catch (e) {
  if (e instanceof DOMException && e.name === 'NotAllowedError') {
    handleDenied(); // handles both old SecurityError path and all NotAllowedError paths
  }
}

see also