|
1 | 1 | # Background |
| 2 | +The WebView2 team has been asked to provide support for handling permission |
| 3 | +requests that come from iframes. These permission requests occur when content |
| 4 | +within the iframe are requesting access to priveleged resources. The permission |
| 5 | +request types that we support are: Microphone, Camera, Geolocation, |
| 6 | +Notifications, Other Sensors, and Clipboard Read. |
2 | 7 |
|
| 8 | +We currently have a `PermissionRequested` event on our `CoreWebView2` which is |
| 9 | +raised for any permission requests (either from webview or iframes). However, |
| 10 | +our customers do not have a way to determine whether the request has come from |
| 11 | +the webview or an iframe and handle these cases seperately. As such, we plan to |
| 12 | +expand our `CoreWebView2Frame` API to include the `PermissionRequested` event |
| 13 | +as well. |
| 14 | + |
| 15 | +A current limitation of our `CoreWebView2Frame` API is that it only supports top |
| 16 | +level iframes. Any nested iframes will not have a `CoreWebView2Frame` |
| 17 | +associated with them. |
| 18 | + |
| 19 | +In this document we describe the updated API. We'd appreciate your feedback. |
3 | 20 |
|
4 | 21 | # Description |
| 22 | +We propose extending `CoreWebView2Frame` to include the `PermissionRequested` |
| 23 | +event. This event will be raised whenever an iframe requests permission to |
| 24 | +priveleged resources. |
| 25 | + |
| 26 | +Additionally, we propose extending `CoreWebView2PermissionRequestedEventArgs` |
| 27 | +to include a `Handled` property. |
5 | 28 |
|
| 29 | +To maintain backwards compatibility, by default we plan to raise |
| 30 | +`PermissionRequested` on both `CoreWebView2Frame` and `CoreWebView2`. The |
| 31 | +`CoreWebView2Frame` event handlers will be invoked first, |
| 32 | +before the `CoreWebView2` event handlers. If `Handled` is set true as part of |
| 33 | +the `CoreWebView2Frame` event handlers, then the `PermissionRequested` event |
| 34 | +will not be raised on the `CoreWebView2`, and its event handlers will not be |
| 35 | +invoked. |
| 36 | + |
| 37 | +In the case of a nested frame requesting permission, we will raise the event |
| 38 | +off of the top level iframe. This is due to the previously mentioned limitation |
| 39 | +of not having `CoreWebView2Frame`'s for nested iframes. |
6 | 40 |
|
7 | 41 | # Examples |
8 | 42 | ## C++: Regestering IFrame Permission Requested Handler |
9 | | - |
10 | 43 | ``` cpp |
11 | 44 | wil::com_ptr<ICoreWebView2> m_webview; |
| 45 | +std::map<std::tuple<std::wstring, COREWEBVIEW2_PERMISSION_KIND, BOOL>, bool> |
| 46 | + m_cached_permissions; |
12 | 47 |
|
13 | 48 | EventRegistrationToken m_frameCreatedToken = {}; |
14 | 49 | EventRegistrationToken m_permissionRequestedToken = {}; |
15 | 50 |
|
| 51 | +// Example Use Case - an app may want to specify that all iframes should |
| 52 | +// create a message box to prompt the user for approval on a permission request. |
| 53 | +// The approval state could be cached so that future requests are automatically |
| 54 | +// handled if they have been requested previously. |
16 | 55 | void RegisterIFramePermissionRequestedHandler() |
17 | 56 | { |
18 | 57 | webview4 = m_webview.try_query<ICoreWebView2_4>(); |
@@ -81,7 +120,6 @@ void RegisterIFramePermissionRequestedHandler() |
81 | 120 | nullptr, message.c_str(), L"Permission Request", |
82 | 121 | MB_YESNOCANCEL | MB_ICONWARNING); |
83 | 122 |
|
84 | | - |
85 | 123 | COREWEBVIEW2_PERMISSION_STATE state = |
86 | 124 | COREWEBVIEW2_PERMISSION_STATE_DEFAULT; |
87 | 125 |
|
@@ -141,15 +179,99 @@ static void PutHandled(ICoreWebView2PermissionRequestedEventArgs* args) |
141 | 179 | // PermissionRequested event off the CoreWebView2. |
142 | 180 | wil::com_ptr<ICoreWebView2PermissionRequestedEventArgs2> args2; |
143 | 181 | CHECK_FAILURE(args->QueryInterface(IID_PPV_ARGS(&args2))); |
144 | | - if (args2) { |
| 182 | + if (args2) |
| 183 | + { |
145 | 184 | CHECK_FAILURE(args2->put_Handled(true)); |
146 | 185 | } |
147 | 186 | } |
148 | 187 | ``` |
149 | 188 |
|
150 | | -## C#: TBD |
| 189 | +## C#: Regestering IFrame Permission Requested Handler |
151 | 190 | ```c# |
| 191 | +private WebView2 m_webview; |
| 192 | +Dictionary<Tuple<string, CoreWebView2PermissionKind, bool>, bool> m_cached_permissions; |
| 193 | +
|
| 194 | +// Example Use Case - an app may want to specify that all iframes should |
| 195 | +// create a message box to prompt the user for approval on a permission request. |
| 196 | +// The approval state could be cached so that future requests are automatically |
| 197 | +// handled if they have been requested previously. |
| 198 | +void RegisterIFramePermissionRequestedHandler() |
| 199 | +{ |
| 200 | + m_webview.CoreWebView2.FrameCreated += (sender, CoreWebView2FrameCreatedEventArgs args) => |
| 201 | + { |
| 202 | + args.Frame.PermissionRequested += (frameSender, CoreWebView2PermissionRequestedEventArgs permissionArgs) => |
| 203 | + { |
| 204 | + var cached_key = new Tuple<string, CoreWebView2PermissionKind, bool> |
| 205 | + (permissionArgs.Uri, permissionArgs.Kind, permissionArgs.IsUserInitiated); |
| 206 | +
|
| 207 | + if (m_cached_permissions.ContainsKey(cached_key)) |
| 208 | + { |
| 209 | + bool allow = m_cached_permissions[cached_key].Item2; |
| 210 | + if (allow) |
| 211 | + { |
| 212 | + permissionArgs.State = CoreWebView2PermissionKind.Allow; |
| 213 | + } |
| 214 | + else |
| 215 | + { |
| 216 | + permissionArgs.State = CoreWebView2PermissionKind.Deny; |
| 217 | + } |
| 218 | +
|
| 219 | + permissionArgs.Handled = true; |
| 220 | + return; |
| 221 | + } |
| 222 | + |
| 223 | + string message = "An iframe has requested device permission for "; |
| 224 | + message += NameOfPermissionKind(permissionArgs.Kind); |
| 225 | + message += " to the website at "; |
| 226 | + message += permissionArgs.Uri; |
| 227 | + message += "\n\n"; |
| 228 | + message += "Do you want to grant permission?\n" |
| 229 | + message += |
| 230 | + (permissionArgs.UserInitiated |
| 231 | + ? "This request came from a user gesture." |
| 232 | + : "This request did not come from a user gesture."); |
| 233 | +
|
| 234 | + var selection = MessageBox.Show(message, "iframe PermissionRequest", MessageBoxButton.YesNoCancel); |
| 235 | + if (selection == MessageBoxResult.Yes) |
| 236 | + { |
| 237 | + permissionArgs.State = CoreWebView2PermissionState.Allow; |
| 238 | + m_cached_permissions[cached_key] = false; |
| 239 | + } |
| 240 | + else if (selection == MessageBoxResult.No) |
| 241 | + { |
| 242 | + permissionArgs.State = CoreWebView2PermissionState.Deny; |
| 243 | + m_cached_permissions[cached_key] = true; |
| 244 | + } |
| 245 | + else |
| 246 | + { |
| 247 | + permissionArgs.State = CoreWebView2PermissionState.Default; |
| 248 | + } |
| 249 | +
|
| 250 | + permissionArgs.Handled = true; |
| 251 | + }; |
| 252 | + }; |
| 253 | +} |
152 | 254 |
|
| 255 | +string NameOfPermissionKind(CoreWebView2PermissionKind kind) |
| 256 | +{ |
| 257 | + switch (kind) |
| 258 | + { |
| 259 | + case CoreWebView2PermissionKind.Microphone: |
| 260 | + return "Microphone"; |
| 261 | + case CoreWebView2PermissionKind.Camera: |
| 262 | + return "Camera"; |
| 263 | + case CoreWebView2PermissionKind.Geolocation: |
| 264 | + return "Geolocation"; |
| 265 | + case CoreWebView2PermissionKind.Notifications: |
| 266 | + return "Notifications"; |
| 267 | + case CoreWebView2PermissionKind.OtherSensors: |
| 268 | + return "Generic Sensors"; |
| 269 | + case CoreWebView2PermissionKind.ClipboardRead: |
| 270 | + return "Clipboard Read"; |
| 271 | + default: |
| 272 | + return "Unknown resources"; |
| 273 | + } |
| 274 | +} |
153 | 275 | ``` |
154 | 276 |
|
155 | 277 | # API Details |
|
0 commit comments