fix(studio): guard cross-origin iframe access to prevent SecurityError crashes

Wrap all contentWindow/contentDocument access and addEventListener/removeEventListener
calls in try/catch across usePlaybackKeyboard, useAppHotkeys, and CompositionsTab.
Prevents SecurityError from propagating to the React error boundary (white screen).
Affects 1,885 crashes / 648 unique users in the last 7 days.
This commit is contained in:
Miguel Ángel
2026-05-25 16:49:40 +00:00
parent 179241e932
commit c1b26fcb32
3 changed files with 122 additions and 53 deletions
@@ -62,33 +62,46 @@ function parsePositiveNumber(value: string | null): number | null {
return Number.isFinite(parsed) && parsed > 0 ? parsed : null;
}
// fallow-ignore-next-line complexity
function resolveIframeDuration(iframe: HTMLIFrameElement | null): number | null {
const win = iframe?.contentWindow as PreviewWindow | null;
const playerDuration = win?.__player?.getDuration?.();
if (Number.isFinite(playerDuration) && playerDuration != null && playerDuration > 0) {
return playerDuration;
try {
const win = iframe?.contentWindow as PreviewWindow | null;
const playerDuration = win?.__player?.getDuration?.();
if (Number.isFinite(playerDuration) && playerDuration != null && playerDuration > 0) {
return playerDuration;
}
} catch {
/* cross-origin iframe */
}
const doc = iframe?.contentDocument;
const root = doc?.querySelector("[data-composition-id]") ?? doc?.documentElement ?? null;
return (
parsePositiveNumber(root?.getAttribute("data-composition-duration") ?? null) ??
parsePositiveNumber(root?.getAttribute("data-duration") ?? null)
);
try {
const doc = iframe?.contentDocument;
const root = doc?.querySelector("[data-composition-id]") ?? doc?.documentElement ?? null;
return (
parsePositiveNumber(root?.getAttribute("data-composition-duration") ?? null) ??
parsePositiveNumber(root?.getAttribute("data-duration") ?? null)
);
} catch {
return null;
}
}
function syncIframePlayback(iframe: HTMLIFrameElement | null, shouldPlay: boolean): boolean {
const player = (iframe?.contentWindow as PreviewWindow | null)?.__player;
if (!player) return false;
try {
const player = (iframe?.contentWindow as PreviewWindow | null)?.__player;
if (!player) return false;
if (shouldPlay) {
player.play?.();
if (shouldPlay) {
player.play?.();
return true;
}
player.pause?.();
player.seek?.(resolveThumbnailSeekTime(resolveIframeDuration(iframe)));
return true;
} catch {
return false;
}
player.pause?.();
player.seek?.(resolveThumbnailSeekTime(resolveIframeDuration(iframe)));
return true;
}
function CompCard({