DevTools: Draw collapsed groups overview with WebGL.

BUG=874116

Change-Id: I8384cebe03348556df733ee8098500eab6352188
Reviewed-on: https://chromium-review.googlesource.com/1180515
Commit-Queue: Alexei Filippov <[email protected]>
Reviewed-by: Aleksey Kozyatinskiy <[email protected]>
Cr-Original-Commit-Position: refs/heads/master@{#584299}
Cr-Mirrored-From: https://chromium.googlesource.com/chromium/src
Cr-Mirrored-Commit: ed15175aebbe896c0cf82e387a8b7dc9756eaba7
diff --git a/front_end/perf_ui/FlameChart.js b/front_end/perf_ui/FlameChart.js
index c54f989..86f2a16 100644
--- a/front_end/perf_ui/FlameChart.js
+++ b/front_end/perf_ui/FlameChart.js
@@ -763,7 +763,7 @@
       this._drawGL();
     } else {
       context.save();
-      this._forEachGroup((offset, index, group, isFirst, groupHeight) => {
+      this._forEachGroupInViewport((offset, index, group, isFirst, groupHeight) => {
         if (index === this._selectedGroup) {
           context.fillStyle = this._selectedGroupBackroundColor;
           context.fillRect(0, offset, width, groupHeight - group.style.padding);
@@ -957,9 +957,23 @@
     /** @type {!Array<number>} */
     const colors = [];
 
+    const collapsedOverviewLevels = new Array(this._visibleLevels.length);
+    const groups = this._rawTimelineData.groups || [];
+    this._forEachGroup((offset, index, group) => {
+      if (group.style.useFirstLineForOverview || !this._isGroupCollapsible(index) || group.expanded)
+        return;
+      let nextGroup = index + 1;
+      while (nextGroup < groups.length && groups[nextGroup].style.nestingLevel > group.style.nestingLevel)
+        ++nextGroup;
+      const endLevel = nextGroup < groups.length ? groups[nextGroup].startLevel : this._dataProvider.maxStackDepth();
+      for (let i = group.startLevel; i < endLevel; ++i)
+        collapsedOverviewLevels[i] = offset;
+    });
+
     for (let i = 0; i < entryTotalTimes.length; ++i) {
       const level = entryLevels[i];
-      if (!this._visibleLevels[level])
+      const collapsedGroupOffset = collapsedOverviewLevels[level];
+      if (!this._visibleLevels[level] && !collapsedGroupOffset)
         continue;
       const color = this._entryColorsCache[i];
       if (!color)
@@ -980,7 +994,7 @@
       const vpos = vertex * 2;
       const x0 = entryStartTimes[i] - this._minimumBoundary;
       const x1 = x0 + entryTotalTimes[i];
-      const y0 = this._levelToOffset(level);
+      const y0 = collapsedGroupOffset || this._levelToOffset(level);
       const y1 = y0 + this._levelHeight(level) - 1;
       vertexArray[vpos + 0] = x0;
       vertexArray[vpos + 1] = y0;
@@ -1079,7 +1093,7 @@
     context.font = defaultFont;
 
     context.fillStyle = UI.themeSupport.patchColorText('#fff', colorUsage.Background);
-    this._forEachGroup((offset, index, group) => {
+    this._forEachGroupInViewport((offset, index, group) => {
       const paddingHeight = group.style.padding;
       if (paddingHeight < 5)
         return;
@@ -1090,7 +1104,7 @@
 
     context.strokeStyle = UI.themeSupport.patchColorText('#eee', colorUsage.Background);
     context.beginPath();
-    this._forEachGroup((offset, index, group, isFirst) => {
+    this._forEachGroupInViewport((offset, index, group, isFirst) => {
       if (isFirst || group.style.padding < 4)
         return;
       hLine(offset - 2.5);
@@ -1098,7 +1112,7 @@
     hLine(lastGroupOffset + 1.5);
     context.stroke();
 
-    this._forEachGroup((offset, index, group) => {
+    this._forEachGroupInViewport((offset, index, group) => {
       if (group.style.useFirstLineForOverview)
         return;
       if (!this._isGroupCollapsible(index) || group.expanded) {
@@ -1108,6 +1122,8 @@
         }
         return;
       }
+      if (this._useWebGL)
+        return;
       let nextGroup = index + 1;
       while (nextGroup < groups.length && groups[nextGroup].style.nestingLevel > group.style.nestingLevel)
         nextGroup++;
@@ -1116,7 +1132,7 @@
     });
 
     context.save();
-    this._forEachGroup((offset, index, group) => {
+    this._forEachGroupInViewport((offset, index, group) => {
       context.font = group.style.font;
       if (this._isGroupCollapsible(index) && !group.expanded || group.style.shareHeaderLine) {
         const width = this._labelWidthForGroup(context, group) + 2;
@@ -1138,7 +1154,7 @@
 
     context.fillStyle = UI.themeSupport.patchColorText('#6e6e6e', colorUsage.Foreground);
     context.beginPath();
-    this._forEachGroup((offset, index, group) => {
+    this._forEachGroupInViewport((offset, index, group) => {
       if (this._isGroupCollapsible(index)) {
         drawExpansionArrow.call(
             this, this._expansionArrowIndent * (group.style.nestingLevel + 1),
@@ -1151,7 +1167,7 @@
     context.beginPath();
     context.stroke();
 
-    this._forEachGroup((offset, index, group, isFirst, groupHeight) => {
+    this._forEachGroupInViewport((offset, index, group, isFirst, groupHeight) => {
       if (index === this._selectedGroup) {
         const lineWidth = 2;
         const bracketLength = 10;
@@ -1195,7 +1211,6 @@
    * @param {function(number, number, !PerfUI.FlameChart.Group, boolean, number)} callback
    */
   _forEachGroup(callback) {
-    const top = this._chartViewport.scrollOffset();
     const groups = this._rawTimelineData.groups || [];
     if (!groups.length)
       return;
@@ -1205,8 +1220,6 @@
     for (let i = 0; i < groups.length; ++i) {
       const groupTop = groupOffsets[i];
       const group = groups[i];
-      if (groupTop - group.style.padding > top + this._offsetHeight)
-        break;
       let firstGroup = true;
       while (groupStack.peekLast().nestingLevel >= group.style.nestingLevel) {
         groupStack.pop();
@@ -1216,13 +1229,27 @@
       const thisGroupVisible = parentGroupVisible && (!this._isGroupCollapsible(i) || group.expanded);
       groupStack.push({nestingLevel: group.style.nestingLevel, visible: thisGroupVisible});
       const nextOffset = i === groups.length - 1 ? groupOffsets[i + 1] + group.style.padding : groupOffsets[i + 1];
-      if (!parentGroupVisible || nextOffset < top)
+      if (!parentGroupVisible)
         continue;
       callback(groupTop, i, group, firstGroup, nextOffset - groupTop);
     }
   }
 
   /**
+   * @param {function(number, number, !PerfUI.FlameChart.Group, boolean, number)} callback
+   */
+  _forEachGroupInViewport(callback) {
+    const top = this._chartViewport.scrollOffset();
+    this._forEachGroup((groupTop, index, group, firstGroup, height) => {
+      if (groupTop - group.style.padding > top + this._offsetHeight)
+        return;
+      if (groupTop + height < top)
+        return;
+      callback(groupTop, index, group, firstGroup, height);
+    });
+  }
+
+  /**
    * @param {!CanvasRenderingContext2D} context
    * @param {!PerfUI.FlameChart.Group} group
    * @return {number}