Fix AnchorMode_End_PrependAtTop_ViewportStaysStable - #68064
Open
ilonatommy wants to merge 5 commits into
Open
Conversation
…apshot and avoid ignoring spacer being visible.
Contributor
There was a problem hiding this comment.
Pull request overview
Fixes a timing race in Blazor Server virtualization that could cause the viewport to jump when items are prepended at the very top in End anchor mode, leading to flakiness in ServerVirtualizationTest.AnchorMode_End_PrependAtTop_ViewportStaysStable(useItemsProvider: true).
Changes:
- When
updateAnchorSnapshot()preserves the pre-shift snapshot, it now suppresses spacer IntersectionObserver callbacks only if the preserved snapshot originated at the very top (existing.scrollTop < 1). - This prevents
spacerBeforevisibility callbacks from racing ahead of the anchor-restore round-trip specifically in the “pinned at top” scenario, while avoiding suppression in cases that would break reachability of prepended rows in other modes.
The scrollTop<1 suppression added for the End-mode prepend-at-top race also fired for None mode (!anchorModeIs.beginning). In None mode there is no anchor restore to race, so suppressing the before-spacer callback at the top leaves the topmost item unloaded with no user scroll to clear it - regressing default Virtualize scroll-to-top (e.g. a 50px before spacer that never converges to 0). Restrict the suppression to End mode, which is the only mode that pins the top row while prepended items land. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: fe1ee7a6-0928-46b2-bbb1-0d9df6adee51
Mirror AnchorMode_Start_LargePrependAtTop_StillShowsNewItems for None and End modes, prepending 100 items at the top and asserting the viewport stays stable (the same item keeps its position). None additionally verifies the prepended items are reachable after scrolling to the top. Covers the large-prepend + variable-height + delayed-provider combination that was only exercised for Start mode. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: fe1ee7a6-0928-46b2-bbb1-0d9df6adee51
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes a flaky failure in
ServerVirtualizationTest.AnchorMode_End_PrependAtTop_ViewportStaysStable(useItemsProvider: true). The test intermittently failed with the viewport jumping to the prepended items instead of staying stable:Root cause
This is a Blazor Server timing race, not a logic error (the anchor mechanism is correct).
When items are prepended while the viewport is at the very top (
scrollTop == 0),Virtualizepreserves the pre-shift anchor snapshot and relies onRestoreAnchorAsyncto reposition the scroll so the previously-visible row stays at its place. On Server,RestoreAnchorAsyncis a SignalR round-trip. Because the grownspacerBeforeis already visible atscrollTop == 0, itsIntersectionObserverfires and dispatchesOnSpacerBeforeVisibleto the server, loading the prepended rows (-10..-1) into the viewport before the restore round-trip completes. An asyncItemsProviderwith delay widens the window, making the race more probable to happen. WebAssembly/synchronous paths don't race because restore is effectively synchronous.Fix
In the snapshot-preserve branch of
updateAnchorSnapshot, setsuppressSpacerCallbacks = trueonly when the pin originated at the very top (existing.scrollTop < 1) and the anchor mode isEnd. This blocks the racingspacerBeforecallback from dispatching a load to the server for the one render cycle until the anchor restore runs. The suppression is cleared on the next user scroll (handleScroll->reobserveSpacers), so the prepended rows remain fully reachable when the user scrolls up.The
existing.scrollTop < 1gate is required: an unconditional suppress would re-suppress the spacer every time the user scrolled back to the top, permanently preventing the above-viewport rows from loading and breaking the "prepended items become reachable" assertion.