Skip to content

Commit 987d6ed

Browse files
fix: prevent IntersectionObserver infinite loop in observeMove (#10099) (#10103)
Co-authored-by: Serhii Kulykov <[email protected]>
1 parent 95b410c commit 987d6ed

File tree

2 files changed

+17
-16
lines changed

2 files changed

+17
-16
lines changed

packages/overlay/src/vaadin-overlay-utils.js

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,12 @@
1616
*/
1717
export function observeMove(element, callback) {
1818
let io = null;
19+
let timeout;
1920

2021
const root = document.documentElement;
2122

2223
function cleanup() {
24+
timeout && clearTimeout(timeout);
2325
io && io.disconnect();
2426
io = null;
2527
}
@@ -52,27 +54,22 @@ export function observeMove(element, callback) {
5254
let isFirstUpdate = true;
5355

5456
function handleObserve(entries) {
55-
let ratio = entries[0].intersectionRatio;
57+
const ratio = entries[0].intersectionRatio;
5658

5759
if (ratio !== threshold) {
5860
if (!isFirstUpdate) {
5961
return refresh();
6062
}
6163

62-
// It's possible for the watched element to not be at perfect 1.0 visibility when we create
63-
// the IntersectionObserver. This has a couple of causes:
64-
// - elements being on partial pixels
65-
// - elements being hidden offscreen (e.g., <html> has `overflow: hidden`)
66-
// - delays: if your DOM change occurs due to e.g., page resize, you can see elements
67-
// behind their actual position
68-
//
69-
// In all of these cases, refresh but with this lower ratio of threshold. When the element
70-
// moves beneath _that_ new value, the user will get notified.
71-
if (ratio === 0.0) {
72-
ratio = 0.0000001; // Just needs to be non-zero
64+
if (!ratio) {
65+
// If the reference is clipped, the ratio is 0. Throttle the refresh
66+
// to prevent an infinite loop of updates.
67+
timeout = setTimeout(() => {
68+
refresh(false, 1e-7);
69+
}, 1000);
70+
} else {
71+
refresh(false, ratio);
7372
}
74-
75-
refresh(false, ratio);
7673
}
7774

7875
isFirstUpdate = false;

packages/overlay/test/position-mixin-listeners.test.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -190,11 +190,15 @@ describe('position mixin listeners', () => {
190190
});
191191

192192
it('should update position on target move by changing style', async () => {
193+
overlay.noVerticalOverlap = true;
194+
await nextFrame();
195+
193196
target.style.position = 'static';
194197
await nextFrame();
198+
await nextFrame();
195199
updatePositionSpy.resetHistory();
196200

197-
target.marginTop = '20px';
201+
target.style.marginTop = '20px';
198202
// Wait for intersection observer
199203
await nextFrame();
200204
await nextFrame();
@@ -209,7 +213,7 @@ describe('position mixin listeners', () => {
209213

210214
overlay.opened = false;
211215

212-
target.marginTop = '20px';
216+
target.style.marginTop = '20px';
213217
await nextFrame();
214218
await nextFrame();
215219
expect(updatePositionSpy.called).to.be.false;

0 commit comments

Comments
 (0)