fix(core): applyPositionEdits fails silently across iframe realms

applyPositionEdits(doc) guarded each element with `instanceof HTMLElement` —
but `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.

Use the document's own realm's HTMLElement constructor (doc.defaultView);
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.

Added a regression test using a real jsdom iframe, confirmed it fails on the
old `instanceof HTMLElement` check and passes with the fix.
This commit is contained in:
Vance Ingalls
2026-07-08 23:56:20 -07:00
parent 31f0810be8
commit 5a48321726
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;