Chat+ link previews: a link in a chat shows its page's title, text and picture, YouTube playable in place

X's web chat shows a link as a bare card. An X tab on a chat page may now ask native code
(link_preview, x-capture only, chat pages only) for one link's preview. Native code fetches it:
https and public hosts only (addresses checked and pinned), never X's hosts, no cookies or
referrer, redirects re-checked, page head <= 512 KB, images <= 1.5 MB by magic bytes as data:,
30 fetches a minute, memory-only cache. Only the link leaves the page. On by default (owner,
2026-10-10).
This commit is contained in:
CRTkafa 2026-10-10 21:47:31 +08:00
parent 7530fb1de7
commit 6d1798038c
21 changed files with 1455 additions and 519 deletions

View file

@ -1,430 +1,434 @@
use url::Url;
use better_twttr::{policy::*, site::SITE, theme::{x_default, XTheme, X_THEMES}};
#[test]
fn exact_origin_and_window_scope_is_enforced() {
let x = Url::parse("https://x.com/home").unwrap();
assert!(require_capture("tab-1", &x).is_ok());
assert!(require_capture("shell", &x).is_err());
assert!(require_local(
"tab-1",
&Url::parse("tauri://localhost/index.html").unwrap(),
false
)
.is_err());
for value in [
"https://evil.x.com/home",
"http://x.com/home",
"https://x.com:8443/home",
"https://x.com.evil.test/home",
"https://x.com@evil.test/home",
"https://evil@x.com/home",
"file:///tmp/index.html",
"https://twitter.com/home",
] {
assert!(
require_capture("tab-1", &Url::parse(value).unwrap()).is_err(),
"accepted {value}"
);
}
}
#[test]
fn local_origin_has_no_production_localhost_backdoor() {
for value in [
"tauri://localhost/index.html",
"http://tauri.localhost/index.html",
"https://tauri.localhost/index.html",
] {
assert!(require_local("shell", &Url::parse(value).unwrap(), false).is_ok());
}
assert!(require_local("shell", &Url::parse("http://127.0.0.1:1420/").unwrap(), true).is_ok());
for value in [
"https://x.com/",
"http://127.0.0.1:1420/",
"http://localhost:1420/",
"https://tauri.localhost.evil.test/",
"tauri://evil/",
] {
assert!(
require_local("shell", &Url::parse(value).unwrap(), false).is_err(),
"accepted {value}"
);
}
}
#[test]
fn navigation_allows_only_x_and_specific_legacy_login_routes() {
for value in [
"https://x.com/home",
"https://twitter.com/login",
"https://twitter.com/i/flow/login",
"https://twitter.com/i/flow/login/step",
] {
assert!(x_navigation_allowed(&Url::parse(value).unwrap()));
}
for value in [
"https://twitter.com/home",
"https://t.co/test",
"https://accounts.google.com/",
"javascript:alert(1)",
"mailto:test@example.com",
"https://twitter.com/login/evil",
] {
assert!(
!x_navigation_allowed(&Url::parse(value).unwrap()),
"accepted {value}"
);
}
}
#[test]
fn remote_capabilities_cannot_reach_the_vault() {
let read = |json: &str| serde_json::from_str::<serde_json::Value>(json).unwrap();
let x = read(include_str!("../capabilities/x-capture.json"));
// Drafts popover (2026-09-27): X may only open/close it about itself and ask one yes/no hint question.
assert_eq!(x["permissions"], serde_json::json!(["allow-capture-draft", "allow-open-panel", "allow-drafts-picker-open", "allow-drafts-popover-dismiss", "allow-drafts-hint", "allow-keyword-alert"]));
for vault in ["allow-popover-drafts", "allow-popover-insert", "allow-popover-state", "allow-draft-list"] {
assert!(!x["permissions"].as_array().unwrap().iter().any(|p| p == vault), "X tabs have {vault}");
}
// The drafts popover is local-only and gets nothing beyond its own five commands.
let popover = read(include_str!("../capabilities/drafts-popover.json"));
assert_eq!(popover["webviews"], serde_json::json!(["popover-*"]));
assert_eq!(popover["local"], true);
assert!(popover.get("remote").is_none());
assert_eq!(popover["permissions"], serde_json::json!(["allow-popover-state", "allow-popover-drafts", "allow-popover-insert", "allow-popover-close", "allow-popover-open-vault"]));
assert_eq!(x["webviews"], serde_json::json!(["tab-*"]));
assert_eq!(x["remote"]["urls"], serde_json::json!(["https://x.com/*"]));
assert_eq!(x["local"], false);
// Linux joined with the same permissions (owner request, 2026-09-28): platforms only, no new grants.
assert_eq!(x["platforms"], serde_json::json!(["macOS", "windows", "linux"]));
let tabs = read(include_str!("../capabilities/tab-chrome.json"));
assert_eq!(tabs["permissions"], serde_json::json!(["allow-tab-report", "allow-tab-shortcut", "allow-tab-link-drag"]));
assert_eq!(tabs["webviews"], serde_json::json!(["tab-*"]));
assert_eq!(tabs["local"], false);
let local = read(include_str!("../capabilities/workspace-local.json"));
assert!(local.get("remote").is_none());
// My Space (profile customization) is removed: its commands do not exist, so no capability
// file grants them and neither the command manifest nor the handler names them.
let (build, desktop) = (include_str!("../build.rs"), include_str!("../src/desktop.rs"));
for gone in [
"profile_owner_get", "profile_create", "profile_api", "profile_media_pick", "profile_media_still", "profile_media_upload", "profile_still_to_x", "profile_view_get", "profile_view_set",
"bt_profiles_resolve", "bt_media_get", "bt_profile_link_seen",
] {
let permission = format!("allow-{}", gone.replace('_', "-"));
for (name, capability) in [("x-capture", &x), ("drafts-popover", &popover), ("tab-chrome", &tabs), ("workspace-local", &local)] {
assert!(!capability["permissions"].as_array().unwrap().iter().any(|p| p == permission.as_str()), "{name} grants {permission}");
}
assert!(!build.contains(&format!("\"{gone}\"")), "build.rs still lists {gone}");
assert!(!desktop.contains(gone), "desktop.rs still names {gone}");
}
assert_eq!(local["webviews"], serde_json::json!(["shell"]));
// Update commands are shell-only; no capability exposes the updater/process plugin JS APIs.
let perms = |v: &serde_json::Value| v["permissions"].as_array().unwrap().iter().map(|p| p.as_str().unwrap().to_owned()).collect::<Vec<_>>();
// Account profiles (separate X logins) are managed only by the shell.
// Drafts are inserted and alert keywords edited only by the shell; X tabs only ask/report.
for name in [
"allow-update-status", "allow-apply-update", "allow-app-ready", "allow-profiles-get", "allow-profiles-set",
"allow-tab-insert-text", "allow-alerts-get", "allow-alerts-set", "allow-draft-list", "allow-draft-revisions",
// Google Fonts downloads (network), account/pane themes and the builder preview: shell only.
"allow-gfonts-installed", "allow-gfonts-install", "allow-gfonts-remove", "allow-gfonts-faces", "allow-gfonts-preview",
"allow-theme-scopes-get", "allow-theme-scopes-set", "allow-theme-preview",
// Installing themes/plugins (links, dropped files, the signed store; owner-approved 2026-09-28):
// only the shell, after its confirmation screen. X tabs and web pages cannot install anything.
"allow-install-pending", "allow-install-stage-link", "allow-install-stage-file", "allow-install-cancel",
"allow-install-commit", "allow-install-records", "allow-install-forget", "allow-plugin-remove",
"allow-store-catalog", "allow-store-stage",
] {
assert!(perms(&local).iter().any(|p| p == name), "shell lacks {name}");
assert!(!perms(&x).iter().chain(perms(&tabs).iter()).any(|p| p == name), "tab has {name}");
}
for p in perms(&local).iter().chain(perms(&x).iter()).chain(perms(&tabs).iter()) {
assert!(
!p.starts_with("updater:") && !p.starts_with("process:") && !p.starts_with("notification:") && !p.starts_with("deep-link:"),
"plugin API exposed: {p}"
);
}
}
#[test]
fn theme_accepts_only_known_presets_and_hex_accents() {
let script = |theme: XTheme| theme.script(&X_THEMES, &SITE);
let valid = XTheme { preset: "night-ink".into(), accent: Some("#1d9bf0".into()), ..x_default() };
let text = script(valid).unwrap();
assert!(text.contains("location.origin!=='https://x.com'"));
assert!(text.contains(r#""preset":"night-ink""#));
assert!(x_default().is_default(&X_THEMES));
for preset in ["x", "classic", "night-ink", "terminal", "paper"] {
assert!(script(XTheme { preset: preset.into(), ..x_default() }).is_ok());
}
for hostile in ["evil", "x;display:none", "</script>", ""] {
assert!(script(XTheme { preset: hostile.into(), ..x_default() }).is_err(), "accepted {hostile}");
}
for hostile in ["red", "#fff", "url(https://evil.test)", "#000000;display:none", "</style><script>", "#00000é"] {
assert!(script(XTheme { accent: Some(hostile.into()), ..x_default() }).is_err(), "accepted {hostile}");
}
assert!(serde_json::from_str::<XTheme>(r#"{"preset":"x","css":"body{}"}"#).is_err());
}
#[test]
fn bundle_keeps_macos_targets_and_signed_updater() {
let conf: serde_json::Value = serde_json::from_str(include_str!("../tauri.conf.json")).unwrap();
let targets = conf["bundle"]["targets"].as_array().unwrap();
for target in ["nsis", "app", "dmg"] {
assert!(targets.iter().any(|t| t == target), "missing bundle target {target}");
}
assert!(!conf["plugins"]["updater"]["pubkey"].as_str().unwrap_or("").is_empty());
for endpoint in conf["plugins"]["updater"]["endpoints"].as_array().unwrap() {
assert!(endpoint.as_str().unwrap().starts_with("https://"), "updater endpoint must be https");
}
// One URL scheme, only for install links; the installer/Info.plist register exactly this.
assert_eq!(conf["plugins"]["deep-link"]["desktop"]["schemes"], serde_json::json!(["waybetter"]));
assert!(conf["plugins"]["deep-link"].get("mobile").is_none());
}
#[test]
fn backup_sync_workspace_palette_and_dev_commands_are_shell_only() {
let read = |json: &str| serde_json::from_str::<serde_json::Value>(json).unwrap();
let perms = |v: &serde_json::Value| v["permissions"].as_array().unwrap().iter().map(|p| p.as_str().unwrap().to_owned()).collect::<Vec<_>>();
let local = perms(&read(include_str!("../capabilities/workspace-local.json")));
let x = perms(&read(include_str!("../capabilities/x-capture.json")));
let tabs = perms(&read(include_str!("../capabilities/tab-chrome.json")));
let popover = perms(&read(include_str!("../capabilities/drafts-popover.json")));
// Settings backup/sync (files and the sync key stay native), workspaces, running a plugin's
// palette command in a tab, and developer mode (file watcher, hot reload).
for name in [
"allow-tab-run-command", "allow-workspaces-get", "allow-workspaces-set",
"allow-backup-collect", "allow-backup-export-file", "allow-backup-open-file", "allow-backup-apply",
"allow-sync-status", "allow-sync-choose-folder", "allow-sync-unlock", "allow-sync-lock", "allow-sync-disable",
"allow-sync-configure", "allow-sync-push", "allow-sync-check", "allow-sync-open", "allow-sync-mark-pulled",
"allow-dev-get", "allow-dev-set", "allow-dev-pick-theme", "allow-dev-clear-theme", "allow-dev-reload-plugin", "allow-dev-report-error",
] {
assert!(local.iter().any(|p| p == name), "shell lacks {name}");
assert!(!x.iter().chain(tabs.iter()).chain(popover.iter()).any(|p| p == name), "a tab or the popover has {name}");
}
// X tabs report their plugins' commands only through tab_report (owner-approved 2026-09-28);
// the tab capabilities gained nothing for it.
assert_eq!(tabs, ["allow-tab-report", "allow-tab-shortcut", "allow-tab-link-drag"]);
}
#[test]
fn language_commands_are_shell_only() {
let read = |json: &str| serde_json::from_str::<serde_json::Value>(json).unwrap();
let perms = |v: &serde_json::Value| v["permissions"].as_array().unwrap().iter().map(|p| p.as_str().unwrap().to_owned()).collect::<Vec<_>>();
let local = perms(&read(include_str!("../capabilities/workspace-local.json")));
let others: Vec<String> = [
include_str!("../capabilities/x-capture.json"),
include_str!("../capabilities/tab-chrome.json"),
include_str!("../capabilities/drafts-popover.json"),
]
.iter()
.flat_map(|json| perms(&read(json)))
.collect();
// The shell picks the language and hands native code and X tabs their strings; tabs and the
// popover only ever receive them (X tabs as data through the runtime), never set them.
for command in ["allow-i18n-get", "allow-i18n-set"] {
assert!(local.iter().any(|p| p == command), "shell lacks {command}");
assert!(!others.iter().any(|p| p == command), "a tab or the popover has {command}");
}
// The commands are the framework's (waybetter-host `app`); each checks shell_only first.
for command in ["i18n_get", "i18n_set"] {
waybetter_host::testkit::assert_guarded(include_str!("../../../../waybetter/crates/waybetter-host/src/app.rs"), command, "shell_only(&webview)?");
}
// X tabs get the strings only as a guarded data value, and only the runtime's own prefixes.
let source = include_str!("../src/desktop_i18n.rs");
assert!(source.contains(r#"&["x.", "kit.", "plugin."]"#), "X tabs receive only x./kit./plugin. strings");
assert!(include_str!("../src/desktop.rs").contains("page_string_prefixes: crate::desktop_i18n::PAGE_PREFIXES"), "X's config hands tabs exactly those");
let strings = waybetter_host::i18n::NativeStrings {
locale: "tr".into(),
preference: "site".into(),
messages: [("x.fab.title", "wayBetter"), ("kit.ok", "Tamam"), ("native.notify.alert.title", "Uyarı")]
.into_iter()
.map(|(key, value)| (key.to_owned(), value.to_owned()))
.collect(),
};
let script = waybetter_host::i18n::page_script(&better_twttr::site::SITE, &strings.page_payload(&["x.", "kit.", "plugin."]));
assert!(script.starts_with("if(location.origin==='https://x.com')("), "page strings must be x.com only: {script}");
assert!(script.contains("x.fab.title") && script.contains("kit.ok") && !script.contains("native.notify"), "{script}");
// "Follow X": a tab may report X's <html lang>, only as a valid tag and only while on x.com.
let desktop = include_str!("../src/desktop.rs");
assert!(
desktop.contains("report.lang.filter(|lang| waybetter_host::i18n::valid_tag(lang) && webview.url().is_ok_and(|url| policy::x_origin(&url)))"),
"tab_report lang must be a validated tag from a webview on x.com"
);
}
#[test]
fn language_strings_are_plain_text() {
use waybetter_host::i18n::NativeStrings;
let set = |value: &str| NativeStrings {
locale: "ar".into(),
preference: "site".into(),
messages: [("x.fab.title".to_owned(), value.to_owned())].into_iter().collect(),
};
assert!(set("wayBetter لـ X").validated().is_ok());
for bad in ["<img src=x onerror=alert(1)>", "&lt;script&gt;", "javascript:alert(1)", "a\u{202e}b"] {
assert!(set(bad).validated().is_err(), "{bad:?}");
}
}
#[test]
fn shell_raise_is_shell_only() {
let read = |json: &str| serde_json::from_str::<serde_json::Value>(json).unwrap();
let perms = |v: &serde_json::Value| v["permissions"].as_array().unwrap().iter().map(|p| p.as_str().unwrap().to_owned()).collect::<Vec<_>>();
let local = perms(&read(include_str!("../capabilities/workspace-local.json")));
let x = perms(&read(include_str!("../capabilities/x-capture.json")));
let tabs = perms(&read(include_str!("../capabilities/tab-chrome.json")));
let popover = perms(&read(include_str!("../capabilities/drafts-popover.json")));
// Re-orders the shell webview over the tabs for its own overlays: only the shell may.
assert!(local.iter().any(|p| p == "allow-shell-raise"), "shell lacks allow-shell-raise");
assert!(!x.iter().chain(tabs.iter()).chain(popover.iter()).any(|p| p == "allow-shell-raise"), "a tab or the popover can raise the shell");
// The command is the framework's (waybetter-host `app`).
waybetter_host::testkit::assert_guarded(include_str!("../../../../waybetter/crates/waybetter-host/src/app.rs"), "shell_raise", "shell_only(&webview)?");
waybetter_host::testkit::Capabilities::from_dir(concat!(env!("CARGO_MANIFEST_DIR"), "/capabilities")).assert_shell_only("shell_raise");
}
/// The wayBetter host's own commands (tab layout and actions, shell raise, external links, window
/// menu, profiles, language; tab shortcuts and link drags) keep their callers in X's capability
/// files, and every command wrapper checks its caller first. Every capability file is read, so a
/// new one cannot quietly widen them.
#[test]
fn host_commands_keep_their_callers() {
use waybetter_host::testkit::{assert_guarded, assert_host_guards, Capabilities};
let caps = Capabilities::from_dir(concat!(env!("CARGO_MANIFEST_DIR"), "/capabilities"));
caps.assert_host_commands();
assert_host_guards();
// Only Rust drives the plugins; no capability exposes their JS APIs.
caps.assert_no_plugin_apis(&["updater", "process", "notification", "deep-link", "single-instance", "window-state", "opener", "dialog"]);
// X's own: vault, settings, themes, installs, backups, sync, dev mode, updates are the shell's.
for command in [
"draft_list", "draft_save", "draft_revisions", "draft_status", "observation_list", "data_export",
"gfonts_installed", "gfonts_install", "gfonts_remove", "gfonts_faces", "gfonts_preview",
"tab_run_command", "workspaces_get", "workspaces_set", "backup_collect", "backup_export_file", "backup_open_file", "backup_apply",
"sync_status", "sync_choose_folder", "sync_unlock", "sync_lock", "sync_disable", "sync_configure", "sync_push", "sync_check", "sync_open", "sync_mark_pulled",
"dev_get", "dev_set", "dev_pick_theme", "dev_clear_theme", "dev_reload_plugin", "dev_report_error",
"plugins_list", "plugin_set_enabled", "plugins_open_folder", "plugin_remove",
"install_pending", "install_stage_link", "install_stage_file", "install_stage_opened", "install_cancel", "install_commit", "install_records", "install_forget", "store_catalog", "store_stage",
"custom_css_get", "custom_css_set", "features_get", "features_set", "mode_get", "mode_set", "settings_get", "settings_set", "column_configure",
"update_status", "update_check", "apply_update", "app_ready", "tab_insert_text", "alerts_get", "alerts_set",
"media_get", "media_set", "gallery_add_folder", "gallery_list", "gallery_thumb", "clip_list", "clip_thumb", "clip_remove", "tab_attach_image",
] {
caps.assert_shell_only(command);
}
// Tabs, about themselves only; the X-only ones are granted only on https://x.com.
for command in ["tab_report", "capture_draft", "open_panel", "drafts_picker_open", "drafts_popover_dismiss", "drafts_hint", "keyword_alert"] {
caps.assert_tab_only(command);
}
let desktop = include_str!("../src/desktop.rs");
assert_guarded(desktop, "tab_insert_text", "shell_only(&webview)?");
// The vault commands are the host's: shell-only in the capability files and each checking its caller
// first; X's own handler no longer has them, and X turns the vault on with its rules.
caps.assert_shell_only_group(waybetter_host::commands::VAULT);
waybetter_host::testkit::assert_vault_guards();
assert!(desktop.contains(".with_vault("), "the app enables the host's vault");
for command in waybetter_host::commands::VAULT {
assert!(!desktop.contains(&format!("fn {command}(")), "{command} is served by the host only");
}
// The updater commands are the shell's too, each checking its caller first; `update_check` runs the
// timer's own check once (no other endpoint), and tabs have no updater command at all.
caps.assert_shell_only_group(waybetter_host::commands::UPDATE);
waybetter_host::testkit::assert_update_guards();
assert!(desktop.contains(".with_updates("), "the app enables the updater");
// Plugins, custom CSS, the tweak flags and the browser settings are served by the host: they
// are in its shell-only list (checked against the capability files and for their caller
// check above), and X's own handler no longer has them.
let host = include_str!("../../../../waybetter/crates/waybetter-host/src/app.rs");
for command in ["plugins_list", "plugin_set_enabled", "plugins_open_folder", "custom_css_get", "custom_css_set", "features_get", "features_set", "mode_get", "mode_set", "settings_get", "settings_set"] {
assert!(waybetter_host::commands::SHELL.contains(&command), "{command} is a host shell-only command");
assert_guarded(host, command, "shell_only(&webview)?");
assert!(!desktop.contains(&format!("fn {command}(")), "{command} is served by the host only");
}
assert_guarded(desktop, "tab_report", "tab_only(&webview)?");
// Opening the panel from a page is the host's site command: only X tabs on x.com have it.
caps.assert_site_only("open_panel", &better_twttr::site::SITE);
assert!(!desktop.contains("fn open_panel("), "open_panel is served by the host only");
}
#[test]
fn install_store_media_and_font_commands_are_shell_only() {
// The store's commands are the framework's (waybetter-host `store_commands`, served because
// this app sets `AppConfig::store`): shell-only in the capability files, and each wrapper
// checks shell_only before anything else.
use waybetter_host::testkit::assert_guarded;
let caps = waybetter_host::testkit::Capabilities::from_dir(concat!(env!("CARGO_MANIFEST_DIR"), "/capabilities"));
caps.assert_shell_only_group(waybetter_host::commands::STORE);
waybetter_host::testkit::assert_store_guards();
// The media panel's commands are the framework's too (AppConfig::with_media): shell-only, each
// checking its caller first; only attaching to X's composer is the app's own.
caps.assert_shell_only_group(waybetter_host::commands::MEDIA);
waybetter_host::testkit::assert_media_guards();
// Workspaces and encrypted sync too (AppConfig::with_sync; workspaces are served for every app).
caps.assert_shell_only_group(waybetter_host::commands::WORKSPACES);
caps.assert_shell_only_group(waybetter_host::commands::SYNC);
waybetter_host::testkit::assert_sync_guards();
// The settings backup's commands are the framework's too (what it carries is the app's hooks).
caps.assert_shell_only_group(waybetter_host::commands::BACKUP);
waybetter_host::testkit::assert_backup_guards();
// Developer mode (AppConfig::with_dev): the framework's commands; the theme file is the app's hook.
caps.assert_shell_only_group(waybetter_host::commands::DEV);
waybetter_host::testkit::assert_dev_guards();
assert!(include_str!("../src/desktop.rs").contains(".with_dev()"), "the app enables developer mode");
assert!(include_str!("../src/desktop.rs").contains(".with_sync()"), "the app enables sync");
// Site themes too (AppConfig::with_themes): the framework's commands; X only names its built-in themes.
caps.assert_shell_only_group(waybetter_host::commands::THEME);
waybetter_host::testkit::assert_theme_guards();
assert!(include_str!("../src/desktop.rs").contains(".with_themes(&crate::theme::THEMES)"), "the app configures its themes");
// The keyword alert settings (AppConfig::with_alerts): the framework's commands; the hit and its notification are X's.
caps.assert_shell_only_group(waybetter_host::commands::ALERTS);
waybetter_host::testkit::assert_alerts_guards();
assert!(include_str!("../src/desktop.rs").contains(".with_alerts(&alerts::ALERTS)"), "the app configures its alerts");
// Google Fonts too (AppConfig::with_fonts).
caps.assert_shell_only_group(waybetter_host::commands::GFONTS);
waybetter_host::testkit::assert_fonts_guards();
assert!(include_str!("../src/desktop.rs").contains(".with_fonts()"), "the app enables Google Fonts");
assert!(include_str!("../src/desktop.rs").contains(".with_media()"), "the app enables the media panel");
assert_guarded(include_str!("../src/desktop_media.rs"), "tab_attach_image", "shell_only(&webview)?");
let source = include_str!("../../../../waybetter/crates/waybetter-host/src/store_commands.rs");
// Links only ever propose an install: the deep-link handler queues and emits, nothing more.
let received = &source[source.find("fn received(").unwrap()..];
let received = &received[..received.find("\n}").unwrap()];
assert!(!received.contains("commit") && !received.contains("stage"), "a link must not stage or install by itself");
// The app's store settings: X's id and the framework's hosts; the compiled-in key; and it asks
// the framework to serve the store.
let settings = include_str!("../src/distribution.rs");
assert!(settings.contains("app_id: \"x\"") && settings.contains("allowed_hosts: DEFAULT_HOSTS") && settings.contains("include_str!(\"../keys/store.pub\")"));
assert!(include_str!("../src/desktop.rs").contains(".with_store(&crate::distribution::STORE)"), "the app configures the store");
}
/// The popover's webview and session are the host's; X's commands reached by the popover each check
/// their caller first (the popover webview with the open session), and inserting a draft only
/// focuses the tab and types (`compose` scripts), never clicks Post.
#[test]
fn popover_commands_check_their_caller_and_never_post() {
use waybetter_host::testkit::assert_guarded;
let source = include_str!("../src/desktop_popover.rs");
assert_guarded(source, "popover_state", "host::session_of(&webview)");
for command in ["popover_drafts", "popover_insert", "popover_open_vault"] {
assert_guarded(source, command, "host::current(&webview,");
}
// popover_close only acts for the current popover, and does nothing otherwise.
let close = &source[source.find("fn popover_close(").unwrap()..];
let close = &close[..close.find("
}").unwrap()];
assert!(close.contains("host::current(&webview, seq).is_ok()"));
let insert = &source[source.find("fn popover_insert(").unwrap()..];
let insert = &insert[..insert.find("
}").unwrap()];
assert!(insert.contains("compose::insert_script") && insert.contains("compose::append_script"));
assert!(!insert.contains("click") && !insert.contains("submit") && !insert.contains("Post"), "popover_insert never posts");
assert!(insert.contains("policy::x_origin(&url)"), "only into X tabs");
// The host's popover check: only a `popover-<n>` webview on the app's own local origin.
let host = include_str!("../../../../waybetter/crates/waybetter-host/src/popover_host.rs");
let only = &host[host.find("pub fn popover_only(").unwrap()..];
let only = &only[..only.find("
}").unwrap()];
assert!(only.contains("popover::valid_label(webview.label())") && only.contains("local_navigation_allowed"));
}
use url::Url;
use better_twttr::{policy::*, site::SITE, theme::{x_default, XTheme, X_THEMES}};
#[test]
fn exact_origin_and_window_scope_is_enforced() {
let x = Url::parse("https://x.com/home").unwrap();
assert!(require_capture("tab-1", &x).is_ok());
assert!(require_capture("shell", &x).is_err());
assert!(require_local(
"tab-1",
&Url::parse("tauri://localhost/index.html").unwrap(),
false
)
.is_err());
for value in [
"https://evil.x.com/home",
"http://x.com/home",
"https://x.com:8443/home",
"https://x.com.evil.test/home",
"https://x.com@evil.test/home",
"https://evil@x.com/home",
"file:///tmp/index.html",
"https://twitter.com/home",
] {
assert!(
require_capture("tab-1", &Url::parse(value).unwrap()).is_err(),
"accepted {value}"
);
}
}
#[test]
fn local_origin_has_no_production_localhost_backdoor() {
for value in [
"tauri://localhost/index.html",
"http://tauri.localhost/index.html",
"https://tauri.localhost/index.html",
] {
assert!(require_local("shell", &Url::parse(value).unwrap(), false).is_ok());
}
assert!(require_local("shell", &Url::parse("http://127.0.0.1:1420/").unwrap(), true).is_ok());
for value in [
"https://x.com/",
"http://127.0.0.1:1420/",
"http://localhost:1420/",
"https://tauri.localhost.evil.test/",
"tauri://evil/",
] {
assert!(
require_local("shell", &Url::parse(value).unwrap(), false).is_err(),
"accepted {value}"
);
}
}
#[test]
fn navigation_allows_only_x_and_specific_legacy_login_routes() {
for value in [
"https://x.com/home",
"https://twitter.com/login",
"https://twitter.com/i/flow/login",
"https://twitter.com/i/flow/login/step",
] {
assert!(x_navigation_allowed(&Url::parse(value).unwrap()));
}
for value in [
"https://twitter.com/home",
"https://t.co/test",
"https://accounts.google.com/",
"javascript:alert(1)",
"mailto:test@example.com",
"https://twitter.com/login/evil",
] {
assert!(
!x_navigation_allowed(&Url::parse(value).unwrap()),
"accepted {value}"
);
}
}
#[test]
fn remote_capabilities_cannot_reach_the_vault() {
let read = |json: &str| serde_json::from_str::<serde_json::Value>(json).unwrap();
let x = read(include_str!("../capabilities/x-capture.json"));
// Drafts popover (2026-09-27): X may only open/close it about itself and ask one yes/no hint question.
// Chat+ link previews (owner-approved, 2026-10-10): one link at a time, from a chat page only.
assert_eq!(x["permissions"], serde_json::json!(["allow-capture-draft", "allow-open-panel", "allow-drafts-picker-open", "allow-drafts-popover-dismiss", "allow-drafts-hint", "allow-keyword-alert", "allow-link-preview"]));
let desktop = include_str!("../src/desktop.rs");
waybetter_host::testkit::assert_guarded(desktop, "link_preview", "policy::require_capture(webview.label(), &webview.url()");
assert!(desktop.contains("link_preview::is_chat_page(page.path())"), "link previews answer chat pages only");
for vault in ["allow-popover-drafts", "allow-popover-insert", "allow-popover-state", "allow-draft-list"] {
assert!(!x["permissions"].as_array().unwrap().iter().any(|p| p == vault), "X tabs have {vault}");
}
// The drafts popover is local-only and gets nothing beyond its own five commands.
let popover = read(include_str!("../capabilities/drafts-popover.json"));
assert_eq!(popover["webviews"], serde_json::json!(["popover-*"]));
assert_eq!(popover["local"], true);
assert!(popover.get("remote").is_none());
assert_eq!(popover["permissions"], serde_json::json!(["allow-popover-state", "allow-popover-drafts", "allow-popover-insert", "allow-popover-close", "allow-popover-open-vault"]));
assert_eq!(x["webviews"], serde_json::json!(["tab-*"]));
assert_eq!(x["remote"]["urls"], serde_json::json!(["https://x.com/*"]));
assert_eq!(x["local"], false);
// Linux joined with the same permissions (owner request, 2026-09-28): platforms only, no new grants.
assert_eq!(x["platforms"], serde_json::json!(["macOS", "windows", "linux"]));
let tabs = read(include_str!("../capabilities/tab-chrome.json"));
assert_eq!(tabs["permissions"], serde_json::json!(["allow-tab-report", "allow-tab-shortcut", "allow-tab-link-drag"]));
assert_eq!(tabs["webviews"], serde_json::json!(["tab-*"]));
assert_eq!(tabs["local"], false);
let local = read(include_str!("../capabilities/workspace-local.json"));
assert!(local.get("remote").is_none());
// My Space (profile customization) is removed: its commands do not exist, so no capability
// file grants them and neither the command manifest nor the handler names them.
let (build, desktop) = (include_str!("../build.rs"), include_str!("../src/desktop.rs"));
for gone in [
"profile_owner_get", "profile_create", "profile_api", "profile_media_pick", "profile_media_still", "profile_media_upload", "profile_still_to_x", "profile_view_get", "profile_view_set",
"bt_profiles_resolve", "bt_media_get", "bt_profile_link_seen",
] {
let permission = format!("allow-{}", gone.replace('_', "-"));
for (name, capability) in [("x-capture", &x), ("drafts-popover", &popover), ("tab-chrome", &tabs), ("workspace-local", &local)] {
assert!(!capability["permissions"].as_array().unwrap().iter().any(|p| p == permission.as_str()), "{name} grants {permission}");
}
assert!(!build.contains(&format!("\"{gone}\"")), "build.rs still lists {gone}");
assert!(!desktop.contains(gone), "desktop.rs still names {gone}");
}
assert_eq!(local["webviews"], serde_json::json!(["shell"]));
// Update commands are shell-only; no capability exposes the updater/process plugin JS APIs.
let perms = |v: &serde_json::Value| v["permissions"].as_array().unwrap().iter().map(|p| p.as_str().unwrap().to_owned()).collect::<Vec<_>>();
// Account profiles (separate X logins) are managed only by the shell.
// Drafts are inserted and alert keywords edited only by the shell; X tabs only ask/report.
for name in [
"allow-update-status", "allow-apply-update", "allow-app-ready", "allow-profiles-get", "allow-profiles-set",
"allow-tab-insert-text", "allow-alerts-get", "allow-alerts-set", "allow-draft-list", "allow-draft-revisions",
// Google Fonts downloads (network), account/pane themes and the builder preview: shell only.
"allow-gfonts-installed", "allow-gfonts-install", "allow-gfonts-remove", "allow-gfonts-faces", "allow-gfonts-preview",
"allow-theme-scopes-get", "allow-theme-scopes-set", "allow-theme-preview",
// Installing themes/plugins (links, dropped files, the signed store; owner-approved 2026-09-28):
// only the shell, after its confirmation screen. X tabs and web pages cannot install anything.
"allow-install-pending", "allow-install-stage-link", "allow-install-stage-file", "allow-install-cancel",
"allow-install-commit", "allow-install-records", "allow-install-forget", "allow-plugin-remove",
"allow-store-catalog", "allow-store-stage",
] {
assert!(perms(&local).iter().any(|p| p == name), "shell lacks {name}");
assert!(!perms(&x).iter().chain(perms(&tabs).iter()).any(|p| p == name), "tab has {name}");
}
for p in perms(&local).iter().chain(perms(&x).iter()).chain(perms(&tabs).iter()) {
assert!(
!p.starts_with("updater:") && !p.starts_with("process:") && !p.starts_with("notification:") && !p.starts_with("deep-link:"),
"plugin API exposed: {p}"
);
}
}
#[test]
fn theme_accepts_only_known_presets_and_hex_accents() {
let script = |theme: XTheme| theme.script(&X_THEMES, &SITE);
let valid = XTheme { preset: "night-ink".into(), accent: Some("#1d9bf0".into()), ..x_default() };
let text = script(valid).unwrap();
assert!(text.contains("location.origin!=='https://x.com'"));
assert!(text.contains(r#""preset":"night-ink""#));
assert!(x_default().is_default(&X_THEMES));
for preset in ["x", "classic", "night-ink", "terminal", "paper"] {
assert!(script(XTheme { preset: preset.into(), ..x_default() }).is_ok());
}
for hostile in ["evil", "x;display:none", "</script>", ""] {
assert!(script(XTheme { preset: hostile.into(), ..x_default() }).is_err(), "accepted {hostile}");
}
for hostile in ["red", "#fff", "url(https://evil.test)", "#000000;display:none", "</style><script>", "#00000é"] {
assert!(script(XTheme { accent: Some(hostile.into()), ..x_default() }).is_err(), "accepted {hostile}");
}
assert!(serde_json::from_str::<XTheme>(r#"{"preset":"x","css":"body{}"}"#).is_err());
}
#[test]
fn bundle_keeps_macos_targets_and_signed_updater() {
let conf: serde_json::Value = serde_json::from_str(include_str!("../tauri.conf.json")).unwrap();
let targets = conf["bundle"]["targets"].as_array().unwrap();
for target in ["nsis", "app", "dmg"] {
assert!(targets.iter().any(|t| t == target), "missing bundle target {target}");
}
assert!(!conf["plugins"]["updater"]["pubkey"].as_str().unwrap_or("").is_empty());
for endpoint in conf["plugins"]["updater"]["endpoints"].as_array().unwrap() {
assert!(endpoint.as_str().unwrap().starts_with("https://"), "updater endpoint must be https");
}
// One URL scheme, only for install links; the installer/Info.plist register exactly this.
assert_eq!(conf["plugins"]["deep-link"]["desktop"]["schemes"], serde_json::json!(["waybetter"]));
assert!(conf["plugins"]["deep-link"].get("mobile").is_none());
}
#[test]
fn backup_sync_workspace_palette_and_dev_commands_are_shell_only() {
let read = |json: &str| serde_json::from_str::<serde_json::Value>(json).unwrap();
let perms = |v: &serde_json::Value| v["permissions"].as_array().unwrap().iter().map(|p| p.as_str().unwrap().to_owned()).collect::<Vec<_>>();
let local = perms(&read(include_str!("../capabilities/workspace-local.json")));
let x = perms(&read(include_str!("../capabilities/x-capture.json")));
let tabs = perms(&read(include_str!("../capabilities/tab-chrome.json")));
let popover = perms(&read(include_str!("../capabilities/drafts-popover.json")));
// Settings backup/sync (files and the sync key stay native), workspaces, running a plugin's
// palette command in a tab, and developer mode (file watcher, hot reload).
for name in [
"allow-tab-run-command", "allow-workspaces-get", "allow-workspaces-set",
"allow-backup-collect", "allow-backup-export-file", "allow-backup-open-file", "allow-backup-apply",
"allow-sync-status", "allow-sync-choose-folder", "allow-sync-unlock", "allow-sync-lock", "allow-sync-disable",
"allow-sync-configure", "allow-sync-push", "allow-sync-check", "allow-sync-open", "allow-sync-mark-pulled",
"allow-dev-get", "allow-dev-set", "allow-dev-pick-theme", "allow-dev-clear-theme", "allow-dev-reload-plugin", "allow-dev-report-error",
] {
assert!(local.iter().any(|p| p == name), "shell lacks {name}");
assert!(!x.iter().chain(tabs.iter()).chain(popover.iter()).any(|p| p == name), "a tab or the popover has {name}");
}
// X tabs report their plugins' commands only through tab_report (owner-approved 2026-09-28);
// the tab capabilities gained nothing for it.
assert_eq!(tabs, ["allow-tab-report", "allow-tab-shortcut", "allow-tab-link-drag"]);
}
#[test]
fn language_commands_are_shell_only() {
let read = |json: &str| serde_json::from_str::<serde_json::Value>(json).unwrap();
let perms = |v: &serde_json::Value| v["permissions"].as_array().unwrap().iter().map(|p| p.as_str().unwrap().to_owned()).collect::<Vec<_>>();
let local = perms(&read(include_str!("../capabilities/workspace-local.json")));
let others: Vec<String> = [
include_str!("../capabilities/x-capture.json"),
include_str!("../capabilities/tab-chrome.json"),
include_str!("../capabilities/drafts-popover.json"),
]
.iter()
.flat_map(|json| perms(&read(json)))
.collect();
// The shell picks the language and hands native code and X tabs their strings; tabs and the
// popover only ever receive them (X tabs as data through the runtime), never set them.
for command in ["allow-i18n-get", "allow-i18n-set"] {
assert!(local.iter().any(|p| p == command), "shell lacks {command}");
assert!(!others.iter().any(|p| p == command), "a tab or the popover has {command}");
}
// The commands are the framework's (waybetter-host `app`); each checks shell_only first.
for command in ["i18n_get", "i18n_set"] {
waybetter_host::testkit::assert_guarded(include_str!("../../../../waybetter/crates/waybetter-host/src/app.rs"), command, "shell_only(&webview)?");
}
// X tabs get the strings only as a guarded data value, and only the runtime's own prefixes.
let source = include_str!("../src/desktop_i18n.rs");
assert!(source.contains(r#"&["x.", "kit.", "plugin."]"#), "X tabs receive only x./kit./plugin. strings");
assert!(include_str!("../src/desktop.rs").contains("page_string_prefixes: crate::desktop_i18n::PAGE_PREFIXES"), "X's config hands tabs exactly those");
let strings = waybetter_host::i18n::NativeStrings {
locale: "tr".into(),
preference: "site".into(),
messages: [("x.fab.title", "wayBetter"), ("kit.ok", "Tamam"), ("native.notify.alert.title", "Uyarı")]
.into_iter()
.map(|(key, value)| (key.to_owned(), value.to_owned()))
.collect(),
};
let script = waybetter_host::i18n::page_script(&better_twttr::site::SITE, &strings.page_payload(&["x.", "kit.", "plugin."]));
assert!(script.starts_with("if(location.origin==='https://x.com')("), "page strings must be x.com only: {script}");
assert!(script.contains("x.fab.title") && script.contains("kit.ok") && !script.contains("native.notify"), "{script}");
// "Follow X": a tab may report X's <html lang>, only as a valid tag and only while on x.com.
let desktop = include_str!("../src/desktop.rs");
assert!(
desktop.contains("report.lang.filter(|lang| waybetter_host::i18n::valid_tag(lang) && webview.url().is_ok_and(|url| policy::x_origin(&url)))"),
"tab_report lang must be a validated tag from a webview on x.com"
);
}
#[test]
fn language_strings_are_plain_text() {
use waybetter_host::i18n::NativeStrings;
let set = |value: &str| NativeStrings {
locale: "ar".into(),
preference: "site".into(),
messages: [("x.fab.title".to_owned(), value.to_owned())].into_iter().collect(),
};
assert!(set("wayBetter لـ X").validated().is_ok());
for bad in ["<img src=x onerror=alert(1)>", "&lt;script&gt;", "javascript:alert(1)", "a\u{202e}b"] {
assert!(set(bad).validated().is_err(), "{bad:?}");
}
}
#[test]
fn shell_raise_is_shell_only() {
let read = |json: &str| serde_json::from_str::<serde_json::Value>(json).unwrap();
let perms = |v: &serde_json::Value| v["permissions"].as_array().unwrap().iter().map(|p| p.as_str().unwrap().to_owned()).collect::<Vec<_>>();
let local = perms(&read(include_str!("../capabilities/workspace-local.json")));
let x = perms(&read(include_str!("../capabilities/x-capture.json")));
let tabs = perms(&read(include_str!("../capabilities/tab-chrome.json")));
let popover = perms(&read(include_str!("../capabilities/drafts-popover.json")));
// Re-orders the shell webview over the tabs for its own overlays: only the shell may.
assert!(local.iter().any(|p| p == "allow-shell-raise"), "shell lacks allow-shell-raise");
assert!(!x.iter().chain(tabs.iter()).chain(popover.iter()).any(|p| p == "allow-shell-raise"), "a tab or the popover can raise the shell");
// The command is the framework's (waybetter-host `app`).
waybetter_host::testkit::assert_guarded(include_str!("../../../../waybetter/crates/waybetter-host/src/app.rs"), "shell_raise", "shell_only(&webview)?");
waybetter_host::testkit::Capabilities::from_dir(concat!(env!("CARGO_MANIFEST_DIR"), "/capabilities")).assert_shell_only("shell_raise");
}
/// The wayBetter host's own commands (tab layout and actions, shell raise, external links, window
/// menu, profiles, language; tab shortcuts and link drags) keep their callers in X's capability
/// files, and every command wrapper checks its caller first. Every capability file is read, so a
/// new one cannot quietly widen them.
#[test]
fn host_commands_keep_their_callers() {
use waybetter_host::testkit::{assert_guarded, assert_host_guards, Capabilities};
let caps = Capabilities::from_dir(concat!(env!("CARGO_MANIFEST_DIR"), "/capabilities"));
caps.assert_host_commands();
assert_host_guards();
// Only Rust drives the plugins; no capability exposes their JS APIs.
caps.assert_no_plugin_apis(&["updater", "process", "notification", "deep-link", "single-instance", "window-state", "opener", "dialog"]);
// X's own: vault, settings, themes, installs, backups, sync, dev mode, updates are the shell's.
for command in [
"draft_list", "draft_save", "draft_revisions", "draft_status", "observation_list", "data_export",
"gfonts_installed", "gfonts_install", "gfonts_remove", "gfonts_faces", "gfonts_preview",
"tab_run_command", "workspaces_get", "workspaces_set", "backup_collect", "backup_export_file", "backup_open_file", "backup_apply",
"sync_status", "sync_choose_folder", "sync_unlock", "sync_lock", "sync_disable", "sync_configure", "sync_push", "sync_check", "sync_open", "sync_mark_pulled",
"dev_get", "dev_set", "dev_pick_theme", "dev_clear_theme", "dev_reload_plugin", "dev_report_error",
"plugins_list", "plugin_set_enabled", "plugins_open_folder", "plugin_remove",
"install_pending", "install_stage_link", "install_stage_file", "install_stage_opened", "install_cancel", "install_commit", "install_records", "install_forget", "store_catalog", "store_stage",
"custom_css_get", "custom_css_set", "features_get", "features_set", "mode_get", "mode_set", "settings_get", "settings_set", "column_configure",
"update_status", "update_check", "apply_update", "app_ready", "tab_insert_text", "alerts_get", "alerts_set",
"media_get", "media_set", "gallery_add_folder", "gallery_list", "gallery_thumb", "clip_list", "clip_thumb", "clip_remove", "tab_attach_image",
] {
caps.assert_shell_only(command);
}
// Tabs, about themselves only; the X-only ones are granted only on https://x.com.
for command in ["tab_report", "capture_draft", "open_panel", "drafts_picker_open", "drafts_popover_dismiss", "drafts_hint", "keyword_alert", "link_preview"] {
caps.assert_tab_only(command);
}
let desktop = include_str!("../src/desktop.rs");
assert_guarded(desktop, "tab_insert_text", "shell_only(&webview)?");
// The vault commands are the host's: shell-only in the capability files and each checking its caller
// first; X's own handler no longer has them, and X turns the vault on with its rules.
caps.assert_shell_only_group(waybetter_host::commands::VAULT);
waybetter_host::testkit::assert_vault_guards();
assert!(desktop.contains(".with_vault("), "the app enables the host's vault");
for command in waybetter_host::commands::VAULT {
assert!(!desktop.contains(&format!("fn {command}(")), "{command} is served by the host only");
}
// The updater commands are the shell's too, each checking its caller first; `update_check` runs the
// timer's own check once (no other endpoint), and tabs have no updater command at all.
caps.assert_shell_only_group(waybetter_host::commands::UPDATE);
waybetter_host::testkit::assert_update_guards();
assert!(desktop.contains(".with_updates("), "the app enables the updater");
// Plugins, custom CSS, the tweak flags and the browser settings are served by the host: they
// are in its shell-only list (checked against the capability files and for their caller
// check above), and X's own handler no longer has them.
let host = include_str!("../../../../waybetter/crates/waybetter-host/src/app.rs");
for command in ["plugins_list", "plugin_set_enabled", "plugins_open_folder", "custom_css_get", "custom_css_set", "features_get", "features_set", "mode_get", "mode_set", "settings_get", "settings_set"] {
assert!(waybetter_host::commands::SHELL.contains(&command), "{command} is a host shell-only command");
assert_guarded(host, command, "shell_only(&webview)?");
assert!(!desktop.contains(&format!("fn {command}(")), "{command} is served by the host only");
}
assert_guarded(desktop, "tab_report", "tab_only(&webview)?");
// Opening the panel from a page is the host's site command: only X tabs on x.com have it.
caps.assert_site_only("open_panel", &better_twttr::site::SITE);
assert!(!desktop.contains("fn open_panel("), "open_panel is served by the host only");
}
#[test]
fn install_store_media_and_font_commands_are_shell_only() {
// The store's commands are the framework's (waybetter-host `store_commands`, served because
// this app sets `AppConfig::store`): shell-only in the capability files, and each wrapper
// checks shell_only before anything else.
use waybetter_host::testkit::assert_guarded;
let caps = waybetter_host::testkit::Capabilities::from_dir(concat!(env!("CARGO_MANIFEST_DIR"), "/capabilities"));
caps.assert_shell_only_group(waybetter_host::commands::STORE);
waybetter_host::testkit::assert_store_guards();
// The media panel's commands are the framework's too (AppConfig::with_media): shell-only, each
// checking its caller first; only attaching to X's composer is the app's own.
caps.assert_shell_only_group(waybetter_host::commands::MEDIA);
waybetter_host::testkit::assert_media_guards();
// Workspaces and encrypted sync too (AppConfig::with_sync; workspaces are served for every app).
caps.assert_shell_only_group(waybetter_host::commands::WORKSPACES);
caps.assert_shell_only_group(waybetter_host::commands::SYNC);
waybetter_host::testkit::assert_sync_guards();
// The settings backup's commands are the framework's too (what it carries is the app's hooks).
caps.assert_shell_only_group(waybetter_host::commands::BACKUP);
waybetter_host::testkit::assert_backup_guards();
// Developer mode (AppConfig::with_dev): the framework's commands; the theme file is the app's hook.
caps.assert_shell_only_group(waybetter_host::commands::DEV);
waybetter_host::testkit::assert_dev_guards();
assert!(include_str!("../src/desktop.rs").contains(".with_dev()"), "the app enables developer mode");
assert!(include_str!("../src/desktop.rs").contains(".with_sync()"), "the app enables sync");
// Site themes too (AppConfig::with_themes): the framework's commands; X only names its built-in themes.
caps.assert_shell_only_group(waybetter_host::commands::THEME);
waybetter_host::testkit::assert_theme_guards();
assert!(include_str!("../src/desktop.rs").contains(".with_themes(&crate::theme::THEMES)"), "the app configures its themes");
// The keyword alert settings (AppConfig::with_alerts): the framework's commands; the hit and its notification are X's.
caps.assert_shell_only_group(waybetter_host::commands::ALERTS);
waybetter_host::testkit::assert_alerts_guards();
assert!(include_str!("../src/desktop.rs").contains(".with_alerts(&alerts::ALERTS)"), "the app configures its alerts");
// Google Fonts too (AppConfig::with_fonts).
caps.assert_shell_only_group(waybetter_host::commands::GFONTS);
waybetter_host::testkit::assert_fonts_guards();
assert!(include_str!("../src/desktop.rs").contains(".with_fonts()"), "the app enables Google Fonts");
assert!(include_str!("../src/desktop.rs").contains(".with_media()"), "the app enables the media panel");
assert_guarded(include_str!("../src/desktop_media.rs"), "tab_attach_image", "shell_only(&webview)?");
let source = include_str!("../../../../waybetter/crates/waybetter-host/src/store_commands.rs");
// Links only ever propose an install: the deep-link handler queues and emits, nothing more.
let received = &source[source.find("fn received(").unwrap()..];
let received = &received[..received.find("\n}").unwrap()];
assert!(!received.contains("commit") && !received.contains("stage"), "a link must not stage or install by itself");
// The app's store settings: X's id and the framework's hosts; the compiled-in key; and it asks
// the framework to serve the store.
let settings = include_str!("../src/distribution.rs");
assert!(settings.contains("app_id: \"x\"") && settings.contains("allowed_hosts: DEFAULT_HOSTS") && settings.contains("include_str!(\"../keys/store.pub\")"));
assert!(include_str!("../src/desktop.rs").contains(".with_store(&crate::distribution::STORE)"), "the app configures the store");
}
/// The popover's webview and session are the host's; X's commands reached by the popover each check
/// their caller first (the popover webview with the open session), and inserting a draft only
/// focuses the tab and types (`compose` scripts), never clicks Post.
#[test]
fn popover_commands_check_their_caller_and_never_post() {
use waybetter_host::testkit::assert_guarded;
let source = include_str!("../src/desktop_popover.rs");
assert_guarded(source, "popover_state", "host::session_of(&webview)");
for command in ["popover_drafts", "popover_insert", "popover_open_vault"] {
assert_guarded(source, command, "host::current(&webview,");
}
// popover_close only acts for the current popover, and does nothing otherwise.
let close = &source[source.find("fn popover_close(").unwrap()..];
let close = &close[..close.find("
}").unwrap()];
assert!(close.contains("host::current(&webview, seq).is_ok()"));
let insert = &source[source.find("fn popover_insert(").unwrap()..];
let insert = &insert[..insert.find("
}").unwrap()];
assert!(insert.contains("compose::insert_script") && insert.contains("compose::append_script"));
assert!(!insert.contains("click") && !insert.contains("submit") && !insert.contains("Post"), "popover_insert never posts");
assert!(insert.contains("policy::x_origin(&url)"), "only into X tabs");
// The host's popover check: only a `popover-<n>` webview on the app's own local origin.
let host = include_str!("../../../../waybetter/crates/waybetter-host/src/popover_host.rs");
let only = &host[host.find("pub fn popover_only(").unwrap()..];
let only = &only[..only.find("
}").unwrap()];
assert!(only.contains("popover::valid_label(webview.label())") && only.contains("local_navigation_allowed"));
}