mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-03 04:38:33 +00:00
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:
@@ -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",
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user