Hacktoberfest 2026:メンテナが10月に向けて印を付けた、オープンで初心者向けの issue。 Hacktoberfest の issue を見る

DOMException Crash in Caret Split via Premature deleteContents()

オープン
#3,031 コメント 1 件 リアクション 0 件 担当者 0 名 GitHub で見る

まだ誰も着手していません。

評価

難易度
3/5
見積もり時間
1〜2日
初心者へのやさしさ
72/100
issue の種類
バグ
明瞭さ
明確に書かれている
活発さ
活発
技術スタック
typescript
領域
frontend

調査の方向性

Start in src/components/modules/caret.ts, especially Caret.extractFragmentFromCaretPosition() around lines 197–233, and reproduce the formatted paragraph split described in the issue. Trace the selection and range boundaries through the native-input and contenteditable paths. Done means pressing Enter across the formatting boundary no longer throws a DOMException and the block splits correctly.

索引モデルが issue の本文から書いたものです。

説明

bug

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();
         }

主要言語
TypeScript
スター
32k
フォーク
2.2k
平均マージ
8時間 45分
マージ済み PR(30日)
1

環境構築

このプロジェクトには開発コンテナ、Dockerfile、コントリビューションガイドがありません。まず README を読み、一般的な手順ははじめてのコントリビューションガイドを参照してください。

はじめの一歩

  1. issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
  2. 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
  3. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

codex-team/editor.js のほかの issue

codex-team/editor.js の issue をすべて見る

似ている issue

TypeScript の issue をもっと見る

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。