fix: WebviewUrl::App hash fragment bug — tool windows loading empty page
All checks were successful
Build & Sign Wraith / Build Windows + Sign (push) Successful in 3m45s
All checks were successful
Build & Sign Wraith / Build Windows + Sign (push) Successful in 3m45s
ROOT CAUSE FOUND: WebviewUrl::App takes a PathBuf, not a URL. Passing "index.html#/tool/ping?sessionId=abc" treated the ENTIRE string including # and ? as a file path. Tauri looked for a file literally named "index.html#/tool/ping?sessionId=abc" which doesn't exist. The webview loaded an empty/404 page and WKWebView killed the content process, closing the window instantly. Fix: - Rust: split URL at '#' — pass only "index.html" to WebviewUrl::App, then set the hash fragment via window.eval() after build() - Vue: App.vue now listens for 'hashchange' event in addition to checking hash on mount, so the eval-injected hash triggers the correct tool/detached mode This was NEVER a CSP issue, focus issue, crossorigin issue, or async chunk loading issue. It was always a bad file path. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
703ebdd557
commit
6015f8669b
@ -2,9 +2,12 @@ use tauri::AppHandle;
|
||||
use tauri::WebviewWindowBuilder;
|
||||
|
||||
/// Open a child window from the Rust side using WebviewWindowBuilder.
|
||||
/// This is more reliable than JS-side WebviewWindow on macOS WKWebView.
|
||||
///
|
||||
/// The `url` parameter supports hash fragments (e.g. "index.html#/tool/ping?sessionId=abc").
|
||||
/// WebviewUrl::App takes a PathBuf and cannot handle hash/query — so we load plain
|
||||
/// index.html and set the hash via JS after the window is created.
|
||||
#[tauri::command]
|
||||
pub async fn open_child_window(
|
||||
pub fn open_child_window(
|
||||
app_handle: AppHandle,
|
||||
label: String,
|
||||
title: String,
|
||||
@ -12,13 +15,26 @@ pub async fn open_child_window(
|
||||
width: f64,
|
||||
height: f64,
|
||||
) -> Result<(), String> {
|
||||
let webview_url = tauri::WebviewUrl::App(url.into());
|
||||
WebviewWindowBuilder::new(&app_handle, &label, webview_url)
|
||||
// Split "index.html#/tool/ping?sessionId=abc" into path and fragment
|
||||
let (path, hash) = match url.split_once('#') {
|
||||
Some((p, h)) => (p.to_string(), Some(format!("#{}", h))),
|
||||
None => (url.clone(), None),
|
||||
};
|
||||
|
||||
let webview_url = tauri::WebviewUrl::App(path.into());
|
||||
let window = WebviewWindowBuilder::new(&app_handle, &label, webview_url)
|
||||
.title(&title)
|
||||
.inner_size(width, height)
|
||||
.resizable(true)
|
||||
.center()
|
||||
.build()
|
||||
.map_err(|e| format!("Failed to create window '{}': {}", label, e))?;
|
||||
|
||||
// Set the hash fragment after the window loads — this triggers App.vue's
|
||||
// onMounted hash detection to render the correct tool/detached component.
|
||||
if let Some(hash) = hash {
|
||||
let _ = window.eval(&format!("window.location.hash = '{}';", hash));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
24
src/App.vue
24
src/App.vue
@ -16,7 +16,6 @@ const DetachedSession = defineAsyncComponent({
|
||||
const app = useAppStore();
|
||||
const appError = ref<string | null>(null);
|
||||
|
||||
// Tool window mode — detected from URL hash: #/tool/network-scanner?sessionId=abc
|
||||
const isToolMode = ref(false);
|
||||
const isDetachedMode = ref(false);
|
||||
const toolName = ref("");
|
||||
@ -28,32 +27,39 @@ onErrorCaptured((err) => {
|
||||
return false;
|
||||
});
|
||||
|
||||
onMounted(async () => {
|
||||
const hash = window.location.hash;
|
||||
/** Parse hash and set mode flags. Called on mount and on hashchange. */
|
||||
function applyHash(hash: string): void {
|
||||
if (hash.startsWith("#/tool/")) {
|
||||
isToolMode.value = true;
|
||||
const rest = hash.substring(7); // after "#/tool/"
|
||||
const rest = hash.substring(7);
|
||||
const [name, query] = rest.split("?");
|
||||
toolName.value = name;
|
||||
toolSessionId.value = new URLSearchParams(query || "").get("sessionId") || "";
|
||||
} else if (hash.startsWith("#/detached-session")) {
|
||||
isDetachedMode.value = true;
|
||||
} else {
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
// Check hash at load time (present if JS-side WebviewWindow set it in the URL)
|
||||
applyHash(window.location.hash);
|
||||
|
||||
// Also listen for hash changes (Rust-side window sets hash via eval after load)
|
||||
window.addEventListener("hashchange", () => applyHash(window.location.hash));
|
||||
|
||||
// Only init vault for the main app window (no hash)
|
||||
if (!isToolMode.value && !isDetachedMode.value) {
|
||||
await app.checkVaultState();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<!-- Error display for debugging -->
|
||||
<div v-if="appError" class="fixed inset-0 z-50 flex items-center justify-center bg-[#0d1117] text-red-400 p-8 text-sm font-mono whitespace-pre-wrap">
|
||||
{{ appError }}
|
||||
</div>
|
||||
<!-- Detached session window mode -->
|
||||
<DetachedSession v-else-if="isDetachedMode" />
|
||||
<!-- Tool popup window mode -->
|
||||
<ToolWindow v-else-if="isToolMode" :tool="toolName" :session-id="toolSessionId" />
|
||||
<!-- Normal app mode -->
|
||||
<div v-else class="app-root">
|
||||
<UnlockLayout v-if="!app.isUnlocked" />
|
||||
<MainLayout v-else />
|
||||
|
||||
Loading…
Reference in New Issue
Block a user