Skip to content

Commit cf9c36d

Browse files
committed
fix(player): preserve runtime readiness through load
1 parent 690683f commit cf9c36d

3 files changed

Lines changed: 58 additions & 2 deletions

File tree

packages/player/src/hyperframes-player.test.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1494,6 +1494,33 @@ describe("HyperframesPlayer srcdoc attribute", () => {
14941494
| undefined;
14951495
expect(ctor).toBeDefined();
14961496
expect(ctor!.observedAttributes).toContain("srcdoc");
1497+
expect(ctor!.observedAttributes).toContain("runtime-src");
1498+
});
1499+
1500+
it("uses a configured runtime source for loopback srcdoc", () => {
1501+
const player = document.createElement("hyperframes-player") as PlayerInternal;
1502+
player.setAttribute("runtime-src", "http://127.0.0.1:8900/hyperframe.runtime.iife.js");
1503+
player.setAttribute("srcdoc", "<!doctype html><html><head></head><body></body></html>");
1504+
document.body.appendChild(player);
1505+
1506+
expect(player.iframe.getAttribute("srcdoc")).toContain(
1507+
'<script src="http://127.0.0.1:8900/hyperframe.runtime.iife.js"></script>',
1508+
);
1509+
1510+
player.remove();
1511+
});
1512+
1513+
it("falls back to the pinned runtime for an unsafe runtime source", () => {
1514+
const player = document.createElement("hyperframes-player") as PlayerInternal;
1515+
player.setAttribute("runtime-src", 'javascript:alert("no")');
1516+
player.setAttribute("srcdoc", "<!doctype html><html><head></head><body></body></html>");
1517+
document.body.appendChild(player);
1518+
1519+
const srcdoc = player.iframe.getAttribute("srcdoc") ?? "";
1520+
expect(srcdoc).not.toContain("javascript:");
1521+
expect(srcdoc).toContain("hyperframe.runtime.iife.js");
1522+
1523+
player.remove();
14971524
});
14981525

14991526
it("forwards an initial srcdoc attribute to the iframe on connect", () => {
@@ -1961,6 +1988,8 @@ describe("HyperframesPlayer runtime ready handshake", () => {
19611988
paused: boolean;
19621989
iframe: HTMLIFrameElement;
19631990
_onMessage: (event: MessageEvent) => void;
1991+
_onIframeLoad: () => void;
1992+
_runtimeBridgeReady: boolean;
19641993
}
19651994

19661995
let player: PlayerInternal;
@@ -2102,6 +2131,15 @@ describe("HyperframesPlayer runtime ready handshake", () => {
21022131
expect(findControlCalls("set-muted")).toHaveLength(2);
21032132
});
21042133

2134+
it("does not erase a DOMContentLoaded runtime handshake when iframe load follows it", () => {
2135+
player._onMessage(readyMessage());
2136+
expect(player._runtimeBridgeReady).toBe(true);
2137+
2138+
player._onIframeLoad();
2139+
2140+
expect(player._runtimeBridgeReady).toBe(true);
2141+
});
2142+
21052143
it("ignores ready events from a different window", () => {
21062144
postSpy.mockClear();
21072145
const otherSource = {} as Window;

packages/player/src/hyperframes-player.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { handleRuntimeMessage } from "./runtime-message-handler.js";
88
import {
99
SHADER_CAPTURE_SCALE_ATTR,
1010
SHADER_LOADING_ATTR,
11+
RUNTIME_SRC_ATTR,
1112
type ShaderLoadingMode,
1213
getShaderCaptureScaleFromElement,
1314
getShaderModeFromElement,
@@ -78,6 +79,7 @@ class HyperframesPlayer extends HTMLElement {
7879
"playback-rate",
7980
"audio-src",
8081
SANDBOX_ORIGIN_ATTR,
82+
RUNTIME_SRC_ATTR,
8183
SHADER_CAPTURE_SCALE_ATTR,
8284
SHADER_LOADING_ATTR,
8385
];
@@ -297,6 +299,7 @@ class HyperframesPlayer extends HTMLElement {
297299
break;
298300
case SHADER_CAPTURE_SCALE_ATTR:
299301
case SHADER_LOADING_ATTR:
302+
case RUNTIME_SRC_ATTR:
300303
this._reloadShaderOptions();
301304
break;
302305
}
@@ -987,7 +990,10 @@ class HyperframesPlayer extends HTMLElement {
987990

988991
private _onIframeLoad() {
989992
this._ready = false;
990-
this._runtimeBridgeReady = false;
993+
// The runtime installs its bridge at DOMContentLoaded, posts `ready`, and only then does the
994+
// iframe's load event fire. Do not erase that authoritative handshake here: doing so strands
995+
// retained data set after load until a second `ready` that never comes. Source setters and
996+
// sandbox-policy reloads already clear bridge readiness before starting a navigation.
991997
this._directTimelineAdapter = null;
992998
this._directTimelineClock.stop();
993999
this._stopParentTickClock();

packages/player/src/shader-options.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { RUNTIME_CDN_URL } from "./runtime-url.js";
99

1010
export const SHADER_CAPTURE_SCALE_ATTR = "shader-capture-scale";
1111
export const SHADER_LOADING_ATTR = "shader-loading";
12+
export const RUNTIME_SRC_ATTR = "runtime-src";
1213
const SHADER_CAPTURE_SCALE_PARAM = "__hf_shader_capture_scale";
1314
const SHADER_LOADING_PARAM = "__hf_shader_loading";
1415

@@ -153,6 +154,17 @@ export function prepareSrcdocForElement(el: Element, srcdoc: string): string {
153154
normalizeShaderCaptureScale(el.getAttribute(SHADER_CAPTURE_SCALE_ATTR)),
154155
getShaderModeFromElement(el),
155156
),
156-
RUNTIME_CDN_URL,
157+
runtimeSrcFromElement(el),
157158
);
158159
}
160+
161+
function runtimeSrcFromElement(el: Element): string {
162+
const configured = el.getAttribute(RUNTIME_SRC_ATTR)?.trim();
163+
if (!configured) return RUNTIME_CDN_URL;
164+
try {
165+
const url = new URL(configured, document.baseURI);
166+
return url.protocol === "http:" || url.protocol === "https:" ? url.href : RUNTIME_CDN_URL;
167+
} catch {
168+
return RUNTIME_CDN_URL;
169+
}
170+
}

0 commit comments

Comments
 (0)