diff --git a/.changes/new-window-requ-handler.md b/.changes/new-window-requ-handler.md new file mode 100644 index 000000000..1c545a952 --- /dev/null +++ b/.changes/new-window-requ-handler.md @@ -0,0 +1,6 @@ +--- +"wry": minor +--- + +Implement new window requested handler + diff --git a/examples/new_window_req_event.rs b/examples/new_window_req_event.rs new file mode 100644 index 000000000..41c544800 --- /dev/null +++ b/examples/new_window_req_event.rs @@ -0,0 +1,61 @@ +// Copyright 2019-2021 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +fn main() -> wry::Result<()> { + use wry::{ + application::{ + event::{Event, StartCause, WindowEvent}, + event_loop::{ControlFlow, EventLoop}, + window::WindowBuilder, + }, + webview::WebViewBuilder, + }; + + enum UserEvent { + NewWindow(String), + } + + let html = r#" + +
+

WRYYYYYYYYYYYYYYYYYYYYYY!

+ Visit Wikipedia + (Try to) visit GitHub +
+ + "#; + + let event_loop: EventLoop = EventLoop::with_user_event(); + let proxy = event_loop.create_proxy(); + let window = WindowBuilder::new() + .with_title("Hello World") + .build(&event_loop)?; + let webview = WebViewBuilder::new(window)? + .with_html(html)? + .with_new_window_req_handler(move |uri: String| { + let submitted = proxy.send_event(UserEvent::NewWindow(uri.clone())).is_ok(); + + submitted && uri.contains("wikipedia") + }) + .build()?; + + #[cfg(debug_assertions)] + webview.open_devtools(); + + event_loop.run(move |event, _, control_flow| { + *control_flow = ControlFlow::Wait; + + match event { + Event::NewEvents(StartCause::Init) => println!("Wry has started!"), + Event::WindowEvent { + event: WindowEvent::CloseRequested, + .. + } => *control_flow = ControlFlow::Exit, + Event::UserEvent(UserEvent::NewWindow(uri)) => { + println!("New Window: {}", uri); + } + _ => (), + } + }); +} diff --git a/src/webview/mod.rs b/src/webview/mod.rs index 0d6c5aca0..70781bed8 100644 --- a/src/webview/mod.rs +++ b/src/webview/mod.rs @@ -140,6 +140,12 @@ pub struct WebViewAttributes { /// allow to nivagate and false is not. pub navigation_handler: Option bool>>, + /// Set a new window handler to decide if incoming url is allowed to open in a new window. + /// + /// The closure take a `String` parameter as url and return `bool` to determine the url. True is + /// allow to nivagate and false is not. + pub new_window_req_handler: Option bool>>, + /// Enables clipboard access for the page rendered on **Linux** and **Windows**. /// /// macOS doesn't provide such method and is always enabled by default. But you still need to add menu @@ -173,6 +179,7 @@ impl Default for WebViewAttributes { ipc_handler: None, file_drop_handler: None, navigation_handler: None, + new_window_req_handler: None, clipboard: false, devtools: false, zoom_hotkeys_enabled: false, @@ -359,6 +366,19 @@ impl<'a> WebViewBuilder<'a> { self } + /// Set a new window request handler to decide if incoming url is allowed to be opened. + /// + /// The closure takes a `String` parameter as url and return `bool` to determine if the url can be + /// opened in a new window. Returning true will open the url in a new window, whilst returning false + /// will neither open a new window nor allow any navigation. + pub fn with_new_window_req_handler( + mut self, + callback: impl Fn(String) -> bool + 'static, + ) -> Self { + self.webview.new_window_req_handler = Some(Box::new(callback)); + self + } + /// Consume the builder and create the [`WebView`]. /// /// Platform-specific behavior: diff --git a/src/webview/webkitgtk/mod.rs b/src/webview/webkitgtk/mod.rs index d74087afa..02b219c87 100644 --- a/src/webview/webkitgtk/mod.rs +++ b/src/webview/webkitgtk/mod.rs @@ -206,14 +206,20 @@ impl InnerWebView { Inhibit(false) }); - if let Some(nav_handler) = attributes.navigation_handler { + if attributes.navigation_handler.is_some() || attributes.new_window_req_handler.is_some() { webview.connect_decide_policy(move |_webview, policy_decision, policy_type| { - if let PolicyDecisionType::NavigationAction = policy_type { + let handler = match policy_type { + PolicyDecisionType::NavigationAction => &attributes.navigation_handler, + PolicyDecisionType::NewWindowAction => &attributes.new_window_req_handler, + _ => &None, + }; + + if let Some(handler) = handler { if let Some(policy) = policy_decision.dynamic_cast_ref::() { if let Some(nav_action) = policy.navigation_action() { if let Some(uri_req) = nav_action.request() { if let Some(uri) = uri_req.uri() { - let allow = nav_handler(uri.to_string()); + let allow = handler(uri.to_string()); let pointer = policy_decision.as_ptr(); unsafe { if allow { diff --git a/src/webview/webview2/mod.rs b/src/webview/webview2/mod.rs index 48df7b7e3..31f23a124 100644 --- a/src/webview/webview2/mod.rs +++ b/src/webview/webview2/mod.rs @@ -331,6 +331,29 @@ window.addEventListener('mousemove', (e) => window.chrome.webview.postMessage('_ } } + if let Some(new_window_req_handler) = attributes.new_window_req_handler { + unsafe { + webview + .add_NewWindowRequested( + NewWindowRequestedEventHandler::create(Box::new(move |_, args| { + if let Some(args) = args { + let mut uri = PWSTR::default(); + args.Uri(&mut uri)?; + let uri = take_pwstr(uri); + + let allow = new_window_req_handler(uri); + + args.SetHandled(!allow)?; + } + + Ok(()) + })), + &mut token, + ) + .map_err(webview2_com::Error::WindowsError)?; + } + } + let mut custom_protocol_names = HashSet::new(); if !attributes.custom_protocols.is_empty() { for (name, _) in &attributes.custom_protocols { diff --git a/src/webview/wkwebview/mod.rs b/src/webview/wkwebview/mod.rs index 40e614731..c6ce4d667 100644 --- a/src/webview/wkwebview/mod.rs +++ b/src/webview/wkwebview/mod.rs @@ -59,7 +59,7 @@ pub struct InnerWebView { // Note that if following functions signatures are changed in the future, // all fucntions pointer declarations in objc callbacks below all need to get updated. ipc_handler_ptr: *mut (Box, Rc), - nav_handler_ptr: *mut Box bool>, + navigation_decide_policy_ptr: *mut Box bool>, #[cfg(target_os = "macos")] file_drop_ptr: *mut (Box bool>, Rc), protocol_ptrs: Vec<*mut Box Result>>, @@ -318,15 +318,17 @@ impl InnerWebView { let request: id = msg_send![action, request]; let url: id = msg_send![request, URL]; let url: id = msg_send![url, absoluteString]; - let url = NSString(url); + let target_frame: id = msg_send![action, targetFrame]; + let is_main_frame: bool = msg_send![target_frame, isMainFrame]; + let handler = handler as *mut block::Block<(NSInteger,), c_void>; let function = this.get_ivar::<*mut c_void>("function"); if !function.is_null() { - let function = &mut *(*function as *mut Box Fn(String) -> bool>); - match (function)(url.to_str().to_string()) { + let function = &mut *(*function as *mut Box Fn(String, bool) -> bool>); + match (function)(url.to_str().to_string(), is_main_frame) { true => (*handler).call((1,)), false => (*handler).call((0,)), }; @@ -337,28 +339,45 @@ impl InnerWebView { } } - let nav_handler_ptr = if let Some(nav_handler) = attributes.navigation_handler { - let cls = match ClassDecl::new("UIViewController", class!(NSObject)) { - Some(mut cls) => { - cls.add_ivar::<*mut c_void>("function"); - cls.add_method( - sel!(webView:decidePolicyForNavigationAction:decisionHandler:), - navigation_policy as extern "C" fn(&Object, Sel, id, id, id), - ); - cls.register() - } - None => class!(UIViewController), - }; + let navigation_decide_policy_ptr = + if attributes.navigation_handler.is_some() || attributes.new_window_req_handler.is_some() { + let cls = match ClassDecl::new("UIViewController", class!(NSObject)) { + Some(mut cls) => { + cls.add_ivar::<*mut c_void>("function"); + cls.add_method( + sel!(webView:decidePolicyForNavigationAction:decisionHandler:), + navigation_policy as extern "C" fn(&Object, Sel, id, id, id), + ); + cls.register() + } + None => class!(UIViewController), + }; - let handler: id = msg_send![cls, new]; - let nav_handler_ptr = Box::into_raw(Box::new(nav_handler)); - (*handler).set_ivar("function", nav_handler_ptr as *mut _ as *mut c_void); + let handler: id = msg_send![cls, new]; + let function_ptr = { + let navigation_handler = attributes.navigation_handler; + let new_window_req_handler = attributes.new_window_req_handler; + Box::into_raw(Box::new( + Box::new(move |url: String, is_main_frame: bool| -> bool { + if is_main_frame { + navigation_handler + .as_ref() + .map_or(true, |navigation_handler| (navigation_handler)(url)) + } else { + new_window_req_handler + .as_ref() + .map_or(true, |new_window_req_handler| (new_window_req_handler)(url)) + } + }) as Box bool>, + )) + }; + (*handler).set_ivar("function", function_ptr as *mut _ as *mut c_void); - let _: () = msg_send![webview, setNavigationDelegate: handler]; - nav_handler_ptr - } else { - null_mut() - }; + let _: () = msg_send![webview, setNavigationDelegate: handler]; + function_ptr + } else { + null_mut() + }; // File upload panel handler extern "C" fn run_file_upload_panel( @@ -435,7 +454,7 @@ impl InnerWebView { ns_window, manager, ipc_handler_ptr, - nav_handler_ptr, + navigation_decide_policy_ptr, #[cfg(target_os = "macos")] file_drop_ptr, protocol_ptrs, @@ -636,8 +655,8 @@ impl Drop for InnerWebView { let _ = Box::from_raw(self.ipc_handler_ptr); } - if !self.nav_handler_ptr.is_null() { - let _ = Box::from_raw(self.ipc_handler_ptr); + if !self.navigation_decide_policy_ptr.is_null() { + let _ = Box::from_raw(self.navigation_decide_policy_ptr); } #[cfg(target_os = "macos")]