// Copyright 2019 The Chromium Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. /* eslint-disable @devtools/no-imperative-dom-api */ import * as i18n from '../../core/i18n/i18n.js'; import * as SDK from '../../core/sdk/sdk.js'; import * as Bindings from '../../models/bindings/bindings.js'; import * as Logs from '../../models/logs/logs.js'; import type * as StackTrace from '../../models/stack_trace/stack_trace.js'; import * as Components from '../../ui/legacy/components/utils/utils.js'; import * as UI from '../../ui/legacy/legacy.js'; import {html, type LitTemplate, nothing, render, type TemplateResult} from '../../ui/lit/lit.js'; import * as VisualLogging from '../../ui/visual_logging/visual_logging.js'; import requestInitiatorViewStyles from './requestInitiatorView.css.js'; import requestInitiatorViewTreeStyles from './requestInitiatorViewTree.css.js'; const {widgetConfig} = UI.Widget; const UIStrings = { /** * @description Text in Request Initiator View of the Network panel if the request has no initiator data */ noInitiator: 'No initiator data', /** * @description Title of a section in Request Initiator view of the Network Panel */ requestCallStack: 'Request call stack', /** * @description Title of a section in Request Initiator view of the Network Panel */ requestInitiatorChain: 'Request initiator chain', } as const; const str_ = i18n.i18n.registerUIStrings('panels/network/RequestInitiatorView.ts', UIStrings); const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_); export interface ViewInput { initiatorGraph: Logs.NetworkLog.InitiatorGraph; stackTrace: StackTrace.StackTrace.StackTrace|null; request: SDK.NetworkRequest.NetworkRequest; } export const DEFAULT_VIEW = (input: ViewInput, _output: undefined, target: HTMLElement): void => { const hasInitiatorData = input.initiatorGraph.initiators.size > 1 || input.initiatorGraph.initiated.size > 1 || input.stackTrace; if (!hasInitiatorData) { render( html`
${i18nString(UIStrings.noInitiator)}
`, target); return; } const renderStackTraceSection = (): TemplateResult => { if (!input.stackTrace) { return html`${nothing}`; } return html`
  • ${i18nString(UIStrings.requestCallStack)}
  • `; }; const renderInitiatorNodes = (initiators: SDK.NetworkRequest.NetworkRequest[], index: number, initiated: Map, visited: Set): TemplateResult => { if (index >= initiators.length) { return html`${nothing}`; } const request = initiators[index]; const isCurrentRequest = (index === initiators.length - 1); const hasFurtherInitiatedNodes = index + 1 < initiators.length; const renderedChildren = isCurrentRequest ? renderInitiatedNodes(initiated, request, visited) : nothing; // clang-format off return html`
  • ${request.url()} ${hasFurtherInitiatedNodes || renderedChildren !== nothing ? html`
      ${renderInitiatorNodes(initiators, index + 1, initiated, visited)} ${renderedChildren}
    ` : nothing}
  • `; // clang-format on }; const renderInitiatedNodes = (initiated: Map, parentRequest: SDK.NetworkRequest.NetworkRequest, visited: Set): LitTemplate => { const children = []; for (const [request, initiator] of initiated) { if (initiator === parentRequest) { children.push(request); } } if (children.length === 0) { return nothing; } return html` ${children.map(child => { const shouldRecurse = !visited.has(child); if (shouldRecurse) { visited.add(child); } const renderedChildren = shouldRecurse ? renderInitiatedNodes(initiated, child, visited) : nothing; return html`
  • ${child.url()} ${renderedChildren !== nothing ? html`
      ${renderedChildren}
    ` : nothing}
  • `; })} `; }; const renderInitiatorChain = (initiatorGraph: Logs.NetworkLog.InitiatorGraph): TemplateResult => { const initiators = Array.from(initiatorGraph.initiators).reverse(); const visited = new Set(); visited.add(input.request); const hasInitiatorChain = initiators.length > 0; // clang-format off return html`
  • ${i18nString(UIStrings.requestInitiatorChain)} ${hasInitiatorChain ? html`
      ${renderInitiatorNodes(initiators, 0, initiatorGraph.initiated, visited)}
    ` : nothing}
  • `; // clang-format on }; const hasInitiatorChain = input.initiatorGraph.initiators.size > 1 || input.initiatorGraph.initiated.size > 1; // clang-format off render(html`
    ${requestInitiatorViewTreeStyles} ${input.stackTrace || hasInitiatorChain ? html`
      ${renderStackTraceSection()} ${hasInitiatorChain ? renderInitiatorChain(input.initiatorGraph) : nothing}
    ` : nothing} `}>
    `, target); // clang-format on }; type View = typeof DEFAULT_VIEW; export class RequestInitiatorView extends UI.Widget.VBox { private readonly request: SDK.NetworkRequest.NetworkRequest; #view: View; constructor(request: SDK.NetworkRequest.NetworkRequest, view: View = DEFAULT_VIEW) { super({jslog: `${VisualLogging.pane('initiator').track({resize: true})}`}); this.element.classList.add('request-initiator-view'); this.request = request; this.#view = view; } static async createStackTracePreview( request: SDK.NetworkRequest.NetworkRequest, linkifier: Components.Linkifier.Linkifier, focusableLink?: boolean): Promise<{ preview: Components.JSPresentationUtils.StackTracePreviewContent, stackTrace: StackTrace.StackTrace.StackTrace|null, }|null> { const initiator = request.initiator(); if (!initiator?.stack) { return null; } const targetManager = SDK.TargetManager.TargetManager.instance(); const networkManager = SDK.NetworkManager.NetworkManager.forRequest(request); const target = networkManager?.target() ?? targetManager.primaryPageTarget() ?? targetManager.rootTarget(); let stackTrace: StackTrace.StackTrace.StackTrace|null = null; const preview = new Components.JSPresentationUtils.StackTracePreviewContent(); preview.options = {tabStops: focusableLink}; if (target) { stackTrace = await Bindings.DebuggerWorkspaceBinding.DebuggerWorkspaceBinding.instance() .createStackTraceFromProtocolRuntime(initiator.stack, target); preview.stackTrace = stackTrace; } return {preview, stackTrace}; } override async performUpdate(): Promise { const initiatorGraph = Logs.NetworkLog.NetworkLog.instance().initiatorGraphForRequest(this.request); const targetManager = SDK.TargetManager.TargetManager.instance(); const networkManager = SDK.NetworkManager.NetworkManager.forRequest(this.request); const target = networkManager?.target() ?? targetManager.primaryPageTarget() ?? targetManager.rootTarget(); const rawStack = this.request.initiator()?.stack; let stackTrace: StackTrace.StackTrace.StackTrace|null = null; if (rawStack && target) { stackTrace = await Bindings.DebuggerWorkspaceBinding.DebuggerWorkspaceBinding.instance() .createStackTraceFromProtocolRuntime(rawStack, target); } const viewInput: ViewInput = { initiatorGraph, stackTrace, request: this.request, }; this.#view(viewInput, undefined, this.contentElement); } override wasShown(): void { super.wasShown(); this.registerRequiredCSS(requestInitiatorViewStyles); this.requestUpdate(); } }