Skip to content

Commit

Permalink
feat: Migrate to objc2 (#10924)
Browse files Browse the repository at this point in the history
* Migrate from objc/cocoa to objc2

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

---------

Co-authored-by: Lucas Fernandes Nogueira <lucas@tauri.app>
  • Loading branch information
madsmtm and lucasfernog authored Sep 15, 2024
1 parent 63264a2 commit bc4804d
Show file tree
Hide file tree
Showing 12 changed files with 114 additions and 79 deletions.
6 changes: 6 additions & 0 deletions .changes/change-type-of-macos-webview.md
Original file line number Diff line number Diff line change
@@ -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 changes: 6 additions & 0 deletions .changes/use-objc2.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"tauri": patch:enhance
"tauri-runtime-wry": patch:enhance
---

Use `objc2` internally and in examples, leading to better memory safety.
23 changes: 20 additions & 3 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 8 additions & 2 deletions crates/tauri-runtime-wry/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
20 changes: 10 additions & 10 deletions crates/tauri-runtime-wry/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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) => {
Expand Down Expand Up @@ -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")]
Expand All @@ -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)]
Expand Down Expand Up @@ -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());
}
Expand Down
24 changes: 8 additions & 16 deletions crates/tauri-runtime-wry/src/webview.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}
}

Expand Down
27 changes: 21 additions & 6 deletions crates/tauri/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand All @@ -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]
Expand All @@ -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"]
Expand Down
26 changes: 11 additions & 15 deletions crates/tauri/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 0 additions & 3 deletions crates/tauri/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
23 changes: 12 additions & 11 deletions crates/tauri/src/webview/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand All @@ -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
}

Expand All @@ -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
}

Expand All @@ -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
}

Expand Down Expand Up @@ -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() {
Expand All @@ -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")]
Expand Down
16 changes: 9 additions & 7 deletions crates/tauri/src/webview/webview_window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand All @@ -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")]
Expand All @@ -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>(
Expand Down
9 changes: 3 additions & 6 deletions crates/tauri/src/window/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down

0 comments on commit bc4804d

Please sign in to comment.