Browse Source

feat(core): expose `with_webview` API to access the platform webview (#4058)

Lucas Fernandes Nogueira 3 years ago
parent
commit
c82b4761e1

+ 7 - 0
.changes/webview-getters.md

@@ -0,0 +1,7 @@
+---
+"tauri": "patch"
+"tauri-runtime": patch
+"tauri-runtime-wry": patch
+---
+
+Expose methods to access the underlying native handles of the webview.

+ 8 - 4
core/tauri-runtime-wry/Cargo.toml

@@ -13,7 +13,7 @@ exclude = [ ".license_template", "CHANGELOG.md", "/target" ]
 readme = "README.md"
 
 [dependencies]
-wry = { version = "0.15", default-features = false, features = [ "file-drop", "protocol" ] }
+wry = { version = "0.16", default-features = false, features = [ "file-drop", "protocol" ] }
 tauri-runtime = { version = "0.4.0", path = "../tauri-runtime" }
 tauri-utils = { version = "1.0.0-rc.5", path = "../tauri-utils" }
 uuid = { version = "1", features = [ "v4" ] }
@@ -22,14 +22,18 @@ rand = "0.8"
 [target."cfg(windows)".dependencies]
 webview2-com = "0.13.0"
 
-  [target."cfg(windows)".dependencies.windows]
-  version = "0.30.0"
-  features = [ "Win32_Foundation" ]
+[target."cfg(windows)".dependencies.windows]
+version = "0.30.0"
+features = [ "Win32_Foundation" ]
 
 [target."cfg(any(target_os = \"linux\", target_os = \"dragonfly\", target_os = \"freebsd\", target_os = \"openbsd\", target_os = \"netbsd\"))".dependencies]
 gtk = { version = "0.15", features = [ "v3_20" ] }
+webkit2gtk = { version = "0.17", features = [ "v2_22" ] }
 percent-encoding = "2.1"
 
+[target."cfg(any(target_os = \"ios\", target_os = \"macos\"))".dependencies]
+cocoa = "0.24"
+
 [features]
 dox = [ "wry/dox" ]
 devtools = [ "wry/devtools", "tauri-runtime/devtools" ]

+ 45 - 2
core/tauri-runtime-wry/src/lib.rs

@@ -100,6 +100,9 @@ use std::{
 
 type WebviewId = u64;
 
+mod webview;
+pub use webview::Webview;
+
 #[cfg(feature = "system-tray")]
 mod system_tray;
 #[cfg(feature = "system-tray")]
@@ -1000,8 +1003,8 @@ pub struct GtkWindow(gtk::ApplicationWindow);
 #[allow(clippy::non_send_fields_in_send_ty)]
 unsafe impl Send for GtkWindow {}
 
-#[derive(Debug, Clone)]
 pub enum WindowMessage {
+  WithWebview(Box<dyn FnOnce(Webview) + Send>),
   // Devtools
   #[cfg(any(debug_assertions, feature = "devtools"))]
   OpenDevTools,
@@ -1121,7 +1124,6 @@ pub enum Message<T: 'static> {
 impl<T: UserEvent> Clone for Message<T> {
   fn clone(&self) -> Self {
     match self {
-      Self::Window(i, m) => Self::Window(*i, m.clone()),
       Self::Webview(i, m) => Self::Webview(*i, m.clone()),
       #[cfg(feature = "system-tray")]
       Self::Tray(m) => Self::Tray(m.clone()),
@@ -1146,6 +1148,15 @@ pub struct WryDispatcher<T: UserEvent> {
 #[allow(clippy::non_send_fields_in_send_ty)]
 unsafe impl<T: UserEvent> Sync for WryDispatcher<T> {}
 
+impl<T: UserEvent> WryDispatcher<T> {
+  pub fn with_webview<F: FnOnce(Webview) + Send + 'static>(&self, f: F) -> Result<()> {
+    send_user_message(
+      &self.context,
+      Message::Window(self.window_id, WindowMessage::WithWebview(Box::new(f))),
+    )
+  }
+}
+
 impl<T: UserEvent> Dispatch<T> for WryDispatcher<T> {
   type Runtime = Wry<T>;
   type WindowBuilder = WindowBuilderWrapper;
@@ -2138,6 +2149,38 @@ fn handle_user_message<T: UserEvent>(
       {
         let window = window_handle.window();
         match window_message {
+          WindowMessage::WithWebview(f) => {
+            if let WindowHandle::Webview(w) = window_handle {
+              #[cfg(any(
+                target_os = "linux",
+                target_os = "dragonfly",
+                target_os = "freebsd",
+                target_os = "netbsd",
+                target_os = "openbsd"
+              ))]
+              {
+                use wry::webview::WebviewExtUnix;
+                f(w.webview());
+              }
+              #[cfg(target_os = "macos")]
+              {
+                use wry::webview::WebviewExtMacOS;
+                f(Webview {
+                  webview: w.webview(),
+                  manager: w.manager(),
+                  ns_window: w.ns_window(),
+                });
+              }
+
+              #[cfg(windows)]
+              {
+                f(Webview {
+                  controller: w.controller(),
+                });
+              }
+            }
+          }
+
           #[cfg(any(debug_assertions, feature = "devtools"))]
           WindowMessage::OpenDevTools => {
             if let WindowHandle::Webview(w) = &window_handle {

+ 37 - 0
core/tauri-runtime-wry/src/webview.rs

@@ -0,0 +1,37 @@
+// Copyright 2019-2021 Tauri Programme within The Commons Conservancy
+// SPDX-License-Identifier: Apache-2.0
+// SPDX-License-Identifier: MIT
+
+#[cfg(any(
+  target_os = "linux",
+  target_os = "dragonfly",
+  target_os = "freebsd",
+  target_os = "netbsd",
+  target_os = "openbsd"
+))]
+mod imp {
+  use std::rc::Rc;
+
+  pub type Webview = Rc<webkit2gtk::WebView>;
+}
+
+#[cfg(target_os = "macos")]
+mod imp {
+  use cocoa::base::id;
+
+  pub struct Webview {
+    pub webview: id,
+    pub manager: id,
+    pub ns_window: id,
+  }
+}
+
+#[cfg(windows)]
+mod imp {
+  use webview2_com::Microsoft::Web::WebView2::Win32::ICoreWebView2Controller;
+  pub struct Webview {
+    pub controller: ICoreWebView2Controller,
+  }
+}
+
+pub use imp::*;

+ 3 - 3
core/tauri-runtime/src/lib.rs

@@ -473,9 +473,6 @@ pub trait Dispatch<T: UserEvent>: Debug + Clone + Send + Sync + Sized + 'static
   #[cfg(windows)]
   fn hwnd(&self) -> Result<HWND>;
 
-  /// Returns the current window theme.
-  fn theme(&self) -> Result<Theme>;
-
   /// Returns the native handle that is used by this window.
   #[cfg(target_os = "macos")]
   fn ns_window(&self) -> Result<*mut std::ffi::c_void>;
@@ -490,6 +487,9 @@ pub trait Dispatch<T: UserEvent>: Debug + Clone + Send + Sync + Sized + 'static
   ))]
   fn gtk_window(&self) -> Result<gtk::ApplicationWindow>;
 
+  /// Returns the current window theme.
+  fn theme(&self) -> Result<Theme>;
+
   // SETTERS
 
   /// Centers the window.

+ 6 - 0
core/tauri/Cargo.toml

@@ -97,9 +97,15 @@ ico = { version = "0.1", optional = true }
 [target."cfg(any(target_os = \"linux\", target_os = \"dragonfly\", target_os = \"freebsd\", target_os = \"openbsd\", target_os = \"netbsd\"))".dependencies]
 gtk = { version = "0.15", features = [ "v3_20" ] }
 glib = "0.15"
+webkit2gtk = { version = "0.17", features = [ "v2_22" ] }
 
 [target."cfg(target_os = \"macos\")".dependencies]
 embed_plist = "1.2"
+cocoa = "0.24"
+objc = "0.2"
+
+[target."cfg(windows)".dependencies]
+webview2-com = "0.13.0"
 
 [target."cfg(windows)".dependencies.windows]
 version = "0.30.0"

+ 133 - 9
core/tauri/src/window.rs

@@ -543,6 +543,130 @@ impl<'de, R: Runtime> CommandArg<'de, R> for Window<R> {
   }
 }
 
+/// The platform webview handle. Accessed with [`Window#method.with_webview`];
+#[cfg(feature = "wry")]
+#[cfg_attr(doc_cfg, doc(cfg(feature = "wry")))]
+pub struct PlatformWebview(tauri_runtime_wry::Webview);
+
+#[cfg(feature = "wry")]
+impl PlatformWebview {
+  /// Returns [`webkit2gtk::WebView`] handle.
+  #[cfg(any(
+    target_os = "linux",
+    target_os = "dragonfly",
+    target_os = "freebsd",
+    target_os = "netbsd",
+    target_os = "openbsd"
+  ))]
+  #[cfg_attr(
+    doc_cfg,
+    doc(cfg(any(
+      target_os = "linux",
+      target_os = "dragonfly",
+      target_os = "freebsd",
+      target_os = "netbsd",
+      target_os = "openbsd"
+    )))
+  )]
+  pub fn inner(&self) -> std::rc::Rc<webkit2gtk::WebView> {
+    self.0.clone()
+  }
+
+  /// Returns the WebView2 controller.
+  #[cfg(windows)]
+  #[cfg_attr(doc_cfg, doc(cfg(windows)))]
+  pub fn controller(
+    &self,
+  ) -> webview2_com::Microsoft::Web::WebView2::Win32::ICoreWebView2Controller {
+    self.0.controller.clone()
+  }
+
+  /// Returns the [WKWebView] handle.
+  ///
+  /// [WKWebView]: https://developer.apple.com/documentation/webkit/wkwebview
+  #[cfg(target_os = "macos")]
+  #[cfg_attr(doc_cfg, doc(cfg(target_os = "macos")))]
+  pub fn inner(&self) -> cocoa::base::id {
+    self.0.webview.clone()
+  }
+
+  /// Returns WKWebView [controller] handle.
+  ///
+  /// [controller]: https://developer.apple.com/documentation/webkit/wkusercontentcontroller
+  #[cfg(target_os = "macos")]
+  #[cfg_attr(doc_cfg, doc(cfg(target_os = "macos")))]
+  pub fn controller(&self) -> cocoa::base::id {
+    self.0.manager.clone()
+  }
+
+  /// Returns [NSWindow] associated with the WKWebView webview.
+  ///
+  /// [NSWindow]: https://developer.apple.com/documentation/appkit/nswindow
+  #[cfg(target_os = "macos")]
+  #[cfg_attr(doc_cfg, doc(cfg(target_os = "macos")))]
+  pub fn ns_window(&self) -> cocoa::base::id {
+    self.0.ns_window.clone()
+  }
+}
+
+#[cfg(feature = "wry")]
+impl Window<crate::Wry> {
+  /// Executes the closure accessing the platform's webview handle.
+  ///
+  /// The closure is executed in the main thread.
+  ///
+  /// # Examples
+  ///
+  /// ```rust,no_run
+  /// #[cfg(target_os = "macos")]
+  /// #[macro_use]
+  /// extern crate objc;
+  /// use tauri::Manager;
+  ///
+  /// fn main() {
+  ///   tauri::Builder::default()
+  ///     .setup(|app| {
+  ///       let main_window = app.get_window("main").unwrap();
+  ///       main_window.with_webview(|webview| {
+  ///         #[cfg(target_os = "linux")]
+  ///         {
+  ///           // see https://docs.rs/webkit2gtk/latest/webkit2gtk/struct.WebView.html
+  ///           // and https://docs.rs/webkit2gtk/latest/webkit2gtk/trait.WebViewExt.html
+  ///           use webkit2gtk::traits::WebViewExt;
+  ///           webview.inner().set_zoom_level(4.);
+  ///         }
+  ///   
+  ///         #[cfg(windows)]
+  ///         unsafe {
+  ///           // see https://docs.rs/webview2-com/latest/webview2_com/Microsoft/Web/WebView2/Win32/struct.ICoreWebView2Controller.html
+  ///           webview.controller().SetZoomFactor(4.).unwrap();
+  ///         }
+  ///
+  ///         #[cfg(target_os = "macos")]
+  ///         unsafe {
+  ///           let () = msg_send![webview.inner(), setPageZoom: 4.];
+  ///           let () = msg_send![webview.controller(), removeAllUserScripts];
+  ///           let bg_color: cocoa::base::id = msg_send![class!(NSColor), colorWithDeviceRed:0.5 green:0.2 blue:0.4 alpha:1.];
+  ///           let () = msg_send![webview.ns_window(), setBackgroundColor: bg_color];
+  ///         }
+  ///       });
+  ///       Ok(())
+  ///   });
+  /// }
+  /// ```
+  #[cfg_attr(doc_cfg, doc(cfg(eature = "wry")))]
+  pub fn with_webview<F: FnOnce(PlatformWebview) + Send + 'static>(
+    &self,
+    f: F,
+  ) -> crate::Result<()> {
+    self
+      .window
+      .dispatcher
+      .with_webview(|w| f(PlatformWebview(w)))
+      .map_err(Into::into)
+  }
+}
+
 impl<R: Runtime> Window<R> {
   /// Create a new window that is attached to the manager.
   pub(crate) fn new(
@@ -976,15 +1100,6 @@ impl<R: Runtime> Window<R> {
     self.window.dispatcher.hwnd().map_err(Into::into)
   }
 
-  /// Returns the current window theme.
-  ///
-  /// ## Platform-specific
-  ///
-  /// - **macOS / Linux**: Not implemented, always return [`Theme::Light`].
-  pub fn theme(&self) -> crate::Result<Theme> {
-    self.window.dispatcher.theme().map_err(Into::into)
-  }
-
   /// Returns the `ApplicatonWindow` from gtk crate that is used by this window.
   ///
   /// Note that this can only be used on the main thread.
@@ -999,6 +1114,15 @@ impl<R: Runtime> Window<R> {
     self.window.dispatcher.gtk_window().map_err(Into::into)
   }
 
+  /// Returns the current window theme.
+  ///
+  /// ## Platform-specific
+  ///
+  /// - **macOS / Linux**: Not implemented, always return [`Theme::Light`].
+  pub fn theme(&self) -> crate::Result<Theme> {
+    self.window.dispatcher.theme().map_err(Into::into)
+  }
+
   // Setters
 
   /// Centers the window.