diff --git a/apps/browser/src/document_loader.rs b/apps/browser/src/document_loader.rs index 4613d6107..f0b1820d0 100644 --- a/apps/browser/src/document_loader.rs +++ b/apps/browser/src/document_loader.rs @@ -1,12 +1,8 @@ -use std::sync::{ - Arc, - atomic::{AtomicUsize, Ordering}, -}; +use std::sync::Arc; use blitz_dom::{DocumentConfig, FontContext}; use blitz_html::{HtmlDocument, HtmlProvider}; use blitz_traits::{net::Request, shell::ShellProvider}; -use dioxus_core::Task; use dioxus_native::{SubDocumentAttr, prelude::*}; use linebender_resource_handle::Blob; @@ -14,19 +10,23 @@ use crate::StdNetProvider; use crate::history::{BrowserNavProvider, History, SyncStore}; pub enum DocumentLoaderStatus { - Loading { request_id: usize, task: Task }, + Loading, Idle, } +#[derive(Clone)] +pub struct LoadedDocument { + pub document: SubDocumentAttr, + pub html_source: String, + pub title: String, +} + pub struct DocumentLoader { pub font_ctx: FontContext, pub net_provider: Arc, pub status: Signal, - pub request_id_counter: AtomicUsize, - pub doc: Signal>, pub history: SyncStore, - pub html_source: Signal, - pub title: Signal, + pub reload_generation: Signal, } pub fn make_doc_config( @@ -49,12 +49,7 @@ pub fn make_doc_config( } impl DocumentLoader { - pub fn new( - net_provider: Arc, - history: SyncStore, - html_source: Signal, - title: Signal, - ) -> Self { + pub fn new(net_provider: Arc, history: SyncStore) -> Self { let mut font_ctx = FontContext::default(); font_ctx .collection @@ -64,19 +59,22 @@ impl DocumentLoader { font_ctx, net_provider, status: Signal::new(DocumentLoaderStatus::Idle), - request_id_counter: AtomicUsize::new(0), - doc: Signal::new(None), history, - html_source, - title, + reload_generation: Signal::new(0), } } - pub fn load_document(&self, req: Request) { + pub fn reload(&self) { + let mut reload_generation = self.reload_generation; + *reload_generation.write() += 1; + } + + pub fn reload_generation(&self) -> u64 { + *self.reload_generation.read() + } + + pub async fn load_document(&self, req: Request) -> LoadedDocument { if req.url.scheme() == "about" && req.url.path() == "newtab" { - if let DocumentLoaderStatus::Loading { task, .. } = *self.status.peek() { - task.cancel(); - } let config = make_doc_config( None, Arc::clone(&self.net_provider), @@ -84,88 +82,65 @@ impl DocumentLoader { self.font_ctx.clone(), ); let html = include_str!("../assets/start.html"); - *self.html_source.write_unchecked() = html.to_string(); let document = HtmlDocument::from_html(html, config).into_inner(); - *self.title.write_unchecked() = String::new(); - *self.doc.write_unchecked() = Some(SubDocumentAttr::new(document)); - *self.status.write_unchecked() = DocumentLoaderStatus::Idle; - return; + return LoadedDocument { + document: SubDocumentAttr::new(document), + html_source: html.to_string(), + title: String::new(), + }; } - let request_id = self.request_id_counter.fetch_add(1, Ordering::Relaxed); let net_provider = Arc::clone(&self.net_provider); let font_ctx = self.font_ctx.clone(); - let status = self.status; - let doc_signal = self.doc; let history = self.history; - let html_source = self.html_source; - let title = self.title; - - if let DocumentLoaderStatus::Loading { task, .. } = *self.status.peek() { - task.cancel(); - }; - - let task = spawn(async move { - let response = net_provider.fetch_async(req).await; - - // Discard response if a newer navigation has started - if let DocumentLoaderStatus::Loading { - request_id: stored_id, - .. - } = *status.peek() - { - if request_id != stored_id { - tracing::debug!("Ignoring stale navigation response (id {request_id})"); - return; + + let response = net_provider.fetch_async(req).await; + + match response { + Ok((resolved_url, bytes)) => { + tracing::info!("Loaded {}", resolved_url); + let config = make_doc_config(Some(resolved_url), net_provider, history, font_ctx); + + let bytes_str; + let html: &str = if bytes.is_empty() { + include_str!("../assets/404.html") + } else { + bytes_str = String::from_utf8_lossy(&bytes); + &bytes_str + }; + + let document = HtmlDocument::from_html(html, config).into_inner(); + let parsed_title = document + .find_title_node() + .map(|n| n.text_content()) + .unwrap_or_default(); + LoadedDocument { + document: SubDocumentAttr::new(document), + html_source: html.to_string(), + title: parsed_title, } } - - match response { - Ok((resolved_url, bytes)) => { - tracing::info!("Loaded {}", resolved_url); - let config = - make_doc_config(Some(resolved_url), net_provider, history, font_ctx); - - let bytes_str; - let html: &str = if bytes.is_empty() { - include_str!("../assets/404.html") - } else { - bytes_str = String::from_utf8_lossy(&bytes); - &bytes_str - }; - - *html_source.write_unchecked() = html.to_string(); - - let document = HtmlDocument::from_html(html, config).into_inner(); - let parsed_title = document - .find_title_node() - .map(|n| n.text_content()) - .unwrap_or_default(); - *title.write_unchecked() = parsed_title; - *doc_signal.write_unchecked() = Some(SubDocumentAttr::new(document)); + Err(err) => { + tracing::error!("Error loading document: {:?}", err); + + let error_msg = format!("{err:?}"); + let config = make_doc_config(None, net_provider, history, font_ctx); + + let error_html = include_str!("../assets/error.html"); + let mut document = HtmlDocument::from_html(error_html, config).into_inner(); + if let Some(text_node) = document + .get_element_by_id("error") + .and_then(|el| document.get_node(el)) + .and_then(|node| node.children.first().copied()) + { + document.mutate().set_node_text(text_node, &error_msg); } - Err(err) => { - tracing::error!("Error loading document: {:?}", err); - - let error_msg = format!("{err:?}"); - let config = make_doc_config(None, net_provider, history, font_ctx); - - let error_html = include_str!("../assets/error.html"); - let mut document = HtmlDocument::from_html(error_html, config).into_inner(); - if let Some(text_node) = document - .get_element_by_id("error") - .and_then(|el| document.get_node(el)) - .and_then(|node| node.children.first().copied()) - { - document.mutate().set_node_text(text_node, &error_msg); - } - *title.write_unchecked() = String::new(); - *doc_signal.write_unchecked() = Some(SubDocumentAttr::new(document)); + LoadedDocument { + document: SubDocumentAttr::new(document), + html_source: error_html.to_string(), + title: String::new(), } } - *status.write_unchecked() = DocumentLoaderStatus::Idle; - }); - - *status.write_unchecked() = DocumentLoaderStatus::Loading { request_id, task }; + } } } diff --git a/apps/browser/src/history.rs b/apps/browser/src/history.rs index c44600d92..9dd344b26 100644 --- a/apps/browser/src/history.rs +++ b/apps/browser/src/history.rs @@ -1,5 +1,5 @@ use blitz_traits::navigation::{NavigationOptions, NavigationProvider}; -use blitz_traits::net::{Request, Url}; +use blitz_traits::net::Request; use dioxus_native::prelude::*; pub type SyncStore = Store>; @@ -11,9 +11,9 @@ pub struct History { } impl History { - pub fn new(initial_url: Url) -> Self { + pub fn new(initial_request: Request) -> Self { Self { - urls: vec![Request::get(initial_url)], + urls: vec![initial_request], current: 0, } } diff --git a/apps/browser/src/main.rs b/apps/browser/src/main.rs index b6d66ba4b..c67b0845e 100644 --- a/apps/browser/src/main.rs +++ b/apps/browser/src/main.rs @@ -30,33 +30,11 @@ mod tab; mod tab_strip; mod toolbar; -use history::HistoryNav; use status_bar::StatusBar; -use tab::{Tab, TabId, active_tab, tab_title_or_url}; +use tab::{Tab, TabId, TabStoreImplExt, TabWebView, active_tab, open_tab, tab_title_or_url}; use tab_strip::TabStrip; use toolbar::Toolbar; -#[component] -fn TabView(tab: Tab, is_active: bool) -> Element { - use_effect(move || { - let request = (*tab.history.current_url().read()).clone(); - tab.loader.load_document(request); - }); - - let mut tab_node_handle = tab.node_handle; - rsx!( - web-view { - class: "webview", - style: if is_active { "display: block" } else { "display: none" }, - "__webview_document": tab.document.cloned(), - onmounted: move |evt: Event| { - let node_handle = evt.downcast::().unwrap(); - *tab_node_handle.write() = Some(node_handle.clone()); - }, - } - ) -} - static BROWSER_UI_STYLES: Asset = asset!("../assets/browser.css"); pub(crate) const IS_MOBILE: bool = cfg!(any(target_os = "android", target_os = "ios")); pub(crate) const HOME_URL_STR: &str = "about:newtab"; @@ -91,16 +69,15 @@ fn app() -> Element { let url_input_handle: Signal> = use_signal(|| None); let url_input_value = use_signal(|| home_url.to_string()); - let mut tabs: Signal> = - use_hook(|| Signal::new(vec![Tab::new(home_url.clone(), net_provider.clone())])); - let mut active_tab_id: Signal = - use_hook(|| Signal::new(tabs.read().first().map(|t| t.id).unwrap_or(0))); + let tabs: Store> = use_store(Vec::new); + let mut active_tab_id: Signal = use_hook(|| { + let tab = open_tab(tabs, home_url.clone(), net_provider.clone()); + Signal::new(tab.tab_id()) + }); let open_new_tab = use_callback(move |url: Url| { - let new_tab = Tab::new(url, net_provider.clone()); - let new_id = new_tab.id; - tabs.write().push(new_tab); - active_tab_id.set(new_id); + let new_id = open_tab(tabs, url, net_provider.clone()); + active_tab_id.set(new_id.tab_id()); if let Some(handle) = url_input_handle() { drop(handle.set_focus(true)); } @@ -128,7 +105,7 @@ fn app() -> Element { #[cfg(not(feature = "vello"))] let fps_overlay_el = rsx!(); - let window_title = tab_title_or_url(&active_tab(&tabs, active_tab_id())); + let window_title = tab_title_or_url(active_tab(tabs, active_tab_id())); rsx!( div { @@ -151,13 +128,11 @@ fn app() -> Element { active_tab_id, show_fps, } - for tab in tabs() { - { - let id = tab.id; - let is_active = id == active_tab_id(); - rsx!( - TabView { key: "{id}", tab, is_active } - ) + for tab in tabs.iter() { + TabWebView { + key: "{tab.tab_id()}", + tab, + active_tab_id, } } {fps_overlay_el} diff --git a/apps/browser/src/status_bar.rs b/apps/browser/src/status_bar.rs index 015a460e7..820c5e3b1 100644 --- a/apps/browser/src/status_bar.rs +++ b/apps/browser/src/status_bar.rs @@ -4,10 +4,13 @@ use dioxus_native::prelude::*; use crate::document_loader::DocumentLoaderStatus; use crate::history::HistoryNav; -use crate::tab::{Tab, TabId, active_tab}; +use crate::tab::{Tab, TabId, TabStoreExt, TabStoreImplExt, active_tab}; -fn hovered_href(tab: &Tab) -> Option { - let nh = tab.node_handle.peek(); +fn hovered_href(tab: Store) -> Option +where + L: Copy + Readable + 'static, +{ + let nh = tab.node_handle().peek_unchecked(); let handle = (*nh).as_ref()?; let node_id = handle.node_id(); // Skip this cycle if the event loop currently holds a mutable borrow. @@ -34,7 +37,7 @@ fn hovered_href(tab: &Tab) -> Option { } #[component] -pub fn StatusBar(tabs: Signal>, active_tab_id: Signal) -> Element { +pub fn StatusBar(tabs: Store>, active_tab_id: Signal) -> Element { let mut hover_url: Signal = use_signal(String::new); // Hover state lives inside blitz-dom's BaseDocument, not a Dioxus signal, @@ -44,13 +47,13 @@ pub fn StatusBar(tabs: Signal>, active_tab_id: Signal) -> Elemen loop { tokio::time::sleep(Duration::from_millis(100)).await; - let tab = active_tab(&tabs, *active_tab_id.peek()); - let raw_href = hovered_href(&tab); + let tab = active_tab(tabs, active_tab_id()); + let raw_href = hovered_href(tab); // All doc borrows dropped here; safe to read history. let found = match raw_href { None => String::new(), Some(raw) => { - let base = tab.history.current_url().read().url.clone(); + let base = tab.nav_history().current_url().read().url.clone(); base.join(&raw).map(|u| u.to_string()).unwrap_or(raw) } }; @@ -61,10 +64,10 @@ pub fn StatusBar(tabs: Signal>, active_tab_id: Signal) -> Elemen }); }); - let tab = active_tab(&tabs, active_tab_id()); + let tab = active_tab(tabs, active_tab_id()); let is_loading = matches!( - *tab.loader.status.read(), - DocumentLoaderStatus::Loading { .. } + *tab.loader_rc().status.read(), + DocumentLoaderStatus::Loading ); let status_text = { @@ -72,7 +75,7 @@ pub fn StatusBar(tabs: Signal>, active_tab_id: Signal) -> Elemen if !hov.is_empty() { hov.clone() } else if is_loading { - format!("Loading {}…", tab.history.current_url().read().url) + format!("Loading {}…", tab.nav_history().current_url().read().url) } else { String::new() } diff --git a/apps/browser/src/tab.rs b/apps/browser/src/tab.rs index 055fe3482..772b08144 100644 --- a/apps/browser/src/tab.rs +++ b/apps/browser/src/tab.rs @@ -6,11 +6,11 @@ use std::{ }, }; -use blitz_traits::net::Url; +use blitz_traits::net::{Request, Url}; use dioxus_native::{NodeHandle, SubDocumentAttr, prelude::*}; use crate::StdNetProvider; -use crate::document_loader::DocumentLoader; +use crate::document_loader::{DocumentLoader, DocumentLoaderStatus, LoadedDocument}; use crate::history::{History, HistoryNav, SyncStore}; pub type TabId = u64; @@ -21,62 +21,147 @@ pub fn next_tab_id() -> TabId { TAB_ID_COUNTER.fetch_add(1, Ordering::Relaxed) } -#[derive(Clone)] +#[derive(Store)] pub struct Tab { pub id: TabId, pub history: SyncStore, - pub loader: Rc, - pub document: Signal>, - pub node_handle: Signal>, - pub html_source: Signal, - pub title: Signal, + pub loader: Option>, + pub document: Option, + pub node_handle: Option, + pub html_source: String, + pub title: String, } -impl PartialEq for Tab { - fn eq(&self, other: &Self) -> bool { - self.id == other.id +#[store(pub)] +impl Store { + fn nav_history(&self) -> SyncStore { + *self.history().read() } -} -impl Tab { - pub fn new(url: Url, net_provider: Arc) -> Self { - let id = next_tab_id(); - let history: SyncStore = Store::new_maybe_sync(History::new(url)); - let html_source: Signal = Signal::new(String::new()); - let title: Signal = Signal::new(String::new()); - let loader = Rc::new(DocumentLoader::new( - net_provider, - history, - html_source, - title, - )); - let document = loader.doc; - Tab { - id, - history, - loader, - document, - node_handle: Signal::new(None), - html_source, - title, - } + fn loader_rc(&self) -> Rc { + self.loader().cloned().expect("loader uninitialized") + } + + fn tab_id(&self) -> TabId { + *self.id().read() + } + + fn navigate(&self, req: Request) { + self.nav_history().navigate(req); + } + + fn reload(&self) { + self.loader_rc().reload(); } + + fn go_back(&self) { + self.nav_history().go_back(); + } + + fn go_forward(&self) { + self.nav_history().go_forward(); + } + + fn apply_loaded_document(&self, loaded: LoadedDocument) + where + Lens: Writable, + { + *self.html_source().write_unchecked() = loaded.html_source; + *self.title().write_unchecked() = loaded.title; + *self.document().write_unchecked() = Some(loaded.document); + } +} + +pub fn open_tab( + mut tabs: Store>, + url: Url, + net_provider: Arc, +) -> Store + Copy> { + let id = next_tab_id(); + let initial_request = Request::get(url); + let history: SyncStore = Store::new_maybe_sync(History::new(initial_request.clone())); + + tabs.push(Tab { + id, + history, + loader: None, + document: None, + node_handle: None, + html_source: String::new(), + title: String::new(), + }); + + let len = tabs.len(); + let tab_lens = tabs.get(len - 1).expect("just pushed"); + + let loader = Rc::new(DocumentLoader::new(net_provider, history)); + + *tab_lens.loader().write() = Some(loader.clone()); + + tab_lens } -pub fn active_tab(tabs: &Signal>, active_id: TabId) -> Tab { - let tabs_ref = tabs.read(); - tabs_ref - .iter() - .find(|t| t.id == active_id) - .or_else(|| tabs_ref.first()) +pub fn active_tab(tabs: Store>, active_id: TabId) -> Store { + tabs.iter() + .find(|tab| tab.tab_id() == active_id) .expect("tabs vec is never empty") - .clone() + .into() +} + +#[component] +pub fn TabWebView(tab: Store, active_tab_id: Signal) -> Element { + let loader = tab.loader_rc(); + let loaded_document = use_resource(move || { + let req = (*tab.nav_history().current_url().read()).clone(); + let _reload_generation = loader.reload_generation(); + let loader = loader.clone(); + async move { loader.load_document(req).await } + }); + + use_effect(move || { + let loader = tab.loader_rc(); + let mut status = loader.status; + match loaded_document.state().cloned() { + UseResourceState::Pending => status.set(DocumentLoaderStatus::Loading), + UseResourceState::Ready | UseResourceState::Stopped | UseResourceState::Paused => { + status.set(DocumentLoaderStatus::Idle) + } + } + }); + + use_effect(move || { + if loaded_document.read().is_some() { + if let Some(loaded) = loaded_document.write_unchecked().take() { + tab.apply_loaded_document(loaded); + } + } + }); + + let id = tab.tab_id(); + let document = tab.document().cloned(); + let mut node_handle_lens = tab.node_handle(); + + rsx!( + web-view { + key: "{id}", + class: "webview", + style: if id == active_tab_id() { "display: block" } else { "display: none" }, + "__webview_document": document, + onmounted: move |evt: Event| { + let node_handle = evt.downcast::().unwrap(); + node_handle_lens.set(Some(node_handle.clone())); + }, + } + ) } -pub fn tab_title_or_url(tab: &Tab) -> String { - let title = tab.title.read(); +pub fn tab_title_or_url(tab: Store) -> String +where + L: Copy + Readable + 'static, +{ + let title = tab.title().cloned(); if !title.trim().is_empty() { - return title.clone(); + return title; } - tab.history.current_url().read().url.to_string() + tab.nav_history().current_url().read().url.to_string() } diff --git a/apps/browser/src/tab_strip.rs b/apps/browser/src/tab_strip.rs index cfd405207..adf29c7ad 100644 --- a/apps/browser/src/tab_strip.rs +++ b/apps/browser/src/tab_strip.rs @@ -1,7 +1,7 @@ use blitz_traits::net::Url; use dioxus_native::prelude::*; -use crate::tab::{Tab, TabId, tab_title_or_url}; +use crate::tab::{Tab, TabId, TabStoreImplExt, tab_title_or_url}; #[cfg(target_os = "macos")] const TABSTRIP_CLASS: &str = "tabstrip merged-titlebar"; @@ -10,7 +10,7 @@ const TABSTRIP_CLASS: &str = "tabstrip"; #[component] pub fn TabStrip( - tabs: Signal>, + mut tabs: Store>, active_tab_id: Signal, home_url: Url, open_new_tab: Callback, @@ -20,32 +20,30 @@ pub fn TabStrip( }); let close_tab = use_callback(move |id: TabId| { - let mut tabs_w = tabs.write(); - let current_active = *active_tab_id.peek(); - let idx = tabs_w.iter().position(|t| t.id == id).unwrap_or(0); - tabs_w.remove(idx); + let current_active = active_tab_id(); + let idx = tabs.iter().position(|tab| tab.tab_id() == id).unwrap_or(0); + let len_after = tabs.len() - 1; + tabs.remove(idx); if current_active == id { - let new_idx = if idx < tabs_w.len() { + let new_idx = if idx < len_after { idx } else { - tabs_w.len().saturating_sub(1) + len_after.saturating_sub(1) }; - if let Some(t) = tabs_w.get(new_idx) { - let new_id = t.id; - drop(tabs_w); + if let Some(new_id) = tabs.get(new_idx).map(|tab| tab.tab_id()) { active_tab_id.set(new_id); } } }); + let tab_count = tabs.len(); rsx!( - div { class: TABSTRIP_CLASS, - for tab in tabs() { + div { class:TABSTRIP_CLASS, + for tab_lens in tabs.iter() { { - let is_active = tab.id == active_tab_id(); - let tab_id = tab.id; - let title = tab_title_or_url(&tab); - let tab_count = tabs.read().len(); + let tab_id = tab_lens.tab_id(); + let is_active = tab_id == active_tab_id(); + let title = tab_title_or_url(tab_lens); rsx!( div { key: "{tab_id}", diff --git a/apps/browser/src/toolbar.rs b/apps/browser/src/toolbar.rs index 0b9a970c7..0ae662923 100644 --- a/apps/browser/src/toolbar.rs +++ b/apps/browser/src/toolbar.rs @@ -8,14 +8,14 @@ use dioxus_native::{NodeHandle, SubDocumentAttr, prelude::*}; use crate::history::HistoryNav; use crate::icons::{self, IconButton}; -use crate::tab::{Tab, TabId, active_tab}; +use crate::tab::{Tab, TabId, TabStoreExt, TabStoreImplExt, active_tab}; use crate::{HOME_URL_STR, IS_MOBILE, StdNetProvider}; #[component] pub fn Toolbar( url_input_handle: Signal>, mut url_input_value: Signal, - tabs: Signal>, + tabs: Store>, active_tab_id: Signal, mut show_fps: Signal, ) -> Element { @@ -29,16 +29,15 @@ pub fn Toolbar( // Sync URL bar when active tab changes use_effect(move || { let aid = active_tab_id(); - let tab = active_tab(&tabs, aid); - *url_input_value.write_unchecked() = tab.history.current_url().read().url.to_string(); + let tab = active_tab(tabs, aid); + *url_input_value.write_unchecked() = tab.nav_history().current_url().read().url.to_string(); }); - let load_current_url = use_callback(move |_| { - let tab = active_tab(&tabs, *active_tab_id.peek()); - let request = (*tab.history.current_url().read()).clone(); - *url_input_value.write_unchecked() = request.url.to_string(); - - if let Some(handle) = &*tab.node_handle.peek() { + let clear_document_focus = use_callback(move |_| { + let tab = active_tab(tabs, active_tab_id()); + let nh_lens = tab.node_handle(); + let nh_guard = nh_lens.peek(); + if let Some(handle) = (*nh_guard).as_ref() { let node_id = handle.node_id(); let mut doc = handle.doc_mut(); if let Some(sub_doc) = doc @@ -49,30 +48,29 @@ pub fn Toolbar( sub_doc.inner_mut().clear_focus(); } } - - tracing::info!("Loading {}", request.url.as_str()); - tab.loader.load_document(request); }); let back_action = use_callback(move |_| { - active_tab(&tabs, *active_tab_id.peek()).history.go_back(); + clear_document_focus(()); + active_tab(tabs, active_tab_id()).go_back(); }); let forward_action = use_callback(move |_| { - active_tab(&tabs, *active_tab_id.peek()) - .history - .go_forward(); + clear_document_focus(()); + active_tab(tabs, active_tab_id()).go_forward(); }); let home_action = use_callback(move |_| { - active_tab(&tabs, *active_tab_id.peek()) - .history - .navigate(Request::get(home_url.clone())); + clear_document_focus(()); + active_tab(tabs, active_tab_id()).navigate(Request::get(home_url.clone())); + }); + let refresh_action = use_callback(move |_| { + clear_document_focus(()); + active_tab(tabs, active_tab_id()).reload(); }); - let refresh_action = load_current_url; let open_action = use_callback(move |_| { menu_open.set(false); open_in_external_browser( - &active_tab(&tabs, *active_tab_id.peek()) - .history + &active_tab(tabs, active_tab_id()) + .nav_history() .current_url() .read(), ); @@ -80,12 +78,12 @@ pub fn Toolbar( let view_source_action = use_callback(move |_| { menu_open.set(false); - let tab = active_tab(&tabs, *active_tab_id.peek()); - let source = tab.html_source.read().clone(); + let tab = active_tab(tabs, active_tab_id()); + let source = tab.html_source().cloned(); if source.is_empty() { return; } - let current_url = tab.history.current_url().read().url.to_string(); + let current_url = tab.nav_history().current_url().read().url.to_string(); *url_input_value.write() = format!("view-source://{current_url}"); let view_source_html = include_str!("../assets/view-source.html"); @@ -97,7 +95,7 @@ pub fn Toolbar( navigation_provider: None, shell_provider: None, html_parser_provider: Some(Arc::new(HtmlProvider)), - font_ctx: Some(tab.loader.font_ctx.clone()), + font_ctx: Some(tab.loader_rc().font_ctx.clone()), media_type: None, }; let mut document = HtmlDocument::from_html(view_source_html, config).into_inner(); @@ -106,18 +104,18 @@ pub fn Toolbar( let text_node = mutator.create_text_node(&source); mutator.append_children(parent_id, &[text_node]); } - *tab.document.write_unchecked() = Some(SubDocumentAttr::new(document)); + *tab.document().write_unchecked() = Some(SubDocumentAttr::new(document)); }); #[cfg(feature = "screenshot")] let screenshot_action = use_callback(move |_| { menu_open.set(false); - let node_handle = active_tab(&tabs, *active_tab_id.peek()).node_handle; + let node_handle = active_tab(tabs, active_tab_id()).node_handle(); async move { let Some(path) = crate::capture::try_get_save_path("PNG Image", "png").await else { return; }; - if let Some(handle) = node_handle() { + if let Some(handle) = node_handle.cloned() { let node_id = handle.node_id(); let mut doc = handle.doc_mut(); if let Some(sub_doc) = doc @@ -134,13 +132,13 @@ pub fn Toolbar( #[cfg(feature = "capture")] let capture_action = use_callback(move |_| { menu_open.set(false); - let node_handle = active_tab(&tabs, *active_tab_id.peek()).node_handle; + let node_handle = active_tab(tabs, active_tab_id()).node_handle(); async move { let Some(path) = crate::capture::try_get_save_path("AnyRender Scene", "scene").await else { return; }; - if let Some(handle) = node_handle() { + if let Some(handle) = node_handle.cloned() { let node_id = handle.node_id(); let mut doc = handle.doc_mut(); if let Some(sub_doc) = doc @@ -156,8 +154,8 @@ pub fn Toolbar( let devtools_action = use_callback(move |_| { menu_open.set(false); - let node_handle = active_tab(&tabs, *active_tab_id.peek()).node_handle; - if let Some(handle) = node_handle() { + let node_handle = active_tab(tabs, active_tab_id()).node_handle(); + if let Some(handle) = node_handle.cloned() { let node_id = handle.node_id(); let mut doc = handle.doc_mut(); if let Some(sub_doc) = doc @@ -216,9 +214,9 @@ pub fn Toolbar( #[cfg(not(feature = "cache"))] let clear_cache_item = rsx!(); - let current_tab = active_tab(&tabs, active_tab_id()); - let has_back = current_tab.history.has_back(); - let has_forward = current_tab.history.has_forward(); + let current_tab = active_tab(tabs, active_tab_id()); + let has_back = current_tab.nav_history().has_back(); + let has_forward = current_tab.nav_history().has_forward(); rsx!( div { class: "urlbar", @@ -290,7 +288,8 @@ pub fn Toolbar( } let req = req_from_string(&url_input_value.read()); if let Some(req) = req { - active_tab(&tabs, *active_tab_id.peek()).history.navigate(req); + clear_document_focus(()); + active_tab(tabs, active_tab_id()).navigate(req); } else { tracing::warn!("Error parsing URL {}", &*url_input_value.read()); }