diff --git a/packages/player/src/slideshow/hyperframes-slideshow.test.ts b/packages/player/src/slideshow/hyperframes-slideshow.test.ts index 2c664d3776..b2d3efe67a 100644 --- a/packages/player/src/slideshow/hyperframes-slideshow.test.ts +++ b/packages/player/src/slideshow/hyperframes-slideshow.test.ts @@ -199,6 +199,30 @@ describe("", () => { el.remove(); }); + it("accepts embedding-parent and self navigation but rejects other senders", () => { + const onNext = vi.fn(); + const el = makeEl({ onNext }); + const embedding = document.createElement("iframe"); + const foreign = document.createElement("iframe"); + document.body.append(embedding, foreign); + vi.stubGlobal("parent", embedding.contentWindow); + try { + for (const source of [foreign.contentWindow, null]) { + window.dispatchEvent(new MessageEvent("message", { source, data: { type: "next" } })); + } + expect(onNext).not.toHaveBeenCalled(); + for (const source of [window, embedding.contentWindow]) { + window.dispatchEvent(new MessageEvent("message", { source, data: { type: "next" } })); + } + expect(onNext).toHaveBeenCalledTimes(2); + } finally { + vi.unstubAllGlobals(); + embedding.remove(); + foreign.remove(); + el.remove(); + } + }); + it("handles postMessage next", () => { const el = document.createElement("hyperframes-slideshow") as any; document.body.appendChild(el); @@ -215,7 +239,7 @@ describe("", () => { currentSlide: { hotspots: [] }, nextSlide: null, }); - window.dispatchEvent(new MessageEvent("message", { data: { type: "next" } })); + window.dispatchEvent(new MessageEvent("message", { source: window, data: { type: "next" } })); expect(nextCalled).toBe(true); el.remove(); }); @@ -1981,7 +2005,7 @@ describe(" Fix 7 — audience mode ignores window postMes currentSlide: { hotspots: [] }, nextSlide: null, }); - window.dispatchEvent(new MessageEvent("message", { data: { type: "next" } })); + window.dispatchEvent(new MessageEvent("message", { source: window, data: { type: "next" } })); expect(nextCalled).toBe(false); el.remove(); }); @@ -2003,7 +2027,7 @@ describe(" Fix 7 — audience mode ignores window postMes currentSlide: { hotspots: [] }, nextSlide: null, }); - window.dispatchEvent(new MessageEvent("message", { data: { type: "next" } })); + window.dispatchEvent(new MessageEvent("message", { source: window, data: { type: "next" } })); expect(nextCalled).toBe(true); el.remove(); }); @@ -2474,7 +2498,7 @@ describe(" Fix 4 — back affordance (postMessage only; c backCalled = true; }, }); - window.dispatchEvent(new MessageEvent("message", { data: { type: "back" } })); + window.dispatchEvent(new MessageEvent("message", { source: window, data: { type: "back" } })); expect(backCalled).toBe(true); el.remove(); }); @@ -2488,7 +2512,7 @@ describe(" Fix 4 — back affordance (postMessage only; c }, }); el.setAttribute("mode", "audience"); - window.dispatchEvent(new MessageEvent("message", { data: { type: "back" } })); + window.dispatchEvent(new MessageEvent("message", { source: window, data: { type: "back" } })); expect(backCalled).toBe(false); el.remove(); }); diff --git a/packages/player/src/slideshow/hyperframes-slideshow.ts b/packages/player/src/slideshow/hyperframes-slideshow.ts index 6bd034665a..df95c4b12c 100644 --- a/packages/player/src/slideshow/hyperframes-slideshow.ts +++ b/packages/player/src/slideshow/hyperframes-slideshow.ts @@ -914,6 +914,7 @@ export class HyperframesSlideshow extends HTMLElement { // fallow-ignore-next-line complexity private onMessage = (e: MessageEvent): void => { + if (e.source !== window.parent && e.source !== window) return; // Audience mode is driven by BroadcastChannel; ignore embed postMessage nav. if (this.resolveMode() === "audience") return; const data = e.data as { type?: unknown; slideIndex?: unknown } | null; diff --git a/packages/sdk-playground/src/main.test.ts b/packages/sdk-playground/src/main.test.ts index 06af34db89..4bbec37c08 100644 --- a/packages/sdk-playground/src/main.test.ts +++ b/packages/sdk-playground/src/main.test.ts @@ -4,6 +4,11 @@ import { expect, it, vi } from "vitest"; // Keep initialization at the asynchronous persistence boundary while exercising // the real static UI and its registered message listener. +// The paused initializer never opens a composition. Keep unrelated SDK/parser +// and raw animation-library transforms out of this message-boundary witness. +vi.mock("@hyperframes/sdk", () => ({ openComposition: vi.fn() })); +vi.mock("@hyperframes/core/gsap-parser-acorn", () => ({ parseGsapScriptAcorn: vi.fn() })); +vi.mock("gsap/dist/gsap.min.js?raw", () => ({ default: "" })); vi.mock("./fileAdapter.js", () => ({ createFileAdapter: () => new Promise(() => {}), }));