Safe mode: one run with only the shell working (the app menu, --safe-mode, Shift at launch on Windows)
Host: `safe.rs` is a flag in this process's memory, never written to disk, so the next normal start is back to the owner's setup. While it is on a site tab gets the tab bridge and nothing of the app's page layers: its document-start scripts are `SiteHooks::safe_init_scripts` (default: the bridge alone) instead of `init_scripts`; `site_page_loaded`, `tab_placed` and `tab_background` are not called; `page_scripts` is empty; `eval_in_site_tabs` does nothing. So no page runtime, plugins, theme, custom CSS, feature flags, fixes, hotfix data or words reach a page: the site runs as in a browser, and only the shell is the app's. It starts from the launch argument `--safe-mode` (for when the interface itself is broken), from Shift held while the app starts (Windows), or from the shell: `safe_mode_get` / `safe_mode_set {on}` (shell-only), where a switch rebuilds every tab webview (profiles marked stale, `tabs-rebuild` asks the shell for its layout) so document-start scripts change too. Tests: the argument, the flag, and that app.rs and host.rs gate every layer.
Frame: the app menu's "Restart in safe mode…" says what it does before anything reloads; while on, the titlebar shows an amber "Safe mode | Leave" chip (one click leaves) and the row reads "Leave safe mode"; the chrome drops its theme (and pane themes) and takes it back on leaving, without touching the first-frame cache. `features.safeMode` (on by default).
This commit is contained in:
parent
b55c802097
commit
8dd570147e
19 changed files with 342 additions and 20 deletions
|
|
@ -137,6 +137,13 @@ pub trait SiteHooks: Send + Sync + 'static {
|
|||
/// (`waybetter_host::bridge`), then the app's page runtime.
|
||||
fn init_scripts(&self, app: &AppHandle, tab: &NewTab) -> Vec<String>;
|
||||
|
||||
/// The document-start scripts of a tab in safe mode ([`crate::safe`]) instead of
|
||||
/// [`Self::init_scripts`]: by default the host's tab bridge alone, so nothing of the app's page
|
||||
/// layers runs. An app returns more here only for a layer it wants to survive safe mode.
|
||||
fn safe_init_scripts(&self, _app: &AppHandle, _tab: &NewTab) -> Vec<String> {
|
||||
vec![crate::bridge::SOURCE.to_owned()]
|
||||
}
|
||||
|
||||
/// The new tab webview's background until the page paints.
|
||||
fn tab_background(&self, _app: &AppHandle, _label: &str) -> Option<tauri::window::Color> {
|
||||
None
|
||||
|
|
@ -272,6 +279,10 @@ impl PageScripts {
|
|||
/// The page state as scripts for a site document that just loaded (evaluate them natively, so the
|
||||
/// page's CSP cannot block them). An app with scripts of its own places them between these.
|
||||
pub fn page_scripts(app: &AppHandle) -> Result<PageScripts, String> {
|
||||
// Safe mode: a site page gets none of them.
|
||||
if crate::safe::active() {
|
||||
return Ok(PageScripts { features: String::new(), links: String::new(), css: None, plugins: Vec::new() });
|
||||
}
|
||||
let site = config(app).site;
|
||||
let page = page(app)?;
|
||||
let css = page.plugins.css();
|
||||
|
|
@ -484,7 +495,7 @@ pub(crate) async fn tabs_sync(webview: Webview, app: AppHandle, items: Vec<TabLa
|
|||
let new_tab = NewTab { id: &item.id, label: &label, profile: &item.profile, item: &item };
|
||||
let builder = tab_builder(&app, &hooks, &new_tab, url, data_dir);
|
||||
// The site's page color until it paints (never the WebView's white).
|
||||
let builder = match hooks.tab_background(&app, &label) {
|
||||
let builder = match crate::safe::layer(crate::safe::active(), || hooks.tab_background(&app, &label), || None) {
|
||||
Some(color) => builder.background_color(color),
|
||||
None => builder,
|
||||
};
|
||||
|
|
@ -506,7 +517,9 @@ pub(crate) async fn tabs_sync(webview: Webview, app: AppHandle, items: Vec<TabLa
|
|||
}
|
||||
}
|
||||
};
|
||||
hooks.tab_placed(&app, &tab, &item);
|
||||
if !crate::safe::active() {
|
||||
hooks.tab_placed(&app, &tab, &item);
|
||||
}
|
||||
host::place_tab(&tab, (x, y, width, height), visible, item.parked || rect.is_none());
|
||||
placed.push((item.id.clone(), (x, y, width, height), visible));
|
||||
}
|
||||
|
|
@ -568,6 +581,38 @@ async fn tab_find(webview: Webview, app: AppHandle, id: String, action: String,
|
|||
host::tab_find(&app, &id, &action, query.as_deref())
|
||||
}
|
||||
|
||||
/// Whether safe mode is on ([`crate::safe`]): the shell shows it in the titlebar and drops its chrome theme.
|
||||
#[tauri::command]
|
||||
async fn safe_mode_get(webview: Webview) -> Result<crate::safe::SafeState, String> {
|
||||
shell_only(&webview)?;
|
||||
Ok(crate::safe::state())
|
||||
}
|
||||
|
||||
/// Switches safe mode for this run (memory only, never saved) and rebuilds every tab webview, so
|
||||
/// each page starts again with or without the app's page layers. The app menu's "Restart in safe
|
||||
/// mode" and the titlebar's "Leave".
|
||||
#[tauri::command]
|
||||
async fn safe_mode_set(webview: Webview, app: AppHandle, on: bool) -> Result<crate::safe::SafeState, String> {
|
||||
shell_only(&webview)?;
|
||||
if crate::safe::set(on) {
|
||||
emit_shell(&app, crate::safe::EVENT, crate::safe::state());
|
||||
rebuild_tabs(&app);
|
||||
}
|
||||
Ok(crate::safe::state())
|
||||
}
|
||||
|
||||
/// Every tab webview is built again on the shell's next layout: their recorded profiles are marked
|
||||
/// stale (`tabs_sync` then closes and recreates each one with the document-start scripts of the
|
||||
/// moment) and the shell is asked to send its layout.
|
||||
pub fn rebuild_tabs(app: &AppHandle) {
|
||||
if let Ok(mut data) = profiles(app) {
|
||||
for profile in data.tab_profiles.values_mut() {
|
||||
*profile = crate::safe::STALE_PROFILE.to_owned();
|
||||
}
|
||||
}
|
||||
emit_shell(app, crate::safe::EVENT_REBUILD, ());
|
||||
}
|
||||
|
||||
/// Gives keyboard focus back to the shell (e.g. Ctrl+L pressed inside a tab).
|
||||
#[tauri::command]
|
||||
async fn shell_focus(webview: Webview) -> Result<(), String> {
|
||||
|
|
@ -848,7 +893,8 @@ fn tab_builder(app: &AppHandle, hooks: &Arc<dyn SiteHooks>, tab: &NewTab, url: U
|
|||
builder = builder.data_store_identifier(profiles::store_identifier(tab.profile));
|
||||
}
|
||||
}
|
||||
for script in hooks.init_scripts(app, tab) {
|
||||
// Safe mode: the safe set (the tab bridge) instead of the app's page layers.
|
||||
for script in crate::safe::layer(crate::safe::active(), || hooks.init_scripts(app, tab), || hooks.safe_init_scripts(app, tab)) {
|
||||
builder = builder.initialization_script(script);
|
||||
}
|
||||
builder
|
||||
|
|
@ -927,7 +973,7 @@ fn tab_builder(app: &AppHandle, hooks: &Arc<dyn SiteHooks>, tab: &NewTab, url: U
|
|||
host::restore_tab_zoom(app, &tab);
|
||||
}
|
||||
emit_tab_state(app, TabState { id: load_id.clone(), url: Some(payload.url().to_string()), loading: Some(!finished), ..Default::default() });
|
||||
if finished && site.is_origin(payload.url()) {
|
||||
if finished && site.is_origin(payload.url()) && !crate::safe::active() {
|
||||
load_hooks.site_page_loaded(app, &tab, payload.url());
|
||||
}
|
||||
})
|
||||
|
|
@ -984,6 +1030,8 @@ fn host_handler() -> impl Fn(tauri::ipc::Invoke<Wry>) -> bool + Send + Sync + 's
|
|||
tab_action,
|
||||
tab_zoom,
|
||||
tab_find,
|
||||
safe_mode_get,
|
||||
safe_mode_set,
|
||||
open_external_url,
|
||||
shell_focus,
|
||||
shell_ready,
|
||||
|
|
@ -1118,6 +1166,8 @@ impl App {
|
|||
});
|
||||
tauri_app.manage(Page(Mutex::new(PageState { plugins, features, settings, modes })));
|
||||
tauri_app.manage(Host { config, hooks });
|
||||
// Safe mode for this run: the launch argument, or Shift held while starting (Windows).
|
||||
crate::safe::set(crate::safe::at_launch(std::env::args(), host::shift_held()));
|
||||
#[cfg(debug_assertions)]
|
||||
crate::devloop::start(&handle);
|
||||
if let Some(setup) = setup {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue