Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Implement new window requested event, closes #527 #526

Merged
Merged
6 changes: 6 additions & 0 deletions .changes/new-window-requ-handler.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"wry": minor
---

Implement new window requested handler

61 changes: 61 additions & 0 deletions examples/new_window_req_event.rs
Original file line number Diff line number Diff line change
@@ -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#"
<body>
<div>
<p> WRYYYYYYYYYYYYYYYYYYYYYY! </p>
<a href="https://www.wikipedia.org" target="_blank">Visit Wikipedia</a>
<a href="https://www.github.com" target="_blank">(Try to) visit GitHub</a>
</div>
</body>
"#;

let event_loop: EventLoop<UserEvent> = 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);
}
_ => (),
}
});
}
20 changes: 20 additions & 0 deletions src/webview/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,12 @@ pub struct WebViewAttributes {
/// allow to nivagate and false is not.
pub navigation_handler: Option<Box<dyn Fn(String) -> 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<Box<dyn Fn(String) -> 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
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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:
Expand Down
12 changes: 9 additions & 3 deletions src/webview/webkitgtk/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::<NavigationPolicyDecision>() {
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 {
Expand Down
23 changes: 23 additions & 0 deletions src/webview/webview2/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
73 changes: 46 additions & 27 deletions src/webview/wkwebview/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<dyn Fn(&Window, String)>, Rc<Window>),
nav_handler_ptr: *mut Box<dyn Fn(String) -> bool>,
navigation_decide_policy_ptr: *mut Box<dyn Fn(String, bool) -> bool>,
#[cfg(target_os = "macos")]
file_drop_ptr: *mut (Box<dyn Fn(&Window, FileDropEvent) -> bool>, Rc<Window>),
protocol_ptrs: Vec<*mut Box<dyn Fn(&HttpRequest) -> Result<HttpResponse>>>,
Expand Down Expand Up @@ -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<dyn for<'s> Fn(String) -> bool>);
match (function)(url.to_str().to_string()) {
let function = &mut *(*function as *mut Box<dyn for<'s> Fn(String, bool) -> bool>);
match (function)(url.to_str().to_string(), is_main_frame) {
true => (*handler).call((1,)),
false => (*handler).call((0,)),
};
Expand All @@ -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<dyn Fn(String, bool) -> 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(
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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")]
Expand Down