3
3
// SPDX-License-Identifier: MIT
4
4
5
5
#[ cfg( target_os = "macos" ) ]
6
- use std:: ptr:: null_mut;
6
+ use std:: { cell :: RefCell , ptr:: null_mut, sync :: Arc } ;
7
7
8
8
use block2:: Block ;
9
+ #[ cfg( target_os = "macos" ) ]
10
+ use objc2:: DefinedClass ;
9
11
use objc2:: { define_class, msg_send, rc:: Retained , runtime:: NSObject , MainThreadOnly } ;
10
12
#[ cfg( target_os = "macos" ) ]
11
- use objc2_app_kit:: { NSModalResponse , NSModalResponseOK , NSOpenPanel } ;
13
+ use objc2_app_kit:: { NSModalResponse , NSModalResponseOK , NSOpenPanel , NSWindowDelegate } ;
12
14
use objc2_foundation:: { MainThreadMarker , NSObjectProtocol } ;
13
15
#[ cfg( target_os = "macos" ) ]
14
16
use objc2_foundation:: { NSArray , NSURL } ;
@@ -21,7 +23,69 @@ use objc2_web_kit::{
21
23
22
24
use crate :: WryWebView ;
23
25
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
+ }
25
89
26
90
define_class ! (
27
91
#[ unsafe ( super ( NSObject ) ) ]
@@ -73,14 +137,123 @@ define_class!(
73
137
//https://developer.apple.com/documentation/webkit/wkpermissiondecision?language=objc
74
138
( * decision_handler) . call( ( WKPermissionDecision :: Grant , ) ) ;
75
139
}
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
+ }
76
238
}
77
239
) ;
78
240
79
241
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
+
81
249
let delegate = mtm
82
250
. 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
+ } ) ;
84
257
unsafe { msg_send ! [ super ( delegate) , init] }
85
258
}
86
259
}
0 commit comments