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#"
+
+
+
+ "#;
+
+ 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")]