Add crashed tab recovery

This commit is contained in:
2026-05-08 10:14:00 -04:00
parent f23e5d4105
commit 1ec3bfcc3d
11 changed files with 394 additions and 1 deletions
@@ -24,6 +24,7 @@ fn internal_page_title(url: &str) -> Option<&'static str> {
"ely://archive" => Some("Archived Tabs"),
"ely://task-manager" => Some("Task Manager"),
"ely://plugins" => Some("Plugin Marketplace"),
url if crash_route_tab_id(url).is_some() => Some("Tab Recovery"),
url if plugin_detail_route_id(url).is_some() => Some("Plugin Details"),
url if SiteOrigin::from_site_route(url).ok().flatten().is_some() => Some("Site Settings"),
"ely://about" => Some("About ELY Browser"),
@@ -248,3 +249,8 @@ fn plugin_detail_route_id(url: &str) -> Option<&str> {
let plugin_id = url.strip_prefix("ely://plugin/")?;
(!plugin_id.is_empty() && !plugin_id.contains('/')).then_some(plugin_id)
}
pub(crate) fn crash_route_tab_id(url: &str) -> Option<&str> {
let tab_id = url.strip_prefix("ely://crash/")?;
(!tab_id.is_empty() && !tab_id.contains('/')).then_some(tab_id)
}
+1
View File
@@ -13,6 +13,7 @@ use sync::SyncObjectPolicies;
mod bookmarks;
mod commands;
mod crashes;
mod downloads;
mod history;
mod notes;
@@ -273,6 +273,15 @@ impl BrowserCore {
self.close_active_tab()?;
Ok(true)
}
"crash-tab" | "crash tab" => {
self.crash_active_tab()?;
Ok(true)
}
"recover-tab" | "recover tab" | "recover-crashed-tab" | "recover crashed tab" => {
let tab_id = self.active_tab()?.id().clone();
self.recover_crashed_tab(&tab_id)?;
Ok(true)
}
"close-split-view" | "close split view" => {
Ok(self.close_active_saved_split_view()?.is_some())
}
@@ -0,0 +1,35 @@
use ely_domain::TabId;
use super::BrowserCore;
use crate::CoreError;
impl BrowserCore {
pub fn crash_active_tab(&mut self) -> Result<TabId, CoreError> {
let tab_id = self.active_tab_id.clone();
self.crash_tab(&tab_id)
}
pub fn crash_tab(&mut self, tab_id: &TabId) -> Result<TabId, CoreError> {
let tab = self
.tabs
.iter_mut()
.find(|tab| tab.id() == tab_id)
.ok_or_else(|| CoreError::TabNotFound { id: tab_id.clone() })?;
tab.mark_crashed();
Ok(tab_id.clone())
}
pub fn recover_crashed_tab(&mut self, tab_id: &TabId) -> Result<TabId, CoreError> {
{
let tab = self
.tabs
.iter_mut()
.find(|tab| tab.id() == tab_id)
.ok_or_else(|| CoreError::TabNotFound { id: tab_id.clone() })?;
tab.mark_ready();
}
self.select_tab(tab_id)?;
Ok(tab_id.clone())
}
}
@@ -0,0 +1,88 @@
use std::error::Error;
use ely_browser_core::{BrowserCore, InitialBrowserConfig};
use ely_domain::{CommandIntent, TabState, UrlText};
#[test]
fn crash_active_tab_preserves_tab_metadata() -> Result<(), Box<dyn Error>> {
let mut core = BrowserCore::new(InitialBrowserConfig::ely_defaults()?)?;
let tab_id = core.open_tab(UrlText::parse("https://example.com/form")?);
let title = core.active_tab()?.title().to_string();
core.set_tab_favicon_key(&tab_id, "favicons/example.ico")?;
core.crash_active_tab()?;
let active_tab = core.active_tab()?;
assert_eq!(active_tab.id(), &tab_id);
assert_eq!(active_tab.state(), &TabState::Crashed);
assert_eq!(active_tab.url().as_str(), "https://example.com/form");
assert_eq!(active_tab.title(), title);
assert_eq!(active_tab.favicon_key(), Some("favicons/example.ico"));
Ok(())
}
#[test]
fn recover_crashed_tab_marks_tab_ready() -> Result<(), Box<dyn Error>> {
let mut core = BrowserCore::new(InitialBrowserConfig::ely_defaults()?)?;
let tab_id = core.open_tab(UrlText::parse("https://example.com/recover")?);
core.crash_active_tab()?;
core.recover_crashed_tab(&tab_id)?;
let active_tab = core.active_tab()?;
assert_eq!(active_tab.id(), &tab_id);
assert_eq!(active_tab.state(), &TabState::Ready);
assert_eq!(active_tab.url().as_str(), "https://example.com/recover");
Ok(())
}
#[test]
fn crash_tab_command_marks_active_tab_crashed() -> Result<(), Box<dyn Error>> {
let mut core = BrowserCore::new(InitialBrowserConfig::ely_defaults()?)?;
let tab_id = core.open_tab(UrlText::parse("https://example.com/crash-loop")?);
core.set_command_query(">crash-tab");
let intent = core.submit_command()?;
let snapshot = core.snapshot()?;
let active_tab = core.active_tab()?;
assert_eq!(intent, Some(CommandIntent::Command("crash-tab".to_string())));
assert_eq!(snapshot.active_tab_id, tab_id);
assert_eq!(active_tab.state(), &TabState::Crashed);
assert_eq!(active_tab.url().as_str(), "https://example.com/crash-loop");
assert_eq!(snapshot.command_query, "");
Ok(())
}
#[test]
fn recover_tab_command_restores_active_crashed_tab() -> Result<(), Box<dyn Error>> {
let mut core = BrowserCore::new(InitialBrowserConfig::ely_defaults()?)?;
let tab_id = core.open_tab(UrlText::parse("https://example.com/recover-command")?);
core.crash_active_tab()?;
core.set_command_query(">recover-tab");
let intent = core.submit_command()?;
let snapshot = core.snapshot()?;
let active_tab = core.active_tab()?;
assert_eq!(intent, Some(CommandIntent::Command("recover-tab".to_string())));
assert_eq!(snapshot.active_tab_id, tab_id);
assert_eq!(active_tab.state(), &TabState::Ready);
assert_eq!(active_tab.url().as_str(), "https://example.com/recover-command");
assert_eq!(snapshot.command_query, "");
Ok(())
}
#[test]
fn crash_route_title_marks_internal_recovery_page() -> Result<(), Box<dyn Error>> {
let mut core = BrowserCore::new(InitialBrowserConfig::ely_defaults()?)?;
let tab_id = core.active_tab()?.id().clone();
let recovery_url = UrlText::parse(format!("ely://crash/{}", tab_id.as_str()))?;
core.open_tab(recovery_url);
let active_tab = core.active_tab()?;
assert_eq!(active_tab.title(), "Tab Recovery");
assert_eq!(active_tab.url().as_str(), format!("ely://crash/{}", tab_id.as_str()));
Ok(())
}