Add sleeping tab recovery
This commit is contained in:
@@ -21,8 +21,10 @@ mod shortcuts;
|
|||||||
mod sidebar_tabs;
|
mod sidebar_tabs;
|
||||||
mod site_permissions_settings;
|
mod site_permissions_settings;
|
||||||
mod site_settings;
|
mod site_settings;
|
||||||
|
mod sleep;
|
||||||
mod spaces;
|
mod spaces;
|
||||||
mod sync;
|
mod sync;
|
||||||
|
mod tab_context;
|
||||||
mod task_manager;
|
mod task_manager;
|
||||||
|
|
||||||
use ely_browser_core::BrowserSnapshot;
|
use ely_browser_core::BrowserSnapshot;
|
||||||
@@ -47,6 +49,9 @@ impl ElyShell {
|
|||||||
if tab.state() == &TabState::Crashed {
|
if tab.state() == &TabState::Crashed {
|
||||||
return self.render_crash_page(tab, snapshot, cx);
|
return self.render_crash_page(tab, snapshot, cx);
|
||||||
}
|
}
|
||||||
|
if tab.state() == &TabState::Discarded {
|
||||||
|
return self.render_sleep_page(tab, snapshot, cx);
|
||||||
|
}
|
||||||
|
|
||||||
match tab.url().as_str() {
|
match tab.url().as_str() {
|
||||||
"ely://bookmarks" => self.render_bookmarks_page(snapshot, cx),
|
"ely://bookmarks" => self.render_bookmarks_page(snapshot, cx),
|
||||||
|
|||||||
@@ -3,11 +3,14 @@ use ely_design_system::colors;
|
|||||||
use ely_domain::BrowserTab;
|
use ely_domain::BrowserTab;
|
||||||
use gpui::{AnyElement, Context, IntoElement, ParentElement, Styled, div, px, rgb};
|
use gpui::{AnyElement, Context, IntoElement, ParentElement, Styled, div, px, rgb};
|
||||||
use gpui_component::{
|
use gpui_component::{
|
||||||
IconName, Sizable, StyledExt,
|
IconName, Sizable,
|
||||||
button::{Button, ButtonVariants},
|
button::{Button, ButtonVariants},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{ElyShell, render_canvas_surface};
|
use super::{
|
||||||
|
ElyShell, render_canvas_surface,
|
||||||
|
tab_context::{favicon_status, profile_name, space_name, tab_context_row},
|
||||||
|
};
|
||||||
|
|
||||||
impl ElyShell {
|
impl ElyShell {
|
||||||
pub(super) fn render_crash_page(
|
pub(super) fn render_crash_page(
|
||||||
@@ -60,12 +63,12 @@ fn render_crash_content(
|
|||||||
.flex()
|
.flex()
|
||||||
.flex_col()
|
.flex_col()
|
||||||
.gap_3()
|
.gap_3()
|
||||||
.child(crash_detail("URL", tab.url().as_str().to_string()))
|
.child(tab_context_row("URL", tab.url().as_str().to_string()))
|
||||||
.child(crash_detail("Title", tab.title().to_string()))
|
.child(tab_context_row("Title", tab.title().to_string()))
|
||||||
.child(crash_detail("Favicon", favicon_status(tab)))
|
.child(tab_context_row("Favicon", favicon_status(tab)))
|
||||||
.child(crash_detail("Space", space_name(snapshot, tab)))
|
.child(tab_context_row("Space", space_name(snapshot, tab)))
|
||||||
.child(crash_detail("Profile", profile_name(snapshot, tab)))
|
.child(tab_context_row("Profile", profile_name(snapshot, tab)))
|
||||||
.child(crash_detail(
|
.child(tab_context_row(
|
||||||
"Form restore prompt",
|
"Form restore prompt",
|
||||||
"Session data remains attached to this tab.".to_string(),
|
"Session data remains attached to this tab.".to_string(),
|
||||||
)),
|
)),
|
||||||
@@ -155,46 +158,6 @@ fn render_missing_crash_route(message: &'static str) -> AnyElement {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn crash_detail(label: &'static str, value: String) -> AnyElement {
|
|
||||||
div()
|
|
||||||
.flex()
|
|
||||||
.items_center()
|
|
||||||
.justify_between()
|
|
||||||
.gap_4()
|
|
||||||
.child(div().min_w(px(128.0)).text_xs().text_color(rgb(colors::MUTED)).child(label))
|
|
||||||
.child(
|
|
||||||
div()
|
|
||||||
.min_w_0()
|
|
||||||
.text_sm()
|
|
||||||
.font_semibold()
|
|
||||||
.truncate()
|
|
||||||
.text_color(rgb(colors::INK))
|
|
||||||
.child(value),
|
|
||||||
)
|
|
||||||
.into_any_element()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn favicon_status(tab: &BrowserTab) -> String {
|
|
||||||
tab.favicon_key()
|
|
||||||
.map_or_else(|| "No favicon saved".to_string(), |favicon| format!("Saved: {favicon}"))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn space_name(snapshot: &BrowserSnapshot, tab: &BrowserTab) -> String {
|
|
||||||
snapshot
|
|
||||||
.spaces
|
|
||||||
.iter()
|
|
||||||
.find(|space| space.id() == tab.space_id())
|
|
||||||
.map_or_else(|| tab.space_id().as_str().to_string(), |space| space.name().to_string())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn profile_name(snapshot: &BrowserSnapshot, tab: &BrowserTab) -> String {
|
|
||||||
snapshot
|
|
||||||
.profiles
|
|
||||||
.iter()
|
|
||||||
.find(|profile| profile.id() == tab.profile_id())
|
|
||||||
.map_or_else(|| tab.profile_id().as_str().to_string(), |profile| profile.name().to_string())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn crash_route_tab_id(url: &str) -> Option<&str> {
|
fn crash_route_tab_id(url: &str) -> Option<&str> {
|
||||||
let tab_id = url.strip_prefix("ely://crash/")?;
|
let tab_id = url.strip_prefix("ely://crash/")?;
|
||||||
(!tab_id.is_empty() && !tab_id.contains('/')).then_some(tab_id)
|
(!tab_id.is_empty() && !tab_id.contains('/')).then_some(tab_id)
|
||||||
|
|||||||
@@ -0,0 +1,129 @@
|
|||||||
|
use ely_browser_core::BrowserSnapshot;
|
||||||
|
use ely_design_system::colors;
|
||||||
|
use ely_domain::BrowserTab;
|
||||||
|
use gpui::{AnyElement, Context, IntoElement, ParentElement, Styled, div, px, rgb};
|
||||||
|
use gpui_component::{
|
||||||
|
IconName, Sizable,
|
||||||
|
button::{Button, ButtonVariants},
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::{
|
||||||
|
ElyShell, render_canvas_surface,
|
||||||
|
tab_context::{favicon_status, profile_name, space_name, tab_context_row},
|
||||||
|
};
|
||||||
|
|
||||||
|
impl ElyShell {
|
||||||
|
pub(super) fn render_sleep_page(
|
||||||
|
&mut self,
|
||||||
|
tab: &BrowserTab,
|
||||||
|
snapshot: &BrowserSnapshot,
|
||||||
|
cx: &mut Context<Self>,
|
||||||
|
) -> AnyElement {
|
||||||
|
render_canvas_surface(render_sleep_content(tab, snapshot, cx))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_sleep_content(
|
||||||
|
tab: &BrowserTab,
|
||||||
|
snapshot: &BrowserSnapshot,
|
||||||
|
cx: &mut Context<ElyShell>,
|
||||||
|
) -> AnyElement {
|
||||||
|
let tab_id = tab.id().clone();
|
||||||
|
|
||||||
|
div()
|
||||||
|
.size_full()
|
||||||
|
.p_8()
|
||||||
|
.flex()
|
||||||
|
.flex_col()
|
||||||
|
.gap_5()
|
||||||
|
.child(render_sleep_header(tab, cx))
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.border_t_1()
|
||||||
|
.border_b_1()
|
||||||
|
.border_color(rgb(colors::HAIRLINE))
|
||||||
|
.py_4()
|
||||||
|
.flex()
|
||||||
|
.flex_col()
|
||||||
|
.gap_3()
|
||||||
|
.child(tab_context_row("URL", tab.url().as_str().to_string()))
|
||||||
|
.child(tab_context_row("Title", tab.title().to_string()))
|
||||||
|
.child(tab_context_row("Favicon", favicon_status(tab)))
|
||||||
|
.child(tab_context_row("Space", space_name(snapshot, tab)))
|
||||||
|
.child(tab_context_row("Profile", profile_name(snapshot, tab)))
|
||||||
|
.child(tab_context_row(
|
||||||
|
"Session",
|
||||||
|
"Page session remains attached to this tab.".to_string(),
|
||||||
|
)),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.flex()
|
||||||
|
.items_center()
|
||||||
|
.gap_3()
|
||||||
|
.text_sm()
|
||||||
|
.text_color(rgb(colors::MUTED))
|
||||||
|
.child(IconName::CircleCheck)
|
||||||
|
.child("This tab is sleeping. Restore wakes the tab and keeps its saved context."),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
div().flex().child(
|
||||||
|
Button::new("wake-discarded-tab")
|
||||||
|
.primary()
|
||||||
|
.small()
|
||||||
|
.icon(IconName::Undo2)
|
||||||
|
.label("Restore")
|
||||||
|
.tooltip("Restore sleeping tab")
|
||||||
|
.on_click(cx.listener(move |shell, _, window, cx| {
|
||||||
|
shell.wake_discarded_tab(&tab_id, window, cx);
|
||||||
|
})),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.into_any_element()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_sleep_header(tab: &BrowserTab, cx: &mut Context<ElyShell>) -> AnyElement {
|
||||||
|
let tab_id = tab.id().clone();
|
||||||
|
|
||||||
|
div()
|
||||||
|
.flex()
|
||||||
|
.items_end()
|
||||||
|
.justify_between()
|
||||||
|
.gap_4()
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.min_w_0()
|
||||||
|
.flex()
|
||||||
|
.flex_col()
|
||||||
|
.gap_2()
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.flex()
|
||||||
|
.items_center()
|
||||||
|
.gap_3()
|
||||||
|
.text_size(px(26.0))
|
||||||
|
.text_color(rgb(colors::INK))
|
||||||
|
.child(IconName::CircleCheck)
|
||||||
|
.child("Sleeping Tab"),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.text_sm()
|
||||||
|
.truncate()
|
||||||
|
.text_color(rgb(colors::MUTED))
|
||||||
|
.child(format!("Sleeping {}", tab.display_url())),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
Button::new("wake-discarded-tab-header")
|
||||||
|
.ghost()
|
||||||
|
.small()
|
||||||
|
.icon(IconName::Undo2)
|
||||||
|
.label("Restore")
|
||||||
|
.tooltip("Restore sleeping tab")
|
||||||
|
.on_click(cx.listener(move |shell, _, window, cx| {
|
||||||
|
shell.wake_discarded_tab(&tab_id, window, cx);
|
||||||
|
})),
|
||||||
|
)
|
||||||
|
.into_any_element()
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
use ely_browser_core::BrowserSnapshot;
|
||||||
|
use ely_design_system::colors;
|
||||||
|
use ely_domain::BrowserTab;
|
||||||
|
use gpui::{AnyElement, IntoElement, ParentElement, Styled, div, px, rgb};
|
||||||
|
use gpui_component::StyledExt;
|
||||||
|
|
||||||
|
pub(super) fn tab_context_row(label: &'static str, value: String) -> AnyElement {
|
||||||
|
div()
|
||||||
|
.flex()
|
||||||
|
.items_center()
|
||||||
|
.justify_between()
|
||||||
|
.gap_4()
|
||||||
|
.child(div().min_w(px(128.0)).text_xs().text_color(rgb(colors::MUTED)).child(label))
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.min_w_0()
|
||||||
|
.text_sm()
|
||||||
|
.font_semibold()
|
||||||
|
.truncate()
|
||||||
|
.text_color(rgb(colors::INK))
|
||||||
|
.child(value),
|
||||||
|
)
|
||||||
|
.into_any_element()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn favicon_status(tab: &BrowserTab) -> String {
|
||||||
|
tab.favicon_key()
|
||||||
|
.map_or_else(|| "No favicon saved".to_string(), |favicon| format!("Saved: {favicon}"))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn space_name(snapshot: &BrowserSnapshot, tab: &BrowserTab) -> String {
|
||||||
|
snapshot
|
||||||
|
.spaces
|
||||||
|
.iter()
|
||||||
|
.find(|space| space.id() == tab.space_id())
|
||||||
|
.map_or_else(|| tab.space_id().as_str().to_string(), |space| space.name().to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn profile_name(snapshot: &BrowserSnapshot, tab: &BrowserTab) -> String {
|
||||||
|
snapshot
|
||||||
|
.profiles
|
||||||
|
.iter()
|
||||||
|
.find(|profile| profile.id() == tab.profile_id())
|
||||||
|
.map_or_else(|| tab.profile_id().as_str().to_string(), |profile| profile.name().to_string())
|
||||||
|
}
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
mod archive_labels;
|
mod archive_labels;
|
||||||
mod bookmarks;
|
mod bookmarks;
|
||||||
mod crashes;
|
|
||||||
mod downloads;
|
mod downloads;
|
||||||
mod history;
|
mod history;
|
||||||
mod internal_pages;
|
mod internal_pages;
|
||||||
@@ -13,6 +12,7 @@ mod site_permissions;
|
|||||||
mod spaces;
|
mod spaces;
|
||||||
mod splits;
|
mod splits;
|
||||||
mod tab_groups;
|
mod tab_groups;
|
||||||
|
mod tab_lifecycle;
|
||||||
|
|
||||||
use ely_browser_core::{BrowserCore, InitialBrowserConfig};
|
use ely_browser_core::{BrowserCore, InitialBrowserConfig};
|
||||||
use ely_domain::{
|
use ely_domain::{
|
||||||
|
|||||||
@@ -18,4 +18,19 @@ impl ElyShell {
|
|||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(super) fn wake_discarded_tab(
|
||||||
|
&mut self,
|
||||||
|
tab_id: &TabId,
|
||||||
|
window: &mut Window,
|
||||||
|
cx: &mut Context<Self>,
|
||||||
|
) {
|
||||||
|
if let ShellState::Ready(core) = &mut self.state
|
||||||
|
&& core.wake_discarded_tab(tab_id).is_ok()
|
||||||
|
{
|
||||||
|
self.sync_address_input(window, cx);
|
||||||
|
self.focus_address_bar(window, cx);
|
||||||
|
cx.notify();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -13,7 +13,6 @@ use sync::SyncObjectPolicies;
|
|||||||
|
|
||||||
mod bookmarks;
|
mod bookmarks;
|
||||||
mod commands;
|
mod commands;
|
||||||
mod crashes;
|
|
||||||
mod downloads;
|
mod downloads;
|
||||||
mod history;
|
mod history;
|
||||||
mod notes;
|
mod notes;
|
||||||
@@ -25,6 +24,7 @@ mod spaces;
|
|||||||
mod splits;
|
mod splits;
|
||||||
mod sync;
|
mod sync;
|
||||||
mod tab_groups;
|
mod tab_groups;
|
||||||
|
mod tab_lifecycle;
|
||||||
mod tab_order;
|
mod tab_order;
|
||||||
mod tabs;
|
mod tabs;
|
||||||
|
|
||||||
|
|||||||
@@ -277,11 +277,20 @@ impl BrowserCore {
|
|||||||
self.crash_active_tab()?;
|
self.crash_active_tab()?;
|
||||||
Ok(true)
|
Ok(true)
|
||||||
}
|
}
|
||||||
|
"sleep-tab" | "sleep tab" | "discard-tab" | "discard tab" => {
|
||||||
|
self.discard_active_tab()?;
|
||||||
|
Ok(true)
|
||||||
|
}
|
||||||
"recover-tab" | "recover tab" | "recover-crashed-tab" | "recover crashed tab" => {
|
"recover-tab" | "recover tab" | "recover-crashed-tab" | "recover crashed tab" => {
|
||||||
let tab_id = self.active_tab()?.id().clone();
|
let tab_id = self.active_tab()?.id().clone();
|
||||||
self.recover_crashed_tab(&tab_id)?;
|
self.recover_crashed_tab(&tab_id)?;
|
||||||
Ok(true)
|
Ok(true)
|
||||||
}
|
}
|
||||||
|
"wake-tab" | "wake tab" | "restore-sleeping-tab" | "restore sleeping tab" => {
|
||||||
|
let tab_id = self.active_tab()?.id().clone();
|
||||||
|
self.wake_discarded_tab(&tab_id)?;
|
||||||
|
Ok(true)
|
||||||
|
}
|
||||||
"close-split-view" | "close split view" => {
|
"close-split-view" | "close split view" => {
|
||||||
Ok(self.close_active_saved_split_view()?.is_some())
|
Ok(self.close_active_saved_split_view()?.is_some())
|
||||||
}
|
}
|
||||||
|
|||||||
+23
@@ -20,6 +20,29 @@ impl BrowserCore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn recover_crashed_tab(&mut self, tab_id: &TabId) -> Result<TabId, CoreError> {
|
pub fn recover_crashed_tab(&mut self, tab_id: &TabId) -> Result<TabId, CoreError> {
|
||||||
|
self.mark_tab_ready(tab_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn discard_active_tab(&mut self) -> Result<TabId, CoreError> {
|
||||||
|
let tab_id = self.active_tab_id.clone();
|
||||||
|
self.discard_tab(&tab_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn discard_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_discarded();
|
||||||
|
Ok(tab_id.clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn wake_discarded_tab(&mut self, tab_id: &TabId) -> Result<TabId, CoreError> {
|
||||||
|
self.mark_tab_ready(tab_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mark_tab_ready(&mut self, tab_id: &TabId) -> Result<TabId, CoreError> {
|
||||||
{
|
{
|
||||||
let tab = self
|
let tab = self
|
||||||
.tabs
|
.tabs
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
use std::error::Error;
|
||||||
|
|
||||||
|
use ely_browser_core::{BrowserCore, InitialBrowserConfig};
|
||||||
|
use ely_domain::{CommandIntent, TabState, UrlText};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn discard_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/sleep")?);
|
||||||
|
let title = core.active_tab()?.title().to_string();
|
||||||
|
core.set_tab_favicon_key(&tab_id, "favicons/example.ico")?;
|
||||||
|
|
||||||
|
core.discard_active_tab()?;
|
||||||
|
let active_tab = core.active_tab()?;
|
||||||
|
|
||||||
|
assert_eq!(active_tab.id(), &tab_id);
|
||||||
|
assert_eq!(active_tab.state(), &TabState::Discarded);
|
||||||
|
assert_eq!(active_tab.url().as_str(), "https://example.com/sleep");
|
||||||
|
assert_eq!(active_tab.title(), title);
|
||||||
|
assert_eq!(active_tab.favicon_key(), Some("favicons/example.ico"));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn wake_discarded_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/wake")?);
|
||||||
|
|
||||||
|
core.discard_active_tab()?;
|
||||||
|
core.wake_discarded_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/wake");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn sleep_tab_command_discards_active_tab() -> Result<(), Box<dyn Error>> {
|
||||||
|
let mut core = BrowserCore::new(InitialBrowserConfig::ely_defaults()?)?;
|
||||||
|
let tab_id = core.open_tab(UrlText::parse("https://example.com/sleep-command")?);
|
||||||
|
|
||||||
|
core.set_command_query(">sleep-tab");
|
||||||
|
let intent = core.submit_command()?;
|
||||||
|
let snapshot = core.snapshot()?;
|
||||||
|
let active_tab = core.active_tab()?;
|
||||||
|
|
||||||
|
assert_eq!(intent, Some(CommandIntent::Command("sleep-tab".to_string())));
|
||||||
|
assert_eq!(snapshot.active_tab_id, tab_id);
|
||||||
|
assert_eq!(active_tab.state(), &TabState::Discarded);
|
||||||
|
assert_eq!(active_tab.url().as_str(), "https://example.com/sleep-command");
|
||||||
|
assert_eq!(snapshot.command_query, "");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn wake_tab_command_restores_active_discarded_tab() -> Result<(), Box<dyn Error>> {
|
||||||
|
let mut core = BrowserCore::new(InitialBrowserConfig::ely_defaults()?)?;
|
||||||
|
let tab_id = core.open_tab(UrlText::parse("https://example.com/wake-command")?);
|
||||||
|
core.discard_active_tab()?;
|
||||||
|
|
||||||
|
core.set_command_query(">wake-tab");
|
||||||
|
let intent = core.submit_command()?;
|
||||||
|
let snapshot = core.snapshot()?;
|
||||||
|
let active_tab = core.active_tab()?;
|
||||||
|
|
||||||
|
assert_eq!(intent, Some(CommandIntent::Command("wake-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/wake-command");
|
||||||
|
assert_eq!(snapshot.command_query, "");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -165,6 +165,10 @@ impl BrowserTab {
|
|||||||
self.state = TabState::Crashed;
|
self.state = TabState::Crashed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn mark_discarded(&mut self) {
|
||||||
|
self.state = TabState::Discarded;
|
||||||
|
}
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn flags(&self) -> &TabFlags {
|
pub fn flags(&self) -> &TabFlags {
|
||||||
&self.flags
|
&self.flags
|
||||||
|
|||||||
@@ -47,3 +47,24 @@ Crash recovery uses the same productive register:
|
|||||||
│ │ └─────────────────────────────────────────┘ │
|
│ │ └─────────────────────────────────────────┘ │
|
||||||
└──────────────────────────────┴───────────────────────────────────────────────┘
|
└──────────────────────────────┴───────────────────────────────────────────────┘
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Sleeping tabs keep the same layout rhythm:
|
||||||
|
|
||||||
|
```text
|
||||||
|
┌──────────────────────────────────────────────────────────────────────────────┐
|
||||||
|
│ ELY Browser [ https://example.com/sleep................... ] [pin] [*] [+] │
|
||||||
|
├──────────────────────────────┬───────────────────────────────────────────────┤
|
||||||
|
│ Tabs │ ┌─────────────────────────────────────────┐ │
|
||||||
|
│ * example.com │ │ Sleeping Tab [Restore] │ │
|
||||||
|
│ example.com │ │ Sleeping example.com │ │
|
||||||
|
│ │ ├─────────────────────────────────────────┤ │
|
||||||
|
│ Profile │ │ URL https://example.com/sleep │ │
|
||||||
|
│ Default │ │ Title example.com │ │
|
||||||
|
│ │ │ Favicon No favicon saved │ │
|
||||||
|
│ │ │ Space Work │ │
|
||||||
|
│ │ │ Profile Default │ │
|
||||||
|
│ │ │ Session Page session remains attached │ │
|
||||||
|
│ │ │ [Restore] │ │
|
||||||
|
│ │ └─────────────────────────────────────────┘ │
|
||||||
|
└──────────────────────────────┴───────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|||||||
Reference in New Issue
Block a user