Skip to content

Commit

Permalink
Fix issue where _increasePoolIfNeeded could loop indefinitely. Fixes #…
Browse files Browse the repository at this point in the history
…559

Ensure the `_physicalTop` is never recalculated to be below the current scroll position, and make sure cached scroll positions are used in methods which may be called outside of a scroll event handler for internal consistency.
  • Loading branch information
kevinpschaaf committed Oct 10, 2019
1 parent 30eb60a commit 676506d
Show file tree
Hide file tree
Showing 3 changed files with 103 additions and 5 deletions.
14 changes: 9 additions & 5 deletions iron-list.js
Original file line number Diff line number Diff line change
Expand Up @@ -803,9 +803,11 @@ Polymer({
Math.round(delta / this._physicalAverage) * this._itemsPerRow;
this._virtualStart = this._virtualStart + idxAdjustment;
this._physicalStart = this._physicalStart + idxAdjustment;
// Estimate new physical offset.
this._physicalTop = Math.floor(this._virtualStart / this._itemsPerRow) *
this._physicalAverage;
// Estimate new physical offset based on the virtual start (but never allow
// to be more than the current scrollTop)
this._physicalTop = Math.min(
Math.floor(this._virtualStart / this._itemsPerRow) *
this._physicalAverage, this._scrollPosition);
this._update();
} else if (this._physicalCount > 0) {
var reusables = this._getReusables(isScrollingDown);
Expand Down Expand Up @@ -841,7 +843,8 @@ Polymer({
var physicalCount = this._physicalCount;
var top = this._physicalTop + this._scrollOffset;
var bottom = this._physicalBottom + this._scrollOffset;
var scrollTop = this._scrollTop;
// This may be called outside of a scrollHandler, so use last cached position
var scrollTop = this._scrollPosition;
var scrollBottom = this._scrollBottom;

if (fromTop) {
Expand Down Expand Up @@ -1362,7 +1365,8 @@ Polymer({
// Note: the delta can be positive or negative.
if (deltaHeight !== 0) {
this._physicalTop = this._physicalTop - deltaHeight;
var scrollTop = this._scrollTop;
// This may be called outside of a scrollHandler, so use last cached position
var scrollTop = this._scrollPosition;
// juking scroll position during interial scrolling on iOS is no bueno
if (!IOS_TOUCH_SCROLLING && scrollTop > 0) {
this._resetScrollPosition(scrollTop - deltaHeight);
Expand Down
36 changes: 36 additions & 0 deletions test/basic.html
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,16 @@
</template>
</test-fixture>

<test-fixture id="oddSizedList">
<template>
<odd-sized-list></odd-sized-list>
</template>
</test-fixture>

<script type="module">
import './fixtures/helpers.js';
import './fixtures/x-list.js';
import './fixtures/odd-sized-list.js';
import {dom} from '@polymer/polymer/lib/legacy/polymer.dom.js';
suite('basic features', function() {
var list, container;
Expand Down Expand Up @@ -300,6 +306,36 @@
});
});
});

suite('scrolling through heterogeneously-sized list', function() {
var list;

setup(function() {
list = fixture('oddSizedList').$.list;
PolymerFlush();
});

// This test catches a runaway condition where _increasePoolIfNeeded
// could recurse indefinitely due to the physical average becoming
// large enough (due to heterogeneously-sized items) to position the
// physical top lower than the scroll position, causing _isClientFull
// to return true; this
test('Issue #559: scroll heterogeneously sized list', function(done) {
list.scrollTop = 2300;
requestAnimationFrame(function() {
list.scrollTop = 0;
list.fire('iron-resize');
requestAnimationFrame(function() {
// In the current implementation, there should be ~29, but
// being generous here to allow for changes in the algorithm;
// idea being to catch a runaway situation
assert.isAtMost(list.children.length, 50);
done();
});
});
});
});

</script>

</body>
Expand Down
58 changes: 58 additions & 0 deletions test/fixtures/odd-sized-list.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/**
@license
Copyright (c) 3015 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at
http://polymer.github.io/LICENSE The complete set of authors may be found at
http://polymer.github.io/AUTHORS The complete set of contributors may be found
at http://polymer.github.io/CONTRIBUTORS Code distributed by Google as part of
the polymer project is also subject to an additional IP rights grant found at
http://polymer.github.io/PATENTS
*/
import '@polymer/polymer/polymer-legacy.js';

import '../../iron-list.js';
import {Polymer} from '@polymer/polymer/lib/legacy/polymer-fn.js';
import {html} from '@polymer/polymer/lib/utils/html-tag.js';
Polymer({
is: 'odd-sized-list',
_template: html`
<style>
:host {
display: block;
width: 300px;
height: 300px;
border: 1px solid black;
}
iron-list {
height: 100%;
}
.item {
overflow: hidden;
border-bottom: 1px solid gray;
}
</style>
<iron-list id="list" items="{{items}}">
<template>
<div class="item" style="height: [[item]]px;">Item [[index]] ([[item]]px)</div>
</template>
</iron-list>`,
properties: {
items: {
value: function() {
// Some hand-tuned sizes to ensure the physical average
// after scrolling back up is such to trigger the bug
const items = [];
while (items.length < 70) {
items.push(30);
}
while (items.length < 90) {
items.push(90);
}
while (items.length < 5000) {
items.push(30);
}
return items;
}
}
}
});

0 comments on commit 676506d

Please sign in to comment.