Fixed error, cannot read properties of undefined reading .nodeType #175

Closed
nanov94 wants to merge 1 commit from patch-1 into master
nanov94 commented 2024-11-13 14:18:39 +01:00 (Migrated from github.com)

What?

Get error: TypeError: Cannot read properties of undefined (reading 'nodeType')

Why?

In some cases node does not have children and as a result we get property nodeType from undefined

How?

Checked existing of prev

### What? Get error: `TypeError: Cannot read properties of undefined (reading 'nodeType')` ### Why? In some cases `node` does not have children and as a result we get property `nodeType` from `undefined` ### How? Checked existing of `prev`
marijnh commented 2024-11-13 14:28:21 +01:00 (Migrated from github.com)

The only way this seems to be possible is if offset is either negative or pointing beyond the length of the node. Both would suggest something is going wrong before this point, and I don't like adding tests like this to paper over other bugs. So I'd be interested in an example of a situation that triggers this crash.

The only way this seems to be possible is if `offset` is either negative or pointing beyond the length of the node. Both would suggest something is going wrong before this point, and I don't like adding tests like this to paper over other bugs. So I'd be interested in an example of a situation that triggers this crash.
nanov94 commented 2024-11-21 08:48:26 +01:00 (Migrated from github.com)

Hi @marijnh :)
Thank you for your comment 🙏

Summary:

We are encountering an error (below) when selecting table cells, particularly when the table contains empty rows.
Error: TypeError: Cannot read properties of undefined (reading 'nodeType')

More details:

Yes, you are correct about the case where offset is either negative or points beyond the length of the node. In my app, we have another case where offset is 0 and node has no children.

Why is this the case? Empty rows without table cells:
Firstly, a tr element in HTML can have empty content here. This mean that node.childNodes is empty array:

  1. node does not have any children => node.childNodes = []
  2. we got element by index in empty array => node.childNodes[offset - 1] = undefined
  3. after got field from undefined element => node.childNodes[offset - 1]).nodeType

Secondly, this happened after using another ProseMirror library: prosemirror-tables.
How can we create tables with empty rows via ProseMirror? You need to select a few table cells and rows and merge them. We use mergeCells from prosemirror-tables for it (video attached)
Demo merge table.webm

Hi @marijnh :) Thank you for your comment 🙏 ### **Summary**: We are encountering an error (below) when selecting table cells, particularly when the table contains empty rows. _Error: TypeError: Cannot read properties of undefined (reading 'nodeType')_ ### **More details:** Yes, you are correct about the case where `offset` is either negative or points beyond the length of the node. In my app, we have another case where `offset` is 0 and `node` has no children. **Why is this the case? Empty rows without table cells:** _Firstly_, a `tr` element in HTML can have empty content [here](https://html.spec.whatwg.org/multipage/tables.html#the-tr-element). This mean that `node.childNodes` is empty array: 1. `node` does not have any `children` => `node.childNodes` = [] 2. we got element by index in empty array => `node.childNodes[offset - 1]` = undefined 3. after got field from undefined element => `node.childNodes[offset - 1]).nodeType` _Secondly_, this happened after using another ProseMirror library: [prosemirror-tables](https://github.com/ProseMirror/prosemirror-tables). **How can we create tables with empty rows via ProseMirror?** You need to select a few table cells and rows and merge them. We use [mergeCells](https://github.com/ProseMirror/prosemirror-tables/blob/582b4e45b70da49472eed91698e5d3ecfbfcf5eb/src/commands.ts#L406) from `prosemirror-tables` for it (video attached) [Demo merge table.webm](https://github.com/user-attachments/assets/e7e793ac-f25a-436f-8c68-2477ebbb935d)
marijnh commented 2024-11-21 10:49:43 +01:00 (Migrated from github.com)

where offset is 0 and node has no children.

The line already contains && offset, which will be false when offset == 0, so the code that could cause this crash will simply not execute in that situation.

> where offset is 0 and node has no children. The line already contains `&& offset`, which will be false when `offset == 0`, so the code that could cause this crash will simply not execute in that situation.
nanov94 commented 2024-11-25 07:56:11 +01:00 (Migrated from github.com)

The line already contains && offset, which will be false when offset == 0, so the code that could cause this crash will simply not execute in that situation.

Yep, @marijnh, you are right, sorry for confusing 🙏

We catch error when offset more than 0 and node.childNodes does not have children

image

