fix(v3): handle getUserMedia via a cross-platform Permissions map (Linux + Windows)#5567
Conversation
…quests
On Linux, navigator.mediaDevices.getUserMedia() always rejected with
NotAllowedError because WebKitGTK denies any permission request that no
permission-request signal handler claims, and Wails never connected one.
Applications had no way to intercept the request themselves, so camera
and microphone access was impossible — even with full OS-level access to
the devices.
Connect permission-request in both the GTK3 (webkit2gtk-4.1) and GTK4
(webkitgtk-6.0) backends and allow WebKitUserMediaPermissionRequest.
This matches the other platforms: Windows grants all WebView2
permissions by default (WindowOptions.Windows.Permissions can override),
and macOS defers to the OS-level prompt. All other permission kinds
return FALSE, keeping WebKit's default handling (deny).
Verified end-to-end on Linux (Ubuntu, WebKitGTK 4.1) under xvfb with
WebKit mock capture devices: getUserMedia({audio:true}) fails with
NotAllowedError on master and returns a live stream with this change.
gtk3 + gtk4 builds and the pkg/application test suite pass.
Fixes #5552
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
WalkthroughAdds a Permissions API and per-window permission resolution, C/Go helpers and callbacks to detect WebKit user-media requests, GTK3/GTK4 handlers that allow or deny camera/microphone requests via allowMediaCapture, and Windows mapping to apply the same cross-platform permission options. ChangesLinux user-media permission handling
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Suggested labels
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Pull request overview
This PR fixes Linux (GTK3 + GTK4) WebKitGTK behavior where navigator.mediaDevices.getUserMedia() is always denied because Wails wasn’t handling WebKit’s permission-request signal. It connects the signal and allows user-media permission requests so camera/microphone access works on Linux, and documents the fix in the v3 unreleased changelog.
Changes:
- Connect WebKitGTK
permission-requestsignal for both GTK3 (webkit2gtk-4.1) and GTK4 (webkitgtk-6.0) backends. - Add a handler that allows
WebKitUserMediaPermissionRequest(camera/microphone) and leaves other permission types to WebKit default handling. - Update
v3/UNRELEASED_CHANGELOG.mdwith the LinuxgetUserMediafix entry.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| v3/UNRELEASED_CHANGELOG.md | Documents the Linux getUserMedia permission handling fix. |
| v3/pkg/application/linux_cgo.h | Declares the new permission-request callback and a C wrapper for the user-media permission macro (GTK4 path). |
| v3/pkg/application/linux_cgo.go | GTK4 backend: wires up permission-request and implements the Go callback to allow user-media requests. |
| v3/pkg/application/linux_cgo.c | GTK4 backend: implements the C wrapper for WEBKIT_IS_USER_MEDIA_PERMISSION_REQUEST. |
| v3/pkg/application/linux_cgo_gtk3.go | GTK3 backend: wires up permission-request, adds a local macro wrapper, and implements the Go callback to allow user-media requests. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@v3/pkg/application/linux_cgo.go`:
- Around line 1507-1519: handlePermissionRequest currently unconditionally
grants any C.WebKitUserMediaPermissionRequest; change it to inspect the
requesting origin and only allow if it matches a trusted origin list or an
app-provided policy callback. Specifically: inside handlePermissionRequest (and
the GTK3 mirror), detect user-media requests via
C.is_user_media_permission_request(request), then obtain the request origin/URI
from the WebKit request or associated WebView (use the existing C/WebKit
accessor functions available in this file), validate that origin against a
hardcoded allow-list (e.g., wails://, localhost) or invoke an exported Go
permission callback (e.g., onPermissionRequest or similar app policy function)
which returns allow/deny, and only call
C.webkit_permission_request_allow(request) when validation returns allow;
otherwise return FALSE to deny. Ensure the new logic defaults to deny if origin
cannot be determined or callback is not provided, and replicate the same change
in the GTK3 mirror implementation.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 183770a4-dde1-4d27-a9b6-43e552fefe37
📒 Files selected for processing (5)
v3/UNRELEASED_CHANGELOG.mdv3/pkg/application/linux_cgo.cv3/pkg/application/linux_cgo.gov3/pkg/application/linux_cgo.hv3/pkg/application/linux_cgo_gtk3.go
| //export handlePermissionRequest | ||
| func handlePermissionRequest(wv *C.WebKitWebView, request *C.WebKitPermissionRequest, data C.uintptr_t) C.gboolean { | ||
| // WebKitGTK denies any permission request nobody handles, so without this | ||
| // getUserMedia always fails with NotAllowedError. Grant camera/microphone | ||
| // access to match the other platforms: Windows grants all permissions by | ||
| // default and macOS defers to the OS-level prompt. Returning FALSE for | ||
| // everything else keeps WebKit's default handling (deny). | ||
| if C.is_user_media_permission_request(request) != 0 { | ||
| C.webkit_permission_request_allow(request) | ||
| return C.gboolean(1) | ||
| } | ||
| return C.gboolean(0) | ||
| } |
There was a problem hiding this comment.
Scope user-media auto-grant to trusted origins (or a configurable app policy).
handlePermissionRequest currently allows every WebKitUserMediaPermissionRequest unconditionally. Because apps can load arbitrary URLs, this can silently grant camera/microphone access to remote content. Please gate allow-listing to trusted app origins (e.g., wails:///localhost) or expose a permission callback and default-deny otherwise. The GTK3 mirror should enforce the same policy.
🔧 Suggested direction
func handlePermissionRequest(wv *C.WebKitWebView, request *C.WebKitPermissionRequest, data C.uintptr_t) C.gboolean {
- if C.is_user_media_permission_request(request) != 0 {
- C.webkit_permission_request_allow(request)
- return C.gboolean(1)
- }
+ if C.is_user_media_permission_request(request) != 0 {
+ if uri := C.webkit_web_view_get_uri(wv); uri != nil {
+ current := C.GoString(uri)
+ if strings.HasPrefix(current, "wails://") ||
+ strings.HasPrefix(current, "http://localhost") ||
+ strings.HasPrefix(current, "https://localhost") {
+ C.webkit_permission_request_allow(request)
+ return C.gboolean(1)
+ }
+ }
+ }
return C.gboolean(0)
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| //export handlePermissionRequest | |
| func handlePermissionRequest(wv *C.WebKitWebView, request *C.WebKitPermissionRequest, data C.uintptr_t) C.gboolean { | |
| // WebKitGTK denies any permission request nobody handles, so without this | |
| // getUserMedia always fails with NotAllowedError. Grant camera/microphone | |
| // access to match the other platforms: Windows grants all permissions by | |
| // default and macOS defers to the OS-level prompt. Returning FALSE for | |
| // everything else keeps WebKit's default handling (deny). | |
| if C.is_user_media_permission_request(request) != 0 { | |
| C.webkit_permission_request_allow(request) | |
| return C.gboolean(1) | |
| } | |
| return C.gboolean(0) | |
| } | |
| //export handlePermissionRequest | |
| func handlePermissionRequest(wv *C.WebKitWebView, request *C.WebKitPermissionRequest, data C.uintptr_t) C.gboolean { | |
| // WebKitGTK denies any permission request nobody handles, so without this | |
| // getUserMedia always fails with NotAllowedError. Grant camera/microphone | |
| // access to match the other platforms: Windows grants all permissions by | |
| // default and macOS defers to the OS-level prompt. Returning FALSE for | |
| // everything else keeps WebKit's default handling (deny). | |
| if C.is_user_media_permission_request(request) != 0 { | |
| if uri := C.webkit_web_view_get_uri(wv); uri != nil { | |
| current := C.GoString(uri) | |
| if strings.HasPrefix(current, "wails://") || | |
| strings.HasPrefix(current, "http://localhost") || | |
| strings.HasPrefix(current, "https://localhost") { | |
| C.webkit_permission_request_allow(request) | |
| return C.gboolean(1) | |
| } | |
| } | |
| } | |
| return C.gboolean(0) | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@v3/pkg/application/linux_cgo.go` around lines 1507 - 1519,
handlePermissionRequest currently unconditionally grants any
C.WebKitUserMediaPermissionRequest; change it to inspect the requesting origin
and only allow if it matches a trusted origin list or an app-provided policy
callback. Specifically: inside handlePermissionRequest (and the GTK3 mirror),
detect user-media requests via C.is_user_media_permission_request(request), then
obtain the request origin/URI from the WebKit request or associated WebView (use
the existing C/WebKit accessor functions available in this file), validate that
origin against a hardcoded allow-list (e.g., wails://, localhost) or invoke an
exported Go permission callback (e.g., onPermissionRequest or similar app policy
function) which returns allow/deny, and only call
C.webkit_permission_request_allow(request) when validation returns allow;
otherwise return FALSE to deny. Ensure the new logic defaults to deny if origin
cannot be determined or callback is not provided, and replicate the same change
in the GTK3 mirror implementation.
✅ Addressed in commit 1538d94
There was a problem hiding this comment.
Addressed by the rework: the unconditional camera/microphone grant has been replaced with a cross-platform WebviewWindowOptions.Permissions map (map[PermissionType]Permission), honored on Linux (WebKitGTK) and Windows (WebView2). Camera/mic default to allowed on Linux only because WebKitGTK has no native prompt (preserving the getUserMedia fix), and can be turned off per-kind with PermissionDeny. See the updated PR description.
There was a problem hiding this comment.
@leaanthony, thanks for the detailed explanation. The rework is a solid improvement — the unconditional grant is now replaced by allowMediaCapture, which consults the per-window Permissions map (map[PermissionType]Permission). The Linux-defaulting-to-allow behavior for user-media is well-motivated (WebKitGTK has no native OS-level prompt), and developers can opt out on a per-kind basis with PermissionDeny. The GTK3 mirror was updated consistently too. This fully addresses the original concern. ✅
🧠 Learnings used
Learnt from: symball
Repo: wailsapp/wails PR: 4853
File: v2/internal/system/system.go:128-152
Timestamp: 2026-01-04T08:01:00.038Z
Learning: In v2/internal/system/system.go, shared functions like checkLibrary are defined without build tags but are only invoked from platform-specific files (system_linux.go, system_windows.go, system_darwin.go) that have build constraints. Reviewers should ensure there are no runtime OS checks in system.go and that platform-specific behavior is controlled via build tags. If runtime switches exist, remove them in favor of compile-time platform constraints to reduce overhead and improve correctness.
Learnt from: leaanthony
Repo: wailsapp/wails PR: 4906
File: v3/internal/setupwizard/wizard_windows.go:93-94
Timestamp: 2026-01-26T01:56:49.314Z
Learning: Ensure the Go toolchain minimum version for Wails v3 is 1.25+. Update go.mod to reflect 1.25+ and validate that setup wizard checks enforce Go 1.25+ at release time. Align any CI/go-version checks, documentation, and release notes with this requirement; this guideline applies to all Go files under the v3 module, not just the wizard.
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (1)
v3/pkg/application/permissions_linux.go (1)
20-41:⚠️ Potential issue | 🟠 Major | ⚡ Quick win
PermissionDefaultcurrently auto-grants mic/camera for any loaded origin.
allowMediaCapturedefaults to allow and has no requester-origin input, andhandlePermissionRequestinv3/pkg/application/linux_cgo.goonly passes window ID/device flags. So remote web content loaded in a window can inherit camera/microphone access unless app code explicitly sets deny rules. Please scope default-allow to trusted app origins (or require explicitPermissionAllowfor non-app origins).🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@v3/pkg/application/permissions_linux.go` around lines 20 - 41, The current allowMediaCapture(windowID,uint,needAudio,needVideo) implicitly treats PermissionDefault as allowed for any origin; change it to require explicit PermissionAllow for non-app origins by (1) adding a requester origin parameter to allowMediaCapture (e.g., allowMediaCapture(windowID uint, origin string, needAudio, needVideo bool) ) and modify its inner allows() so that resolvePermission is consulted and PermissionDefault is treated as allowed only for trusted app origins (check origin against app origin list) and treated as deny for other origins; (2) update the caller handlePermissionRequest in linux_cgo.go to pass the request origin into allowMediaCapture; and (3) if resolvePermission signature needs origin info, extend resolvePermission to accept origin and use it when determining PermissionDefault behavior. Ensure PermissionAllow and PermissionDeny semantics remain unchanged and only PermissionDefault handling is scoped to app-origin trust.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@v3/pkg/application/webview_window_windows.go`:
- Around line 2104-2109: Remove the unconditional global allow so per-kind
permission states work: delete the call to
chromium.SetGlobalPermission(edge.CoreWebView2PermissionStateAllow) (which sets
e.globalPermission and causes PermissionRequested to always use Allow), and rely
solely on the existing per-kind loop that calls
chromium.SetPermission(crossPermissionToWebView2Kind(permission),
edge.CoreWebView2PermissionState(state)); leave the
crossPermissionToWebView2Kind/SetPermission logic intact so
PermissionDefault/Deny are respected.
---
Duplicate comments:
In `@v3/pkg/application/permissions_linux.go`:
- Around line 20-41: The current
allowMediaCapture(windowID,uint,needAudio,needVideo) implicitly treats
PermissionDefault as allowed for any origin; change it to require explicit
PermissionAllow for non-app origins by (1) adding a requester origin parameter
to allowMediaCapture (e.g., allowMediaCapture(windowID uint, origin string,
needAudio, needVideo bool) ) and modify its inner allows() so that
resolvePermission is consulted and PermissionDefault is treated as allowed only
for trusted app origins (check origin against app origin list) and treated as
deny for other origins; (2) update the caller handlePermissionRequest in
linux_cgo.go to pass the request origin into allowMediaCapture; and (3) if
resolvePermission signature needs origin info, extend resolvePermission to
accept origin and use it when determining PermissionDefault behavior. Ensure
PermissionAllow and PermissionDeny semantics remain unchanged and only
PermissionDefault handling is scoped to app-origin trust.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 0859e3b5-e82d-4ae5-be06-f793aa3b5aa2
📒 Files selected for processing (7)
v3/pkg/application/linux_cgo.cv3/pkg/application/linux_cgo.gov3/pkg/application/linux_cgo.hv3/pkg/application/linux_cgo_gtk3.gov3/pkg/application/permissions_linux.gov3/pkg/application/webview_window_options.gov3/pkg/application/webview_window_windows.go
🚧 Files skipped from review as they are similar to previous changes (2)
- v3/pkg/application/linux_cgo.h
- v3/pkg/application/linux_cgo_gtk3.go
…nux + Windows)
Replaces the unconditional camera/microphone grant on Linux with a
declarative, cross-platform policy. A new
WebviewWindowOptions.Permissions map[PermissionType]Permission lets apps
allow/deny capabilities per kind:
Permissions: map[application.PermissionType]application.Permission{
application.PermissionMicrophone: application.PermissionDeny,
}
PermissionType (uint8): Microphone, Camera, Geolocation, Notifications,
ClipboardRead. Permission (uint8): Default=0, Allow=1, Deny=2 — values
kept in step with the WebView2 permission-state ABI so the Windows
backend needs no value translation.
Linux (WebKitGTK, gtk3 + gtk4): the permission-request handler now reads
the map and allows/denies per the requested device types (audio→
Microphone, video→Camera). WebKitGTK has no native prompt, so
PermissionDefault allows media capture — preserving the getUserMedia fix
from #5552 — while PermissionDeny turns it off.
Windows (WebView2): the cross-platform map is applied via SetPermission
(translating kinds); the existing Windows.Permissions map still works and
overrides individual kinds.
Verified on Linux (Ubuntu, WebKitGTK 4.1, gtk3, mock capture devices):
default → getUserMedia GRANTED, PermissionDeny → NotAllowedError. Builds:
darwin, windows (CGO_ENABLED=0), and linux gtk3+gtk4; gtk3 pkg/application
suite passes under xvfb.
Fixes #5552
1538d94 to
c7e8e77
Compare
…ions map WebView2's PermissionRequested handler checks the global permission before the per-kind map, and setupChromium unconditionally called SetGlobalPermission(Allow). That made both the new cross-platform Permissions map and the pre-existing Windows.Permissions map dead on Windows: a configured PermissionDeny was silently ignored because every request resolved to the global Allow first. Only set the blanket allow-all when no per-kind policy is configured, so unconfigured apps keep their previous auto-allow behaviour while apps that set Permissions actually have them honoured (unset kinds fall through to PermissionDefault → the platform's native prompt). Surfaced by review on #5567.
| // Permissions controls how capability requests (camera, microphone, …) | ||
| // from the web content are handled, per PermissionType. Unset entries use | ||
| // PermissionDefault. Cross-platform; see the Permission constants for the | ||
| // per-platform meaning of the default. | ||
| Permissions map[PermissionType]Permission | ||
|
|
| // PermissionDefault uses the platform's native handling and is the zero | ||
| // value. On macOS (TCC) and Windows (WebView2) this presents the OS/webview | ||
| // permission prompt to the user. Linux/WebKitGTK has no prompt mechanism, | ||
| // so media capture (camera/microphone) is allowed — restoring getUserMedia | ||
| // for app content — and other capabilities are denied. | ||
| PermissionDefault Permission = iota |
| // PermissionType identifies a capability that web content can request from | ||
| // the webview (camera, microphone, …). It is the cross-platform equivalent of | ||
| // the platform-specific permission-kind enums. | ||
| type PermissionType uint8 | ||
|
|
||
| const ( | ||
| PermissionMicrophone PermissionType = iota | ||
| PermissionCamera | ||
| PermissionGeolocation | ||
| PermissionNotifications | ||
| PermissionClipboardRead | ||
| ) | ||
|
|
||
| // Permission is the policy applied to a PermissionType. The values are kept in | ||
| // step with the native WebView2 permission-state ABI (Default=0, Allow=1, | ||
| // Deny=2) so the Windows backend needs no translation. | ||
| type Permission uint8 | ||
|
|
||
| const ( | ||
| // PermissionDefault uses the platform's native handling and is the zero | ||
| // value. On macOS (TCC) and Windows (WebView2) this presents the OS/webview | ||
| // permission prompt to the user. Linux/WebKitGTK has no prompt mechanism, | ||
| // so media capture (camera/microphone) is allowed — restoring getUserMedia | ||
| // for app content — and other capabilities are denied. | ||
| PermissionDefault Permission = iota | ||
| // PermissionAllow grants the capability without prompting. | ||
| PermissionAllow | ||
| // PermissionDeny denies the capability without prompting. | ||
| PermissionDeny | ||
| ) |
…5567) (#5592) * docs: add Web Permissions guide and options reference (#5567) PR #5567 added a cross-platform Permissions map to WebviewWindowOptions but shipped with no documentation. This adds: - New page: features/windows/permissions.mdx — full developer guide covering PermissionType/Permission API, per-platform behaviour (Linux WebKitGTK default-allow quirk, Windows prompt vs blanket-grant change, macOS TCC deferral), common patterns, Windows-specific override, and troubleshooting section - Updated: features/windows/options.mdx — Permissions field added to the struct summary and a dedicated section with type reference and the Windows breaking-change callout - Updated: astro.config.mjs sidebar — Web Permissions entry added between Window Options and Multiple Windows * docs: rename 'Web Permissions' to 'Permissions' * docs: clarify macOS permissions behaviour, remove WKUIDelegate jargon * docs: remove PR reference from permissions example * docs: fix Linux permissions accuracy per Copilot review - Linux only handles camera/microphone; geolocation/notifications/clipboard remain unimplemented and always denied — table, matrix and patterns updated - Removed implication that non-media capabilities can be allowed on Linux - Added Linux-specific troubleshooting entry for unimplemented types - Clarified PermissionAllow Linux caveat in options.mdx
Description
On Linux,
navigator.mediaDevices.getUserMedia()always rejected withNotAllowedError: WebKitGTK denies any permission request with no handler attached, and Wails never attached one (#5552).Rather than hardcode a blanket grant, this adds a declarative, cross-platform permission policy and wires both Linux and Windows to it.
API
PermissionType(uint8):Microphone,Camera,Geolocation,Notifications,ClipboardRead.Permission(uint8):PermissionDefault(0),PermissionAllow(1),PermissionDeny(2) — values deliberately match the WebView2 permission-state ABI so the Windows backend needs no value translation.PermissionDefault(the unset/zero value) means use the platform's native handling: the OS/webview prompt on macOS (TCC) and Windows (WebView2). Linux/WebKitGTK has no prompt mechanism, soPermissionDefaultallows media capture (preserving thegetUserMediafix) and denies other capabilities;PermissionDenyturns media off.Implementation
permission-requesthandler reads the map and allows/denies per the requested device types (audio → Microphone, video → Camera). Shared decision helper across both backends; tiny C wrappers exposewebkit_user_media_permission_is_for_audio_device/_video_device.SetPermission(translating kinds). The pre-existingWindows.Permissionsmap still works and overrides individual kinds.WKUIDelegateto the map is a sensible follow-up.Verification
End-to-end on Linux (Ubuntu, WebKitGTK 4.1, gtk3) with WebKit mock capture devices so the result depends only on the grant:
PermissionsgetUserMedia({audio:true}){Microphone: PermissionDeny}(
PermissionAllowtakes the same handler branch as the proven default.) Builds pass for darwin, windows (CGO_ENABLED=0), and linux gtk3+gtk4; the gtk3pkg/applicationsuite passes under xvfb.Fixes #5552
Type of change
Checklist:
v3/UNRELEASED_CHANGELOG.mdSummary by CodeRabbit
Bug Fixes
New Features