//! wayBetter native host. //! //! A wayBetter app is one native window with a trusted local **shell** webview (tab strip, address //! bars, split panes, settings) and one native child webview per browser tab, positioned over the //! pane rectangles the shell reports. One site (x.com, web.whatsapp.com, kick.com, ...) is the //! app's *site*: its tabs get the app's page runtime and may call a small set of site-only commands. //! //! This crate holds the site-agnostic parts, all headless-testable: //! - [`site`]: the site adapter ([`site::SiteSpec`], [`site::AccountRules`]). //! - [`policy`]: webview labels, origin checks and the tab rectangle clamp (security boundary). //! - [`profiles`]: account profiles, one WebView data directory each, with the account seen inside. //! - [`plugins`]: owner-installed `*.plugin.js` files, custom CSS and bundled-plugin upgrades; //! [`plugins::migrate`]: the one-time move of replaced plugin files to feature flags; //! [`plugins::commands`]: the palette commands site tabs report and the script that runs one. //! - [`hotfix`]: the signed, data-only hotfix channel. //! - [`update`]: silent-update scheduling and the crash handshake. //! - [`tabs`]: tab layout/state payloads, link routing and tab-chrome validation. //! - [`bridge`]: the tab bridge script every tab gets (URL/title/focus reports, shortcut and link-drag forwarding). //! - [`layout`]: native placement bookkeeping (unchanged bounds skipped, window-resize follow). //! - [`i18n`]: the strings native UI and site tabs get in the owner's language (validated data), OS locale. //! - [`backup`]: the settings backup envelope, secret scrubbing and the plugin/account sections. //! - [`sync`]: encrypted settings sync through a folder the owner syncs (Argon2id + XChaCha20-Poly1305). //! - [`theme`]: the owner's theme choice and account/pane themes (envelope checks, which theme a tab //! shows, its page color and delivery script) over a site's built-in themes. //! - [`workspaces`]: saved tab/pane/deck layouts, bounded and well-formed (never evaluated). //! - [`devmode`]: developer mode's debounced watcher of plugins, custom CSS and a theme file. //! - `devloop` (debug builds with `app` only): page scripts read from disk and every tab rebuilt when //! one changes; an app names its files with [`page_script!`]. //! - [`settings`]: the shell's settings (home page, where links open, session restore) and the page's //! feature flags, as validated data and the scripts that hand them to site tabs. //! - [`modes`]: the onboarding mode the owner started from (the presets are the app's feature table). //! - [`zoom`]: page zoom per tab (a clamped factor the shell sets, remembered in `zoom.json`). //! - [`safe`]: safe mode for one run (a flag in memory: site tabs get the tab bridge and nothing else). //! - [`find`]: find in page (the request the shell sends, the fallback script; the query is kept nowhere). //! - [`alerts`]: local keyword alerts (keywords, snippets, per-item/per-keyword rate limits). //! - [`media`]: gallery folders (path containment) and the opt-in clipboard history (private copies skipped). //! - [`gfonts`]: self-hosted Google Fonts: CSS parsing, URL/woff2 checks, the cache format, the page script; //! [`fonts`] (`net` feature) downloads and caches them. //! - [`store`]: installing themes and plugins from `waybetter://install` links, dropped files and the //! signed multi-app wayBetter Store (allow-list, caps, catalog signatures, staging, install records); //! downloads with the `net` feature. //! //! - [`commands`]: the commands the host serves for every app and who may call them (for `build.rs`); //! [`testkit`]: capability and caller-check assertions for an app's security tests. //! //! With the `tauri` feature, [`host`] adds the Tauri glue that does not depend on app state; with //! `tauri` and `net`, [`updater`] runs signed native updates and the hotfix channel. With `app`, //! [`app`] is the whole app around a site: [`app::run_app`] makes the window, the shell and the tab //! webviews, registers the plugins and serves the generic commands (tab layout and actions, profiles, //! language, the plugins folder and custom CSS, page tweaks, the shell's settings); the app adds its own. pub mod alerts; pub mod backup; pub mod bridge; pub mod commands; pub mod devmode; pub mod find; #[cfg(feature = "net")] pub mod fonts; pub mod gfonts; pub mod hotfix; pub mod i18n; pub mod layout; pub mod media; pub mod modes; pub mod plugins; pub mod policy; pub mod profiles; pub mod safe; pub mod settings; pub mod site; pub mod store; pub mod sync; pub mod tabs; pub mod testkit; pub mod theme; pub mod update; pub mod workspaces; pub mod zoom; #[cfg(feature = "app")] pub mod app; #[cfg(all(feature = "app", feature = "net"))] mod store_commands; #[cfg(all(feature = "app", debug_assertions))] pub mod devloop; #[cfg(feature = "tauri")] pub mod host; #[cfg(all(feature = "tauri", feature = "net"))] pub mod updater; /// An app's page script for `SiteHooks::init_scripts`: `$embedded` (an `include_str!`) in every /// build, except that a debug build reads `$file` (relative to the app crate's manifest directory) /// from disk each time and falls back to the embedded copy when it is missing. The files named /// this way are watched: a change rebuilds every tab (`devloop`). In a release build the disk arm /// is not compiled at all: the expansion is `$embedded.to_owned()`, `$file` appears nowhere. #[macro_export] macro_rules! page_script { ($embedded:expr, $file:literal) => {{ #[cfg(debug_assertions)] { $crate::devloop::from_disk(concat!(env!("CARGO_MANIFEST_DIR"), "/", $file), $embedded) } #[cfg(not(debug_assertions))] { ($embedded).to_owned() } }}; } #[cfg(test)] mod release_guard { /// Release builds must not contain the dev loop: every mention of it in this crate is behind /// `cfg(debug_assertions)` (checked on the source, so it also holds when only debug is built). #[test] fn every_devloop_mention_is_debug_only() { for (file, source) in [("lib.rs", include_str!("lib.rs")), ("app.rs", include_str!("app.rs"))] { // This test module itself talks about the dev loop; only the code before it counts. let lines: Vec<&str> = source.split("mod release_guard").next().unwrap_or(source).lines().collect(); for (index, line) in lines.iter().enumerate() { let code = line.trim_start(); if code.starts_with("//") || !(code.contains("devloop::") || code.starts_with("pub mod devloop")) || code.contains("$crate::devloop") { continue; } let gated = (index.saturating_sub(2)..index).any(|before| lines[before].contains("debug_assertions")); assert!(gated, "{file}:{}: devloop used outside cfg(debug_assertions)", index + 1); } } } /// `cargo test --release`: the macro is just the embedded text. #[cfg(not(debug_assertions))] #[test] fn release_page_script_is_the_embedded_copy() { assert_eq!(crate::page_script!("embedded", "no/such/file.js"), "embedded"); } }