> The line already contains && offset, which will be false when offset == 0, so the code that could cause this crash will simply not execute in that situation. Yep, @marijnh, you are right, sorry for confusing 🙏 We catch error when `offset` more than 0 and `node.childNodes` does not have children ![image](https://github.com/user-attachments/assets/fc5f2d38-83fb-4cfc-ae16-7b19f989c7d7)
marijnh commented 2024-11-25 09:45:35 +01:00 (Migrated from github.com)

What version of prosemirror-view are you using? In anything older than 1.31.0 there might be a situation where this happens, on Chrome when the document contains an <input> or <textarea> element.

What version of prosemirror-view are you using? In anything older than 1.31.0 there might be a situation where this happens, on Chrome when the document contains an `<input>` or `<textarea>` element.
nanov94 commented 2024-11-25 15:14:26 +01:00 (Migrated from github.com)

we use "prosemirror-view": "1.33.5",

we use "prosemirror-view": "1.33.5",
marijnh commented 2024-11-25 23:02:04 +01:00 (Migrated from github.com)

In that case, I'd really want to see instructions on how to reproduce this error, so that I can figure out what the underlying issue is.

In that case, I'd really want to see instructions on how to reproduce this error, so that I can figure out what the underlying issue is.
maccman commented 2025-08-28 16:39:47 +02:00 (Migrated from github.com)

We are also seeing this error in Refect Notes (reflect.app). I'm sorry I don't know how to reproduce it though. We're just seeing it in our error logs.

We are also seeing this error in Refect Notes (reflect.app). I'm sorry I don't know how to reproduce it though. We're just seeing it in our error logs.
maccman commented 2025-08-28 16:50:45 +02:00 (Migrated from github.com)

This is what GPT5 is saying:

  • The error is thrown when prev ends up undefined and the code still tries to read prev.nodeType.
  • This can happen in WebKit/Chromium when caret*FromPoint returns an out-of-bounds offset (e.g., offset > node.childNodes.length or offset == 1 on an empty element). Then node.childNodes[offset - 1] is undefined.

The problematic spot is here:

    let prev
    // When clicking above the right side of an uneditable node, Chrome will report a cursor position after that node.
    if (browser.webkit && offset && node.nodeType == 1 && (prev = node.childNodes[offset - 1]).nodeType == 1 &&
        (prev as HTMLElement).contentEditable == "false" && (prev as HTMLElement).getBoundingClientRect().top >= coords.top)
      offset--
    // Suspiciously specific kludge to work around caret*FromPoint
    // never returning a position at the end of the document
    if (node == view.dom && offset == node.childNodes.length - 1 && node.lastChild!.nodeType == 1 &&

Two minimal fixes (either is fine; you can also do both):

  • Add a null-check before accessing nodeType:

    if (browser.webkit && offset && node.nodeType == 1 &&
        (prev = node.childNodes[offset - 1]) && (prev as Node).nodeType == 1 &&
        (prev as HTMLElement).contentEditable == "false" &&
        (prev as HTMLElement).getBoundingClientRect().top >= coords.top) {
      offset--
    }
    
  • Clamp offset to the valid range before indexing:

    if (node.nodeType == 1) {
      offset = Math.max(0, Math.min(offset, node.childNodes.length))
    }
    

Note there’s a second potential out-of-bounds read a few lines below:

else if (offset == 0 || node.nodeType != 1 || node.childNodes[offset - 1].nodeName != "BR")

Clamping offset (as above) will prevent that too.

  • Impact: Prevents TypeError when browsers return out-of-range offsets for empty or special elements.
This is what GPT5 is saying: - The error is thrown when `prev` ends up undefined and the code still tries to read `prev.nodeType`. - This can happen in WebKit/Chromium when `caret*FromPoint` returns an out-of-bounds offset (e.g., `offset` > `node.childNodes.length` or `offset == 1` on an empty element). Then `node.childNodes[offset - 1]` is undefined. The problematic spot is here: ```306:315:/Users/alexmaccaw/tmp/prosemirror-view/src/domcoords.ts let prev // When clicking above the right side of an uneditable node, Chrome will report a cursor position after that node. if (browser.webkit && offset && node.nodeType == 1 && (prev = node.childNodes[offset - 1]).nodeType == 1 && (prev as HTMLElement).contentEditable == "false" && (prev as HTMLElement).getBoundingClientRect().top >= coords.top) offset-- // Suspiciously specific kludge to work around caret*FromPoint // never returning a position at the end of the document if (node == view.dom && offset == node.childNodes.length - 1 && node.lastChild!.nodeType == 1 && ``` Two minimal fixes (either is fine; you can also do both): - Add a null-check before accessing `nodeType`: ```ts if (browser.webkit && offset && node.nodeType == 1 && (prev = node.childNodes[offset - 1]) && (prev as Node).nodeType == 1 && (prev as HTMLElement).contentEditable == "false" && (prev as HTMLElement).getBoundingClientRect().top >= coords.top) { offset-- } ``` - Clamp `offset` to the valid range before indexing: ```ts if (node.nodeType == 1) { offset = Math.max(0, Math.min(offset, node.childNodes.length)) } ``` Note there’s a second potential out-of-bounds read a few lines below: ```ts else if (offset == 0 || node.nodeType != 1 || node.childNodes[offset - 1].nodeName != "BR") ``` Clamping `offset` (as above) will prevent that too. - Impact: Prevents `TypeError` when browsers return out-of-range offsets for empty or special elements.

Pull request closed

Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
prosemirror/prosemirror-view!175
No description provided.