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 navigation event and cancellation, closes #456 #519

Merged
merged 20 commits into from
Mar 18, 2022
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
32d41e7
Modify webcontext to take EventLoopProxy
atlanticaccent Mar 5, 2022
4b4ee4d
Attempt to make other platforms accept generic webcontext
atlanticaccent Mar 5, 2022
f6ce724
Modify with_event_loop proxy to allow different event_loop types
atlanticaccent Mar 6, 2022
f296b3d
Fix WebViewBuilder to be able to switch types
atlanticaccent Mar 6, 2022
8f6197e
Add event loop proxy getter
atlanticaccent Mar 6, 2022
a76bca4
Add getter and setter for naviagtion event constructor callback
atlanticaccent Mar 6, 2022
460d896
Complete initial implementation of nav events, cancellation
atlanticaccent Mar 7, 2022
9978b07
Move majority implementation into WebView/WebViewData
atlanticaccent Mar 7, 2022
6ae72f5
Revert changes to Gtk WebContext
atlanticaccent Mar 7, 2022
6893f74
Revert changes to wkwebview
atlanticaccent Mar 7, 2022
f0f5d96
Extend event to cover new window/popup events
atlanticaccent Mar 8, 2022
011e612
Move navigation callback into WebViewAttributes
atlanticaccent Mar 8, 2022
7099b87
Remove unnecessary brackets in WebView
atlanticaccent Mar 8, 2022
8c0f535
Remove new window handler
atlanticaccent Mar 8, 2022
8a5df2f
Remove references to new window values
atlanticaccent Mar 8, 2022
0500e74
Implement navigation event callback handler on gtk
atlanticaccent Mar 9, 2022
e9a0929
Add navigation handler on macOS
Mar 15, 2022
10ce4aa
Call the navigation handler on macOS
Mar 15, 2022
b03231f
Update navifation logic and type signature
Mar 16, 2022
eff4a6b
Fix errors on windows
Mar 16, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changes/nav-handler.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"wry": patch
---

Add navigation handler to decide if a url is allowed to navigate.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ sys-info = "0.9"
]

[target."cfg(any(target_os = \"ios\", target_os = \"macos\"))".dependencies]
block = "0.1"
cocoa = "0.24"
core-graphics = "0.22"
objc = { version = "0.2", features = [ "exception" ] }
Expand Down
51 changes: 51 additions & 0 deletions examples/navigation_event.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// 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 {
Navigation(String),
}

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_url("http://neverssl.com")?
.with_navigation_handler(move |uri: String| {
let submitted = proxy.send_event(UserEvent::Navigation(uri.clone())).is_ok();

submitted && uri.contains("neverssl")
})
.build()?;

#[cfg(debug_assertions)]
webview.devtool();

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::Navigation(uri)) => {
println!("{}", uri);
}
_ => (),
}
});
}
16 changes: 16 additions & 0 deletions src/webview/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,12 @@ pub struct WebViewAttributes {
#[cfg(not(feature = "file-drop"))]
file_drop_handler: Option<Box<dyn Fn(&Window, FileDropEvent) -> bool>>,

/// Set a navigation handler to decide if incoming url is allowed to navigate.
///
/// 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 navigation_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 @@ -139,6 +145,7 @@ impl Default for WebViewAttributes {
custom_protocols: vec![],
ipc_handler: None,
file_drop_handler: None,
navigation_handler: None,
clipboard: false,
devtool: false,
}
Expand Down Expand Up @@ -296,6 +303,15 @@ impl<'a> WebViewBuilder<'a> {
self
}

/// Set a navigation handler to decide if incoming url is allowed to navigate.
///
/// The closure takes a `String` parameter as url and return `bool` to determine the url. True is
/// allowed to nivagate and false is not.
pub fn with_navigation_handler(mut self, callback: impl Fn(String) -> bool + 'static) -> Self {
self.webview.navigation_handler = Some(Box::new(callback));
self
}

