fix(vscode): append embed flag after asExternalUri so the query = survives unencoded

This commit is contained in:
Kayshen-X 2026-07-18 15:49:47 +08:00
parent c2cfce221c
commit df2198739c
3 changed files with 33 additions and 5 deletions

View file

@ -13,7 +13,7 @@ import { PenSession, type SessionHost } from "../session/pen-session";
import type { SessionRegistry } from "../session/session-registry";
import type { BridgeOutboundToPage } from "../protocol/bridge";
import { encodeOutbound } from "../protocol/bridge";
import { buildBootHtml, buildWebviewHtml } from "./webview-shell";
import { appendEmbedQuery, buildBootHtml, buildWebviewHtml } from "./webview-shell";
import { pickRestartSource, resolveDaemonBinary, type LatestDurable } from "./restart-source";
import { isShellControl, parseShellReadyOrigin } from "./shell-messages";
import { encodeFigBytes, figSaveTargetPath, isFigPath } from "./fig-source";
@ -249,8 +249,13 @@ export class PenEditorProvider implements vscode.CustomEditorProvider<PenDocumen
): Promise<void> {
const key = document.uri.fsPath;
const nonce = makeNonce();
const external = await vscode.env.asExternalUri(vscode.Uri.parse(`${client.baseUrl}/?embed=vscode`));
panel.webview.html = buildWebviewHtml({ iframeSrc: external.toString(), nonce });
// The embed flag is appended AFTER asExternalUri: putting it in the Uri
// makes toString() percent-encode the "=" ("?embed%3Dvscode"), which the
// wasm-side EmbedHost::from_query correctly refuses — the editor then
// silently renders full chrome inside the plugin.
const external = await vscode.env.asExternalUri(vscode.Uri.parse(`${client.baseUrl}/`));
const iframeSrc = appendEmbedQuery(external.toString(true));
panel.webview.html = buildWebviewHtml({ iframeSrc, nonce });
const host = this.makeHost(document, panel, watcherState);
const session = new PenSession(host, client.handshake.token, document.bootJson);

View file

@ -1,5 +1,5 @@
import { test, expect } from "bun:test";
import { buildBootHtml, buildWebviewHtml, originOf } from "./webview-shell";
import { describe, expect, test } from "bun:test";
import { appendEmbedQuery, buildBootHtml, buildWebviewHtml, originOf } from "./webview-shell";
test("originOf extracts scheme://host:port", () => {
expect(originOf("http://127.0.0.1:45001/")).toBe("http://127.0.0.1:45001");
@ -56,3 +56,17 @@ test("nonce is not reused across boot and full unless the caller reuses it", ()
expect(buildBootHtml("A")).toContain('nonce="A"');
expect(buildWebviewHtml({ iframeSrc: "http://x.y:1/", nonce: "B" })).toContain('nonce="B"');
});
describe("appendEmbedQuery", () => {
test("appends ?embed=vscode to a bare externalized URL", () => {
expect(appendEmbedQuery("http://127.0.0.1:49793/")).toBe("http://127.0.0.1:49793/?embed=vscode");
});
test("appends with & when the base already has a query", () => {
expect(appendEmbedQuery("https://tunnel.example/x/?tkn=abc")).toBe(
"https://tunnel.example/x/?tkn=abc&embed=vscode",
);
});
test("never percent-encodes the equals sign", () => {
expect(appendEmbedQuery("http://127.0.0.1:1/")).not.toContain("%3D");
});
});

View file

@ -11,6 +11,15 @@ export function originOf(url: string): string {
return new URL(url).origin;
}
/** Append the `embed=vscode` flag to an already-externalized editor URL.
* Kept as plain string concatenation (never `vscode.Uri`): Uri.toString()
* percent-encodes the query's "=" into "%3D", which the wasm-side
* `EmbedHost::from_query` refuses by design. Tolerates a base that already
* carries a query (e.g. tunnel tokens in remote scenarios). */
export function appendEmbedQuery(base: string): string {
return `${base}${base.includes("?") ? "&" : "?"}embed=vscode`;
}
/** Phase 1: no iframe. On load it reports window.origin so the extension can
* spawn the daemon with the correct --allow-origin, then waits for the
* extension to replace the HTML with the full shell. */