[RPP insights] Fix the auto-scroll to the top of Network Dependency Tree insight

Screenshot: https://screenshot.googleplex.com/49BdxA9iogWgtUf
Bug: 414571447
Change-Id: I473ba79cdbdacf686b8bfde305163f91de644c38
Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/6494824
Commit-Queue: Nancy Li <[email protected]>
Reviewed-by: Connor Clark <[email protected]>
diff --git a/front_end/models/trace/insights/NetworkDependencyTree.ts b/front_end/models/trace/insights/NetworkDependencyTree.ts
index fc8e796..9229249 100644
--- a/front_end/models/trace/insights/NetworkDependencyTree.ts
+++ b/front_end/models/trace/insights/NetworkDependencyTree.ts
@@ -45,6 +45,10 @@
    * the browser must download before it can render the page.
    */
   maxCriticalPathLatency: 'Max critical path latency:',
+  /** Label for a column in a data table; entries will be the network request */
+  columnRequest: 'Request',
+  /** Label for a column in a data table; entries will be the time from main document till current network request. */
+  columnTime: 'Time',
 } as const;
 
 const str_ = i18n.i18n.registerUIStrings('models/trace/insights/NetworkDependencyTree.ts', UIStrings);
diff --git a/front_end/panels/timeline/components/insights/NetworkDependencyTree.ts b/front_end/panels/timeline/components/insights/NetworkDependencyTree.ts
index e293302..81b5e06 100644
--- a/front_end/panels/timeline/components/insights/NetworkDependencyTree.ts
+++ b/front_end/panels/timeline/components/insights/NetworkDependencyTree.ts
@@ -2,6 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import './Table.js';
 import '../../../../ui/components/icon_button/icon_button.js';
 
 import * as i18n from '../../../../core/i18n/i18n.js';
@@ -15,6 +16,7 @@
 import {BaseInsightComponent} from './BaseInsightComponent.js';
 import {eventRef} from './EventRef.js';
 import networkDependencyTreeInsightStyles from './networkDependencyTreeInsight.css.js';
+import type {TableData, TableDataRow} from './Table.js';
 
 const {UIStrings, i18nString} = Trace.Insights.Models.NetworkDependencyTree;
 
@@ -47,59 +49,62 @@
     return overlays;
   }
 
