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
88use block2:: Block ;
9+ #[ cfg( target_os = "macos" ) ]
10+ use objc2:: DefinedClass ;
911use 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 } ;
1214use objc2_foundation:: { MainThreadMarker , NSObjectProtocol } ;
1315#[ cfg( target_os = "macos" ) ]
1416use objc2_foundation:: { NSArray , NSURL } ;
@@ -21,7 +23,69 @@ use objc2_web_kit::{
2123
2224use 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
2690define_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
79241impl 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}
0 commit comments