wvwasi_wry/
lib.rs

1// Copyright 2020-2023 Tauri Programme within The Commons Conservancy
2// SPDX-License-Identifier: Apache-2.0
3// SPDX-License-Identifier: MIT
4
5//! Wry is a Cross-platform WebView rendering library.
6//!
7//! The webview requires a running event loop and a window type that implements [`HasRawWindowHandle`],
8//! or a gtk container widget if you need to support X11 and Wayland.
9//! You can use a windowing library like [`tao`] or [`winit`].
10//!
11//! ## Examples
12//!
13//! This example leverages the [`HasRawWindowHandle`] and supports Windows, macOS, iOS, Android and Linux (X11 Only)
14//!
15//! ```no_run
16//! use wry::{WebViewBuilder, raw_window_handle};
17//!
18//! # struct T;
19//! # unsafe impl raw_window_handle::HasRawWindowHandle for T {
20//! #   fn raw_window_handle(&self) -> raw_window_handle::RawWindowHandle {
21//! #     raw_window_handle::RawWindowHandle::Win32(raw_window_handle::Win32WindowHandle::empty())
22//! #   }
23//! # }
24//! # let window = T;
25//! let webview = WebViewBuilder::new(&window)
26//!   .with_url("https://tauri.app")
27//!   .unwrap()
28//!   .build()
29//!   .unwrap();
30//! ```
31//!
32//! If you also want to support Wayland too, then we recommend you use [`WebViewBuilderExtUnix::new_gtk`] on Linux.
33//!
34//! ```no_run,ignore
35//! use wry::WebViewBuilder;
36//!
37//! #[cfg(any(
38//!   target_os = "windows",
39//!   target_os = "macos",
40//!   target_os = "ios",
41//!   target_os = "android"
42//! ))]
43//! let builder = WebViewBuilder::new(&window);
44//! #[cfg(not(any(
45//!   target_os = "windows",
46//!   target_os = "macos",
47//!   target_os = "ios",
48//!   target_os = "android"
49//! )))]
50//! let builder = {
51//!   use tao::platform::unix::WindowExtUnix;
52//!   use wry::WebViewBuilderExtUnix;
53//!   WebViewBuilder::new_gtk(&window.gtk_window())
54//! };
55//!
56//! let webview = builder
57//!   .with_url("https://tauri.app")
58//!   .unwrap()
59//!   .build()
60//!   .unwrap();
61//! ```
62//!
63//! ## Child webviews
64//!
65//! You can use [`WebView::new_as_child`] to create the webview as a child inside another window. This is supported on
66//! macOS, Windows and Linux (X11 Only).
67//!
68//! ```no_run
69//! use wry::{WebViewBuilder, raw_window_handle};
70//!
71//! # struct T;
72//! # unsafe impl raw_window_handle::HasRawWindowHandle for T {
73//! #   fn raw_window_handle(&self) -> raw_window_handle::RawWindowHandle {
74//! #     raw_window_handle::RawWindowHandle::Win32(raw_window_handle::Win32WindowHandle::empty())
75//! #   }
76//! # }
77//! # let window = T;
78//! let webview = WebViewBuilder::new_as_child(&window)
79//!   .with_url("https://tauri.app")
80//!   .unwrap()
81//!   .build()
82//!   .unwrap();
83//! ```
84//!
85//! ## Platform Considerations
86//!
87//! Note that on Linux, we use webkit2gtk webviews so if the windowing library doesn't support gtk (as in [`winit`])
88//! you'll need to call [`gtk::init`] before creating the webview and then call [`gtk::main_iteration_do`] alongside
89//! your windowing library event loop.
90//!
91//! ```no_run,ignore
92//! use winit::{event_loop::EventLoop, window::Window};
93//! use wry::WebView;
94//!
95//! fn main() {
96//!   let event_loop = EventLoop::new().unwrap();
97//!   gtk::init().unwrap(); // <----- IMPORTANT
98//!   let window = Window::new(&event_loop).unwrap();
99//!   let webview = WebView::new(&window);
100//!   event_loop.run(|_e, _evl|{
101//!     // process winit events
102//!      
103//!     // then advance gtk event loop  <----- IMPORTANT
104//!     while gtk::events_pending() {
105//!       gtk::main_iteration_do(false);
106//!     }
107//!   }).unwrap();
108//! }
109//! ```
110//!
111//! ## Android
112//!
113//! In order for `wry` to be able to create webviews on Android, there is a few requirements that your application needs to uphold:
114//! 1. You need to set a few environment variables that will be used to generate the necessary kotlin
115//! files that you need to include in your Android application for wry to function properly.
116//!     - `WRY_ANDROID_PACKAGE`: which is the reversed domain name of your android project and the app name in snake_case, for example, `com.wry.example.wry_app`
117//!     - `WRY_ANDROID_LIBRARY`: for example, if your cargo project has a lib name `wry_app`, it will generate `libwry_app.so` so you se this env var to `wry_app`
118//!     - `WRY_ANDROID_KOTLIN_FILES_OUT_DIR`: for example, `path/to/app/src/main/kotlin/com/wry/example`
119//! 2. Your main Android Activity needs to inherit `AppCompatActivity`, preferably it should use the generated `WryActivity` or inherit it.
120//! 3. Your Rust app needs to call `wry::android_setup` function to setup the necessary logic to be able to create webviews later on.
121//! 4. Your Rust app needs to call `wry::android_binding!` macro to setup the JNI functions that will be called by `WryActivity` and various other places.
122//!
123//! It is recommended to use [`tao`](https://docs.rs/tao/latest/tao/) crate as it provides maximum compatibility with `wry`
124//!
125//! ```
126//! #[cfg(target_os = "android")]
127//! {
128//!   tao::android_binding!(
129//!       com_example,
130//!       wry_app,
131//!       WryActivity,
132//!       wry::android_setup, // pass the wry::android_setup function to tao which will invoke when the event loop is created
133//!       _start_app
134//!   );
135//!   wry::android_binding!(com_example, ttt);
136//! }
137//! ```
138//!
139//! If this feels overwhelming, you can just use the preconfigured template from [`cargo-mobile2`](https://github.com/tauri-apps/cargo-mobile2).
140//!
141//! For more inforamtion, checkout [MOBILE.md](https://github.com/tauri-apps/wry/blob/dev/MOBILE.md).
142//!
143//! ## Feature flags
144//!
145//! Wry uses a set of feature flags to toggle several advanced features.
146//!
147//! - `os-webview` (default): Enables the default WebView framework on the platform. This must be enabled
148//! for the crate to work. This feature was added in preparation of other ports like cef and servo.
149//! - `protocol` (default): Enables [`WebViewBuilder::with_custom_protocol`] to define custom URL scheme for handling tasks like
150//! loading assets.
151//! - `file-drop` (default): Enables [`WebViewBuilder::with_file_drop_handler`] to control the behaviour when there are files
152//! interacting with the window.
153//! - `devtools`: Enables devtools on release builds. Devtools are always enabled in debug builds.
154//! On **macOS**, enabling devtools, requires calling private apis so you should not enable this flag in release
155//! build if your app needs to publish to App Store.
156//! - `transparent`: Transparent background on **macOS** requires calling private functions.
157//! Avoid this in release build if your app needs to publish to App Store.
158//! - `fullscreen`: Fullscreen video and other media on **macOS** requires calling private functions.
159//! Avoid this in release build if your app needs to publish to App Store.
160//! libraries and prevent from building documentation on doc.rs fails.
161//! - `linux-body`: Enables body support of custom protocol request on Linux. Requires
162//! webkit2gtk v2.40 or above.
163//!
164//! [`tao`]: https://docs.rs/tao
165//! [`winit`]: https://docs.rs/winit
166
167#![allow(clippy::new_without_default)]
168#![allow(clippy::default_constructed_unit_structs)]
169#![allow(clippy::type_complexity)]
170#![cfg_attr(docsrs, feature(doc_cfg))]
171
172#[cfg(any(target_os = "macos", target_os = "ios"))]
173#[macro_use]
174extern crate objc;
175
176mod error;
177mod proxy;
178mod web_context;
179
180#[cfg(target_os = "android")]
181pub(crate) mod android;
182#[cfg(target_os = "android")]
183pub use crate::android::android_setup;
184#[cfg(target_os = "android")]
185pub mod prelude {
186  pub use crate::android::{binding::*, dispatch, find_class, Context};
187  pub use tao_macros::{android_fn, generate_package_name};
188}
189#[cfg(target_os = "android")]
190pub use android::JniHandle;
191#[cfg(target_os = "android")]
192use android::*;
193
194#[cfg(gtk)]
195pub(crate) mod webkitgtk;
196/// Re-exported [raw-window-handle](https://docs.rs/raw-window-handle/latest/raw_window_handle/) crate.
197pub use raw_window_handle;
198use raw_window_handle::HasRawWindowHandle;
199#[cfg(gtk)]
200use webkitgtk::*;
201
202#[cfg(any(target_os = "macos", target_os = "ios"))]
203pub(crate) mod wkwebview;
204#[cfg(any(target_os = "macos", target_os = "ios"))]
205use wkwebview::*;
206
207#[cfg(target_os = "windows")]
208pub(crate) mod webview2;
209#[cfg(target_os = "windows")]
210use self::webview2::*;
211#[cfg(target_os = "windows")]
212use webview2_com::Microsoft::Web::WebView2::Win32::ICoreWebView2Controller;
213use std::{borrow::Cow, path::PathBuf, rc::Rc};
214
215use http::{Request, Response};
216
217pub use error::*;
218pub use http;
219pub use proxy::{ProxyConfig, ProxyEndpoint};
220pub use url::Url;
221pub use web_context::WebContext;
222
223/// A rectangular region.
224#[derive(Clone, Copy, Debug, Default)]
225pub struct Rect {
226  /// x coordinate of top left corner
227  pub x: i32,
228  /// y coordinate of top left corner
229  pub y: i32,
230  /// width
231  pub width: u32,
232  /// height
233  pub height: u32,
234}
235
236/// Resolves a custom protocol [`Request`] asynchronously.
237///
238/// See [`WebViewBuilder::with_asynchronous_custom_protocol`] for more information.
239pub struct RequestAsyncResponder {
240  pub(crate) responder: Box<dyn FnOnce(Response<Cow<'static, [u8]>>)>,
241}
242
243// SAFETY: even though the webview bindings do not indicate the responder is Send,
244// it actually is and we need it in order to let the user do the protocol computation
245// on a separate thread or async task.
246unsafe impl Send for RequestAsyncResponder {}
247
248impl RequestAsyncResponder {
249  /// Resolves the request with the given response.
250  pub fn respond<T: Into<Cow<'static, [u8]>>>(self, response: Response<T>) {
251    let (parts, body) = response.into_parts();
252    (self.responder)(Response::from_parts(parts, body.into()))
253  }
254}
255
256pub struct WebViewAttributes {
257  /// Whether the WebView should have a custom user-agent.
258  pub user_agent: Option<String>,
259
260  /// Whether the WebView window should be visible.
261  pub visible: bool,
262
263  /// Whether the WebView should be transparent.
264  ///
265  /// ## Platform-specific:
266  ///
267  /// **Windows 7**: Not supported.
268  pub transparent: bool,
269
270  /// Specify the webview background color. This will be ignored if `transparent` is set to `true`.
271  ///
272  /// The color uses the RGBA format.
273  ///
274  /// ## Platform-specific:
275  ///
276  /// - **macOS / iOS**: Not implemented.
277  /// - **Windows**:
278  ///   - On Windows 7, transparency is not supported and the alpha value will be ignored.
279  ///   - On Windows higher than 7: translucent colors are not supported so any alpha value other than `0` will be replaced by `255`
280  pub background_color: Option<RGBA>,
281
282  /// Whether load the provided URL to [`WebView`].
283  pub url: Option<Url>,
284
285  /// Headers used when loading the requested `url`.
286  pub headers: Option<http::HeaderMap>,
287
288  /// Whether page zooming by hotkeys is enabled
289  ///
290  /// ## Platform-specific
291  ///
292  /// **macOS / Linux / Android / iOS**: Unsupported
293  pub zoom_hotkeys_enabled: bool,
294
295  /// Whether load the provided html string to [`WebView`].
296  /// This will be ignored if the `url` is provided.
297  ///
298  /// # Warning
299  ///
300  /// The Page loaded from html string will have `null` origin.
301  ///
302  /// ## PLatform-specific:
303  ///
304  /// - **Windows:** the string can not be larger than 2 MB (2 * 1024 * 1024 bytes) in total size
305  pub html: Option<String>,
306
307  /// Initialize javascript code when loading new pages. When webview load a new page, this
308  /// initialization code will be executed. It is guaranteed that code is executed before
309  /// `window.onload`.
310  ///
311  /// ## Platform-specific
312  ///
313  /// - **Android:** The Android WebView does not provide an API for initialization scripts,
314  /// so we prepend them to each HTML head. They are only implemented on custom protocol URLs.
315  pub initialization_scripts: Vec<String>,
316
317  /// A list of custom loading protocols with pairs of scheme uri string and a handling
318  /// closure.
319  ///
320  /// The closure takes a [Request] and returns a [Response].
321  ///
322  /// # Warning
323  ///
324  /// Pages loaded from custom protocol will have different Origin on different platforms. And
325  /// servers which enforce CORS will need to add exact same Origin header in `Access-Control-Allow-Origin`
326  /// if you wish to send requests with native `fetch` and `XmlHttpRequest` APIs. Here are the
327  /// different Origin headers across platforms:
328  ///
329  /// - macOS, iOS and Linux: `<scheme_name>://<path>` (so it will be `wry://path/to/page/`).
330  /// - Windows and Android: `http://<scheme_name>.<path>` by default (so it will be `http://wry.path/to/page). To use `https` instead of `http`, use [`WebViewBuilderExtWindows::with_https_scheme`] and [`WebViewBuilderExtAndroid::with_https_scheme`].
331  ///
332  /// # Reading assets on mobile
333  ///
334  /// - Android: Android has `assets` and `resource` path finder to
335  /// locate your files in those directories. For more information, see [Loading in-app content](https://developer.android.com/guide/webapps/load-local-content) page.
336  /// - iOS: To get the path of your assets, you can call [`CFBundle::resources_path`](https://docs.rs/core-foundation/latest/core_foundation/bundle/struct.CFBundle.html#method.resources_path). So url like `wry://assets/index.html` could get the html file in assets directory.
337  pub custom_protocols: Vec<(String, Box<dyn Fn(Request<Vec<u8>>, RequestAsyncResponder)>)>,
338
339  /// The IPC handler to receive the message from Javascript on webview
340  /// using `window.ipc.postMessage("insert_message_here")` to host Rust code.
341  pub ipc_handler: Option<Box<dyn Fn(String)>>,
342
343  /// A handler closure to process incoming [`FileDropEvent`] of the webview.
344  ///
345  /// # Blocking OS Default Behavior
346  /// Return `true` in the callback to block the OS' default behavior of handling a file drop.
347  ///
348  /// Note, that if you do block this behavior, it won't be possible to drop files on `<input type="file">` forms.
349  /// Also note, that it's not possible to manually set the value of a `<input type="file">` via JavaScript for security reasons.
350  #[cfg(feature = "file-drop")]
351  pub file_drop_handler: Option<Box<dyn Fn(FileDropEvent) -> bool>>,
352  #[cfg(not(feature = "file-drop"))]
353  file_drop_handler: Option<Box<dyn Fn(FileDropEvent) -> bool>>,
354
355  /// A navigation handler to decide if incoming url is allowed to navigate.
356  ///
357  /// The closure take a `String` parameter as url and returns a `bool` to determine whether the navigation should happen.
358  /// `true` allows to navigate and `false` does not.
359  pub navigation_handler: Option<Box<dyn Fn(String) -> bool>>,
360
361  /// A download started handler to manage incoming downloads.
362  ///
363  /// The closure takes two parameters, the first is a `String` representing the url being downloaded from and and the
364  /// second is a mutable `PathBuf` reference that (possibly) represents where the file will be downloaded to. The latter
365  /// parameter can be used to set the download location by assigning a new path to it, the assigned path _must_ be
366  /// absolute. The closure returns a `bool` to allow or deny the download.
367  pub download_started_handler: Option<Box<dyn FnMut(String, &mut PathBuf) -> bool>>,
368
369  /// A download completion handler to manage downloads that have finished.
370  ///
371  /// The closure is fired when the download completes, whether it was successful or not.
372  /// The closure takes a `String` representing the URL of the original download request, an `Option<PathBuf>`
373  /// potentially representing the filesystem path the file was downloaded to, and a `bool` indicating if the download
374  /// succeeded. A value of `None` being passed instead of a `PathBuf` does not necessarily indicate that the download
375  /// did not succeed, and may instead indicate some other failure, always check the third parameter if you need to
376  /// know if the download succeeded.
377  ///
378  /// ## Platform-specific:
379  ///
380  /// - **macOS**: The second parameter indicating the path the file was saved to, is always empty,
381  /// due to API limitations.
382  pub download_completed_handler: Option<Rc<dyn Fn(String, Option<PathBuf>, bool) + 'static>>,
383
384  /// A new window handler to decide if incoming url is allowed to open in a new window.
385  ///
386  /// The closure take a `String` parameter as url and return `bool` to determine whether the window should open.
387  /// `true` allows to open and `false` does not.
388  pub new_window_req_handler: Option<Box<dyn Fn(String) -> bool>>,
389
390  /// Enables clipboard access for the page rendered on **Linux** and **Windows**.
391  ///
392  /// macOS doesn't provide such method and is always enabled by default. But your app will still need to add menu
393  /// item accelerators to use the clipboard shortcuts.
394  pub clipboard: bool,
395
396  /// Enable web inspector which is usually called browser devtools.
397  ///
398  /// Note this only enables devtools to the webview. To open it, you can call
399  /// [`WebView::open_devtools`], or right click the page and open it from the context menu.
400  ///
401  /// ## Platform-specific
402  ///
403  /// - macOS: This will call private functions on **macOS**. It is enabled in **debug** builds,
404  /// but requires `devtools` feature flag to actually enable it in **release** builds.
405  /// - Android: Open `chrome://inspect/#devices` in Chrome to get the devtools window. Wry's `WebView` devtools API isn't supported on Android.
406  /// - iOS: Open Safari > Develop > [Your Device Name] > [Your WebView] to get the devtools window.
407  pub devtools: bool,
408
409  /// Whether clicking an inactive window also clicks through to the webview. Default is `false`.
410  ///
411  /// ## Platform-specific
412  ///
413  /// This configuration only impacts macOS.
414  pub accept_first_mouse: bool,
415
416  /// Indicates whether horizontal swipe gestures trigger backward and forward page navigation.
417  ///
418  /// ## Platform-specific:
419  ///
420  /// - **Android / iOS:** Unsupported.
421  pub back_forward_navigation_gestures: bool,
422
423  /// Set a handler closure to process the change of the webview's document title.
424  pub document_title_changed_handler: Option<Box<dyn Fn(String)>>,
425
426  /// Run the WebView with incognito mode. Note that WebContext will be ingored if incognito is
427  /// enabled.
428  ///
429  /// ## Platform-specific:
430  ///
431  /// - **Android:** Unsupported yet.
432  pub incognito: bool,
433
434  /// Whether all media can be played without user interaction.
435  pub autoplay: bool,
436
437  /// Set a handler closure to process page load events.
438  pub on_page_load_handler: Option<Box<dyn Fn(PageLoadEvent, String)>>,
439
440  /// Set a proxy configuration for the webview. Supports HTTP CONNECT and SOCKSv5 proxies
441  ///
442  /// - **macOS**: Requires macOS 14.0+ and the `mac-proxy` feature flag to be enabled.
443  /// - **Android / iOS:** Not supported.
444  pub proxy_config: Option<ProxyConfig>,
445
446  /// Whether the webview should be focused when created.
447  ///
448  /// ## Platform-specific:
449  ///
450  /// - **macOS / Android / iOS:** Unsupported.
451  pub focused: bool,
452
453  /// The webview bounds. Defaults to `x: 0, y: 0, width: 200, height: 200`.
454  /// This is only effective if the webview was created by [`WebView::new_as_child`] or [`WebViewBuilder::new_as_child`].
455  pub bounds: Option<Rect>,
456}
457
458impl Default for WebViewAttributes {
459  fn default() -> Self {
460    Self {
461      user_agent: None,
462      visible: true,
463      transparent: false,
464      background_color: None,
465      url: None,
466      headers: None,
467      html: None,
468      initialization_scripts: vec![],
469      custom_protocols: vec![],
470      ipc_handler: None,
471      file_drop_handler: None,
472      navigation_handler: None,
473      download_started_handler: None,
474      download_completed_handler: None,
475      new_window_req_handler: None,
476      clipboard: false,
477      #[cfg(debug_assertions)]
478      devtools: true,
479      #[cfg(not(debug_assertions))]
480      devtools: false,
481      zoom_hotkeys_enabled: false,
482      accept_first_mouse: false,
483      back_forward_navigation_gestures: false,
484      document_title_changed_handler: None,
485      incognito: false,
486      autoplay: true,
487      on_page_load_handler: None,
488      proxy_config: None,
489      focused: true,
490      bounds: Some(Rect {
491        x: 0,
492        y: 0,
493        width: 200,
494        height: 200,
495      }),
496    }
497  }
498}
499
500/// Builder type of [`WebView`].
501///
502/// [`WebViewBuilder`] / [`WebView`] are the basic building blocks to construct WebView contents and
503/// scripts for those who prefer to control fine grained window creation and event handling.
504/// [`WebViewBuilder`] provides ability to setup initialization before web engine starts.
505pub struct WebViewBuilder<'a> {
506  pub attrs: WebViewAttributes,
507  as_child: bool,
508  window: Option<&'a dyn HasRawWindowHandle>,
509  platform_specific: PlatformSpecificWebViewAttributes,
510  web_context: Option<&'a mut WebContext>,
511  #[cfg(gtk)]
512  gtk_widget: Option<&'a gtk::Container>,
513}
514
515impl<'a> WebViewBuilder<'a> {
516  /// Create a [`WebViewBuilder`] from a type that implements [`HasRawWindowHandle`].
517  ///
518  /// # Platform-specific:
519  ///
520  /// - **Linux**: Only X11 is supported, if you want to support Wayland too, use [`WebViewBuilderExtUnix::new_gtk`].
521  ///
522  ///   Although this methods only needs an X11 window handle, we use webkit2gtk, so you still need to initialize gtk
523  ///   by callling [`gtk::init`] and advance its loop alongside your event loop using [`gtk::main_iteration_do`].
524  ///   Checkout the [Platform Considerations](https://docs.rs/wry/latest/wry/#platform-considerations) section in the crate root documentation.
525  /// - **Windows**: The webview will auto-resize when the passed handle is resized.
526  /// - **Linux (X11)**: Unlike macOS and Windows, the webview will not auto-resize and you'll need to call [`WebView::set_size`] manually.
527  ///
528  /// # Panics:
529  ///
530  /// - Panics if the provided handle was not supported or invalid.
531  /// - Panics on Linux, if [`gtk::init`] was not called in this thread.
532  pub fn new(window: &'a impl HasRawWindowHandle) -> Self {
533    Self {
534      attrs: WebViewAttributes::default(),
535      window: Some(window),
536      as_child: false,
537      #[allow(clippy::default_constructed_unit_structs)]
538      platform_specific: PlatformSpecificWebViewAttributes::default(),
539      web_context: None,
540      #[cfg(gtk)]
541      gtk_widget: None,
542    }
543  }
544
545  /// Create [`WebViewBuilder`] as a child window inside the provided [`HasRawWindowHandle`].
546  ///
547  /// ## Platform-specific
548  ///
549  /// - **Windows**: This will create the webview as a child window of the `parent` window.
550  /// - **macOS**: This will create the webview as a `NSView` subview of the `parent` window's
551  /// content view.
552  /// - **Linux**: This will create the webview as a child window of the `parent` window. Only X11
553  /// is supported. This method won't work on Wayland.
554  ///
555  ///   Although this methods only needs an X11 window handle, you use webkit2gtk, so you still need to initialize gtk
556  ///   by callling [`gtk::init`] and advance its loop alongside your event loop using [`gtk::main_iteration_do`].
557  ///   Checkout the [Platform Considerations](https://docs.rs/wry/latest/wry/#platform-considerations) section in the crate root documentation.
558  /// - **Android/iOS:** Unsupported.
559  ///
560  /// # Panics:
561  ///
562  /// - Panics if the provided handle was not support or invalid.
563  /// - Panics on Linux, if [`gtk::init`] was not called in this thread.
564  pub fn new_as_child(parent: &'a impl HasRawWindowHandle) -> Self {
565    Self {
566      attrs: WebViewAttributes::default(),
567      window: Some(parent),
568      as_child: true,
569      #[allow(clippy::default_constructed_unit_structs)]
570      platform_specific: PlatformSpecificWebViewAttributes::default(),
571      web_context: None,
572      #[cfg(gtk)]
573      gtk_widget: None,
574    }
575  }
576
577  /// Indicates whether horizontal swipe gestures trigger backward and forward page navigation.
578  ///
579  /// ## Platform-specific:
580  ///
581  /// - **Android / iOS:** Unsupported.
582  pub fn with_back_forward_navigation_gestures(mut self, gesture: bool) -> Self {
583    self.attrs.back_forward_navigation_gestures = gesture;
584    self
585  }
586
587  /// Sets whether the WebView should be transparent.
588  ///
589  /// ## Platform-specific:
590  ///
591  /// **Windows 7**: Not supported.
592  pub fn with_transparent(mut self, transparent: bool) -> Self {
593    self.attrs.transparent = transparent;
594    self
595  }
596
597  /// Specify the webview background color. This will be ignored if `transparent` is set to `true`.
598  ///
599  /// The color uses the RGBA format.
600  ///
601  /// ## Platfrom-specific:
602  ///
603  /// - **macOS / iOS**: Not implemented.
604  /// - **Windows**:
605  ///   - on Windows 7, transparency is not supported and the alpha value will be ignored.
606  ///   - on Windows higher than 7: translucent colors are not supported so any alpha value other than `0` will be replaced by `255`
607  pub fn with_background_color(mut self, background_color: RGBA) -> Self {
608    self.attrs.background_color = Some(background_color);
609    self
610  }
611
612  /// Sets whether the WebView should be visible or not.
613  pub fn with_visible(mut self, visible: bool) -> Self {
614    self.attrs.visible = visible;
615    self
616  }
617
618  /// Sets whether all media can be played without user interaction.
619  pub fn with_autoplay(mut self, autoplay: bool) -> Self {
620    self.attrs.autoplay = autoplay;
621    self
622  }
623
624  /// Initialize javascript code when loading new pages. When webview load a new page, this
625  /// initialization code will be executed. It is guaranteed that code is executed before
626  /// `window.onload`.
627  ///
628  /// ## Platform-specific
629  ///
630  /// - **Android:** When [addDocumentStartJavaScript] is not supported,
631  /// we prepend them to each HTML head (implementation only supported on custom protocol URLs).
632  /// For remote URLs, we use [onPageStarted] which is not guaranteed to run before other scripts.
633  ///
634  /// [addDocumentStartJavaScript]: https://developer.android.com/reference/androidx/webkit/WebViewCompat#addDocumentStartJavaScript(android.webkit.WebView,java.lang.String,java.util.Set%3Cjava.lang.String%3E)
635  /// [onPageStarted]: https://developer.android.com/reference/android/webkit/WebViewClient#onPageStarted(android.webkit.WebView,%20java.lang.String,%20android.graphics.Bitmap)
636  pub fn with_initialization_script(mut self, js: &str) -> Self {
637    if !js.is_empty() {
638      self.attrs.initialization_scripts.push(js.to_string());
639    }
640    self
641  }
642
643  /// Register custom loading protocols with pairs of scheme uri string and a handling
644  /// closure.
645  ///
646  /// The closure takes a [Request] and returns a [Response]
647  ///
648  /// # Warning
649  ///
650  /// Pages loaded from custom protocol will have different Origin on different platforms. And
651  /// servers which enforce CORS will need to add exact same Origin header in `Access-Control-Allow-Origin`
652  /// if you wish to send requests with native `fetch` and `XmlHttpRequest` APIs. Here are the
653  /// different Origin headers across platforms:
654  ///
655  /// - macOS, iOS and Linux: `<scheme_name>://<path>` (so it will be `wry://path/to/page).
656  /// - Windows and Android: `http://<scheme_name>.<path>` by default (so it will be `http://wry.path/to/page`). To use `https` instead of `http`, use [`WebViewBuilderExtWindows::with_https_scheme`] and [`WebViewBuilderExtAndroid::with_https_scheme`].
657  ///
658  /// # Reading assets on mobile
659  ///
660  /// - Android: For loading content from the `assets` folder (which is copied to the Andorid apk) please
661  /// use the function [`with_asset_loader`] from [`WebViewBuilderExtAndroid`] instead.
662  /// This function on Android can only be used to serve assets you can embed in the binary or are
663  /// elsewhere in Android (provided the app has appropriate access), but not from the `assets`
664  /// folder which lives within the apk. For the cases where this can be used, it works the same as in macOS and Linux.
665  /// - iOS: To get the path of your assets, you can call [`CFBundle::resources_path`](https://docs.rs/core-foundation/latest/core_foundation/bundle/struct.CFBundle.html#method.resources_path). So url like `wry://assets/index.html` could get the html file in assets directory.
666  #[cfg(feature = "protocol")]
667  pub fn with_custom_protocol<F>(mut self, name: String, handler: F) -> Self
668  where
669    F: Fn(Request<Vec<u8>>) -> Response<Cow<'static, [u8]>> + 'static,
670  {
671    self.attrs.custom_protocols.push((
672      name,
673      Box::new(move |request, responder| {
674        let http_response = handler(request);
675        responder.respond(http_response);
676      }),
677    ));
678    self
679  }
680
681  /// Same as [`Self::with_custom_protocol`] but with an asynchronous responder.
682  ///
683  /// # Examples
684  ///
685  /// ```no_run
686  /// use wry::{WebViewBuilder, raw_window_handle};
687  ///
688  /// # struct T;
689  /// # unsafe impl raw_window_handle::HasRawWindowHandle for T {
690  /// #   fn raw_window_handle(&self) -> raw_window_handle::RawWindowHandle {
691  /// #     raw_window_handle::RawWindowHandle::Win32(raw_window_handle::Win32WindowHandle::empty())
692  /// #   }
693  /// # }
694  /// # let window = T;
695  /// WebViewBuilder::new(&window)
696  ///   .with_asynchronous_custom_protocol("wry".into(), |request, responder| {
697  ///     // here you can use a tokio task, thread pool or anything
698  ///     // to do heavy computation to resolve your request
699  ///     // e.g. downloading files, opening the camera...
700  ///     std::thread::spawn(move || {
701  ///       std::thread::sleep(std::time::Duration::from_secs(2));
702  ///       responder.respond(http::Response::builder().body(Vec::new()).unwrap());
703  ///     });
704  ///   });
705  /// ```
706  #[cfg(feature = "protocol")]
707  pub fn with_asynchronous_custom_protocol<F>(mut self, name: String, handler: F) -> Self
708  where
709    F: Fn(Request<Vec<u8>>, RequestAsyncResponder) + 'static,
710  {
711    self.attrs.custom_protocols.push((name, Box::new(handler)));
712    self
713  }
714
715  /// Set the IPC handler to receive the message from Javascript on webview
716  /// using `window.ipc.postMessage("insert_message_here")` to host Rust code.
717  pub fn with_ipc_handler<F>(mut self, handler: F) -> Self
718  where
719    F: Fn(String) + 'static,
720  {
721    self.attrs.ipc_handler = Some(Box::new(handler));
722    self
723  }
724
725  /// Set a handler closure to process incoming [`FileDropEvent`] of the webview.
726  ///
727  /// # Blocking OS Default Behavior
728  /// Return `true` in the callback to block the OS' default behavior of handling a file drop.
729  ///
730  /// Note, that if you do block this behavior, it won't be possible to drop files on `<input type="file">` forms.
731  /// Also note, that it's not possible to manually set the value of a `<input type="file">` via JavaScript for security reasons.
732  #[cfg(feature = "file-drop")]
733  pub fn with_file_drop_handler<F>(mut self, handler: F) -> Self
734  where
735    F: Fn(FileDropEvent) -> bool + 'static,
736  {
737    self.attrs.file_drop_handler = Some(Box::new(handler));
738    self
739  }
740
741  /// Load the provided URL with given headers when the builder calling [`WebViewBuilder::build`] to create the [`WebView`].
742  /// The provided URL must be valid.
743  pub fn with_url_and_headers(mut self, url: &str, headers: http::HeaderMap) -> Result<Self> {
744    self.attrs.url = Some(url.parse()?);
745    self.attrs.headers = Some(headers);
746    Ok(self)
747  }
748
749  /// Load the provided URL when the builder calling [`WebViewBuilder::build`] to create the [`WebView`].
750  /// The provided URL must be valid.
751  pub fn with_url(mut self, url: &str) -> Result<Self> {
752    self.attrs.url = Some(Url::parse(url)?);
753    self.attrs.headers = None;
754    Ok(self)
755  }
756
757  /// Load the provided HTML string when the builder calling [`WebViewBuilder::build`] to create the [`WebView`].
758  /// This will be ignored if `url` is provided.
759  ///
760  /// # Warning
761  ///
762  /// The Page loaded from html string will have `null` origin.
763  ///
764  /// ## PLatform-specific:
765  ///
766  /// - **Windows:** the string can not be larger than 2 MB (2 * 1024 * 1024 bytes) in total size
767  pub fn with_html(mut self, html: impl Into<String>) -> Result<Self> {
768    self.attrs.html = Some(html.into());
769    Ok(self)
770  }
771
772  /// Set the web context that can be shared with multiple [`WebView`]s.
773  pub fn with_web_context(mut self, web_context: &'a mut WebContext) -> Self {
774    self.web_context = Some(web_context);
775    self
776  }
777
778  /// Set a custom [user-agent](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/User-Agent) for the WebView.
779  pub fn with_user_agent(mut self, user_agent: &str) -> Self {
780    self.attrs.user_agent = Some(user_agent.to_string());
781    self
782  }
783
784  /// Enable or disable web inspector which is usually called devtools.
785  ///
786  /// Note this only enables devtools to the webview. To open it, you can call
787  /// [`WebView::open_devtools`], or right click the page and open it from the context menu.
788  ///
789  /// ## Platform-specific
790  ///
791  /// - macOS: This will call private functions on **macOS**. It is enabled in **debug** builds,
792  /// but requires `devtools` feature flag to actually enable it in **release** builds.
793  /// - Android: Open `chrome://inspect/#devices` in Chrome to get the devtools window. Wry's `WebView` devtools API isn't supported on Android.
794  /// - iOS: Open Safari > Develop > [Your Device Name] > [Your WebView] to get the devtools window.
795  pub fn with_devtools(mut self, devtools: bool) -> Self {
796    self.attrs.devtools = devtools;
797    self
798  }
799
800  /// Whether page zooming by hotkeys or gestures is enabled
801  ///
802  /// ## Platform-specific
803  ///
804  /// **macOS / Linux / Android / iOS**: Unsupported
805  pub fn with_hotkeys_zoom(mut self, zoom: bool) -> Self {
806    self.attrs.zoom_hotkeys_enabled = zoom;
807    self
808  }
809
810  /// Set a navigation handler to decide if incoming url is allowed to navigate.
811  ///
812  /// The closure take a `String` parameter as url and returns a `bool` to determine whether the navigation should happen.
813  /// `true` allows to navigate and `false` does not.
814  pub fn with_navigation_handler(mut self, callback: impl Fn(String) -> bool + 'static) -> Self {
815    self.attrs.navigation_handler = Some(Box::new(callback));
816    self
817  }
818
819  /// Set a download started handler to manage incoming downloads.
820  ///
821  //// The closure takes two parameters, the first is a `String` representing the url being downloaded from and and the
822  /// second is a mutable `PathBuf` reference that (possibly) represents where the file will be downloaded to. The latter
823  /// parameter can be used to set the download location by assigning a new path to it, the assigned path _must_ be
824  /// absolute. The closure returns a `bool` to allow or deny the download.
825  pub fn with_download_started_handler(
826    mut self,
827    started_handler: impl FnMut(String, &mut PathBuf) -> bool + 'static,
828  ) -> Self {
829    self.attrs.download_started_handler = Some(Box::new(started_handler));
830    self
831  }
832
833  /// Sets a download completion handler to manage downloads that have finished.
834  ///
835  /// The closure is fired when the download completes, whether it was successful or not.
836  /// The closure takes a `String` representing the URL of the original download request, an `Option<PathBuf>`
837  /// potentially representing the filesystem path the file was downloaded to, and a `bool` indicating if the download
838  /// succeeded. A value of `None` being passed instead of a `PathBuf` does not necessarily indicate that the download
839  /// did not succeed, and may instead indicate some other failure, always check the third parameter if you need to
840  /// know if the download succeeded.
841  ///
842  /// ## Platform-specific:
843  ///
844  /// - **macOS**: The second parameter indicating the path the file was saved to, is always empty,
845  /// due to API limitations.
846  pub fn with_download_completed_handler(
847    mut self,
848    download_completed_handler: impl Fn(String, Option<PathBuf>, bool) + 'static,
849  ) -> Self {
850    self.attrs.download_completed_handler = Some(Rc::new(download_completed_handler));
851    self
852  }
853
854  /// Enables clipboard access for the page rendered on **Linux** and **Windows**.
855  ///
856  /// macOS doesn't provide such method and is always enabled by default. But your app will still need to add menu
857  /// item accelerators to use the clipboard shortcuts.
858  pub fn with_clipboard(mut self, clipboard: bool) -> Self {
859    self.attrs.clipboard = clipboard;
860    self
861  }
862
863  /// Set a new window request handler to decide if incoming url is allowed to be opened.
864  ///
865  /// The closure take a `String` parameter as url and return `bool` to determine whether the window should open.
866  /// `true` allows to open and `false` does not.
867  pub fn with_new_window_req_handler(
868    mut self,
869    callback: impl Fn(String) -> bool + 'static,
870  ) -> Self {
871    self.attrs.new_window_req_handler = Some(Box::new(callback));
872    self
873  }
874
875  /// Sets whether clicking an inactive window also clicks through to the webview. Default is `false`.
876  ///
877  /// ## Platform-specific
878  ///
879  /// This configuration only impacts macOS.
880  pub fn with_accept_first_mouse(mut self, accept_first_mouse: bool) -> Self {
881    self.attrs.accept_first_mouse = accept_first_mouse;
882    self
883  }
884
885  /// Set a handler closure to process the change of the webview's document title.
886  pub fn with_document_title_changed_handler(
887    mut self,
888    callback: impl Fn(String) + 'static,
889  ) -> Self {
890    self.attrs.document_title_changed_handler = Some(Box::new(callback));
891    self
892  }
893
894  /// Run the WebView with incognito mode. Note that WebContext will be ingored if incognito is
895  /// enabled.
896  ///
897  /// ## Platform-specific:
898  ///
899  /// - **Android:** Unsupported yet.
900  pub fn with_incognito(mut self, incognito: bool) -> Self {
901    self.attrs.incognito = incognito;
902    self
903  }
904
905  /// Set a handler to process page loading events.
906  pub fn with_on_page_load_handler(
907    mut self,
908    handler: impl Fn(PageLoadEvent, String) + 'static,
909  ) -> Self {
910    self.attrs.on_page_load_handler = Some(Box::new(handler));
911    self
912  }
913
914  /// Set a proxy configuration for the webview.
915  ///
916  /// - **macOS**: Requires macOS 14.0+ and the `mac-proxy` feature flag to be enabled. Supports HTTP CONNECT and SOCKSv5 proxies.
917  /// - **Windows / Linux**: Supports HTTP CONNECT and SOCKSv5 proxies.
918  /// - **Android / iOS:** Not supported.
919  pub fn with_proxy_config(mut self, configuration: ProxyConfig) -> Self {
920    self.attrs.proxy_config = Some(configuration);
921    self
922  }
923
924  /// Set whether the webview should be focused when created.
925  ///
926  /// ## Platform-specific:
927  ///
928  /// - **macOS / Android / iOS:** Unsupported.
929  pub fn with_focused(mut self, focused: bool) -> Self {
930    self.attrs.focused = focused;
931    self
932  }
933
934  /// Specify the webview position relative to its parent if it will be created as a child.
935  /// Defaults to `x: 0, y: 0, width: 200, height: 200`.
936  pub fn with_bounds(mut self, bounds: Rect) -> Self {
937    self.attrs.bounds.replace(bounds);
938    self
939  }
940
941  /// Consume the builder and create the [`WebView`].
942  ///
943  /// # Panics:
944  ///
945  /// - Panics if the provided handle was not support or invalid.
946  /// - Panics on Linux, if [`gtk::init`] was not called in this thread.
947  pub fn build(self) -> Result<WebView> {
948    let webview = if let Some(window) = &self.window {
949      if self.as_child {
950        InnerWebView::new_as_child(window, self.attrs, self.platform_specific, self.web_context)?
951      } else {
952        InnerWebView::new(window, self.attrs, self.platform_specific, self.web_context)?
953      }
954    } else {
955      #[cfg(gtk)]
956      if let Some(widget) = self.gtk_widget {
957        InnerWebView::new_gtk(widget, self.attrs, self.platform_specific, self.web_context)?
958      } else {
959        unreachable!()
960      }
961
962      #[cfg(not(gtk))]
963      unreachable!()
964    };
965
966    Ok(WebView { webview })
967  }
968}
969
970#[cfg(windows)]
971#[derive(Clone)]
972pub(crate) struct PlatformSpecificWebViewAttributes {
973  additional_browser_args: Option<String>,
974  browser_accelerator_keys: bool,
975  theme: Option<Theme>,
976  https_scheme: bool,
977}
978
979#[cfg(windows)]
980impl Default for PlatformSpecificWebViewAttributes {
981  fn default() -> Self {
982    Self {
983      additional_browser_args: None,
984      browser_accelerator_keys: true, // This is WebView2's default behavior
985      theme: None,
986      https_scheme: false, // To match macOS & Linux behavior in the context of mixed content.
987    }
988  }
989}
990
991#[cfg(windows)]
992pub trait WebViewBuilderExtWindows {
993  /// Pass additional args to WebView2 upon creating the webview.
994  ///
995  /// ## Warning
996  ///
997  /// By default wry passes `--disable-features=msWebOOUI,msPdfOOUI,msSmartScreenProtection`
998  /// `--autoplay-policy=no-user-gesture-required` if autoplay is enabled
999  /// and `--proxy-server=<scheme>://<host>:<port>` if a proxy is set.
1000  /// so if you use this method, you have to add these arguments yourself if you want to keep the same behavior.
1001  fn with_additional_browser_args<S: Into<String>>(self, additional_args: S) -> Self;
1002
1003  /// Determines whether browser-specific accelerator keys are enabled. When this setting is set to
1004  /// `false`, it disables all accelerator keys that access features specific to a web browser.
1005  /// The default value is `true`. See the following link to know more details.
1006  ///
1007  /// https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/winrt/microsoft_web_webview2_core/corewebview2settings#arebrowseracceleratorkeysenabled
1008  fn with_browser_accelerator_keys(self, enabled: bool) -> Self;
1009
1010  /// Specifies the theme of webview2. This affects things like `prefers-color-scheme`.
1011  ///
1012  /// Defaults to [`Theme::Auto`] which will follow the OS defaults.
1013  fn with_theme(self, theme: Theme) -> Self;
1014
1015  /// Determines whether the custom protocols should use `https://<scheme>.path/to/page` instead of the default `http://<scheme>.path/to/page`.
1016  ///
1017  /// Using a `http` scheme will allow mixed content when trying to fetch `http` endpoints
1018  /// and is therefore less secure but will match the behavior of the `<scheme>://path/to/page` protocols used on macOS and Linux.
1019  ///
1020  /// The default value is `false`.
1021  fn with_https_scheme(self, enabled: bool) -> Self;
1022}
1023
1024#[cfg(windows)]
1025impl WebViewBuilderExtWindows for WebViewBuilder<'_> {
1026  fn with_additional_browser_args<S: Into<String>>(mut self, additional_args: S) -> Self {
1027    self.platform_specific.additional_browser_args = Some(additional_args.into());
1028    self
1029  }
1030
1031  fn with_browser_accelerator_keys(mut self, enabled: bool) -> Self {
1032    self.platform_specific.browser_accelerator_keys = enabled;
1033    self
1034  }
1035
1036  fn with_theme(mut self, theme: Theme) -> Self {
1037    self.platform_specific.theme = Some(theme);
1038    self
1039  }
1040
1041  fn with_https_scheme(mut self, enabled: bool) -> Self {
1042    self.platform_specific.https_scheme = enabled;
1043    self
1044  }
1045}
1046
1047#[cfg(target_os = "android")]
1048#[derive(Default)]
1049pub(crate) struct PlatformSpecificWebViewAttributes {
1050  on_webview_created:
1051    Option<Box<dyn Fn(prelude::Context) -> std::result::Result<(), jni::errors::Error> + Send>>,
1052  with_asset_loader: bool,
1053  asset_loader_domain: Option<String>,
1054  https_scheme: bool,
1055}
1056
1057#[cfg(target_os = "android")]
1058pub trait WebViewBuilderExtAndroid {
1059  fn on_webview_created<
1060    F: Fn(prelude::Context<'_, '_>) -> std::result::Result<(), jni::errors::Error> + Send + 'static,
1061  >(
1062    self,
1063    f: F,
1064  ) -> Self;
1065
1066  /// Use [WebViewAssetLoader](https://developer.android.com/reference/kotlin/androidx/webkit/WebViewAssetLoader)
1067  /// to load assets from Android's `asset` folder when using `with_url` as `<protocol>://assets/` (e.g.:
1068  /// `wry://assets/index.html`). Note that this registers a custom protocol with the provided
1069  /// String, similar to [`with_custom_protocol`], but also sets the WebViewAssetLoader with the
1070  /// necessary domain (which is fixed as `<protocol>.assets`). This cannot be used in conjunction
1071  /// to `with_custom_protocol` for Android, as it changes the way in which requests are handled.
1072  #[cfg(feature = "protocol")]
1073  fn with_asset_loader(self, protocol: String) -> Self;
1074
1075  /// Determines whether the custom protocols should use `https://<scheme>.localhost` instead of the default `http://<scheme>.localhost`.
1076  ///
1077  /// Using a `http` scheme will allow mixed content when trying to fetch `http` endpoints
1078  /// and is therefore less secure but will match the behavior of the `<scheme>://localhost` protocols used on macOS and Linux.
1079  ///
1080  /// The default value is `false`.
1081  fn with_https_scheme(self, enabled: bool) -> Self;
1082}
1083
1084#[cfg(target_os = "android")]
1085impl WebViewBuilderExtAndroid for WebViewBuilder<'_> {
1086  fn on_webview_created<
1087    F: Fn(prelude::Context<'_, '_>) -> std::result::Result<(), jni::errors::Error> + Send + 'static,
1088  >(
1089    mut self,
1090    f: F,
1091  ) -> Self {
1092    self.platform_specific.on_webview_created = Some(Box::new(f));
1093    self
1094  }
1095
1096  #[cfg(feature = "protocol")]
1097  fn with_asset_loader(mut self, protocol: String) -> Self {
1098    // register custom protocol with empty Response return,
1099    // this is necessary due to the need of fixing a domain
1100    // in WebViewAssetLoader.
1101    self.attrs.custom_protocols.push((
1102      protocol.clone(),
1103      Box::new(|_, api| {
1104        api.respond(Response::builder().body(Vec::new()).unwrap());
1105      }),
1106    ));
1107    self.platform_specific.with_asset_loader = true;
1108    self.platform_specific.asset_loader_domain = Some(format!("{}.assets", protocol));
1109    self
1110  }
1111
1112  fn with_https_scheme(mut self, enabled: bool) -> Self {
1113    self.platform_specific.https_scheme = enabled;
1114    self
1115  }
1116}
1117
1118#[cfg(any(
1119  target_os = "linux",
1120  target_os = "dragonfly",
1121  target_os = "freebsd",
1122  target_os = "netbsd",
1123  target_os = "openbsd",
1124))]
1125pub trait WebViewBuilderExtUnix<'a> {
1126  /// Create the webview from a GTK container widget, such as GTK window.
1127  ///
1128  /// # Panics:
1129  ///
1130  /// - Panics if [`gtk::init`] was not called in this thread.
1131  fn new_gtk<W>(widget: &'a W) -> Self
1132  where
1133    W: gtk::prelude::IsA<gtk::Container>;
1134}
1135
1136#[cfg(any(
1137  target_os = "linux",
1138  target_os = "dragonfly",
1139  target_os = "freebsd",
1140  target_os = "netbsd",
1141  target_os = "openbsd",
1142))]
1143impl<'a> WebViewBuilderExtUnix<'a> for WebViewBuilder<'a> {
1144  fn new_gtk<W>(widget: &'a W) -> Self
1145  where
1146    W: gtk::prelude::IsA<gtk::Container>,
1147  {
1148    use gdkx11::glib::Cast;
1149
1150    Self {
1151      attrs: WebViewAttributes::default(),
1152      window: None,
1153      as_child: false,
1154      #[allow(clippy::default_constructed_unit_structs)]
1155      platform_specific: PlatformSpecificWebViewAttributes::default(),
1156      web_context: None,
1157      gtk_widget: Some(widget.dynamic_cast_ref().unwrap()),
1158    }
1159  }
1160}
1161
1162/// The fundamental type to present a [`WebView`].
1163///
1164/// [`WebViewBuilder`] / [`WebView`] are the basic building blocks to construct WebView contents and
1165/// scripts for those who prefer to control fine grained window creation and event handling.
1166/// [`WebView`] presents the actual WebView window and let you still able to perform actions on it.
1167pub struct WebView {
1168  webview: InnerWebView,
1169}
1170
1171impl WebView {
1172  /// Create a [`WebView`] from from a type that implements [`HasRawWindowHandle`].
1173  /// Note that calling this directly loses
1174  /// abilities to initialize scripts, add ipc handler, and many more before starting WebView. To
1175  /// benefit from above features, create a [`WebViewBuilder`] instead.
1176  ///
1177  /// # Platform-specific:
1178  ///
1179  /// - **Linux**: Only X11 is supported, if you want to support Wayland too, use [`WebViewExtUnix::new_gtk`].
1180  ///
1181  ///   Although this methods only needs an X11 window handle, you use webkit2gtk, so you still need to initialize gtk
1182  ///   by callling [`gtk::init`] and advance its loop alongside your event loop using [`gtk::main_iteration_do`].
1183  ///   Checkout the [Platform Considerations](https://docs.rs/wry/latest/wry/#platform-considerations) section in the crate root documentation.
1184  /// - **macOS / Windows**: The webview will auto-resize when the passed handle is resized.
1185  /// - **Linux (X11)**: Unlike macOS and Windows, the webview will not auto-resize and you'll need to call [`WebView::set_size`] manually.
1186  ///
1187  /// # Panics:
1188  ///
1189  /// - Panics if the provided handle was not supported or invalid.
1190  /// - Panics on Linux, if [`gtk::init`] was not called in this thread.
1191  pub fn new(window: &impl HasRawWindowHandle) -> Result<Self> {
1192    WebViewBuilder::new(window).build()
1193  }
1194
1195  /// Create [`WebViewBuilder`] as a child window inside the provided [`HasRawWindowHandle`].
1196  ///
1197  /// ## Platform-specific
1198  ///
1199  /// - **Windows**: This will create the webview as a child window of the `parent` window.
1200  /// - **macOS**: This will create the webview as a `NSView` subview of the `parent` window's
1201  /// content view.
1202  /// - **Linux**: This will create the webview as a child window of the `parent` window. Only X11
1203  /// is supported. This method won't work on Wayland.
1204  ///
1205  ///   Although this methods only needs an X11 window handle, you use webkit2gtk, so you still need to initialize gtk
1206  ///   by callling [`gtk::init`] and advance its loop alongside your event loop using [`gtk::main_iteration_do`].
1207  ///   Checkout the [Platform Considerations](https://docs.rs/wry/latest/wry/#platform-considerations) section in the crate root documentation.
1208  /// - **Android/iOS:** Unsupported.
1209  ///
1210  /// # Panics:
1211  ///
1212  /// - Panics if the provided handle was not support or invalid.
1213  /// - Panics on Linux, if [`gtk::init`] was not called in this thread.
1214  pub fn new_as_child(parent: &impl HasRawWindowHandle) -> Result<Self> {
1215    WebViewBuilder::new_as_child(parent).build()
1216  }
1217
1218  /// Get the current url of the webview
1219  pub fn url(&self) -> Url {
1220    self.webview.url()
1221  }
1222
1223  /// Evaluate and run javascript code.
1224  pub fn evaluate_script(&self, js: &str) -> Result<()> {
1225    self
1226      .webview
1227      .eval(js, None::<Box<dyn Fn(String) + Send + 'static>>)
1228  }
1229
1230  /// Evaluate and run javascript code with callback function. The evaluation result will be
1231  /// serialized into a JSON string and passed to the callback function.
1232  ///
1233  /// Exception is ignored because of the limitation on windows. You can catch it yourself and return as string as a workaround.
1234  ///
1235  /// - ** Android:** Not implemented yet.
1236  pub fn evaluate_script_with_callback(
1237    &self,
1238    js: &str,
1239    callback: impl Fn(String) + Send + 'static,
1240  ) -> Result<()> {
1241    self.webview.eval(js, Some(callback))
1242  }
1243
1244  /// Launch print modal for the webview content.
1245  pub fn print(&self) -> Result<()> {
1246    self.webview.print();
1247    Ok(())
1248  }
1249
1250  /// Open the web inspector which is usually called dev tool.
1251  ///
1252  /// ## Platform-specific
1253  ///
1254  /// - **Android / iOS:** Not supported.
1255  #[cfg(any(debug_assertions, feature = "devtools"))]
1256  pub fn open_devtools(&self) {
1257    self.webview.open_devtools();
1258  }
1259
1260  /// Close the web inspector which is usually called dev tool.
1261  ///
1262  /// ## Platform-specific
1263  ///
1264  /// - **Windows / Android / iOS:** Not supported.
1265  #[cfg(any(debug_assertions, feature = "devtools"))]
1266  pub fn close_devtools(&self) {
1267    self.webview.close_devtools();
1268  }
1269
1270  /// Gets the devtool window's current visibility state.
1271  ///
1272  /// ## Platform-specific
1273  ///
1274  /// - **Windows / Android / iOS:** Not supported.
1275  #[cfg(any(debug_assertions, feature = "devtools"))]
1276  pub fn is_devtools_open(&self) -> bool {
1277    self.webview.is_devtools_open()
1278  }
1279
1280  /// Set the webview zoom level
1281  ///
1282  /// ## Platform-specific:
1283  ///
1284  /// - **Android**: Not supported.
1285  /// - **macOS**: available on macOS 11+ only.
1286  /// - **iOS**: available on iOS 14+ only.
1287  pub fn zoom(&self, scale_factor: f64) {
1288    self.webview.zoom(scale_factor);
1289  }
1290
1291  /// Specify the webview background color.
1292  ///
1293  /// The color uses the RGBA format.
1294  ///
1295  /// ## Platfrom-specific:
1296  ///
1297  /// - **macOS / iOS**: Not implemented.
1298  /// - **Windows**:
1299  ///   - On Windows 7, transparency is not supported and the alpha value will be ignored.
1300  ///   - On Windows higher than 7: translucent colors are not supported so any alpha value other than `0` will be replaced by `255`
1301  pub fn set_background_color(&self, background_color: RGBA) -> Result<()> {
1302    self.webview.set_background_color(background_color)
1303  }
1304
1305  /// Navigate to the specified url
1306  pub fn load_url(&self, url: &str) {
1307    self.webview.load_url(url)
1308  }
1309
1310  /// Navigate to the specified url using the specified headers
1311  pub fn load_url_with_headers(&self, url: &str, headers: http::HeaderMap) {
1312    self.webview.load_url_with_headers(url, headers)
1313  }
1314
1315  /// Clear all browsing data
1316  pub fn clear_all_browsing_data(&self) -> Result<()> {
1317    self.webview.clear_all_browsing_data()
1318  }
1319
1320  pub fn bounds(&self) -> Rect {
1321    self.webview.bounds()
1322  }
1323
1324  /// Set the webview bounds.
1325  ///
1326  /// This is only effective if the webview was created as a child.
1327  pub fn set_bounds(&self, bounds: Rect) {
1328    self.webview.set_bounds(bounds)
1329  }
1330
1331  /// Shows or hides the webview.
1332  pub fn set_visible(&self, visible: bool) {
1333    self.webview.set_visible(visible)
1334  }
1335
1336  /// Try moving focus to the webview.
1337  pub fn focus(&self) {
1338    self.webview.focus()
1339  }
1340}
1341
1342/// An event describing the files drop on the webview.
1343#[non_exhaustive]
1344#[derive(Debug, serde::Serialize, Clone)]
1345pub enum FileDropEvent {
1346  /// The file(s) have been dragged onto the window, but have not been dropped yet.
1347  Hovered {
1348    paths: Vec<PathBuf>,
1349    /// The position of the mouse cursor.
1350    position: (i32, i32),
1351  },
1352  /// The file(s) have been dropped onto the window.
1353  Dropped {
1354    paths: Vec<PathBuf>,
1355    /// The position of the mouse cursor.
1356    position: (i32, i32),
1357  },
1358  /// The file drop was aborted.
1359  Cancelled,
1360}
1361
1362/// Get WebView/Webkit version on current platform.
1363pub fn webview_version() -> Result<String> {
1364  platform_webview_version()
1365}
1366
1367/// The [memory usage target level][1]. There are two levels 'Low' and 'Normal' and the default
1368/// level is 'Normal'. When the application is going inactive, setting the level to 'Low' can
1369/// significantly reduce the application's memory consumption.
1370///
1371/// [1]: https://learn.microsoft.com/en-us/dotnet/api/microsoft.web.webview2.core.corewebview2memoryusagetargetlevel
1372#[cfg(target_os = "windows")]
1373#[non_exhaustive]
1374#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash)]
1375pub enum MemoryUsageLevel {
1376  /// The 'Normal' memory usage. Applications should set this level when they are becoming active.
1377  #[default]
1378  Normal,
1379  /// The 'Low' memory usage. Applications can reduce memory comsumption by setting this level when
1380  /// they are becoming inactive.
1381  Low,
1382}
1383
1384/// Additional methods on `WebView` that are specific to Windows.
1385#[cfg(target_os = "windows")]
1386pub trait WebViewExtWindows {
1387  /// Returns WebView2 Controller
1388  fn controller(&self) -> ICoreWebView2Controller;
1389
1390  /// Changes the webview2 theme.
1391  fn set_theme(&self, theme: Theme);
1392
1393  /// Sets the [memory usage target level][1].
1394  ///
1395  /// When to best use this mode depends on the app in question. Most commonly it's called when
1396  /// the app's visiblity state changes.
1397  ///
1398  /// Please read the [guide for WebView2][2] for more details.
1399  ///
1400  /// This method uses a WebView2 API added in Runtime version 114.0.1823.32. When it is used in
1401  /// an older Runtime version, it does nothing.
1402  ///
1403  /// [1]: https://learn.microsoft.com/en-us/dotnet/api/microsoft.web.webview2.core.corewebview2memoryusagetargetlevel
1404  /// [2]: https://learn.microsoft.com/en-us/dotnet/api/microsoft.web.webview2.core.corewebview2.memoryusagetargetlevel?view=webview2-dotnet-1.0.2088.41#remarks
1405  fn set_memory_usage_level(&self, level: MemoryUsageLevel);
1406
1407  unsafe fn add_host_object_to_script<P0>(
1408    &self,
1409    name: P0,
1410    object: *mut ::windows::Win32::System::Variant::VARIANT,
1411  ) -> ::windows::core::Result<()>
1412  where
1413      P0: ::windows::core::IntoParam<::windows::core::PCWSTR>;
1414
1415  unsafe fn create_shared_buffer(
1416    &self,
1417    size: u64,
1418  ) -> ::windows::core::Result<webview2_com::Microsoft::Web::WebView2::Win32::ICoreWebView2SharedBuffer>;
1419
1420  unsafe fn post_shared_buffer_to_script<P0, P1>(
1421    &self,
1422    sharedbuffer: P0,
1423    access: webview2_com::Microsoft::Web::WebView2::Win32::COREWEBVIEW2_SHARED_BUFFER_ACCESS,
1424    additionaldataasjson: P1, 
1425  ) -> ::windows::core::Result<()>
1426  where
1427    P0: ::windows::core::IntoParam<webview2_com::Microsoft::Web::WebView2::Win32::ICoreWebView2SharedBuffer>,
1428    P1: ::windows::core::IntoParam<::windows::core::PCWSTR>;
1429}
1430
1431#[cfg(target_os = "windows")]
1432impl WebViewExtWindows for WebView {
1433  fn controller(&self) -> ICoreWebView2Controller {
1434    self.webview.controller.clone()
1435  }
1436
1437  fn set_theme(&self, theme: Theme) {
1438    self.webview.set_theme(theme)
1439  }
1440
1441  fn set_memory_usage_level(&self, level: MemoryUsageLevel) {
1442    self.webview.set_memory_usage_level(level);
1443  }
1444
1445  unsafe fn add_host_object_to_script<P0>(
1446    &self,
1447    name: P0,
1448    object: *mut ::windows::Win32::System::Variant::VARIANT,
1449  ) -> ::windows::core::Result<()> where P0: ::windows::core::IntoParam<::windows::core::PCWSTR>,
1450  {
1451    self.webview.add_host_object_to_script(name, object)
1452  }
1453
1454  unsafe fn create_shared_buffer(
1455    &self,
1456    size: u64,
1457  ) -> ::windows::core::Result<webview2_com::Microsoft::Web::WebView2::Win32::ICoreWebView2SharedBuffer> {
1458    self.webview.create_shared_buffer(size)
1459  }
1460
1461  unsafe fn post_shared_buffer_to_script<P0, P1>(
1462    &self,
1463    sharedbuffer: P0,
1464    access: webview2_com::Microsoft::Web::WebView2::Win32::COREWEBVIEW2_SHARED_BUFFER_ACCESS,
1465    additionaldataasjson: P1, 
1466  ) -> ::windows::core::Result<()>
1467  where
1468    P0: ::windows::core::IntoParam<webview2_com::Microsoft::Web::WebView2::Win32::ICoreWebView2SharedBuffer>,
1469    P1: ::windows::core::IntoParam<::windows::core::PCWSTR>,
1470  {
1471    self.webview.post_shared_buffer_to_script(sharedbuffer, access, additionaldataasjson)
1472  }
1473}
1474
1475/// Additional methods on `WebView` that are specific to Linux.
1476#[cfg(gtk)]
1477pub trait WebViewExtUnix: Sized {
1478  /// Create the webview from a GTK container widget, such as GTK window.
1479  ///
1480  /// # Panics:
1481  ///
1482  /// - Panics if [`gtk::init`] was not called in this thread.
1483  fn new_gtk<W>(widget: &W) -> Result<Self>
1484  where
1485    W: gtk::prelude::IsA<gtk::Container>;
1486
1487  /// Returns Webkit2gtk Webview handle
1488  fn webview(&self) -> webkit2gtk::WebView;
1489}
1490
1491#[cfg(gtk)]
1492impl WebViewExtUnix for WebView {
1493  fn new_gtk<W>(widget: &W) -> Result<Self>
1494  where
1495    W: gtk::prelude::IsA<gtk::Container>,
1496  {
1497    WebViewBuilder::new_gtk(widget).build()
1498  }
1499
1500  fn webview(&self) -> webkit2gtk::WebView {
1501    self.webview.webview.clone()
1502  }
1503}
1504
1505/// Additional methods on `WebView` that are specific to macOS.
1506#[cfg(target_os = "macos")]
1507pub trait WebViewExtMacOS {
1508  /// Returns WKWebView handle
1509  fn webview(&self) -> cocoa::base::id;
1510  /// Returns WKWebView manager [(userContentController)](https://developer.apple.com/documentation/webkit/wkscriptmessagehandler/1396222-usercontentcontroller) handle
1511  fn manager(&self) -> cocoa::base::id;
1512  /// Returns NSWindow associated with the WKWebView webview
1513  fn ns_window(&self) -> cocoa::base::id;
1514}
1515
1516#[cfg(target_os = "macos")]
1517impl WebViewExtMacOS for WebView {
1518  fn webview(&self) -> cocoa::base::id {
1519    self.webview.webview
1520  }
1521
1522  fn manager(&self) -> cocoa::base::id {
1523    self.webview.manager
1524  }
1525
1526  fn ns_window(&self) -> cocoa::base::id {
1527    self.webview.ns_window
1528  }
1529}
1530
1531/// Additional methods on `WebView` that are specific to iOS.
1532#[cfg(target_os = "ios")]
1533pub trait WebViewExtIOS {
1534  /// Returns WKWebView handle
1535  fn webview(&self) -> cocoa::base::id;
1536  /// Returns WKWebView manager [(userContentController)](https://developer.apple.com/documentation/webkit/wkscriptmessagehandler/1396222-usercontentcontroller) handle
1537  fn manager(&self) -> cocoa::base::id;
1538}
1539
1540#[cfg(target_os = "ios")]
1541impl WebViewExtIOS for WebView {
1542  fn webview(&self) -> cocoa::base::id {
1543    self.webview.webview
1544  }
1545
1546  fn manager(&self) -> cocoa::base::id {
1547    self.webview.manager
1548  }
1549}
1550
1551#[cfg(target_os = "android")]
1552/// Additional methods on `WebView` that are specific to Android
1553pub trait WebViewExtAndroid {
1554  fn handle(&self) -> JniHandle;
1555}
1556
1557#[cfg(target_os = "android")]
1558impl WebViewExtAndroid for WebView {
1559  fn handle(&self) -> JniHandle {
1560    JniHandle
1561  }
1562}
1563
1564/// WebView theme.
1565#[derive(Debug, Clone, Copy)]
1566pub enum Theme {
1567  /// Dark
1568  Dark,
1569  /// Light
1570  Light,
1571  /// System preference
1572  Auto,
1573}
1574
1575/// Type alias for a color in the RGBA format.
1576///
1577/// Each value can be 0..255 inclusive.
1578pub type RGBA = (u8, u8, u8, u8);
1579
1580/// Type of of page loading event
1581pub enum PageLoadEvent {
1582  /// Indicates that the content of the page has started loading
1583  Started,
1584  /// Indicates that the page content has finished loading
1585  Finished,
1586}
1587
1588#[cfg(any(
1589  target_os = "linux",
1590  target_os = "dragonfly",
1591  target_os = "freebsd",
1592  target_os = "netbsd",
1593  target_os = "openbsd",
1594  target_os = "ios",
1595  target_os = "macos",
1596))]
1597#[derive(Default)]
1598pub(crate) struct PlatformSpecificWebViewAttributes;
1599
1600#[cfg(test)]
1601mod tests {
1602  use super::*;
1603
1604  #[test]
1605  #[cfg_attr(miri, ignore)]
1606  fn should_get_webview_version() {
1607    if let Err(error) = webview_version() {
1608      panic!("{}", error);
1609    }
1610  }
1611}