Skip to content

Commit

Permalink
feat: Implement navigation event and cancellation, closes #456 (#519)
Browse files Browse the repository at this point in the history
* Modify webcontext to take EventLoopProxy

Adjust relevant structs/functions to take generic arguments due to new
constraint.

* Attempt to make other platforms accept generic webcontext

* Modify with_event_loop proxy to allow different event_loop types

* Fix WebViewBuilder to be able to switch types

* Add event loop proxy getter

* Add getter and setter for naviagtion event constructor callback

* Complete initial implementation of nav events, cancellation

* Move majority implementation into WebView/WebViewData

No longer make WebContext generic over user event type.
Only take single closure, which defines behaviour to execute when
navigation occurs, and also whether to cancel navigation.
If user wishes to submit an event, they should simply move an event
proxy into the event closure and submit an event through it - see
updated example.
WebViewBuilder will now raise return an error if there is no WebContext
to attach callback to.

* Revert changes to Gtk WebContext

* Revert changes to wkwebview

* Extend event to cover new window/popup events

* Move navigation callback into WebViewAttributes

* Remove unnecessary brackets in WebView

Co-authored-by: Amr Bashir <[email protected]>

* Remove new window handler

Implement in a different PR

* Remove references to new window values

* Implement navigation event callback handler on gtk

* Add navigation handler on macOS

The function is still not called yet. But this is a prove it can work.

* Call the navigation handler on macOS

* Update navifation logic and type signature

* Fix errors on windows

Co-authored-by: Amr Bashir <[email protected]>
Co-authored-by: Yu-Wei Wu <[email protected]>
  • Loading branch information
3 people authored Mar 18, 2022
1 parent 6fb9b82 commit aa8af02
Show file tree
Hide file tree
Showing 8 changed files with 178 additions and 4 deletions.
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 @@ -77,6 +77,7 @@ windows_macros = "0.30.0"
]

[target."cfg(any(target_os = \"ios\", target_os = \"macos\"))".dependencies]
block = "0.1"
cocoa = "0.24"
core-graphics = "0.22"
objc = "0.2"
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 @@ -120,6 +120,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 @@ -149,6 +155,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 @@ -306,6 +313,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
23 changes: 23 additions & 0 deletions src/webview/webview2/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,29 @@ window.addEventListener('mousemove', (e) => window.chrome.webview.postMessage('_
}
.map_err(webview2_com::Error::WindowsError)?;

if let Some(nav_callback) = attributes.navigation_handler {
unsafe {
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_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 @@ -312,6 +313,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 @@ -333,6 +380,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 @@ -501,6 +549,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

0 comments on commit aa8af02

Please sign in to comment.