Forráskód Böngészése

feat: Migrate to `objc2` (#10924)

* Migrate from objc/cocoa to objc2

* Update crates/tauri-runtime-wry/src/webview.rs

---------

Co-authored-by: Lucas Fernandes Nogueira <lucas@tauri.app>
Mads Marquart 10 hónapja
szülő
commit
bc4804d484

+ 6 - 0
.changes/change-type-of-macos-webview.md

@@ -0,0 +1,6 @@
+---
+"tauri": minor:breaking
+"tauri-runtime-wry": minor:breaking
+---
+
+Change the pointer type of `PlatformWebview`'s `inner`, `controller`, `ns_window` and `view_controller` to `c_void`, to avoid publically depending on `objc`.

+ 6 - 0
.changes/use-objc2.md

@@ -0,0 +1,6 @@
+---
+"tauri": patch:enhance
+"tauri-runtime-wry": patch:enhance
+---
+
+Use `objc2` internally and in examples, leading to better memory safety.

+ 20 - 3
Cargo.lock

@@ -4576,6 +4576,19 @@ dependencies = [
  "objc2-metal",
 ]
 
+[[package]]
+name = "objc2-web-kit"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "68bc69301064cebefc6c4c90ce9cba69225239e4b8ff99d445a2b5563797da65"
+dependencies = [
+ "bitflags 2.6.0",
+ "block2",
+ "objc2",
+ "objc2-app-kit",
+ "objc2-foundation",
+]
+
 [[package]]
 name = "objc_exception"
 version = "0.1.2"
@@ -7276,7 +7289,6 @@ dependencies = [
  "anyhow",
  "bytes",
  "cargo_toml",
- "cocoa 0.26.0",
  "data-url",
  "dirs",
  "dunce",
@@ -7294,7 +7306,10 @@ dependencies = [
  "log",
  "mime",
  "muda",
- "objc",
+ "objc2",
+ "objc2-app-kit",
+ "objc2-foundation",
+ "objc2-web-kit",
  "percent-encoding",
  "plist",
  "proptest",
@@ -7661,11 +7676,13 @@ dependencies = [
 name = "tauri-runtime-wry"
 version = "2.0.0-rc.11"
 dependencies = [
- "cocoa 0.26.0",
  "gtk",
  "http 1.1.0",
  "jni",
  "log",
+ "objc2",
+ "objc2-app-kit",
+ "objc2-foundation",
  "percent-encoding",
  "raw-window-handle",
  "softbuffer",

+ 8 - 2
crates/tauri-runtime-wry/Cargo.toml

@@ -45,8 +45,14 @@ gtk = { version = "0.18", features = ["v3_24"] }
 webkit2gtk = { version = "=2.0", features = ["v2_40"] }
 percent-encoding = "2.1"
 
-[target."cfg(any(target_os = \"ios\", target_os = \"macos\"))".dependencies]
-cocoa = "0.26"
+[target.'cfg(target_os = "macos")'.dependencies]
+objc2 = "0.5.2"
+objc2-foundation = { version = "0.2.2", features = [] }
+objc2-app-kit = { version = "0.2.2", features = [
+  "NSResponder",
+  "NSView",
+  "NSWindow",
+] }
 
 [target."cfg(target_os = \"android\")".dependencies]
 jni = "0.21"

+ 10 - 10
crates/tauri-runtime-wry/src/lib.rs

@@ -2820,9 +2820,8 @@ fn handle_user_message<T: UserEvent>(
 
             #[cfg(target_os = "macos")]
             {
-              use cocoa::{appkit::NSWindow, base::id};
-              let ns_window: id = window.ns_window() as _;
-              unsafe { ns_window.center() };
+              let ns_window: &objc2_app_kit::NSWindow = unsafe { &*window.ns_window().cast() };
+              ns_window.center();
             }
           }
           WindowMessage::RequestUserAttention(request_type) => {
@@ -3239,9 +3238,9 @@ fn handle_user_message<T: UserEvent>(
             {
               use wry::WebViewExtMacOS;
               f(Webview {
-                webview: webview.webview(),
-                manager: webview.manager(),
-                ns_window: webview.ns_window(),
+                webview: webview.webview().cast(),
+                manager: webview.manager().cast(),
+                ns_window: webview.ns_window().cast(),
               });
             }
             #[cfg(target_os = "ios")]
@@ -3250,9 +3249,9 @@ fn handle_user_message<T: UserEvent>(
               use wry::WebViewExtIOS;
 
               f(Webview {
-                webview: webview.inner.webview(),
-                manager: webview.inner.manager(),
-                view_controller: window.ui_view_controller() as cocoa::base::id,
+                webview: webview.inner.webview().cast(),
+                manager: webview.inner.manager().cast(),
+                view_controller: window.ui_view_controller().cast(),
               });
             }
             #[cfg(windows)]
@@ -4259,7 +4258,8 @@ fn inner_size(
   if !has_children && !webviews.is_empty() {
     use wry::WebViewExtMacOS;
     let webview = webviews.first().unwrap();
-    let view_frame = unsafe { cocoa::appkit::NSView::frame(webview.webview()) };
+    let view: &objc2_app_kit::NSView = unsafe { &*webview.webview().cast() };
+    let view_frame = view.frame();
     let logical: TaoLogicalSize<f64> = (view_frame.size.width, view_frame.size.height).into();
     return logical.to_physical(window.scale_factor());
   }

+ 8 - 16
crates/tauri-runtime-wry/src/webview.rs

@@ -13,25 +13,17 @@ mod imp {
   pub type Webview = webkit2gtk::WebView;
 }
 
-#[cfg(target_os = "macos")]
+#[cfg(target_vendor = "apple")]
 mod imp {
-  use cocoa::base::id;
+  use std::ffi::c_void;
 
   pub struct Webview {
-    pub webview: id,
-    pub manager: id,
-    pub ns_window: id,
-  }
-}
-
-#[cfg(target_os = "ios")]
-mod imp {
-  use cocoa::base::id;
-
-  pub struct Webview {
-    pub webview: id,
-    pub manager: id,
-    pub view_controller: id,
+    pub webview: *mut c_void,
+    pub manager: *mut c_void,
+    #[cfg(target_os = "macos")]
+    pub ns_window: *mut c_void,
+    #[cfg(target_os = "ios")]
+    pub view_controller: *mut c_void,
   }
 }
 

+ 21 - 6
crates/tauri/Cargo.toml

@@ -101,11 +101,19 @@ tray-icon = { version = "0.17", default-features = false, features = [
 gtk = { version = "0.18", features = ["v3_24"] }
 webkit2gtk = { version = "=2.0.1", features = ["v2_40"] }
 
-[target."cfg(target_os = \"macos\")".dependencies]
+# macOS
+[target.'cfg(target_os = "macos")'.dependencies]
 embed_plist = "1.2"
 plist = "1"
-cocoa = "0.26"
-objc = "0.2"
+objc2 = "0.5.2"
+objc2-foundation = { version = "0.2.2", features = ["NSData", "NSThread"] }
+objc2-app-kit = { version = "0.2.2", features = [
+  "NSApplication",
+  "NSColor",
+  "NSResponder",
+  "NSView",
+  "NSWindow",
+] }
 window-vibrancy = "0.5"
 
 [target."cfg(windows)".dependencies]
@@ -119,10 +127,9 @@ features = ["Win32_Foundation"]
 [target."cfg(target_os = \"android\")".dependencies]
 jni = "0.21"
 
-[target."cfg(target_os = \"ios\")".dependencies]
+# UIKit, i.e. iOS/tvOS/watchOS/visionOS
+[target.'cfg(all(target_vendor = "apple", not(target_os = "macos")))'.dependencies]
 libc = "0.2"
-objc = "0.2"
-cocoa = "0.26"
 swift-rs = "1.0.7"
 
 [build-dependencies]
@@ -143,6 +150,14 @@ tokio = { version = "1", features = ["full"] }
 cargo_toml = "0.17"
 http-range = "0.1.5"
 
+# macOS
+[target.'cfg(target_os = "macos")'.dev-dependencies]
+objc2-web-kit = { version = "0.2.2", features = [
+  "objc2-app-kit",
+  "WKWebView",
+  "WKUserContentController",
+] }
+
 [features]
 default = ["wry", "compression", "objc-exception", "common-controls-v6"]
 unstable = ["tauri-runtime-wry/unstable"]

+ 11 - 15
crates/tauri/src/app.rs

@@ -2053,22 +2053,18 @@ fn on_event_loop_event<R: Runtime>(
     RuntimeRunEvent::Ready => {
       // set the app icon in development
       #[cfg(all(dev, target_os = "macos"))]
-      unsafe {
-        use cocoa::{
-          appkit::NSImage,
-          base::{id, nil},
-          foundation::NSData,
-        };
-        use objc::*;
+      {
+        use objc2::ClassType;
+        use objc2_app_kit::{NSApplication, NSImage};
+        use objc2_foundation::{MainThreadMarker, NSData};
+
         if let Some(icon) = app_handle.manager.app_icon.clone() {
-          let ns_app: id = msg_send![class!(NSApplication), sharedApplication];
-          let data = NSData::dataWithBytes_length_(
-            nil,
-            icon.as_ptr() as *const std::os::raw::c_void,
-            icon.len() as u64,
-          );
-          let app_icon = NSImage::initWithData_(NSImage::alloc(nil), data);
-          let _: () = msg_send![ns_app, setApplicationIconImage: app_icon];
+          // TODO: Enable this check.
+          let mtm = unsafe { MainThreadMarker::new_unchecked() };
+          let app = NSApplication::sharedApplication(mtm);
+          let data = NSData::with_bytes(&icon);
+          let app_icon = NSImage::initWithData(NSImage::alloc(), &data).expect("creating icon");
+          unsafe { app.setApplicationIconImage(Some(&app_icon)) };
         }
       }
       RunEvent::Ready

+ 0 - 3
crates/tauri/src/lib.rs

@@ -62,9 +62,6 @@ macro_rules! ios_plugin_binding {
     tauri::swift_rs::swift!(fn $fn_name() -> *const ::std::ffi::c_void);
   }
 }
-#[cfg(target_os = "ios")]
-#[doc(hidden)]
-pub use cocoa;
 #[cfg(target_os = "macos")]
 #[doc(hidden)]
 pub use embed_plist;

+ 12 - 11
crates/tauri/src/webview/mod.rs

@@ -179,7 +179,7 @@ impl PlatformWebview {
   /// [WKWebView]: https://developer.apple.com/documentation/webkit/wkwebview
   #[cfg(any(target_os = "macos", target_os = "ios"))]
   #[cfg_attr(docsrs, doc(cfg(any(target_os = "macos", target_os = "ios"))))]
-  pub fn inner(&self) -> cocoa::base::id {
+  pub fn inner(&self) -> *mut std::ffi::c_void {
     self.0.webview
   }
 
@@ -188,7 +188,7 @@ impl PlatformWebview {
   /// [controller]: https://developer.apple.com/documentation/webkit/wkusercontentcontroller
   #[cfg(any(target_os = "macos", target_os = "ios"))]
   #[cfg_attr(docsrs, doc(cfg(any(target_os = "macos", target_os = "ios"))))]
-  pub fn controller(&self) -> cocoa::base::id {
+  pub fn controller(&self) -> *mut std::ffi::c_void {
     self.0.manager
   }
 
@@ -197,7 +197,7 @@ impl PlatformWebview {
   /// [NSWindow]: https://developer.apple.com/documentation/appkit/nswindow
   #[cfg(target_os = "macos")]
   #[cfg_attr(docsrs, doc(cfg(target_os = "macos")))]
-  pub fn ns_window(&self) -> cocoa::base::id {
+  pub fn ns_window(&self) -> *mut std::ffi::c_void {
     self.0.ns_window
   }
 
@@ -206,7 +206,7 @@ impl PlatformWebview {
   /// [UIViewController]: https://developer.apple.com/documentation/uikit/uiviewcontroller
   #[cfg(target_os = "ios")]
   #[cfg_attr(docsrs, doc(cfg(target_os = "ios")))]
-  pub fn view_controller(&self) -> cocoa::base::id {
+  pub fn view_controller(&self) -> *mut std::ffi::c_void {
     self.0.view_controller
   }
 
@@ -1000,9 +1000,6 @@ impl<R: Runtime> Webview<R> {
     feature = "unstable",
     doc = r####"
 ```rust,no_run
-#[cfg(target_os = "macos")]
-#[macro_use]
-extern crate objc;
 use tauri::Manager;
 
 fn main() {
@@ -1026,10 +1023,14 @@ fn main() {
 
         #[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];
+          let view: &objc2_web_kit::WKWebView = &*webview.inner().cast();
+          let controller: &objc2_web_kit::WKUserContentController = &*webview.controller().cast();
+          let window: &objc2_app_kit::NSWindow = &*webview.ns_window().cast();
+
+          view.setPageZoom(4.);
+          controller.removeAllUserScripts();
+          let bg_color = objc2_app_kit::NSColor::colorWithDeviceRed_green_blue_alpha(0.5, 0.2, 0.4, 1.);
+          window.setBackgroundColor(Some(&bg_color));
         }
 
         #[cfg(target_os = "android")]

+ 9 - 7
crates/tauri/src/webview/webview_window.rs

@@ -1592,9 +1592,6 @@ impl<R: Runtime> WebviewWindow<R> {
   /// # Examples
   ///
   /// ```rust,no_run
-  /// #[cfg(target_os = "macos")]
-  /// #[macro_use]
-  /// extern crate objc;
   /// use tauri::Manager;
   ///
   /// fn main() {
@@ -1618,10 +1615,14 @@ impl<R: Runtime> WebviewWindow<R> {
   ///
   ///         #[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];
+  ///           let view: &objc2_web_kit::WKWebView = &*webview.inner().cast();
+  ///           let controller: &objc2_web_kit::WKUserContentController = &*webview.controller().cast();
+  ///           let window: &objc2_app_kit::NSWindow = &*webview.ns_window().cast();
+  ///
+  ///           view.setPageZoom(4.);
+  ///           controller.removeAllUserScripts();
+  ///           let bg_color = objc2_app_kit::NSColor::colorWithDeviceRed_green_blue_alpha(0.5, 0.2, 0.4, 1.);
+  ///           window.setBackgroundColor(Some(&bg_color));
   ///         }
   ///
   ///         #[cfg(target_os = "android")]
@@ -1636,6 +1637,7 @@ impl<R: Runtime> WebviewWindow<R> {
   ///   });
   /// }
   /// ```
+  #[allow(clippy::needless_doctest_main)] // To avoid a large diff
   #[cfg(feature = "wry")]
   #[cfg_attr(docsrs, doc(feature = "wry"))]
   pub fn with_webview<F: FnOnce(crate::webview::PlatformWebview) + Send + 'static>(

+ 3 - 6
crates/tauri/src/window/mod.rs

@@ -1461,12 +1461,9 @@ impl<R: Runtime> Window<R> {
       .map_err(Into::into)
       .and_then(|handle| {
         if let raw_window_handle::RawWindowHandle::AppKit(h) = handle.as_raw() {
-          Ok(unsafe {
-            use objc::*;
-            let ns_window: cocoa::base::id =
-              objc::msg_send![h.ns_view.as_ptr() as cocoa::base::id, window];
-            ns_window as *mut _
-          })
+          let view: &objc2_app_kit::NSView = unsafe { h.ns_view.cast().as_ref() };
+          let ns_window = view.window().expect("view to be installed in window");
+          Ok(objc2::rc::Retained::autorelease_ptr(ns_window).cast())
         } else {
           Err(crate::Error::InvalidWindowHandle)
         }