Host: `tab_find {id, action, query}` (shell-only; start, next, previous, stop). On Windows it drives WebView2's own find (`ICoreWebView2Find` from `ICoreWebView2_28`, find options from `ICoreWebView2Environment15`; its built-in find bar suppressed, all matches highlighted) and reports the engine's match count and active match to the shell as `tab-find` events. On macOS, Linux and an older WebView2 Runtime native code evaluates the engine's `window.find` in the tab instead (next and previous work, no count); WKWebView's and WebKitGTK's native find are not wired here because this tree cannot be compiled or run for them on this machine. The query is one typed line (1..=256 characters, no control characters), goes to the engine and is kept nowhere. Tabs gain no command: a tab forwards the existing key name `find` (Ctrl+F) through tab_shortcut.
Frame: FindBar.tsx takes the address field's place in its pane's toolbar while open, so the page does not move and in split view the bar is the searched pane's. "3/17" where the engine counts, "No matches" for none; Enter / F3 next, Shift+Enter / Shift+F3 previous, Esc closes, clears the highlights and gives the page the keyboard. `features.find` (on by default) gates the menu row, the palette entry and the keys. Only what the page has rendered can be found, as in a browser.
Also: this makes `pub mod find` in lib.rs (already in HEAD) resolve again.
67 lines
3.4 KiB
Rust
67 lines
3.4 KiB
Rust
//! The tab bridge: the script every tab webview gets as a main-frame initialization script
|
|
//! ([`script`]). It reports the tab's own URL, title, favicon and focus to the shell (`tab_report`)
|
|
//! and, when the app enables them, forwards browser shortcuts pressed in the page (`tab_shortcut`,
|
|
//! [`crate::tabs::valid_shortcut`]) and link drags (`tab_link_drag`, [`crate::tabs::link_drag`]).
|
|
//! The app's `tab-chrome` capability grants exactly the commands it calls, about the tab itself.
|
|
|
|
/// The bridge source (`tab-bridge.js` next to this crate's `Cargo.toml`), all features on.
|
|
pub const SOURCE: &str = include_str!("../tab-bridge.js");
|
|
const DEFAULT_OPTIONS: &str = "const options = { shortcuts: true, linkDrags: true };";
|
|
|
|
/// What the bridge forwards besides the tab report. Turn a feature off when the app's shell does
|
|
/// not handle it: a forwarded shortcut is taken from the page (`preventDefault`).
|
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
|
pub struct BridgeOptions {
|
|
/// Browser shortcuts (new/close/reopen tab, reload, back/forward, split, palette, the app menu, zoom, deck keys).
|
|
pub shortcuts: bool,
|
|
/// Link drags out of (or into) the page, for the shell's split/tab drop zones.
|
|
pub link_drags: bool,
|
|
}
|
|
|
|
impl BridgeOptions {
|
|
pub const ALL: Self = Self { shortcuts: true, link_drags: true };
|
|
/// Only `tab_report` (URL, title, favicon, focus).
|
|
pub const REPORT_ONLY: Self = Self { shortcuts: false, link_drags: false };
|
|
}
|
|
|
|
/// The bridge script with `options`.
|
|
pub fn script(options: BridgeOptions) -> String {
|
|
let set = format!("const options = {{ shortcuts: {}, linkDrags: {} }};", options.shortcuts, options.link_drags);
|
|
SOURCE.replacen(DEFAULT_OPTIONS, &set, 1)
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn options_are_set_in_the_one_options_line() {
|
|
assert_eq!(SOURCE.matches(DEFAULT_OPTIONS).count(), 1);
|
|
assert_eq!(script(BridgeOptions::ALL), SOURCE);
|
|
let report = script(BridgeOptions::REPORT_ONLY);
|
|
assert!(report.contains("const options = { shortcuts: false, linkDrags: false };"));
|
|
assert!(!report.contains(DEFAULT_OPTIONS));
|
|
assert!(report.contains("if (options.shortcuts) addEventListener('keydown'"));
|
|
assert!(report.contains("if (options.linkDrags) {"));
|
|
}
|
|
|
|
#[test]
|
|
fn bridge_calls_only_the_tab_chrome_commands() {
|
|
let mut commands: Vec<&str> = SOURCE.match_indices("invoke('").map(|(at, _)| SOURCE[at + 8..].split('\'').next().unwrap()).collect();
|
|
commands.sort();
|
|
commands.dedup();
|
|
assert_eq!(commands, ["tab_link_drag", "tab_report", "tab_shortcut"]);
|
|
assert!(SOURCE.contains("if (window.top !== window || window.__wbTabBridge) return;"), "top frame only, once");
|
|
}
|
|
|
|
#[test]
|
|
fn forwarded_shortcuts_are_ones_the_host_accepts() {
|
|
for name in ["deck-toggle", "deck-compose", "column-page-prev", "column-page-next", "column-prev", "column-next", "reopen-tab", "new-tab",
|
|
"open-panel", "close-tab", "focus-address", "command-palette", "prev-tab", "next-tab", "hard-reload", "reload", "split-down",
|
|
"split-right", "back", "forward", "app-menu", "zoom-in", "zoom-out", "zoom-reset", "find"] {
|
|
assert!(SOURCE.contains(&format!("'{name}'")), "{name} is not in the bridge");
|
|
assert!(crate::tabs::valid_shortcut(name), "{name} would be refused");
|
|
}
|
|
assert!(crate::tabs::valid_shortcut("column-3"));
|
|
}
|
|
}
|