-  #onMouseOver(relatedRequests: Set<Trace.Types.Events.SyntheticNetworkRequest>): void {
-    this.#relatedRequests = relatedRequests;
-    const overlays = this.#createOverlayForChain(this.#relatedRequests);
-    this.toggleTemporaryOverlays(overlays, {
-      // The trace window doesn't need to be updated because the request is being hovered.
-      updateTraceWindow: false,
+  #renderNetworkTreeRow(node: CriticalRequestNode): Lit.LitTemplate {
+    const requestStyles = Lit.Directives.styleMap({
+      display: 'flex',
+      '--override-timeline-link-text-color': node.isLongest ? 'var(--sys-color-error)' : '',
+      color: node.isLongest ? 'var(--sys-color-error)' : '',
+      backgroundColor: this.#relatedRequests?.has(node.request) ? 'var(--sys-color-state-hover-on-subtle)' : '',
     });
-    this.scheduleRender();
-  }
-
-  #onMouseOut(): void {
-    this.#relatedRequests = null;
-    this.toggleTemporaryOverlays(null, {
-      updateTraceWindow: false,
+    const urlStyles = Lit.Directives.styleMap({
+      flex: 'auto',
     });
-    this.scheduleRender();
-  }
 
-  renderTree(nodes: CriticalRequestNode[]): Lit.LitTemplate|null {
-    if (nodes.length === 0) {
-      return null;
-    }
     // clang-format off
     return html`
-      <ul>
-        ${nodes.map(({request, timeFromInitialRequest, children, isLongest, relatedRequests}) => {
-          const hasChildren = children.length > 0;
-
-          const requestClasses = Lit.Directives.classMap({
-            request: true,
-            longest: Boolean(isLongest),
-            highlighted: this.#relatedRequests?.has(request) ?? false,
-          });
-
-          return html`
-            <li>
-              <div class=${requestClasses}
-                   @mouseover=${this.#onMouseOver.bind(this, relatedRequests)}
-                   @mouseout=${this.#onMouseOut.bind(this)}>
-                <span class="url">${eventRef(request)}</span>
-                <span class="chain-time">
-                  ${i18n.TimeUtilities.formatMicroSecondsTime(Trace.Types.Timing.Micro(timeFromInitialRequest))}
-                </span>
-              </div>
-            </li>
-            ${hasChildren ? html`${this.renderTree(children)}` : Lit.nothing}
-          `;
-        })}
-      </ul>`;
+      <div style=${requestStyles}>
+        <span style=${urlStyles}>${eventRef(node.request)}</span>
+        <span>
+          ${i18n.TimeUtilities.formatMicroSecondsTime(Trace.Types.Timing.Micro(node.timeFromInitialRequest))}
+        </span>
+      </div>
+    `;
     // clang-format on
   }
 
-  override renderContent(): Lit.LitTemplate {
+  #mapNetworkDependencyToRow(node: CriticalRequestNode): TableDataRow {
+    return {
+      values: [this.#renderNetworkTreeRow(node)],
+      overlays: this.#createOverlayForChain(node.relatedRequests),
+      subRows: node.children.map(child => this.#mapNetworkDependencyToRow(child)),
+    };
+  }
+
+  #renderNetworkDependencyTree(nodes: CriticalRequestNode[]): Lit.LitTemplate|null {
+    if (nodes.length === 0) {
+      return null;
+    }
+
+    const rows: TableDataRow[] = [{
+      // Add one empty row so the main document request can also has a left border
+      values: [Lit.nothing],
+      subRows: nodes.map(node => this.#mapNetworkDependencyToRow(node))
+    }];
+
+    // clang-format off
+    return html`
+      <devtools-performance-table
+          .data=${{
+            insight: this,
+            headers: [i18nString(UIStrings.columnRequest), i18nString(UIStrings.columnTime)],
+            rows,
+          } as TableData}>
+      </devtools-performance-table>
+    `;
+    // clang-format on
+  }
+
+  #renderNetworkTreeSection(): Lit.LitTemplate {
     if (!this.model) {
       return Lit.nothing;
     }
@@ -122,13 +127,19 @@
           <br>
           <span class='longest'> ${i18n.TimeUtilities.formatMicroSecondsTime((this.model.maxTime))}</span>
         </div>
-
-        <!-- a divider is added here, through |tree-view| element's border-top -->
-        <div class="tree-view">${this.renderTree(this.model.rootNodes)} </div>
+      </div>
+      <div class="insight-section">
+        ${this.#renderNetworkDependencyTree(this.model.rootNodes)}
       </div>
     `;
     // clang-format on
   }
+
+  override renderContent(): Lit.LitTemplate {
+    return html`
+      ${this.#renderNetworkTreeSection()}
+    `;
+  }
 }
 
 function getAllOverlays(nodes: CriticalRequestNode[], overlays: Overlays.Overlays.TimelineOverlay[]): void {
diff --git a/front_end/panels/timeline/components/insights/Table.ts b/front_end/panels/timeline/components/insights/Table.ts
index f835832..e7c51e6 100644
--- a/front_end/panels/timeline/components/insights/Table.ts
+++ b/front_end/panels/timeline/components/insights/Table.ts
@@ -113,7 +113,7 @@
     this.#headers = data.headers;
     this.#rows = data.rows;
     // If this table isn't interactive, don't attach mouse listeners or use CSS :hover.
-    this.#interactive = this.#rows.some(row => row.overlays);
+    this.#interactive = this.#rows.some(row => row.overlays || row.subRows);
     void ComponentHelpers.ScheduledRender.scheduleRender(this, this.#render);
   }
 
diff --git a/front_end/panels/timeline/components/insights/networkDependencyTreeInsight.css b/front_end/panels/timeline/components/insights/networkDependencyTreeInsight.css
index 8029975..b1d7eed 100644
--- a/front_end/panels/timeline/components/insights/networkDependencyTreeInsight.css
+++ b/front_end/panels/timeline/components/insights/networkDependencyTreeInsight.css
@@ -15,45 +15,4 @@
       color: var(--sys-color-error);
     }
   }
-
-  .tree-view {
-    border-top: var(--sys-size-1) solid var(--sys-color-divider);
-    padding-top: var(--sys-size-5);
-    margin-top: var(--sys-size-5);
-
-    ul:first-child{
-      padding-left: 0;
-    }
-
-    ul:not(:first-child){
-      border-left: var(--sys-size-1) solid var(--sys-color-tonal-outline);
-      list-style: none;
-      padding-left: var(--sys-size-5)
-    }
-
-    li {
-      border-left: var(--sys-size-1) solid var(--sys-color-tonal-outline);
-      list-style: none;
-      min-height: var(--sys-size-8);
-      padding: var(--sys-size-2) 0 var(--sys-size-2) var(--sys-size-5);
-
-      .request {
-        display: flex;
-
-        &.longest {
-          --override-timeline-link-text-color: var(--sys-color-error);
-
-          color: var(--sys-color-error);
-        }
-
-        &.highlighted {
-          background-color: var(--sys-color-state-hover-on-subtle);
-        }
-
-        .url {
-          flex: auto;
-        }
-      }
-    }
-  }
 }