media: ask for camera/mic the way a browser does - #31
Merged
Conversation
A cefWebview page could not reach the camera at all: cef_host's permission handler denied every getUserMedia unconditionally, and the release entitlements had dropped camera/audio-input, so even a granted request was refused by the sandbox. Replace the deny-all gate with the browser flow. A page's getUserMedia with no remembered decision now raises kOpMediaRequest and the callback is HELD (id-keyed per slot, exactly like the JS-dialog path) until the host answers with kOpMediaResponse, so the embedder can show a permission prompt. Only DEVICE capture is ever on the table — desktop/screen bits are dropped — and a grant is all-or-nothing because CEF requires the answer to match the request. kOpMediaState reports what is actually capturing plus the site's stored decision, for an in-use indicator; kOpSetMediaSetting is the site-settings path behind it. Held callbacks are cancelled on navigation and on close. Only an ALLOW is persisted as a content setting. A refusal deliberately is NOT: a stored BLOCK is readable by the page through navigator.permissions.query(), and sites check it before deciding whether to ask — Google Meet saw "denied", never called getUserMedia, and its own "use camera" button went inert with no request left to prompt on and no way back from inside the page. Under Alloy style that BLOCK also enforced nothing: CheckMediaAccessPermission always returns true and the request path consults no content settings, so OnRequestMediaAccessPermission is the only real gate. A BLOCK left by an older build is cleared at page load. `remember` on the response marks a real human answer, so the defensive denies (no handler wired, handler threw, prompt abandoned by a navigation or teardown) refuse one request without silently persisting a site-wide block. Protocol 5 -> 6; publish a matching cef_host and bump the pin in lockstep.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
A
cefWebviewpage could not reach the camera at all.cef_host's permission handler denied everygetUserMediaunconditionally, and the release entitlements had droppeddevice.camera/device.audio-input, so even a granted request was refused by the sandbox. Joining a Google Meet from a web tile was impossible.What this does
Replaces the deny-all gate with the browser permission flow:
getUserMediawith no remembered decision raisesmediaRequestand theCefMediaAccessCallbackis held — id-keyed per slot, mirroring the existing JS-dialog path — until the embedder answersrespondMediaRequest. That lets the host show a real permission prompt.getUserMediarequest to match what was asked for.mediaStatereports what is actually capturing plus the site's stored decision, for an in-use / allowed indicator.setMediaSettingis the site-settings path behind it.device.camera+device.audio-input(load-bearing now that the gate can grant).Why a refusal is not persisted
Only an ALLOW is written as a content setting. A block deliberately is not, and this is the subtle part:
A stored
CONTENT_SETTING_BLOCKis readable by the page throughnavigator.permissions.query(), and sites check it before deciding whether to ask. Meet readdenied, never calledgetUserMediaat all, and its own "use microphone and camera" button became inert — with no request left for the embedder to prompt on and no way back from inside the page. Verified live:Under Alloy style that BLOCK also enforced nothing:
CheckMediaAccessPermissionreturns true unconditionally andRequestMediaAccessPermissionconsults no content settings, soOnRequestMediaAccessPermissionwas already the only real gate. It bought no enforcement and cost the entire recovery path. Chrome escapes the same trap with omnibox chrome an OSR embedder doesn't render (and shipped the<permission>element because of it); Firefox avoids it by keeping a plain "Block" temporary. Embedders hold the refusal themselves instead.A
BLOCKleft in a profile by an earlier build is cleared at page load.Fail-closed, without persisting
rememberon the response marks a real human answer. The defensive denies — no handler wired, handler threw, prompt abandoned by navigation or teardown — refuse that one request without silently writing a site-wide block. That regression is covered by tests: an abandoned prompt denies without remembering, while an explicit Block does persist the caller's intent.Protocol
kCefHostProtocolVersion5 → 6, bumped in lockstep withCefProfileHost.protocolVersion. Consumers must publish a matchingcef_hostand bump the pin together — a stale host is refused at handshake.Windows does not implement the new verbs and degrades to deny (nothing is granted without an explicit answer); documented in
PORTING.md.Testing
flutter analyzeclean;flutter test67/67 including 7 new tests for the round-trip, the fail-closed paths, and the remember/don't-remember split. Verified live in Campus against a real Google Meet: prompt on the site's own button, allow grants camera + mic, and a previously-stuck site now reportsstate=promptinstead ofdenied.