Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(214)

Unified Diff: third_party/WebKit/Source/devtools/front_end/ui/ViewportControl.js

Issue 2592483002: [DevTools] Move console viewport to console, rename StaticViewportControl. (Closed)
Patch Set: Created 4 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: third_party/WebKit/Source/devtools/front_end/ui/ViewportControl.js
diff --git a/third_party/WebKit/Source/devtools/front_end/ui/ViewportControl.js b/third_party/WebKit/Source/devtools/front_end/ui/ViewportControl.js
index b3f40d42015177bbbe6e1656e481e50084bd295a..dbd36008c8a08dd7baf1be0f7679ec221d521a0e 100644
--- a/third_party/WebKit/Source/devtools/front_end/ui/ViewportControl.js
+++ b/third_party/WebKit/Source/devtools/front_end/ui/ViewportControl.js
@@ -1,33 +1,6 @@
-/*
- * Copyright (C) 2013 Google Inc. All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are
- * met:
- *
- * * Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- * * Redistributions in binary form must reproduce the above
- * copyright notice, this list of conditions and the following disclaimer
- * in the documentation and/or other materials provided with the
- * distribution.
- * * Neither the name of Google Inc. nor the names of its
- * contributors may be used to endorse or promote products derived from
- * this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
/**
* @unrestricted
*/
@@ -38,490 +11,90 @@ UI.ViewportControl = class {
constructor(provider) {
this.element = createElement('div');
this.element.style.overflow = 'auto';
- this._topGapElement = this.element.createChild('div');
- this._topGapElement.style.height = '0px';
- this._topGapElement.style.color = 'transparent';
- this._contentElement = this.element.createChild('div');
- this._bottomGapElement = this.element.createChild('div');
- this._bottomGapElement.style.height = '0px';
- this._bottomGapElement.style.color = 'transparent';
-
- // Text content needed for range intersection checks in _updateSelectionModel.
- // Use Unicode ZERO WIDTH NO-BREAK SPACE, which avoids contributing any height to the element's layout overflow.
- this._topGapElement.textContent = '\uFEFF';
- this._bottomGapElement.textContent = '\uFEFF';
+ this._innerElement = this.element.createChild('div');
+ this._innerElement.style.height = '0px';
+ this._innerElement.style.position = 'relative';
+ this._innerElement.style.overflow = 'hidden';
this._provider = provider;
- this.element.addEventListener('scroll', this._onScroll.bind(this), false);
- this.element.addEventListener('copy', this._onCopy.bind(this), false);
- this.element.addEventListener('dragstart', this._onDragStart.bind(this), false);
-
- this._firstActiveIndex = 0;
- this._lastActiveIndex = -1;
- this._renderedItems = [];
- this._anchorSelection = null;
- this._headSelection = null;
+ this.element.addEventListener('scroll', this._update.bind(this), false);
this._itemCount = 0;
-
- // Listen for any changes to descendants and trigger a refresh. This ensures
- // that items updated asynchronously will not break stick-to-bottom behavior
- // if they change the scroll height.
- this._observer = new MutationObserver(this.refresh.bind(this));
- this._observerConfig = {childList: true, subtree: true};
- }
-
- /**
- * @return {boolean}
- */
- stickToBottom() {
- return this._stickToBottom;
- }
-
- /**
- * @param {boolean} value
- */
- setStickToBottom(value) {
- this._stickToBottom = value;
- if (this._stickToBottom)
- this._observer.observe(this._contentElement, this._observerConfig);
- else
- this._observer.disconnect();
- }
-
- /**
- * @param {!Event} event
- */
- _onCopy(event) {
- var text = this._selectedText();
- if (!text)
- return;
- event.preventDefault();
- event.clipboardData.setData('text/plain', text);
+ this._indexSymbol = Symbol('UI.ViewportControl._indexSymbol');
}
- /**
- * @param {!Event} event
- */
- _onDragStart(event) {
- var text = this._selectedText();
- if (!text)
- return false;
- event.dataTransfer.clearData();
- event.dataTransfer.setData('text/plain', text);
- event.dataTransfer.effectAllowed = 'copy';
- return true;
- }
-
- /**
- * @return {!Element}
- */
- contentElement() {
- return this._contentElement;
- }
-
- invalidate() {
- delete this._cumulativeHeights;
- delete this._cachedProviderElements;
+ refresh() {
this._itemCount = this._provider.itemCount();
- this.refresh();
- }
+ this._innerElement.removeChildren();
- /**
- * @param {number} index
- * @return {?UI.ViewportElement}
- */
- _providerElement(index) {
- if (!this._cachedProviderElements)
- this._cachedProviderElements = new Array(this._itemCount);
- var element = this._cachedProviderElements[index];
- if (!element) {
- element = this._provider.itemElement(index);
- this._cachedProviderElements[index] = element;
- }
- return element;
- }
-
- _rebuildCumulativeHeightsIfNeeded() {
- if (this._cumulativeHeights)
- return;
- if (!this._itemCount)
- return;
- var firstActiveIndex = this._firstActiveIndex;
- var lastActiveIndex = this._lastActiveIndex;
var height = 0;
this._cumulativeHeights = new Int32Array(this._itemCount);
for (var i = 0; i < this._itemCount; ++i) {
- if (firstActiveIndex <= i && i <= lastActiveIndex)
- height += this._renderedItems[i - firstActiveIndex].element().offsetHeight;
- else
- height += this._provider.fastHeight(i);
+ height += this._provider.fastItemHeight(i);
this._cumulativeHeights[i] = height;
}
- }
-
- /**
- * @param {number} index
- * @return {number}
- */
- _cachedItemHeight(index) {
- return index === 0 ? this._cumulativeHeights[0] :
- this._cumulativeHeights[index] - this._cumulativeHeights[index - 1];
- }
-
- /**
- * @param {?Selection} selection
- * @suppressGlobalPropertiesCheck
- */
- _isSelectionBackwards(selection) {
- if (!selection || !selection.rangeCount)
- return false;
- var range = document.createRange();
- range.setStart(selection.anchorNode, selection.anchorOffset);
- range.setEnd(selection.focusNode, selection.focusOffset);
- return range.collapsed;
- }
-
- /**
- * @param {number} itemIndex
- * @param {!Node} node
- * @param {number} offset
- * @return {!{item: number, node: !Node, offset: number}}
- */
- _createSelectionModel(itemIndex, node, offset) {
- return {item: itemIndex, node: node, offset: offset};
- }
-
- /**
- * @param {?Selection} selection
- */
- _updateSelectionModel(selection) {
- var range = selection && selection.rangeCount ? selection.getRangeAt(0) : null;
- if (!range || selection.isCollapsed || !this.element.hasSelection()) {
- this._headSelection = null;
- this._anchorSelection = null;
- return false;
- }
-
- var firstSelected = Number.MAX_VALUE;
- var lastSelected = -1;
+ this._innerElement.style.height = height + 'px';
- var hasVisibleSelection = false;
- for (var i = 0; i < this._renderedItems.length; ++i) {
- if (range.intersectsNode(this._renderedItems[i].element())) {
- var index = i + this._firstActiveIndex;
- firstSelected = Math.min(firstSelected, index);
- lastSelected = Math.max(lastSelected, index);
- hasVisibleSelection = true;
- }
- }
- if (hasVisibleSelection) {
- firstSelected =
- this._createSelectionModel(firstSelected, /** @type {!Node} */ (range.startContainer), range.startOffset);
- lastSelected =
- this._createSelectionModel(lastSelected, /** @type {!Node} */ (range.endContainer), range.endOffset);
- }
- var topOverlap = range.intersectsNode(this._topGapElement) && this._topGapElement._active;
- var bottomOverlap = range.intersectsNode(this._bottomGapElement) && this._bottomGapElement._active;
- if (!topOverlap && !bottomOverlap && !hasVisibleSelection) {
- this._headSelection = null;
- this._anchorSelection = null;
- return false;
- }
-
- if (!this._anchorSelection || !this._headSelection) {
- this._anchorSelection = this._createSelectionModel(0, this.element, 0);
- this._headSelection = this._createSelectionModel(this._itemCount - 1, this.element, this.element.children.length);
- this._selectionIsBackward = false;
- }
-
- var isBackward = this._isSelectionBackwards(selection);
- var startSelection = this._selectionIsBackward ? this._headSelection : this._anchorSelection;
- var endSelection = this._selectionIsBackward ? this._anchorSelection : this._headSelection;
- if (topOverlap && bottomOverlap && hasVisibleSelection) {
- firstSelected = firstSelected.item < startSelection.item ? firstSelected : startSelection;
- lastSelected = lastSelected.item > endSelection.item ? lastSelected : endSelection;
- } else if (!hasVisibleSelection) {
- firstSelected = startSelection;
- lastSelected = endSelection;
- } else if (topOverlap) {
- firstSelected = isBackward ? this._headSelection : this._anchorSelection;
- } else if (bottomOverlap) {
- lastSelected = isBackward ? this._anchorSelection : this._headSelection;
- }
-
- if (isBackward) {
- this._anchorSelection = lastSelected;
- this._headSelection = firstSelected;
- } else {
- this._anchorSelection = firstSelected;
- this._headSelection = lastSelected;
- }
- this._selectionIsBackward = isBackward;
- return true;
+ this._update();
}
- /**
- * @param {?Selection} selection
- */
- _restoreSelection(selection) {
- var anchorElement = null;
- var anchorOffset;
- if (this._firstActiveIndex <= this._anchorSelection.item && this._anchorSelection.item <= this._lastActiveIndex) {
- anchorElement = this._anchorSelection.node;
- anchorOffset = this._anchorSelection.offset;
- } else {
- if (this._anchorSelection.item < this._firstActiveIndex)
- anchorElement = this._topGapElement;
- else if (this._anchorSelection.item > this._lastActiveIndex)
- anchorElement = this._bottomGapElement;
- anchorOffset = this._selectionIsBackward ? 1 : 0;
- }
-
- var headElement = null;
- var headOffset;
- if (this._firstActiveIndex <= this._headSelection.item && this._headSelection.item <= this._lastActiveIndex) {
- headElement = this._headSelection.node;
- headOffset = this._headSelection.offset;
- } else {
- if (this._headSelection.item < this._firstActiveIndex)
- headElement = this._topGapElement;
- else if (this._headSelection.item > this._lastActiveIndex)
- headElement = this._bottomGapElement;
- headOffset = this._selectionIsBackward ? 0 : 1;
- }
-
- selection.setBaseAndExtent(anchorElement, anchorOffset, headElement, headOffset);
- }
-
- refresh() {
- this._observer.disconnect();
- this._innerRefresh();
- if (this._stickToBottom)
- this._observer.observe(this._contentElement, this._observerConfig);
- }
-
- _innerRefresh() {
- if (!this._visibleHeight())
- return; // Do nothing for invisible controls.
-
- if (!this._itemCount) {
- for (var i = 0; i < this._renderedItems.length; ++i)
- this._renderedItems[i].willHide();
- this._renderedItems = [];
- this._contentElement.removeChildren();
- this._topGapElement.style.height = '0px';
- this._bottomGapElement.style.height = '0px';
- this._firstActiveIndex = -1;
- this._lastActiveIndex = -1;
+ _update() {
+ if (!this._cumulativeHeights) {
+ this.refresh();
return;
}
- var selection = this.element.getComponentSelection();
- var shouldRestoreSelection = this._updateSelectionModel(selection);
-
- var visibleFrom = this.element.scrollTop;
var visibleHeight = this._visibleHeight();
- var isInvalidating = !this._cumulativeHeights;
-
- for (var i = 0; i < this._renderedItems.length; ++i) {
- // Tolerate 1-pixel error due to double-to-integer rounding errors.
- if (this._cumulativeHeights &&
- Math.abs(this._cachedItemHeight(this._firstActiveIndex + i) - this._renderedItems[i].element().offsetHeight) >
- 1)
- delete this._cumulativeHeights;
- }
- this._rebuildCumulativeHeightsIfNeeded();
- var oldFirstActiveIndex = this._firstActiveIndex;
- var oldLastActiveIndex = this._lastActiveIndex;
+ var visibleFrom = this.element.scrollTop;
var activeHeight = visibleHeight * 2;
- // When the viewport is scrolled to the bottom, using the cumulative heights estimate is not
- // precise enough to determine next visible indices. This stickToBottom check avoids extra
- // calls to refresh in those cases.
- if (this._stickToBottom) {
- this._firstActiveIndex =
- Math.max(this._itemCount - Math.ceil(activeHeight / this._provider.minimumRowHeight()), 0);
- this._lastActiveIndex = this._itemCount - 1;
- } else {
- this._firstActiveIndex = Math.max(
- Array.prototype.lowerBound.call(
- this._cumulativeHeights, visibleFrom + 1 - (activeHeight - visibleHeight) / 2),
- 0);
- // Proactively render more rows in case some of them will be collapsed without triggering refresh. @see crbug.com/390169
- this._lastActiveIndex = this._firstActiveIndex + Math.ceil(activeHeight / this._provider.minimumRowHeight()) - 1;
- this._lastActiveIndex = Math.min(this._lastActiveIndex, this._itemCount - 1);
- }
-
- var topGapHeight = this._cumulativeHeights[this._firstActiveIndex - 1] || 0;
- var bottomGapHeight =
- this._cumulativeHeights[this._cumulativeHeights.length - 1] - this._cumulativeHeights[this._lastActiveIndex];
-
- /**
- * @this {UI.ViewportControl}
- */
- function prepare() {
- this._topGapElement.style.height = topGapHeight + 'px';
- this._bottomGapElement.style.height = bottomGapHeight + 'px';
- this._topGapElement._active = !!topGapHeight;
- this._bottomGapElement._active = !!bottomGapHeight;
- this._contentElement.style.setProperty('height', '10000000px');
- }
-
- if (isInvalidating)
- this._fullViewportUpdate(prepare.bind(this));
- else
- this._partialViewportUpdate(oldFirstActiveIndex, oldLastActiveIndex, prepare.bind(this));
- this._contentElement.style.removeProperty('height');
- // Should be the last call in the method as it might force layout.
- if (shouldRestoreSelection)
- this._restoreSelection(selection);
- if (this._stickToBottom)
- this.element.scrollTop = 10000000;
- }
+ var firstActiveIndex = Math.max(
+ Array.prototype.lowerBound.call(this._cumulativeHeights, visibleFrom + 1 - (activeHeight - visibleHeight) / 2),
+ 0);
+ var lastActiveIndex = Math.min(
+ Array.prototype.lowerBound.call(
+ this._cumulativeHeights, visibleFrom + visibleHeight + (activeHeight - visibleHeight) / 2),
+ this._itemCount - 1);
- /**
- * @param {function()} prepare
- */
- _fullViewportUpdate(prepare) {
- for (var i = 0; i < this._renderedItems.length; ++i)
- this._renderedItems[i].willHide();
- prepare();
- this._renderedItems = [];
- this._contentElement.removeChildren();
- for (var i = this._firstActiveIndex; i <= this._lastActiveIndex; ++i) {
- var viewportElement = this._providerElement(i);
- this._contentElement.appendChild(viewportElement.element());
- this._renderedItems.push(viewportElement);
- }
- for (var i = 0; i < this._renderedItems.length; ++i)
- this._renderedItems[i].wasShown();
- }
-
- /**
- * @param {number} oldFirstActiveIndex
- * @param {number} oldLastActiveIndex
- * @param {function()} prepare
- */
- _partialViewportUpdate(oldFirstActiveIndex, oldLastActiveIndex, prepare) {
- var willBeHidden = [];
- for (var i = 0; i < this._renderedItems.length; ++i) {
- var index = oldFirstActiveIndex + i;
- if (index < this._firstActiveIndex || this._lastActiveIndex < index)
- willBeHidden.push(this._renderedItems[i]);
+ var children = this._innerElement.children;
+ for (var i = children.length - 1; i >= 0; --i) {
+ var element = children[i];
+ if (element[this._indexSymbol] < firstActiveIndex || element[this._indexSymbol] > lastActiveIndex)
+ element.remove();
}
- for (var i = 0; i < willBeHidden.length; ++i)
- willBeHidden[i].willHide();
- prepare();
- for (var i = 0; i < willBeHidden.length; ++i)
- willBeHidden[i].element().remove();
- this._renderedItems = [];
- var anchor = this._contentElement.firstChild;
- var wasShown = [];
- for (var i = this._firstActiveIndex; i <= this._lastActiveIndex; ++i) {
- var viewportElement = this._providerElement(i);
- var element = viewportElement.element();
- if (element !== anchor) {
- this._contentElement.insertBefore(element, anchor);
- wasShown.push(viewportElement);
- } else {
- anchor = anchor.nextSibling;
- }
- this._renderedItems.push(viewportElement);
- }
- for (var i = 0; i < wasShown.length; ++i)
- wasShown[i].wasShown();
+ for (var i = firstActiveIndex; i <= lastActiveIndex; ++i)
+ this._insertElement(i);
}
/**
- * @return {?string}
- */
- _selectedText() {
- this._updateSelectionModel(this.element.getComponentSelection());
- if (!this._headSelection || !this._anchorSelection)
- return null;
-
- var startSelection = null;
- var endSelection = null;
- if (this._selectionIsBackward) {
- startSelection = this._headSelection;
- endSelection = this._anchorSelection;
- } else {
- startSelection = this._anchorSelection;
- endSelection = this._headSelection;
- }
-
- var textLines = [];
- for (var i = startSelection.item; i <= endSelection.item; ++i)
- textLines.push(this._providerElement(i).element().deepTextContent());
-
- var endSelectionElement = this._providerElement(endSelection.item).element();
- if (endSelection.node && endSelection.node.isSelfOrDescendant(endSelectionElement)) {
- var itemTextOffset = this._textOffsetInNode(endSelectionElement, endSelection.node, endSelection.offset);
- textLines[textLines.length - 1] = textLines.peekLast().substring(0, itemTextOffset);
- }
-
- var startSelectionElement = this._providerElement(startSelection.item).element();
- if (startSelection.node && startSelection.node.isSelfOrDescendant(startSelectionElement)) {
- var itemTextOffset = this._textOffsetInNode(startSelectionElement, startSelection.node, startSelection.offset);
- textLines[0] = textLines[0].substring(itemTextOffset);
- }
-
- return textLines.join('\n');
- }
-
- /**
- * @param {!Element} itemElement
- * @param {!Node} container
- * @param {number} offset
- * @return {number}
+ * @param {number} index
*/
- _textOffsetInNode(itemElement, container, offset) {
- var chars = 0;
- var node = itemElement;
- while ((node = node.traverseNextTextNode()) && !node.isSelfOrDescendant(container))
- chars += node.textContent.length;
- return chars + offset;
- }
+ _insertElement(index) {
+ var element = this._provider.itemElement(index);
+ if (!element || element.parentElement === this._innerElement)
+ return;
- /**
- * @param {!Event} event
- */
- _onScroll(event) {
- this.refresh();
+ element.style.position = 'absolute';
+ element.style.top = (this._cumulativeHeights[index - 1] || 0) + 'px';
+ element.style.left = '0';
+ element.style.right = '0';
+ element[this._indexSymbol] = index;
+ this._innerElement.appendChild(element);
}
/**
* @return {number}
*/
firstVisibleIndex() {
- var firstVisibleIndex =
- Math.max(Array.prototype.lowerBound.call(this._cumulativeHeights, this.element.scrollTop + 1), 0);
- return Math.max(firstVisibleIndex, this._firstActiveIndex);
+ return Math.max(Array.prototype.lowerBound.call(this._cumulativeHeights, this.element.scrollTop + 1), 0);
}
/**
* @return {number}
*/
lastVisibleIndex() {
- var lastVisibleIndex;
- if (this._stickToBottom) {
- lastVisibleIndex = this._itemCount - 1;
- } else {
- lastVisibleIndex =
- this.firstVisibleIndex() + Math.ceil(this._visibleHeight() / this._provider.minimumRowHeight()) - 1;
- }
- return Math.min(lastVisibleIndex, this._lastActiveIndex);
- }
-
- /**
- * @return {?Element}
- */
- renderedElementAt(index) {
- if (index < this._firstActiveIndex)
- return null;
- if (index > this._lastActiveIndex)
- return null;
- return this._renderedItems[index - this._firstActiveIndex].element();
+ return Math.min(
+ Array.prototype.lowerBound.call(this._cumulativeHeights, this.element.scrollTop + this._visibleHeight()),
+ this._itemCount);
}
/**
@@ -545,31 +118,22 @@ UI.ViewportControl = class {
* @param {number} index
*/
forceScrollItemToBeFirst(index) {
- this.setStickToBottom(false);
- this._rebuildCumulativeHeightsIfNeeded();
this.element.scrollTop = index > 0 ? this._cumulativeHeights[index - 1] : 0;
- if (this.element.isScrolledToBottom())
- this.setStickToBottom(true);
- this.refresh();
+ this._update();
}
/**
* @param {number} index
*/
forceScrollItemToBeLast(index) {
- this.setStickToBottom(false);
- this._rebuildCumulativeHeightsIfNeeded();
this.element.scrollTop = this._cumulativeHeights[index] - this._visibleHeight();
- if (this.element.isScrolledToBottom())
- this.setStickToBottom(true);
- this.refresh();
+ this._update();
}
/**
* @return {number}
*/
_visibleHeight() {
- // Use offsetHeight instead of clientHeight to avoid being affected by horizontal scroll.
return this.element.offsetHeight;
}
};
@@ -584,7 +148,7 @@ UI.ViewportControl.Provider.prototype = {
* @param {number} index
* @return {number}
*/
- fastHeight(index) {
+ fastItemHeight(index) {
return 0;
},
@@ -596,65 +160,10 @@ UI.ViewportControl.Provider.prototype = {
},
/**
- * @return {number}
- */
- minimumRowHeight() {
- return 0;
- },
-
- /**
* @param {number} index
- * @return {?UI.ViewportElement}
+ * @return {?Element}
*/
itemElement(index) {
return null;
}
};
-
-/**
- * @interface
- */
-UI.ViewportElement = function() {};
-UI.ViewportElement.prototype = {
- willHide() {},
-
- wasShown() {},
-
- /**
- * @return {!Element}
- */
- element() {},
-};
-
-/**
- * @implements {UI.ViewportElement}
- * @unrestricted
- */
-UI.StaticViewportElement = class {
- /**
- * @param {!Element} element
- */
- constructor(element) {
- this._element = element;
- }
-
- /**
- * @override
- */
- willHide() {
- }
-
- /**
- * @override
- */
- wasShown() {
- }
-
- /**
- * @override
- * @return {!Element}
- */
- element() {
- return this._element;
- }
-};

Powered by Google App Engine
This is Rietveld 408576698