Skip to content

Commit

Permalink
feat: Implement new window requested event, closes #527 (#526)
Browse files Browse the repository at this point in the history
* Add handler + setter to webviewattr

* Basic impl for windows

* Add new window request event example

* Implement new window request event for linux

* Attempt WKWebview implementation

* Fix WKWebview implementation

Also fixes incorrect drop impl

* Improve formatting

* Update examples/new_window_req_event.rs

Remove set automation

Co-authored-by: Amr Bashir <amr.bashir2015@gmail.com>

* Improve docs

* Adjust to match changes in dev

* fmt

* changefil

* fix windows

Co-authored-by: Iain Laird <ilaird@expediagroup.com>
Co-authored-by: Amr Bashir <amr.bashir2015@gmail.com>
  • Loading branch information
3 people committed Jun 19, 2022
1 parent 0cb6961 commit fa5456c
Show file tree
Hide file tree
Showing 6 changed files with 165 additions and 30 deletions.
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

0 comments on commit fa5456c

Please sign in to comment.