host: the palette commands of site tabs are the host's (tab_commands, tab_run_command)
tab_commands keeps the list each site tab reported (validated by plugins::commands, accepted only from a tab on the site's origin), clears it when a new document starts, tells the shell, and checks the shell-only tab_run_command: only a command that tab reported, only while it is on the site. tab_run_command joins commands::SHELL (guard checked by assert_host_guards); the app's tab_report hands its `commands` to tab_commands::reported. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
6b37744226
commit
d40bf273bd
4 changed files with 84 additions and 0 deletions
|
|
@ -940,6 +940,14 @@ async fn features_set(webview: Webview, app: AppHandle, features: serde_json::Ma
|
|||
set_features(&app, features)
|
||||
}
|
||||
|
||||
/// Runs a plugin's palette command in tab `id`: only a command that tab reported, only while it is
|
||||
/// on the site. The command's code runs in the page; the tab gets keyboard focus for what it opens.
|
||||
#[tauri::command]
|
||||
async fn tab_run_command(webview: Webview, app: AppHandle, id: String, command: String) -> Result<(), String> {
|
||||
shell_only(&webview)?;
|
||||
crate::tab_commands::run(&app, &id, &command)
|
||||
}
|
||||
|
||||
/// The onboarding mode: what was chosen, whether the first-run screen was answered, and whether
|
||||
/// this app was set up before modes existed.
|
||||
#[tauri::command]
|
||||
|
|
@ -1126,6 +1134,7 @@ fn tab_builder(app: &AppHandle, hooks: &Arc<dyn SiteHooks>, tab: &NewTab, url: U
|
|||
}
|
||||
let finished = matches!(payload.event(), PageLoadEvent::Finished);
|
||||
if !finished {
|
||||
crate::tab_commands::cleared(app, tab.label());
|
||||
load_hooks.page_started(app, &tab);
|
||||
// An engine that resets page zoom on navigation still shows this tab's.
|
||||
host::restore_tab_zoom(app, &tab);
|
||||
|
|
@ -1239,6 +1248,7 @@ fn host_handler() -> impl Fn(tauri::ipc::Invoke<Wry>) -> bool + Send + Sync + 's
|
|||
mode_set,
|
||||
settings_get,
|
||||
settings_set,
|
||||
tab_run_command,
|
||||
tab_shortcut,
|
||||
tab_link_drag,
|
||||
open_panel
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ pub const SHELL: &[&str] = &[
|
|||
"mode_set",
|
||||
"settings_get",
|
||||
"settings_set",
|
||||
"tab_run_command",
|
||||
];
|
||||
|
||||
/// Tab-only: any tab webview, speaking only about itself.
|
||||
|
|
|
|||
|
|
@ -93,6 +93,8 @@ mod dev_commands;
|
|||
#[cfg(feature = "app")]
|
||||
pub mod theme_commands;
|
||||
#[cfg(feature = "app")]
|
||||
pub mod tab_commands;
|
||||
#[cfg(feature = "app")]
|
||||
pub mod alerts_commands;
|
||||
#[cfg(feature = "app")]
|
||||
mod backup_commands;
|
||||
|
|
|
|||
71
crates/waybetter-host/src/tab_commands.rs
Normal file
71
crates/waybetter-host/src/tab_commands.rs
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
//! The plugin commands in the shell's command palette, native side, for every app: the list each
|
||||
//! site tab last reported ([`reported`]; validated by [`crate::plugins::commands`]), kept per tab,
|
||||
//! forwarded to the shell, and the check behind the shell-only `tab_run_command` ([`run`]: only a
|
||||
//! command that tab reported, only while it is on the site; the command's code runs in the page).
|
||||
|
||||
use crate::app::config;
|
||||
use crate::host::{emit_shell, tab_webview};
|
||||
use crate::plugins::commands::{self as palette, PaletteCommand};
|
||||
use serde::Serialize;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::{Mutex, MutexGuard, OnceLock};
|
||||
use tauri::{AppHandle, Webview};
|
||||
|
||||
fn lists() -> MutexGuard<'static, HashMap<String, Vec<PaletteCommand>>> {
|
||||
static LISTS: OnceLock<Mutex<HashMap<String, Vec<PaletteCommand>>>> = OnceLock::new();
|
||||
LISTS.get_or_init(Default::default).lock().unwrap_or_else(|poisoned| poisoned.into_inner())
|
||||
}
|
||||
|
||||
/// Stores what tab `label` has (empty = none) and tells the shell when it changed.
|
||||
fn store(app: &AppHandle, label: &str, list: Vec<PaletteCommand>) {
|
||||
let Some(id) = crate::policy::tab_id(label) else { return };
|
||||
let changed = {
|
||||
let mut lists = lists();
|
||||
let changed = lists.get(label).map(Vec::as_slice).unwrap_or(&[]) != list.as_slice();
|
||||
if list.is_empty() {
|
||||
lists.remove(label);
|
||||
} else {
|
||||
lists.insert(label.to_owned(), list.clone());
|
||||
}
|
||||
changed
|
||||
};
|
||||
if changed {
|
||||
#[derive(Clone, Serialize)]
|
||||
struct Commands {
|
||||
id: String,
|
||||
commands: Vec<PaletteCommand>,
|
||||
}
|
||||
emit_shell(app, palette::EVENT, Commands { id: id.to_owned(), commands: list });
|
||||
}
|
||||
}
|
||||
|
||||
/// A new document started in tab `label`: its plugins register their commands again.
|
||||
pub(crate) fn cleared(app: &AppHandle, label: &str) {
|
||||
store(app, label, Vec::new());
|
||||
}
|
||||
|
||||
/// A tab reported its commands. Accepted only from a tab that is on the site right now; the list
|
||||
/// is validated as a whole (any bad entry rejects the report).
|
||||
pub fn reported(app: &AppHandle, webview: &Webview, list: Vec<PaletteCommand>) -> Result<(), String> {
|
||||
if !webview.url().is_ok_and(|url| config(app).site.is_origin(&url)) {
|
||||
return Ok(());
|
||||
}
|
||||
store(app, webview.label(), palette::validate(list)?);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Runs command `command` in tab `id` (the caller checked it is the shell).
|
||||
pub(crate) fn run(app: &AppHandle, id: &str, command: &str) -> Result<(), String> {
|
||||
let label = crate::policy::tab_label(id);
|
||||
if !lists().get(&label).is_some_and(|list| list.iter().any(|item| item.id == command)) {
|
||||
return Err("VALIDATION: that tab has no such command".into());
|
||||
}
|
||||
let site = config(app).site;
|
||||
let tab = tab_webview(app, id).ok_or("VALIDATION: no such tab")?;
|
||||
if !tab.url().is_ok_and(|url| site.is_origin(&url)) {
|
||||
return Err("VALIDATION: the tab is not on the site".into());
|
||||
}
|
||||
let script = palette::run_script(site, command)?;
|
||||
let _ = tab.set_focus();
|
||||
tab.eval(&script).map_err(|error| format!("TABS: {error}"))
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue