chrome-devtools-frontend
Version:
Chrome DevTools UI
1,490 lines (1,351 loc) • 87.9 kB
JavaScript
/*
* Copyright (C) 2007, 2008 Apple Inc. All rights reserved.
* Copyright (C) 2008, 2009 Anthony Ricaud <rik@webkit.org>
* Copyright (C) 2011 Google Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
* its contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
import * as Bindings from '../bindings/bindings.js';
import * as BrowserSDK from '../browser_sdk/browser_sdk.js';
import * as Common from '../common/common.js';
import * as Components from '../components/components.js';
import * as DataGrid from '../data_grid/data_grid.js';
import * as HARImporter from '../har_importer/har_importer.js';
import * as Host from '../host/host.js';
import * as i18n from '../i18n/i18n.js';
import * as PerfUI from '../perf_ui/perf_ui.js';
import * as Platform from '../platform/platform.js';
import * as SDK from '../sdk/sdk.js';
import * as TextUtils from '../text_utils/text_utils.js';
import * as ThemeSupport from '../theme_support/theme_support.js';
import * as UI from '../ui/ui.js';
import {HARWriter} from './HARWriter.js';
import {Events, NetworkGroupNode, NetworkLogViewInterface, NetworkNode, NetworkRequestNode} from './NetworkDataGridNode.js'; // eslint-disable-line no-unused-vars
import {NetworkFrameGrouper} from './NetworkFrameGrouper.js';
import {NetworkLogViewColumns} from './NetworkLogViewColumns.js';
import {FilterOptions} from './NetworkPanel.js'; // eslint-disable-line no-unused-vars
import {NetworkTimeBoundary, NetworkTimeCalculator, NetworkTransferDurationCalculator, NetworkTransferTimeCalculator,} from './NetworkTimeCalculator.js'; // eslint-disable-line no-unused-vars
export const UIStrings = {
/**
*@description Text in Network Log View of the Network panel
*/
hideDataUrls: 'Hide data URLs',
/**
*@description Data urlfilter ui element title in Network Log View of the Network panel
*/
hidesDataAndBlobUrls: 'Hides data: and blob: URLs',
/**
*@description Aria accessible name in Network Log View of the Network panel
*/
resourceTypesToInclude: 'Resource types to include',
/**
*@description Label for a filter in the Network panel
*/
hasBlockedCookies: 'Has blocked cookies',
/**
*@description Tooltip for a checkbox in the Network panel. The response to a network request may include a
* cookie (https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies). Such response cookies can
* be malformed or otherwise invalid and the browser may choose to ignore or not accept invalid cookies.
*/
onlyShowRequestsWithBlocked: 'Only show requests with blocked response cookies',
/**
*@description Label for a filter in the Network panel
*/
blockedRequests: 'Blocked Requests',
/**
*@description Tooltip for a filter in the Network panel
*/
onlyShowBlockedRequests: 'Only show blocked requests',
/**
*@description Text that appears when user drag and drop something (for example, a file) in Network Log View of the Network panel
*/
dropHarFilesHere: 'Drop HAR files here',
/**
*@description Recording text text content in Network Log View of the Network panel
*/
recordingNetworkActivity: 'Recording network activity…',
/**
*@description Text in Network Log View of the Network panel
*@example {Ctrl + R} PH1
*/
performARequestOrHitSToRecordThe: 'Perform a request or hit {PH1} to record the reload.',
/**
*@description Shown in the Network Log View of the Network panel when the user has not yet
* recorded any network activity. This is an instruction to the user to start recording in order to
* show network activity in the current UI.
*@example {Ctrl + E} PH1
*/
recordSToDisplayNetworkActivity: 'Record ({PH1}) to display network activity.',
/**
*@description Text that is usually a hyperlink to more documentation
*/
learnMore: 'Learn more',
/**
*@description Text to announce to screen readers that network data is available.
*/
networkDataAvailable: 'Network Data Available',
/**
*@description Text in Network Log View of the Network panel
*@example {3} PH1
*@example {5} PH2
*/
sSRequests: '{PH1} / {PH2} requests',
/**
*@description Text in Network Log View of the Network panel
*@example {5} PH1
*@example {10} PH2
*/
sSTransferred: '{PH1} / {PH2} transferred',
/**
*@description Text in Network Log View of the Network panel
*@example {10} PH1
*@example {15} PH2
*/
sBSBTransferredOverNetwork: '{PH1} B / {PH2} B transferred over network',
/**
*@description Text in Network Log View of the Network panel
*@example {40MB} PH1
*@example {50MB} PH2
*/
sSResources: '{PH1} / {PH2} resources',
/**
*@description Text in Network Log View of the Network panel
*@example {40} PH1
*@example {50} PH2
*/
sBSBResourcesLoadedByThePage: '{PH1} B / {PH2} B resources loaded by the page',
/**
*@description Text in Network Log View of the Network panel
*@example {6} PH1
*/
sRequests: '{PH1} requests',
/**
*@description Text in Network Log View of the Network panel
*@example {4 B} PH1
*/
sTransferred: '{PH1} transferred',
/**
*@description Text in Network Log View of the Network panel
*@example {4} PH1
*/
sBTransferredOverNetwork: '{PH1} B transferred over network',
/**
*@description Text in Network Log View of the Network panel
*@example {4} PH1
*/
sResources: '{PH1} resources',
/**
*@description Text in Network Log View of the Network panel
*@example {10} PH1
*/
sBResourcesLoadedByThePage: '{PH1} B resources loaded by the page',
/**
*@description Text in Network Log View of the Network panel
*@example {120ms} PH1
*/
finishS: 'Finish: {PH1}',
/**
*@description Text in Network Log View of the Network panel
*@example {3000ms} PH1
*/
domcontentloadedS: 'DOMContentLoaded: {PH1}',
/**
*@description Text in Network Log View of the Network panel
*@example {40ms} PH1
*/
loadS: 'Load: {PH1}',
/**
*@description Text for copying
*/
copy: 'Copy',
/**
*@description Text in Network Log View of the Network panel
*/
copyRequestHeaders: 'Copy request headers',
/**
*@description Text in Network Log View of the Network panel
*/
copyResponseHeaders: 'Copy response headers',
/**
*@description Text in Network Log View of the Network panel
*/
copyResponse: 'Copy response',
/**
*@description Text in Network Log View of the Network panel
*/
copyStacktrace: 'Copy stacktrace',
/**
*@description Text in Network Log View of the Network panel
*/
copyAsPowershell: 'Copy as PowerShell',
/**
*@description Text in Network Log View of the Network panel
*/
copyAsFetch: 'Copy as fetch',
/**
*@description Text in Network Log View of the Network panel. An action that copies a command to
* the developer's clipboard. The command allows the developer to replay this specific network
* request in Node.js, a desktop application/framework. 'Node.js fetch' is a noun phrase for the
* type of request that will be copied.
*/
copyAsNodejsFetch: 'Copy as Node.js fetch',
/**
*@description Text in Network Log View of the Network panel. An action that copies a command to
*the clipboard. It will copy the command in the format compatible with cURL (a program, not
*translatable).
*/
copyAsCurlCmd: 'Copy as cURL (cmd)',
/**
*@description Text in Network Log View of the Network panel. An action that copies a command to
*the clipboard. It will copy the command in the format compatible with a Bash script.
*/
copyAsCurlBash: 'Copy as cURL (bash)',
/**
*@description Text in Network Log View of the Network panel. An action that copies a command to
*the clipboard. It will copy the command in the format compatible with a PowerShell script.
*/
copyAllAsPowershell: 'Copy all as PowerShell',
/**
*@description Text in Network Log View of the Network panel. An action that copies a command to
*the clipboard. It will copy the command in the format compatible with a 'fetch' command (fetch
*should not be translated).
*/
copyAllAsFetch: 'Copy all as fetch',
/**
*@description Text in Network Log View of the Network panel. An action that copies a command to
*the clipboard. It will copy the command in the format compatible with a Node.js 'fetch' command
*(fetch and Node.js should not be translated).
*/
copyAllAsNodejsFetch: 'Copy all as Node.js fetch',
/**
*@description Text in Network Log View of the Network panel. An action that copies a command to
*the clipboard. It will copy the command in the format compatible with cURL (a program, not
*translatable).
*/
copyAllAsCurlCmd: 'Copy all as cURL (cmd)',
/**
*@description Text in Network Log View of the Network panel. An action that copies a command to
*the clipboard. It will copy the command in the format compatible with a Bash script.
*/
copyAllAsCurlBash: 'Copy all as cURL (bash)',
/**
*@description Text in Network Log View of the Network panel. An action that copies a command to
*the clipboard. It will copy the command in the format compatible with cURL (a program, not
*translatable).
*/
copyAsCurl: 'Copy as cURL',
/**
*@description Text in Network Log View of the Network panel. An action that copies a command to
*the clipboard. It will copy the command in the format compatible with cURL (a program, not
*translatable).
*/
copyAllAsCurl: 'Copy all as cURL',
/**
*@description Text in Network Log View of the Network panel. An action that copies data to the
*clipboard. It will copy the data in the HAR (not translatable) format.
*/
copyAllAsHar: 'Copy all as HAR',
/**
*@description A context menu item in the Network Log View of the Network panel
*/
saveAllAsHarWithContent: 'Save all as HAR with content',
/**
*@description A context menu item in the Network Log View of the Network panel
*/
clearBrowserCache: 'Clear browser cache',
/**
*@description A context menu item in the Network Log View of the Network panel
*/
clearBrowserCookies: 'Clear browser cookies',
/**
*@description A context menu item in the Network Log View of the Network panel
*/
blockRequestUrl: 'Block request URL',
/**
*@description A context menu item in the Network Log View of the Network panel
*@example {example.com} PH1
*/
unblockS: 'Unblock {PH1}',
/**
*@description A context menu item in the Network Log View of the Network panel
*/
blockRequestDomain: 'Block request domain',
/**
*@description Text to replay an XHR request
*/
replayXhr: 'Replay XHR',
/**
*@description Text in Network Log View of the Network panel
*/
areYouSureYouWantToClearBrowser: 'Are you sure you want to clear browser cache?',
/**
*@description Text in Network Log View of the Network panel
*/
areYouSureYouWantToClearBrowserCookies: 'Are you sure you want to clear browser cookies?',
};
const str_ = i18n.i18n.registerUIStrings('network/NetworkLogView.js', UIStrings);
const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
/**
* @implements {SDK.SDKModel.SDKModelObserver<!SDK.NetworkManager.NetworkManager>}
* @implements {NetworkLogViewInterface}
*/
export class NetworkLogView extends UI.Widget.VBox {
/**
* @param {!UI.FilterBar.FilterBar} filterBar
* @param {!Element} progressBarContainer
* @param {!Common.Settings.Setting<number>} networkLogLargeRowsSetting
*/
constructor(filterBar, progressBarContainer, networkLogLargeRowsSetting) {
super();
this.setMinimumSize(50, 64);
this.registerRequiredCSS('network/networkLogView.css', {enableLegacyPatching: true});
this.element.id = 'network-container';
this.element.classList.add('no-node-selected');
this._networkHideDataURLSetting = Common.Settings.Settings.instance().createSetting('networkHideDataURL', false);
this._networkShowIssuesOnlySetting =
Common.Settings.Settings.instance().createSetting('networkShowIssuesOnly', false);
this._networkOnlyBlockedRequestsSetting =
Common.Settings.Settings.instance().createSetting('networkOnlyBlockedRequests', false);
this._networkResourceTypeFiltersSetting =
Common.Settings.Settings.instance().createSetting('networkResourceTypeFilters', {});
this._rawRowHeight = 0;
this._progressBarContainer = progressBarContainer;
this._networkLogLargeRowsSetting = networkLogLargeRowsSetting;
this._networkLogLargeRowsSetting.addChangeListener(updateRowHeight.bind(this), this);
/**
* @this {NetworkLogView}
*/
function updateRowHeight() {
this._rawRowHeight = Boolean(this._networkLogLargeRowsSetting.get()) ? 41 : 21;
this._rowHeight = this._computeRowHeight();
}
this._rawRowHeight = 0;
this._rowHeight = 0;
updateRowHeight.call(this);
/** @type {!NetworkTransferTimeCalculator} */
this._timeCalculator = new NetworkTransferTimeCalculator();
/** @type {!NetworkTransferDurationCalculator} */
this._durationCalculator = new NetworkTransferDurationCalculator();
this._calculator = this._timeCalculator;
this._columns =
new NetworkLogViewColumns(this, this._timeCalculator, this._durationCalculator, networkLogLargeRowsSetting);
this._columns.show(this.element);
/** @type {!Set<!SDK.NetworkRequest.NetworkRequest>} */
this._staleRequests = new Set();
/** @type {number} */
this._mainRequestLoadTime = -1;
/** @type {number} */
this._mainRequestDOMContentLoadedTime = -1;
/** @type {*} */
this._highlightedSubstringChanges = [];
/** @type {!Array.<!Filter>} */
this._filters = [];
/** @type {?Filter} */
this._timeFilter = null;
/** @type {?NetworkNode} */
this._hoveredNode = null;
/** @type {?Element} */
this._recordingHint = null;
/** @type {?number} */
this._refreshRequestId = null;
/** @type {?NetworkRequestNode} */
this._highlightedNode = null;
this._linkifier = new Components.Linkifier.Linkifier();
this._recording = false;
this._needsRefresh = false;
this._headerHeight = 0;
/** @type {!Map<string, !GroupLookupInterface>} */
this._groupLookups = new Map();
this._groupLookups.set('Frame', new NetworkFrameGrouper(this));
/** @type {?GroupLookupInterface} */
this._activeGroupLookup = null;
this._textFilterUI = new UI.FilterBar.TextFilterUI();
this._textFilterUI.addEventListener(UI.FilterBar.FilterUI.Events.FilterChanged, this._filterChanged, this);
filterBar.addFilter(this._textFilterUI);
this._dataURLFilterUI = new UI.FilterBar.CheckboxFilterUI(
'hide-data-url', i18nString(UIStrings.hideDataUrls), true, this._networkHideDataURLSetting);
this._dataURLFilterUI.addEventListener(
UI.FilterBar.FilterUI.Events.FilterChanged, this._filterChanged.bind(this), this);
UI.Tooltip.Tooltip.install(this._dataURLFilterUI.element(), i18nString(UIStrings.hidesDataAndBlobUrls));
filterBar.addFilter(this._dataURLFilterUI);
const filterItems =
Object.values(Common.ResourceType.resourceCategories)
.map(category => ({name: category.title, label: () => category.shortTitle, title: category.title}));
this._resourceCategoryFilterUI =
new UI.FilterBar.NamedBitSetFilterUI(filterItems, this._networkResourceTypeFiltersSetting);
UI.ARIAUtils.setAccessibleName(
this._resourceCategoryFilterUI.element(), i18nString(UIStrings.resourceTypesToInclude));
this._resourceCategoryFilterUI.addEventListener(
UI.FilterBar.FilterUI.Events.FilterChanged, this._filterChanged.bind(this), this);
filterBar.addFilter(this._resourceCategoryFilterUI);
this._onlyIssuesFilterUI = new UI.FilterBar.CheckboxFilterUI(
'only-show-issues', i18nString(UIStrings.hasBlockedCookies), true, this._networkShowIssuesOnlySetting);
this._onlyIssuesFilterUI.addEventListener(
UI.FilterBar.FilterUI.Events.FilterChanged, this._filterChanged.bind(this), this);
UI.Tooltip.Tooltip.install(this._onlyIssuesFilterUI.element(), i18nString(UIStrings.onlyShowRequestsWithBlocked));
filterBar.addFilter(this._onlyIssuesFilterUI);
this._onlyBlockedRequestsUI = new UI.FilterBar.CheckboxFilterUI(
'only-show-blocked-requests', i18nString(UIStrings.blockedRequests), true,
this._networkOnlyBlockedRequestsSetting);
this._onlyBlockedRequestsUI.addEventListener(
UI.FilterBar.FilterUI.Events.FilterChanged, this._filterChanged.bind(this), this);
UI.Tooltip.Tooltip.install(this._onlyBlockedRequestsUI.element(), i18nString(UIStrings.onlyShowBlockedRequests));
filterBar.addFilter(this._onlyBlockedRequestsUI);
this._filterParser = new TextUtils.TextUtils.FilterParser(_searchKeys);
this._suggestionBuilder =
new UI.FilterSuggestionBuilder.FilterSuggestionBuilder(_searchKeys, NetworkLogView._sortSearchValues);
this._resetSuggestionBuilder();
this._dataGrid = this._columns.dataGrid();
this._setupDataGrid();
this._columns.sortByCurrentColumn();
filterBar.filterButton().addEventListener(
UI.Toolbar.ToolbarButton.Events.Click,
this._dataGrid.scheduleUpdate.bind(this._dataGrid, true /* isFromUser */));
this._summaryToolbar = new UI.Toolbar.Toolbar('network-summary-bar', this.element);
new UI.DropTarget.DropTarget(
this.element, [UI.DropTarget.Type.File], i18nString(UIStrings.dropHarFilesHere), this._handleDrop.bind(this));
Common.Settings.Settings.instance()
.moduleSetting('networkColorCodeResourceTypes')
.addChangeListener(this._invalidateAllItems.bind(this, false), this);
SDK.SDKModel.TargetManager.instance().observeModels(SDK.NetworkManager.NetworkManager, this);
SDK.NetworkLog.NetworkLog.instance().addEventListener(
SDK.NetworkLog.Events.RequestAdded, this._onRequestUpdated, this);
SDK.NetworkLog.NetworkLog.instance().addEventListener(
SDK.NetworkLog.Events.RequestUpdated, this._onRequestUpdated, this);
SDK.NetworkLog.NetworkLog.instance().addEventListener(SDK.NetworkLog.Events.Reset, this._reset, this);
this._updateGroupByFrame();
Common.Settings.Settings.instance()
.moduleSetting('network.group-by-frame')
.addChangeListener(() => this._updateGroupByFrame());
this._filterBar = filterBar;
this._textFilterSetting = Common.Settings.Settings.instance().createSetting('networkTextFilter', '');
if (this._textFilterSetting.get()) {
this._textFilterUI.setValue(this._textFilterSetting.get());
}
}
_updateGroupByFrame() {
const value = Common.Settings.Settings.instance().moduleSetting('network.group-by-frame').get();
this._setGrouping(value ? 'Frame' : null);
}
/**
* @param {string} key
* @param {!Array<string>} values
*/
static _sortSearchValues(key, values) {
if (key === FilterType.Priority) {
values.sort((a, b) => {
const aPriority =
/** @type {!Protocol.Network.ResourcePriority} */ (PerfUI.NetworkPriorities.uiLabelToNetworkPriority(a));
const bPriority =
/** @type {!Protocol.Network.ResourcePriority} */ (PerfUI.NetworkPriorities.uiLabelToNetworkPriority(b));
return PerfUI.NetworkPriorities.networkPriorityWeight(aPriority) -
PerfUI.NetworkPriorities.networkPriorityWeight(bPriority);
});
} else {
values.sort();
}
}
/**
* @param {!Filter} filter
* @param {!SDK.NetworkRequest.NetworkRequest} request
* @return {boolean}
*/
static _negativeFilter(filter, request) {
return !filter(request);
}
/**
* @param {?RegExp} regex
* @param {!SDK.NetworkRequest.NetworkRequest} request
* @return {boolean}
*/
static _requestPathFilter(regex, request) {
if (!regex) {
return false;
}
return regex.test(request.path() + '/' + request.name());
}
/**
* @param {string} domain
* @return {!Array.<string>}
*/
static _subdomains(domain) {
const result = [domain];
let indexOfPeriod = domain.indexOf('.');
while (indexOfPeriod !== -1) {
result.push('*' + domain.substring(indexOfPeriod));
indexOfPeriod = domain.indexOf('.', indexOfPeriod + 1);
}
return result;
}
/**
* @param {string} value
* @return {!Filter}
*/
static _createRequestDomainFilter(value) {
const escapedPattern = value.split('*').map(Platform.StringUtilities.escapeForRegExp).join('.*');
return NetworkLogView._requestDomainFilter.bind(null, new RegExp('^' + escapedPattern + '$', 'i'));
}
/**
* @param {!RegExp} regex
* @param {!SDK.NetworkRequest.NetworkRequest} request
* @return {boolean}
*/
static _requestDomainFilter(regex, request) {
return regex.test(request.domain);
}
/**
* @param {!SDK.NetworkRequest.NetworkRequest} request
* @return {boolean}
*/
static _runningRequestFilter(request) {
return !request.finished;
}
/**
* @param {!SDK.NetworkRequest.NetworkRequest} request
* @return {boolean}
*/
static _fromCacheRequestFilter(request) {
return request.cached();
}
/**
* @param {!SDK.NetworkRequest.NetworkRequest} request
* @return {boolean}
*/
static _interceptedByServiceWorkerFilter(request) {
return request.fetchedViaServiceWorker;
}
/**
* @param {!SDK.NetworkRequest.NetworkRequest} request
* @return {boolean}
*/
static _initiatedByServiceWorkerFilter(request) {
return request.initiatedByServiceWorker();
}
/**
* @param {string} value
* @param {!SDK.NetworkRequest.NetworkRequest} request
* @return {boolean}
*/
static _requestResponseHeaderFilter(value, request) {
return request.responseHeaderValue(value) !== undefined;
}
/**
* @param {string} value
* @param {!SDK.NetworkRequest.NetworkRequest} request
* @return {boolean}
*/
static _requestMethodFilter(value, request) {
return request.requestMethod === value;
}
/**
* @param {string} value
* @param {!SDK.NetworkRequest.NetworkRequest} request
* @return {boolean}
*/
static _requestPriorityFilter(value, request) {
return request.priority() === value;
}
/**
* @param {string} value
* @param {!SDK.NetworkRequest.NetworkRequest} request
* @return {boolean}
*/
static _requestMimeTypeFilter(value, request) {
return request.mimeType === value;
}
/**
* @param {!MixedContentFilterValues} value
* @param {!SDK.NetworkRequest.NetworkRequest} request
* @return {boolean}
*/
static _requestMixedContentFilter(value, request) {
if (value === MixedContentFilterValues.Displayed) {
return request.mixedContentType === Protocol.Security.MixedContentType.OptionallyBlockable;
}
if (value === MixedContentFilterValues.Blocked) {
return request.mixedContentType === Protocol.Security.MixedContentType.Blockable && request.wasBlocked();
}
if (value === MixedContentFilterValues.BlockOverridden) {
return request.mixedContentType === Protocol.Security.MixedContentType.Blockable && !request.wasBlocked();
}
if (value === MixedContentFilterValues.All) {
return request.mixedContentType !== Protocol.Security.MixedContentType.None;
}
return false;
}
/**
* @param {string} value
* @param {!SDK.NetworkRequest.NetworkRequest} request
* @return {boolean}
*/
static _requestSchemeFilter(value, request) {
return request.scheme === value;
}
/**
* @param {string} value
* @param {!SDK.NetworkRequest.NetworkRequest} request
* @return {boolean}
*/
static _requestCookieDomainFilter(value, request) {
return request.allCookiesIncludingBlockedOnes().some(cookie => cookie.domain() === value);
}
/**
* @param {string} value
* @param {!SDK.NetworkRequest.NetworkRequest} request
* @return {boolean}
*/
static _requestCookieNameFilter(value, request) {
return request.allCookiesIncludingBlockedOnes().some(cookie => cookie.name() === value);
}
/**
* @param {string} value
* @param {!SDK.NetworkRequest.NetworkRequest} request
* @return {boolean}
*/
static _requestCookiePathFilter(value, request) {
return request.allCookiesIncludingBlockedOnes().some(cookie => cookie.path() === value);
}
/**
* @param {string} value
* @param {!SDK.NetworkRequest.NetworkRequest} request
* @return {boolean}
*/
static _requestCookieValueFilter(value, request) {
return request.allCookiesIncludingBlockedOnes().some(cookie => cookie.value() === value);
}
/**
* @param {string} value
* @param {!SDK.NetworkRequest.NetworkRequest} request
* @return {boolean}
*/
static _requestSetCookieDomainFilter(value, request) {
return request.responseCookies.some(cookie => cookie.domain() === value);
}
/**
* @param {string} value
* @param {!SDK.NetworkRequest.NetworkRequest} request
* @return {boolean}
*/
static _requestSetCookieNameFilter(value, request) {
return request.responseCookies.some(cookie => cookie.name() === value);
}
/**
* @param {string} value
* @param {!SDK.NetworkRequest.NetworkRequest} request
* @return {boolean}
*/
static _requestSetCookieValueFilter(value, request) {
return request.responseCookies.some(cookie => cookie.value() === value);
}
/**
* @param {number} value
* @param {!SDK.NetworkRequest.NetworkRequest} request
* @return {boolean}
*/
static _requestSizeLargerThanFilter(value, request) {
return request.transferSize >= value;
}
/**
* @param {string} value
* @param {!SDK.NetworkRequest.NetworkRequest} request
* @return {boolean}
*/
static _statusCodeFilter(value, request) {
return (String(request.statusCode)) === value;
}
/**
* @param {!SDK.NetworkRequest.NetworkRequest} request
* @return {boolean}
*/
static HTTPRequestsFilter(request) {
return request.parsedURL.isValid && (request.scheme in HTTPSchemas);
}
/**
* @param {string} value
* @param {!SDK.NetworkRequest.NetworkRequest} request
* @return {boolean}
*/
static _resourceTypeFilter(value, request) {
return request.resourceType().name() === value;
}
/**
* @param {string} value
* @param {!SDK.NetworkRequest.NetworkRequest} request
* @return {boolean}
*/
static _requestUrlFilter(value, request) {
const regex = new RegExp(Platform.StringUtilities.escapeForRegExp(value), 'i');
return regex.test(request.url());
}
/**
* @param {number} windowStart
* @param {number} windowEnd
* @param {!SDK.NetworkRequest.NetworkRequest} request
* @return {boolean}
*/
static _requestTimeFilter(windowStart, windowEnd, request) {
if (request.issueTime() > windowEnd) {
return false;
}
if (request.endTime !== -1 && request.endTime < windowStart) {
return false;
}
return true;
}
/**
* @param {!SDK.NetworkRequest.NetworkRequest} request
*/
static _copyRequestHeaders(request) {
Host.InspectorFrontendHost.InspectorFrontendHostInstance.copyText(request.requestHeadersText());
}
/**
* @param {!SDK.NetworkRequest.NetworkRequest} request
*/
static _copyResponseHeaders(request) {
Host.InspectorFrontendHost.InspectorFrontendHostInstance.copyText(request.responseHeadersText);
}
/**
* @param {!SDK.NetworkRequest.NetworkRequest} request
*/
static async _copyResponse(request) {
const contentData = await request.contentData();
/** @type {?string} */
let content = contentData.content || '';
if (!request.contentType().isTextType()) {
content = TextUtils.ContentProvider.contentAsDataURL(content, request.mimeType, contentData.encoded);
} else if (contentData.encoded && content) {
content = window.atob(content);
}
Host.InspectorFrontendHost.InspectorFrontendHostInstance.copyText(content);
}
/**
* @param {!DataTransfer} dataTransfer
*/
_handleDrop(dataTransfer) {
const items = dataTransfer.items;
if (!items.length) {
return;
}
const entry = items[0].webkitGetAsEntry();
if (entry.isDirectory) {
return;
}
entry.file(this.onLoadFromFile.bind(this));
}
/**
* @override
* @param {!File} file
*/
async onLoadFromFile(file) {
const outputStream = new Common.StringOutputStream.StringOutputStream();
const reader = new Bindings.FileUtils.ChunkedFileReader(file, /* chunkSize */ 10000000);
const success = await reader.read(outputStream);
if (!success) {
const error = reader.error();
if (error) {
this._harLoadFailed(/** @type {*} */ (error).message);
}
return;
}
let harRoot;
try {
// HARRoot and JSON.parse might throw.
harRoot = new HARImporter.HARFormat.HARRoot(JSON.parse(outputStream.data()));
} catch (e) {
this._harLoadFailed(e);
return;
}
SDK.NetworkLog.NetworkLog.instance().importRequests(
HARImporter.HARImporter.Importer.requestsFromHARLog(harRoot.log));
}
/**
* @param {string} message
*/
_harLoadFailed(message) {
Common.Console.Console.instance().error('Failed to load HAR file with following error: ' + message);
}
/**
* @param {?string} groupKey
*/
_setGrouping(groupKey) {
if (this._activeGroupLookup) {
this._activeGroupLookup.reset();
}
const groupLookup = groupKey ? this._groupLookups.get(groupKey) || null : null;
this._activeGroupLookup = groupLookup;
this._invalidateAllItems();
}
/**
* @return {number}
*/
_computeRowHeight() {
return Math.round(this._rawRowHeight * window.devicePixelRatio) / window.devicePixelRatio;
}
/**
* @override
* @param {!SDK.NetworkRequest.NetworkRequest} request
* @return {?NetworkRequestNode}
*/
nodeForRequest(request) {
return networkRequestToNode.get(request) || null;
}
/**
* @override
* @return {number}
*/
headerHeight() {
return this._headerHeight;
}
/**
* @override
* @param {boolean} recording
*/
setRecording(recording) {
this._recording = recording;
this._updateSummaryBar();
}
/**
* @override
* @param {!SDK.NetworkManager.NetworkManager} networkManager
*/
modelAdded(networkManager) {
// TODO(allada) Remove dependency on networkManager and instead use NetworkLog and PageLoad for needed data.
if (networkManager.target().parentTarget()) {
return;
}
const resourceTreeModel = networkManager.target().model(SDK.ResourceTreeModel.ResourceTreeModel);
if (resourceTreeModel) {
resourceTreeModel.addEventListener(SDK.ResourceTreeModel.Events.Load, this._loadEventFired, this);
resourceTreeModel.addEventListener(
SDK.ResourceTreeModel.Events.DOMContentLoaded, this._domContentLoadedEventFired, this);
}
}
/**
* @override
* @param {!SDK.NetworkManager.NetworkManager} networkManager
*/
modelRemoved(networkManager) {
if (!networkManager.target().parentTarget()) {
const resourceTreeModel = networkManager.target().model(SDK.ResourceTreeModel.ResourceTreeModel);
if (resourceTreeModel) {
resourceTreeModel.removeEventListener(SDK.ResourceTreeModel.Events.Load, this._loadEventFired, this);
resourceTreeModel.removeEventListener(
SDK.ResourceTreeModel.Events.DOMContentLoaded, this._domContentLoadedEventFired, this);
}
}
}
/**
* @override
* @return {!Components.Linkifier.Linkifier}
*/
linkifier() {
return this._linkifier;
}
/**
* @override
* @param {number} start
* @param {number} end
*/
setWindow(start, end) {
if (!start && !end) {
this._timeFilter = null;
this._timeCalculator.setWindow(null);
} else {
this._timeFilter = NetworkLogView._requestTimeFilter.bind(null, start, end);
this._timeCalculator.setWindow(new NetworkTimeBoundary(start, end));
}
this._filterRequests();
}
/** @override */
resetFocus() {
this._dataGrid.element.focus();
}
_resetSuggestionBuilder() {
this._suggestionBuilder.clear();
this._suggestionBuilder.addItem(FilterType.Is, IsFilterType.Running);
this._suggestionBuilder.addItem(FilterType.Is, IsFilterType.FromCache);
this._suggestionBuilder.addItem(FilterType.Is, IsFilterType.ServiceWorkerIntercepted);
this._suggestionBuilder.addItem(FilterType.Is, IsFilterType.ServiceWorkerInitiated);
this._suggestionBuilder.addItem(FilterType.LargerThan, '100');
this._suggestionBuilder.addItem(FilterType.LargerThan, '10k');
this._suggestionBuilder.addItem(FilterType.LargerThan, '1M');
this._textFilterUI.setSuggestionProvider(this._suggestionBuilder.completions.bind(this._suggestionBuilder));
}
/**
* @param {!Common.EventTarget.EventTargetEvent} event
*/
_filterChanged(event) {
this.removeAllNodeHighlights();
this._parseFilterQuery(this._textFilterUI.value());
this._filterRequests();
this._textFilterSetting.set(this._textFilterUI.value());
}
async resetFilter() {
this._textFilterUI.clear();
}
_showRecordingHint() {
this._hideRecordingHint();
this._recordingHint = this.element.createChild('div', 'network-status-pane fill');
const hintText = this._recordingHint.createChild('div', 'recording-hint');
if (this._recording) {
let reloadShortcutNode = null;
const reloadShortcut =
UI.ShortcutRegistry.ShortcutRegistry.instance().shortcutsForAction('inspector_main.reload')[0];
if (reloadShortcut) {
reloadShortcutNode = this._recordingHint.createChild('b');
reloadShortcutNode.textContent = reloadShortcut.title();
}
const recordingText = hintText.createChild('span');
recordingText.textContent = i18nString(UIStrings.recordingNetworkActivity);
if (reloadShortcutNode) {
hintText.createChild('br');
hintText.appendChild(i18n.i18n.getFormatLocalizedString(
str_, UIStrings.performARequestOrHitSToRecordThe, {PH1: reloadShortcutNode}));
}
} else {
const recordNode = hintText.createChild('b');
recordNode.textContent =
UI.ShortcutRegistry.ShortcutRegistry.instance().shortcutTitleForAction('network.toggle-recording') || '';
hintText.appendChild(
i18n.i18n.getFormatLocalizedString(str_, UIStrings.recordSToDisplayNetworkActivity, {PH1: recordNode}));
}
hintText.createChild('br');
hintText.appendChild(UI.XLink.XLink.create(
'https://developers.google.com/web/tools/chrome-devtools/network/?utm_source=devtools&utm_campaign=2019Q1',
i18nString(UIStrings.learnMore)));
this._setHidden(true);
}
_hideRecordingHint() {
this._setHidden(false);
if (this._recordingHint) {
this._recordingHint.remove();
}
UI.ARIAUtils.alert(i18nString(UIStrings.networkDataAvailable), this._summaryToolbar.element);
this._recordingHint = null;
}
/**
* @param {boolean} value
*/
_setHidden(value) {
this._columns.setHidden(value);
UI.ARIAUtils.setHidden(this._summaryToolbar.element, value);
}
/**
* @override
* @return {!Array.<!Element>}
*/
elementsToRestoreScrollPositionsFor() {
if (!this._dataGrid) // Not initialized yet.
{
return [];
}
return [this._dataGrid.scrollContainer];
}
/** @override */
columnExtensionResolved() {
this._invalidateAllItems(true);
}
_setupDataGrid() {
this._dataGrid.setRowContextMenuCallback((contextMenu, node) => {
const request = (/** @type {!NetworkNode} */ (node)).request();
if (request) {
this.handleContextMenuForRequest(contextMenu, request);
}
});
this._dataGrid.setStickToBottom(true);
this._dataGrid.setName('networkLog');
this._dataGrid.setResizeMethod(DataGrid.DataGrid.ResizeMethod.Last);
this._dataGrid.element.classList.add('network-log-grid');
this._dataGrid.element.addEventListener('mousedown', this._dataGridMouseDown.bind(this), true);
this._dataGrid.element.addEventListener('mousemove', this._dataGridMouseMove.bind(this), true);
this._dataGrid.element.addEventListener('mouseleave', () => this._setHoveredNode(null), true);
this._dataGrid.element.addEventListener('keydown', event => {
if (isEnterOrSpaceKey(event)) {
this.dispatchEventToListeners(Events.RequestActivated, {showPanel: true, takeFocus: true});
event.consume(true);
}
});
this._dataGrid.element.addEventListener('focus', this._onDataGridFocus.bind(this), true);
this._dataGrid.element.addEventListener('blur', this._onDataGridBlur.bind(this), true);
return this._dataGrid;
}
/**
* @param {!Event} event
*/
_dataGridMouseMove(event) {
const mouseEvent = /** @type {!MouseEvent} */ (event);
const node =
/** @type {!NetworkNode} */ (this._dataGrid.dataGridNodeFromNode(/** @type {!Node} */ (mouseEvent.target)));
const highlightInitiatorChain = mouseEvent.shiftKey;
this._setHoveredNode(node, highlightInitiatorChain);
}
/**
* @override
* @return {?NetworkNode}
*/
hoveredNode() {
return this._hoveredNode;
}
/**
* @param {?NetworkNode} node
* @param {boolean=} highlightInitiatorChain
*/
_setHoveredNode(node, highlightInitiatorChain) {
if (this._hoveredNode) {
this._hoveredNode.setHovered(false, false);
}
this._hoveredNode = node;
if (this._hoveredNode) {
this._hoveredNode.setHovered(true, Boolean(highlightInitiatorChain));
}
}
/**
* @param {!Event} event
*/
_dataGridMouseDown(event) {
const mouseEvent = /** @type {!MouseEvent} */ (event);
if (!this._dataGrid.selectedNode && mouseEvent.button) {
mouseEvent.consume();
}
}
_updateSummaryBar() {
this._hideRecordingHint();
let transferSize = 0;
let resourceSize = 0;
let selectedNodeNumber = 0;
let selectedTransferSize = 0;
let selectedResourceSize = 0;
let baseTime = -1;
let maxTime = -1;
let nodeCount = 0;
for (const request of SDK.NetworkLog.NetworkLog.instance().requests()) {
const node = networkRequestToNode.get(request);
if (!node) {
continue;
}
nodeCount++;
const requestTransferSize = request.transferSize;
transferSize += requestTransferSize;
const requestResourceSize = request.resourceSize;
resourceSize += requestResourceSize;
if (!filteredNetworkRequests.has(node)) {
selectedNodeNumber++;
selectedTransferSize += requestTransferSize;
selectedResourceSize += requestResourceSize;
}
const networkManager = SDK.NetworkManager.NetworkManager.forRequest(request);
// TODO(allada) inspectedURL should be stored in PageLoad used instead of target so HAR requests can have an
// inspected url.
if (networkManager && request.url() === networkManager.target().inspectedURL() &&
request.resourceType() === Common.ResourceType.resourceTypes.Document &&
!networkManager.target().parentTarget()) {
baseTime = request.startTime;
}
if (request.endTime > maxTime) {
maxTime = request.endTime;
}
}
if (!nodeCount) {
this._showRecordingHint();
return;
}
this._summaryToolbar.removeToolbarItems();
/**
* @param {string} chunk
* @param {string=} title
* @return {!HTMLDivElement}
*/
const appendChunk = (chunk, title) => {
const toolbarText = new UI.Toolbar.ToolbarText(chunk);
toolbarText.setTitle(title ? title : chunk);
this._summaryToolbar.appendToolbarItem(toolbarText);
return /** @type {!HTMLDivElement} */ (toolbarText.element);
};
if (selectedNodeNumber !== nodeCount) {
appendChunk(i18nString(UIStrings.sSRequests, {PH1: selectedNodeNumber, PH2: nodeCount}));
this._summaryToolbar.appendSeparator();
appendChunk(
i18nString(UIStrings.sSTransferred, {
PH1: Platform.NumberUtilities.bytesToString(selectedTransferSize),
PH2: Platform.NumberUtilities.bytesToString(transferSize)
}),
i18nString(UIStrings.sBSBTransferredOverNetwork, {PH1: selectedTransferSize, PH2: transferSize}));
this._summaryToolbar.appendSeparator();
appendChunk(
i18nString(UIStrings.sSResources, {
PH1: Platform.NumberUtilities.bytesToString(selectedResourceSize),
PH2: Platform.NumberUtilities.bytesToString(resourceSize)
}),
i18nString(UIStrings.sBSBResourcesLoadedByThePage, {PH1: selectedResourceSize, PH2: resourceSize}));
} else {
appendChunk(i18nString(UIStrings.sRequests, {PH1: nodeCount}));
this._summaryToolbar.appendSeparator();
appendChunk(
i18nString(UIStrings.sTransferred, {PH1: Platform.NumberUtilities.bytesToString(transferSize)}),
i18nString(UIStrings.sBTransferredOverNetwork, {PH1: transferSize}));
this._summaryToolbar.appendSeparator();
appendChunk(
i18nString(UIStrings.sResources, {PH1: Platform.NumberUtilities.bytesToString(resourceSize)}),
i18nString(UIStrings.sBResourcesLoadedByThePage, {PH1: resourceSize}));
}
if (baseTime !== -1 && maxTime !== -1) {
this._summaryToolbar.appendSeparator();
appendChunk(i18nString(UIStrings.finishS, {PH1: Number.secondsToString(maxTime - baseTime)}));
if (this._mainRequestDOMContentLoadedTime !== -1 && this._mainRequestDOMContentLoadedTime > baseTime) {
this._summaryToolbar.appendSeparator();
const domContentLoadedText = i18nString(
UIStrings.domcontentloadedS,
{PH1: Number.secondsToString(this._mainRequestDOMContentLoadedTime - baseTime)});
appendChunk(domContentLoadedText).style.color = NetworkLogView.getDCLEventColor();
}
if (this._mainRequestLoadTime !== -1) {
this._summaryToolbar.appendSeparator();
const loadText =
i18nString(UIStrings.loadS, {PH1: Number.secondsToString(this._mainRequestLoadTime - baseTime)});
appendChunk(loadText).style.color = NetworkLogView.getLoadEventColor();
}
}
}
/** @override */
scheduleRefresh() {
if (this._needsRefresh) {
return;
}
this._needsRefresh = true;
if (this.isShowing() && !this._refreshRequestId) {
this._refreshRequestId = this.element.window().requestAnimationFrame(this._refresh.bind(this));
}
}
/**
* @override
* @param {!Array<number>} times
*/
addFilmStripFrames(times) {
this._columns.addEventDividers(times, 'network-frame-divider');
}
/**
* @override
* @param {number} time
*/
selectFilmStripFrame(time) {
this._columns.selectFilmStripFrame(time);
}
/** @override */
clearFilmStripFrame() {
this._columns.clearFilmStripFrame();
}
_refreshIfNeeded() {
if (this._needsRefresh) {
this._refresh();
}
}
/**
* @param {boolean=} deferUpdate
*/
_invalidateAllItems(deferUpdate) {
this._staleRequests = new Set(SDK.NetworkLog.NetworkLog.instance().requests());
if (deferUpdate) {
this.scheduleRefresh();
} else {
this._refresh();
}
}
/**
* @override
* @return {!NetworkTimeCalculator}
*/
timeCalculator() {
return this._timeCalculator;
}
/**
* @override
* @return {!NetworkTimeCalculator}
*/
calculator() {
return this._calculator;
}
/**
* @override
* @param {!NetworkTimeCalculator} x
*/
setCalculator(x) {
if (!x || this._calculator === x) {
return;
}
if (this._calculator !== x) {
this._calculator = x;
this._columns.setCalculator(this._calculator);
}
this._calculator.reset();
if (this._calculator.startAtZero) {
this._columns.hideEventDividers();
} else {
this._columns.showEventDividers();
}
this._invalidateAllItems();
}
/**
* @param {!Common.EventTarget.EventTargetEvent} event
*/
_loadEventFired(event) {
if (!this._recording) {
return;
}
const time = /** @type {number} */ (event.data.loadTime);
if (time) {
this._mainRequestLoadTime = time;
this._columns.addEventDividers([time], 'network-load-divider');
}
}
/**
* @param {!Common.EventTarget.EventTargetEvent} event
*/
_domContentLoadedEventFired(event) {
if (!this._recording) {
return;
}
const data = /** @type {number} */ (event.data);
if (data) {
this._mainRequestDOMContentLoadedTime = data;
this._columns.addEventDividers([data], 'network-dcl-divider');
}
}
/**
* @override
*/
wasShown() {
this._refreshIfNeeded();
this._columns.wasShown();
}
/**
* @override
*/
willHide() {
this._columns.willHide();
}
/**
* @override
*/
onResize() {
this._rowHeight = this._computeRowHeight();
}
/**
* @override
* @return {!Array<!NetworkNode>}
*/
flatNodesList() {
/** @type {!DataGrid.ViewportDataGrid.ViewportDataGridNode<!DataGrid.SortableDataGrid.SortableDataGridNode<!NetworkNode>>} */
const rootNode = (this._dataGrid.rootNode());
return /** @type {!Array<!NetworkNode>} */ (rootNode.flatChildren());
}
_onDataGridFocus() {
if (this._dataGrid.element.matches(':focus-visible')) {
this.element.classList.add('grid-focused');
}
this.updateNodeBackground();
}
_onDataGridBlur() {
this.element.classList.remove('grid-focused');
this.updateNodeBackground();
}
/** @override */
updateNodeBackground() {
if (this._dataGrid.selectedNode) {
(/** @type {!NetworkNode} */ (this._dataGrid.selectedNode)).updateBackgroundColor();
}
}
/**
* @override
* @param {boolean} isSelected
*/
updateNodeSelectedClass(isSelected) {
if (isSelected) {
this.element.classList.remove('no-node-selected');
} else {
this.element.classList.add('no-node-selected');
}
}
/** @override */
stylesChanged() {
this._columns.scheduleRefresh();
}
_refresh() {
this._needsRefresh = false;
if (this._refreshRequestId) {
this.element.window().cancelAnimationFrame(this._refreshRequestId);
this._refreshRequestId = null;
}
this.removeAllNodeHighlights();
this._timeCalculator.updateBoundariesForEventTime(this._mainRequestLoadTime);
this._durationCalculator.updateBoundariesForEventTime(this._mainRequestLoadTime);
this._timeCalculator.updateBoundariesForEventTime(this._mainRequestDOMContentLoadedTime);
this._durationCalculator.updateBoundariesForEventTime(this._mainRequestDOMContentLoadedTime);
/** @type {!Map<!NetworkNode, !NetworkNode>} */
const nodesToInsert = new Map();
/** @type {!Array<!NetworkNode>} */
const nodesToRefresh = [];
/** @type {!Set<!NetworkRequestNode>} */
const staleNodes = new Set();
// While creating nodes it may add more entries into _staleRequests because redirect request nodes update the parent
// node so we loop until we have no more stale requests.
while (this._staleRequests.size) {
const request = this._staleRequests.values().next().value;
this._staleRequests.delete(request);
let node = networkRequestToNode.get(request);
if (!node) {
node = this._createNodeForRequest(request);
}
staleNodes.add(node);
}
for (const node of staleNodes) {
const isFilteredOut = !this._applyFilter(node);
if (isFilteredOut && node === this._hoveredNode) {
this._setHoveredNode(null);
}
if (!isFilteredOut) {
nodesToRefresh.push(node);
}
const request = node.request();
this._timeCalculator.updateBoundaries(request);
this._durationCalculator.updateBoundaries(request);
const newParent = this._parentNodeForInsert(node);
const wasAlreadyFiltered = filteredNetworkRequests.has(node);
if (wasAlreadyFiltered === isFilteredOut && node.parent === newParent) {
continue;
}
if (isFilteredOut) {
filteredNetworkRequests.add(node);
} else {
filteredNetworkRequests.delete(node);
}
const removeFromParent = node.parent && (isFilteredOut || node.parent !== newParent);
if (removeFromParent) {
let parent = node.parent;
if (!parent) {
continue;
}
parent.removeChild(node);
while (parent && !parent.hasChildren() && parent.dataGrid && parent.dataGrid.rootNode() !== parent) {
const grandpa