/// Consume the builder and create the [`WebView`].
///
/// Platform-specific behavior:
Expand Down
2 changes: 1 addition & 1 deletion src/webview/web_context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ impl Default for WebContext {
}

/// Data that all [`WebContext`] share regardless of platform.
#[derive(Debug, Default)]
#[derive(Default, Debug)]
pub struct WebContextData {
data_directory: Option<PathBuf>,
}
Expand Down
30 changes: 28 additions & 2 deletions src/webview/webkitgtk/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,12 @@ use gio::Cancellable;
use glib::signal::Inhibit;
use gtk::prelude::*;
use webkit2gtk::{
traits::*, UserContentInjectedFrames, UserScript, UserScriptInjectionTime, WebView,
WebViewBuilder,
traits::*, NavigationPolicyDecision, PolicyDecisionType, UserContentInjectedFrames, UserScript,
UserScriptInjectionTime, WebView, WebViewBuilder,
};
use webkit2gtk_sys::{
webkit_get_major_version, webkit_get_micro_version, webkit_get_minor_version,
webkit_policy_decision_ignore, webkit_policy_decision_use,
};

use web_context::WebContextExt;
Expand Down Expand Up @@ -198,6 +199,31 @@ impl InnerWebView {
Inhibit(false)
});

if let Some(nav_handler) = attributes.navigation_handler {
webview.connect_decide_policy(move |_webview, policy_decision, policy_type| {
if let PolicyDecisionType::NavigationAction = policy_type {
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 pointer = policy_decision.as_ptr();
unsafe {
if allow {
webkit_policy_decision_use(pointer)
} else {
webkit_policy_decision_ignore(pointer)
}
}
}
}
}
}
}
true
});
}

// Gtk application window can only contain one widget at a time.
// In tao, we add a GtkBox to pack menu bar. So we check if
// there's a box widget here.
Expand Down
24 changes: 24 additions & 0 deletions src/webview/webview2/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,30 @@ window.addEventListener('mousemove', (e) => window.chrome.webview.postMessage('_
}
.map_err(webview2_com::Error::WindowsError)?;

if let Some(nav_callback) = attributes.navigation_handler {
unsafe {
let nav_starting_callback = nav_callback.clone();
webview
.NavigationStarting(
NavigationStartingEventHandler::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 = nav_starting_callback(uri);

args.SetCancel(!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
54 changes: 53 additions & 1 deletion src/webview/wkwebview/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ use cocoa::{
};
use cocoa::{
base::id,
foundation::{NSDictionary, NSFastEnumeration},
foundation::{NSDictionary, NSFastEnumeration, NSInteger},
};

use std::{
Expand Down Expand Up @@ -62,6 +62,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>,
#[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 @@ -309,6 +310,52 @@ impl InnerWebView {
null_mut()
};

// Navigation handler
extern "C" fn navigation_policy(this: &Object, _: Sel, _: id, action: id, handler: id) {
unsafe {
let request: id = msg_send![action, request];
let url: id = msg_send![request, URL];
let url: id = msg_send![url, absoluteString];
let url = NSString(Id::from_ptr(url));
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()) {
true => (*handler).call((1,)),
false => (*handler).call((0,)),
};
} else {
log::warn!("WebView instance is dropped! This navigation handler shouldn't be called.");
(*handler).call((1,));
}
}
}

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 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 _: () = msg_send![webview, setNavigationDelegate: handler];
nav_handler_ptr
} else {
null_mut()
};

// File drop handling
#[cfg(target_os = "macos")]
let file_drop_ptr = match attributes.file_drop_handler {
Expand All @@ -330,6 +377,7 @@ impl InnerWebView {
ns_window,
manager,
ipc_handler_ptr,
nav_handler_ptr,
#[cfg(target_os = "macos")]
file_drop_ptr,
protocol_ptrs,
Expand Down Expand Up @@ -498,6 +546,10 @@ 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);
}

#[cfg(target_os = "macos")]
if !self.file_drop_ptr.is_null() {
let _ = Box::from_raw(self.file_drop_ptr);
Expand Down