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

Rewrite ReactDOMSelection to use fewer ranges #9992

Merged
merged 1 commit into from
Oct 4, 2017

Conversation

sophiebits
Copy link
Collaborator

We heard from Chrome engineers that creating too many Range objects slows down Chrome because it needs to keep track of all of them for the case that anchor/focus nodes get removed from the document. We can just implement this calculation without ranges anyway.

@sophiebits
Copy link
Collaborator Author

I think this logic is right. I tested it by hand and it seemed to match in all cases I tried (in the Notes composer especially). Works with backwards selections. Apparently "old IE" (not sure which IEs) uses this path for textareas so I should probably test that too.

cc @n8schloss

Copy link
Collaborator

@sebmarkbage sebmarkbage left a comment

Choose a reason for hiding this comment

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

Are we really doing all this work for every commit?

start: isBackward ? end : start,
end: isBackward ? start : end,
start: start,
end: end,
};
Copy link
Collaborator

Choose a reason for hiding this comment

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

New allocation per update? 😲

let start = null;
let end = null;

function traverse(node) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Seems like this could be rewritten without a closure and non-recursive by utilizing parentNode. Like the Fiber traversals.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

ok ok fine, I wanted to be lazy but I can do that too

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I think I need it this way because I need the stack of indices. Since I only need the index if node is one of [anchorNode, focusNode] I could technically do it in constant space but it would be a huge pain. Can we leave it like this?

Copy link
Collaborator

@sebmarkbage sebmarkbage Jun 21, 2017

Choose a reason for hiding this comment

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

You could have two index variables. On for anchor and one for focus. You only increment the index if you're currently traversing anchor or focus parent.

We could leave the recursion but can we at least avoid the closure by passing anchorNode/focusNode/anchorOffset/focusOffset as arguments? There are two return values but we're already allocating for that so just return the object from the recursive function.

Also, isn't text in selection something we should know the optimal solution for? :)

@sophiebits
Copy link
Collaborator Author

Are we really doing all this work for every commit?

When the focus is inside a contenteditable, apparently we are.

@sebmarkbage
Copy link
Collaborator

Oh ok. It's not as bad as I thought. I thought we did this for non-content editable. Phew.

@sophiebits
Copy link
Collaborator Author

Rewritten with no closures or recursion. I am not proud of the complexity but I think it hits all the requirements.

I included a fuzz test against a much simpler (slower?) implementation, added to our test suite. jsdom doesn't support Range objects, but I copied the fuzz test code into my browser and manually compared it against our old implementation https://gist.github.com/sophiebits/2e6d571f4f10f33b62ea138a6e9c265c; with 200,000 trials no differences were found.

Think this should be good.

while (true) {
if (
node === anchorNode &&
(anchorOffset === 0 || node.nodeType === TEXT_NODE)
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I think this basically counts as zeugma. My code is poetry.

Copy link
Collaborator

@sebmarkbage sebmarkbage left a comment

Choose a reason for hiding this comment

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

It's beautiful.

Wait until after 16-final?

let node = outerNode;
let parentNode = null;

outer: while (true) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think V8 (at least the old one) deopts in some cases when it thinks it might be an infinite loop. We have more of these though so we can probably do a pass over all of them and work around it, if that's the case.

focusOffset,
) {
let length = 0;
let start = null;
Copy link
Collaborator

Choose a reason for hiding this comment

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

-1 might help these be monomorphic in type-driven optimizations.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

k

We heard from Chrome engineers that creating too many Range objects slows down Chrome because it needs to keep track of all of them for the case that anchor/focus nodes get removed from the document. We can just implement this calculation without ranges anyway.

jsdom doesn't support Range objects, but I copied the fuzz test code into my browser and manually compared it against our old implementation https://gist.github.com/sophiebits/2e6d571f4f10f33b62ea138a6e9c265c; with 200,000 trials no differences were found.
length += node.nodeValue.length;
}

if ((next = node.firstChild) === null) {
Copy link
Contributor

Choose a reason for hiding this comment

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

node.firstChild can never be undefined?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

No, only null or an actual Node.

@gaearon
Copy link
Collaborator

gaearon commented Oct 3, 2017

Let’s land it?

@sophiebits sophiebits merged commit 1ba7dfc into facebook:master Oct 4, 2017
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
Copy link

Choose a reason for hiding this comment

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

MIT MIT MIT

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Thank you. Will fix in the morning.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Maybe we need a lint rule 😄

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

Successfully merging this pull request may close these issues.

None yet

6 participants