Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Conversation

@graynorton
Copy link
Contributor

Fixes #4125, which was a regression caused by #3976 (a fix for #3904).

When connected, a virtualizer walks up the DOM tree, visiting its ancestor elements and determining which of them are "clipping ancestors" (ancestor elements whose scroll positions may affect the visibility of the virtualizer itself). In principle, virtualizer should stop looking for clipping ancestors as soon as it comes to a position: fixed ancestor, since fixed positioning is always relative to the document, making any higher-level ancestors irrelevant.

Prior to #3976, we didn't pay attention to whether ancestors were position: fixed, causing the issue described in #3904.

However, the logic in #3976 wasn't quite right; it excluded the position: fixed ancestor itself, along with its higher-level ancestors. In the case where the position: fixed ancestor was itself a scroller, this caused virtualizer not to listen for scroll events and therefore not re-render children when scrolling occurred (as described in #4125).

Fixed the logic and added regression tests for both issues.

@changeset-bot
Copy link

changeset-bot bot commented Aug 22, 2023

🦋 Changeset detected

Latest commit: 6a7dc7f

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@lit-labs/virtualizer Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@github-actions
Copy link
Contributor

github-actions bot commented Aug 22, 2023

📊 Tachometer Benchmark Results

Summary

nop-update

  • this-change, tip-of-tree, previous-release: unsure 🔍 -4% - +9% (-0.75ms - +1.75ms)
    this-change vs tip-of-tree

render

  • this-change: 69.18ms - 72.20ms
  • this-change, tip-of-tree, previous-release: unsure 🔍 -1% - +6% (-0.23ms - +1.68ms)
    this-change vs tip-of-tree
  • this-change, tip-of-tree, previous-release: unsure 🔍 -4% - +0% (-2.11ms - +0.21ms)
    this-change vs tip-of-tree
  • this-change, tip-of-tree, previous-release: unsure 🔍 -1% - +2% (-0.47ms - +0.98ms)
    this-change vs tip-of-tree

update

  • this-change: 660.36ms - 669.17ms
  • this-change, tip-of-tree, previous-release: unsure 🔍 -8% - +4% (-5.96ms - +3.15ms)
    this-change vs tip-of-tree
  • this-change, tip-of-tree, previous-release: unsure 🔍 -3% - +0% (-2.61ms - +0.18ms)
    this-change vs tip-of-tree
  • this-change, tip-of-tree, previous-release: unsure 🔍 -0% - +1% (-3.19ms - +5.81ms)
    this-change vs tip-of-tree

update-reflect

  • this-change: 642.03ms - 647.42ms
  • this-change, tip-of-tree, previous-release: unsure 🔍 -1% - +1% (-3.52ms - +6.45ms)
    this-change vs tip-of-tree

Results

this-change

render

VersionAvg timevs
69.18ms - 72.20ms-

update

VersionAvg timevs
660.36ms - 669.17ms-

update-reflect

VersionAvg timevs
642.03ms - 647.42ms-
this-change, tip-of-tree, previous-release

render

VersionAvg timevs this-change
vs tip-of-tree
tip-of-tree
vs previous-release
previous-release
this-change
26.26ms - 27.66ms-unsure 🔍
-1% - +6%
-0.23ms - +1.68ms
unsure 🔍
-5% - +3%
-1.41ms - +0.87ms
tip-of-tree
tip-of-tree
25.59ms - 26.89msunsure 🔍
-6% - +1%
-1.68ms - +0.23ms
-unsure 🔍
-8% - +0%
-2.11ms - +0.12ms
previous-release
previous-release
26.33ms - 28.14msunsure 🔍
-3% - +5%
-0.87ms - +1.41ms
unsure 🔍
-0% - +8%
-0.12ms - +2.11ms
-

update

VersionAvg timevs this-change
vs tip-of-tree
tip-of-tree
vs previous-release
previous-release
this-change
68.43ms - 73.33ms-unsure 🔍
-8% - +4%
-5.96ms - +3.15ms
unsure 🔍
-6% - +4%
-4.01ms - +3.14ms
tip-of-tree
tip-of-tree
68.44ms - 76.12msunsure 🔍
-4% - +8%
-3.15ms - +5.96ms
-unsure 🔍
-5% - +8%
-3.67ms - +5.61ms
previous-release
previous-release
68.71ms - 73.91msunsure 🔍
-4% - +6%
-3.14ms - +4.01ms
unsure 🔍
-8% - +5%
-5.61ms - +3.67ms
-

nop-update

