feat(reload): reload the active tab with Cmd/Ctrl+R

This commit is contained in:
2026-07-10 16:55:25 -04:00
parent a12b8c8bd8
commit c5890058db
16 changed files with 147 additions and 5 deletions
+2
View File
@@ -38,6 +38,7 @@ actions!(
OpenSettings,
OpenTaskManager,
Quit,
ReloadTab,
RestoreClosedTab,
ResetZoom,
SelectNextSpace,
@@ -105,6 +106,7 @@ fn main() {
MenuItem::action("Zoom Out", ZoomOut),
MenuItem::action("Reset Zoom", ResetZoom),
MenuItem::separator(),
MenuItem::action("Reload Tab", ReloadTab),
MenuItem::action("Close Tab", CloseCurrentTab),
MenuItem::separator(),
MenuItem::action("Restore Closed Tab", RestoreClosedTab),
@@ -177,6 +177,10 @@ impl ServoLiveClient {
})
}
pub fn reload(&mut self, tab_id: String) -> Result<(), ServoLiveError> {
self.request(LiveRequest::Reload { tab_id }).map(|_| ())
}
pub fn close(&mut self, tab_id: String) -> Result<(), ServoLiveError> {
self.request(LiveRequest::Close { tab_id }).map(|_| ())
}
@@ -41,6 +41,9 @@ pub(super) enum LiveRequest {
ready_surface_ids: Vec<u64>,
pending_surface_ids: Vec<u64>,
},
Reload {
tab_id: String,
},
Close {
tab_id: String,
},
+1
View File
@@ -119,6 +119,7 @@ impl ElyShell {
.on_action(cx.listener(Self::on_open_new_tab))
.on_action(cx.listener(Self::on_open_settings))
.on_action(cx.listener(Self::on_open_task_manager))
.on_action(cx.listener(Self::on_reload_tab))
.on_action(cx.listener(Self::on_reset_zoom))
.on_action(cx.listener(Self::on_restore_closed_tab))
.on_action(cx.listener(Self::on_select_next_space))
+10 -1
View File
@@ -2,7 +2,7 @@ use gpui::{Context, Window};
use crate::{
CloseCurrentTab, DownloadCurrentPage, FocusAddressBar, FocusCommandMode, OpenDownloads,
OpenHistory, OpenNewTab, OpenSettings, OpenTaskManager, ResetZoom, RestoreClosedTab,
OpenHistory, OpenNewTab, OpenSettings, OpenTaskManager, ReloadTab, ResetZoom, RestoreClosedTab,
SelectNextTab, SelectPreviousTab, ToggleFavoriteTab, TogglePinnedTab, ZoomIn, ZoomOut,
};
@@ -45,6 +45,15 @@ impl ElyShell {
self.open_new_tab(window, cx);
}
pub(super) fn on_reload_tab(
&mut self,
_: &ReloadTab,
window: &mut Window,
cx: &mut Context<Self>,
) {
self.reload_active_tab(window, cx);
}
pub(super) fn on_open_downloads(
&mut self,
_: &OpenDownloads,
+15
View File
@@ -20,6 +20,21 @@ impl ElyShell {
}
}
/// Reload the active tab: recover it to the ready state in `BrowserCore`
/// and, when it hosts a live web surface, tell that surface's Servo
/// sidecar to refetch the page.
pub(super) fn reload_active_tab(&mut self, window: &mut Window, cx: &mut Context<Self>) {
let ShellState::Ready(core) = &mut self.state else {
return;
};
let Ok(tab_id) = core.reload_active_tab() else {
return;
};
self.web_surfaces.reload_tab(&tab_id);
self.sync_address_input(window, cx);
cx.notify();
}
pub(super) fn wake_discarded_tab(
&mut self,
tab_id: &TabId,
+4
View File
@@ -353,6 +353,10 @@ impl WebSurfaceStore {
self.surfaces.entry(tab_id.clone()).or_insert_with(PerTabSurface::new)
}
pub(super) fn reload_tab(&mut self, tab_id: &TabId) {
self.runtime.reload_tab(tab_id);
}
pub(super) fn close_surface(&mut self, tab_id: &TabId) {
self.runtime.close_tab(tab_id);
self.surfaces.remove(tab_id);
@@ -78,6 +78,15 @@ impl WebSurfaceRuntime {
self.take_retired_permission_consumptions()
}
pub(in crate::shell) fn reload_tab(&mut self, tab_id: &TabId) {
let Some(session) = self.sessions.get(tab_id) else {
return;
};
if let Some(scoped) = self.workers.get(&session.scope) {
scoped.worker.submit_reload(tab_id.as_str().to_string());
}
}
pub(in crate::shell) fn close_tab(&mut self, tab_id: &TabId) {
let Some(session) = self.sessions.remove(tab_id) else {
return;
+41 -3
View File
@@ -29,6 +29,11 @@ pub(super) trait LiveRuntimeClient {
fn poll(&mut self, tab_id: String) -> Result<Option<ServoLiveFrame>, LiveRuntimeClientError>;
fn reload(&mut self, tab_id: String) -> Result<(), LiveRuntimeClientError> {
let _ = tab_id;
Ok(())
}
fn close(&mut self, tab_id: String) -> Result<(), LiveRuntimeClientError>;
fn take_permission_consumptions(&mut self) -> Vec<ServoLivePermissionGrant> {
@@ -48,6 +53,10 @@ impl LiveRuntimeClient for ServoLiveClient {
ServoLiveClient::poll(self, tab_id).map_err(LiveRuntimeClientError::from)
}
fn reload(&mut self, tab_id: String) -> Result<(), LiveRuntimeClientError> {
ServoLiveClient::reload(self, tab_id).map_err(LiveRuntimeClientError::from)
}
fn close(&mut self, tab_id: String) -> Result<(), LiveRuntimeClientError> {
ServoLiveClient::close(self, tab_id).map_err(LiveRuntimeClientError::from)
}
@@ -147,6 +156,7 @@ struct WorkerQueue {
pending: BTreeMap<String, VecDeque<WorkerRequest>>,
/// Round-robin tab order, with each pending tab represented once.
ready_tabs: VecDeque<String>,
reloads: VecDeque<String>,
closes: VecDeque<String>,
in_flight: bool,
in_flight_tab: Option<String>,
@@ -170,6 +180,7 @@ impl LiveRuntimeWorker {
Mutex::new(WorkerQueue {
pending: BTreeMap::new(),
ready_tabs: VecDeque::new(),
reloads: VecDeque::new(),
closes: VecDeque::new(),
in_flight: false,
in_flight_tab: None,
@@ -264,6 +275,21 @@ impl LiveRuntimeWorker {
inserted
}
pub(super) fn submit_reload(&self, tab_id: String) {
let (lock, cvar) = &*self.queue;
let mut q = match lock.lock() {
Ok(guard) => guard,
Err(poisoned) => poisoned.into_inner(),
};
if q.shutdown || q.initialization_failure.is_some() {
return;
}
if !q.reloads.contains(&tab_id) {
q.reloads.push_back(tab_id);
}
cvar.notify_one();
}
pub(super) fn submit_close(&self, tab_id: String) {
let (lock, cvar) = &*self.queue;
let mut q = match lock.lock() {
@@ -278,6 +304,7 @@ impl LiveRuntimeWorker {
}
q.pending.remove(&tab_id);
q.ready_tabs.retain(|ready_tab_id| ready_tab_id != &tab_id);
q.reloads.retain(|reload_tab_id| reload_tab_id != &tab_id);
q.closes.push_back(tab_id);
cvar.notify_one();
}
@@ -298,7 +325,8 @@ impl LiveRuntimeWorker {
Ok(guard) => guard,
Err(poisoned) => poisoned.into_inner(),
};
while !q.pending.is_empty() || !q.closes.is_empty() || q.in_flight {
while !q.pending.is_empty() || !q.reloads.is_empty() || !q.closes.is_empty() || q.in_flight
{
q = match cvar.wait(q) {
Ok(guard) => guard,
Err(poisoned) => poisoned.into_inner(),
@@ -336,6 +364,7 @@ fn fail_worker_initialization(
};
q.initialization_failure = Some(message.clone());
q.ready_tabs.clear();
q.reloads.clear();
q.closes.clear();
q.in_flight = false;
q.in_flight_tab = None;
@@ -368,7 +397,8 @@ fn run_worker(
q.in_flight = false;
q.in_flight_tab = None;
cvar.notify_all();
while q.pending.is_empty() && q.closes.is_empty() && !q.shutdown {
while q.pending.is_empty() && q.reloads.is_empty() && q.closes.is_empty() && !q.shutdown
{
q = match cvar.wait(q) {
Ok(guard) => guard,
Err(poisoned) => poisoned.into_inner(),
@@ -379,6 +409,8 @@ fn run_worker(
}
let next = if let Some(close_id) = q.closes.pop_front() {
Work::Close(close_id)
} else if let Some(reload_id) = q.reloads.pop_front() {
Work::Reload(reload_id)
} else {
if q.ready_tabs.len() > 1
&& q.ready_tabs.front() == last_dispatched_tab.as_ref()
@@ -407,7 +439,7 @@ fn run_worker(
};
q.in_flight = true;
q.in_flight_tab = match &next {
Work::Close(_) => None,
Work::Close(_) | Work::Reload(_) => None,
Work::Request(request) => Some(request.tab_id().to_string()),
};
next
@@ -419,6 +451,11 @@ fn run_worker(
forward_permission_consumptions(&mut *client, &response_tx);
false
}
Work::Reload(tab_id) => {
let _ = client.reload(tab_id);
forward_permission_consumptions(&mut *client, &response_tx);
false
}
Work::Request(WorkerRequest::Ensure { generation, mut request }) => {
let tab_id = request.tab_id.clone();
let allow_once_grants = std::mem::take(&mut request.allow_once_grants);
@@ -454,6 +491,7 @@ fn run_worker(
enum Work {
Close(String),
Reload(String),
Request(WorkerRequest),
}
+9 -1
View File
@@ -10,7 +10,7 @@ pub(crate) use profile::{
use crate::{
CloseCurrentTab, FocusAddressBar, FocusCommandMode, OpenDownloads, OpenHistory, OpenNewTab,
OpenPrivateWindow, OpenSettings, OpenTaskManager, Quit, ResetZoom, RestoreClosedTab,
OpenPrivateWindow, OpenSettings, OpenTaskManager, Quit, ReloadTab, ResetZoom, RestoreClosedTab,
SelectNextSpace, SelectNextTab, SelectPreviousSpace, SelectPreviousTab, SplitRight,
ToggleFavoriteTab, ToggleSidebar, ZoomIn, ZoomOut,
};
@@ -39,6 +39,7 @@ pub(crate) enum ShortcutAction {
OpenNewTab,
OpenPrivateWindow,
CloseCurrentTab,
ReloadTab,
RestoreClosedTab,
SelectNextSpace,
SelectPreviousSpace,
@@ -65,6 +66,7 @@ impl ShortcutAction {
Self::OpenNewTab => "New Tab",
Self::OpenPrivateWindow => "New Private Window",
Self::CloseCurrentTab => "Close Tab",
Self::ReloadTab => "Reload Tab",
Self::RestoreClosedTab => "Restore Closed Tab",
Self::SelectNextSpace => "Next Space",
Self::SelectPreviousSpace => "Previous Space",
@@ -90,6 +92,7 @@ impl ShortcutAction {
Self::OpenNewTab
| Self::OpenPrivateWindow
| Self::CloseCurrentTab
| Self::ReloadTab
| Self::RestoreClosedTab
| Self::SelectNextSpace
| Self::SelectPreviousSpace
@@ -114,6 +117,7 @@ impl ShortcutAction {
Self::OpenNewTab => Some(">new-tab"),
Self::OpenPrivateWindow => None,
Self::CloseCurrentTab => Some(">close-tab"),
Self::ReloadTab => None,
Self::RestoreClosedTab => Some(">restore-tab"),
Self::SelectNextSpace => None,
Self::SelectPreviousSpace => None,
@@ -168,6 +172,7 @@ pub(crate) const SHORTCUT_ACTIONS: &[ShortcutAction] = &[
ShortcutAction::OpenNewTab,
ShortcutAction::OpenPrivateWindow,
ShortcutAction::CloseCurrentTab,
ShortcutAction::ReloadTab,
ShortcutAction::RestoreClosedTab,
ShortcutAction::SelectNextSpace,
ShortcutAction::SelectPreviousSpace,
@@ -189,6 +194,8 @@ pub(crate) const SHORTCUT_ACTIONS: &[ShortcutAction] = &[
pub(crate) const SHORTCUT_BINDINGS: &[ShortcutBinding] = &[
shortcut(ShortcutAction::OpenNewTab, ShortcutPlatform::Macos, "cmd-t"),
shortcut(ShortcutAction::OpenNewTab, ShortcutPlatform::WindowsLinux, "ctrl-t"),
shortcut(ShortcutAction::ReloadTab, ShortcutPlatform::Macos, "cmd-r"),
shortcut(ShortcutAction::ReloadTab, ShortcutPlatform::WindowsLinux, "ctrl-r"),
shortcut(ShortcutAction::OpenPrivateWindow, ShortcutPlatform::Macos, "cmd-shift-n"),
shortcut(ShortcutAction::OpenPrivateWindow, ShortcutPlatform::WindowsLinux, "ctrl-shift-n"),
shortcut(ShortcutAction::SplitRight, ShortcutPlatform::Macos, "cmd-\\"),
@@ -278,6 +285,7 @@ pub(crate) fn key_binding_for_action(action: ShortcutAction, keystroke: &str) ->
ShortcutAction::OpenSettings => KeyBinding::new(keystroke, OpenSettings, None),
ShortcutAction::OpenTaskManager => KeyBinding::new(keystroke, OpenTaskManager, None),
ShortcutAction::Quit => KeyBinding::new(keystroke, Quit, None),
ShortcutAction::ReloadTab => KeyBinding::new(keystroke, ReloadTab, None),
ShortcutAction::ResetZoom => KeyBinding::new(keystroke, ResetZoom, None),
ShortcutAction::RestoreClosedTab => KeyBinding::new(keystroke, RestoreClosedTab, None),
ShortcutAction::SelectNextSpace => KeyBinding::new(keystroke, SelectNextSpace, None),