diff --git a/crates/ely_app/src/shell/internal_pages/history.rs b/crates/ely_app/src/shell/internal_pages/history.rs index bfd8df4..330bfc6 100644 --- a/crates/ely_app/src/shell/internal_pages/history.rs +++ b/crates/ely_app/src/shell/internal_pages/history.rs @@ -445,6 +445,7 @@ mod tests { tab.id().clone(), "example.com", UrlText::parse("https://example.com/research")?, + None, UNIX_EPOCH, ); @@ -473,6 +474,7 @@ mod tests { tab.id().clone(), "example.com", UrlText::parse("https://example.com/closed")?, + None, UNIX_EPOCH, ); let archived_tab = ArchivedTab::new(tab, ArchiveSource::ManualClose); diff --git a/crates/ely_browser_core/src/state/history.rs b/crates/ely_browser_core/src/state/history.rs index 7664a29..b1881af 100644 --- a/crates/ely_browser_core/src/state/history.rs +++ b/crates/ely_browser_core/src/state/history.rs @@ -44,7 +44,7 @@ impl BrowserCore { && entry.space_id() == tab.space_id() && entry.url() == tab.url() }) { - entry.record_visit(tab.id().clone(), tab.title(), visited_at); + entry.record_visit(tab.id().clone(), tab.title(), tab.favicon_key(), visited_at); return; } @@ -54,10 +54,36 @@ impl BrowserCore { tab.id().clone(), tab.title(), tab.url().clone(), + tab.favicon_key().map(ToOwned::to_owned), visited_at, )); } + pub(super) fn set_history_favicon_key_for_tab( + &mut self, + tab: &BrowserTab, + favicon_key: impl Into, + ) { + let favicon_key = favicon_key.into(); + if let Some(entry) = self.history_entries.iter_mut().find(|entry| { + entry.profile_id() == tab.profile_id() + && entry.space_id() == tab.space_id() + && entry.url() == tab.url() + }) { + entry.set_favicon_key(favicon_key); + } + } + + pub(super) fn clear_history_favicon_key_for_tab(&mut self, tab: &BrowserTab) { + if let Some(entry) = self.history_entries.iter_mut().find(|entry| { + entry.profile_id() == tab.profile_id() + && entry.space_id() == tab.space_id() + && entry.url() == tab.url() + }) { + entry.clear_favicon_key(); + } + } + pub(super) fn find_history_match(&self, query: &str) -> Option { let normalized_query = query.trim().to_lowercase(); if normalized_query.is_empty() { diff --git a/crates/ely_browser_core/src/state/tabs.rs b/crates/ely_browser_core/src/state/tabs.rs index 6ce909f..697b924 100644 --- a/crates/ely_browser_core/src/state/tabs.rs +++ b/crates/ely_browser_core/src/state/tabs.rs @@ -279,22 +279,27 @@ impl BrowserCore { tab_id: &TabId, favicon_key: impl Into, ) -> Result<(), CoreError> { - let tab = self + let favicon_key = favicon_key.into(); + let tab_index = self .tabs - .iter_mut() - .find(|tab| tab.id() == tab_id) + .iter() + .position(|tab| tab.id() == tab_id) .ok_or_else(|| CoreError::TabNotFound { id: tab_id.clone() })?; - tab.set_favicon_key(favicon_key)?; + self.tabs[tab_index].set_favicon_key(favicon_key.clone())?; + let tab = self.tabs[tab_index].clone(); + self.set_history_favicon_key_for_tab(&tab, favicon_key); Ok(()) } pub fn clear_tab_favicon_key(&mut self, tab_id: &TabId) -> Result<(), CoreError> { - let tab = self + let tab_index = self .tabs - .iter_mut() - .find(|tab| tab.id() == tab_id) + .iter() + .position(|tab| tab.id() == tab_id) .ok_or_else(|| CoreError::TabNotFound { id: tab_id.clone() })?; - tab.clear_favicon_key(); + self.tabs[tab_index].clear_favicon_key(); + let tab = self.tabs[tab_index].clone(); + self.clear_history_favicon_key_for_tab(&tab); Ok(()) } diff --git a/crates/ely_browser_core/tests/history.rs b/crates/ely_browser_core/tests/history.rs index 6227bb0..bcfe2b0 100644 --- a/crates/ely_browser_core/tests/history.rs +++ b/crates/ely_browser_core/tests/history.rs @@ -25,6 +25,31 @@ fn navigation_records_profile_and_space_history() -> Result<(), Box> Ok(()) } +#[test] +fn history_entry_updates_favicon_key_after_navigation() -> Result<(), Box> { + let mut core = BrowserCore::new(InitialBrowserConfig::ely_defaults()?)?; + let tab_id = core.open_tab(UrlText::parse("https://example.com/research")?); + + core.set_tab_favicon_key(&tab_id, "favicons/example.ico")?; + let snapshot = core.snapshot()?; + + assert_eq!(snapshot.history_entries[0].favicon_key(), Some("favicons/example.ico")); + Ok(()) +} + +#[test] +fn history_entry_clears_favicon_key_with_source_tab() -> Result<(), Box> { + let mut core = BrowserCore::new(InitialBrowserConfig::ely_defaults()?)?; + let tab_id = core.open_tab(UrlText::parse("https://example.com/research")?); + + core.set_tab_favicon_key(&tab_id, "favicons/example.ico")?; + core.clear_tab_favicon_key(&tab_id)?; + let snapshot = core.snapshot()?; + + assert_eq!(snapshot.history_entries[0].favicon_key(), None); + Ok(()) +} + #[test] fn repeated_history_visits_increment_count_in_active_context() -> Result<(), Box> { let mut core = BrowserCore::new(InitialBrowserConfig::ely_defaults()?)?; diff --git a/crates/ely_domain/src/history.rs b/crates/ely_domain/src/history.rs index bf51a95..839d86f 100644 --- a/crates/ely_domain/src/history.rs +++ b/crates/ely_domain/src/history.rs @@ -9,6 +9,7 @@ pub struct HistoryEntry { source_tab_id: TabId, title: String, url: UrlText, + favicon_key: Option, visited_at: SystemTime, visit_count: u32, } @@ -21,6 +22,7 @@ impl HistoryEntry { source_tab_id: TabId, title: impl Into, url: UrlText, + favicon_key: Option, visited_at: SystemTime, ) -> Self { Self { @@ -29,6 +31,7 @@ impl HistoryEntry { source_tab_id, title: title.into(), url, + favicon_key, visited_at, visit_count: 1, } @@ -38,10 +41,14 @@ impl HistoryEntry { &mut self, source_tab_id: TabId, title: impl Into, + favicon_key: Option<&str>, visited_at: SystemTime, ) { self.source_tab_id = source_tab_id; self.title = title.into(); + if let Some(favicon_key) = favicon_key { + self.favicon_key = Some(favicon_key.to_string()); + } self.visited_at = visited_at; self.visit_count = self.visit_count.saturating_add(1); } @@ -71,6 +78,19 @@ impl HistoryEntry { &self.url } + #[must_use] + pub fn favicon_key(&self) -> Option<&str> { + self.favicon_key.as_deref() + } + + pub fn set_favicon_key(&mut self, favicon_key: impl Into) { + self.favicon_key = Some(favicon_key.into()); + } + + pub fn clear_favicon_key(&mut self) { + self.favicon_key = None; + } + #[must_use] pub fn visited_at(&self) -> SystemTime { self.visited_at