zebra-spaces: camera permission probe telemetry (firefox NotAllowedError diagnosis)

Ticket 0002 fox 2026-06-08: even with the saved permission removed
AND a bare {video:true} retry, Firefox-on-Fedora returns
NotAllowedError instantly with no prompt. Webcamtests.com works on
the same Firefox session — so the defect is specific to our origin's
runtime state, not the browser or hardware.

Need to distinguish (a) Permissions API state=denied vs prompt vs
granted (b) hidden document vs active (c) user activation
inactive vs active. Each picks a different fix branch — (a) saved
block or about:config pref, (b) bfcache / non-active document,
(c) gesture propagation lost between click and getUserMedia.

Add a permission probe in the catch (runs AFTER getUserMedia
returned so it doesn't burn transient activation needed for a
retry). Logs:

  camera permission probe: state=<perm> vis=<vis> activation=<act>

state = navigator.permissions.query({name:'camera'}).state
vis   = document.visibilityState
act   = navigator.userActivation isActive + hasBeenActive

Next click on blanka's firefox surfaces all three at once.
This commit is contained in:
Russell Ballestrini 2026-06-08 08:02:49 -04:00
parent a933196265
commit 2892a8f6ef
No known key found for this signature in database

View file

@ -4618,6 +4618,30 @@ async function sfuPublishCamera(){
'camera open cancelled: '+(e.name||'Error')+' — '+e.message+
' (constraints: 1280x720@30 16:9'+(exact?' deviceId='+String(exact).slice(0,8):'')+')'
);
/* Diagnostic snapshot — runs AFTER getUserMedia returned, so the
* await doesn't break transient activation for any subsequent
* retry. Captures (1) Permissions API state for camera (what
* Firefox itself says about the permission, separate from
* getUserMedia's return), (2) document visibility (Firefox auto-
* denies for hidden documents), (3) user-activation state (gesture
* propagation). Lets us tell apart "saved deny" / "global pref
* block" / "no gesture" / "hidden doc" without another roundtrip.
* Fox ticket 0002. */
let permState = 'n/a', visState = 'n/a', activation = 'n/a';
try {
if (navigator.permissions && navigator.permissions.query){
const p = await navigator.permissions.query({ name: 'camera' });
permState = p.state;
}
} catch(_){}
try { visState = document.visibilityState || 'n/a'; } catch(_){}
try {
if (navigator.userActivation){
activation = (navigator.userActivation.isActive ? 'active' : 'inactive') +
'/hasBeenActive=' + (navigator.userActivation.hasBeenActive ? 1 : 0);
}
} catch(_){}
logLine('', 'camera permission probe: state='+permState+' vis='+visState+' activation='+activation);
/* OverconstrainedError + a saved deviceId is the canonical "saved
* device gone or renamed" case — retry once without deviceId so the
* user gets default camera rather than a permanent fail. */
@ -8161,8 +8185,8 @@ logLine('', 'ready — pick a handle, type a rendezvous code, enter the space');
<footer style="margin:2.2rem auto 0;font-size:0.65rem;color:#999;line-height:1.7;word-break:break-all;font-family:monospace">
<span id="pi-seal" style="color:#777;cursor:default;user-select:none" title="">page integrity</span> &nbsp;·&nbsp; built <span class="stamp-date">2026-06-08</span><br>
md5 <span class="stamp-md5">dac485556cfbbfd960687ca80326869b</span><br>
sha256 <span class="stamp-sha">733bf1210df3b6fbd314fa4c22811823474d5e71ffef40ff5ccbc79de99c41b1</span><br>
md5 <span class="stamp-md5">f0683ffe29d81f94723827e7a30dfd79</span><br>
sha256 <span class="stamp-sha">82b472dbfb211e43d0e9a38c671fde545e87a242a8dd500115a6837d94b0d137</span><br>
<span style="color:#bbb">hashes are of this page with these two fields zeroed — to verify, blank them and re-hash</span><br>
<span style="color:#bbb">one self-contained file — <strong>save a copy</strong> and verify against these hashes; point at your own servers with ?signal= and ?turncred=, or <a href="host-your-own.html" style="color:#999">host your own community</a></span>
</footer>