ltk_webkit/wpe/
favicon.rsuse std::ffi::{ c_char, CStr };
use std::ptr;
use std::sync::{ Arc, Mutex, Weak };
use gio::ffi::GAsyncResult;
use glib_sys::{ g_error_free, g_free, gboolean, gpointer, GError };
use gobject_sys::{ g_object_unref, GObject };
use ltk::ImageData;
use wpe_webkit_sys::{ webkit_web_view_call_async_javascript_function, WebKitWebView };
use super::scripting::{ webkit_web_view_call_async_javascript_function_finish, JSCValue };
use super::{ Inner, WebView };
const SIZE: u32 = 64;
const WORLD: &[ u8 ] = b"ltk-webkit-favicon\0";
const SCRIPT: &str = r#"
const link = document.querySelector( 'link[rel~="icon"]' );
const img = new Image();
img.crossOrigin = 'anonymous';
img.src = link ? link.href : new URL( '/favicon.ico', location.href ).href;
await img.decode();
const canvas = document.createElement( 'canvas' );
canvas.width = canvas.height = 64;
const ctx = canvas.getContext( '2d' );
ctx.drawImage( img, 0, 0, 64, 64 );
let hex = '';
for ( const b of ctx.getImageData( 0, 0, 64, 64 ).data ) hex += ( b < 16 ? '0' : '' ) + b.toString( 16 );
return hex;
"#;
extern "C"
{
fn jsc_value_is_string( value: *mut JSCValue ) -> gboolean;
fn jsc_value_to_string( value: *mut JSCValue ) -> *mut c_char;
}
impl WebView
{
pub fn favicon( &self ) -> Option<ImageData>
{
self.inner.lock().ok()?.favicon.clone()
}
pub fn request_favicon( &self )
{
let webview = match self.inner.lock() { Ok( g ) => g.webview, Err( _ ) => return };
if webview.is_null() { return; }
let data = Box::into_raw( Box::new( Arc::downgrade( &self.inner ) ) ) as gpointer;
unsafe
{
webkit_web_view_call_async_javascript_function(
webview,
SCRIPT.as_ptr() as *const c_char,
SCRIPT.len() as isize,
ptr::null_mut(),
WORLD.as_ptr() as *const c_char,
ptr::null(),
ptr::null_mut(),
Some( on_result ),
data,
);
}
}
}
unsafe extern "C" fn on_result( source: *mut GObject, result: *mut GAsyncResult, user_data: gpointer )
{
let inner = unsafe { Box::from_raw( user_data as *mut Weak<Mutex<Inner>> ) };
let mut err: *mut GError = ptr::null_mut();
let icon = unsafe
{
let value = webkit_web_view_call_async_javascript_function_finish( source as *mut WebKitWebView, result, &mut err );
if !err.is_null() { g_error_free( err ); }
take_pixels( value )
};
let Some( inner ) = inner.upgrade() else { return };
if let Ok( mut g ) = inner.lock() { g.favicon = icon; };
}
unsafe fn take_pixels( value: *mut JSCValue ) -> Option<ImageData>
{
if value.is_null() { return None; }
let hex = unsafe
{
let text = if jsc_value_is_string( value ) != 0 { jsc_value_to_string( value ) } else { ptr::null_mut() };
g_object_unref( value as *mut GObject );
if text.is_null() { return None; }
let owned = CStr::from_ptr( text ).to_bytes().to_vec();
g_free( text as gpointer );
owned
};
let rgba = decode_hex( &hex )?;
if rgba.len() != ( SIZE * SIZE * 4 ) as usize { return None; }
Some( ( Arc::new( rgba ), SIZE, SIZE ) )
}
fn decode_hex( hex: &[ u8 ] ) -> Option<Vec<u8>>
{
if hex.len() % 2 != 0 { return None; }
hex.chunks_exact( 2 )
.map( |pair| Some( ( nibble( pair[ 0 ] )? << 4 ) | nibble( pair[ 1 ] )? ) )
.collect()
}
fn nibble( c: u8 ) -> Option<u8>
{
( c as char ).to_digit( 16 ).map( |d| d as u8 )
}
#[ cfg( test ) ]
mod tests
{
use super::*;
#[ test ]
fn hex_decodes_and_rejects_garbage()
{
assert_eq!( decode_hex( b"00ff7f" ), Some( vec![ 0, 255, 127 ] ) );
assert_eq!( decode_hex( b"0" ), None );
assert_eq!( decode_hex( b"zz" ), None );
}
}