diff --git a/apps/desktop/electron/main/browser-view.ts b/apps/desktop/electron/main/browser-view.ts index 48c2af1d65..28192c3cd8 100644 --- a/apps/desktop/electron/main/browser-view.ts +++ b/apps/desktop/electron/main/browser-view.ts @@ -118,7 +118,16 @@ export class BrowserPane { getState(): BrowserState | null { const wc = this.view?.webContents; - if (!wc || wc.isDestroyed()) return null; + if (!wc || wc.isDestroyed()) { + return this.loadError ? { + url: this.loadError.url, + title: "", + isLoading: false, + loadError: this.loadError.message, + canGoBack: false, + canGoForward: false, + } : null; + } return { url: this.loadError?.url ?? this.pendingTarget ?? wc.getURL(), title: wc.getTitle(), @@ -163,12 +172,13 @@ export class BrowserPane { ): Promise { if (fileRoot) this.fileRoot = fileRoot; const localPath = resolveLocalFile(raw, this.fileRoot); + const localInput = /^file:/i.test(raw.trim()) || isAbsolute(raw.trim()); const target = localPath ? pathToFileURL(localPath).toString() - : normalizeUrl(raw); + : localInput ? null : normalizeUrl(raw); if (!target) { this.beginManagedNavigation(); - this.loadError = { url: raw, message: "INVALID_URL" }; + this.loadError = { url: raw, message: localInput ? "LOCAL_FILE_NOT_ALLOWED" : "INVALID_URL" }; const state = this.getState(); if (state) this.onState(state); return null; diff --git a/apps/desktop/resources/plugins/pi.browser/views/browser.html b/apps/desktop/resources/plugins/pi.browser/views/browser.html index 8fbc76edf1..1fd6112112 100644 --- a/apps/desktop/resources/plugins/pi.browser/views/browser.html +++ b/apps/desktop/resources/plugins/pi.browser/views/browser.html @@ -169,6 +169,7 @@

No page open

stoppedBody: "Enter an address to try again.", failedTitle: "Could not open this page", failedBody: "Check the address or connection, then try again.", + localFileBody: "Only existing files inside this project's workspace can be opened.", emptyTitle: "No page open", emptyBody: "Enter an address, or preview an HTML file from the conversation.", }, @@ -185,6 +186,7 @@

No page open

stoppedBody: "输入网址可重新打开页面。", failedTitle: "无法打开此页面", failedBody: "请检查网址或网络连接,然后重试。", + localFileBody: "只能打开此项目工作区内已存在的文件。", emptyTitle: "尚未打开页面", emptyBody: "输入地址,或从对话预览 HTML 文件。", }, @@ -232,7 +234,8 @@

No page open

empty.hidden = started && !state?.isLoading; const phase = state?.loadError === "ERR_ABORTED" ? "stopped" : state?.loadError ? "failed" : state?.isLoading ? "loading" : "empty"; emptyTitle.textContent = t(`${phase}Title`); - emptyBody.textContent = t(`${phase}Body`); + emptyBody.textContent = state?.loadError === "LOCAL_FILE_NOT_ALLOWED" + ? t("localFileBody") : t(`${phase}Body`); back.disabled = !state?.canGoBack; forward.disabled = !state?.canGoForward; reload.disabled = !started; diff --git a/apps/desktop/test/browser-local-file-feedback.test.mjs b/apps/desktop/test/browser-local-file-feedback.test.mjs new file mode 100644 index 0000000000..f77779274d --- /dev/null +++ b/apps/desktop/test/browser-local-file-feedback.test.mjs @@ -0,0 +1,60 @@ +import assert from "node:assert/strict"; +import { readFileSync } from "node:fs"; +import { runInNewContext } from "node:vm"; +import test from "node:test"; + +const html = readFileSync(new URL("../resources/plugins/pi.browser/views/browser.html", import.meta.url), "utf8"); +const script = html.match(/