Skip to content

Commit eb562ca

Browse files
authored
fix(macos): new_window_req not firing on window.open (#1596)
* fix(macos): new_window_req not firing on window.open currently the new_window_req_handler is fired on navigation events, which is incorrect as it calls the handler on iframe navigations and doesn't call it on window.open calls, not matching behavior on other platforms this PR changes it to run under [`webView:createWebViewWithConfiguration:forNavigationAction:windowFeatures:`](https://developer.apple.com/documentation/webkit/wkuidelegate/webview(_:createwebviewwith:for:windowfeatures:)?language=objc) instead I've noticed this when testing tauri-apps/tauri#13876 * fix build * fix coordinates * fix features
1 parent 66c91aa commit eb562ca

File tree

8 files changed

+203
-22
lines changed

8 files changed

+203
-22
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"wry": patch
3+
---
4+
5+
Do not fire `new_window_req_handler` on navigation events on macOS and iOS.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"wry": patch
3+
---
4+
5+
Fixes `new_window_req_handler` not fired on macOS when executing `window.open()`.

Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@ objc2-web-kit = { version = "0.3.0", default-features = false, features = [
129129
"WKNavigationResponse",
130130
"WKUserScript",
131131
"WKHTTPCookieStore",
132+
"WKWindowFeatures",
132133
] }
133134
objc2-core-foundation = { version = "0.3.0", default-features = false, features = [
134135
"std",
@@ -186,6 +187,8 @@ objc2-app-kit = { version = "0.3.0", default-features = false, features = [
186187
"NSOpenPanel",
187188
"NSSavePanel",
188189
"NSMenu",
190+
"NSGraphics",
191+
"NSScreen",
189192
] }
190193

191194
[target."cfg(target_os = \"android\")".dependencies]

examples/simple.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ fn main() -> wry::Result<()> {
1515

1616
let builder = WebViewBuilder::new()
1717
.with_url("http://tauri.app")
18+
.with_new_window_req_handler(|url| {
19+
println!("new window req: {url}");
20+
true
21+
})
1822
.with_drag_drop_handler(|e| {
1923
match e {
2024
wry::DragDropEvent::Enter { paths, position } => {

src/wkwebview/class/wry_navigation_delegate.rs

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ use super::wry_download_delegate::WryDownloadDelegate;
3232
pub struct WryNavigationDelegateIvars {
3333
pub pending_scripts: Arc<Mutex<Option<Vec<String>>>>,
3434
pub has_download_handler: bool,
35-
pub navigation_policy_function: Box<dyn Fn(String, bool) -> bool>,
35+
pub navigation_policy_function: Box<dyn Fn(String) -> bool>,
3636
pub download_delegate: Option<Retained<WryDownloadDelegate>>,
3737
pub on_page_load_handler: Option<Box<dyn Fn(PageLoadEvent)>>,
3838
}
@@ -106,21 +106,14 @@ impl WryNavigationDelegate {
106106
pending_scripts: Arc<Mutex<Option<Vec<String>>>>,
107107
has_download_handler: bool,
108108
navigation_handler: Option<Box<dyn Fn(String) -> bool>>,
109-
new_window_req_handler: Option<Box<dyn Fn(String) -> bool>>,
110109
download_delegate: Option<Retained<WryDownloadDelegate>>,
111110
on_page_load_handler: Option<Box<dyn Fn(PageLoadEvent, String)>>,
112111
mtm: MainThreadMarker,
113112
) -> Retained<Self> {
114-
let navigation_policy_function = Box::new(move |url: String, is_main_frame: bool| -> bool {
115-
if is_main_frame {
116-
navigation_handler
117-
.as_ref()
118-
.map_or(true, |navigation_handler| (navigation_handler)(url))
119-
} else {
120-
new_window_req_handler
121-
.as_ref()
122-
.map_or(true, |new_window_req_handler| (new_window_req_handler)(url))
123-
}
113+
let navigation_policy_function = Box::new(move |url: String| -> bool {
114+
navigation_handler
115+
.as_ref()
116+
.map_or(true, |navigation_handler| (navigation_handler)(url))
124117
});
125118

126119
let on_page_load_handler = if let Some(handler) = on_page_load_handler {

src/wkwebview/class/wry_web_view_ui_delegate.rs

Lines changed: 178 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,14 @@
33
// SPDX-License-Identifier: MIT
44

55
#[cfg(target_os = "macos")]
6-
use std::ptr::null_mut;
6+
use std::{cell::RefCell, ptr::null_mut, sync::Arc};
77

88
use block2::Block;
9+
#[cfg(target_os = "macos")]
10+
use objc2::DefinedClass;
911
use objc2::{define_class, msg_send, rc::Retained, runtime::NSObject, MainThreadOnly};
1012
#[cfg(target_os = "macos")]
11-
use objc2_app_kit::{NSModalResponse, NSModalResponseOK, NSOpenPanel};
13+
use objc2_app_kit::{NSModalResponse, NSModalResponseOK, NSOpenPanel, NSWindowDelegate};
1214
use objc2_foundation::{MainThreadMarker, NSObjectProtocol};
1315
#[cfg(target_os = "macos")]
1416
use objc2_foundation::{NSArray, NSURL};
@@ -21,7 +23,69 @@ use objc2_web_kit::{
2123

2224
use crate::WryWebView;
2325

24-
pub struct WryWebViewUIDelegateIvars {}
26+
#[cfg(target_os = "macos")]
27+
struct NewWindow {
28+
#[allow(dead_code)]
29+
ns_window: Retained<objc2_app_kit::NSWindow>,
30+
#[allow(dead_code)]
31+
webview: Retained<objc2_web_kit::WKWebView>,
32+
#[allow(dead_code)]
33+
delegate: Retained<WryNSWindowDelegate>,
34+
}
35+
36+
// SAFETY: we are not using the new window at all, just dropping it on another thread
37+
#[cfg(target_os = "macos")]
38+
unsafe impl Send for NewWindow {}
39+
40+
#[cfg(target_os = "macos")]
41+
impl Drop for NewWindow {
42+
fn drop(&mut self) {
43+
unsafe {
44+
self.webview.removeFromSuperview();
45+
}
46+
}
47+
}
48+
49+
#[cfg(target_os = "macos")]
50+
struct WryNSWindowDelegateIvars {
51+
on_close: Box<dyn Fn()>,
52+
}
53+
54+
#[cfg(target_os = "macos")]
55+
define_class!(
56+
#[unsafe(super(NSObject))]
57+
#[name = "WryNSWindowDelegate"]
58+
#[thread_kind = MainThreadOnly]
59+
#[ivars = WryNSWindowDelegateIvars]
60+
struct WryNSWindowDelegate;
61+
62+
unsafe impl NSObjectProtocol for WryNSWindowDelegate {}
63+
64+
unsafe impl NSWindowDelegate for WryNSWindowDelegate {
65+
#[unsafe(method(windowWillClose:))]
66+
unsafe fn will_close(&self, _notification: &objc2_foundation::NSNotification) {
67+
let on_close = &self.ivars().on_close;
68+
on_close();
69+
}
70+
}
71+
);
72+
73+
#[cfg(target_os = "macos")]
74+
impl WryNSWindowDelegate {
75+
pub fn new(mtm: MainThreadMarker, on_close: Box<dyn Fn()>) -> Retained<Self> {
76+
let delegate = mtm
77+
.alloc::<WryNSWindowDelegate>()
78+
.set_ivars(WryNSWindowDelegateIvars { on_close });
79+
unsafe { msg_send![super(delegate), init] }
80+
}
81+
}
82+
83+
pub struct WryWebViewUIDelegateIvars {
84+
#[cfg(target_os = "macos")]
85+
new_window_req_handler: Option<Box<dyn Fn(String) -> bool>>,
86+
#[cfg(target_os = "macos")]
87+
new_windows: Arc<RefCell<Vec<NewWindow>>>,
88+
}
2589

2690
define_class!(
2791
#[unsafe(super(NSObject))]
@@ -73,14 +137,123 @@ define_class!(
73137
//https://developer.apple.com/documentation/webkit/wkpermissiondecision?language=objc
74138
(*decision_handler).call((WKPermissionDecision::Grant,));
75139
}
140+
141+
#[cfg(target_os = "macos")]
142+
#[unsafe(method_id(webView:createWebViewWithConfiguration:forNavigationAction:windowFeatures:))]
143+
unsafe fn create_web_view_for_navigation_action(
144+
&self,
145+
webview: &WryWebView,
146+
configuration: &objc2_web_kit::WKWebViewConfiguration,
147+
action: &objc2_web_kit::WKNavigationAction,
148+
window_features: &objc2_web_kit::WKWindowFeatures,
149+
) -> Option<Retained<objc2_web_kit::WKWebView>> {
150+
if let Some(new_window_req_handler) = &self.ivars().new_window_req_handler {
151+
let request = action.request();
152+
let url = request.URL().unwrap().absoluteString().unwrap();
153+
let create = new_window_req_handler(url.to_string());
154+
if create {
155+
let mtm = MainThreadMarker::new().unwrap();
156+
157+
let current_window = webview.window().unwrap();
158+
let screen = current_window.screen().unwrap();
159+
let screen_frame = screen.frame();
160+
let defaults = current_window.frame();
161+
let size = objc2_foundation::NSSize::new(
162+
window_features
163+
.width()
164+
.map_or(defaults.size.width, |width| width.doubleValue()),
165+
window_features
166+
.height()
167+
.map_or(defaults.size.height, |height| height.doubleValue()),
168+
);
169+
let position = objc2_foundation::NSPoint::new(
170+
window_features
171+
.x()
172+
.map_or(defaults.origin.x, |x| x.doubleValue()),
173+
window_features.y().map_or(defaults.origin.y, |y| {
174+
screen_frame.size.height - y.doubleValue() - size.height
175+
}),
176+
);
177+
let rect = objc2_foundation::NSRect::new(position, size);
178+
179+
let mut flags = objc2_app_kit::NSWindowStyleMask::Titled
180+
| objc2_app_kit::NSWindowStyleMask::Closable
181+
| objc2_app_kit::NSWindowStyleMask::Miniaturizable;
182+
let resizable = window_features
183+
.allowsResizing()
184+
.map_or(true, |resizable| resizable.boolValue());
185+
if resizable {
186+
flags |= objc2_app_kit::NSWindowStyleMask::Resizable;
187+
}
188+
189+
let window = objc2_app_kit::NSWindow::initWithContentRect_styleMask_backing_defer(
190+
mtm.alloc::<objc2_app_kit::NSWindow>(),
191+
rect,
192+
flags,
193+
objc2_app_kit::NSBackingStoreType::Buffered,
194+
false,
195+
);
196+
197+
// SAFETY: Disable auto-release when closing windows.
198+
// This is required when creating `NSWindow` outside a window
199+
// controller.
200+
window.setReleasedWhenClosed(false);
201+
202+
let webview = objc2_web_kit::WKWebView::initWithFrame_configuration(
203+
mtm.alloc::<objc2_web_kit::WKWebView>(),
204+
window.frame(),
205+
configuration,
206+
);
207+
208+
let new_windows = self.ivars().new_windows.clone();
209+
let window_id = Retained::as_ptr(&window) as usize;
210+
let delegate = WryNSWindowDelegate::new(
211+
mtm,
212+
Box::new(move || {
213+
let new_windows = new_windows.clone();
214+
new_windows
215+
.borrow_mut()
216+
.retain(|window| Retained::as_ptr(&window.ns_window) as usize != window_id);
217+
}),
218+
);
219+
window.setDelegate(Some(objc2::runtime::ProtocolObject::from_ref(&*delegate)));
220+
221+
window.setContentView(Some(&webview));
222+
window.makeKeyAndOrderFront(None);
223+
224+
self.ivars().new_windows.borrow_mut().push(NewWindow {
225+
ns_window: window,
226+
webview: webview.clone(),
227+
delegate,
228+
});
229+
230+
Some(webview)
231+
} else {
232+
None
233+
}
234+
} else {
235+
None
236+
}
237+
}
76238
}
77239
);
78240

79241
impl WryWebViewUIDelegate {
80-
pub fn new(mtm: MainThreadMarker) -> Retained<Self> {
242+
pub fn new(
243+
mtm: MainThreadMarker,
244+
new_window_req_handler: Option<Box<dyn Fn(String) -> bool>>,
245+
) -> Retained<Self> {
246+
#[cfg(target_os = "ios")]
247+
let _new_window_req_handler = new_window_req_handler;
248+
81249
let delegate = mtm
82250
.alloc::<WryWebViewUIDelegate>()
83-
.set_ivars(WryWebViewUIDelegateIvars {});
251+
.set_ivars(WryWebViewUIDelegateIvars {
252+
#[cfg(target_os = "macos")]
253+
new_window_req_handler,
254+
#[cfg(target_os = "macos")]
255+
new_windows: Arc::new(RefCell::new(vec![])),
256+
});
84257
unsafe { msg_send![super(delegate), init] }
85258
}
86259
}

src/wkwebview/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -515,7 +515,6 @@ impl InnerWebView {
515515
pending_scripts.clone(),
516516
has_download_handler,
517517
attributes.navigation_handler,
518-
attributes.new_window_req_handler,
519518
download_delegate.clone(),
520519
attributes.on_page_load_handler,
521520
mtm,
@@ -524,7 +523,8 @@ impl InnerWebView {
524523
let proto_navigation_policy_delegate = ProtocolObject::from_ref(&*navigation_policy_delegate);
525524
webview.setNavigationDelegate(Some(proto_navigation_policy_delegate));
526525

527-
let ui_delegate: Retained<WryWebViewUIDelegate> = WryWebViewUIDelegate::new(mtm);
526+
let ui_delegate: Retained<WryWebViewUIDelegate> =
527+
WryWebViewUIDelegate::new(mtm, attributes.new_window_req_handler);
528528
let proto_ui_delegate = ProtocolObject::from_ref(&*ui_delegate);
529529
webview.setUIDelegate(Some(proto_ui_delegate));
530530

src/wkwebview/navigation.rs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,6 @@ pub(crate) fn navigation_policy(
6363
};
6464
let request = action.request();
6565
let url = request.URL().unwrap().absoluteString().unwrap();
66-
let target_frame = action.targetFrame();
67-
let is_main_frame = target_frame.is_some_and(|frame| frame.isMainFrame());
6866

6967
if should_download {
7068
let has_download_handler = this.ivars().has_download_handler;
@@ -75,7 +73,7 @@ pub(crate) fn navigation_policy(
7573
}
7674
} else {
7775
let function = &this.ivars().navigation_policy_function;
78-
match function(url.to_string(), is_main_frame) {
76+
match function(url.to_string()) {
7977
true => (*handler).call((WKNavigationActionPolicy::Allow,)),
8078
false => (*handler).call((WKNavigationActionPolicy::Cancel,)),
8179
};

0 commit comments

Comments
 (0)