| OLD | NEW |
| 1 /* | 1 /* |
| 2 * Copyright (C) 2013 Google Inc. All rights reserved. | 2 * Copyright (C) 2013 Google Inc. All rights reserved. |
| 3 * | 3 * |
| 4 * Redistribution and use in source and binary forms, with or without | 4 * Redistribution and use in source and binary forms, with or without |
| 5 * modification, are permitted provided that the following conditions are | 5 * modification, are permitted provided that the following conditions are |
| 6 * met: | 6 * met: |
| 7 * | 7 * |
| 8 * * Redistributions of source code must retain the above copyright | 8 * * Redistributions of source code must retain the above copyright |
| 9 * notice, this list of conditions and the following disclaimer. | 9 * notice, this list of conditions and the following disclaimer. |
| 10 * * Redistributions in binary form must reproduce the above | 10 * * Redistributions in binary form must reproduce the above |
| (...skipping 43 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 54 this.element.addEventListener("copy", this._onCopy.bind(this), false); | 54 this.element.addEventListener("copy", this._onCopy.bind(this), false); |
| 55 this.element.addEventListener("dragstart", this._onDragStart.bind(this), fal
se); | 55 this.element.addEventListener("dragstart", this._onDragStart.bind(this), fal
se); |
| 56 | 56 |
| 57 this._firstVisibleIndex = 0; | 57 this._firstVisibleIndex = 0; |
| 58 this._lastVisibleIndex = -1; | 58 this._lastVisibleIndex = -1; |
| 59 this._renderedItems = []; | 59 this._renderedItems = []; |
| 60 this._anchorSelection = null; | 60 this._anchorSelection = null; |
| 61 this._headSelection = null; | 61 this._headSelection = null; |
| 62 this._stickToBottom = false; | 62 this._stickToBottom = false; |
| 63 this._scrolledToBottom = true; | 63 this._scrolledToBottom = true; |
| 64 this._itemCount = 0; |
| 64 } | 65 } |
| 65 | 66 |
| 66 /** | 67 /** |
| 67 * @interface | 68 * @interface |
| 68 */ | 69 */ |
| 69 WebInspector.ViewportControl.Provider = function() | 70 WebInspector.ViewportControl.Provider = function() |
| 70 { | 71 { |
| 71 } | 72 } |
| 72 | 73 |
| 73 WebInspector.ViewportControl.Provider.prototype = { | 74 WebInspector.ViewportControl.Provider.prototype = { |
| (...skipping 114 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 188 */ | 189 */ |
| 189 contentElement: function() | 190 contentElement: function() |
| 190 { | 191 { |
| 191 return this._contentElement; | 192 return this._contentElement; |
| 192 }, | 193 }, |
| 193 | 194 |
| 194 invalidate: function() | 195 invalidate: function() |
| 195 { | 196 { |
| 196 delete this._cumulativeHeights; | 197 delete this._cumulativeHeights; |
| 197 delete this._cachedProviderElements; | 198 delete this._cachedProviderElements; |
| 198 this.refresh(); | 199 this._itemCount = this._provider.itemCount(); |
| 200 this._innerRefresh(false); |
| 199 }, | 201 }, |
| 200 | 202 |
| 201 /** | 203 /** |
| 202 * @param {number} index | 204 * @param {number} index |
| 203 * @return {?WebInspector.ViewportElement} | 205 * @return {?WebInspector.ViewportElement} |
| 204 */ | 206 */ |
| 205 _providerElement: function(index) | 207 _providerElement: function(index) |
| 206 { | 208 { |
| 207 if (!this._cachedProviderElements) | 209 if (!this._cachedProviderElements) |
| 208 this._cachedProviderElements = new Array(this._provider.itemCount())
; | 210 this._cachedProviderElements = new Array(this._itemCount); |
| 209 var element = this._cachedProviderElements[index]; | 211 var element = this._cachedProviderElements[index]; |
| 210 if (!element) { | 212 if (!element) { |
| 211 element = this._provider.itemElement(index); | 213 element = this._provider.itemElement(index); |
| 212 this._cachedProviderElements[index] = element; | 214 this._cachedProviderElements[index] = element; |
| 213 } | 215 } |
| 214 return element; | 216 return element; |
| 215 }, | 217 }, |
| 216 | 218 |
| 217 _rebuildCumulativeHeightsIfNeeded: function() | 219 _rebuildCumulativeHeightsIfNeeded: function() |
| 218 { | 220 { |
| 219 if (this._cumulativeHeights) | 221 if (this._cumulativeHeights) |
| 220 return; | 222 return; |
| 221 var itemCount = this._provider.itemCount(); | 223 if (!this._itemCount) |
| 222 if (!itemCount) | |
| 223 return; | 224 return; |
| 224 var firstVisibleIndex = this._firstVisibleIndex; | 225 var firstVisibleIndex = this._firstVisibleIndex; |
| 225 var lastVisibleIndex = this._lastVisibleIndex; | 226 var lastVisibleIndex = this._lastVisibleIndex; |
| 226 var height = 0; | 227 var height = 0; |
| 227 this._cumulativeHeights = new Int32Array(itemCount); | 228 this._cumulativeHeights = new Int32Array(this._itemCount); |
| 228 for (var i = 0; i < itemCount; ++i) { | 229 for (var i = 0; i < this._itemCount; ++i) { |
| 229 if (firstVisibleIndex <= i && i <= lastVisibleIndex) | 230 if (firstVisibleIndex <= i && i <= lastVisibleIndex) |
| 230 height += this._renderedItems[i - firstVisibleIndex].element().o
ffsetHeight; | 231 height += this._renderedItems[i - firstVisibleIndex].element().o
ffsetHeight; |
| 231 else | 232 else |
| 232 height += this._provider.fastHeight(i); | 233 height += this._provider.fastHeight(i); |
| 233 this._cumulativeHeights[i] = height; | 234 this._cumulativeHeights[i] = height; |
| 234 } | 235 } |
| 235 }, | 236 }, |
| 236 | 237 |
| 237 /** | 238 /** |
| 238 * @param {number} index | 239 * @param {number} index |
| (...skipping 64 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 303 var topOverlap = range.intersectsNode(this._topGapElement) && this._topG
apElement._active; | 304 var topOverlap = range.intersectsNode(this._topGapElement) && this._topG
apElement._active; |
| 304 var bottomOverlap = range.intersectsNode(this._bottomGapElement) && this
._bottomGapElement._active; | 305 var bottomOverlap = range.intersectsNode(this._bottomGapElement) && this
._bottomGapElement._active; |
| 305 if (!topOverlap && !bottomOverlap && !hasVisibleSelection) { | 306 if (!topOverlap && !bottomOverlap && !hasVisibleSelection) { |
| 306 this._headSelection = null; | 307 this._headSelection = null; |
| 307 this._anchorSelection = null; | 308 this._anchorSelection = null; |
| 308 return false; | 309 return false; |
| 309 } | 310 } |
| 310 | 311 |
| 311 if (!this._anchorSelection || !this._headSelection) { | 312 if (!this._anchorSelection || !this._headSelection) { |
| 312 this._anchorSelection = this._createSelectionModel(0, this.element,
0); | 313 this._anchorSelection = this._createSelectionModel(0, this.element,
0); |
| 313 this._headSelection = this._createSelectionModel(this._provider.item
Count() - 1, this.element, this.element.children.length); | 314 this._headSelection = this._createSelectionModel(this._itemCount - 1
, this.element, this.element.children.length); |
| 314 this._selectionIsBackward = false; | 315 this._selectionIsBackward = false; |
| 315 } | 316 } |
| 316 | 317 |
| 317 var isBackward = this._isSelectionBackwards(selection); | 318 var isBackward = this._isSelectionBackwards(selection); |
| 318 var startSelection = this._selectionIsBackward ? this._headSelection : t
his._anchorSelection; | 319 var startSelection = this._selectionIsBackward ? this._headSelection : t
his._anchorSelection; |
| 319 var endSelection = this._selectionIsBackward ? this._anchorSelection : t
his._headSelection; | 320 var endSelection = this._selectionIsBackward ? this._anchorSelection : t
his._headSelection; |
| 320 if (topOverlap && bottomOverlap && hasVisibleSelection) { | 321 if (topOverlap && bottomOverlap && hasVisibleSelection) { |
| 321 firstSelected = firstSelected.item < startSelection.item ? firstSele
cted : startSelection; | 322 firstSelected = firstSelected.item < startSelection.item ? firstSele
cted : startSelection; |
| 322 lastSelected = lastSelected.item > endSelection.item ? lastSelected
: endSelection; | 323 lastSelected = lastSelected.item > endSelection.item ? lastSelected
: endSelection; |
| 323 } else if (!hasVisibleSelection) { | 324 } else if (!hasVisibleSelection) { |
| (...skipping 44 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 368 else if (this._headSelection.item > this._lastVisibleIndex) | 369 else if (this._headSelection.item > this._lastVisibleIndex) |
| 369 headElement = this._bottomGapElement; | 370 headElement = this._bottomGapElement; |
| 370 headOffset = this._selectionIsBackward ? 0 : 1; | 371 headOffset = this._selectionIsBackward ? 0 : 1; |
| 371 } | 372 } |
| 372 | 373 |
| 373 selection.setBaseAndExtent(anchorElement, anchorOffset, headElement, hea
dOffset); | 374 selection.setBaseAndExtent(anchorElement, anchorOffset, headElement, hea
dOffset); |
| 374 }, | 375 }, |
| 375 | 376 |
| 376 refresh: function() | 377 refresh: function() |
| 377 { | 378 { |
| 379 this._innerRefresh(false); |
| 380 }, |
| 381 |
| 382 /** |
| 383 * @param {boolean} isUserGesture |
| 384 */ |
| 385 _innerRefresh: function(isUserGesture) |
| 386 { |
| 378 if (!this._visibleHeight()) | 387 if (!this._visibleHeight()) |
| 379 return; // Do nothing for invisible controls. | 388 return; // Do nothing for invisible controls. |
| 380 | 389 |
| 381 if (!this._provider.itemCount()) { | 390 if (!this._itemCount) { |
| 382 for (var i = 0; i < this._renderedItems.length; ++i) | 391 for (var i = 0; i < this._renderedItems.length; ++i) |
| 383 this._renderedItems[i].willHide(); | 392 this._renderedItems[i].willHide(); |
| 384 this._renderedItems = []; | 393 this._renderedItems = []; |
| 385 this._contentElement.removeChildren(); | 394 this._contentElement.removeChildren(); |
| 386 this._topGapElement.style.height = "0px"; | 395 this._topGapElement.style.height = "0px"; |
| 387 this._bottomGapElement.style.height = "0px"; | 396 this._bottomGapElement.style.height = "0px"; |
| 388 this._firstVisibleIndex = -1; | 397 this._firstVisibleIndex = -1; |
| 389 this._lastVisibleIndex = -1; | 398 this._lastVisibleIndex = -1; |
| 390 return; | 399 return; |
| 391 } | 400 } |
| 392 | 401 |
| 393 var selection = this.element.getComponentSelection(); | 402 var selection = this.element.getComponentSelection(); |
| 394 var shouldRestoreSelection = this._updateSelectionModel(selection); | 403 var shouldRestoreSelection = this._updateSelectionModel(selection); |
| 395 | 404 |
| 396 var visibleFrom = this.element.scrollTop; | 405 var visibleFrom = this.element.scrollTop; |
| 397 var visibleHeight = this._visibleHeight(); | 406 var visibleHeight = this._visibleHeight(); |
| 398 this._scrolledToBottom = this.element.isScrolledToBottom(); | 407 this._scrolledToBottom = this.element.isScrolledToBottom(); |
| 399 var isInvalidating = !this._cumulativeHeights; | 408 var isInvalidating = !this._cumulativeHeights; |
| 400 | 409 |
| 401 for (var i = 0; i < this._renderedItems.length; ++i) { | 410 for (var i = 0; i < this._renderedItems.length; ++i) { |
| 402 // Tolerate 1-pixel error due to double-to-integer rounding errors. | 411 // Tolerate 1-pixel error due to double-to-integer rounding errors. |
| 403 if (this._cumulativeHeights && Math.abs(this._cachedItemHeight(this.
_firstVisibleIndex + i) - this._renderedItems[i].element().offsetHeight) > 1) | 412 if (this._cumulativeHeights && Math.abs(this._cachedItemHeight(this.
_firstVisibleIndex + i) - this._renderedItems[i].element().offsetHeight) > 1) |
| 404 delete this._cumulativeHeights; | 413 delete this._cumulativeHeights; |
| 405 } | 414 } |
| 406 this._rebuildCumulativeHeightsIfNeeded(); | 415 this._rebuildCumulativeHeightsIfNeeded(); |
| 407 var itemCount = this._cumulativeHeights.length; | |
| 408 var oldFirstVisibleIndex = this._firstVisibleIndex; | 416 var oldFirstVisibleIndex = this._firstVisibleIndex; |
| 409 var oldLastVisibleIndex = this._lastVisibleIndex; | 417 var oldLastVisibleIndex = this._lastVisibleIndex; |
| 410 | 418 |
| 411 var shouldStickToBottom = isInvalidating && this._stickToBottom && this.
_scrolledToBottom; | 419 var shouldStickToBottom = !isUserGesture && this._stickToBottom && this.
_scrolledToBottom; |
| 412 | 420 |
| 413 if (shouldStickToBottom) { | 421 if (shouldStickToBottom) { |
| 414 this._lastVisibleIndex = itemCount - 1; | 422 this._lastVisibleIndex = this._itemCount - 1; |
| 415 this._firstVisibleIndex = Math.max(itemCount - Math.ceil(visibleHeig
ht / this._provider.minimumRowHeight()), 0); | 423 this._firstVisibleIndex = Math.max(this._itemCount - Math.ceil(visib
leHeight / this._provider.minimumRowHeight()), 0); |
| 416 } else { | 424 } else { |
| 417 this._firstVisibleIndex = Math.max(Array.prototype.lowerBound.call(t
his._cumulativeHeights, visibleFrom + 1), 0); | 425 this._firstVisibleIndex = Math.max(Array.prototype.lowerBound.call(t
his._cumulativeHeights, visibleFrom + 1), 0); |
| 418 // Proactively render more rows in case some of them will be collaps
ed without triggering refresh. @see crbug.com/390169 | 426 // Proactively render more rows in case some of them will be collaps
ed without triggering refresh. @see crbug.com/390169 |
| 419 this._lastVisibleIndex = this._firstVisibleIndex + Math.ceil(visible
Height / this._provider.minimumRowHeight()) - 1; | 427 this._lastVisibleIndex = this._firstVisibleIndex + Math.ceil(visible
Height / this._provider.minimumRowHeight()) - 1; |
| 420 this._lastVisibleIndex = Math.min(this._lastVisibleIndex, itemCount
- 1); | 428 this._lastVisibleIndex = Math.min(this._lastVisibleIndex, this._item
Count - 1); |
| 421 } | 429 } |
| 422 var topGapHeight = this._cumulativeHeights[this._firstVisibleIndex - 1]
|| 0; | 430 var topGapHeight = this._cumulativeHeights[this._firstVisibleIndex - 1]
|| 0; |
| 423 var bottomGapHeight = this._cumulativeHeights[this._cumulativeHeights.le
ngth - 1] - this._cumulativeHeights[this._lastVisibleIndex]; | 431 var bottomGapHeight = this._cumulativeHeights[this._cumulativeHeights.le
ngth - 1] - this._cumulativeHeights[this._lastVisibleIndex]; |
| 424 | 432 |
| 425 /** | 433 /** |
| 426 * @this {WebInspector.ViewportControl} | 434 * @this {WebInspector.ViewportControl} |
| 427 */ | 435 */ |
| 428 function prepare() | 436 function prepare() |
| 429 { | 437 { |
| 430 this._topGapElement.style.height = topGapHeight + "px"; | 438 this._topGapElement.style.height = topGapHeight + "px"; |
| 431 this._bottomGapElement.style.height = bottomGapHeight + "px"; | 439 this._bottomGapElement.style.height = bottomGapHeight + "px"; |
| 432 this._topGapElement._active = !!topGapHeight; | 440 this._topGapElement._active = !!topGapHeight; |
| 433 this._bottomGapElement._active = !!bottomGapHeight; | 441 this._bottomGapElement._active = !!bottomGapHeight; |
| 434 this._contentElement.style.setProperty("height", "10000000px"); | 442 this._contentElement.style.setProperty("height", "10000000px"); |
| 435 } | 443 } |
| 436 | 444 |
| 437 if (isInvalidating) | 445 if (isInvalidating) |
| 438 this._fullViewportUpdate(prepare.bind(this)); | 446 this._fullViewportUpdate(prepare.bind(this)); |
| 439 else | 447 else |
| 440 this._partialViewportUpdate(oldFirstVisibleIndex, oldLastVisibleInde
x, prepare.bind(this)); | 448 this._partialViewportUpdate(oldFirstVisibleIndex, oldLastVisibleInde
x, prepare.bind(this)); |
| 441 this._contentElement.style.removeProperty("height"); | 449 this._contentElement.style.removeProperty("height"); |
| 442 // Should be the last call in the method as it might force layout. | 450 // Should be the last call in the method as it might force layout. |
| 443 if (shouldRestoreSelection) | 451 if (shouldRestoreSelection) |
| 444 this._restoreSelection(selection); | 452 this._restoreSelection(selection); |
| 445 if (shouldStickToBottom) | 453 if (shouldStickToBottom) |
| 446 this.element.scrollTop = this.element.scrollHeight; | 454 this.element.scrollTop = 10000000; |
| 447 }, | 455 }, |
| 448 | 456 |
| 449 /** | 457 /** |
| 450 * @param {function()} prepare | 458 * @param {function()} prepare |
| 451 */ | 459 */ |
| 452 _fullViewportUpdate: function(prepare) | 460 _fullViewportUpdate: function(prepare) |
| 453 { | 461 { |
| 454 for (var i = 0; i < this._renderedItems.length; ++i) | 462 for (var i = 0; i < this._renderedItems.length; ++i) |
| 455 this._renderedItems[i].willHide(); | 463 this._renderedItems[i].willHide(); |
| 456 prepare(); | 464 prepare(); |
| (...skipping 96 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 553 while ((node = node.traverseNextTextNode()) && node !== container) | 561 while ((node = node.traverseNextTextNode()) && node !== container) |
| 554 chars += node.textContent.length; | 562 chars += node.textContent.length; |
| 555 return chars + offset; | 563 return chars + offset; |
| 556 }, | 564 }, |
| 557 | 565 |
| 558 /** | 566 /** |
| 559 * @param {!Event} event | 567 * @param {!Event} event |
| 560 */ | 568 */ |
| 561 _onScroll: function(event) | 569 _onScroll: function(event) |
| 562 { | 570 { |
| 563 this.refresh(); | 571 this._innerRefresh(event.isTrusted); |
| 564 }, | 572 }, |
| 565 | 573 |
| 566 /** | 574 /** |
| 567 * @return {number} | 575 * @return {number} |
| 568 */ | 576 */ |
| 569 firstVisibleIndex: function() | 577 firstVisibleIndex: function() |
| 570 { | 578 { |
| 571 return this._firstVisibleIndex; | 579 return this._firstVisibleIndex; |
| 572 }, | 580 }, |
| 573 | 581 |
| (...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 607 this.forceScrollItemToBeLast(index); | 615 this.forceScrollItemToBeLast(index); |
| 608 }, | 616 }, |
| 609 | 617 |
| 610 /** | 618 /** |
| 611 * @param {number} index | 619 * @param {number} index |
| 612 */ | 620 */ |
| 613 forceScrollItemToBeFirst: function(index) | 621 forceScrollItemToBeFirst: function(index) |
| 614 { | 622 { |
| 615 this._rebuildCumulativeHeightsIfNeeded(); | 623 this._rebuildCumulativeHeightsIfNeeded(); |
| 616 this.element.scrollTop = index > 0 ? this._cumulativeHeights[index - 1]
: 0; | 624 this.element.scrollTop = index > 0 ? this._cumulativeHeights[index - 1]
: 0; |
| 617 this.refresh(); | 625 this._innerRefresh(false); |
| 618 }, | 626 }, |
| 619 | 627 |
| 620 /** | 628 /** |
| 621 * @param {number} index | 629 * @param {number} index |
| 622 */ | 630 */ |
| 623 forceScrollItemToBeLast: function(index) | 631 forceScrollItemToBeLast: function(index) |
| 624 { | 632 { |
| 625 this._rebuildCumulativeHeightsIfNeeded(); | 633 this._rebuildCumulativeHeightsIfNeeded(); |
| 626 this.element.scrollTop = this._cumulativeHeights[index] - this._visibleH
eight(); | 634 this.element.scrollTop = this._cumulativeHeights[index] - this._visibleH
eight(); |
| 627 this.refresh(); | 635 this._innerRefresh(false); |
| 628 }, | 636 }, |
| 629 | 637 |
| 630 /** | 638 /** |
| 631 * @return {number} | 639 * @return {number} |
| 632 */ | 640 */ |
| 633 _visibleHeight: function() | 641 _visibleHeight: function() |
| 634 { | 642 { |
| 635 // Use offsetHeight instead of clientHeight to avoid being affected by h
orizontal scroll. | 643 // Use offsetHeight instead of clientHeight to avoid being affected by h
orizontal scroll. |
| 636 return this.element.offsetHeight; | 644 return this.element.offsetHeight; |
| 637 } | 645 } |
| 638 } | 646 } |
| OLD | NEW |