windows-webview 0.100.0

Windows WebView2 library
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
use super::*;
use crate::handler::subscription;

/// The level WebView2 should target for the browser's memory usage, set with
/// [`WebView::set_memory_usage_target_level`].
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum MemoryUsageTargetLevel {
    /// Normal memory usage.
    Normal,
    /// Reduced memory usage, suitable for a hidden or background `WebView`.
    Low,
}

impl MemoryUsageTargetLevel {
    fn from_raw(value: COREWEBVIEW2_MEMORY_USAGE_TARGET_LEVEL) -> Self {
        match value {
            1 => Self::Low,
            _ => Self::Normal,
        }
    }

    fn to_raw(self) -> COREWEBVIEW2_MEMORY_USAGE_TARGET_LEVEL {
        match self {
            Self::Normal => 0,
            Self::Low => 1,
        }
    }
}

/// A request to navigate with a custom HTTP method, headers, or body, passed to
/// [`WebView::navigate_with_request`]. Defaults to a `GET` with no extra headers
/// or body.
#[derive(Clone, Debug)]
pub struct NavigationRequest {
    uri: String,
    method: String,
    headers: Vec<(String, String)>,
    body: Vec<u8>,
}

impl NavigationRequest {
    /// Creates a `GET` request for `uri`.
    pub fn new(uri: &str) -> Self {
        Self {
            uri: uri.to_string(),
            method: "GET".to_string(),
            headers: Vec::new(),
            body: Vec::new(),
        }
    }

    /// Sets the HTTP method, for example `POST`.
    pub fn method(mut self, method: &str) -> Self {
        self.method = method.to_string();
        self
    }

    /// Adds a request header, such as an `Authorization` token.
    pub fn header(mut self, name: &str, value: &str) -> Self {
        self.headers.push((name.to_string(), value.to_string()));
        self
    }

    /// Sets the request body bytes.
    pub fn body(mut self, body: impl Into<Vec<u8>>) -> Self {
        self.body = body.into();
        self
    }
}

/// How a folder mapped with
/// [`WebView::set_virtual_host_name_to_folder_mapping`] may be accessed.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum HostResourceAccessKind {
    /// Resources from other origins cannot access the mapped content.
    Deny,
    /// Resources from any origin may access the mapped content.
    Allow,
    /// Like [`Deny`](Self::Deny), but cross-origin requests are allowed through
    /// CORS.
    DenyCors,
}

impl HostResourceAccessKind {
    fn to_raw(self) -> COREWEBVIEW2_HOST_RESOURCE_ACCESS_KIND {
        match self {
            Self::Deny => 0,
            Self::Allow => 1,
            Self::DenyCors => 2,
        }
    }
}

/// A WebView2 browser. Navigate to URLs and run JavaScript against the hosted
/// page.
#[derive(Clone)]
pub struct WebView(pub(crate) ICoreWebView2);

impl WebView {
    /// Wraps an existing `ICoreWebView2`. Used by the optional `reactor` feature
    /// to build a `WebView` from the WinUI XAML `WebView2` control's bridged COM
    /// core.
    #[cfg(feature = "reactor")]
    pub(crate) fn from_core(core: ICoreWebView2) -> Self {
        Self(core)
    }

    /// Navigates the browser to the given URI.
    pub fn navigate(&self, uri: &str) -> Result<()> {
        let uri = HSTRING::from(uri);
        unsafe { self.0.Navigate(&uri) }.ok()
    }

    /// Navigates the browser to the given HTML content as the document.
    pub fn navigate_to_string(&self, html: &str) -> Result<()> {
        let html = HSTRING::from(html);
        unsafe { self.0.NavigateToString(&html) }.ok()
    }

    /// Navigates the browser using a [`NavigationRequest`], allowing a custom
    /// HTTP method, request headers, or body - for example a `POST` or an
    /// `Authorization` header that a plain [`navigate`](Self::navigate) cannot
    /// supply.
    pub fn navigate_with_request(&self, request: &NavigationRequest) -> Result<()> {
        let source: ICoreWebView2_2 = self.0.cast()?;
        let environment: ICoreWebView2Environment2 = unsafe { source.Environment()? }.cast()?;

        let uri = HSTRING::from(&request.uri);
        let method = HSTRING::from(&request.method);
        let mut headers = String::new();
        for (name, value) in &request.headers {
            headers.push_str(name);
            headers.push_str(": ");
            headers.push_str(value);
            headers.push_str("\r\n");
        }
        let headers = HSTRING::from(&headers);
        let stream = if request.body.is_empty() {
            None
        } else {
            unsafe { SHCreateMemStream(request.body.as_ptr(), request.body.len() as u32) }
        };

        unsafe {
            let request =
                environment.CreateWebResourceRequest(&uri, &method, stream.as_ref(), &headers)?;
            source.NavigateWithWebResourceRequest(&request).ok()
        }
    }

