diff --git a/src/components/canvas/players/html5-player.ts b/src/components/canvas/players/html5-player.ts index 37732243..7b6eb47d 100644 --- a/src/components/canvas/players/html5-player.ts +++ b/src/components/canvas/players/html5-player.ts @@ -376,6 +376,8 @@ export class Html5Player extends Player { .join(""); const animationOverride = ``; const bodyClone = doc.body.cloneNode(true) as HTMLElement; + // Scripts never run in an SVG image, and their source can hold characters XML forbids, which fail the frame. + bodyClone.querySelectorAll("script").forEach(script => script.remove()); const existingStyle = bodyClone.getAttribute("style") ?? ""; bodyClone.setAttribute("style", `width:${width}px;height:${height}px;margin:0;overflow:hidden;${existingStyle}`); const bodyXml = serializer.serializeToString(bodyClone); diff --git a/tests/html5-player.test.ts b/tests/html5-player.test.ts index dfac1419..006db64c 100644 --- a/tests/html5-player.test.ts +++ b/tests/html5-player.test.ts @@ -124,6 +124,21 @@ it("captures inline SVG backgrounds and CSS metacharacters as valid XML without expect(doc.head.querySelector("style")?.textContent).toBe(css); }); +it("leaves scripts out of the capture so their source can't break the frame", () => { + const { harness, iframe } = createPlayer(); + const doc = iframe.contentDocument!; + const script = doc.createElement("script"); + script.textContent = 'const unit = "\u0001";'; + doc.body.replaceChildren(script, Object.assign(doc.createElement("div"), { className: "title", textContent: "Shown" })); + + const parsed = new DOMParser().parseFromString(harness.captureIframeAsForeignObjectSvg(1080, 1920), "image/svg+xml"); + + expect(parsed.querySelector("parsererror")).toBeNull(); + expect(parsed.querySelector("script")).toBeNull(); + expect(parsed.querySelector(".title")?.textContent).toBe("Shown"); + expect(doc.body.querySelector("script")).not.toBeNull(); +}); + it("keeps a failed capture inside its canvas layer and retries only after the asset changes", async () => { const { player, harness, iframe, events, edit } = createPlayer(); jest.spyOn(console, "warn").mockImplementation(() => {});