VersionAvg timevs this-change
vs tip-of-tree
tip-of-tree
vs previous-release
previous-release
this-change
18.99ms - 20.80ms-unsure 🔍
-4% - +9%
-0.75ms - +1.75ms
unsure 🔍
-7% - +8%
-1.38ms - +1.49ms
tip-of-tree
tip-of-tree
18.54ms - 20.25msunsure 🔍
-9% - +4%
-1.75ms - +0.75ms
-unsure 🔍
-9% - +5%
-1.85ms - +0.96ms
previous-release
previous-release
18.73ms - 20.96msunsure 🔍
-7% - +7%
-1.49ms - +1.38ms
unsure 🔍
-5% - +10%
-0.96ms - +1.85ms
-
this-change, tip-of-tree, previous-release

render

VersionAvg timevs this-change
vs tip-of-tree
tip-of-tree
vs previous-release
previous-release
this-change
47.55ms - 48.78ms-unsure 🔍
-4% - +0%
-2.11ms - +0.21ms
unsure 🔍
-2% - +1%
-1.10ms - +0.53ms
tip-of-tree
tip-of-tree
48.13ms - 50.10msunsure 🔍
-0% - +4%
-0.21ms - +2.11ms
-unsure 🔍
-1% - +4%
-0.45ms - +1.78ms
previous-release
previous-release
47.92ms - 48.98msunsure 🔍
-1% - +2%
-0.53ms - +1.10ms
unsure 🔍
-4% - +1%
-1.78ms - +0.45ms
-

update

VersionAvg timevs this-change
vs tip-of-tree
tip-of-tree
vs previous-release
previous-release
this-change
98.60ms - 100.39ms-unsure 🔍
-3% - +0%
-2.61ms - +0.18ms
faster ✔
0% - 3%
0.34ms - 2.69ms
tip-of-tree
tip-of-tree
99.64ms - 101.78msunsure 🔍
-0% - +3%
-0.18ms - +2.61ms
-unsure 🔍
-2% - +1%
-1.62ms - +1.01ms
previous-release
previous-release
100.26ms - 101.78msslower ❌
0% - 3%
0.34ms - 2.69ms
unsure 🔍
-1% - +2%
-1.01ms - +1.62ms
-
this-change, tip-of-tree, previous-release

render

VersionAvg timevs this-change
vs tip-of-tree
tip-of-tree
vs previous-release
previous-release
this-change
43.61ms - 44.76ms-unsure 🔍
-1% - +2%
-0.47ms - +0.98ms
unsure 🔍
-2% - +1%
-0.96ms - +0.62ms
tip-of-tree
tip-of-tree
43.49ms - 44.37msunsure 🔍
-2% - +1%
-0.98ms - +0.47ms
-unsure 🔍
-3% - +1%
-1.11ms - +0.27ms
previous-release
previous-release
43.82ms - 44.89msunsure 🔍
-1% - +2%
-0.62ms - +0.96ms
unsure 🔍
-1% - +3%
-0.27ms - +1.11ms
-

update

VersionAvg timevs this-change
vs tip-of-tree
tip-of-tree
vs previous-release
previous-release
this-change
657.27ms - 663.95ms-unsure 🔍
-0% - +1%
-3.19ms - +5.81ms
unsure 🔍
-1% - +1%
-3.67ms - +5.81ms
tip-of-tree
tip-of-tree
656.29ms - 662.32msunsure 🔍
-1% - +0%
-5.81ms - +3.19ms
-unsure 🔍
-1% - +1%
-4.76ms - +4.28ms
previous-release
previous-release
656.18ms - 662.90msunsure 🔍
-1% - +1%
-5.81ms - +3.67ms
unsure 🔍
-1% - +1%
-4.28ms - +4.76ms
-

update-reflect

VersionAvg timevs this-change
vs tip-of-tree
tip-of-tree
vs previous-release
previous-release
this-change
665.98ms - 673.67ms-unsure 🔍
-1% - +1%
-3.52ms - +6.45ms
unsure 🔍
-1% - +1%
-3.62ms - +5.88ms
tip-of-tree
tip-of-tree
665.20ms - 671.53msunsure 🔍
-1% - +1%
-6.45ms - +3.52ms
-unsure 🔍
-1% - +1%
-4.55ms - +3.89ms
previous-release
previous-release
665.91ms - 671.49msunsure 🔍
-1% - +1%
-5.88ms - +3.62ms
unsure 🔍
-1% - +1%
-3.89ms - +4.55ms
-