    /// Reloads the current page.
    pub fn reload(&self) -> Result<()> {
        unsafe { self.0.Reload() }.ok()
    }

    /// Opens the DevTools window for the page, the same view shown by the
    /// browser's "Inspect" command.
    pub fn open_dev_tools_window(&self) -> Result<()> {
        unsafe { self.0.OpenDevToolsWindow() }.ok()
    }

    /// Stops any in-progress navigation or download.
    pub fn stop(&self) -> Result<()> {
        unsafe { self.0.Stop() }.ok()
    }

    /// Navigates back to the previous page in the navigation history.
    pub fn go_back(&self) -> Result<()> {
        unsafe { self.0.GoBack() }.ok()
    }

    /// Navigates forward to the next page in the navigation history.
    pub fn go_forward(&self) -> Result<()> {
        unsafe { self.0.GoForward() }.ok()
    }

    /// Returns the URI of the current top-level document.
    pub fn source(&self) -> String {
        unsafe { string::take_result(self.0.Source()) }
    }

    /// Returns the title of the current top-level document.
    pub fn document_title(&self) -> String {
        unsafe { string::take_result(self.0.DocumentTitle()) }
    }

    /// Maps a virtual host name to a local folder so the page can load its files
    /// over a normal URL such as `https://app.example/index.html`. `access_kind`
    /// controls cross-origin access to the mapped files.
    pub fn set_virtual_host_name_to_folder_mapping(
        &self,
        host_name: &str,
        folder_path: &str,
        access_kind: HostResourceAccessKind,
    ) -> Result<()> {
        let source: ICoreWebView2_3 = self.0.cast()?;
        let host_name = HSTRING::from(host_name);
        let folder_path = HSTRING::from(folder_path);
        unsafe {
            source
                .SetVirtualHostNameToFolderMapping(&host_name, &folder_path, access_kind.to_raw())
                .ok()
        }
    }

    /// Removes a mapping previously created with
    /// [`set_virtual_host_name_to_folder_mapping`](Self::set_virtual_host_name_to_folder_mapping).
    pub fn clear_virtual_host_name_to_folder_mapping(&self, host_name: &str) -> Result<()> {
        let source: ICoreWebView2_3 = self.0.cast()?;
        let host_name = HSTRING::from(host_name);
        unsafe { source.ClearVirtualHostNameToFolderMapping(&host_name) }.ok()
    }

    /// Returns the [`CookieManager`] for reading, writing, and deleting the
    /// browser's cookies.
    pub fn cookie_manager(&self) -> Result<CookieManager> {
        let source: ICoreWebView2_2 = self.0.cast()?;
        Ok(CookieManager(unsafe { source.CookieManager()? }))
    }

    /// Returns the [`Profile`] this browser belongs to, exposing its color
    /// scheme, download folder, and browsing-data controls.
    pub fn profile(&self) -> Result<Profile> {
        let source: ICoreWebView2_13 = self.0.cast()?;
        Ok(Profile(unsafe { source.Profile()? }))
    }

    /// Returns `true` if the page currently has an element displayed full screen
    /// (for example a video using the HTML Fullscreen API).
    pub fn contains_fullscreen_element(&self) -> bool {
        unsafe { self.0.ContainsFullScreenElement() }.is_ok_and(|value| value.as_bool())
    }

    /// Returns the memory-usage level WebView2 is targeting.
    pub fn memory_usage_target_level(&self) -> Result<MemoryUsageTargetLevel> {
        let source: ICoreWebView2_19 = self.0.cast()?;
        Ok(MemoryUsageTargetLevel::from_raw(unsafe {
            source.MemoryUsageTargetLevel()?
        }))
    }

    /// Hints the memory-usage level WebView2 should target. Set
    /// [`MemoryUsageTargetLevel::Low`] when the `WebView` is hidden so the
    /// browser can trim memory, and back to `Normal` when it is shown again.
    pub fn set_memory_usage_target_level(&self, level: MemoryUsageTargetLevel) -> Result<()> {
        let source: ICoreWebView2_19 = self.0.cast()?;
        unsafe { source.SetMemoryUsageTargetLevel(level.to_raw()) }.ok()
    }

    /// Returns the [`Settings`] controlling features such as JavaScript, the dev
    /// tools, and context menus.
    pub fn settings(&self) -> Result<Settings> {
        unsafe { Ok(Settings(self.0.Settings()?)) }
    }

