Skip to content

Commit 5fa8673

Browse files
authored
Merge pull request #1617 from Nwakaego-Ego/fix/codeframe-postmessage-origin-main
Apply CodeFrame postMessage origin fix to main
2 parents e9851a1 + 939bcaf commit 5fa8673

2 files changed

Lines changed: 98 additions & 17 deletions

File tree

‎src/components/CodeEmbed/frame.tsx‎

Lines changed: 27 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ interface CodeBundle {
88
base?: string;
99
scripts?: string[];
1010
instanceMode?: boolean;
11+
parentOrigin: string;
1112
}
1213

1314
/*
@@ -27,7 +28,7 @@ const wrapSketch = (sketchCode?: string) => {
2728

2829
/*
2930
* Wraps the given code in a html document for display.
30-
* Single object argument, all properties optional:
31+
* Single object argument:
3132
*/
3233
const wrapInMarkup = (code: CodeBundle) =>
3334
`<!DOCTYPE html>
@@ -45,13 +46,18 @@ ${code.css || ""}
4546
</style>
4647
<!-- If we need an addon script, load p5 the usual way with no caching to make sure
4748
the import order doesn't get messed up. -->
48-
${(code.instanceMode || (code.scripts?.length ?? 0) > 0 ? [cdnLibraryUrl, ...(code.scripts ?? [])] : []).map((src) => `<script type="text/javascript" src="${src}"></script>`).join('\n')}
49+
${(code.instanceMode || (code.scripts?.length ?? 0) > 0 ? [cdnLibraryUrl, ...(code.scripts ?? [])] : []).map((src) => `<script type="text/javascript" src="${src}"></script>`).join("\n")}
4950
<body>${code.htmlBody || ""}</body>
5051
<script id="code" type="text/javascript">${wrapSketch(code.js) || ""}</script>
51-
${(code.scripts?.length ?? 0) > 0 ? '' : `
52+
${
53+
(code.scripts?.length ?? 0) > 0
54+
? ""
55+
: `
5256
<script type="text/javascript">
5357
// Listen for p5.min.js text content and include in iframe's head as script
5458
window.addEventListener("message", event => {
59+
if (event.origin !== '${code.parentOrigin}') return;
60+
if (event.source !== window.parent) return;
5561
// Include check to prevent p5.min.js from being loaded twice
5662
const scriptExists = !!document.getElementById("p5ScriptTagInIframe");
5763
if (!scriptExists && event.data?.sender === '${cdnLibraryUrl}') {
@@ -63,7 +69,8 @@ ${(code.scripts?.length ?? 0) > 0 ? '' : `
6369
}
6470
})
6571
</script>
66-
`}
72+
`
73+
}
6774
`.replace(/\u00A0/g, " ");
6875

6976
export interface CodeFrameProps {
@@ -140,14 +147,14 @@ export const CodeFrame = (props: CodeFrameProps) => {
140147
sender: cdnLibraryUrl,
141148
message: p5ScriptText,
142149
},
143-
"*",
150+
window.location.origin,
144151
);
145152
} catch (e) {
146153
console.error(`Error loading ${p5ScriptTag.src}`);
147154
return;
148155
}
149156
})();
150-
}, [props.jsCode, mounted,p5ScriptTag]);
157+
}, [props.jsCode, mounted, p5ScriptTag]);
151158

152159
return (
153160
<div
@@ -156,19 +163,24 @@ export const CodeFrame = (props: CodeFrameProps) => {
156163
>
157164
<iframe
158165
ref={iframeRef}
159-
srcDoc={mounted ? wrapInMarkup({
160-
js: props.jsCode,
161-
css: props.cssCode,
162-
htmlBody: props.htmlBodyCode,
163-
base: props.base,
164-
scripts: props.scripts,
165-
instanceMode: props.jsCode.includes('new p5'),
166-
}) : ""}
166+
srcDoc={
167+
mounted
168+
? wrapInMarkup({
169+
js: props.jsCode,
170+
css: props.cssCode,
171+
htmlBody: props.htmlBodyCode,
172+
base: props.base,
173+
scripts: props.scripts,
174+
instanceMode: props.jsCode.includes("new p5"),
175+
parentOrigin: window.location.origin,
176+
})
177+
: ""
178+
}
167179
sandbox="allow-scripts allow-popups allow-modals allow-forms allow-same-origin"
168180
aria-label="Code Preview"
169181
title="Code Preview"
170182
loading={props.lazyLoad ? "lazy" : "eager"}
171-
style={{width: "100%", height: "100%"}}
183+
style={{ width: "100%", height: "100%" }}
172184
/>
173185
</div>
174186
);

‎test/components/CodeFrame.test.tsx‎

Lines changed: 71 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,14 @@
1-
import { it, expect, suite, assert } from "vitest";
2-
import { render, screen, act, cleanup, within } from "@testing-library/preact";
1+
import { it, expect, suite, assert, vi } from "vitest";
2+
import {
3+
render,
4+
screen,
5+
act,
6+
cleanup,
7+
waitFor,
8+
within,
9+
} from "@testing-library/preact";
310
import { CodeFrame } from "@components/CodeEmbed/frame";
11+
import { cdnLibraryUrl } from "@/src/globals/globals";
412

513
suite("CodeFrame", () => {
614
it("defaults to empty DOM", async () => {
@@ -30,4 +38,65 @@ suite("CodeFrame", () => {
3038

3139
cleanup();
3240
});
41+
42+
it("only sends the p5 script to its own iframe origin", async () => {
43+
const postMessage = vi.fn();
44+
const contentWindowSpy = vi
45+
.spyOn(HTMLIFrameElement.prototype, "contentWindow", "get")
46+
.mockReturnValue({ postMessage } as unknown as Window);
47+
class ImmediateIntersectionObserver {
48+
private callback: IntersectionObserverCallback;
49+
50+
constructor(callback: IntersectionObserverCallback) {
51+
this.callback = callback;
52+
}
53+
54+
observe() {
55+
this.callback(
56+
[{ isIntersecting: true } as IntersectionObserverEntry],
57+
this as unknown as IntersectionObserver,
58+
);
59+
}
60+
61+
disconnect() {}
62+
}
63+
vi.stubGlobal("IntersectionObserver", ImmediateIntersectionObserver);
64+
vi.stubGlobal(
65+
"fetch",
66+
vi.fn().mockResolvedValue({ text: () => Promise.resolve("p5 source") }),
67+
);
68+
const p5Script = document.createElement("script");
69+
p5Script.id = "p5ScriptTag";
70+
p5Script.src = "/p5.min.js";
71+
document.body.appendChild(p5Script);
72+
73+
try {
74+
act(() => {
75+
render(<CodeFrame jsCode="function setup() {}" />);
76+
});
77+
78+
const frame = screen.getByTitle("Code Preview") as HTMLIFrameElement;
79+
80+
await waitFor(() =>
81+
expect(postMessage).toHaveBeenCalledWith(
82+
{
83+
sender: cdnLibraryUrl,
84+
message: "p5 source",
85+
},
86+
window.location.origin,
87+
),
88+
);
89+
expect(frame.srcdoc).toContain(
90+
`if (event.origin !== '${window.location.origin}') return;`,
91+
);
92+
expect(frame.srcdoc).toContain(
93+
"if (event.source !== window.parent) return;",
94+
);
95+
} finally {
96+
cleanup();
97+
p5Script.remove();
98+
contentWindowSpy.mockRestore();
99+
vi.unstubAllGlobals();
100+
}
101+
});
33102
});

0 commit comments

Comments
 (0)