tachometer-reporter-action v2 for Benchmarks

Copy link
Member

@augustjk augustjk left a comment

Choose a reason for hiding this comment

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

Excellent fix and test additions!

I had a some questions about whether we might not need the awaiting of layoutComplete but seems like it is indeed needed at least when we want to test scrolling.

Comment on lines +67 to +70
// In practice, we'll time out if we fail here because the `layoutComplete`
// promise will never be fulfilled.
await virtualizer.layoutComplete;
expect(virtualizer.textContent).to.contain('Item 0');
Copy link
Member

Choose a reason for hiding this comment

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

Considering a failure's a failure so I'm okay either way, but what are your thoughts on using pass here since the failure message would be more explicit?

Copy link
Contributor Author

@graynorton graynorton Aug 23, 2023

Choose a reason for hiding this comment

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

I prefer to avoid using pass except in cases where we really can't avoid it; instead, I'd prefer to make sure layoutComplete is as reliable as possible and use that for cases where we want to check the state of things after a virtualizer renders.

That said, I was also bothered by the fact that this test failed with a timeout rather than something more akin to the failure you'd observe in manual testing, so I did a bit of digging. It turns out that there were two quirks in the layoutComplete implementation that resulted in the timeout in this case:

  • When you provided a layout explicitly, rather than relying on the default layout to be dynamically loaded, the initial layout pass would complete before you had a chance to request the layoutComplete promise.

  • If a layout pass resulted in no children being rendered (as in the failure case for this test), layoutComplete would never resolve.

I have made tweaks to address both of these quirks and pushed a new commit to this branch. As a result, this test now fails not with a timeout but with a bad render, as you'd expect.

However, there is a decent chance that releasing this change will require users to update some existing tests; specifically, if they are rendering a virtualizer and immediately checking the state of its initial render (without any delay or retry logic), they will likely need to insert an await virtualizer.layoutComplete. (I had to do this to two of our existing tests.) So we need to decide whether we want to go ahead (I'm inclined to) and, if so, whether we call this a breaking change for the Virtualizer release (I'm open to discussion).

Edit: If we do go ahead, we'll want to edit the change log entry for this release to separately call out the change to layoutComplete.

Copy link
Member

Choose a reason for hiding this comment

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

I'm torn. I think the change to layoutComplete is worth discussing but probably better to be split from this PR just so we can land the position: fixed scroller fix first.

Like the comment you added, it feels bad to introduce a delay purely for the sake of a promise whose main use is for testing.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, that makes sense. I'll roll back that commit here and make the layoutComplete changes into a separate PR.

Comment on lines +106 to +117
await virtualizer.layoutComplete;
expect(virtualizer.textContent).to.contain('Item 0');

// If the position: fixed scroller has properly been recognized as
// a clipping ancestor, then virtualizer will re-render as scrolling
// occurs; otherwise, not.
//
// In practice, we'll time out if we fail here because the `layoutComplete`
// promise will never be fulfilled.
scroller.scrollTo(0, scroller.scrollHeight);
await virtualizer.layoutComplete;
expect(virtualizer.textContent).to.contain('Item 99');
Copy link
Member

Choose a reason for hiding this comment

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

Interestingly replacing both of the await virtualizer.layoutComplete here with pass() makes this fail. There must be something about starting a scroller.scrollTo() specifically after awaiting layoutComplete that looking for 'Item 0' alone isn't enough.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, it is possible in a testing context to initiate a scroll while virtualizer is still in the middle of a layout pass, and this can cause unpredictable results. Waiting for layoutComplete avoids this situation, which is another reason why I'd like to lean on it more heavily in our tests.

In my updated version of this test, I added some ad hoc logic to race the layoutComplete promise against a short timeout so that we don't fail with a timeout; instead, we fail because the rendered state of the virtualizer is not correct (which aligns nicely with how you'd observe this failure in manual testing).

@graynorton graynorton requested a review from augustjk August 23, 2023 23:55
@graynorton
Copy link
Contributor Author

@augustjk Thanks for your review! I made some changes in response to your comments, though a bit different from the ones you proposed. See my detailed comments above. It's not obvious whether we should proceed with the changes in my last commit, so please take a look and let's discuss.

@graynorton graynorton merged commit d7bd030 into main Aug 24, 2023
@graynorton graynorton deleted the virtualizer-fix-4125 branch August 24, 2023 15:20
@lit-robot lit-robot mentioned this pull request Aug 30, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[labs/virtualizer] Fixed-position virtualizer not refreshing items on scroll

2 participants