Back to Plate

Slate iframe mounted DOM nodes need path metadata fallback

docs/solutions/ui-bugs/2026-05-02-slate-iframe-mounted-dom-nodes-need-path-metadata-fallback.md

53.0.65.2 KB
Original Source

Slate iframe mounted DOM nodes need path metadata fallback

Problem

Clicking mounted Slate content inside the iframe example crashed or logged false DOM coverage warnings because the DOM bridge only trusted hot weak-map state. In the iframe/custom renderer path, the paragraph and text spans were real Slate DOM, but lifecycle timing could leave bridge maps behind the mounted DOM.

Symptoms

  • dev-browser reproduced the crash on http://localhost:3100/examples/iframe by clicking the first iframe paragraph.
  • The console/page error was Cannot resolve a Slate node from DOM node.
  • The dev-safety checker also logged omitted editable child without a DOM coverage boundary for ordinary paragraph children.
  • Inspecting the iframe DOM showed data-slate-node="element" and data-slate-node="text" without data-slate-path or runtime metadata.

What Didn't Work

  • Treating this as DOM coverage boundary fallout was the wrong shape. No Slate content was intentionally hidden or virtualized; the target DOM was mounted.
  • Relying only on useSlateNodeRef to populate weak maps left no fallback when the event path reached a mounted node whose mapping was missing.
  • Adding only a toDOMNode fallback was incomplete. toDOMPoint can already know the model path while node-to-path maps are still catching up, so it needs a direct mounted-DOM-by-path fallback.
  • A microtask dev-safety check fired too early for iframe/portal descendants. It reported normal mounted children as omitted before the child refs caught up.
  • Accepting arbitrary data-slate-path from foreign DOM would have hidden real bugs and opened the bridge to unsafe outside-editor nodes.

Solution

Make the mounted DOM contract explicit and guarded:

  • render data-slate-path and data-slate-runtime-id directly on Slate element and text DOM, including custom renderElement / renderText attributes;
  • let DOMEditor.toSlateNode fall back from a missing weak-map entry to data-slate-path only when the DOM node is inside the current editor and the path still exists;
  • let DOMEditor.toDOMNode and toDOMPoint recover mounted DOM from the current Slate path plus matching data-slate-runtime-id;
  • repair the normal weak maps after a successful fallback so later lookups are fast again;
  • schedule the editable-child dev-safety check on the next macrotask and cancel it on cleanup, so iframe/portal descendants get one commit turn to register;
  • keep the iframe integration test listening for the false DOM coverage warning and wait long enough to catch delayed logs;
  • keep foreign path-tagged DOM throwing.

Representative bridge shape:

ts
const fallbackPath =
  domEl && DOMEditor.hasDOMNode(editor, domEl)
    ? parseSlateDOMPath(domEl.getAttribute('data-slate-path'))
    : null

if (fallbackPath && Editor.hasPath(editor, fallbackPath)) {
  const [fallbackNode] = editor.read((state) => state.nodes.get(fallbackPath))
  const key = DOMEditor.findKey(editor, fallbackNode)

  keyToElement.set(key, domEl)
  ELEMENT_TO_NODE.set(domEl, fallbackNode)
  NODE_TO_ELEMENT.set(fallbackNode, domEl)

  return fallbackNode
}

For Slate-to-DOM point export, do not force recovery through stale node maps when the caller already resolved the model path:

ts
const el =
  DOMEditor.toDOMNode(editor, text, { suppressThrow: true }) ??
  findMountedDOMNodeByPath(editor, resolvedPoint.path)

if (!el) {
  throw new Error(`Cannot resolve a DOM node from Slate node: ${text}`)
}

Why This Works

Weak maps are the fast path, but mounted Slate DOM needs a stable recovery lane when an iframe, custom renderer, or lifecycle gap leaves that fast path empty. The fallback is safe because it is scoped to the editor root, matched to the runtime id, and validated against the live model path before resolving the node.

The dev-safety checker should catch genuinely omitted editable children, not race normal iframe children. A macrotask is enough for the mounted bridge to catch up while still surfacing real omissions in the same browser turn.

Prevention

  • Browser examples that use iframes or custom renderers should assert mounted Slate nodes expose path metadata.
  • DOM bridge fallbacks must validate both editor containment and live model paths before accepting DOM-provided metadata.
  • toDOMPoint fallback tests should cover stale node path maps separately from stale DOM weak maps.
  • Browser regressions for dev-safety warnings should wait a short bounded delay after interaction; immediate console assertions can miss delayed checks.
  • Tests should cover both sides: recovery for mounted in-editor nodes and a hard throw for path-tagged foreign DOM.