fix(core): applyPositionEdits fails silently across iframe realms (#2096)

## What

`applyPositionEdits(doc)` in `@hyperframes/core/runtime/position-edits` guarded each candidate element with `instanceof HTMLElement`. `doc` is frequently an iframe's document (the SDK's edit preview, any host embedding a composition), whose elements are `HTMLElement` instances of *that frame's realm* — never this module's. The check silently no-ops on every single element cross-realm, so bulk position edits never apply inside an iframe.

## Why

Found during an audit of `@hyperframes/sdk`'s surface against pacific's movio integration. Pacific's `canvas-react` code has an explicit workaround comment for this exact bug: *"Upstream fix would be duck-typing in `@hyperframes/core` — until then, all host code must use this wrapper."* Every iframe-hosted consumer has had to reimplement the bulk-apply loop themselves to avoid it.

## How

Use the document's own realm's `HTMLElement` constructor (`doc.defaultView?.HTMLElement`) instead of the module-scope global. Duck-type on `.style` when `defaultView` is unavailable (a detached/synthetic document). The single-element `applyPositionEditToElement` was already realm-safe — only the bulk wrapper had the bug.

## Test plan

- [x] New regression test using a real jsdom iframe — confirmed it fails on the old `instanceof HTMLElement` check (0 applied, expected 1) and passes with the fix
- [x] Full existing `positionEdits.test.ts` suite passes (14/14)
- [x] Full `@hyperframes/core` suite passes (81 files / 1131 tests)
- [x] `bun run build` clean (core + full workspace, incl. studio)
This commit is contained in:
Vance Ingalls
2026-07-09 11:16:45 -07:00
committed by GitHub
2 changed files with 38 additions and 2 deletions
@@ -115,6 +115,32 @@ describe("applyPositionEdits", () => {
b.remove();
});
it("applies edits to elements from a DIFFERENT realm (an iframe's document)", () => {
// Regression test: a module-scope `instanceof HTMLElement` check fails for
// elements from another window's realm even though they're genuine,
// stylable HTMLElements — exactly the case for any iframe-hosted editor
// (the SDK's edit preview, a host embedding a composition).
const iframe = document.createElement("iframe");
document.body.appendChild(iframe);
const iframeDoc = iframe.contentDocument;
if (!iframeDoc) throw new Error("iframe.contentDocument unavailable in this test env");
// Sanity check the premise: the iframe's HTMLElement is NOT this realm's.
const iframeWindow = iframe.contentWindow as (Window & typeof globalThis) | null;
expect(iframeWindow?.HTMLElement).not.toBe(globalThis.HTMLElement);
const el = iframeDoc.createElement("div");
el.setAttribute("data-x", "50");
el.setAttribute("data-y", "-10");
el.setAttribute(EDIT_BASE_X_ATTR, "0");
el.setAttribute(EDIT_BASE_Y_ATTR, "0");
iframeDoc.body.appendChild(el);
expect(applyPositionEdits(iframeDoc)).toBe(1);
expect(el.style.getPropertyValue("translate")).toBe("50px -10px");
iframe.remove();
});
it("skips re-apply when the written translate was consumed externally (GSAP fold)", () => {
const el = makeElement({
"data-x": "10",
+12 -2
View File
@@ -156,11 +156,21 @@ export function applyPositionEditToElement(el: HTMLElement, opts?: { force?: boo
*/
export function applyPositionEdits(doc: Document): number {
const marked = doc.querySelectorAll(`[${EDIT_BASE_X_ATTR}], [${EDIT_BASE_Y_ATTR}]`);
// Not `instanceof HTMLElement`: `doc` is frequently an iframe's document (the
// SDK's edit preview, a host embedding a composition), and its elements are
// HTMLElement instances of THAT frame's realm — never this module's. A
// module-scope `instanceof HTMLElement` check silently no-ops on every element
// cross-realm. Use the document's own realm's constructor; duck-type on
// `.style` when defaultView is unavailable (a detached/synthetic document).
const RealmHTMLElement = doc.defaultView?.HTMLElement;
let applied = 0;
for (let i = 0; i < marked.length; i++) {
const el = marked[i];
if (!(el instanceof HTMLElement)) continue;
applyPositionEditToElement(el);
const isStylable = RealmHTMLElement
? el instanceof RealmHTMLElement
: typeof (el as HTMLElement).style?.setProperty === "function";
if (!isStylable) continue;
applyPositionEditToElement(el as HTMLElement);
applied += 1;
}
return applied;