Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[lexical] Bug Fix: Point.isBefore could return incorrect result due to normalization #7256

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

etrepum
Copy link
Collaborator

@etrepum etrepum commented Feb 27, 2025

Description

Fixes a long-standing bug in Point.isBefore by using the NodeCaret implementation of $comparePointCaretNext

Closes #3181

Test plan

Before

If two points that are not normalized are compared, and isBefore's incorrect normalization causes the compared nodes to match (without fixing up the offsets) then the result can be wrong because the offsets refer to a different node entirely.

After

The normalization is now correctly performed with carets, and there's a unit test to confirm that the known broken situation is now fixed.

Copy link

vercel bot commented Feb 27, 2025

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
lexical ✅ Ready (Inspect) Visit Preview 💬 Add feedback Feb 27, 2025 4:17pm
lexical-playground ✅ Ready (Inspect) Visit Preview 💬 Add feedback Feb 27, 2025 4:17pm

@facebook-github-bot facebook-github-bot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Feb 27, 2025
@etrepum etrepum added the extended-tests Run extended e2e tests on a PR label Feb 27, 2025
@@ -98,10 +98,18 @@ export function $setBlocksType<T extends ElementNode>(
prevNode.replace(element, true);
if (newSelection) {
if (key === newSelection.anchor.key) {
newSelection.anchor.key = element.getKey();
newSelection.anchor.set(
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function was the only remaining place I could find, outside of the tests, where we were manually setting properties on the Point. This is probably something we should disallow in the future (e.g. make it readonly in the PointType interface) so that caches and such can be updated correctly. I can see us eventually wanting to store some more information on the point such as a cached NodeCaret or a direction or something like that which we may clear when it's changed.

Comment on lines +155 to +156
if (this.key === b.key) {
return this.offset < b.offset;
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is left in only as an optimization, could be removed for code size

Comment on lines +158 to +160
const aCaret = $normalizeCaret($caretFromPoint(this, 'next'));
const bCaret = $normalizeCaret($caretFromPoint(b, 'next'));
return $comparePointCaretNext(aCaret, bCaret) < 0;
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Normalization is done here for backwards compatibility. PointCaret's ordering before normalization is more precise - two points can refer to the same "visual" location but be "logically" distinct. An 'element' point with the offset of a TextNode is before the a 'text' point at offset 0 to the same TextNode (because you would descend into the TextNode after encountering it as a child of the element), but these are indistinguishable visually and to the descendant-based algorithm initially used by Point.

There are cases where the Point class can still distinguish between "logical" points that are "visually" the same. For example given a textNode that has a text node sibling, the 'text' point at textNode with offset textNode.getTextContentSize() and the 'text' point at textNode.getNextSibling() with offset 0 are visually indistinguishable, but the first point isBefore the second point. This is true both before and after this fix, PointCaret normalization does not bias TextPointCaret.

Comment on lines +158 to +161
const anchorCaret = $caretFromPoint(anchor, 'next');
const focusCaret = $caretFromPoint(focus, 'next');
const direction =
$comparePointCaretNext(anchorCaret, focusCaret) <= 0 ? 'next' : 'previous';
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Refactored this to use the better implementation of ordering now that we have one, this code was written before #7135 and not refactored in that PR

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. extended-tests Run extended e2e tests on a PR
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Bug: Point's isBefore calculation can be incorrect
3 participants