mirror of
https://github.com/block/buzz.git
synced 2026-11-02 15:31:27 +00:00
fix(desktop): allow the configured dev URL in allow_webview_navigation
The Canvas navigation policy only allowed `http://localhost:1420` in debug builds, but every `just` desktop recipe derives a per-worktree Vite port from a hash of the worktree root (`scripts/instance-env.sh`) and injects it as `build.devUrl`. `1420` is only what a bare `pnpm tauri dev` would use. Because wry installs the navigation delegate before it issues the first `loadRequest`, the cancelled navigation was the *initial* page load, not a later link: WKWebView returned `WKNavigationActionPolicy::Cancel`, the bundle never fetched, React never mounted, and the reveal timer gave up with "initial render did not commit before reveal timeout" and showed an empty window. `just dev`, `just staging`, and `just production` all opened blank on macOS; the plugin is macOS-only, so Linux and Windows were unaffected. Take the dev URL as a parameter and compare origins instead of hardcoding a port. `lib.rs` passes `webview.config().build.dev_url`, which is the same config the app was built with — `--config` merges into it via `TAURI_CONFIG` at build time, so it is the origin the webview is actually pointed at. Comparing full origins keeps the containment the old predicate had: a different port, a different host spelling, and a non-http scheme are all still refused, and release builds have no `devUrl`, so plain http stays blocked there. Two regression tests bind the guard to that seam — the configured origin is allowed, and `localhost:1420`, `127.0.0.1` on the same port, and a missing dev URL are not. Reverting to the hardcoded port fails the first one. Verified with `just desktop-standalone`: the app boots on this worktree's port 30164, the webview's Vite HMR client connects and forwards frontend console output, and the reveal-timeout line is gone. Tauri fmt, clippy, and the file-size gate pass; the Tauri suite passes 3059 tests. One unrelated test (`forced_discovery::cheap_discovery_reports_absent_before_any_forced_probe`) fails only under the full parallel run and passes in isolation. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: Matt Toohey <contact@matttoohey.com>
This commit is contained in:
@@ -139,7 +139,12 @@ pub fn run() {
|
||||
#[cfg(target_os = "macos")]
|
||||
let builder = builder.plugin(
|
||||
tauri::plugin::Builder::<_, ()>::new("navigation-policy")
|
||||
.on_navigation(|_, url| project_canvas_package::allow_webview_navigation(url))
|
||||
.on_navigation(|webview, url| {
|
||||
project_canvas_package::allow_webview_navigation(
|
||||
url,
|
||||
webview.config().build.dev_url.as_ref(),
|
||||
)
|
||||
})
|
||||
.build(),
|
||||
);
|
||||
let builder = builder
|
||||
|
||||
@@ -532,13 +532,22 @@ fn validate_widget_in_data(data: &serde_json::Value, widget_id: &str) -> Result<
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn allow_webview_navigation(url: &tauri::Url) -> bool {
|
||||
/// Decides whether the main webview may commit a top-level navigation.
|
||||
///
|
||||
/// `dev_url` is the dev server origin the app was actually built against
|
||||
/// (`webview.config().build.dev_url`). Every `just` desktop recipe derives a
|
||||
/// per-worktree Vite port via `scripts/instance-env.sh`, so the origin cannot
|
||||
/// be hardcoded — and because the dev server load *is* the initial navigation,
|
||||
/// cancelling it leaves a blank window rather than a blocked link.
|
||||
pub(crate) fn allow_webview_navigation(url: &tauri::Url, dev_url: Option<&tauri::Url>) -> bool {
|
||||
match url.scheme() {
|
||||
"about" => url.as_str() == "about:blank",
|
||||
"buzz-canvas" => url.host_str() == Some("localhost"),
|
||||
"tauri" => url.host_str() == Some("localhost"),
|
||||
// Debug builds serve the frontend from a dev server; release builds
|
||||
// have none, so plain http stays blocked there.
|
||||
"http" if cfg!(debug_assertions) => {
|
||||
url.host_str() == Some("localhost") && url.port() == Some(1420)
|
||||
dev_url.is_some_and(|dev_url| dev_url.origin() == url.origin())
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
|
||||
@@ -640,19 +640,62 @@ fn protocol_security_policy_has_no_network_or_tauri_ipc_source() {
|
||||
#[test]
|
||||
fn native_navigation_policy_blocks_external_document_navigation() {
|
||||
assert!(super::allow_webview_navigation(
|
||||
&"buzz-canvas://localhost/load/".parse().unwrap()
|
||||
&"buzz-canvas://localhost/load/".parse().unwrap(),
|
||||
None
|
||||
));
|
||||
assert!(super::allow_webview_navigation(
|
||||
&"tauri://localhost/".parse().unwrap()
|
||||
&"tauri://localhost/".parse().unwrap(),
|
||||
None
|
||||
));
|
||||
assert!(super::allow_webview_navigation(
|
||||
&"about:blank".parse().unwrap()
|
||||
&"about:blank".parse().unwrap(),
|
||||
None
|
||||
));
|
||||
assert!(!super::allow_webview_navigation(
|
||||
&"https://example.com/leak?snapshot=secret".parse().unwrap()
|
||||
&"https://example.com/leak?snapshot=secret".parse().unwrap(),
|
||||
None
|
||||
));
|
||||
assert!(!super::allow_webview_navigation(
|
||||
&"file:///tmp/secret".parse().unwrap()
|
||||
&"file:///tmp/secret".parse().unwrap(),
|
||||
None
|
||||
));
|
||||
}
|
||||
|
||||
// The dev server load is the webview's *initial* navigation, so a policy that
|
||||
// does not recognise the configured origin opens a blank window. Every `just`
|
||||
// desktop recipe derives a per-worktree Vite port, so the origin the app is
|
||||
// launched on is never the `tauri.conf.json` default.
|
||||
#[test]
|
||||
fn native_navigation_policy_allows_the_configured_dev_server() {
|
||||
let dev_url: tauri::Url = "http://localhost:30164".parse().unwrap();
|
||||
|
||||
assert!(super::allow_webview_navigation(
|
||||
&"http://localhost:30164/".parse().unwrap(),
|
||||
Some(&dev_url)
|
||||
));
|
||||
assert!(super::allow_webview_navigation(
|
||||
&"http://localhost:30164/index.html".parse().unwrap(),
|
||||
Some(&dev_url)
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn native_navigation_policy_blocks_other_localhost_origins() {
|
||||
let dev_url: tauri::Url = "http://localhost:30164".parse().unwrap();
|
||||
|
||||
// Some other server on the loopback interface is not the frontend.
|
||||
assert!(!super::allow_webview_navigation(
|
||||
&"http://localhost:1420/".parse().unwrap(),
|
||||
Some(&dev_url)
|
||||
));
|
||||
assert!(!super::allow_webview_navigation(
|
||||
&"http://127.0.0.1:30164/".parse().unwrap(),
|
||||
Some(&dev_url)
|
||||
));
|
||||
// Release builds have no dev server, so plain http stays blocked.
|
||||
assert!(!super::allow_webview_navigation(
|
||||
&"http://localhost:30164/".parse().unwrap(),
|
||||
None
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user