    /// Asynchronously runs JavaScript in the context of the current page. The
    /// `handler` closure receives the JSON-encoded result on the UI thread.
    pub fn execute_script<F: FnOnce(Result<String>) + 'static>(
        &self,
        javascript: &str,
        handler: F,
    ) -> Result<()> {
        let javascript = HSTRING::from(javascript);
        let handler = handler::ExecuteScriptCompleted::create(handler);
        unsafe { self.0.ExecuteScript(&javascript, &handler) }.ok()
    }

    /// Asynchronously calls a Chrome DevTools Protocol method.
    ///
    /// `params_json` is the method's JSON object argument, or `"{}"` for none.
    pub fn call_dev_tools_protocol_method<F: FnOnce(Result<String>) + 'static>(
        &self,
        method: &str,
        params_json: &str,
        handler: F,
    ) -> Result<()> {
        let method = HSTRING::from(method);
        let params = HSTRING::from(params_json);
        let handler = handler::CallDevToolsProtocolMethodCompleted::create(handler);
        unsafe {
            self.0
                .CallDevToolsProtocolMethod(&method, &params, &handler)
        }
        .ok()
    }

    /// Subscribes to a Chrome DevTools Protocol event by name.
    ///
    /// Most CDP events require enabling their domain before they fire.
    pub fn on_dev_tools_protocol_event<F>(
        &self,
        event_name: &str,
        handler: F,
    ) -> Result<EventRegistration>
    where
        F: FnMut(DevToolsProtocolEventReceivedArgs) + 'static,
    {
        let event_name = HSTRING::from(event_name);
        let receiver = unsafe { self.0.GetDevToolsProtocolEventReceiver(&event_name)? };
        let handler = handler::DevToolsProtocolEventReceived::create(handler);
        let token = unsafe { receiver.add_DevToolsProtocolEventReceived(&handler)? };
        Ok(EventRegistration::new(move || {
            let _ = unsafe { receiver.remove_DevToolsProtocolEventReceived(token) };
        }))
    }

    /// Registers JavaScript to run before any other script in each new document.
    ///
    /// Pumps the calling thread's message loop until registration completes, so
    /// call it during setup before handing control to your own message loop.
    pub fn add_script_to_execute_on_document_created(&self, javascript: &str) -> Result<ScriptId> {
        let javascript = HSTRING::from(javascript);
        let slot = pump::slot();
        let handler = handler::AddScriptCompleted::create(pump::slot_handler(&slot));
        unsafe {
            self.0
                .AddScriptToExecuteOnDocumentCreated(&javascript, &handler)
                .ok()?;
        }
        Ok(ScriptId(pump::wait(&slot)?))
    }

    /// Removes a script previously registered on document creation.
    pub fn remove_script_to_execute_on_document_created(&self, id: &ScriptId) -> Result<()> {
        let id = HSTRING::from(&id.0);
        unsafe { self.0.RemoveScriptToExecuteOnDocumentCreated(&id) }.ok()
    }

    subscription! {
        /// Subscribes to the navigation-starting event, raised before each
        /// navigation. The handler may inspect the target and cancel it via
        /// [`NavigationStartingArgs::set_cancel`].
        on_navigation_starting(NavigationStartingArgs) =>
            NavigationStarting, add_NavigationStarting / remove_NavigationStarting
    }

    subscription! {
        /// Subscribes to the navigation-completed event.
        on_navigation_completed(NavigationCompletedArgs) =>
            NavigationCompleted, add_NavigationCompleted / remove_NavigationCompleted
    }

    subscription! {
        /// Subscribes to the process-failed event.
        ///
        /// A renderer crash can be reloaded; a browser-process exit requires a new `WebView`.
        on_process_failed(ProcessFailedArgs) =>
            ProcessFailed, add_ProcessFailed / remove_ProcessFailed
    }

    /// Subscribes to HTML fullscreen state changes.
    pub fn on_contains_fullscreen_element_changed<F: FnMut(bool) + 'static>(
        &self,
        handler: F,
    ) -> Result<EventRegistration> {
        let handler = handler::ContainsFullScreenElementChanged::create(handler);
        let token = unsafe { self.0.add_ContainsFullScreenElementChanged(&handler)? };
        let source = self.0.clone();
        Ok(EventRegistration::new(move || {
            let _ = unsafe { source.remove_ContainsFullScreenElementChanged(token) };
        }))
    }

    /// Posts a message to the hosted page as a JSON value. The page receives it
    /// via the `window.chrome.webview.addEventListener("message", ...)` event,
    /// with `event.data` set to the parsed JSON.
    pub fn post_web_message_as_json(&self, json: &str) -> Result<()> {
        let json = HSTRING::from(json);
        unsafe { self.0.PostWebMessageAsJson(&json) }.ok()
    }

    /// Posts a message to the hosted page as a string. The page receives it via
    /// the `window.chrome.webview.addEventListener("message", ...)` event, with
    /// `event.data` set to the string.
    pub fn post_web_message_as_string(&self, message: &str) -> Result<()> {
        let message = HSTRING::from(message);
        unsafe { self.0.PostWebMessageAsString(&message) }.ok()
    }

    subscription! {
        /// Subscribes to the web-message-received event, raised when the hosted
        /// page calls `window.chrome.webview.postMessage`.
        on_web_message_received(WebMessageReceivedArgs) =>
            WebMessageReceived, add_WebMessageReceived / remove_WebMessageReceived
    }

    subscription! {
        /// Subscribes to the content-loading event, raised when the browser
        /// starts loading content for a new document.
        on_content_loading(ContentLoadingArgs) =>
            ContentLoading, add_ContentLoading / remove_ContentLoading
    }

    subscription! {
        /// Subscribes to the document-title-changed event. The handler receives
        /// the new [`document_title`](Self::document_title).
        on_document_title_changed(String) =>
            DocumentTitleChanged, add_DocumentTitleChanged / remove_DocumentTitleChanged
    }

    /// Subscribes to the window-close-requested event, raised when the hosted
    /// page calls `window.close()`. The host typically responds by closing its
    /// window.
    pub fn on_window_close_requested<F: FnMut() + 'static>(
        &self,
        handler: F,
    ) -> Result<EventRegistration> {
        let handler = handler::WindowCloseRequested::create(handler);
        let token = unsafe { self.0.add_WindowCloseRequested(&handler)? };
        let source = self.0.clone();
        Ok(EventRegistration::new(move || {
            let _ = unsafe { source.remove_WindowCloseRequested(token) };
        }))
    }

    subscription! {
        /// Subscribes to the new-window-requested event, raised when the page
        /// tries to open a new window (for example via `window.open`). The
        /// handler may suppress, redirect, or
        /// [defer](NewWindowRequestedArgs::defer) the request.
        on_new_window_requested(NewWindowRequestedArgs) =>
            NewWindowRequested, add_NewWindowRequested / remove_NewWindowRequested
    }

    subscription! {
        /// Subscribes to the permission-requested event, raised when the page
        /// requests access to a capability such as the camera or geolocation.
        /// The handler decides the outcome via
        /// [`PermissionRequestedArgs::set_state`] and may
        /// [defer](PermissionRequestedArgs::defer) the decision.
        on_permission_requested(PermissionRequestedArgs) =>
            PermissionRequested, add_PermissionRequested / remove_PermissionRequested
    }

    /// Subscribes to the download-starting event, raised when a download begins.
    /// The handler receives a [`DownloadStartingArgs`] to inspect or control the
    /// [`DownloadOperation`], change its destination, or cancel it.
    pub fn on_download_starting<F: FnMut(DownloadStartingArgs) + 'static>(
        &self,
        handler: F,
    ) -> Result<EventRegistration> {
        let source: ICoreWebView2_4 = self.0.cast()?;
        let handler = handler::DownloadStarting::create(handler);
        let token = unsafe { source.add_DownloadStarting(&handler)? };
        Ok(EventRegistration::new(move || {
            let _ = unsafe { source.remove_DownloadStarting(token) };
        }))
    }

    /// Subscribes to matching resource requests and optionally fulfills them from memory.
    pub fn on_web_resource_requested<F>(
        &self,
        uri_filter: &str,
        handler: F,
    ) -> Result<EventRegistration>
    where
        F: FnMut(WebResourceRequest) -> Option<WebResourceResponse> + 'static,
    {
        let environment = unsafe { self.0.cast::<ICoreWebView2_2>()?.Environment()? };
        let filter = HSTRING::from(uri_filter);
        unsafe { protocol::add_requested_filter(&self.0, &filter)? };
        let handler = protocol::WebResourceRequested::create(environment, handler);
        let token = match unsafe { self.0.add_WebResourceRequested(&handler) } {
            Ok(token) => token,
            Err(err) => {
                unsafe {
                    protocol::remove_requested_filter(&self.0, &filter);
                };
                return Err(err);
            }
        };
        let source = self.0.clone();
        Ok(EventRegistration::new(move || {
            let _ = unsafe { source.remove_WebResourceRequested(token) };
            unsafe {
                protocol::remove_requested_filter(&source, &filter);
            };
        }))
    }
}