diff --git a/src/components/virtualRepeat/virtualRepeater.js b/src/components/virtualRepeat/virtualRepeater.js
index 006fbd12014..c058773179e 100644
--- a/src/components/virtualRepeat/virtualRepeater.js
+++ b/src/components/virtualRepeat/virtualRepeater.js
@@ -48,7 +48,7 @@ function VirtualRepeatContainerDirective() {
}
-function virtualRepeatContainerTemplate($element, $attrs) {
+function virtualRepeatContainerTemplate($element) {
return '
';
}
+/**
+ * Maximum size, in pixels, that can be explicitly set to an element. The actual value varies
+ * between browsers, but IE11 has the very lowest size at a mere 1,533,917px. Ideally we could
+ * *compute* this value, but Firefox always reports an element to have a size of zero if it
+ * goes over the max, meaning that we'd have to binary search for the value.
+ * @const {number}
+ */
+var MAX_ELEMENT_SIZE = 1533917;
+
+/**
+ * Number of additional elements to render above and below the visible area inside
+ * of the virtual repeat container. A higher number results in less flicker when scrolling
+ * very quickly in Safari, but comes with a higher rendering and dirty-checking cost.
+ * @const {number}
+ */
+var NUM_EXTRA = 3;
/** @ngInject */
function VirtualRepeatContainerController($$rAF, $scope, $element, $attrs) {
@@ -131,11 +147,40 @@ VirtualRepeatContainerController.prototype.getScrollSize = function() {
/**
* Sets the scrollHeight or scrollWidth. Called by the repeater based on
* its item count and item size.
- * @param {number} The new size.
+ * @param {number} size The new size.
*/
VirtualRepeatContainerController.prototype.setScrollSize = function(size) {
if (this.scrollSize !== size) {
- this.sizer.style[this.isHorizontal() ? 'width' : 'height'] = size + 'px';
+ var dimension = this.isHorizontal() ? 'width' : 'height';
+ var crossDimension = this.isHorizontal() ? 'height' : 'width';
+
+ // If the size falls within the browser's maximum explicit size for a single element, we can
+ // set the size and be done. Otherwise, we have to create children that add up the the desired
+ // size.
+ if (size < MAX_ELEMENT_SIZE) {
+ this.sizer.style[dimension] = size + 'px';
+ } else {
+ // Clear any existing dimensions.
+ this.sizer.innerHTML = '';
+ this.sizer.style[dimension] = 'auto';
+ this.sizer.style[crossDimension] = 'auto';
+
+ // Divide the total size we have to render into N max-size pieces.
+ var numChildren = Math.floor(size / MAX_ELEMENT_SIZE);
+
+ // Element template to clone for each max-size piece.
+ var sizerChild = document.createElement('div');
+ sizerChild.style[dimension] = MAX_ELEMENT_SIZE + 'px';
+ sizerChild.style[crossDimension] = '1px';
+
+ for (var i = 0; i < numChildren; i++) {
+ this.sizer.appendChild(sizerChild.cloneNode(false));
+ }
+
+ // Re-use the element template for the remainder.
+ sizerChild.style[dimension] = (size - (numChildren * MAX_ELEMENT_SIZE)) + 'px';
+ this.sizer.appendChild(sizerChild);
+ }
}
this.scrollSize = size;
@@ -159,7 +204,7 @@ VirtualRepeatContainerController.prototype.handleScroll_ = function() {
if (offset === this.scrollOffset) return;
var itemSize = this.repeater.getItemSize();
- var numItems = Math.max(0, Math.floor(offset / itemSize) - VirtualRepeatController.NUM_EXTRA);
+ var numItems = Math.max(0, Math.floor(offset / itemSize) - NUM_EXTRA);
var transform = this.isHorizontal() ? 'translateX(' : 'translateY(';
transform += (numItems * itemSize) + 'px)';
@@ -265,15 +310,6 @@ function VirtualRepeatController($scope, $element, $attrs, $browser, $document)
VirtualRepeatController.Block;
-/**
- * Number of additional elements to render above and below the visible area inside
- * of the virtual repeat container. A higher number results in less flicker when scrolling
- * very quickly in Safari, but comes with a higher rendering and dirty-checking cost.
- * @const {number}
- */
-VirtualRepeatController.NUM_EXTRA = 3;
-
-
/**
* Called at startup by the md-virtual-repeat postLink function.
* @param {!VirtualRepeatContainerController} container The container's controller.
@@ -516,7 +552,6 @@ VirtualRepeatController.prototype.updateIndexes_ = function() {
this.newStartIndex = Math.max(0, Math.min(
itemsLength - containerLength,
Math.floor(this.container.getScrollOffset() / this.itemSize)));
- this.newEndIndex = Math.min(itemsLength, this.newStartIndex + containerLength +
- VirtualRepeatController.NUM_EXTRA);
- this.newStartIndex = Math.max(0, this.newStartIndex - VirtualRepeatController.NUM_EXTRA);
+ this.newEndIndex = Math.min(itemsLength, this.newStartIndex + containerLength + NUM_EXTRA);
+ this.newStartIndex = Math.max(0, this.newStartIndex - NUM_EXTRA);
};
diff --git a/src/components/virtualRepeat/virtualRepeater.spec.js b/src/components/virtualRepeat/virtualRepeater.spec.js
index a4ca603e871..e6614d09e29 100644
--- a/src/components/virtualRepeat/virtualRepeater.spec.js
+++ b/src/components/virtualRepeat/virtualRepeater.spec.js
@@ -181,8 +181,6 @@ describe('', function() {
expect(repeated[0].textContent.trim()).toBe('s184s 92');
});
-
-
it('should dirty-check only the swapped scope on scroll', function() {
createRepeater();
scope.items = createItems(NUM_ITEMS);
@@ -227,6 +225,30 @@ describe('', function() {
expect(getRepeated()[0].textContent.trim()).toBe('a 0');
});
+ it('should cap individual element size for the sizer in large item sets', function() {
+ // Copy max element size because we don't have a good way to reference it.
+ var maxElementSize = 1533917;
+
+ // Create a much larger number of items than will fit in one maximum element size.
+ var numItems = 2000000;
+ createRepeater();
+ scope.items = createItems(numItems);
+ scope.$apply();
+ $$rAF.flush();
+
+ // Expect that the sizer as a whole is still exactly the height it should be.
+ expect(sizer[0].offsetHeight).toBe(numItems * ITEM_SIZE);
+
+ // Expect that sizer only adds as many children as it needs to.
+ var numChildren = sizer[0].childNodes.length;
+ expect(numChildren).toBe(Math.ceil(numItems * ITEM_SIZE / maxElementSize));
+
+ // Expect that every child of sizer does not exceed the maximum element size.
+ for (var i = 0; i < numChildren; i++) {
+ expect(sizer[0].childNodes[i].offsetHeight).toBeLessThan(maxElementSize + 1);
+ }
+ });
+
/**
* Facade to access transform properly even when jQuery is used;
* since jQuery's css function is obtaining the computed style (not wanted)