use std::cell::RefCell;
use std::ffi::c_char;
use std::rc::Rc;
use std::sync::{ Arc, Mutex, Weak };
use glib_sys::{ gboolean, gpointer, GError };
use gobject_sys::{ g_signal_connect_data, GObject };
use wpe_webkit_sys::*;
use super::browsing::owned;
use super::{ popups, Inner, WebView };
use crate::types::{ LoadError, LoadEvent, Navigation, NavigationKind };
pub( crate ) type NavigationHandler = Rc<RefCell<Box<dyn FnMut( &Navigation ) -> bool>>>;
type LoadChangedFn = unsafe extern "C" fn( *mut WebKitWebView, WebKitLoadEvent, gpointer );
type LoadFailedFn = unsafe extern "C" fn( *mut WebKitWebView, WebKitLoadEvent, *const c_char, *mut GError, gpointer ) -> gboolean;
type TlsFailedFn = unsafe extern "C" fn( *mut WebKitWebView, *const c_char, gpointer, u32, gpointer ) -> gboolean;
impl WebView
{
pub fn set_navigation_handler( &self, handler: impl FnMut( &Navigation ) -> bool + 'static )
{
if let Ok( mut g ) = self.inner.lock() { g.navigation_handler = Some( Rc::new( RefCell::new( Box::new( handler ) ) ) ); }
}
pub fn take_load_events( &self ) -> Vec<LoadEvent>
{
self.inner.lock().map( |mut g| std::mem::take( &mut g.load_events ) ).unwrap_or_default()
}
pub( crate ) fn mark_host_load( &self )
{
if let Ok( mut g ) = self.inner.lock() { g.host_load_pending = true; }
}
}
pub( crate ) unsafe fn connect( webview: *mut WebKitWebView, inner: Weak<Mutex<Inner>> )
{
unsafe
{
let changed = std::mem::transmute::<LoadChangedFn, unsafe extern "C" fn()>( on_load_changed );
let failed = std::mem::transmute::<LoadFailedFn, unsafe extern "C" fn()>( on_load_failed );
let tls = std::mem::transmute::<TlsFailedFn, unsafe extern "C" fn()>( on_tls_failed );
for ( signal, handler ) in [ ( &b"load-changed\0"[ .. ], changed ), ( &b"load-failed\0"[ .. ], failed ), ( &b"load-failed-with-tls-errors\0"[ .. ], tls ) ]
{
let data = Box::into_raw( Box::new( inner.clone() ) ) as gpointer;
g_signal_connect_data( webview as *mut GObject, signal.as_ptr() as *const c_char, Some( handler ), data, Some( popups::drop_weak ), 0 );
}
}
}
pub( crate ) unsafe fn decide( decision: *mut WebKitPolicyDecision, inner: &Arc<Mutex<Inner>> ) -> gboolean
{
let Some( mut navigation ) = ( unsafe { describe( decision as *mut WebKitNavigationPolicyDecision ) } ) else { return 0 };
let handler = match inner.lock()
{
Ok( mut g ) =>
{
navigation.host_initiated = std::mem::take( &mut g.host_load_pending ) && !navigation.redirect;
g.navigation_handler.clone()
}
Err( _ ) => return 0,
};
let Some( handler ) = handler else { return 0 };
let Ok( mut handler ) = handler.try_borrow_mut() else { return 0 };
if ( *handler )( &navigation ) { return 0; }
unsafe { webkit_policy_decision_ignore( decision ); }
1
}
unsafe fn describe( decision: *mut WebKitNavigationPolicyDecision ) -> Option<Navigation>
{
unsafe
{
let action = webkit_navigation_policy_decision_get_navigation_action( decision );
if action.is_null() { return None; }
let request = webkit_navigation_action_get_request( action );
if request.is_null() { return None; }
let kind = match webkit_navigation_action_get_navigation_type( action )
{
WEBKIT_NAVIGATION_TYPE_LINK_CLICKED => NavigationKind::Link,
WEBKIT_NAVIGATION_TYPE_FORM_SUBMITTED => NavigationKind::FormSubmission,
WEBKIT_NAVIGATION_TYPE_BACK_FORWARD => NavigationKind::BackForward,
WEBKIT_NAVIGATION_TYPE_RELOAD => NavigationKind::Reload,
WEBKIT_NAVIGATION_TYPE_FORM_RESUBMITTED => NavigationKind::FormResubmission,
_ => NavigationKind::Other,
};
let method = owned( webkit_uri_request_get_http_method( request ) );
Some( Navigation
{
uri: owned( webkit_uri_request_get_uri( request ) ),
method: if method.is_empty() { "GET".into() } else { method },
kind,
user_gesture: webkit_navigation_action_is_user_gesture( action ) != 0,
redirect: webkit_navigation_action_is_redirect( action ) != 0,
host_initiated: false,
} )
}
}
fn push( user_data: gpointer, event: LoadEvent )
{
let Some( inner ) = unsafe { &*( user_data as *const Weak<Mutex<Inner>> ) }.upgrade() else { return };
if let Ok( mut g ) = inner.lock()
{
if matches!( event, LoadEvent::Started( _ ) ) { g.host_load_pending = false; }
g.load_events.push( event );
};
}
unsafe extern "C" fn on_load_changed( webview: *mut WebKitWebView, event: WebKitLoadEvent, user_data: gpointer )
{
let uri = owned( unsafe { webkit_web_view_get_uri( webview ) } );
let event = match event
{
WEBKIT_LOAD_STARTED => LoadEvent::Started( uri ),
WEBKIT_LOAD_REDIRECTED => LoadEvent::Redirected( uri ),
WEBKIT_LOAD_COMMITTED => LoadEvent::Committed( uri ),
_ => LoadEvent::Finished( uri ),
};
push( user_data, event );
}
unsafe extern "C" fn on_load_failed( _webview: *mut WebKitWebView, _event: WebKitLoadEvent, uri: *const c_char, error: *mut GError, user_data: gpointer ) -> gboolean
{
let ( error, message ) = match unsafe { error.as_ref() }
{
Some( e ) => ( classify( e ), owned( e.message ) ),
None => ( LoadError::Other, String::new() ),
};
push( user_data, LoadEvent::Failed { uri: owned( uri ), error, message } );
0
}
unsafe extern "C" fn on_tls_failed( _webview: *mut WebKitWebView, uri: *const c_char, _certificate: gpointer, _errors: u32, user_data: gpointer ) -> gboolean
{
push( user_data, LoadEvent::Failed { uri: owned( uri ), error: LoadError::Tls, message: "TLS certificate not trusted".into() } );
0
}
fn classify( e: &GError ) -> LoadError
{
let ( network, policy ) = unsafe { ( webkit_network_error_quark(), webkit_policy_error_quark() ) };
if e.domain == network
{
return match e.code
{
WEBKIT_NETWORK_ERROR_CANCELLED => LoadError::Cancelled,
WEBKIT_NETWORK_ERROR_UNKNOWN_PROTOCOL => LoadError::UnsupportedScheme,
WEBKIT_NETWORK_ERROR_FILE_DOES_NOT_EXIST => LoadError::FileNotFound,
_ => LoadError::Other,
};
}
if e.domain == policy && e.code == WEBKIT_POLICY_ERROR_FRAME_LOAD_INTERRUPTED_BY_POLICY_CHANGE
{
return LoadError::Cancelled;
}
LoadError::Other
}