Recognize nodes from other realms via realm-safe type checks - #156
Recognize nodes from other realms via realm-safe type checks#156lizarusi wants to merge 5 commits into
Conversation
c9da9c7 to
a52cd2f
Compare
|
@lizarusi I'm not a maintainer - just a contributor/community member - so please take my drive-by suggestions with a grain of salt! That said - with a few fixes to the JSDocs and a guard for HTML docs - this patch has my vote! |
|
Additionally, |
83a029b to
40d5be0
Compare
|
@myabc Thanks a lot for looking into this! I've applied your suggestions (the Additionally, I decided to keep |
… behaviors One test per realm-sensitive check: entry normalization (crash), id-based element preservation (detached and SlicedParentNode paths), template content morphing, input/option/textarea value syncing, head handling, and Document normalization.
Nodes created in another JS realm (e.g. an iframe's document) fail `instanceof` checks, even after being adopted into this document, because each realm has its own constructors. This made morph() throw "TypeError: newContent is not iterable" when given a cross-realm node, and silently skip template handling, id-based matching, input/option/textarea value syncing, head handling, and Document normalization for cross-realm nodes. Replace realm-sensitive `instanceof` checks with helpers that duck-type via `nodeType` and `localName` (the approach morphdom uses). The `document.activeElement` checks keep `instanceof`, since the active element always belongs to this document. Each converted check has a dedicated test that fails if that single check is reverted to `instanceof`.
localName-only checks matched foreign-namespace elements (e.g. SVG <template>, XML <head>), sending them down HTML-specific code paths: SVG templates crashed morphChildren via an undefined .content, and non-HTML head elements were diverted into head-merge logic. Fold the five helpers into an isHtmlElement factory that also requires the XHTML namespace, and tighten the JSDoc param types. Suggested-by: myabc <myabc@users.noreply.github.com> Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ENWz1cbCtHAYGK5kGFLyBD
The duck-typed nodeType checks can be fooled by named-property shadowing: a form containing <input name="nodeType"> shadows form.nodeType with that input element, so isNode rejected the form and normalizeParent scattered its children into the morph, dropping the <form> element itself. instanceof cannot be shadowed, so try it first and fall back to duck-typing only for nodes from other realms. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ENWz1cbCtHAYGK5kGFLyBD
saveAndRestoreFocus consulted the host realm's document.activeElement and instanceof checks, so restoreFocus silently no-oped when morphing content inside another document (e.g. an iframe). Resolve the active element via ctx.target.ownerDocument and the realm-safe input/textarea helpers. Suggested-by: myabc <myabc@users.noreply.github.com> Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ENWz1cbCtHAYGK5kGFLyBD
40d5be0 to
e18d2c5
Compare
Hi!
While using idiomorph at Walnut, we hit a bug that we've been carrying as a local patch — contributing the fix upstream.
The bug
Passing a node created in another JS realm (e.g. an iframe's
contentDocument) asnewContentthrows:Repro:
Every realm has its own constructors, so cross-realm nodes fail
instanceof NodeinnormalizeParentand fall through to the array/HTMLCollection branch, which blows up on the spread.The crash is only the visible part.
instanceofstays false for these nodes even after they are adopted into this document, so once past normalization they also silently fail every other realm-sensitiveinstanceofcheck: template content handling, id-based matching of new children (newChild instanceof Element), andsyncInputValue— meaning input/option/textarea values would silently not sync from cross-realm content. The second test demonstrates that.We hit this in production at Walnut, where we morph DOM captured from customer apps across document boundaries — we've been carrying a fix as a local patch since 0.7.2.
The fix
Replace realm-sensitive
instanceofchecks with tiny duck-typing helpers (nodeType/localName) — the same approach morphdom uses. The helpers carry JSDoc type predicates, so TypeScript narrows exactly likeinstanceofdid andnpm run typecheckstays green. Thedocument.activeElementchecks in the focus-preservation code keepinstanceof, since the active element always belongs to this document.First commit adds the failing tests, second commit makes them pass. There is a dedicated test per converted check — entry normalization, id-based element preservation (both the dummy-parent and
SlicedParentNodepaths), template content morphing, input/option/textarea value syncing, head handling, and Document normalization — and each test was verified to fail if its single check alone is reverted toinstanceof.npm run typecheck,npm run format:check, and the full suite pass, with coverage at 100%.Relates to #103 — whichever direction the
newContenttype-narrowing goes, cross-realm nodes currently crash rather than being handled or rejected cleanly, so this seems worth fixing today.