Skip to content

Commit 0abc221

Browse files
Jason TsaiamrbashirFabianLarsthewh1teaglegithub-actions[bot]
authoredOct 11, 2024··
refactor(macos): migrate to objc2 (#1316)
* migrate drag & drop * refactor: migrate to `dpi` crate (#1202) * refactor: migrate to `dpi` crate closes #1172 * macOS * linux * fix doctests * imports * more doctests * fix android and ios * Update examples/winit.rs Co-authored-by: Jason Tsai <jason@pews.dev> * Update src/webview2/mod.rs --------- Co-authored-by: Jason Tsai <jason@pews.dev> * fix(windows): avoid double-free the controller (#1206) * fix(linux): Disable deprecated applicationCache web api. (#1207) fixes tauri-apps/tauri#9300 ref WebKit/WebKit#23382 * fix(wkwebview): menu shortcuts (#1208) * fix(wkwebview): menu shortcuts * Update wkwebview.md * Apply Version Updates From Current Changes (#1203) Co-authored-by: amrbashir <amrbashir@users.noreply.github.com> * migrate to `objc2` * fix(macos): response body being double freed * fix(macos): eval callback NSStrgin convertion error * chore: remove objc dependency * refactor(macos): migrate WebViewDelegate * refactor(macos): migrate proxy to objc2 * refactor(macos): migrate document title change observer to objc2 * refactor(macos): move drag&drop handler to delegate * refactor(macos): move ipc_handler into WryWebViewDelegate * refactor(macos): migrate download handler * fix(macos): prevent unsafe async custom protocol panic * chore: target os import * refactor(ios): migrate to objc2 * refactor(macos): migrate WebViewUIDelegate to objc2 * refactor(macos): migrate WryWebViewParent to objc2 * refactor(macos): move custom class to individual files * chore: fix clippy * refector: use reference for task. use objc2::exception::catch. * fix(dnd): use msg_send super and impl NSDraggingDestination * chore: call msg_send super * fix: wrap Box<dyn FnMut(..)> with RefCell * chore(deps): update rust crate tao to 0.29 (#1343) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * refactor: use bitflags way to handle mask bit manipulation * WIP: refactor(ios): add wkwebview for ios * Update Cargo.toml Co-authored-by: Mads Marquart <mads@marquart.dk> * fix: remove `.copy()` from RcBlock * add change file * lint * fmt --------- Co-authored-by: Amr Bashir <amr.bashir2015@gmail.com> Co-authored-by: Fabian-Lars <fabianlars@fabianlars.de> Co-authored-by: thewh1teagle <61390950+thewh1teagle@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: amrbashir <amrbashir@users.noreply.github.com> Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Mads Marquart <mads@marquart.dk> Co-authored-by: Lucas Nogueira <lucas@tauri.app>
1 parent 8cc2a7f commit 0abc221

23 files changed

+2452
-1412
lines changed
 

‎.changes/objc2.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"wry": minor
3+
---
4+
5+
Migrate to obj2.

‎Cargo.toml

+95-29
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
workspace = { }
1+
workspace = {}
22

33
[package]
44
name = "wry"
@@ -10,53 +10,55 @@ description = "Cross-platform WebView rendering library"
1010
readme = "README.md"
1111
repository = "https://github.com/tauri-apps/wry"
1212
documentation = "https://docs.rs/wry"
13-
categories = [ "gui" ]
14-
exclude = [ "/.changes", "/.github", "/audits", "/wry-logo.svg" ]
13+
categories = ["gui"]
14+
exclude = ["/.changes", "/.github", "/audits", "/wry-logo.svg"]
1515

1616
[package.metadata.docs.rs]
1717
no-default-features = true
18-
features = [ "drag-drop", "protocol", "os-webview" ]
18+
features = ["drag-drop", "protocol", "os-webview"]
1919
targets = [
2020
"x86_64-unknown-linux-gnu",
2121
"x86_64-pc-windows-msvc",
22-
"x86_64-apple-darwin"
22+
"x86_64-apple-darwin",
2323
]
24-
rustc-args = [ "--cfg", "docsrs" ]
25-
rustdoc-args = [ "--cfg", "docsrs" ]
24+
rustc-args = ["--cfg", "docsrs"]
25+
rustdoc-args = ["--cfg", "docsrs"]
2626

2727
[features]
28-
default = [ "drag-drop", "objc-exception", "protocol", "os-webview" ]
29-
serde = [ "dpi/serde" ]
30-
objc-exception = [ "objc/exception" ]
31-
drag-drop = [ ]
32-
protocol = [ ]
33-
devtools = [ ]
34-
transparent = [ ]
35-
fullscreen = [ ]
36-
linux-body = [ "webkit2gtk/v2_40", "os-webview" ]
37-
mac-proxy = [ ]
28+
default = ["drag-drop", "objc-exception", "protocol", "os-webview"]
29+
serde = ["dpi/serde"]
30+
objc-exception = ["objc2/catch-all"]
31+
drag-drop = []
32+
protocol = []
33+
devtools = []
34+
transparent = []
35+
fullscreen = []
36+
linux-body = ["webkit2gtk/v2_40", "os-webview"]
37+
mac-proxy = []
3838
os-webview = [
3939
"javascriptcore-rs",
4040
"webkit2gtk",
4141
"webkit2gtk-sys",
4242
"dep:gtk",
4343
"soup3",
4444
"x11-dl",
45-
"gdkx11"
45+
"gdkx11",
4646
]
47-
tracing = [ "dep:tracing" ]
47+
tracing = ["dep:tracing"]
4848

4949
[dependencies]
5050
tracing = { version = "0.1", optional = true }
5151
once_cell = "1"
5252
thiserror = "1.0"
5353
http = "1.1"
54-
raw-window-handle = { version = "0.6", features = [ "std" ] }
54+
raw-window-handle = { version = "0.6", features = ["std"] }
5555
dpi = "0.1"
5656

5757
[target."cfg(any(target_os = \"linux\", target_os = \"dragonfly\", target_os = \"freebsd\", target_os = \"openbsd\", target_os = \"netbsd\"))".dependencies]
58-
javascriptcore-rs = { version = "=1.1.2", features = [ "v2_28" ], optional = true }
59-
webkit2gtk = { version = "=2.0.1", features = [ "v2_38" ], optional = true }
58+
javascriptcore-rs = { version = "=1.1.2", features = [
59+
"v2_28",
60+
], optional = true }
61+
webkit2gtk = { version = "=2.0.1", features = ["v2_38"], optional = true }
6062
webkit2gtk-sys = { version = "=2.0.1", optional = true }
6163
gtk = { version = "0.18", optional = true }
6264
soup3 = { version = "0.5", optional = true }
@@ -87,15 +89,79 @@ features = [
8789
"Win32_Globalization",
8890
"Win32_UI_HiDpi",
8991
"Win32_UI_Input",
90-
"Win32_UI_Input_KeyboardAndMouse"
92+
"Win32_UI_Input_KeyboardAndMouse",
9193
]
9294

9395
[target."cfg(any(target_os = \"ios\", target_os = \"macos\"))".dependencies]
94-
block = "0.1"
95-
cocoa = "0.26"
96-
core-graphics = "0.24"
97-
objc = "0.2"
98-
objc_id = "0.1"
96+
block2 = "0.5"
97+
objc2 = "0.5"
98+
objc2-web-kit = { version = "0.2.0", features = [
99+
"objc2-app-kit",
100+
"block2",
101+
"WKWebView",
102+
"WKWebViewConfiguration",
103+
"WKWebsiteDataStore",
104+
"WKDownload",
105+
"WKDownloadDelegate",
106+
"WKNavigation",
107+
"WKNavigationDelegate",
108+
"WKUserContentController",
109+
"WKURLSchemeHandler",
110+
"WKPreferences",
111+
"WKURLSchemeTask",
112+
"WKScriptMessageHandler",
113+
"WKUIDelegate",
114+
"WKOpenPanelParameters",
115+
"WKFrameInfo",
116+
"WKSecurityOrigin",
117+
"WKScriptMessage",
118+
"WKNavigationAction",
119+
"WKWebpagePreferences",
120+
"WKNavigationResponse",
121+
"WKUserScript",
122+
] }
123+
objc2-foundation = { version = "0.2.0", features = [
124+
"NSURLRequest",
125+
"NSURL",
126+
"NSString",
127+
"NSKeyValueCoding",
128+
"NSStream",
129+
"NSDictionary",
130+
"NSObject",
131+
"NSData",
132+
"NSKeyValueObserving",
133+
"NSThread",
134+
"NSJSONSerialization",
135+
"NSDate",
136+
"NSBundle",
137+
"NSProcessInfo",
138+
"NSValue",
139+
"NSRange",
140+
] }
141+
142+
[target."cfg(target_os = \"ios\")".dependencies]
143+
objc2-ui-kit = { version = "0.2.2", features = [
144+
"UIResponder",
145+
"UIScrollView",
146+
"UIView",
147+
"UIWindow",
148+
"UIApplication",
149+
"UIEvent",
150+
] }
151+
152+
[target."cfg(target_os = \"macos\")".dependencies]
153+
objc2-app-kit = { version = "0.2.0", features = [
154+
"NSApplication",
155+
"NSEvent",
156+
"NSWindow",
157+
"NSView",
158+
"NSPasteboard",
159+
"NSPanel",
160+
"NSResponder",
161+
"NSOpenPanel",
162+
"NSSavePanel",
163+
"NSMenu",
164+
] }
99165

100166
[target."cfg(target_os = \"android\")".dependencies]
101167
crossbeam-channel = "0.5"
@@ -119,4 +185,4 @@ percent-encoding = "2.3"
119185

120186
[lints.rust.unexpected_cfgs]
121187
level = "warn"
122-
check-cfg = [ "cfg(linux)", "cfg(gtk)" ]
188+
check-cfg = ["cfg(linux)", "cfg(gtk)"]

‎examples/reparent.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use tao::{
1111
use wry::WebViewBuilder;
1212

1313
#[cfg(target_os = "macos")]
14-
use {tao::platform::macos::WindowExtMacOS, wry::WebViewExtMacOS};
14+
use {objc2_app_kit::NSWindow, tao::platform::macos::WindowExtMacOS, wry::WebViewExtMacOS};
1515
#[cfg(target_os = "windows")]
1616
use {tao::platform::windows::WindowExtWindows, wry::WebViewExtWindows};
1717

@@ -91,7 +91,7 @@ fn main() -> wry::Result<()> {
9191

9292
#[cfg(target_os = "macos")]
9393
webview
94-
.reparent(new_parent.ns_window() as cocoa::base::id)
94+
.reparent(new_parent.ns_window() as *mut NSWindow)
9595
.unwrap();
9696
#[cfg(not(any(
9797
target_os = "windows",

‎src/error.rs

+2
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@ pub enum Error {
5555
#[cfg(target_os = "android")]
5656
#[error(transparent)]
5757
CrossBeamRecvError(#[from] crossbeam_channel::RecvError),
58+
#[error("not on the main thread")]
59+
NotMainThread,
5860
#[error("Custom protocol task is invalid.")]
5961
CustomProtocolTaskInvalid,
6062
#[error("Failed to register URL scheme: {0}, could be due to invalid URL scheme or the scheme is already registered.")]

‎src/lib.rs

+29-35
Original file line numberDiff line numberDiff line change
@@ -190,9 +190,9 @@
190190
#![allow(clippy::type_complexity)]
191191
#![cfg_attr(docsrs, feature(doc_cfg))]
192192

193-
#[cfg(any(target_os = "macos", target_os = "ios"))]
194-
#[macro_use]
195-
extern crate objc;
193+
// #[cfg(any(target_os = "macos", target_os = "ios"))]
194+
// #[macro_use]
195+
// extern crate objc;
196196

197197
mod error;
198198
mod proxy;
@@ -222,12 +222,18 @@ use raw_window_handle::HasWindowHandle;
222222
#[cfg(gtk)]
223223
use webkitgtk::*;
224224

225+
#[cfg(any(target_os = "macos", target_os = "ios"))]
226+
use objc2::rc::Retained;
227+
#[cfg(target_os = "macos")]
228+
use objc2_app_kit::NSWindow;
229+
#[cfg(any(target_os = "macos", target_os = "ios"))]
230+
use objc2_web_kit::WKUserContentController;
225231
#[cfg(any(target_os = "macos", target_os = "ios"))]
226232
pub(crate) mod wkwebview;
227233
#[cfg(any(target_os = "macos", target_os = "ios"))]
228234
use wkwebview::*;
229235
#[cfg(any(target_os = "macos", target_os = "ios"))]
230-
pub use wkwebview::{PrintMargin, PrintOptions};
236+
pub use wkwebview::{PrintMargin, PrintOptions, WryWebView};
231237

232238
#[cfg(target_os = "windows")]
233239
pub(crate) mod webview2;
@@ -416,7 +422,7 @@ pub struct WebViewAttributes<'a> {
416422
/// second is a mutable `PathBuf` reference that (possibly) represents where the file will be downloaded to. The latter
417423
/// parameter can be used to set the download location by assigning a new path to it, the assigned path _must_ be
418424
/// absolute. The closure returns a `bool` to allow or deny the download.
419-
pub download_started_handler: Option<Box<dyn FnMut(String, &mut PathBuf) -> bool>>,
425+
pub download_started_handler: Option<Box<dyn FnMut(String, &mut PathBuf) -> bool + 'static>>,
420426

421427
/// A download completion handler to manage downloads that have finished.
422428
///
@@ -1146,20 +1152,11 @@ impl<'a> WebViewBuilder<'a> {
11461152
}
11471153

11481154
#[cfg(any(target_os = "macos", target_os = "ios",))]
1149-
#[derive(Clone)]
1155+
#[derive(Clone, Default)]
11501156
pub(crate) struct PlatformSpecificWebViewAttributes {
11511157
data_store_identifier: Option<[u8; 16]>,
11521158
}
11531159

1154-
#[cfg(any(target_os = "macos", target_os = "ios",))]
1155-
impl Default for PlatformSpecificWebViewAttributes {
1156-
fn default() -> Self {
1157-
Self {
1158-
data_store_identifier: None,
1159-
}
1160-
}
1161-
}
1162-
11631160
#[cfg(any(target_os = "macos", target_os = "ios",))]
11641161
pub trait WebViewBuilderExtDarwin {
11651162
/// Initialize the WebView with a custom data store identifier.
@@ -1767,35 +1764,32 @@ impl WebViewExtUnix for WebView {
17671764
#[cfg(target_os = "macos")]
17681765
pub trait WebViewExtMacOS {
17691766
/// Returns WKWebView handle
1770-
fn webview(&self) -> cocoa::base::id;
1767+
fn webview(&self) -> Retained<WryWebView>;
17711768
/// Returns WKWebView manager [(userContentController)](https://developer.apple.com/documentation/webkit/wkscriptmessagehandler/1396222-usercontentcontroller) handle
1772-
fn manager(&self) -> cocoa::base::id;
1769+
fn manager(&self) -> Retained<WKUserContentController>;
17731770
/// Returns NSWindow associated with the WKWebView webview
1774-
fn ns_window(&self) -> cocoa::base::id;
1771+
fn ns_window(&self) -> Retained<NSWindow>;
17751772
/// Attaches this webview to the given NSWindow and removes it from the current one.
1776-
fn reparent(&self, window: cocoa::base::id) -> Result<()>;
1773+
fn reparent(&self, window: *mut NSWindow) -> Result<()>;
17771774
// Prints with extra options
17781775
fn print_with_options(&self, options: &PrintOptions) -> Result<()>;
17791776
}
17801777

17811778
#[cfg(target_os = "macos")]
17821779
impl WebViewExtMacOS for WebView {
1783-
fn webview(&self) -> cocoa::base::id {
1784-
self.webview.webview
1780+
fn webview(&self) -> Retained<WryWebView> {
1781+
self.webview.webview.clone()
17851782
}
17861783

1787-
fn manager(&self) -> cocoa::base::id {
1788-
self.webview.manager
1784+
fn manager(&self) -> Retained<WKUserContentController> {
1785+
self.webview.manager.clone()
17891786
}
17901787

1791-
fn ns_window(&self) -> cocoa::base::id {
1792-
unsafe {
1793-
let ns_window: cocoa::base::id = msg_send![self.webview.webview, window];
1794-
ns_window
1795-
}
1788+
fn ns_window(&self) -> Retained<NSWindow> {
1789+
self.webview.webview.window().unwrap().clone()
17961790
}
17971791

1798-
fn reparent(&self, window: cocoa::base::id) -> Result<()> {
1792+
fn reparent(&self, window: *mut NSWindow) -> Result<()> {
17991793
self.webview.reparent(window)
18001794
}
18011795

@@ -1808,19 +1802,19 @@ impl WebViewExtMacOS for WebView {
18081802
#[cfg(target_os = "ios")]
18091803
pub trait WebViewExtIOS {
18101804
/// Returns WKWebView handle
1811-
fn webview(&self) -> cocoa::base::id;
1805+
fn webview(&self) -> Retained<WryWebView>;
18121806
/// Returns WKWebView manager [(userContentController)](https://developer.apple.com/documentation/webkit/wkscriptmessagehandler/1396222-usercontentcontroller) handle
1813-
fn manager(&self) -> cocoa::base::id;
1807+
fn manager(&self) -> Retained<WKUserContentController>;
18141808
}
18151809

18161810
#[cfg(target_os = "ios")]
18171811
impl WebViewExtIOS for WebView {
1818-
fn webview(&self) -> cocoa::base::id {
1819-
self.webview.webview
1812+
fn webview(&self) -> Retained<WryWebView> {
1813+
self.webview.webview.clone()
18201814
}
18211815

1822-
fn manager(&self) -> cocoa::base::id {
1823-
self.webview.manager
1816+
fn manager(&self) -> Retained<WKUserContentController> {
1817+
self.webview.manager.clone()
18241818
}
18251819
}
18261820

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
// Copyright 2020-2024 Tauri Programme within The Commons Conservancy
2+
// SPDX-License-Identifier: Apache-2.0
3+
// SPDX-License-Identifier: MIT
4+
5+
use std::{ffi::c_void, ptr::null_mut};
6+
7+
use objc2::{
8+
declare_class, msg_send, msg_send_id,
9+
mutability::InteriorMutable,
10+
rc::Retained,
11+
runtime::{AnyObject, NSObject},
12+
ClassType, DeclaredClass,
13+
};
14+
use objc2_foundation::{
15+
NSDictionary, NSKeyValueChangeKey, NSKeyValueObservingOptions,
16+
NSObjectNSKeyValueObserverRegistration, NSObjectProtocol, NSString,
17+
};
18+
19+
use crate::WryWebView;
20+
pub struct DocumentTitleChangedObserverIvars {
21+
pub object: Retained<WryWebView>,
22+
pub handler: Box<dyn Fn(String)>,
23+
}
24+
25+
declare_class!(
26+
pub struct DocumentTitleChangedObserver;
27+
28+
unsafe impl ClassType for DocumentTitleChangedObserver {
29+
type Super = NSObject;
30+
type Mutability = InteriorMutable;
31+
const NAME: &'static str = "DocumentTitleChangedObserver";
32+
}
33+
34+
impl DeclaredClass for DocumentTitleChangedObserver {
35+
type Ivars = DocumentTitleChangedObserverIvars;
36+
}
37+
38+
unsafe impl DocumentTitleChangedObserver {
39+
#[method(observeValueForKeyPath:ofObject:change:context:)]
40+
fn observe_value_for_key_path(
41+
&self,
42+
key_path: Option<&NSString>,
43+
of_object: Option<&AnyObject>,
44+
_change: Option<&NSDictionary<NSKeyValueChangeKey, AnyObject>>,
45+
_context: *mut c_void,
46+
) {
47+
if let (Some(key_path), Some(object)) = (key_path, of_object) {
48+
if key_path.to_string() == "title" {
49+
unsafe {
50+
let handler = &self.ivars().handler;
51+
// if !handler.is_null() {
52+
let title: *const NSString = msg_send![object, title];
53+
handler((*title).to_string());
54+
// }
55+
}
56+
}
57+
}
58+
}
59+
}
60+
61+
unsafe impl NSObjectProtocol for DocumentTitleChangedObserver {}
62+
);
63+
64+
impl DocumentTitleChangedObserver {
65+
pub fn new(webview: Retained<WryWebView>, handler: Box<dyn Fn(String)>) -> Retained<Self> {
66+
let observer = Self::alloc().set_ivars(DocumentTitleChangedObserverIvars {
67+
object: webview,
68+
handler,
69+
});
70+
71+
let observer: Retained<Self> = unsafe { msg_send_id![super(observer), init] };
72+
73+
unsafe {
74+
observer
75+
.ivars()
76+
.object
77+
.addObserver_forKeyPath_options_context(
78+
&observer,
79+
&NSString::from_str("title"),
80+
NSKeyValueObservingOptions::NSKeyValueObservingOptionNew,
81+
null_mut(),
82+
);
83+
}
84+
85+
observer
86+
}
87+
}
88+
89+
impl Drop for DocumentTitleChangedObserver {
90+
fn drop(&mut self) {
91+
unsafe {
92+
self
93+
.ivars()
94+
.object
95+
.removeObserver_forKeyPath(self, &NSString::from_str("title"));
96+
}
97+
}
98+
}

‎src/wkwebview/class/mod.rs

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// Copyright 2020-2024 Tauri Programme within The Commons Conservancy
2+
// SPDX-License-Identifier: Apache-2.0
3+
// SPDX-License-Identifier: MIT
4+
5+
pub mod document_title_changed_observer;
6+
pub mod url_scheme_handler;
7+
pub mod wry_download_delegate;
8+
pub mod wry_navigation_delegate;
9+
pub mod wry_web_view;
10+
pub mod wry_web_view_delegate;
11+
pub mod wry_web_view_parent;
12+
pub mod wry_web_view_ui_delegate;
+304
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,304 @@
1+
// Copyright 2020-2024 Tauri Programme within The Commons Conservancy
2+
// SPDX-License-Identifier: Apache-2.0
3+
// SPDX-License-Identifier: MIT
4+
5+
use std::{
6+
borrow::Cow,
7+
ffi::{c_char, c_void, CStr},
8+
panic::AssertUnwindSafe,
9+
ptr::NonNull,
10+
slice,
11+
};
12+
13+
use http::{
14+
header::{CONTENT_LENGTH, CONTENT_TYPE},
15+
Request, Response as HttpResponse, StatusCode, Version,
16+
};
17+
use objc2::{
18+
rc::Retained,
19+
runtime::{AnyClass, AnyObject, ClassBuilder, ProtocolObject},
20+
ClassType,
21+
};
22+
use objc2_foundation::{
23+
NSData, NSHTTPURLResponse, NSMutableDictionary, NSObject, NSObjectProtocol, NSString, NSURL,
24+
NSUUID,
25+
};
26+
use objc2_web_kit::{WKURLSchemeHandler, WKURLSchemeTask};
27+
28+
use crate::{wkwebview::WEBVIEW_IDS, RequestAsyncResponder, WryWebView};
29+
30+
pub fn create(name: &str) -> &AnyClass {
31+
unsafe {
32+
let scheme_name = format!("{}URLSchemeHandler", name);
33+
let cls = ClassBuilder::new(&scheme_name, NSObject::class());
34+
match cls {
35+
Some(mut cls) => {
36+
cls.add_ivar::<*mut c_void>("function");
37+
cls.add_ivar::<*mut c_char>("webview_id");
38+
cls.add_method(
39+
objc2::sel!(webView:startURLSchemeTask:),
40+
start_task as extern "C" fn(_, _, _, _),
41+
);
42+
cls.add_method(
43+
objc2::sel!(webView:stopURLSchemeTask:),
44+
stop_task as extern "C" fn(_, _, _, _),
45+
);
46+
cls.register()
47+
}
48+
None => AnyClass::get(&scheme_name).expect("Failed to get the class definition"),
49+
}
50+
}
51+
}
52+
53+
// Task handler for custom protocol
54+
extern "C" fn start_task(
55+
this: &AnyObject,
56+
_sel: objc2::runtime::Sel,
57+
webview: &'static mut WryWebView,
58+
task: &'static ProtocolObject<dyn WKURLSchemeTask>,
59+
) {
60+
unsafe {
61+
#[cfg(feature = "tracing")]
62+
let span = tracing::info_span!(parent: None, "wry::custom_protocol::handle", uri = tracing::field::Empty)
63+
.entered();
64+
65+
let task_key = task.hash(); // hash by task object address
66+
let task_uuid = webview.add_custom_task_key(task_key);
67+
68+
let ivar = this.class().instance_variable("webview_id").unwrap();
69+
let webview_id_ptr: *mut c_char = *ivar.load(this);
70+
let webview_id = CStr::from_ptr(webview_id_ptr)
71+
.to_str()
72+
.ok()
73+
.unwrap_or_default();
74+
75+
let ivar = this.class().instance_variable("function").unwrap();
76+
let function: &*mut c_void = ivar.load(this);
77+
if !function.is_null() {
78+
let function = &mut *(*function
79+
as *mut Box<dyn Fn(crate::WebViewId, Request<Vec<u8>>, RequestAsyncResponder)>);
80+
81+
// Get url request
82+
let request = task.request();
83+
let url = request.URL().unwrap();
84+
85+
let uri = url.absoluteString().unwrap().to_string();
86+
87+
#[cfg(feature = "tracing")]
88+
span.record("uri", uri.clone());
89+
90+
// Get request method (GET, POST, PUT etc...)
91+
let method = request.HTTPMethod().unwrap().to_string();
92+
93+
// Prepare our HttpRequest
94+
let mut http_request = Request::builder().uri(uri).method(method.as_str());
95+
96+
// Get body
97+
let mut sent_form_body = Vec::new();
98+
let body = request.HTTPBody();
99+
let body_stream = request.HTTPBodyStream();
100+
if let Some(body) = body {
101+
let length = body.length();
102+
let data_bytes = body.bytes();
103+
sent_form_body = slice::from_raw_parts(data_bytes.as_ptr(), length).to_vec();
104+
} else if let Some(body_stream) = body_stream {
105+
body_stream.open();
106+
107+
while body_stream.hasBytesAvailable() {
108+
sent_form_body.reserve(128);
109+
let p = sent_form_body.as_mut_ptr().add(sent_form_body.len());
110+
let read_length = sent_form_body.capacity() - sent_form_body.len();
111+
let count = body_stream.read_maxLength(NonNull::new(p).unwrap(), read_length);
112+
sent_form_body.set_len(sent_form_body.len() + count as usize);
113+
}
114+
115+
body_stream.close();
116+
}
117+
118+
// Extract all headers fields
119+
let all_headers = request.allHTTPHeaderFields();
120+
121+
// get all our headers values and inject them in our request
122+
if let Some(all_headers) = all_headers {
123+
for current_header in all_headers.allKeys().to_vec() {
124+
let header_value = all_headers.valueForKey(current_header).unwrap();
125+
126+
// inject the header into the request
127+
http_request = http_request.header(current_header.to_string(), header_value.to_string());
128+
}
129+
}
130+
131+
let respond_with_404 = || {
132+
let urlresponse = NSHTTPURLResponse::alloc();
133+
let response = NSHTTPURLResponse::initWithURL_statusCode_HTTPVersion_headerFields(
134+
urlresponse,
135+
&url,
136+
StatusCode::NOT_FOUND.as_u16().try_into().unwrap(),
137+
Some(&NSString::from_str(
138+
format!("{:#?}", Version::HTTP_11).as_str(),
139+
)),
140+
None,
141+
)
142+
.unwrap();
143+
task.didReceiveResponse(&response);
144+
// Finish
145+
task.didFinish();
146+
};
147+
148+
// send response
149+
match http_request.body(sent_form_body) {
150+
Ok(final_request) => {
151+
let responder: Box<dyn FnOnce(HttpResponse<Cow<'static, [u8]>>)> =
152+
Box::new(move |sent_response| {
153+
fn check_webview_id_valid(webview_id: &str) -> crate::Result<()> {
154+
if !WEBVIEW_IDS.lock().unwrap().contains(webview_id) {
155+
return Err(crate::Error::CustomProtocolTaskInvalid);
156+
}
157+
Ok(())
158+
}
159+
/// Task may not live longer than async custom protocol handler.
160+
///
161+
/// There are roughly 2 ways to cause segfault:
162+
/// 1. Task has stopped. pointer of the task not valid anymore.
163+
/// 2. Task had stopped, but the pointer of the task has allocated to a new task.
164+
/// Outdated custom handler may call to the new task instance and cause segfault.
165+
fn check_task_is_valid(
166+
webview: &WryWebView,
167+
task_key: usize,
168+
current_uuid: Retained<NSUUID>,
169+
) -> crate::Result<()> {
170+
let latest_task_uuid = webview.get_custom_task_uuid(task_key);
171+
if let Some(latest_uuid) = latest_task_uuid {
172+
if latest_uuid != current_uuid {
173+
return Err(crate::Error::CustomProtocolTaskInvalid);
174+
}
175+
} else {
176+
return Err(crate::Error::CustomProtocolTaskInvalid);
177+
}
178+
Ok(())
179+
}
180+
181+
unsafe fn response(
182+
// FIXME: though we give it a static lifetime, it's not guaranteed to be valid.
183+
task: &'static ProtocolObject<dyn WKURLSchemeTask>,
184+
// FIXME: though we give it a static lifetime, it's not guaranteed to be valid.
185+
webview: &'static mut WryWebView,
186+
task_key: usize,
187+
task_uuid: Retained<NSUUID>,
188+
webview_id: &str,
189+
url: Retained<NSURL>,
190+
sent_response: HttpResponse<Cow<'_, [u8]>>,
191+
) -> crate::Result<()> {
192+
check_task_is_valid(&*webview, task_key, task_uuid.clone())?;
193+
194+
let content = sent_response.body();
195+
// default: application/octet-stream, but should be provided by the client
196+
let wanted_mime = sent_response.headers().get(CONTENT_TYPE);
197+
// default to 200
198+
let wanted_status_code = sent_response.status().as_u16() as i32;
199+
// default to HTTP/1.1
200+
let wanted_version = format!("{:#?}", sent_response.version());
201+
202+
let mut headers = NSMutableDictionary::new();
203+
204+
if let Some(mime) = wanted_mime {
205+
headers.insert_id(
206+
NSString::from_str(mime.to_str().unwrap()).as_ref(),
207+
NSString::from_str(CONTENT_TYPE.as_str()),
208+
);
209+
}
210+
headers.insert_id(
211+
NSString::from_str(&content.len().to_string()).as_ref(),
212+
NSString::from_str(CONTENT_LENGTH.as_str()),
213+
);
214+
215+
// add headers
216+
for (name, value) in sent_response.headers().iter() {
217+
if let Ok(value) = value.to_str() {
218+
headers.insert_id(
219+
NSString::from_str(name.as_str()).as_ref(),
220+
NSString::from_str(value),
221+
);
222+
}
223+
}
224+
225+
let urlresponse = NSHTTPURLResponse::alloc();
226+
let response = NSHTTPURLResponse::initWithURL_statusCode_HTTPVersion_headerFields(
227+
urlresponse,
228+
&url,
229+
wanted_status_code.try_into().unwrap(),
230+
Some(&NSString::from_str(&wanted_version)),
231+
Some(&headers),
232+
)
233+
.unwrap();
234+
235+
check_webview_id_valid(webview_id)?;
236+
check_task_is_valid(&*webview, task_key, task_uuid.clone())?;
237+
238+
objc2::exception::catch(AssertUnwindSafe(|| {
239+
task.didReceiveResponse(&response);
240+
}))
241+
.unwrap();
242+
243+
// Send data
244+
let bytes = content.as_ptr() as *mut c_void;
245+
let data = NSData::alloc();
246+
// MIGRATE NOTE: we copied the content to the NSData because content will be freed
247+
// when out of scope but NSData will also free the content when it's done and cause doube free.
248+
let data = NSData::initWithBytes_length(data, bytes, content.len());
249+
check_webview_id_valid(webview_id)?;
250+
check_task_is_valid(&*webview, task_key, task_uuid.clone())?;
251+
objc2::exception::catch(AssertUnwindSafe(|| {
252+
task.didReceiveData(&data);
253+
}))
254+
.unwrap();
255+
256+
// Finish
257+
check_webview_id_valid(webview_id)?;
258+
check_task_is_valid(&*webview, task_key, task_uuid.clone())?;
259+
objc2::exception::catch(AssertUnwindSafe(|| {
260+
task.didFinish();
261+
}))
262+
.unwrap();
263+
264+
webview.remove_custom_task_key(task_key);
265+
Ok(())
266+
}
267+
268+
let _ = response(
269+
task,
270+
webview,
271+
task_key,
272+
task_uuid,
273+
webview_id,
274+
url.clone(),
275+
sent_response,
276+
);
277+
});
278+
279+
#[cfg(feature = "tracing")]
280+
let _span = tracing::info_span!("wry::custom_protocol::call_handler").entered();
281+
function(
282+
webview_id,
283+
final_request,
284+
RequestAsyncResponder { responder },
285+
);
286+
}
287+
Err(_) => respond_with_404(),
288+
};
289+
} else {
290+
#[cfg(feature = "tracing")]
291+
tracing::warn!(
292+
"Either WebView or WebContext instance is dropped! This handler shouldn't be called."
293+
);
294+
}
295+
}
296+
}
297+
extern "C" fn stop_task(
298+
_this: &ProtocolObject<dyn WKURLSchemeHandler>,
299+
_sel: objc2::runtime::Sel,
300+
webview: &mut WryWebView,
301+
task: &ProtocolObject<dyn WKURLSchemeTask>,
302+
) {
303+
webview.remove_custom_task_key(task.hash());
304+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
// Copyright 2020-2024 Tauri Programme within The Commons Conservancy
2+
// SPDX-License-Identifier: Apache-2.0
3+
// SPDX-License-Identifier: MIT
4+
5+
use std::{cell::RefCell, path::PathBuf, rc::Rc};
6+
7+
use objc2::{
8+
declare_class, msg_send_id, mutability::MainThreadOnly, rc::Retained, runtime::NSObject,
9+
ClassType, DeclaredClass,
10+
};
11+
use objc2_foundation::{
12+
MainThreadMarker, NSData, NSError, NSObjectProtocol, NSString, NSURLResponse, NSURL,
13+
};
14+
use objc2_web_kit::{WKDownload, WKDownloadDelegate};
15+
16+
use crate::wkwebview::download::{download_did_fail, download_did_finish, download_policy};
17+
18+
pub struct WryDownloadDelegateIvars {
19+
pub started: Option<RefCell<Box<dyn FnMut(String, &mut PathBuf) -> bool + 'static>>>,
20+
pub completed: Option<Rc<dyn Fn(String, Option<PathBuf>, bool) + 'static>>,
21+
}
22+
23+
declare_class!(
24+
pub struct WryDownloadDelegate;
25+
26+
unsafe impl ClassType for WryDownloadDelegate {
27+
type Super = NSObject;
28+
type Mutability = MainThreadOnly;
29+
const NAME: &'static str = "WryDownloadDelegate";
30+
}
31+
32+
impl DeclaredClass for WryDownloadDelegate {
33+
type Ivars = WryDownloadDelegateIvars;
34+
}
35+
36+
unsafe impl NSObjectProtocol for WryDownloadDelegate {}
37+
38+
unsafe impl WKDownloadDelegate for WryDownloadDelegate {
39+
#[method(download:decideDestinationUsingResponse:suggestedFilename:completionHandler:)]
40+
fn download_policy(
41+
&self,
42+
download: &WKDownload,
43+
response: &NSURLResponse,
44+
suggested_path: &NSString,
45+
handler: &block2::Block<dyn Fn(*const NSURL)>,
46+
) {
47+
download_policy(self, download, response, suggested_path, handler);
48+
}
49+
50+
#[method(downloadDidFinish:)]
51+
fn download_did_finish(&self, download: &WKDownload) {
52+
download_did_finish(self, download);
53+
}
54+
55+
#[method(download:didFailWithError:resumeData:)]
56+
fn download_did_fail(
57+
&self,
58+
download: &WKDownload,
59+
error: &NSError,
60+
resume_data: &NSData,
61+
) {
62+
download_did_fail(self, download, error, resume_data);
63+
}
64+
}
65+
);
66+
67+
impl WryDownloadDelegate {
68+
pub fn new(
69+
download_started_handler: Option<Box<dyn FnMut(String, &mut PathBuf) -> bool + 'static>>,
70+
download_completed_handler: Option<Rc<dyn Fn(String, Option<PathBuf>, bool) + 'static>>,
71+
mtm: MainThreadMarker,
72+
) -> Retained<Self> {
73+
let delegate = mtm
74+
.alloc::<WryDownloadDelegate>()
75+
.set_ivars(WryDownloadDelegateIvars {
76+
started: download_started_handler.map(|handler| RefCell::new(handler)),
77+
completed: download_completed_handler,
78+
});
79+
80+
unsafe { msg_send_id![super(delegate), init] }
81+
}
82+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
// Copyright 2020-2024 Tauri Programme within The Commons Conservancy
2+
// SPDX-License-Identifier: Apache-2.0
3+
// SPDX-License-Identifier: MIT
4+
5+
use std::sync::{Arc, Mutex};
6+
7+
use objc2::{
8+
declare_class, msg_send_id, mutability::MainThreadOnly, rc::Retained, runtime::NSObject,
9+
ClassType, DeclaredClass,
10+
};
11+
use objc2_foundation::{MainThreadMarker, NSObjectProtocol};
12+
use objc2_web_kit::{
13+
WKDownload, WKNavigation, WKNavigationAction, WKNavigationActionPolicy, WKNavigationDelegate,
14+
WKNavigationResponse, WKNavigationResponsePolicy,
15+
};
16+
17+
#[cfg(target_os = "ios")]
18+
use crate::wkwebview::ios::WKWebView::WKWebView;
19+
#[cfg(target_os = "macos")]
20+
use objc2_web_kit::WKWebView;
21+
22+
use crate::{
23+
url_from_webview,
24+
wkwebview::{
25+
download::{navigation_download_action, navigation_download_response},
26+
navigation::{
27+
did_commit_navigation, did_finish_navigation, navigation_policy, navigation_policy_response,
28+
},
29+
},
30+
PageLoadEvent, WryWebView,
31+
};
32+
33+
use super::wry_download_delegate::WryDownloadDelegate;
34+
35+
pub struct WryNavigationDelegateIvars {
36+
pub pending_scripts: Arc<Mutex<Option<Vec<String>>>>,
37+
pub has_download_handler: bool,
38+
pub navigation_policy_function: Box<dyn Fn(String, bool) -> bool>,
39+
pub download_delegate: Option<Retained<WryDownloadDelegate>>,
40+
pub on_page_load_handler: Option<Box<dyn Fn(PageLoadEvent)>>,
41+
}
42+
43+
declare_class!(
44+
pub struct WryNavigationDelegate;
45+
46+
unsafe impl ClassType for WryNavigationDelegate {
47+
type Super = NSObject;
48+
type Mutability = MainThreadOnly;
49+
const NAME: &'static str = "WryNavigationDelegate";
50+
}
51+
52+
impl DeclaredClass for WryNavigationDelegate {
53+
type Ivars = WryNavigationDelegateIvars;
54+
}
55+
56+
unsafe impl NSObjectProtocol for WryNavigationDelegate {}
57+
58+
unsafe impl WKNavigationDelegate for WryNavigationDelegate {
59+
#[method(webView:decidePolicyForNavigationAction:decisionHandler:)]
60+
fn navigation_policy(
61+
&self,
62+
webview: &WKWebView,
63+
action: &WKNavigationAction,
64+
handler: &block2::Block<dyn Fn(WKNavigationActionPolicy)>,
65+
) {
66+
navigation_policy(self, webview, action, handler);
67+
}
68+
69+
#[method(webView:decidePolicyForNavigationResponse:decisionHandler:)]
70+
fn navigation_policy_response(
71+
&self,
72+
webview: &WKWebView,
73+
response: &WKNavigationResponse,
74+
handler: &block2::Block<dyn Fn(WKNavigationResponsePolicy)>,
75+
) {
76+
navigation_policy_response(self, webview, response, handler);
77+
}
78+
79+
#[method(webView:didFinishNavigation:)]
80+
fn did_finish_navigation(
81+
&self,
82+
webview: &WKWebView,
83+
navigation: &WKNavigation,
84+
) {
85+
did_finish_navigation(self, webview, navigation);
86+
}
87+
88+
#[method(webView:didCommitNavigation:)]
89+
fn did_commit_navigation(
90+
&self,
91+
webview: &WKWebView,
92+
navigation: &WKNavigation,
93+
) {
94+
did_commit_navigation(self, webview, navigation);
95+
}
96+
97+
#[method(webView:navigationAction:didBecomeDownload:)]
98+
fn navigation_download_action(
99+
&self,
100+
webview: &WKWebView,
101+
action: &WKNavigationAction,
102+
download: &WKDownload,
103+
) {
104+
navigation_download_action(self, webview, action, download);
105+
}
106+
107+
#[method(webView:navigationResponse:didBecomeDownload:)]
108+
fn navigation_download_response(
109+
&self,
110+
webview: &WKWebView,
111+
response: &WKNavigationResponse,
112+
download: &WKDownload,
113+
) {
114+
navigation_download_response(self, webview, response, download);
115+
}
116+
}
117+
);
118+
119+
impl WryNavigationDelegate {
120+
#[allow(clippy::too_many_arguments)]
121+
pub fn new(
122+
webview: Retained<WryWebView>,
123+
pending_scripts: Arc<Mutex<Option<Vec<String>>>>,
124+
has_download_handler: bool,
125+
navigation_handler: Option<Box<dyn Fn(String) -> bool>>,
126+
new_window_req_handler: Option<Box<dyn Fn(String) -> bool>>,
127+
download_delegate: Option<Retained<WryDownloadDelegate>>,
128+
on_page_load_handler: Option<Box<dyn Fn(PageLoadEvent, String)>>,
129+
mtm: MainThreadMarker,
130+
) -> Retained<Self> {
131+
let navigation_policy_function = Box::new(move |url: String, is_main_frame: bool| -> bool {
132+
if is_main_frame {
133+
navigation_handler
134+
.as_ref()
135+
.map_or(true, |navigation_handler| (navigation_handler)(url))
136+
} else {
137+
new_window_req_handler
138+
.as_ref()
139+
.map_or(true, |new_window_req_handler| (new_window_req_handler)(url))
140+
}
141+
});
142+
143+
let on_page_load_handler = if let Some(handler) = on_page_load_handler {
144+
let custom_handler = Box::new(move |event| {
145+
handler(event, url_from_webview(&webview).unwrap_or_default());
146+
}) as Box<dyn Fn(PageLoadEvent)>;
147+
Some(custom_handler)
148+
} else {
149+
None
150+
};
151+
152+
let delegate = mtm
153+
.alloc::<WryNavigationDelegate>()
154+
.set_ivars(WryNavigationDelegateIvars {
155+
pending_scripts,
156+
navigation_policy_function,
157+
has_download_handler,
158+
download_delegate,
159+
on_page_load_handler,
160+
});
161+
162+
unsafe { msg_send_id![super(delegate), init] }
163+
}
164+
}

‎src/wkwebview/class/wry_web_view.rs

+154
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
// Copyright 2020-2024 Tauri Programme within The Commons Conservancy
2+
// SPDX-License-Identifier: Apache-2.0
3+
// SPDX-License-Identifier: MIT
4+
5+
use std::collections::HashMap;
6+
7+
#[cfg(target_os = "macos")]
8+
use objc2::runtime::ProtocolObject;
9+
use objc2::{
10+
declare_class, mutability::MainThreadOnly, rc::Retained, runtime::Bool, ClassType, DeclaredClass,
11+
};
12+
#[cfg(target_os = "macos")]
13+
use objc2_app_kit::{NSDraggingDestination, NSEvent};
14+
use objc2_foundation::{NSObjectProtocol, NSUUID};
15+
16+
#[cfg(target_os = "ios")]
17+
use crate::wkwebview::ios::WKWebView::WKWebView;
18+
#[cfg(target_os = "macos")]
19+
use crate::{
20+
wkwebview::{drag_drop, synthetic_mouse_events},
21+
DragDropEvent,
22+
};
23+
#[cfg(target_os = "ios")]
24+
use objc2_ui_kit::UIEvent as NSEvent;
25+
#[cfg(target_os = "macos")]
26+
use objc2_web_kit::WKWebView;
27+
28+
pub struct WryWebViewIvars {
29+
pub(crate) is_child: bool,
30+
#[cfg(target_os = "macos")]
31+
pub(crate) drag_drop_handler: Box<dyn Fn(DragDropEvent) -> bool>,
32+
#[cfg(target_os = "macos")]
33+
pub(crate) accept_first_mouse: objc2::runtime::Bool,
34+
pub(crate) custom_protocol_task_ids: HashMap<usize, Retained<NSUUID>>,
35+
}
36+
37+
declare_class!(
38+
pub struct WryWebView;
39+
40+
unsafe impl ClassType for WryWebView {
41+
type Super = WKWebView;
42+
type Mutability = MainThreadOnly;
43+
const NAME: &'static str = "WryWebView";
44+
}
45+
46+
impl DeclaredClass for WryWebView {
47+
type Ivars = WryWebViewIvars;
48+
}
49+
50+
unsafe impl WryWebView {
51+
#[method(performKeyEquivalent:)]
52+
fn perform_key_equivalent(
53+
&self,
54+
event: &NSEvent,
55+
) -> Bool {
56+
// This is a temporary workaround for https://github.com/tauri-apps/tauri/issues/9426
57+
// FIXME: When the webview is a child webview, performKeyEquivalent always return YES
58+
// and stop propagating the event to the window, hence the menu shortcut won't be
59+
// triggered. However, overriding this method also means the cmd+key event won't be
60+
// handled in webview, which means the key cannot be listened by JavaScript.
61+
if self.ivars().is_child {
62+
Bool::NO
63+
} else {
64+
unsafe {
65+
objc2::msg_send![super(self), performKeyEquivalent: event]
66+
}
67+
}
68+
}
69+
70+
#[cfg(target_os = "macos")]
71+
#[method(acceptsFirstMouse:)]
72+
fn accept_first_mouse(
73+
&self,
74+
_event: &NSEvent,
75+
) -> Bool {
76+
self.ivars().accept_first_mouse
77+
}
78+
}
79+
unsafe impl NSObjectProtocol for WryWebView {}
80+
81+
// Drag & Drop
82+
#[cfg(target_os = "macos")]
83+
unsafe impl NSDraggingDestination for WryWebView {
84+
#[method(draggingEntered:)]
85+
fn dragging_entered(
86+
&self,
87+
drag_info: &ProtocolObject<dyn objc2_app_kit::NSDraggingInfo>,
88+
) -> objc2_app_kit::NSDragOperation {
89+
drag_drop::dragging_entered(self, drag_info)
90+
}
91+
92+
#[method(draggingUpdated:)]
93+
fn dragging_updated(
94+
&self,
95+
drag_info: &ProtocolObject<dyn objc2_app_kit::NSDraggingInfo>,
96+
) -> objc2_app_kit::NSDragOperation {
97+
drag_drop::dragging_updated(self, drag_info)
98+
}
99+
100+
#[method(performDragOperation:)]
101+
fn perform_drag_operation(
102+
&self,
103+
drag_info: &ProtocolObject<dyn objc2_app_kit::NSDraggingInfo>,
104+
) -> Bool {
105+
drag_drop::perform_drag_operation(self, drag_info)
106+
}
107+
108+
#[method(draggingExited:)]
109+
fn dragging_exited(
110+
&self,
111+
drag_info: &ProtocolObject<dyn objc2_app_kit::NSDraggingInfo>,
112+
) {
113+
drag_drop::dragging_exited(self, drag_info)
114+
}
115+
}
116+
117+
// Synthetic mouse events
118+
#[cfg(target_os = "macos")]
119+
unsafe impl WryWebView {
120+
#[method(otherMouseDown:)]
121+
fn other_mouse_down(
122+
&self,
123+
event: &NSEvent,
124+
) {
125+
synthetic_mouse_events::other_mouse_down(self, event)
126+
}
127+
128+
#[method(otherMouseUp:)]
129+
fn other_mouse_up(
130+
&self,
131+
event: &NSEvent,
132+
) {
133+
synthetic_mouse_events::other_mouse_up(self, event)
134+
}
135+
}
136+
);
137+
138+
// Custom Protocol Task Checker
139+
impl WryWebView {
140+
pub(crate) fn add_custom_task_key(&mut self, task_id: usize) -> Retained<NSUUID> {
141+
let task_uuid = NSUUID::new();
142+
self
143+
.ivars_mut()
144+
.custom_protocol_task_ids
145+
.insert(task_id, task_uuid.clone());
146+
task_uuid
147+
}
148+
pub(crate) fn remove_custom_task_key(&mut self, task_id: usize) {
149+
self.ivars_mut().custom_protocol_task_ids.remove(&task_id);
150+
}
151+
pub(crate) fn get_custom_task_uuid(&self, task_id: usize) -> Option<Retained<NSUUID>> {
152+
self.ivars().custom_protocol_task_ids.get(&task_id).cloned()
153+
}
154+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
// Copyright 2020-2024 Tauri Programme within The Commons Conservancy
2+
// SPDX-License-Identifier: Apache-2.0
3+
// SPDX-License-Identifier: MIT
4+
5+
use std::ffi::CStr;
6+
7+
use http::Request;
8+
use objc2::{
9+
declare_class, msg_send_id,
10+
mutability::MainThreadOnly,
11+
rc::Retained,
12+
runtime::{NSObject, ProtocolObject},
13+
ClassType, DeclaredClass,
14+
};
15+
use objc2_foundation::{MainThreadMarker, NSObjectProtocol, NSString};
16+
use objc2_web_kit::{WKScriptMessage, WKScriptMessageHandler, WKUserContentController};
17+
18+
pub const IPC_MESSAGE_HANDLER_NAME: &str = "ipc";
19+
20+
pub struct WryWebViewDelegateIvars {
21+
pub controller: Retained<WKUserContentController>,
22+
pub ipc_handler: Box<dyn Fn(Request<String>)>,
23+
}
24+
25+
declare_class!(
26+
pub struct WryWebViewDelegate;
27+
28+
unsafe impl ClassType for WryWebViewDelegate {
29+
type Super = NSObject;
30+
type Mutability = MainThreadOnly;
31+
const NAME: &'static str = "WryWebViewDelegate";
32+
}
33+
34+
impl DeclaredClass for WryWebViewDelegate {
35+
type Ivars = WryWebViewDelegateIvars;
36+
}
37+
38+
unsafe impl NSObjectProtocol for WryWebViewDelegate {}
39+
40+
unsafe impl WKScriptMessageHandler for WryWebViewDelegate {
41+
// Function for ipc handler
42+
#[method(userContentController:didReceiveScriptMessage:)]
43+
fn did_receive(
44+
this: &WryWebViewDelegate,
45+
_controller: &WKUserContentController,
46+
msg: &WKScriptMessage,
47+
) {
48+
// Safety: objc runtime calls are unsafe
49+
unsafe {
50+
#[cfg(feature = "tracing")]
51+
let _span = tracing::info_span!(parent: None, "wry::ipc::handle").entered();
52+
53+
let ipc_handler = &this.ivars().ipc_handler;
54+
let body = msg.body();
55+
let is_string = Retained::cast::<NSObject>(body.clone()).isKindOfClass(NSString::class());
56+
if is_string {
57+
let body = Retained::cast::<NSString>(body);
58+
let js_utf8 = body.UTF8String();
59+
60+
let frame_info = msg.frameInfo();
61+
let request = frame_info.request();
62+
let url = request.URL().unwrap();
63+
let absolute_url = url.absoluteString().unwrap();
64+
let url_utf8 = absolute_url.UTF8String();
65+
66+
if let (Ok(url), Ok(js)) = (
67+
CStr::from_ptr(url_utf8).to_str(),
68+
CStr::from_ptr(js_utf8).to_str(),
69+
) {
70+
ipc_handler(Request::builder().uri(url).body(js.to_string()).unwrap());
71+
return;
72+
}
73+
}
74+
75+
#[cfg(feature = "tracing")]
76+
tracing::warn!("WebView received invalid IPC call.");
77+
}
78+
}
79+
}
80+
);
81+
82+
impl WryWebViewDelegate {
83+
pub fn new(
84+
controller: Retained<WKUserContentController>,
85+
ipc_handler: Box<dyn Fn(Request<String>)>,
86+
mtm: MainThreadMarker,
87+
) -> Retained<Self> {
88+
let delegate = mtm
89+
.alloc::<WryWebViewDelegate>()
90+
.set_ivars(WryWebViewDelegateIvars {
91+
ipc_handler,
92+
controller,
93+
});
94+
95+
let delegate: Retained<Self> = unsafe { msg_send_id![super(delegate), init] };
96+
97+
let proto_delegate = ProtocolObject::from_ref(delegate.as_ref());
98+
unsafe {
99+
// this will increate the retain count of the delegate
100+
delegate.ivars().controller.addScriptMessageHandler_name(
101+
proto_delegate,
102+
&NSString::from_str(IPC_MESSAGE_HANDLER_NAME),
103+
);
104+
}
105+
106+
delegate
107+
}
108+
}
+55
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
// Copyright 2020-2024 Tauri Programme within The Commons Conservancy
2+
// SPDX-License-Identifier: Apache-2.0
3+
// SPDX-License-Identifier: MIT
4+
5+
use objc2::{
6+
declare_class, msg_send_id, mutability::MainThreadOnly, rc::Retained, ClassType, DeclaredClass,
7+
};
8+
#[cfg(target_os = "macos")]
9+
use objc2_app_kit::{NSApplication, NSEvent, NSView};
10+
use objc2_foundation::MainThreadMarker;
11+
#[cfg(target_os = "ios")]
12+
use objc2_ui_kit::UIView as NSView;
13+
14+
pub struct WryWebViewParentIvars {}
15+
16+
declare_class!(
17+
pub struct WryWebViewParent;
18+
19+
unsafe impl ClassType for WryWebViewParent {
20+
type Super = NSView;
21+
type Mutability = MainThreadOnly;
22+
const NAME: &'static str = "WryWebViewParent";
23+
}
24+
25+
impl DeclaredClass for WryWebViewParent {
26+
type Ivars = WryWebViewParentIvars;
27+
}
28+
29+
unsafe impl WryWebViewParent {
30+
#[cfg(target_os = "macos")]
31+
#[method(keyDown:)]
32+
fn key_down(
33+
&self,
34+
event: &NSEvent,
35+
) {
36+
let mtm = MainThreadMarker::new().unwrap();
37+
let app = NSApplication::sharedApplication(mtm);
38+
unsafe {
39+
if let Some(menu) = app.mainMenu() {
40+
menu.performKeyEquivalent(event);
41+
}
42+
}
43+
}
44+
}
45+
);
46+
47+
impl WryWebViewParent {
48+
#[allow(dead_code)]
49+
pub fn new(mtm: MainThreadMarker) -> Retained<Self> {
50+
let delegate = mtm
51+
.alloc::<WryWebViewParent>()
52+
.set_ivars(WryWebViewParentIvars {});
53+
unsafe { msg_send_id![super(delegate), init] }
54+
}
55+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
// Copyright 2020-2024 Tauri Programme within The Commons Conservancy
2+
// SPDX-License-Identifier: Apache-2.0
3+
// SPDX-License-Identifier: MIT
4+
5+
#[cfg(target_os = "macos")]
6+
use std::ptr::null_mut;
7+
8+
use block2::Block;
9+
use objc2::{
10+
declare_class, msg_send_id, mutability::MainThreadOnly, rc::Retained, runtime::NSObject,
11+
ClassType, DeclaredClass,
12+
};
13+
#[cfg(target_os = "macos")]
14+
use objc2_app_kit::{NSModalResponse, NSModalResponseOK, NSOpenPanel};
15+
use objc2_foundation::{MainThreadMarker, NSObjectProtocol};
16+
#[cfg(target_os = "macos")]
17+
use objc2_foundation::{NSArray, NSURL};
18+
19+
#[cfg(target_os = "macos")]
20+
use objc2_web_kit::WKOpenPanelParameters;
21+
use objc2_web_kit::{
22+
WKFrameInfo, WKMediaCaptureType, WKPermissionDecision, WKSecurityOrigin, WKUIDelegate,
23+
};
24+
25+
use crate::WryWebView;
26+
27+
pub struct WryWebViewUIDelegateIvars {}
28+
29+
declare_class!(
30+
pub struct WryWebViewUIDelegate;
31+
32+
unsafe impl ClassType for WryWebViewUIDelegate {
33+
type Super = NSObject;
34+
type Mutability = MainThreadOnly;
35+
const NAME: &'static str = "WryWebViewUIDelegate";
36+
}
37+
38+
impl DeclaredClass for WryWebViewUIDelegate {
39+
type Ivars = WryWebViewUIDelegateIvars;
40+
}
41+
42+
unsafe impl NSObjectProtocol for WryWebViewUIDelegate {}
43+
44+
unsafe impl WKUIDelegate for WryWebViewUIDelegate {
45+
#[cfg(target_os = "macos")]
46+
#[method(webView:runOpenPanelWithParameters:initiatedByFrame:completionHandler:)]
47+
fn run_file_upload_panel(
48+
&self,
49+
_webview: &WryWebView,
50+
open_panel_params: &WKOpenPanelParameters,
51+
_frame: &WKFrameInfo,
52+
handler: &block2::Block<dyn Fn(*const NSArray<NSURL>)>
53+
) {
54+
unsafe {
55+
if let Some(mtm) = MainThreadMarker::new() {
56+
let open_panel = NSOpenPanel::openPanel(mtm);
57+
open_panel.setCanChooseFiles(true);
58+
let allow_multi = open_panel_params.allowsMultipleSelection();
59+
open_panel.setAllowsMultipleSelection(allow_multi);
60+
let allow_dir = open_panel_params.allowsDirectories();
61+
open_panel.setCanChooseDirectories(allow_dir);
62+
let ok: NSModalResponse = open_panel.runModal();
63+
if ok == NSModalResponseOK {
64+
let url = open_panel.URLs();
65+
(*handler).call((Retained::as_ptr(&url),));
66+
} else {
67+
(*handler).call((null_mut(),));
68+
}
69+
}
70+
}
71+
}
72+
73+
#[method(webView:requestMediaCapturePermissionForOrigin:initiatedByFrame:type:decisionHandler:)]
74+
fn request_media_capture_permission(
75+
&self,
76+
_webview: &WryWebView,
77+
_origin: &WKSecurityOrigin,
78+
_frame: &WKFrameInfo,
79+
_capture_type: WKMediaCaptureType,
80+
decision_handler: &Block<dyn Fn(WKPermissionDecision)>
81+
) {
82+
//https://developer.apple.com/documentation/webkit/wkpermissiondecision?language=objc
83+
(*decision_handler).call((WKPermissionDecision::Grant,));
84+
}
85+
}
86+
);
87+
88+
impl WryWebViewUIDelegate {
89+
pub fn new(mtm: MainThreadMarker) -> Retained<Self> {
90+
let delegate = mtm
91+
.alloc::<WryWebViewUIDelegate>()
92+
.set_ivars(WryWebViewUIDelegateIvars {});
93+
unsafe { msg_send_id![super(delegate), init] }
94+
}
95+
}

‎src/wkwebview/download.rs

+65-82
Original file line numberDiff line numberDiff line change
@@ -1,123 +1,106 @@
1-
use std::{path::PathBuf, ptr::null_mut, rc::Rc};
1+
use std::{path::PathBuf, ptr::null_mut};
22

3-
use cocoa::base::id;
4-
use objc::{
5-
declare::ClassDecl,
6-
runtime::{Object, Sel},
7-
};
8-
use std::ffi::c_void;
9-
10-
use super::NSString;
3+
use objc2::{rc::Retained, runtime::ProtocolObject, DeclaredClass};
4+
use objc2_foundation::{NSData, NSError, NSString, NSURLResponse, NSURL};
5+
use objc2_web_kit::{WKDownload, WKNavigationAction, WKNavigationResponse};
116

12-
pub(crate) unsafe fn set_download_delegate(webview: *mut Object, download_delegate: *mut Object) {
13-
(*webview).set_ivar(
14-
"DownloadDelegate",
15-
download_delegate as *mut _ as *mut c_void,
16-
);
17-
}
7+
#[cfg(target_os = "ios")]
8+
use crate::wkwebview::ios::WKWebView::WKWebView;
9+
#[cfg(target_os = "macos")]
10+
use objc2_web_kit::WKWebView;
1811

19-
unsafe fn get_download_delegate(this: &mut Object) -> *mut objc::runtime::Object {
20-
let delegate: *mut c_void = *this.get_ivar("DownloadDelegate");
21-
delegate as *mut Object
22-
}
12+
use super::class::{
13+
wry_download_delegate::WryDownloadDelegate, wry_navigation_delegate::WryNavigationDelegate,
14+
};
2315

2416
// Download action handler
25-
extern "C" fn navigation_download_action(this: &mut Object, _: Sel, _: id, _: id, download: id) {
17+
pub(crate) fn navigation_download_action(
18+
this: &WryNavigationDelegate,
19+
_webview: &WKWebView,
20+
_action: &WKNavigationAction,
21+
download: &WKDownload,
22+
) {
2623
unsafe {
27-
let delegate = get_download_delegate(this);
28-
let _: () = msg_send![download, setDelegate: delegate];
24+
if let Some(delegate) = &this.ivars().download_delegate {
25+
let proto_delegate = ProtocolObject::from_ref(delegate.as_ref());
26+
download.setDelegate(Some(proto_delegate));
27+
}
2928
}
3029
}
3130

3231
// Download response handler
33-
extern "C" fn navigation_download_response(this: &mut Object, _: Sel, _: id, _: id, download: id) {
32+
pub(crate) fn navigation_download_response(
33+
this: &WryNavigationDelegate,
34+
_webview: &WKWebView,
35+
_response: &WKNavigationResponse,
36+
download: &WKDownload,
37+
) {
3438
unsafe {
35-
let delegate = get_download_delegate(this);
36-
let _: () = msg_send![download, setDelegate: delegate];
39+
if let Some(delegate) = &this.ivars().download_delegate {
40+
let proto_delegate = ProtocolObject::from_ref(delegate.as_ref());
41+
download.setDelegate(Some(proto_delegate));
42+
}
3743
}
3844
}
3945

40-
pub(crate) unsafe fn add_download_methods(decl: &mut ClassDecl) {
41-
decl.add_ivar::<*mut c_void>("DownloadDelegate");
42-
43-
decl.add_method(
44-
sel!(webView:navigationAction:didBecomeDownload:),
45-
navigation_download_action as extern "C" fn(&mut Object, Sel, id, id, id),
46-
);
47-
48-
decl.add_method(
49-
sel!(webView:navigationResponse:didBecomeDownload:),
50-
navigation_download_response as extern "C" fn(&mut Object, Sel, id, id, id),
51-
);
52-
}
53-
54-
pub extern "C" fn download_policy(
55-
this: &Object,
56-
_: Sel,
57-
download: id,
58-
_: id,
59-
suggested_path: id,
60-
handler: id,
46+
pub(crate) fn download_policy(
47+
this: &WryDownloadDelegate,
48+
download: &WKDownload,
49+
_response: &NSURLResponse,
50+
suggested_path: &NSString,
51+
completion_handler: &block2::Block<dyn Fn(*const NSURL)>,
6152
) {
6253
unsafe {
63-
let request: id = msg_send![download, originalRequest];
64-
let url: id = msg_send![request, URL];
65-
let url: id = msg_send![url, absoluteString];
66-
let url = NSString(url);
67-
let path = NSString(suggested_path);
68-
let mut path = PathBuf::from(path.to_str());
69-
let handler = handler as *mut block::Block<(id,), c_void>;
54+
let request = download.originalRequest().unwrap();
55+
let url = request.URL().unwrap().absoluteString().unwrap();
56+
let mut path = PathBuf::from(suggested_path.to_string());
7057

71-
let function = this.get_ivar::<*mut c_void>("started");
72-
if !function.is_null() {
73-
let function = &mut *(*function as *mut Box<dyn for<'s> FnMut(String, &mut PathBuf) -> bool>);
74-
match (function)(url.to_str().to_string(), &mut path) {
58+
let started_fn = &this.ivars().started;
59+
if let Some(started_fn) = started_fn {
60+
let mut started_fn = started_fn.borrow_mut();
61+
match started_fn(url.to_string().to_string(), &mut path) {
7562
true => {
76-
let nsurl: id = msg_send![class!(NSURL), fileURLWithPath: NSString::new(&path.display().to_string()) isDirectory: false];
77-
(*handler).call((nsurl,))
63+
let path = NSString::from_str(&path.display().to_string());
64+
let ns_url = NSURL::fileURLWithPath_isDirectory(&path, false);
65+
(*completion_handler).call((Retained::as_ptr(&ns_url),))
7866
}
79-
false => (*handler).call((null_mut(),)),
67+
false => (*completion_handler).call((null_mut(),)),
8068
};
8169
} else {
8270
#[cfg(feature = "tracing")]
8371
tracing::warn!("WebView instance is dropped! This navigation handler shouldn't be called.");
84-
(*handler).call((null_mut(),));
72+
(*completion_handler).call((null_mut(),));
8573
}
8674
}
8775
}
8876

89-
pub extern "C" fn download_did_finish(this: &Object, _: Sel, download: id) {
77+
pub(crate) fn download_did_finish(this: &WryDownloadDelegate, download: &WKDownload) {
9078
unsafe {
91-
let function = this.get_ivar::<*mut c_void>("completed");
92-
let original_request: id = msg_send![download, originalRequest];
93-
let url: id = msg_send![original_request, URL];
94-
let url: id = msg_send![url, absoluteString];
95-
let url = NSString(url).to_str().to_string();
96-
if !function.is_null() {
97-
let function = &mut *(*function as *mut Rc<dyn for<'s> Fn(String, Option<PathBuf>, bool)>);
98-
function(url, None, true);
79+
let original_request = download.originalRequest().unwrap();
80+
let url = original_request.URL().unwrap().absoluteString().unwrap();
81+
if let Some(completed_fn) = this.ivars().completed.clone() {
82+
completed_fn(url.to_string(), None, true);
9983
}
10084
}
10185
}
10286

103-
pub extern "C" fn download_did_fail(this: &Object, _: Sel, download: id, _error: id, _: id) {
87+
pub(crate) fn download_did_fail(
88+
this: &WryDownloadDelegate,
89+
download: &WKDownload,
90+
error: &NSError,
91+
_resume_data: &NSData,
92+
) {
10493
unsafe {
10594
#[cfg(debug_assertions)]
10695
{
107-
let description: id = msg_send![_error, localizedDescription];
108-
let description = NSString(description).to_str().to_string();
96+
let description = error.localizedDescription().to_string();
10997
eprintln!("Download failed with error: {}", description);
11098
}
11199

112-
let original_request: id = msg_send![download, originalRequest];
113-
let url: id = msg_send![original_request, URL];
114-
let url: id = msg_send![url, absoluteString];
115-
let url = NSString(url).to_str().to_string();
116-
117-
let function = this.get_ivar::<*mut c_void>("completed");
118-
if !function.is_null() {
119-
let function = &mut *(*function as *mut Rc<dyn for<'s> Fn(String, Option<PathBuf>, bool)>);
120-
function(url, None, false);
100+
let original_request = download.originalRequest().unwrap();
101+
let url = original_request.URL().unwrap().absoluteString().unwrap();
102+
if let Some(completed_fn) = this.ivars().completed.clone() {
103+
completed_fn(url.to_string(), None, false);
121104
}
122105
}
123106
}

‎src/wkwebview/drag_drop.rs

+64-140
Original file line numberDiff line numberDiff line change
@@ -2,179 +2,103 @@
22
// SPDX-License-Identifier: Apache-2.0
33
// SPDX-License-Identifier: MIT
44

5-
use std::{
6-
ffi::{c_void, CStr},
7-
path::PathBuf,
8-
};
5+
use std::{ffi::CStr, path::PathBuf};
96

10-
use cocoa::{
11-
base::{id, BOOL, YES},
12-
foundation::{NSPoint, NSRect},
13-
};
14-
use objc::{
15-
declare::ClassDecl,
16-
runtime::{class_getInstanceMethod, method_getImplementation, Object, Sel},
7+
use objc2::{
8+
rc::Id,
9+
runtime::{AnyObject, Bool, ProtocolObject},
10+
DeclaredClass,
1711
};
18-
use once_cell::sync::Lazy;
12+
use objc2_app_kit::{NSDragOperation, NSDraggingInfo, NSFilenamesPboardType};
13+
use objc2_foundation::{NSArray, NSPoint, NSRect, NSString};
1914

2015
use crate::DragDropEvent;
2116

22-
pub(crate) type NSDragOperation = cocoa::foundation::NSUInteger;
23-
24-
#[allow(non_upper_case_globals)]
25-
const NSDragOperationCopy: NSDragOperation = 1;
26-
27-
const DRAG_DROP_HANDLER_IVAR: &str = "DragDropHandler";
28-
29-
static OBJC_DRAGGING_ENTERED: Lazy<extern "C" fn(*const Object, Sel, id) -> NSDragOperation> =
30-
Lazy::new(|| unsafe {
31-
std::mem::transmute(method_getImplementation(class_getInstanceMethod(
32-
class!(WKWebView),
33-
sel!(draggingEntered:),
34-
)))
35-
});
36-
37-
static OBJC_DRAGGING_EXITED: Lazy<extern "C" fn(*const Object, Sel, id)> = Lazy::new(|| unsafe {
38-
std::mem::transmute(method_getImplementation(class_getInstanceMethod(
39-
class!(WKWebView),
40-
sel!(draggingExited:),
41-
)))
42-
});
43-
44-
static OBJC_PERFORM_DRAG_OPERATION: Lazy<extern "C" fn(*const Object, Sel, id) -> BOOL> =
45-
Lazy::new(|| unsafe {
46-
std::mem::transmute(method_getImplementation(class_getInstanceMethod(
47-
class!(WKWebView),
48-
sel!(performDragOperation:),
49-
)))
50-
});
51-
52-
static OBJC_DRAGGING_UPDATED: Lazy<extern "C" fn(*const Object, Sel, id) -> NSDragOperation> =
53-
Lazy::new(|| unsafe {
54-
std::mem::transmute(method_getImplementation(class_getInstanceMethod(
55-
class!(WKWebView),
56-
sel!(draggingUpdated:),
57-
)))
58-
});
17+
use super::WryWebView;
5918

60-
// Safety: objc runtime calls are unsafe
61-
pub(crate) unsafe fn set_drag_drop_handler(
62-
webview: *mut Object,
63-
handler: Box<dyn Fn(DragDropEvent) -> bool>,
64-
) -> *mut Box<dyn Fn(DragDropEvent) -> bool> {
65-
let listener = Box::into_raw(Box::new(handler));
66-
(*webview).set_ivar(DRAG_DROP_HANDLER_IVAR, listener as *mut _ as *mut c_void);
67-
listener
68-
}
69-
70-
#[allow(clippy::mut_from_ref)]
71-
unsafe fn get_handler(this: &Object) -> &mut Box<dyn Fn(DragDropEvent) -> bool> {
72-
let delegate: *mut c_void = *this.get_ivar(DRAG_DROP_HANDLER_IVAR);
73-
&mut *(delegate as *mut Box<dyn Fn(DragDropEvent) -> bool>)
74-
}
75-
76-
unsafe fn collect_paths(drag_info: id) -> Vec<PathBuf> {
77-
use cocoa::{
78-
appkit::{NSFilenamesPboardType, NSPasteboard},
79-
foundation::{NSFastEnumeration, NSString},
80-
};
81-
82-
let pb: id = msg_send![drag_info, draggingPasteboard];
19+
pub(crate) unsafe fn collect_paths(drag_info: &ProtocolObject<dyn NSDraggingInfo>) -> Vec<PathBuf> {
20+
let pb = drag_info.draggingPasteboard();
8321
let mut drag_drop_paths = Vec::new();
84-
let types: id = msg_send![class!(NSArray), arrayWithObject: NSFilenamesPboardType];
85-
if !NSPasteboard::availableTypeFromArray(pb, types).is_null() {
86-
for path in NSPasteboard::propertyListForType(pb, NSFilenamesPboardType).iter() {
87-
drag_drop_paths.push(PathBuf::from(
88-
CStr::from_ptr(NSString::UTF8String(path))
89-
.to_string_lossy()
90-
.into_owned(),
91-
));
22+
let types = NSArray::arrayWithObject(NSFilenamesPboardType);
23+
24+
if pb.availableTypeFromArray(&types).is_some() {
25+
let paths = pb.propertyListForType(NSFilenamesPboardType).unwrap();
26+
let paths: Id<NSArray<NSString>> = Id::<AnyObject>::cast(paths.clone());
27+
for path in paths.to_vec() {
28+
let path = CStr::from_ptr(path.UTF8String()).to_string_lossy();
29+
drag_drop_paths.push(PathBuf::from(path.into_owned()));
9230
}
9331
}
9432
drag_drop_paths
9533
}
9634

97-
extern "C" fn dragging_updated(this: &mut Object, sel: Sel, drag_info: id) -> NSDragOperation {
98-
let dl: NSPoint = unsafe { msg_send![drag_info, draggingLocation] };
99-
let frame: NSRect = unsafe { msg_send![this, frame] };
35+
pub(crate) fn dragging_entered(
36+
this: &WryWebView,
37+
drag_info: &ProtocolObject<dyn NSDraggingInfo>,
38+
) -> NSDragOperation {
39+
let paths = unsafe { collect_paths(drag_info) };
40+
let dl: NSPoint = unsafe { drag_info.draggingLocation() };
41+
let frame: NSRect = this.frame();
10042
let position = (dl.x as i32, (frame.size.height - dl.y) as i32);
101-
let listener = unsafe { get_handler(this) };
102-
if !listener(DragDropEvent::Over { position }) {
103-
let os_operation = OBJC_DRAGGING_UPDATED(this, sel, drag_info);
104-
if os_operation == 0 {
105-
// 0 will be returned for a drop on any arbitrary location on the webview.
106-
// We'll override that with NSDragOperationCopy.
107-
NSDragOperationCopy
108-
} else {
109-
// A different NSDragOperation is returned when a file is hovered over something like
110-
// a <input type="file">, so we'll make sure to preserve that behaviour.
111-
os_operation
112-
}
43+
44+
let listener = &this.ivars().drag_drop_handler;
45+
if !listener(DragDropEvent::Enter { paths, position }) {
46+
// Reject the Wry file drop (invoke the OS default behaviour)
47+
unsafe { objc2::msg_send![super(this), draggingEntered: drag_info] }
11348
} else {
114-
NSDragOperationCopy
49+
NSDragOperation::Copy
11550
}
11651
}
11752

118-
extern "C" fn dragging_entered(this: &mut Object, sel: Sel, drag_info: id) -> NSDragOperation {
119-
let listener = unsafe { get_handler(this) };
120-
let paths = unsafe { collect_paths(drag_info) };
121-
122-
let dl: NSPoint = unsafe { msg_send![drag_info, draggingLocation] };
123-
let frame: NSRect = unsafe { msg_send![this, frame] };
53+
pub(crate) fn dragging_updated(
54+
this: &WryWebView,
55+
drag_info: &ProtocolObject<dyn NSDraggingInfo>,
56+
) -> NSDragOperation {
57+
let dl: NSPoint = unsafe { drag_info.draggingLocation() };
58+
let frame: NSRect = this.frame();
12459
let position = (dl.x as i32, (frame.size.height - dl.y) as i32);
12560

126-
if !listener(DragDropEvent::Enter { paths, position }) {
127-
// Reject the Wry file drop (invoke the OS default behaviour)
128-
OBJC_DRAGGING_ENTERED(this, sel, drag_info)
61+
let listener = &this.ivars().drag_drop_handler;
62+
if !listener(DragDropEvent::Over { position }) {
63+
unsafe {
64+
let os_operation = objc2::msg_send![super(this), draggingUpdated: drag_info];
65+
if os_operation == NSDragOperation::None {
66+
// 0 will be returned for a drop on any arbitrary location on the webview.
67+
// We'll override that with NSDragOperationCopy.
68+
NSDragOperation::Copy
69+
} else {
70+
// A different NSDragOperation is returned when a file is hovered over something like
71+
// a <input type="file">, so we'll make sure to preserve that behaviour.
72+
os_operation
73+
}
74+
}
12975
} else {
130-
NSDragOperationCopy
76+
NSDragOperation::Copy
13177
}
13278
}
13379

134-
extern "C" fn perform_drag_operation(this: &mut Object, sel: Sel, drag_info: id) -> BOOL {
135-
let listener = unsafe { get_handler(this) };
80+
pub(crate) fn perform_drag_operation(
81+
this: &WryWebView,
82+
drag_info: &ProtocolObject<dyn NSDraggingInfo>,
83+
) -> Bool {
13684
let paths = unsafe { collect_paths(drag_info) };
137-
138-
let dl: NSPoint = unsafe { msg_send![drag_info, draggingLocation] };
139-
let frame: NSRect = unsafe { msg_send![this, frame] };
85+
let dl: NSPoint = unsafe { drag_info.draggingLocation() };
86+
let frame: NSRect = this.frame();
14087
let position = (dl.x as i32, (frame.size.height - dl.y) as i32);
14188

89+
let listener = &this.ivars().drag_drop_handler;
14290
if !listener(DragDropEvent::Drop { paths, position }) {
14391
// Reject the Wry drop (invoke the OS default behaviour)
144-
OBJC_PERFORM_DRAG_OPERATION(this, sel, drag_info)
92+
unsafe { objc2::msg_send![super(this), performDragOperation: drag_info] }
14593
} else {
146-
YES
94+
Bool::YES
14795
}
14896
}
14997

150-
extern "C" fn dragging_exited(this: &mut Object, sel: Sel, drag_info: id) {
151-
let listener = unsafe { get_handler(this) };
98+
pub(crate) fn dragging_exited(this: &WryWebView, drag_info: &ProtocolObject<dyn NSDraggingInfo>) {
99+
let listener = &this.ivars().drag_drop_handler;
152100
if !listener(DragDropEvent::Leave) {
153101
// Reject the Wry drop (invoke the OS default behaviour)
154-
OBJC_DRAGGING_EXITED(this, sel, drag_info);
102+
unsafe { objc2::msg_send![super(this), draggingExited: drag_info] }
155103
}
156104
}
157-
158-
pub(crate) unsafe fn add_drag_drop_methods(decl: &mut ClassDecl) {
159-
decl.add_ivar::<*mut c_void>(DRAG_DROP_HANDLER_IVAR);
160-
161-
decl.add_method(
162-
sel!(draggingEntered:),
163-
dragging_entered as extern "C" fn(&mut Object, Sel, id) -> NSDragOperation,
164-
);
165-
166-
decl.add_method(
167-
sel!(draggingUpdated:),
168-
dragging_updated as extern "C" fn(&mut Object, Sel, id) -> NSDragOperation,
169-
);
170-
171-
decl.add_method(
172-
sel!(performDragOperation:),
173-
perform_drag_operation as extern "C" fn(&mut Object, Sel, id) -> BOOL,
174-
);
175-
176-
decl.add_method(
177-
sel!(draggingExited:),
178-
dragging_exited as extern "C" fn(&mut Object, Sel, id),
179-
);
180-
}

‎src/wkwebview/ios/WKWebView.rs

+621
Large diffs are not rendered by default.

‎src/wkwebview/ios/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
pub mod WKWebView;

‎src/wkwebview/mod.rs

+365-979
Large diffs are not rendered by default.

‎src/wkwebview/navigation.rs

+81-61
Original file line numberDiff line numberDiff line change
@@ -1,86 +1,106 @@
1-
use std::{
2-
ffi::c_void,
3-
ptr::{null, null_mut},
4-
sync::{Arc, Mutex},
1+
use objc2::DeclaredClass;
2+
use objc2_foundation::{NSObjectProtocol, NSString};
3+
use objc2_web_kit::{
4+
WKNavigation, WKNavigationAction, WKNavigationActionPolicy, WKNavigationResponse,
5+
WKNavigationResponsePolicy,
56
};
67

7-
use cocoa::base::id;
8-
use objc::{
9-
declare::ClassDecl,
10-
runtime::{Object, Sel},
11-
};
8+
#[cfg(target_os = "ios")]
9+
use crate::wkwebview::ios::WKWebView::WKWebView;
10+
#[cfg(target_os = "macos")]
11+
use objc2_web_kit::WKWebView;
1212

13-
use super::{url_from_webview, InnerWebView, NSString};
1413
use crate::PageLoadEvent;
1514

16-
extern "C" fn did_commit_navigation(this: &Object, _: Sel, webview: id, _navigation: id) {
15+
use super::class::wry_navigation_delegate::WryNavigationDelegate;
16+
17+
pub(crate) fn did_commit_navigation(
18+
this: &WryNavigationDelegate,
19+
webview: &WKWebView,
20+
_navigation: &WKNavigation,
21+
) {
1722
unsafe {
1823
// Call on_load_handler
19-
let on_page_load = this.get_ivar::<*mut c_void>("on_page_load_function");
20-
if !on_page_load.is_null() {
21-
let on_page_load = &mut *(*on_page_load as *mut Box<dyn Fn(PageLoadEvent)>);
24+
if let Some(on_page_load) = &this.ivars().on_page_load_handler {
2225
on_page_load(PageLoadEvent::Started);
2326
}
2427

2528
// Inject scripts
26-
let pending_scripts_ptr: *mut c_void = *this.get_ivar("pending_scripts");
27-
let pending_scripts = &(*(pending_scripts_ptr as *mut Arc<Mutex<Option<Vec<String>>>>));
28-
let mut pending_scripts_ = pending_scripts.lock().unwrap();
29-
if let Some(pending_scripts) = &*pending_scripts_ {
30-
for script in pending_scripts {
31-
let _: id = msg_send![webview, evaluateJavaScript:NSString::new(script) completionHandler:null::<*const c_void>()];
29+
let mut pending_scripts = this.ivars().pending_scripts.lock().unwrap();
30+
if let Some(scripts) = &*pending_scripts {
31+
for script in scripts {
32+
webview.evaluateJavaScript_completionHandler(&NSString::from_str(script), None);
3233
}
33-
*pending_scripts_ = None;
34+
*pending_scripts = None;
3435
}
3536
}
3637
}
3738

38-
extern "C" fn did_finish_navigation(this: &Object, _: Sel, _webview: id, _navigation: id) {
39-
unsafe {
40-
// Call on_load_handler
41-
let on_page_load = this.get_ivar::<*mut c_void>("on_page_load_function");
42-
if !on_page_load.is_null() {
43-
let on_page_load = &mut *(*on_page_load as *mut Box<dyn Fn(PageLoadEvent)>);
44-
on_page_load(PageLoadEvent::Finished);
45-
}
39+
pub(crate) fn did_finish_navigation(
40+
this: &WryNavigationDelegate,
41+
_webview: &WKWebView,
42+
_navigation: &WKNavigation,
43+
) {
44+
if let Some(on_page_load) = &this.ivars().on_page_load_handler {
45+
on_page_load(PageLoadEvent::Finished);
4646
}
4747
}
4848

49-
pub(crate) unsafe fn add_navigation_mathods(cls: &mut ClassDecl) {
50-
cls.add_ivar::<*mut c_void>("navigation_policy_function");
51-
cls.add_ivar::<*mut c_void>("on_page_load_function");
52-
53-
cls.add_method(
54-
sel!(webView:didFinishNavigation:),
55-
did_finish_navigation as extern "C" fn(&Object, Sel, id, id),
56-
);
57-
cls.add_method(
58-
sel!(webView:didCommitNavigation:),
59-
did_commit_navigation as extern "C" fn(&Object, Sel, id, id),
60-
);
61-
}
49+
// Navigation handler
50+
pub(crate) fn navigation_policy(
51+
this: &WryNavigationDelegate,
52+
_webview: &WKWebView,
53+
action: &WKNavigationAction,
54+
handler: &block2::Block<dyn Fn(WKNavigationActionPolicy)>,
55+
) {
56+
unsafe {
57+
// shouldPerformDownload is only available on macOS 11.3+
58+
let can_download = action.respondsToSelector(objc2::sel!(shouldPerformDownload));
59+
let should_download: bool = if can_download {
60+
action.shouldPerformDownload()
61+
} else {
62+
false
63+
};
64+
let request = action.request();
65+
let url = request.URL().unwrap().absoluteString().unwrap();
66+
let target_frame = action.targetFrame();
67+
let is_main_frame = target_frame.map_or(false, |frame| frame.isMainFrame());
6268

63-
pub(crate) unsafe fn drop_navigation_methods(inner: &mut InnerWebView) {
64-
if !inner.page_load_handler.is_null() {
65-
drop(Box::from_raw(inner.page_load_handler))
69+
if should_download {
70+
let has_download_handler = this.ivars().has_download_handler;
71+
if has_download_handler {
72+
(*handler).call((WKNavigationActionPolicy::Download,));
73+
} else {
74+
(*handler).call((WKNavigationActionPolicy::Cancel,));
75+
}
76+
} else {
77+
let function = &this.ivars().navigation_policy_function;
78+
match function(url.to_string(), is_main_frame) {
79+
true => (*handler).call((WKNavigationActionPolicy::Allow,)),
80+
false => (*handler).call((WKNavigationActionPolicy::Cancel,)),
81+
};
82+
}
6683
}
6784
}
6885

69-
pub(crate) unsafe fn set_navigation_methods(
70-
navigation_policy_handler: *mut Object,
71-
webview: id,
72-
on_page_load_handler: Option<Box<dyn Fn(PageLoadEvent, String)>>,
73-
) -> *mut Box<dyn Fn(PageLoadEvent)> {
74-
if let Some(on_page_load_handler) = on_page_load_handler {
75-
let on_page_load_handler = Box::into_raw(Box::new(Box::new(move |event| {
76-
on_page_load_handler(event, url_from_webview(webview).unwrap_or_default());
77-
}) as Box<dyn Fn(PageLoadEvent)>));
78-
(*navigation_policy_handler).set_ivar(
79-
"on_page_load_function",
80-
on_page_load_handler as *mut _ as *mut c_void,
81-
);
82-
on_page_load_handler
83-
} else {
84-
null_mut()
86+
// Navigation handler
87+
pub(crate) fn navigation_policy_response(
88+
this: &WryNavigationDelegate,
89+
_webview: &WKWebView,
90+
response: &WKNavigationResponse,
91+
handler: &block2::Block<dyn Fn(WKNavigationResponsePolicy)>,
92+
) {
93+
unsafe {
94+
let can_show_mime_type = response.canShowMIMEType();
95+
96+
if !can_show_mime_type {
97+
let has_download_handler = this.ivars().has_download_handler;
98+
if has_download_handler {
99+
(*handler).call((WKNavigationResponsePolicy::Download,));
100+
return;
101+
}
102+
}
103+
104+
(*handler).call((WKNavigationResponsePolicy::Allow,));
85105
}
86106
}

‎src/wkwebview/proxy.rs

+14-37
Original file line numberDiff line numberDiff line change
@@ -2,46 +2,21 @@
22
// SPDX-License-Identifier: Apache-2.0
33
// SPDX-License-Identifier: MIT
44

5-
use cocoa::base::nil;
6-
use std::ffi::c_char;
5+
use objc2_foundation::NSObject;
6+
use std::ffi::{c_char, CString};
77

88
use crate::{proxy::ProxyEndpoint, Error};
99

10-
use super::NSString;
11-
12-
#[allow(non_camel_case_types)]
13-
pub type nw_endpoint_t = *mut objc::runtime::Object;
1410
#[allow(non_camel_case_types)]
15-
pub type nw_relay_hop_t = *mut objc::runtime::Object;
11+
pub type nw_endpoint_t = *mut NSObject;
1612
#[allow(non_camel_case_types)]
17-
pub type nw_protocol_options_t = *mut objc::runtime::Object;
13+
pub type nw_protocol_options_t = *mut NSObject;
1814
#[allow(non_camel_case_types)]
19-
pub type nw_proxy_config_t = *mut objc::runtime::Object;
15+
pub type nw_proxy_config_t = *mut NSObject;
2016

2117
#[link(name = "Network", kind = "framework")]
2218
extern "C" {
23-
#[allow(dead_code)]
24-
fn nw_endpoint_create_url(url: *const c_char) -> nw_endpoint_t;
25-
#[allow(dead_code)]
26-
fn nw_endpoint_get_url(endpoint: nw_endpoint_t) -> *const c_char;
2719
fn nw_endpoint_create_host(host: *const c_char, port: *const c_char) -> nw_endpoint_t;
28-
#[allow(dead_code)]
29-
fn nw_proxy_config_set_username_and_password(
30-
proxy_config: nw_proxy_config_t,
31-
username: *const c_char,
32-
password: *const c_char,
33-
);
34-
#[allow(dead_code)]
35-
fn nw_relay_hop_create(
36-
http3_relay_endpoint: nw_endpoint_t,
37-
http2_relay_endpoint: nw_endpoint_t,
38-
relay_tls_options: nw_protocol_options_t,
39-
) -> nw_relay_hop_t;
40-
#[allow(dead_code)]
41-
fn nw_proxy_config_create_relay(
42-
first_hop: nw_relay_hop_t,
43-
second_hop: nw_relay_hop_t,
44-
) -> nw_proxy_config_t;
4520
pub fn nw_proxy_config_create_socksv5(proxy_endpoint: nw_endpoint_t) -> nw_proxy_config_t;
4621
pub fn nw_proxy_config_create_http_connect(
4722
proxy_endpoint: nw_endpoint_t,
@@ -53,14 +28,16 @@ impl TryFrom<ProxyEndpoint> for nw_endpoint_t {
5328
type Error = Error;
5429
fn try_from(endpoint: ProxyEndpoint) -> Result<Self, Error> {
5530
unsafe {
56-
let endpoint_host = NSString::new(&endpoint.host).to_cstr();
57-
let endpoint_port = NSString::new(&endpoint.port).to_cstr();
58-
let endpoint = nw_endpoint_create_host(endpoint_host, endpoint_port);
31+
let endpoint_host =
32+
CString::new(endpoint.host).map_err(|_| Error::ProxyEndpointCreationFailed)?;
33+
let endpoint_port =
34+
CString::new(endpoint.port).map_err(|_| Error::ProxyEndpointCreationFailed)?;
35+
let endpoint = nw_endpoint_create_host(endpoint_host.as_ptr(), endpoint_port.as_ptr());
5936

60-
match endpoint {
61-
#[allow(non_upper_case_globals)]
62-
nil => Err(Error::ProxyEndpointCreationFailed),
63-
_ => Ok(endpoint),
37+
if endpoint.is_null() {
38+
Err(Error::ProxyEndpointCreationFailed)
39+
} else {
40+
Ok(endpoint)
6441
}
6542
}
6643
}

‎src/wkwebview/synthetic_mouse_events.rs

+27-36
Original file line numberDiff line numberDiff line change
@@ -1,85 +1,76 @@
1-
use super::NSString;
2-
use cocoa::{
3-
appkit::{NSEvent, NSEventModifierFlags, NSEventType, NSView},
4-
base::{id, nil},
1+
use objc2_app_kit::{
2+
NSAlternateKeyMask, NSCommandKeyMask, NSControlKeyMask, NSEvent, NSEventType, NSShiftKeyMask,
3+
NSView,
54
};
6-
use objc::{
7-
declare::ClassDecl,
8-
runtime::{Object, Sel},
9-
};
10-
use std::{ffi::c_void, ptr::null};
5+
use objc2_foundation::NSString;
116

12-
pub unsafe fn setup(decl: &mut ClassDecl) {
13-
decl.add_method(
14-
sel!(otherMouseDown:),
15-
other_mouse_down as extern "C" fn(&mut Object, Sel, id),
16-
);
17-
decl.add_method(
18-
sel!(otherMouseUp:),
19-
other_mouse_up as extern "C" fn(&mut Object, Sel, id),
20-
);
21-
}
7+
use super::WryWebView;
228

23-
extern "C" fn other_mouse_down(this: &mut Object, _sel: Sel, event: id) {
9+
pub(crate) fn other_mouse_down(this: &WryWebView, event: &NSEvent) {
2410
unsafe {
25-
if event.eventType() == NSEventType::NSOtherMouseDown {
11+
if event.r#type() == NSEventType::OtherMouseDown {
2612
let button_number = event.buttonNumber();
2713
match button_number {
2814
// back button
2915
3 => {
3016
let js = create_js_mouse_event(this, event, true, true);
31-
let _: id = msg_send![this, evaluateJavaScript:NSString::new(&js) completionHandler:null::<*const c_void>()];
17+
this.evaluateJavaScript_completionHandler(&NSString::from_str(&js), None);
3218
return;
3319
}
3420
// forward button
3521
4 => {
3622
let js = create_js_mouse_event(this, event, true, false);
37-
let _: id = msg_send![this, evaluateJavaScript:NSString::new(&js) completionHandler:null::<*const c_void>()];
23+
this.evaluateJavaScript_completionHandler(&NSString::from_str(&js), None);
3824
return;
3925
}
4026
_ => {}
4127
}
4228
}
4329

44-
let _: () = msg_send![this, mouseDown: event];
30+
this.mouseDown(event);
4531
}
4632
}
47-
extern "C" fn other_mouse_up(this: &mut Object, _sel: Sel, event: id) {
33+
pub(crate) fn other_mouse_up(this: &WryWebView, event: &NSEvent) {
4834
unsafe {
49-
if event.eventType() == NSEventType::NSOtherMouseUp {
35+
if event.r#type() == NSEventType::OtherMouseUp {
5036
let button_number = event.buttonNumber();
5137
match button_number {
5238
// back button
5339
3 => {
5440
let js = create_js_mouse_event(this, event, false, true);
55-
let _: id = msg_send![this, evaluateJavaScript:NSString::new(&js) completionHandler:null::<*const c_void>()];
41+
this.evaluateJavaScript_completionHandler(&NSString::from_str(&js), None);
5642
return;
5743
}
5844
// forward button
5945
4 => {
6046
let js = create_js_mouse_event(this, event, false, false);
61-
let _: id = msg_send![this, evaluateJavaScript:NSString::new(&js) completionHandler:null::<*const c_void>()];
47+
this.evaluateJavaScript_completionHandler(&NSString::from_str(&js), None);
6248
return;
6349
}
6450
_ => {}
6551
}
6652
}
6753

68-
let _: () = msg_send![this, mouseUp: event];
54+
this.mouseUp(event);
6955
}
7056
}
7157

72-
unsafe fn create_js_mouse_event(view: id, event: id, down: bool, back_button: bool) -> String {
58+
unsafe fn create_js_mouse_event(
59+
view: &NSView,
60+
event: &NSEvent,
61+
down: bool,
62+
back_button: bool,
63+
) -> String {
7364
let event_name = if down { "mousedown" } else { "mouseup" };
7465
// js equivalent https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/button
7566
let button = if back_button { 3 } else { 4 };
7667
let mods_flags = event.modifierFlags();
7768
let window_point = event.locationInWindow();
78-
let view_point = view.convertPoint_fromView_(window_point, nil);
69+
let view_point = view.convertPoint_fromView(window_point, None);
7970
let x = view_point.x as u32;
8071
let y = view_point.y as u32;
8172
// js equivalent https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/buttons
82-
let buttons = NSEvent::pressedMouseButtons(event);
73+
let buttons = NSEvent::pressedMouseButtons();
8374

8475
format!(
8576
r#"(() => {{
@@ -122,10 +113,10 @@ unsafe fn create_js_mouse_event(view: id, event: id, down: bool, back_button: bo
122113
x = x,
123114
y = y,
124115
detail = event.clickCount(),
125-
ctrl_key = mods_flags.contains(NSEventModifierFlags::NSControlKeyMask),
126-
alt_key = mods_flags.contains(NSEventModifierFlags::NSAlternateKeyMask),
127-
shift_key = mods_flags.contains(NSEventModifierFlags::NSShiftKeyMask),
128-
meta_key = mods_flags.contains(NSEventModifierFlags::NSCommandKeyMask),
116+
ctrl_key = mods_flags.contains(NSControlKeyMask),
117+
alt_key = mods_flags.contains(NSAlternateKeyMask),
118+
shift_key = mods_flags.contains(NSShiftKeyMask),
119+
meta_key = mods_flags.contains(NSCommandKeyMask),
129120
button = button,
130121
buttons = buttons,
131122
)

‎src/wkwebview/util.rs

+9-11
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,14 @@
22
// SPDX-License-Identifier: Apache-2.0
33
// SPDX-License-Identifier: MIT
44

5-
use cocoa::{base::id, foundation::NSOperatingSystemVersion};
5+
use objc2_foundation::NSProcessInfo;
66

7-
pub fn operating_system_version() -> (u64, u64, u64) {
8-
unsafe {
9-
let process_info: id = msg_send![class!(NSProcessInfo), processInfo];
10-
let version: NSOperatingSystemVersion = msg_send![process_info, operatingSystemVersion];
11-
(
12-
version.majorVersion,
13-
version.minorVersion,
14-
version.patchVersion,
15-
)
16-
}
7+
pub fn operating_system_version() -> (isize, isize, isize) {
8+
let process_info = NSProcessInfo::processInfo();
9+
let version = process_info.operatingSystemVersion();
10+
(
11+
version.majorVersion,
12+
version.minorVersion,
13+
version.patchVersion,
14+
)
1715
}

0 commit comments

Comments
 (0)
Please sign in to comment.