-
-
Notifications
You must be signed in to change notification settings - Fork 929
Fix nextSibling bugs and refactor updateNodes() and its fuzzer
#3059
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
Conversation
dead-claudia
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In addition to the other comments, can you also put in some deterministic tests to ensure we don't regress in at least the cases you listed?
If they break, I'd rather us see it right away. I don't want to risk the bug unexpectedly reappearing making me roll for initiative after a bad release. 😅 (This may be before your time here, but we got burned hard in a v0.2 release thanks to missing test coverage.)
render/render.js
Outdated
| var oldIndices = new Array(end - start + 1) | ||
| for (var i = 0; i < oldIndices.length; i++) oldIndices[i] = -1 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we do this instead? Or is it slower?
| var oldIndices = new Array(end - start + 1) | |
| for (var i = 0; i < oldIndices.length; i++) oldIndices[i] = -1 | |
| var oldIndices = new Int32Array(end - start + 1).fill(-1) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems like a good idea.
Since this code path isn't tested with npm run perf, I'll take a quick look using dbmonster or some other benchmark.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tried running dbmonster, but I couldn’t tell if there was any noticeable difference. So I changed it to new Array(end - start + 1).fill(-1), which reduces the bundle size.
render/render.js
Outdated
| var il = lisTemp.length = a.length | ||
| for (var i = 0; i < il; i++) lisTemp[i] = a[i] | ||
| for (var i = 0; i < il; ++i) { | ||
| for (var i = 0; i < il; i++) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you revert this change? I'd rather keep the Git blame cleaner
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
reverted it
| // FIXME: This does not take into account the "swaps and list reversals" heuristic in updateNodes(). | ||
| // if (root.appendChild.callCount + root.insertBefore.callCount !== test.expected.creations + test.expected.moves) console.log(test, {aC: root.appendChild.callCount, iB: root.insertBefore.callCount}, [].map.call(root.childNodes, function(n){return n.nodeName.toLowerCase()})) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you elaborate on what the specific issue is with this?
This feels a bit too generic. Given the nature of the issue it's checking, and the fact it's just auditing for perf opportunities, it might take years to get fixed, assuming it gets fixed at all.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're absolutely right. I'd like to leave a more detailed comment.
| // ditto | ||
| // o(root.appendChild.callCount + root.insertBefore.callCount).equals(test.expected.creations + test.expected.moves)("moves") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See above comment
7179aca to
e1ae772
Compare
|
@dead-claudia Thank you for your review comments. |
Because the DOM has not yet been arranged in `vnodes` order, `nextSibling` can point to the wrong DOM node. Since `insertBefore` uses `nextSibling`, nodes may be inserted in the wrong position when deletions and insertions occur. This commit fixes that by performing updates based on `old`, which reflects the actual DOM order.
Previously, `getNextSibling()` scanned all the way to the end of the array. If the array contained vnodes that did not correspond to DOM nodes, or if newly inserted vnodes were present within the scan range, the scan could overrun and capture an incorrect nextSibling. This commit fixes that by limiting the scan range appropriately.
…ses that do not account for the heuristics in `updateNodes()`. Previously, due to a bug that caused the same data to be reused, the fuzzer was not exercising the code adequately. It also included test cases that ignored the heuristics in `updateNodes()`, which had a very low probability of being triggered but, once they were, produced a large burst of false-positive test failures from that same data. The commented-out sections are test cases for the number of append and insert operations related to the longest increasing subsequence algorithm, so it should be safe to leave them commented out for now.
…ons and insertions), as well as cases with fragments that do not correspond to DOM nodes.
…ock. By specializing this logic for the unkeyed path, this change reduces the number of conditional branches and also shrinks the bundle size.
…optimize `getKeyMap()`. In v1, `getKeyMap()` was called inside the loop, but in v2 this is no longer necessary. Since key normalization has been improved in v2, much of `getKeyMap()`’s logic has become redundant, so this change inlines and simplifies it to reduce overhead.
These changes aim to improve code readability and slightly reduce the bundle size.
e1ae772 to
153bb7a
Compare
This PR fixes
nextSibling-related bugs inupdateNodes()and updates updateNodesFuzzer to better cover these cases. It also refactorsupdateNodes()to simplify the logic and improve maintainability.Description
This PR makes the following changes related to
updateNodes().Motivation and Context
This PR fixes the following issues related to the way
nextSiblingis set, which I noticed while reading theupdateNodes()code. (The cases below are simplified versions of those found by the fuzzer.)Additionally, since updateNodesFuzzer also appeared to have problems, I fixed those issues as well and added additional test cases.
case 1 (flems)
case 2 (flems)
case 3 (flems)
Since these cases occur only with keyed vnodes and involve very rare patterns - such as fragments that do not correspond to DOM nodes or tag changes that cause node deletions and insertions - the bugs likely did not surface very often.
How Has This Been Tested?
npm run testI have also confirmed the above flems case fix.
As for the fuzzer, I’ve confirmed that it is tested with different patterns, and that even after increasing the number of runs, all tests still pass.
Types of changes
Checklist