- Revision
- 220180
- Author
- [email protected]
- Date
- 2017-08-02 21:21:32 -0700 (Wed, 02 Aug 2017)
Log Message
Web Inspector: add TreeElement virtualization for the Recording tab
https://bugs.webkit.org/show_bug.cgi?id=174968
Reviewed by Joseph Pecoraro.
* UserInterface/Views/RecordingNavigationSidebarPanel.js:
(WI.RecordingNavigationSidebarPanel):
* UserInterface/Views/TreeOutline.js:
(WI.TreeOutline):
(WI.TreeOutline.prototype.get virtualized):
(WI.TreeOutline.prototype.registerScrollVirtualizer):
(WI.TreeOutline.prototype.updateVirtualizedElements.walk):
(WI.TreeOutline.prototype.updateVirtualizedElements):
Add spacer elements before and after the TreeOutline element that will size to ensure that
the TreeOutline node still takes up the same amount of space after some of the TreeElements
are removed. Whenever the scroll of the container view changes, recalculate the visible area
and add/remove TreeElements based on whether they would be in that. This is only possible if
every TreeElement has the same vertical height, which is given when setting up the scroll
listener on the container view.
* UserInterface/Views/TreeElement.js:
(WI.TreeElement.prototype.set hidden):
(WI.TreeElement.prototype._attach):
(WI.TreeElement.prototype.collapse):
(WI.TreeElement.prototype.expand):
(WI.TreeElement.prototype.reveal):
If the TreeOutline is being virtualized, don't add each TreeElement's node to the DOM. They
will be added at the end of the frame (via setTimeout) if they are within the visible + padding
area of the TreeOutline.
Modified Paths
Diff
Modified: trunk/Source/WebInspectorUI/ChangeLog (220179 => 220180)
--- trunk/Source/WebInspectorUI/ChangeLog 2017-08-03 04:19:10 UTC (rev 220179)
+++ trunk/Source/WebInspectorUI/ChangeLog 2017-08-03 04:21:32 UTC (rev 220180)
@@ -1,3 +1,36 @@
+2017-08-02 Devin Rousso <[email protected]>
+
+ Web Inspector: add TreeElement virtualization for the Recording tab
+ https://bugs.webkit.org/show_bug.cgi?id=174968
+
+ Reviewed by Joseph Pecoraro.
+
+ * UserInterface/Views/RecordingNavigationSidebarPanel.js:
+ (WI.RecordingNavigationSidebarPanel):
+
+ * UserInterface/Views/TreeOutline.js:
+ (WI.TreeOutline):
+ (WI.TreeOutline.prototype.get virtualized):
+ (WI.TreeOutline.prototype.registerScrollVirtualizer):
+ (WI.TreeOutline.prototype.updateVirtualizedElements.walk):
+ (WI.TreeOutline.prototype.updateVirtualizedElements):
+ Add spacer elements before and after the TreeOutline element that will size to ensure that
+ the TreeOutline node still takes up the same amount of space after some of the TreeElements
+ are removed. Whenever the scroll of the container view changes, recalculate the visible area
+ and add/remove TreeElements based on whether they would be in that. This is only possible if
+ every TreeElement has the same vertical height, which is given when setting up the scroll
+ listener on the container view.
+
+ * UserInterface/Views/TreeElement.js:
+ (WI.TreeElement.prototype.set hidden):
+ (WI.TreeElement.prototype._attach):
+ (WI.TreeElement.prototype.collapse):
+ (WI.TreeElement.prototype.expand):
+ (WI.TreeElement.prototype.reveal):
+ If the TreeOutline is being virtualized, don't add each TreeElement's node to the DOM. They
+ will be added at the end of the frame (via setTimeout) if they are within the visible + padding
+ area of the TreeOutline.
+
2017-08-01 Devin Rousso <[email protected]>
Web Inspector: simplify WebInspector with WI
Modified: trunk/Source/WebInspectorUI/UserInterface/Views/RecordingNavigationSidebarPanel.js (220179 => 220180)
--- trunk/Source/WebInspectorUI/UserInterface/Views/RecordingNavigationSidebarPanel.js 2017-08-03 04:19:10 UTC (rev 220179)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/RecordingNavigationSidebarPanel.js 2017-08-03 04:21:32 UTC (rev 220180)
@@ -30,6 +30,7 @@
super("recording", WI.UIString("Recording"));
this.contentTreeOutline.customIndent = true;
+ this.contentTreeOutline.registerScrollVirtualizer(this.contentView.element, 20);
this.filterBar.placeholder = WI.UIString("Filter Actions");
Modified: trunk/Source/WebInspectorUI/UserInterface/Views/TreeElement.js (220179 => 220180)
--- trunk/Source/WebInspectorUI/UserInterface/Views/TreeElement.js 2017-08-03 04:19:10 UTC (rev 220179)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/TreeElement.js 2017-08-03 04:21:32 UTC (rev 220180)
@@ -165,8 +165,11 @@
if (this._childrenListNode)
this._childrenListNode.hidden = this._hidden;
- if (this.treeOutline)
+ if (this.treeOutline) {
+ this.treeOutline.soon.updateVirtualizedElements(this);
+
this.treeOutline.dispatchEventToListeners(WI.TreeOutline.Event.ElementVisibilityDidChange, {element: this});
+ }
}
get shouldRefreshChildren()
@@ -244,9 +247,16 @@
var nextSibling = null;
if (this.nextSibling && this.nextSibling._listItemNode && this.nextSibling._listItemNode.parentNode === this.parent._childrenListNode)
nextSibling = this.nextSibling._listItemNode;
- this.parent._childrenListNode.insertBefore(this._listItemNode, nextSibling);
- if (this._childrenListNode)
- this.parent._childrenListNode.insertBefore(this._childrenListNode, this._listItemNode.nextSibling);
+
+ if (!this.treeOutline || !this.treeOutline.virtualized) {
+ this.parent._childrenListNode.insertBefore(this._listItemNode, nextSibling);
+ if (this._childrenListNode)
+ this.parent._childrenListNode.insertBefore(this._childrenListNode, this._listItemNode.nextSibling);
+ }
+
+ if (this.treeOutline)
+ this.treeOutline.soon.updateVirtualizedElements();
+
if (this.selected)
this.select();
if (this.expanded)
@@ -334,8 +344,11 @@
if (this.oncollapse)
this.oncollapse(this);
- if (this.treeOutline)
+ if (this.treeOutline) {
+ this.treeOutline.soon.updateVirtualizedElements(this);
+
this.treeOutline.dispatchEventToListeners(WI.TreeOutline.Event.ElementDisclosureDidChanged, {element: this});
+ }
}
collapseRecursively()
@@ -398,8 +411,11 @@
if (this.onexpand)
this.onexpand(this);
- if (this.treeOutline)
+ if (this.treeOutline) {
+ this.treeOutline.soon.updateVirtualizedElements(this);
+
this.treeOutline.dispatchEventToListeners(WI.TreeOutline.Event.ElementDisclosureDidChanged, {element: this});
+ }
}
expandRecursively(maxDepth)
@@ -446,6 +462,11 @@
currentAncestor = currentAncestor.parent;
}
+ // This must be called before onreveal, as some subclasses will scrollIntoViewIfNeeded and
+ // we should update the visible elements before attempting to scroll.
+ if (this.treeOutline)
+ this.treeOutline.updateVirtualizedElements(this);
+
if (this.onreveal)
this.onreveal(this);
}
Modified: trunk/Source/WebInspectorUI/UserInterface/Views/TreeOutline.js (220179 => 220180)
--- trunk/Source/WebInspectorUI/UserInterface/Views/TreeOutline.js 2017-08-03 04:19:10 UTC (rev 220179)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/TreeOutline.js 2017-08-03 04:21:32 UTC (rev 220180)
@@ -54,6 +54,11 @@
this._disclosureButtons = true;
this._customIndent = false;
+ this._virtualizedScrollContainer = null;
+ this._virtualizedTreeItemHeight = NaN;
+ this._virtualizedTopSpacer = null;
+ this._virtualizedBottomSpacer = null;
+
this._childrenListNode.tabIndex = 0;
this._childrenListNode.addEventListener("keydown", this._treeKeyDown.bind(this), true);
@@ -633,6 +638,93 @@
return false;
}
+ get virtualized()
+ {
+ return this._virtualizedScrollContainer && !isNaN(this._virtualizedTreeItemHeight);
+ }
+
+ registerScrollVirtualizer(scrollContainer, treeItemHeight)
+ {
+ console.assert(!isNaN(treeItemHeight));
+
+ this._virtualizedScrollContainer = scrollContainer;
+ this._virtualizedTreeItemHeight = treeItemHeight;
+ this._virtualizedTopSpacer = document.createElement("div");
+ this._virtualizedBottomSpacer = document.createElement("div");
+
+ this._virtualizedScrollContainer.addEventListener("scroll", (event) => {
+ this.updateVirtualizedElements();
+ });
+ }
+
+ updateVirtualizedElements(focusedTreeElement)
+ {
+ if (!this.virtualized)
+ return;
+
+ function walk(parent, callback) {
+ let count = 0;
+ let shouldReturn = false;
+ for (let i = 0; i < parent.children.length; ++i) {
+ if (!parent.children[i].revealed(false))
+ continue;
+
+ shouldReturn = callback({
+ parent,
+ treeElement: parent.children[i],
+ count,
+ });
+ if (shouldReturn)
+ break;
+
+ ++count;
+ if (parent.children[i].expanded) {
+ let result = walk(parent.children[i], callback);
+ count += result.count;
+ if (result.shouldReturn)
+ break;
+ }
+ }
+ return {count, shouldReturn};
+ }
+
+ let numberVisible = Math.ceil(this._virtualizedScrollContainer.offsetHeight / this._virtualizedTreeItemHeight);
+ let extraRows = Math.max(numberVisible * 5, 50);
+ let firstItem = Math.floor(this._virtualizedScrollContainer.scrollTop / this._virtualizedTreeItemHeight) - extraRows;
+ let lastItem = firstItem + numberVisible + (extraRows * 2);
+
+ if (focusedTreeElement && focusedTreeElement.revealed(false)) {
+ let index = walk(this, ({treeElement}) => treeElement === focusedTreeElement).count;
+ if (index < firstItem) {
+ firstItem = index - extraRows;
+ lastItem = index + numberVisible + extraRows;
+ } else if (index > lastItem) {
+ firstItem = index - numberVisible - extraRows;
+ lastItem = index + extraRows;
+ }
+ }
+
+ let totalItems = walk(this, ({parent, treeElement, count}) => {
+ if (count < firstItem || count > lastItem)
+ treeElement.element.remove();
+ else {
+ parent._childrenListNode.appendChild(treeElement.element);
+ if (treeElement._childrenListNode)
+ parent._childrenListNode.appendChild(treeElement._childrenListNode);
+ }
+ return false;
+ }).count;
+
+ this._virtualizedTopSpacer.style.height = (Math.max(firstItem, 0) * this._virtualizedTreeItemHeight) + "px";
+ this.element.parentNode.insertBefore(this._virtualizedTopSpacer, this.element);
+
+ this._virtualizedBottomSpacer.style.height = (Math.max(totalItems - lastItem, 0) * this._virtualizedTreeItemHeight) + "px";
+ this.element.parentNode.insertBefore(this._virtualizedBottomSpacer, this.element.nextElementSibling);
+
+ if (focusedTreeElement)
+ this._virtualizedScrollContainer.scrollTop = (firstItem + extraRows) * this._virtualizedTreeItemHeight;
+ }
+
// Protected
treeElementFromEvent(event)