[Blazor] Keep Virtualize spacer callbacks programmatic while an alignment is pending - #68709
Open
lewing wants to merge 2 commits into
Open
[Blazor] Keep Virtualize spacer callbacks programmatic while an alignment is pending#68709lewing wants to merge 2 commits into
lewing wants to merge 2 commits into
Conversation
alignToItemAt returns without scrolling when the target item is not in the committed window yet, recording pendingAlignLocalIndex so a later render can retry. processIntersectionEntries cleared the AlignToItem scroll activity regardless, which downgraded subsequent spacer callbacks from ProgrammaticScroll (ignored by C#) to ViewportFill. Virtualize also clears _currentScrollCts as soon as AlignToItemAsync returns, so those ViewportFill callbacks arrive with no guard left and C# redistributes the window while InitialIndexPhase is still Pending. The alignment is abandoned with scrollTop never applied, and the ordinary end-of-list fill then parks the window at _itemCount - visibleItemCapacity, leaving the viewport covered by the before-spacer and no items rendered in it. Only end the align activity once the alignment has actually landed. A completed alignment still hands control back, so viewport fill can top up the window. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 01389ba9-a838-43b0-81d5-aa36f1f7f82a
Contributor
There was a problem hiding this comment.
Pull request overview
Fixes an intermittent Blazor E2E failure where Virtualize can abandon a deferred InitialItemIndex alignment due to spacer callbacks being misclassified while the JS-side alignment is still pending. The change keeps spacer callbacks treated as programmatic until the alignment actually lands, preventing C# from redistributing the window mid-alignment.
Changes:
- Update
Virtualize.tsto only clearAlignToItemscroll activity once there is no pending align (pendingAlignLocalIndex === null). - Add a focused Jest test suite that simulates the near-end
InitialItemIndexlayout and validates callback classification both while alignment is pending and after it lands.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| src/Components/Web.JS/src/Virtualize.ts | Adds a guard (canEndAlignActivity) so AlignToItem scroll activity is only ended once deferred alignment has completed. |
| src/Components/Web.JS/test/VirtualizePendingAlign.test.ts | Adds state-machine coverage for the pending-align race by stubbing IO + DOM geometry and asserting spacer callback reasons. |
💡 Add a code-review agent skill for context-aware, tailored reviews. Learn more in the docs.
Use runOnlyPendingTimers so the test does not depend on THROTTLE_MS. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 01389ba9-a838-43b0-81d5-aa36f1f7f82a
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.
Fixes #68708
Problem
VirtualizationTest.QuickGrid_InitialIndex_TallContainer_NearEnd_FillsViewportWithoutUserScrollhas been failing intermittently onmainand on unrelated PRs since #67936 merged, on both the Mono and CoreCLR E2E legs, in both the WebAssembly and Interactive Server variants, and for bothuseProvidercases.The DOM captured at failure time explains the
-1: the before-spacer is127588px, the rendered window is items 977–999, andscrollTopis0. The viewport is entirely covered by the before-spacer, so no item intersects it.Root cause
InitialItemIndex = 950puts the component inInitialIndexPhase.Pendingand calls JSalignToItem.alignToItemAtfinds the target is not in the committed window yet, so it recordspendingAlignLocalIndexand returns without scrolling.scrollTopis still0.ScrollToItemAsyncCore'sfinallyclears_currentScrollCtswhile the alignment is still pending in JS.processIntersectionEntriescallscrollActivity.clear()wheneversource === AlignToItem— including when the alignment is merely deferred. That resets the source toNone, so the following spacer callbacks are classifiedViewportFillinstead ofProgrammaticScroll.|| _initialIndex.Phase == InitialIndexPhase.Pendingclause from the C#ViewportFillearly-return. With_currentScrollCtsalready null, C# now acts on those callbacks duringPendingand runsUpdateWindowFromViewport.scrollTopnever applied. The ordinary end-of-list fill then setsitemsBefore = _itemCount - visibleItemCapacity=1000 - 23=977, reproducing the captured DOM exactly.It is intermittent because it depends on an IntersectionObserver callback landing between the deferred alignment and its retry. The Interactive Server variant fails most often, since SignalR round-trips widen that window.
Fix
Only end the align scroll activity once the alignment has actually landed:
While an alignment is pending, spacer callbacks stay
ProgrammaticScroll, which C# ignores, so the window is not redistributed out from under it. C# already re-drivesAlignToTargetAsyncon every render while the phase isPending, so the alignment converges once the DOM catches up.This deliberately does not restore the C#
Pendingguard removed in #67936 — that guard is what enables the viewport-underfill growth that PR added. Keeping the callbacks classified as programmatic addresses the race without giving up that behavior.Testing
src/Components/Web.JS/test/Virtualize.test.tswas a 13-line export smoke test, so none of this state machine was covered. AddedVirtualizePendingAlign.test.ts, which drivesinit/beginProgrammaticScroll/alignToItemagainst a stubbed IntersectionObserver and a DOM mirroring the near-end layout:spacer callbacks stay ProgrammaticScroll while an alignment is pending— fails without the fix, passes with it.spacer callbacks resume ViewportFill once the alignment has landed— passes both with and without the fix, so the first test is not vacuous and Fix InitialItemIndex viewport underfill for small items in big container or on window resize #67936's intended behavior is still exercised.Verified red/green:
fde48e9521^(pre-#67936)main(post-#67936)Full
Web.JSjest suite: 208 passing, up from 206 at baseline. The 5 failing suites are pre-existingCannot find module '@microsoft/signalr'resolution errors, identical with and without this change.eslintreports no new problems in the edited region.Follow-up
src/Components/Web/test/Virtualization/VirtualizeTest.csstill has noInitialItemIndexcoverage at all. Worth adding separately.