Hacktoberfest 2026: những issue maintainer đã đánh dấu cho tháng Mười, đang mở và phù hợp người mới. Xem issue Hacktoberfest

DOMException Crash in Caret Split via Premature deleteContents()

Đang mở
#3,031 1 bình luận 0 reaction 0 người được giao Xem trên GitHub

Chưa có ai nhận issue này.

Đánh giá

Độ khó
3/5
Thời gian dự kiến
1-2 ngày
Mức phù hợp với người mới
72/100
Loại issue
Lỗi
Độ rõ ràng
Đặc tả rõ ràng
Mức độ hoạt động
Sôi nổi
Công nghệ
typescript
Lĩnh vực
frontend

Hướng nghiên cứu

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.

Do mô hình lập chỉ mục viết ra từ nội dung của issue.

Mô tả

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

Ngôn ngữ chính
TypeScript
Star
32k
Fork
2.2k
Merge trung bình
8 giờ 45 phút
Pull request đã merge (30 ngày)
1

Chuẩn bị môi trường

Dự án này không cung cấp dev container, Dockerfile hay hướng dẫn đóng góp, nên bạn cần tự thiết lập môi trường: hãy bắt đầu từ README và xem hướng dẫn đóng góp lần đầu của chúng tôi để biết các bước chung.

Bắt đầu từ đâu

  1. Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
  2. Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
  3. Fork repository và làm thay đổi trên một nhánh.
  4. Mở pull request có tham chiếu số hiệu của issue.

Issue khác của codex-team/editor.js

Tất cả issue của codex-team/editor.js

Issue tương tự

Thêm issue về TypeScript

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.