Skip to content

DOMException Crash in Caret Split via Premature deleteContents()Β #3031

Description

@codeCraft-Ritik

3. DOMException Crash in Caret Split via Premature deleteContents()

πŸ“ Affected Locations

  • src/components/modules/caret.ts (Lines 197–233)

πŸ” Deep Technical Diagnosis

When pressing Enter to split a block, Caret.extractFragmentFromCaretPosition() is called to split content from the caret to the end of the block.

// src/components/modules/caret.ts: Lines 200-231
if (selection.rangeCount) {
  const selectRange = selection.getRangeAt(0);
  const currentBlockInput = this.Editor.BlockManager.currentBlock.currentInput;

  selectRange.deleteContents(); // <-- BUG: Mutates DOM before boundary inspection!

  if (currentBlockInput) {
    if ($.isNativeInput(currentBlockInput)) {
      // ...
    } else {
      const range = selectRange.cloneRange();

      range.selectNodeContents(currentBlockInput);
      range.setStart(selectRange.endContainer, selectRange.endOffset); // <-- CRASH!

      return range.extractContents();
    }
  }
}

Why It Crashes:

  1. selectRange.deleteContents() deletes the selected nodes from the document tree.
  2. In browsers (especially Chrome/WebKit), deleting nodes collapses text nodes and can completely remove the selectRange.endContainer node from the DOM, or reduce its character length below selectRange.endOffset.
  3. When range.setStart(selectRange.endContainer, selectRange.endOffset) runs immediately after:
    • If endContainer is no longer inside currentBlockInput, the browser throws:
      DOMException: Failed to execute 'setStart' on 'Range': The node provided is not a child of this node.
    • If endOffset exceeds the remaining text length, the browser throws:
      DOMException: Failed to execute 'setStart' on 'Range': The offset is larger than the node's length.
  4. This completely halts the block-splitting operation and leaves the editor in a corrupt, uneditable state.

πŸ§ͺ Reproduction Steps

  1. Insert a paragraph with formatting: Hello <b>world and extra</b> text.
  2. Select text spanning across the tag boundary: e.g., select from world to extra.
  3. Press Enter.
  4. Observed: Console throws uncaught DOMException: Failed to execute 'setStart' on 'Range'. Block does not split.

πŸ’‘ Proposed Solution & Patch

Clone the boundary points or collapse the range cleanly before extracting contents:

src/components/modules/caret.ts

@@ -201,8 +201,6 @@ export default class Caret extends Module {
       const selectRange = selection.getRangeAt(0);
       const currentBlockInput = this.Editor.BlockManager.currentBlock.currentInput;

-      selectRange.deleteContents();
-
       if (currentBlockInput) {
         if ($.isNativeInput(currentBlockInput)) {
           /**
@@ -213,6 +211,7 @@ export default class Caret extends Module {
           const input = currentBlockInput as HTMLInputElement | HTMLTextAreaElement;
           const newFragment = document.createDocumentFragment();

+          selectRange.deleteContents();
           const inputRemainingText = input.value.substring(0, input.selectionStart);
           const fragmentText = input.value.substring(input.selectionStart);

@@ -222,8 +221,11 @@ export default class Caret extends Module {
           return newFragment;
         } else {
-          const range = selectRange.cloneRange();
+          selectRange.deleteContents();
+          const range = document.createRange();

           range.selectNodeContents(currentBlockInput);
-          range.setStart(selectRange.endContainer, selectRange.endOffset);
+          const safeSelection = Selection.get();
+          const anchorRange = safeSelection.rangeCount ? safeSelection.getRangeAt(0) : selectRange;
+          range.setStart(anchorRange.startContainer, anchorRange.startOffset);

           return range.extractContents();
         }

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions