UNPKG

chrome-devtools-frontend

Version:
211 lines (189 loc) • 7.92 kB
// 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 Logs from '../../models/logs/logs.js'; import * as Components from '../../ui/legacy/components/utils/utils.js'; import * as UI from '../../ui/legacy/legacy.js'; import * as Lit 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 {html, render, nothing} = Lit; 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; hasStackTrace: boolean; request: SDK.NetworkRequest.NetworkRequest; linkifier: Components.Linkifier.Linkifier; target?: SDK.Target.Target; } export const DEFAULT_VIEW = (input: ViewInput, _output: undefined, target: HTMLElement): void => { const hasInitiatorData = input.initiatorGraph.initiators.size > 1 || input.initiatorGraph.initiated.size > 1 || input.hasStackTrace; if (!hasInitiatorData) { render( html` <div class="empty-view" style="display: flex; justify-content: center; align-items: center; height: 100%; color: var(--sys-color-token-subtle);"> ${i18nString(UIStrings.noInitiator)} </div> `, target); return; } const renderStackTraceSection = (): Lit.TemplateResult => { return html` <li role="treeitem" class="request-initiator-view-section-title" aria-expanded="true"> ${i18nString(UIStrings.requestCallStack)} <ul role="group"> <li role="treeitem"> <devtools-widget .widgetConfig=${widgetConfig(Components.JSPresentationUtils.StackTracePreviewContent, { target: input.target, linkifier: input.linkifier, options: {runtimeStackTrace: input.request.initiator()?.stack, tabStops: true} })}></devtools-widget> </li> </ul> </li> `; }; const renderInitiatorNodes = (initiators: SDK.NetworkRequest.NetworkRequest[], index: number, initiated: Map<SDK.NetworkRequest.NetworkRequest, SDK.NetworkRequest.NetworkRequest>, visited: Set<SDK.NetworkRequest.NetworkRequest>): Lit.TemplateResult => { if (index >= initiators.length) { return html`${nothing}`; } const request = initiators[index]; const isCurrentRequest = (index === initiators.length - 1); return html` <li role="treeitem" ?selected=${isCurrentRequest} aria-expanded="true"> <span style=${isCurrentRequest ? 'font-weight: bold' : ''}>${request.url()}</span> <ul role="group"> ${renderInitiatorNodes(initiators, index + 1, initiated, visited)} ${isCurrentRequest ? renderInitiatedNodes(initiated, request, visited) : nothing} </ul> </li> `; }; const renderInitiatedNodes = (initiated: Map<SDK.NetworkRequest.NetworkRequest, SDK.NetworkRequest.NetworkRequest>, parentRequest: SDK.NetworkRequest.NetworkRequest, visited: Set<SDK.NetworkRequest.NetworkRequest>): Lit.TemplateResult => { const children = []; for (const [request, initiator] of initiated) { if (initiator === parentRequest) { children.push(request); } } if (children.length === 0) { return html`${nothing}`; } return html` ${children.map(child => { const shouldRecurse = !visited.has(child); if (shouldRecurse) { visited.add(child); } return html` <li role="treeitem" aria-expanded="true"> <span>${child.url()}</span> ${shouldRecurse ? html`<ul>${renderInitiatedNodes(initiated, child, visited)}</ul>` : nothing} </li> `; })} `; }; const renderInitiatorChain = (initiatorGraph: Logs.NetworkLog.InitiatorGraph): Lit.TemplateResult => { const initiators = Array.from(initiatorGraph.initiators).reverse(); const visited = new Set<SDK.NetworkRequest.NetworkRequest>(); visited.add(input.request); return html` <li role="treeitem" class="request-initiator-view-section-title" aria-expanded="true"> ${i18nString(UIStrings.requestInitiatorChain)} <ul role="group"> ${renderInitiatorNodes(initiators, 0, initiatorGraph.initiated, visited)} </ul> </li> `; }; render( html` <div class="request-initiator-view-tree" jslog=${VisualLogging.tree('initiator-tree')}> <devtools-tree .template=${ html` <style> ${requestInitiatorViewTreeStyles} </style> <ul role="tree"> ${input.hasStackTrace ? renderStackTraceSection() : Lit.nothing} ${ (input.initiatorGraph.initiators.size > 1 || input.initiatorGraph.initiated.size > 1) ? renderInitiatorChain(input.initiatorGraph) : Lit.nothing} </ul> `}></devtools-tree> </div> `, target); }; type View = typeof DEFAULT_VIEW; export class RequestInitiatorView extends UI.Widget.VBox { private readonly linkifier: Components.Linkifier.Linkifier; 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.linkifier = new Components.Linkifier.Linkifier(); this.request = request; this.#view = view; } static createStackTracePreview( request: SDK.NetworkRequest.NetworkRequest, linkifier: Components.Linkifier.Linkifier, focusableLink?: boolean): Components.JSPresentationUtils.StackTracePreviewContent|null { const initiator = request.initiator(); if (!initiator?.stack) { return null; } const networkManager = SDK.NetworkManager.NetworkManager.forRequest(request); const target = networkManager ? networkManager.target() : undefined; return new Components.JSPresentationUtils.StackTracePreviewContent( undefined, target, linkifier, {runtimeStackTrace: initiator.stack, tabStops: focusableLink}); } override performUpdate(): void { const initiatorGraph = Logs.NetworkLog.NetworkLog.instance().initiatorGraphForRequest(this.request); const hasStackTrace = !!this.request.initiator()?.stack; const networkManager = SDK.NetworkManager.NetworkManager.forRequest(this.request); const target = networkManager ? networkManager.target() : undefined; const viewInput: ViewInput = { initiatorGraph, hasStackTrace, request: this.request, linkifier: this.linkifier, target: target || undefined, }; this.#view(viewInput, undefined, this.contentElement); } override wasShown(): void { super.wasShown(); this.registerRequiredCSS(requestInitiatorViewStyles); this.requestUpdate(); } }