11use std:: collections:: HashMap ;
2- use std:: sync:: atomic:: { AtomicU64 , Ordering } ;
3- use std:: sync:: { Mutex , OnceLock } ;
2+ use std:: sync:: atomic:: { AtomicU64 , AtomicBool , Ordering } ;
3+ use std:: sync:: { Mutex , OnceLock , Arc } ;
44use std:: thread:: ThreadId ;
55
66use wry:: dpi:: { LogicalPosition , LogicalSize } ;
@@ -73,10 +73,25 @@ impl HasWindowHandle for RawWindow {
7373 }
7474}
7575
76- #[ derive( Clone , Copy ) ]
76+ struct WebViewState {
77+ is_loading : AtomicBool ,
78+ current_url : Mutex < String > ,
79+ }
80+
7781struct WebViewEntry {
7882 ptr : * mut WebView ,
7983 thread_id : ThreadId ,
84+ state : Arc < WebViewState > ,
85+ }
86+
87+ impl Clone for WebViewEntry {
88+ fn clone ( & self ) -> Self {
89+ WebViewEntry {
90+ ptr : self . ptr ,
91+ thread_id : self . thread_id ,
92+ state : Arc :: clone ( & self . state ) ,
93+ }
94+ }
8095}
8196
8297// The raw pointer is only dereferenced on the creating thread (checked at runtime).
@@ -299,15 +314,49 @@ fn create_webview_inner(
299314 #[ cfg( target_os = "linux" ) ]
300315 ensure_gtk_initialized ( ) ?;
301316
317+ // Create shared state for tracking loading and URL
318+ let state = Arc :: new ( WebViewState {
319+ is_loading : AtomicBool :: new ( true ) ,
320+ current_url : Mutex :: new ( url. clone ( ) ) ,
321+ } ) ;
322+
323+ let state_for_nav = Arc :: clone ( & state) ;
324+ let state_for_load = Arc :: clone ( & state) ;
325+
302326 let webview = WebViewBuilder :: new ( )
303327 . with_url ( & url)
304328 . with_bounds ( make_bounds ( 0 , 0 , width, height) )
329+ . with_navigation_handler ( move |new_url| {
330+ eprintln ! ( "[wrywebview] navigation_handler url={}" , new_url) ;
331+ // Navigation started
332+ state_for_nav. is_loading . store ( true , Ordering :: SeqCst ) ;
333+ if let Ok ( mut current) = state_for_nav. current_url . lock ( ) {
334+ * current = new_url. clone ( ) ;
335+ }
336+ true // Allow navigation
337+ } )
338+ . with_on_page_load_handler ( move |event, url| {
339+ match event {
340+ wry:: PageLoadEvent :: Started => {
341+ eprintln ! ( "[wrywebview] page_load_handler event=Started url={}" , url) ;
342+ state_for_load. is_loading . store ( true , Ordering :: SeqCst ) ;
343+ }
344+ wry:: PageLoadEvent :: Finished => {
345+ eprintln ! ( "[wrywebview] page_load_handler event=Finished url={}" , url) ;
346+ state_for_load. is_loading . store ( false , Ordering :: SeqCst ) ;
347+ if let Ok ( mut current) = state_for_load. current_url . lock ( ) {
348+ * current = url. clone ( ) ;
349+ }
350+ }
351+ }
352+ } )
305353 . build_as_child ( & window) ?;
306354
307355 let id = NEXT_ID . fetch_add ( 1 , Ordering :: Relaxed ) ;
308356 let entry = WebViewEntry {
309357 ptr : Box :: into_raw ( Box :: new ( webview) ) ,
310358 thread_id : std:: thread:: current ( ) . id ( ) ,
359+ state,
311360 } ;
312361
313362 let mut map = webviews ( )
@@ -379,6 +428,10 @@ pub fn set_bounds(
379428
380429fn load_url_inner ( id : u64 , url : String ) -> Result < ( ) , WebViewError > {
381430 eprintln ! ( "[wrywebview] load_url id={} url={}" , id, url) ;
431+ // Mark as loading before starting navigation
432+ if let Ok ( state) = get_state ( id) {
433+ state. is_loading . store ( true , Ordering :: SeqCst ) ;
434+ }
382435 with_webview ( id, |webview| webview. load_url ( & url) . map_err ( WebViewError :: from) )
383436}
384437
@@ -393,6 +446,9 @@ pub fn load_url(id: u64, url: String) -> Result<(), WebViewError> {
393446
394447fn go_back_inner ( id : u64 ) -> Result < ( ) , WebViewError > {
395448 eprintln ! ( "[wrywebview] go_back id={}" , id) ;
449+ if let Ok ( state) = get_state ( id) {
450+ state. is_loading . store ( true , Ordering :: SeqCst ) ;
451+ }
396452 with_webview ( id, |webview| {
397453 webview. evaluate_script ( "window.history.back()" ) . map_err ( WebViewError :: from)
398454 } )
@@ -409,6 +465,9 @@ pub fn go_back(id: u64) -> Result<(), WebViewError> {
409465
410466fn go_forward_inner ( id : u64 ) -> Result < ( ) , WebViewError > {
411467 eprintln ! ( "[wrywebview] go_forward id={}" , id) ;
468+ if let Ok ( state) = get_state ( id) {
469+ state. is_loading . store ( true , Ordering :: SeqCst ) ;
470+ }
412471 with_webview ( id, |webview| {
413472 webview. evaluate_script ( "window.history.forward()" ) . map_err ( WebViewError :: from)
414473 } )
@@ -425,6 +484,9 @@ pub fn go_forward(id: u64) -> Result<(), WebViewError> {
425484
426485fn reload_inner ( id : u64 ) -> Result < ( ) , WebViewError > {
427486 eprintln ! ( "[wrywebview] reload id={}" , id) ;
487+ if let Ok ( state) = get_state ( id) {
488+ state. is_loading . store ( true , Ordering :: SeqCst ) ;
489+ }
428490 with_webview ( id, |webview| {
429491 webview. evaluate_script ( "window.location.reload()" ) . map_err ( WebViewError :: from)
430492 } )
@@ -439,39 +501,45 @@ pub fn reload(id: u64) -> Result<(), WebViewError> {
439501 run_on_main_thread ( move || reload_inner ( id) )
440502}
441503
442- fn get_url_inner ( id : u64 ) -> Result < String , WebViewError > {
504+ fn focus_inner ( id : u64 ) -> Result < ( ) , WebViewError > {
505+ eprintln ! ( "[wrywebview] focus id={}" , id) ;
443506 with_webview ( id, |webview| {
444- Ok ( webview. url ( ) . map ( |u| u. to_string ( ) ) . unwrap_or_default ( ) )
507+ // Focus the webview by focusing the document
508+ webview. evaluate_script ( "document.documentElement.focus(); window.focus();" ) . map_err ( WebViewError :: from)
445509 } )
446510}
447511
448512#[ uniffi:: export]
449- pub fn get_url ( id : u64 ) -> Result < String , WebViewError > {
513+ pub fn focus ( id : u64 ) -> Result < ( ) , WebViewError > {
450514 #[ cfg( target_os = "linux" ) ]
451515 {
452- return run_on_gtk_thread ( move || get_url_inner ( id) ) ;
516+ return run_on_gtk_thread ( move || focus_inner ( id) ) ;
453517 }
454- run_on_main_thread ( move || get_url_inner ( id) )
518+ run_on_main_thread ( move || focus_inner ( id) )
455519}
456520
457- fn is_loading_inner ( id : u64 ) -> Result < bool , WebViewError > {
458- with_webview ( id, |webview| {
459- // Check if we can get the URL - if we can and it's not about:blank, we're not loading
460- // This is a workaround since evaluate_script doesn't return values synchronously
461- match webview. url ( ) {
462- Ok ( url) => Ok ( url. as_str ( ) == "about:blank" ) ,
463- Err ( _) => Ok ( true ) , // If we can't get URL, assume still loading
464- }
465- } )
521+ fn get_state ( id : u64 ) -> Result < Arc < WebViewState > , WebViewError > {
522+ let map = webviews ( )
523+ . lock ( )
524+ . map_err ( |_| WebViewError :: Internal ( "webview registry lock poisoned" . to_string ( ) ) ) ?;
525+ let entry = map
526+ . get ( & id)
527+ . ok_or ( WebViewError :: WebViewNotFound ( id) ) ?;
528+ Ok ( Arc :: clone ( & entry. state ) )
529+ }
530+
531+ #[ uniffi:: export]
532+ pub fn get_url ( id : u64 ) -> Result < String , WebViewError > {
533+ let state = get_state ( id) ?;
534+ let url = state. current_url . lock ( )
535+ . map_err ( |_| WebViewError :: Internal ( "url lock poisoned" . to_string ( ) ) ) ?;
536+ Ok ( url. clone ( ) )
466537}
467538
468539#[ uniffi:: export]
469540pub fn is_loading ( id : u64 ) -> Result < bool , WebViewError > {
470- #[ cfg( target_os = "linux" ) ]
471- {
472- return run_on_gtk_thread ( move || is_loading_inner ( id) ) ;
473- }
474- run_on_main_thread ( move || is_loading_inner ( id) )
541+ let state = get_state ( id) ?;
542+ Ok ( state. is_loading . load ( Ordering :: SeqCst ) )
475543}
476544
477545fn destroy_webview_inner ( id : u64 ) -> Result < ( ) , WebViewError > {
0 commit comments