Files
hyperframes/packages/player/src/iframe-dom.ts
T
Vance Ingalls e5ec4cf532 fix(player): rescale on cross-origin timeline ready, guard warn spam (#1840)
onRuntimeTimelineReady (the cross-origin ready signal for signed CDN
composition URLs) never called _rescale(), leaving the iframe unscaled
and untransformed if the runtime's stage-size postMessage was ever
skipped. Also adds a one-shot diagnostic warning when a rescale keeps
no-oping after ready, latched so a legitimately hidden/zero-size player
doesn't spam the console.
2026-07-01 14:36:48 -07:00

78 lines
2.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* DOM setup helpers for the player's shadow root.
*
* Keeps constructor boilerplate out of the web component class body.
*/
// Cached Constructable Stylesheet shared across all player instances.
let _sharedSheet: CSSStyleSheet | null = null;
/**
* Adopt `cssText` into `shadow` via a shared Constructable Stylesheet when the
* browser supports it, falling back to a `<style>` element injection. The sheet
* is cached on first creation and reused across all player instances.
*/
export function adoptShadowStyles(shadow: ShadowRoot, cssText: string): void {
if (typeof CSSStyleSheet !== "undefined") {
try {
if (!_sharedSheet) {
_sharedSheet = new CSSStyleSheet();
_sharedSheet.replaceSync(cssText);
}
shadow.adoptedStyleSheets = [_sharedSheet];
return;
} catch {
/* fallthrough */
}
}
const style = document.createElement("style");
style.textContent = cssText;
shadow.appendChild(style);
}
/**
* Creates and configures the iframe element that hosts the composition, plus
* the wrapper container div. Returns handles to both so the constructor can
* attach them to the shadow root and track references without inlining the
* boilerplate.
*/
export function createCompositionIframe(): {
container: HTMLDivElement;
iframe: HTMLIFrameElement;
} {
const container = document.createElement("div");
container.className = "hfp-container";
const iframe = document.createElement("iframe");
iframe.className = "hfp-iframe";
iframe.sandbox.add("allow-scripts", "allow-same-origin");
iframe.allow = "autoplay; fullscreen";
iframe.referrerPolicy = "no-referrer";
iframe.title = "HyperFrames Composition";
container.appendChild(iframe);
return { container, iframe };
}
/**
* Scale the iframe so the composition fits inside the player element while
* preserving aspect ratio. No-ops when the player has no painted size yet.
* Returns whether the transform was actually applied, so callers can tell a
* real no-op (still 0×0) apart from a successful rescale.
*/
export function scaleIframeToFit(
playerElement: HTMLElement,
iframe: HTMLIFrameElement,
compositionWidth: number,
compositionHeight: number,
): boolean {
const w = playerElement.offsetWidth;
const h = playerElement.offsetHeight;
if (w === 0 || h === 0) return false;
const scale = Math.min(w / compositionWidth, h / compositionHeight);
iframe.style.width = `${compositionWidth}px`;
iframe.style.height = `${compositionHeight}px`;
iframe.style.transform = `translate(-50%, -50%) scale(${scale})`;
return true;
}