blob: df9147dd3e79e9843f7946cfba45d85b3d110446 [file] [log] [blame]
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
export default class RequestInitiatorView extends UI.VBox {
/**
* @param {!SDK.NetworkRequest} request
*/
constructor(request) {
super();
this.registerRequiredCSS('network/requestInitiatorView.css');
this.element.classList.add('request-initiator-view');
/** @type {!Components.Linkifier} */
this._linkifier = new Components.Linkifier();
this._request = request;
this._emptyWidget = new UI.EmptyWidget(Common.UIString('This request has no initiator data.'));
this._emptyWidget.show(this.element);
this._hasShown = false;
}
/**
* @param {!SDK.NetworkRequest} request
* @param {!Components.Linkifier} linkifier
* @param {boolean=} focusableLink
* @param {function()=} callback
* @return {?{element: !Element, links: !Array<!Element>}}
*/
static createStackTracePreview(request, linkifier, focusableLink, callback) {
const initiator = request.initiator();
if (!initiator || !initiator.stack) {
return null;
}
const networkManager = SDK.NetworkManager.forRequest(request);
const target = networkManager ? networkManager.target() : null;
const stackTrace =
Components.JSPresentationUtils.buildStackTracePreviewContents(target, linkifier, initiator.stack, callback);
if (focusableLink) {
for (const link of stackTrace.links) {
link.tabIndex = 0;
}
}
return stackTrace;
}
/**
* @param {!Element} sectionContent
* @param {string} title
* @param {boolean} expanded
*/
_appendExpandableSection(sectionContent, title, expanded) {
const section = createElementWithClass('div', 'request-initiator-view-section');
const icon = UI.Icon.create('smallicon-triangle-right');
const clickableElement = section.createChild('div', 'request-initiator-view-section-title');
clickableElement.appendChild(icon);
clickableElement.createTextChild(title);
clickableElement.tabIndex = 0;
sectionContent.classList.add('hidden', 'request-initiator-view-section-content');
section.appendChild(sectionContent);
const expand = expanded => {
icon.setIconType(expanded ? 'smallicon-triangle-down' : 'smallicon-triangle-right');
sectionContent.classList.toggle('hidden', !expanded);
};
self.onInvokeElement(clickableElement, event => {
expand(sectionContent.classList.contains('hidden'));
event.consume();
});
expand(expanded);
this.element.appendChild(section);
}
/**
* @param {!SDK.NetworkLog.InitiatorGraph} initiatorGraph
* @return {!UI.TreeOutlineInShadow}
*/
_buildRequestChainTree(initiatorGraph) {
const root = new UI.TreeOutlineInShadow();
const initiators = initiatorGraph.initiators;
let parent = root;
for (const request of Array.from(initiators).reverse()) {
const treeElement = new UI.TreeElement(request.url());
parent.appendChild(treeElement);
if (parent !== root) {
parent.expand();
}
parent = treeElement;
}
// parent should be this._request tree element now
parent.select();
parent.titleElement.style.fontWeight = 'bold';
const initiated = initiatorGraph.initiated;
this._depthFirstSearchTreeBuilder(initiated, /** @type {!UI.TreeElement} */ (parent), this._request);
return root;
}
/**
* @param {!Map<!SDK.NetworkRequest, !SDK.NetworkRequest>} initiated
* @param {!UI.TreeElement} parentElement
* @param {!SDK.NetworkRequest} parentRequest
*/
_depthFirstSearchTreeBuilder(initiated, parentElement, parentRequest) {
const visited = new Set();
// this._request should be already in the tree when build initiator part
visited.add(this._request);
for (const request of initiated.keys()) {
if (initiated.get(request) === parentRequest) {
const treeElement = new UI.TreeElement(request.url());
parentElement.appendChild(treeElement);
parentElement.expand();
// only do dfs when we haven't done one
if (!visited.has(request)) {
visited.add(request);
this._depthFirstSearchTreeBuilder(initiated, treeElement, request);
}
}
}
}
/**
* @override
*/
wasShown() {
if (this._hasShown) {
return;
}
let initiatorDataPresent = false;
const stackTracePreview =
Network.RequestInitiatorView.createStackTracePreview(this._request, this._linkifier, true);
if (stackTracePreview) {
initiatorDataPresent = true;
this._appendExpandableSection(stackTracePreview.element, ls`Request call stack`, true);
}
const initiatorGraph = SDK.networkLog.initiatorGraphForRequest(this._request);
if (initiatorGraph.initiators.size > 1 || initiatorGraph.initiated.size > 1) {
initiatorDataPresent = true;
this._appendExpandableSection(
this._buildRequestChainTree(initiatorGraph).element, ls`Request initiator chain`, true);
}
if (initiatorDataPresent) {
this._emptyWidget.hideWidget();
}
this._hasShown = true;
}
}
/* Legacy exported object */
self.Network = self.Network || {};
/* Legacy exported object */
Network = Network || {};
/**
* @constructor
*/
Network.RequestInitiatorView = RequestInitiatorView;