feat(permission): add WebKit permission request handling
Add permission-request signal handler with g_object_ref/unref safety. Implement determinePermissionTypes to extract mic, camera, display, device_info. Add OnPermissionRequest callback to WebView port interface. Normalize origin from URI before passing to use case. Fix Destroy() to clear OnPermissionRequest callback.
- commit
- d4ce0c489a0a90349b984c7198aa185e7f69d6c0
- author
- brice · · 2026-01-30 05:01 UTC
- committer
- brice · · 2026-02-07 11:57 UTC
- parent
- 664884d
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
1 file · +161 -0
M internal/infrastructure/webkit/webview.go +161 -0
| @@ -154,6 +154,11 @@ type WebView struct { | |||
| 154 | 154 | OnLinkHover func(uri string) // Called when hovering over a link/image/media (empty string when leaving) | |
| 155 | 155 | OnWebProcessTerminated func(reason webkit.WebProcessTerminationReason, reasonLabel string, uri string) | |
| 156 | 156 | ||
| 157 | + | // PermissionRequest is called when a site requests permission (mic, camera, screen sharing). | |
| 158 | + | // Return true to indicate the request was handled. Call allow()/deny() to respond. | |
| 159 | + | // The permission types are determined from the request object. | |
| 160 | + | OnPermissionRequest func(origin string, permTypes []string, allow, deny func()) bool | |
| 161 | + | ||
| 157 | 162 | logger zerolog.Logger | |
| 158 | 163 | mu sync.RWMutex | |
| 159 | 164 | ||
| @@ -412,6 +417,7 @@ func (wv *WebView) connectSignals() { | |||
| 412 | 417 | wv.connectMouseTargetChangedSignal() | |
| 413 | 418 | wv.connectBackForwardListChangedSignal() | |
| 414 | 419 | wv.connectWebProcessTerminatedSignal() | |
| 420 | + | wv.connectPermissionRequestSignal() | |
| 415 | 421 | } | |
| 416 | 422 | ||
| 417 | 423 | func (wv *WebView) connectLoadChangedSignal() { | |
| @@ -930,6 +936,158 @@ func (wv *WebView) connectWebProcessTerminatedSignal() { | |||
| 930 | 936 | wv.signalIDs = append(wv.signalIDs, uintptr(sigID)) | |
| 931 | 937 | } | |
| 932 | 938 | ||
| 939 | + | // connectPermissionRequestSignal sets up the permission-request signal handler. | |
| 940 | + | // This is emitted when a site calls getUserMedia() or getDisplayMedia(). | |
| 941 | + | func (wv *WebView) connectPermissionRequestSignal() { | |
| 942 | + | permissionCb := func(_ webkit.WebView, requestPtr uintptr) bool { | |
| 943 | + | if wv.OnPermissionRequest == nil { | |
| 944 | + | return false // Not handled, WebKit will deny by default | |
| 945 | + | } | |
| 946 | + | ||
| 947 | + | // Extract and normalize origin from current URI | |
| 948 | + | uri := wv.URI() | |
| 949 | + | if uri == "" { | |
| 950 | + | wv.logger.Debug().Msg("permission request with empty origin, denying") | |
| 951 | + | return false | |
| 952 | + | } | |
| 953 | + | origin, err := extractOrigin(uri) | |
| 954 | + | if err != nil { | |
| 955 | + | wv.logger.Debug().Str("uri", uri).Err(err).Msg("permission request: failed to extract origin, denying") | |
| 956 | + | return false | |
| 957 | + | } | |
| 958 | + | ||
| 959 | + | // Determine permission types from the request | |
| 960 | + | permTypes := wv.determinePermissionTypes(requestPtr) | |
| 961 | + | if len(permTypes) == 0 { | |
| 962 | + | wv.logger.Warn().Msg("permission request with unknown type, denying") | |
| 963 | + | return false | |
| 964 | + | } | |
| 965 | + | ||
| 966 | + | // Ref the request object to prevent use-after-free | |
| 967 | + | // The request may outlive the signal handler if we show a dialog | |
| 968 | + | requestObj := gobject.ObjectNewFromInternalPtr(requestPtr) | |
| 969 | + | if requestObj == nil { | |
| 970 | + | wv.logger.Warn().Msg("permission request: failed to wrap request object") | |
| 971 | + | return false | |
| 972 | + | } | |
| 973 | + | requestObj.Ref() | |
| 974 | + | ||
| 975 | + | // Create allow/deny callbacks that wrap the WebKit permission request | |
| 976 | + | allowCalled := false | |
| 977 | + | denyCalled := false | |
| 978 | + | ||
| 979 | + | allow := func() { | |
| 980 | + | if allowCalled || denyCalled { | |
| 981 | + | requestObj.Unref() | |
| 982 | + | return | |
| 983 | + | } | |
| 984 | + | allowCalled = true | |
| 985 | + | wv.allowPermissionRequest(requestPtr) | |
| 986 | + | requestObj.Unref() | |
| 987 | + | } | |
| 988 | + | ||
| 989 | + | deny := func() { | |
| 990 | + | if allowCalled || denyCalled { | |
| 991 | + | requestObj.Unref() | |
| 992 | + | return | |
| 993 | + | } | |
| 994 | + | denyCalled = true | |
| 995 | + | wv.denyPermissionRequest(requestPtr) | |
| 996 | + | requestObj.Unref() | |
| 997 | + | } | |
| 998 | + | ||
| 999 | + | // Call the handler | |
| 1000 | + | return wv.OnPermissionRequest(origin, permTypes, allow, deny) | |
| 1001 | + | } | |
| 1002 | + | ||
| 1003 | + | sigID := wv.inner.ConnectPermissionRequest(&permissionCb) | |
| 1004 | + | wv.signalIDs = append(wv.signalIDs, uintptr(sigID)) | |
| 1005 | + | } | |
| 1006 | + | ||
| 1007 | + | // extractOrigin extracts the origin (scheme://host) from a URI. | |
| 1008 | + | func extractOrigin(uri string) (string, error) { | |
| 1009 | + | u, err := url.Parse(uri) | |
| 1010 | + | if err != nil { | |
| 1011 | + | return "", err | |
| 1012 | + | } | |
| 1013 | + | if u.Scheme == "" || u.Host == "" { | |
| 1014 | + | return "", fmt.Errorf("URI missing scheme or host") | |
| 1015 | + | } | |
| 1016 | + | return u.Scheme + "://" + u.Host, nil | |
| 1017 | + | } | |
| 1018 | + | ||
| 1019 | + | // determinePermissionTypes extracts permission types from a WebKit permission request. | |
| 1020 | + | // This uses type checking to identify UserMediaPermissionRequest and its specific types. | |
| 1021 | + | func (wv *WebView) determinePermissionTypes(requestPtr uintptr) []string { | |
| 1022 | + | _ = wv // suppress unused receiver warning, method may need receiver in future | |
| 1023 | + | var types []string | |
| 1024 | + | ||
| 1025 | + | // Try to cast to UserMediaPermissionRequest | |
| 1026 | + | userMediaReq := webkit.UserMediaPermissionRequestNewFromInternalPtr(requestPtr) | |
| 1027 | + | if userMediaReq != nil { | |
| 1028 | + | if webkit.UserMediaPermissionIsForAudioDevice(userMediaReq) { | |
| 1029 | + | types = append(types, "microphone") | |
| 1030 | + | } | |
| 1031 | + | if webkit.UserMediaPermissionIsForVideoDevice(userMediaReq) { | |
| 1032 | + | // Check if this is display capture or camera | |
| 1033 | + | if webkit.UserMediaPermissionIsForDisplayDevice(userMediaReq) { | |
| 1034 | + | types = append(types, "display") | |
| 1035 | + | } else { | |
| 1036 | + | types = append(types, "camera") | |
| 1037 | + | } | |
| 1038 | + | } | |
| 1039 | + | return types | |
| 1040 | + | } | |
| 1041 | + | ||
| 1042 | + | // Check for device enumeration request | |
| 1043 | + | deviceInfoReq := webkit.DeviceInfoPermissionRequestNewFromInternalPtr(requestPtr) | |
| 1044 | + | if deviceInfoReq != nil { | |
| 1045 | + | return []string{"device_info"} | |
| 1046 | + | } | |
| 1047 | + | ||
| 1048 | + | // Unknown permission type - could be clipboard, notifications, geolocation, etc. | |
| 1049 | + | // For now, return empty to trigger denial. Future phases will add these types. | |
| 1050 | + | return nil | |
| 1051 | + | } | |
| 1052 | + | ||
| 1053 | + | // allowPermissionRequest calls Allow() on the WebKit permission request. | |
| 1054 | + | func (wv *WebView) allowPermissionRequest(requestPtr uintptr) { | |
| 1055 | + | // Try UserMediaPermissionRequest first | |
| 1056 | + | userMediaReq := webkit.UserMediaPermissionRequestNewFromInternalPtr(requestPtr) | |
| 1057 | + | if userMediaReq != nil { | |
| 1058 | + | userMediaReq.Allow() | |
| 1059 | + | wv.logger.Debug().Msg("permission request allowed") | |
| 1060 | + | return | |
| 1061 | + | } | |
| 1062 | + | ||
| 1063 | + | // Try DeviceInfoPermissionRequest | |
| 1064 | + | deviceInfoReq := webkit.DeviceInfoPermissionRequestNewFromInternalPtr(requestPtr) | |
| 1065 | + | if deviceInfoReq != nil { | |
| 1066 | + | deviceInfoReq.Allow() | |
| 1067 | + | wv.logger.Debug().Msg("permission request allowed") | |
| 1068 | + | return | |
| 1069 | + | } | |
| 1070 | + | } | |
| 1071 | + | ||
| 1072 | + | // denyPermissionRequest calls Deny() on the WebKit permission request. | |
| 1073 | + | func (wv *WebView) denyPermissionRequest(requestPtr uintptr) { | |
| 1074 | + | // Try UserMediaPermissionRequest first | |
| 1075 | + | userMediaReq := webkit.UserMediaPermissionRequestNewFromInternalPtr(requestPtr) | |
| 1076 | + | if userMediaReq != nil { | |
| 1077 | + | userMediaReq.Deny() | |
| 1078 | + | wv.logger.Debug().Msg("permission request denied") | |
| 1079 | + | return | |
| 1080 | + | } | |
| 1081 | + | ||
| 1082 | + | // Try DeviceInfoPermissionRequest | |
| 1083 | + | deviceInfoReq := webkit.DeviceInfoPermissionRequestNewFromInternalPtr(requestPtr) | |
| 1084 | + | if deviceInfoReq != nil { | |
| 1085 | + | deviceInfoReq.Deny() | |
| 1086 | + | wv.logger.Debug().Msg("permission request denied") | |
| 1087 | + | return | |
| 1088 | + | } | |
| 1089 | + | } | |
| 1090 | + | ||
| 933 | 1091 | // ID returns the unique identifier for this WebView. | |
| 934 | 1092 | func (wv *WebView) ID() WebViewID { | |
| 935 | 1093 | return wv.id | |
| @@ -1214,6 +1372,7 @@ func (wv *WebView) SetCallbacks(callbacks *port.WebViewCallbacks) { | |||
| 1214 | 1372 | wv.OnCreate = nil | |
| 1215 | 1373 | wv.OnLinkHover = nil | |
| 1216 | 1374 | wv.OnWebProcessTerminated = nil | |
| 1375 | + | wv.OnPermissionRequest = nil | |
| 1217 | 1376 | return | |
| 1218 | 1377 | } | |
| 1219 | 1378 | ||
| @@ -1260,6 +1419,7 @@ func (wv *WebView) SetCallbacks(callbacks *port.WebViewCallbacks) { | |||
| 1260 | 1419 | } else { | |
| 1261 | 1420 | wv.OnWebProcessTerminated = nil | |
| 1262 | 1421 | } | |
| 1422 | + | wv.OnPermissionRequest = callbacks.OnPermissionRequest | |
| 1263 | 1423 | } | |
| 1264 | 1424 | ||
| 1265 | 1425 | // ShowDevTools opens the WebKit inspector/developer tools. | |
| @@ -1374,6 +1534,7 @@ func (wv *WebView) DestroyWithPolicy(policy string) { | |||
| 1374 | 1534 | wv.OnAudioStateChanged = nil | |
| 1375 | 1535 | wv.OnLinkHover = nil | |
| 1376 | 1536 | wv.OnWebProcessTerminated = nil | |
| 1537 | + | wv.OnPermissionRequest = nil | |
| 1377 | 1538 | ||
| 1378 | 1539 | // 3. Clear async callback references | |
| 1379 | 1540 | wv.mu.Lock() | |
| @@ -154,6 +154,11 @@ type WebView struct { | |||||
| 154 | OnLinkHover func(uri string) // Called when hovering over a link/image/media (empty string when leaving) | 154 | OnLinkHover func(uri string) // Called when hovering over a link/image/media (empty string when leaving) | ||
| 155 | OnWebProcessTerminated func(reason webkit.WebProcessTerminationReason, reasonLabel string, uri string) | 155 | OnWebProcessTerminated func(reason webkit.WebProcessTerminationReason, reasonLabel string, uri string) | ||
| 156 | 156 | ||||
| 157 | + | // PermissionRequest is called when a site requests permission (mic, camera, screen sharing). | |||
| 158 | + | // Return true to indicate the request was handled. Call allow()/deny() to respond. | |||
| 159 | + | // The permission types are determined from the request object. | |||
| 160 | + | OnPermissionRequest func(origin string, permTypes []string, allow, deny func()) bool | |||
| 161 | + | ||||
| 157 | logger zerolog.Logger | 162 | logger zerolog.Logger | ||
| 158 | mu sync.RWMutex | 163 | mu sync.RWMutex | ||
| 159 | 164 | ||||
| @@ -412,6 +417,7 @@ func (wv *WebView) connectSignals() { | |||||
| 412 | wv.connectMouseTargetChangedSignal() | 417 | wv.connectMouseTargetChangedSignal() | ||
| 413 | wv.connectBackForwardListChangedSignal() | 418 | wv.connectBackForwardListChangedSignal() | ||
| 414 | wv.connectWebProcessTerminatedSignal() | 419 | wv.connectWebProcessTerminatedSignal() | ||
| 420 | + | wv.connectPermissionRequestSignal() | |||
| 415 | } | 421 | } | ||
| 416 | 422 | ||||
| 417 | func (wv *WebView) connectLoadChangedSignal() { | 423 | func (wv *WebView) connectLoadChangedSignal() { | ||
| @@ -930,6 +936,158 @@ func (wv *WebView) connectWebProcessTerminatedSignal() { | |||||
| 930 | wv.signalIDs = append(wv.signalIDs, uintptr(sigID)) | 936 | wv.signalIDs = append(wv.signalIDs, uintptr(sigID)) | ||
| 931 | } | 937 | } | ||
| 932 | 938 | ||||
| 939 | + | // connectPermissionRequestSignal sets up the permission-request signal handler. | |||
| 940 | + | // This is emitted when a site calls getUserMedia() or getDisplayMedia(). | |||
| 941 | + | func (wv *WebView) connectPermissionRequestSignal() { | |||
| 942 | + | permissionCb := func(_ webkit.WebView, requestPtr uintptr) bool { | |||
| 943 | + | if wv.OnPermissionRequest == nil { | |||
| 944 | + | return false // Not handled, WebKit will deny by default | |||
| 945 | + | } | |||
| 946 | + | ||||
| 947 | + | // Extract and normalize origin from current URI | |||
| 948 | + | uri := wv.URI() | |||
| 949 | + | if uri == "" { | |||
| 950 | + | wv.logger.Debug().Msg("permission request with empty origin, denying") | |||
| 951 | + | return false | |||
| 952 | + | } | |||
| 953 | + | origin, err := extractOrigin(uri) | |||
| 954 | + | if err != nil { | |||
| 955 | + | wv.logger.Debug().Str("uri", uri).Err(err).Msg("permission request: failed to extract origin, denying") | |||
| 956 | + | return false | |||
| 957 | + | } | |||
| 958 | + | ||||
| 959 | + | // Determine permission types from the request | |||
| 960 | + | permTypes := wv.determinePermissionTypes(requestPtr) | |||
| 961 | + | if len(permTypes) == 0 { | |||
| 962 | + | wv.logger.Warn().Msg("permission request with unknown type, denying") | |||
| 963 | + | return false | |||
| 964 | + | } | |||
| 965 | + | ||||
| 966 | + | // Ref the request object to prevent use-after-free | |||
| 967 | + | // The request may outlive the signal handler if we show a dialog | |||
| 968 | + | requestObj := gobject.ObjectNewFromInternalPtr(requestPtr) | |||
| 969 | + | if requestObj == nil { | |||
| 970 | + | wv.logger.Warn().Msg("permission request: failed to wrap request object") | |||
| 971 | + | return false | |||
| 972 | + | } | |||
| 973 | + | requestObj.Ref() | |||
| 974 | + | ||||
| 975 | + | // Create allow/deny callbacks that wrap the WebKit permission request | |||
| 976 | + | allowCalled := false | |||
| 977 | + | denyCalled := false | |||
| 978 | + | ||||
| 979 | + | allow := func() { | |||
| 980 | + | if allowCalled || denyCalled { | |||
| 981 | + | requestObj.Unref() | |||
| 982 | + | return | |||
| 983 | + | } | |||
| 984 | + | allowCalled = true | |||
| 985 | + | wv.allowPermissionRequest(requestPtr) | |||
| 986 | + | requestObj.Unref() | |||
| 987 | + | } | |||
| 988 | + | ||||
| 989 | + | deny := func() { | |||
| 990 | + | if allowCalled || denyCalled { | |||
| 991 | + | requestObj.Unref() | |||
| 992 | + | return | |||
| 993 | + | } | |||
| 994 | + | denyCalled = true | |||
| 995 | + | wv.denyPermissionRequest(requestPtr) | |||
| 996 | + | requestObj.Unref() | |||
| 997 | + | } | |||
| 998 | + | ||||
| 999 | + | // Call the handler | |||
| 1000 | + | return wv.OnPermissionRequest(origin, permTypes, allow, deny) | |||
| 1001 | + | } | |||
| 1002 | + | ||||
| 1003 | + | sigID := wv.inner.ConnectPermissionRequest(&permissionCb) | |||
| 1004 | + | wv.signalIDs = append(wv.signalIDs, uintptr(sigID)) | |||
| 1005 | + | } | |||
| 1006 | + | ||||
| 1007 | + | // extractOrigin extracts the origin (scheme://host) from a URI. | |||
| 1008 | + | func extractOrigin(uri string) (string, error) { | |||
| 1009 | + | u, err := url.Parse(uri) | |||
| 1010 | + | if err != nil { | |||
| 1011 | + | return "", err | |||
| 1012 | + | } | |||
| 1013 | + | if u.Scheme == "" || u.Host == "" { | |||
| 1014 | + | return "", fmt.Errorf("URI missing scheme or host") | |||
| 1015 | + | } | |||
| 1016 | + | return u.Scheme + "://" + u.Host, nil | |||
| 1017 | + | } | |||
| 1018 | + | ||||
| 1019 | + | // determinePermissionTypes extracts permission types from a WebKit permission request. | |||
| 1020 | + | // This uses type checking to identify UserMediaPermissionRequest and its specific types. | |||
| 1021 | + | func (wv *WebView) determinePermissionTypes(requestPtr uintptr) []string { | |||
| 1022 | + | _ = wv // suppress unused receiver warning, method may need receiver in future | |||
| 1023 | + | var types []string | |||
| 1024 | + | ||||
| 1025 | + | // Try to cast to UserMediaPermissionRequest | |||
| 1026 | + | userMediaReq := webkit.UserMediaPermissionRequestNewFromInternalPtr(requestPtr) | |||
| 1027 | + | if userMediaReq != nil { | |||
| 1028 | + | if webkit.UserMediaPermissionIsForAudioDevice(userMediaReq) { | |||
| 1029 | + | types = append(types, "microphone") | |||
| 1030 | + | } | |||
| 1031 | + | if webkit.UserMediaPermissionIsForVideoDevice(userMediaReq) { | |||
| 1032 | + | // Check if this is display capture or camera | |||
| 1033 | + | if webkit.UserMediaPermissionIsForDisplayDevice(userMediaReq) { | |||
| 1034 | + | types = append(types, "display") | |||
| 1035 | + | } else { | |||
| 1036 | + | types = append(types, "camera") | |||
| 1037 | + | } | |||
| 1038 | + | } | |||
| 1039 | + | return types | |||
| 1040 | + | } | |||
| 1041 | + | ||||
| 1042 | + | // Check for device enumeration request | |||
| 1043 | + | deviceInfoReq := webkit.DeviceInfoPermissionRequestNewFromInternalPtr(requestPtr) | |||
| 1044 | + | if deviceInfoReq != nil { | |||
| 1045 | + | return []string{"device_info"} | |||
| 1046 | + | } | |||
| 1047 | + | ||||
| 1048 | + | // Unknown permission type - could be clipboard, notifications, geolocation, etc. | |||
| 1049 | + | // For now, return empty to trigger denial. Future phases will add these types. | |||
| 1050 | + | return nil | |||
| 1051 | + | } | |||
| 1052 | + | ||||
| 1053 | + | // allowPermissionRequest calls Allow() on the WebKit permission request. | |||
| 1054 | + | func (wv *WebView) allowPermissionRequest(requestPtr uintptr) { | |||
| 1055 | + | // Try UserMediaPermissionRequest first | |||
| 1056 | + | userMediaReq := webkit.UserMediaPermissionRequestNewFromInternalPtr(requestPtr) | |||
| 1057 | + | if userMediaReq != nil { | |||
| 1058 | + | userMediaReq.Allow() | |||
| 1059 | + | wv.logger.Debug().Msg("permission request allowed") | |||
| 1060 | + | return | |||
| 1061 | + | } | |||
| 1062 | + | ||||
| 1063 | + | // Try DeviceInfoPermissionRequest | |||
| 1064 | + | deviceInfoReq := webkit.DeviceInfoPermissionRequestNewFromInternalPtr(requestPtr) | |||
| 1065 | + | if deviceInfoReq != nil { | |||
| 1066 | + | deviceInfoReq.Allow() | |||
| 1067 | + | wv.logger.Debug().Msg("permission request allowed") | |||
| 1068 | + | return | |||
| 1069 | + | } | |||
| 1070 | + | } | |||
| 1071 | + | ||||
| 1072 | + | // denyPermissionRequest calls Deny() on the WebKit permission request. | |||
| 1073 | + | func (wv *WebView) denyPermissionRequest(requestPtr uintptr) { | |||
| 1074 | + | // Try UserMediaPermissionRequest first | |||
| 1075 | + | userMediaReq := webkit.UserMediaPermissionRequestNewFromInternalPtr(requestPtr) | |||
| 1076 | + | if userMediaReq != nil { | |||
| 1077 | + | userMediaReq.Deny() | |||
| 1078 | + | wv.logger.Debug().Msg("permission request denied") | |||
| 1079 | + | return | |||
| 1080 | + | } | |||
| 1081 | + | ||||
| 1082 | + | // Try DeviceInfoPermissionRequest | |||
| 1083 | + | deviceInfoReq := webkit.DeviceInfoPermissionRequestNewFromInternalPtr(requestPtr) | |||
| 1084 | + | if deviceInfoReq != nil { | |||
| 1085 | + | deviceInfoReq.Deny() | |||
| 1086 | + | wv.logger.Debug().Msg("permission request denied") | |||
| 1087 | + | return | |||
| 1088 | + | } | |||
| 1089 | + | } | |||
| 1090 | + | ||||
| 933 | // ID returns the unique identifier for this WebView. | 1091 | // ID returns the unique identifier for this WebView. | ||
| 934 | func (wv *WebView) ID() WebViewID { | 1092 | func (wv *WebView) ID() WebViewID { | ||
| 935 | return wv.id | 1093 | return wv.id | ||
| @@ -1214,6 +1372,7 @@ func (wv *WebView) SetCallbacks(callbacks *port.WebViewCallbacks) { | |||||
| 1214 | wv.OnCreate = nil | 1372 | wv.OnCreate = nil | ||
| 1215 | wv.OnLinkHover = nil | 1373 | wv.OnLinkHover = nil | ||
| 1216 | wv.OnWebProcessTerminated = nil | 1374 | wv.OnWebProcessTerminated = nil | ||
| 1375 | + | wv.OnPermissionRequest = nil | |||
| 1217 | return | 1376 | return | ||
| 1218 | } | 1377 | } | ||
| 1219 | 1378 | ||||
| @@ -1260,6 +1419,7 @@ func (wv *WebView) SetCallbacks(callbacks *port.WebViewCallbacks) { | |||||
| 1260 | } else { | 1419 | } else { | ||
| 1261 | wv.OnWebProcessTerminated = nil | 1420 | wv.OnWebProcessTerminated = nil | ||
| 1262 | } | 1421 | } | ||
| 1422 | + | wv.OnPermissionRequest = callbacks.OnPermissionRequest | |||
| 1263 | } | 1423 | } | ||
| 1264 | 1424 | ||||
| 1265 | // ShowDevTools opens the WebKit inspector/developer tools. | 1425 | // ShowDevTools opens the WebKit inspector/developer tools. | ||
| @@ -1374,6 +1534,7 @@ func (wv *WebView) DestroyWithPolicy(policy string) { | |||||
| 1374 | wv.OnAudioStateChanged = nil | 1534 | wv.OnAudioStateChanged = nil | ||
| 1375 | wv.OnLinkHover = nil | 1535 | wv.OnLinkHover = nil | ||
| 1376 | wv.OnWebProcessTerminated = nil | 1536 | wv.OnWebProcessTerminated = nil | ||
| 1537 | + | wv.OnPermissionRequest = nil | |||
| 1377 | 1538 | ||||
| 1378 | // 3. Clear async callback references | 1539 | // 3. Clear async callback references | ||
| 1379 | wv.mu.Lock() | 1540 | wv.mu.Lock() | ||