ltk_webkit/
page_scripts.rspub( crate ) const SELECTION_SCRIPT: &str = "(() => {
const e = document.activeElement;
const field = e && ( e.tagName === 'TEXTAREA' || ( e.tagName === 'INPUT' && /^(text|search|url|tel|email|password)$/i.test( e.type ) ) );
if ( field && e.type.toLowerCase() === 'password' ) return '';
return encodeURIComponent( field ? e.value.substring( e.selectionStart, e.selectionEnd ) : String( window.getSelection() ) );
})()";
const PASTE_SCRIPT: &str = "((x, y, text) => {
const target = document.elementFromPoint( x, y );
const editable = target && target.closest( 'textarea, input, [contenteditable]:not([contenteditable=\"false\"])' );
if ( !editable || editable.disabled || editable.readOnly ) return false;
editable.focus();
if ( editable.isContentEditable )
{
const range = document.caretRangeFromPoint( x, y );
if ( range ) { const selection = window.getSelection(); selection.removeAllRanges(); selection.addRange( range ); }
}
return document.execCommand( 'insertText', false, text );
})( {x}, {y}, decodeURIComponent( '{text}' ) )";
pub( crate ) fn paste_script( x: f64, y: f64, text: &str ) -> String
{
PASTE_SCRIPT.replace( "{x}", &x.to_string() ).replace( "{y}", &y.to_string() ).replace( "{text}", &percent_encode( text ) )
}
pub( crate ) fn selection_of( json: &str ) -> String
{
percent_decode( json.trim_matches( '"' ) )
}
pub( crate ) fn percent_encode( text: &str ) -> String
{
let mut out = String::with_capacity( text.len() * 3 );
for byte in text.bytes()
{
if byte.is_ascii_alphanumeric() || matches!( byte, b'-' | b'_' | b'.' ) { out.push( byte as char ); } else { out.push_str( &format!( "%{byte:02X}" ) ); }
}
out
}
pub( crate ) fn percent_decode( text: &str ) -> String
{
let bytes = text.as_bytes();
let mut out = Vec::with_capacity( bytes.len() );
let mut i = 0;
while i < bytes.len()
{
let hex = if bytes[ i ] == b'%' && i + 2 < bytes.len()
{
std::str::from_utf8( &bytes[ i + 1..i + 3 ] ).ok().and_then( |h| u8::from_str_radix( h, 16 ).ok() )
} else {
None
};
match hex
{
Some( byte ) => { out.push( byte ); i += 3; }
None => { out.push( bytes[ i ] ); i += 1; }
}
}
String::from_utf8_lossy( &out ).into_owned()
}
#[ cfg( test ) ]
mod tests
{
use super::*;
#[ test ]
fn percent_round_trip()
{
for text in [ "plain", "a b", "comillas ' \" y \\ barra", "áé ñ €", "%41", "" ]
{
let encoded = percent_encode( text );
assert!( !encoded.contains( '\'' ) && !encoded.contains( '"' ) && !encoded.contains( '\\' ) );
assert_eq!( percent_decode( &encoded ), text );
}
}
#[ test ]
fn a_lone_percent_survives()
{
assert_eq!( percent_decode( "100%" ), "100%" );
assert_eq!( percent_decode( "%4" ), "%4" );
}
#[ test ]
fn paste_script_carries_the_point_and_the_text()
{
let script = paste_script( 10.5, 20.0, "a'b" );
assert!( script.ends_with( "( 10.5, 20, decodeURIComponent( 'a%27b' ) )" ) );
}
}