diff --git a/packages/core/src/runtime/manualEditGestureWatch.test.ts b/packages/core/src/runtime/manualEditGestureWatch.test.ts index 37144e0808..f59fbde8ce 100644 --- a/packages/core/src/runtime/manualEditGestureWatch.test.ts +++ b/packages/core/src/runtime/manualEditGestureWatch.test.ts @@ -1,4 +1,5 @@ import { afterEach, describe, expect, it, vi } from "vitest"; +import { JSDOM } from "jsdom"; import { STUDIO_MANUAL_EDIT_GESTURE_ATTR } from "../editing/draftMarkers"; import { createManualEditGestureWatch } from "./manualEditGestureWatch"; @@ -34,6 +35,28 @@ describe("manual-edit gesture watch", () => { } }); + it("sees a gesture on an element adopted from another realm", async () => { + // The composition body is built in the editor window and moved into the + // preview document, so its nodes carry another realm's prototypes and + // `target instanceof Element` answers false for every one of them. A + // gesture on such an element must still be seen, or a parked transport + // never wakes while the user drags. + const foreign = new JSDOM("
"); + const el = foreign.window.document.createElement("div"); + document.body.appendChild(document.adoptNode(el as unknown as Node)); + const watch = createManualEditGestureWatch(document, () => {}); + try { + expect(el instanceof Element).toBe(false); + (el as unknown as Element).setAttribute(STUDIO_MANUAL_EDIT_GESTURE_ATTR, "token"); + expect(watch.isActive()).toBe(true); + await flush(); + (el as unknown as Element).removeAttribute(STUDIO_MANUAL_EDIT_GESTURE_ATTR); + expect(watch.isActive()).toBe(false); + } finally { + watch.disconnect(); + } + }); + it("stops reporting a gesture whose element was torn out mid-drag", async () => { document.body.innerHTML = ``; const watch = createManualEditGestureWatch(document, () => {}); diff --git a/packages/core/src/runtime/manualEditGestureWatch.ts b/packages/core/src/runtime/manualEditGestureWatch.ts index 6455039df7..a5be082806 100644 --- a/packages/core/src/runtime/manualEditGestureWatch.ts +++ b/packages/core/src/runtime/manualEditGestureWatch.ts @@ -1,4 +1,5 @@ import { STUDIO_MANUAL_EDIT_GESTURE_ATTR } from "../editing/draftMarkers"; +import { isElementNode } from "./domRealm"; export interface ManualEditGestureWatch { /** True while any element in the document carries the gesture marker. */ @@ -53,7 +54,10 @@ export function createManualEditGestureWatch( const ingest = (records: MutationRecord[]): void => { for (const record of records) { const target = record.target; - if (!(target instanceof Element)) continue; + // Structural, not `instanceof`: the composition body is adopted into the + // preview frame, so its nodes answer to another realm's Element and an + // identity check silently skips every gesture. See domRealm.ts. + if (!isElementNode(target)) continue; if (target.hasAttribute(STUDIO_MANUAL_EDIT_GESTURE_ATTR)) marked.add(target); else marked.delete(target); }