Move network to panels/network
[email protected]
Bug: 1187573
Change-Id: Id4dbc7537828a72eb58c53428b814155d6ffec8c
Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/2794604
Commit-Queue: Tim van der Lippe <[email protected]>
Reviewed-by: Paul Lewis <[email protected]>
diff --git a/front_end/panels/network/NetworkLogView.ts b/front_end/panels/network/NetworkLogView.ts
new file mode 100644
index 0000000..2c7807d
--- /dev/null
+++ b/front_end/panels/network/NetworkLogView.ts
@@ -0,0 +1,2193 @@
+// Copyright 2021 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.
+
+/*
+ * Copyright (C) 2007, 2008 Apple Inc. All rights reserved.
+ * Copyright (C) 2008, 2009 Anthony Ricaud <[email protected]>
+ * 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.
+ */
+
+/* eslint-disable rulesdir/no_underscored_properties */
+
+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
+
+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 Message in the summary toolbar at the bottom of the Network log that shows the compressed size of the
+ * resources transferred during a selected time frame over the compressed size of all resources transferred during
+ * the whole network log.
+ *@example {5 B} PH1
+ *@example {10 B} PH2
+ */
+ sSTransferred: '{PH1} / {PH2} transferred',
+ /**
+ *@description Message in a tooltip that shows the compressed size of the resources transferred during a selected
+ * time frame over the compressed size of all resources transferred during the whole network log.
+ *@example {10} PH1
+ *@example {15} PH2
+ */
+ sBSBTransferredOverNetwork: '{PH1} B / {PH2} B transferred over network',
+ /**
+ * @description Text in Network Log View of the Network panel. Appears when a particular network
+ * resource is selected by the user. Shows how large the selected resource was (PH1) out of the
+ * total size (PH2).
+ * @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 Message in the summary toolbar at the bottom of the Network log that shows the compressed size of
+ * all resources transferred over network during a network activity log.
+ *@example {4 B} PH1
+ */
+ sTransferred: '{PH1} transferred',
+ /**
+ *@description Message in a tooltip that shows the compressed size of all resources transferred over network during
+ * a network activity log.
+ *@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 stack trace',
+ /**
+ * @description A context menu command in the Network panel, for copying to the clipboard.
+ * PowerShell refers to the format the data will be copied as.
+ */
+ copyAsPowershell: 'Copy as `PowerShell`',
+ /**
+ *@description A context menu command in the Network panel, for copying to the clipboard. 'fetch'
+ * refers to the format the data will be copied as, which is compatible with the fetch web API.
+ */
+ 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. 'all' refers to every
+ * network request that is currently shown.
+ */
+ 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('panels/network/NetworkLogView.ts', UIStrings);
+const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
+export class NetworkLogView extends UI.Widget.VBox implements
+ SDK.SDKModel.SDKModelObserver<SDK.NetworkManager.NetworkManager>, NetworkLogViewInterface {
+ _networkHideDataURLSetting: Common.Settings.Setting<boolean>;
+ _networkShowIssuesOnlySetting: Common.Settings.Setting<boolean>;
+ _networkOnlyBlockedRequestsSetting: Common.Settings.Setting<boolean>;
+ // TODO(crbug.com/1172300) Ignored during the jsdoc to ts migration)
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ _networkResourceTypeFiltersSetting: Common.Settings.Setting<any>;
+ _rawRowHeight: number;
+ _progressBarContainer: Element;
+ _networkLogLargeRowsSetting: Common.Settings.Setting<boolean>;
+ _rowHeight: number;
+ _timeCalculator: NetworkTransferTimeCalculator;
+ _durationCalculator: NetworkTransferDurationCalculator;
+ _calculator: NetworkTransferTimeCalculator;
+ _columns: NetworkLogViewColumns;
+ _staleRequests: Set<SDK.NetworkRequest.NetworkRequest>;
+ _mainRequestLoadTime: number;
+ _mainRequestDOMContentLoadedTime: number;
+ // TODO(crbug.com/1172300) Ignored during the jsdoc to ts migration)
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ _highlightedSubstringChanges: any;
+ _filters: Filter[];
+ _timeFilter: Filter|null;
+ _hoveredNode: NetworkNode|null;
+ _recordingHint: Element|null;
+ _refreshRequestId: number|null;
+ _highlightedNode: NetworkRequestNode|null;
+ _linkifier: Components.Linkifier.Linkifier;
+ _recording: boolean;
+ _needsRefresh: boolean;
+ _headerHeight: number;
+ _groupLookups: Map<string, GroupLookupInterface>;
+ _activeGroupLookup: GroupLookupInterface|null;
+ _textFilterUI: UI.FilterBar.TextFilterUI;
+ _dataURLFilterUI: UI.FilterBar.CheckboxFilterUI;
+ _resourceCategoryFilterUI: UI.FilterBar.NamedBitSetFilterUI;
+ _onlyIssuesFilterUI: UI.FilterBar.CheckboxFilterUI;
+ _onlyBlockedRequestsUI: UI.FilterBar.CheckboxFilterUI;
+ _filterParser: TextUtils.TextUtils.FilterParser;
+ _suggestionBuilder: UI.FilterSuggestionBuilder.FilterSuggestionBuilder;
+ _dataGrid: DataGrid.SortableDataGrid.SortableDataGrid<NetworkNode>;
+ _summaryToolbar: UI.Toolbar.Toolbar;
+ _filterBar: UI.FilterBar.FilterBar;
+ _textFilterSetting: Common.Settings.Setting<string>;
+
+ constructor(
+ filterBar: UI.FilterBar.FilterBar, progressBarContainer: Element,
+ networkLogLargeRowsSetting: Common.Settings.Setting<boolean>) {
+ super();
+ this.setMinimumSize(50, 64);
+ this.registerRequiredCSS('panels/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);
+
+ function updateRowHeight(this: NetworkLogView): void {
+ this._rawRowHeight = Boolean(this._networkLogLargeRowsSetting.get()) ? 41 : 21;
+ this._rowHeight = this._computeRowHeight();
+ }
+ this._rawRowHeight = 0;
+ this._rowHeight = 0;
+ updateRowHeight.call(this);
+
+ this._timeCalculator = new NetworkTransferTimeCalculator();
+ this._durationCalculator = new NetworkTransferDurationCalculator();
+ this._calculator = this._timeCalculator;
+
+ this._columns =
+ // TODO(crbug.com/1172300) Ignored during the jsdoc to ts migration
+ // @ts-expect-error
+ new NetworkLogViewColumns(this, this._timeCalculator, this._durationCalculator, networkLogLargeRowsSetting);
+ this._columns.show(this.element);
+
+ this._staleRequests = new Set();
+ this._mainRequestLoadTime = -1;
+ this._mainRequestDOMContentLoadedTime = -1;
+ this._highlightedSubstringChanges = [];
+
+ this._filters = [];
+ this._timeFilter = null;
+ this._hoveredNode = null;
+ this._recordingHint = null;
+ this._refreshRequestId = null;
+ this._highlightedNode = null;
+
+ this._linkifier = new Components.Linkifier.Linkifier();
+
+ this._recording = false;
+ this._needsRefresh = false;
+
+ this._headerHeight = 0;
+
+ this._groupLookups = new Map();
+ this._groupLookups.set('Frame', new NetworkFrameGrouper(this));
+
+ 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: (): string => 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(): void {
+ const value = Common.Settings.Settings.instance().moduleSetting('network.group-by-frame').get();
+ this._setGrouping(value ? 'Frame' : null);
+ }
+
+ static _sortSearchValues(key: string, values: string[]): void {
+ if (key === FilterType.Priority) {
+ values.sort((a, b) => {
+ const aPriority = (PerfUI.NetworkPriorities.uiLabelToNetworkPriority(a) as Protocol.Network.ResourcePriority);
+ const bPriority = (PerfUI.NetworkPriorities.uiLabelToNetworkPriority(b) as Protocol.Network.ResourcePriority);
+ return PerfUI.NetworkPriorities.networkPriorityWeight(aPriority) -
+ PerfUI.NetworkPriorities.networkPriorityWeight(bPriority);
+ });
+ } else {
+ values.sort();
+ }
+ }
+
+ static _negativeFilter(filter: Filter, request: SDK.NetworkRequest.NetworkRequest): boolean {
+ return !filter(request);
+ }
+
+ static _requestPathFilter(regex: RegExp|null, request: SDK.NetworkRequest.NetworkRequest): boolean {
+ if (!regex) {
+ return false;
+ }
+
+ return regex.test(request.path() + '/' + request.name());
+ }
+
+ static _subdomains(domain: string): string[] {
+ const result = [domain];
+ let indexOfPeriod = domain.indexOf('.');
+ while (indexOfPeriod !== -1) {
+ result.push('*' + domain.substring(indexOfPeriod));
+ indexOfPeriod = domain.indexOf('.', indexOfPeriod + 1);
+ }
+ return result;
+ }
+
+ static _createRequestDomainFilter(value: string): Filter {
+ const escapedPattern = value.split('*').map(Platform.StringUtilities.escapeForRegExp).join('.*');
+ return NetworkLogView._requestDomainFilter.bind(null, new RegExp('^' + escapedPattern + '$', 'i'));
+ }
+
+ static _requestDomainFilter(regex: RegExp, request: SDK.NetworkRequest.NetworkRequest): boolean {
+ return regex.test(request.domain);
+ }
+
+ static _runningRequestFilter(request: SDK.NetworkRequest.NetworkRequest): boolean {
+ return !request.finished;
+ }
+
+ static _fromCacheRequestFilter(request: SDK.NetworkRequest.NetworkRequest): boolean {
+ return request.cached();
+ }
+
+ static _interceptedByServiceWorkerFilter(request: SDK.NetworkRequest.NetworkRequest): boolean {
+ return request.fetchedViaServiceWorker;
+ }
+
+ static _initiatedByServiceWorkerFilter(request: SDK.NetworkRequest.NetworkRequest): boolean {
+ return request.initiatedByServiceWorker();
+ }
+
+ static _requestResponseHeaderFilter(value: string, request: SDK.NetworkRequest.NetworkRequest): boolean {
+ return request.responseHeaderValue(value) !== undefined;
+ }
+
+ static _requestMethodFilter(value: string, request: SDK.NetworkRequest.NetworkRequest): boolean {
+ return request.requestMethod === value;
+ }
+
+ static _requestPriorityFilter(value: string, request: SDK.NetworkRequest.NetworkRequest): boolean {
+ return request.priority() === value;
+ }
+
+ static _requestMimeTypeFilter(value: string, request: SDK.NetworkRequest.NetworkRequest): boolean {
+ return request.mimeType === value;
+ }
+
+ static _requestMixedContentFilter(value: MixedContentFilterValues, request: SDK.NetworkRequest.NetworkRequest):
+ boolean {
+ 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;
+ }
+
+ static _requestSchemeFilter(value: string, request: SDK.NetworkRequest.NetworkRequest): boolean {
+ return request.scheme === value;
+ }
+
+ static _requestCookieDomainFilter(value: string, request: SDK.NetworkRequest.NetworkRequest): boolean {
+ return request.allCookiesIncludingBlockedOnes().some(cookie => cookie.domain() === value);
+ }
+
+ static _requestCookieNameFilter(value: string, request: SDK.NetworkRequest.NetworkRequest): boolean {
+ return request.allCookiesIncludingBlockedOnes().some(cookie => cookie.name() === value);
+ }
+
+ static _requestCookiePathFilter(value: string, request: SDK.NetworkRequest.NetworkRequest): boolean {
+ return request.allCookiesIncludingBlockedOnes().some(cookie => cookie.path() === value);
+ }
+
+ static _requestCookieValueFilter(value: string, request: SDK.NetworkRequest.NetworkRequest): boolean {
+ return request.allCookiesIncludingBlockedOnes().some(cookie => cookie.value() === value);
+ }
+
+ static _requestSetCookieDomainFilter(value: string, request: SDK.NetworkRequest.NetworkRequest): boolean {
+ return request.responseCookies.some(cookie => cookie.domain() === value);
+ }
+
+ static _requestSetCookieNameFilter(value: string, request: SDK.NetworkRequest.NetworkRequest): boolean {
+ return request.responseCookies.some(cookie => cookie.name() === value);
+ }
+
+ static _requestSetCookieValueFilter(value: string, request: SDK.NetworkRequest.NetworkRequest): boolean {
+ return request.responseCookies.some(cookie => cookie.value() === value);
+ }
+
+ static _requestSizeLargerThanFilter(value: number, request: SDK.NetworkRequest.NetworkRequest): boolean {
+ return request.transferSize >= value;
+ }
+
+ static _statusCodeFilter(value: string, request: SDK.NetworkRequest.NetworkRequest): boolean {
+ return (String(request.statusCode)) === value;
+ }
+
+ // TODO(crbug.com/1172300) Ignored during the jsdoc to ts migration)
+ // eslint-disable-next-line @typescript-eslint/naming-convention
+ static HTTPRequestsFilter(request: SDK.NetworkRequest.NetworkRequest): boolean {
+ return request.parsedURL.isValid && (request.scheme in HTTPSchemas);
+ }
+
+ static _resourceTypeFilter(value: string, request: SDK.NetworkRequest.NetworkRequest): boolean {
+ return request.resourceType().name() === value;
+ }
+
+ static _requestUrlFilter(value: string, request: SDK.NetworkRequest.NetworkRequest): boolean {
+ const regex = new RegExp(Platform.StringUtilities.escapeForRegExp(value), 'i');
+ return regex.test(request.url());
+ }
+
+ static _requestTimeFilter(windowStart: number, windowEnd: number, request: SDK.NetworkRequest.NetworkRequest):
+ boolean {
+ if (request.issueTime() > windowEnd) {
+ return false;
+ }
+ if (request.endTime !== -1 && request.endTime < windowStart) {
+ return false;
+ }
+ return true;
+ }
+
+ static _copyRequestHeaders(request: SDK.NetworkRequest.NetworkRequest): void {
+ Host.InspectorFrontendHost.InspectorFrontendHostInstance.copyText(request.requestHeadersText());
+ }
+
+ static _copyResponseHeaders(request: SDK.NetworkRequest.NetworkRequest): void {
+ Host.InspectorFrontendHost.InspectorFrontendHostInstance.copyText(request.responseHeadersText);
+ }
+
+ static async _copyResponse(request: SDK.NetworkRequest.NetworkRequest): Promise<void> {
+ const contentData = await request.contentData();
+ let content: (string|null)|string = 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);
+ }
+
+ _handleDrop(dataTransfer: DataTransfer): void {
+ const items = dataTransfer.items;
+ if (!items.length) {
+ return;
+ }
+ const entry = items[0].webkitGetAsEntry();
+ if (entry.isDirectory) {
+ return;
+ }
+
+ entry.file(this.onLoadFromFile.bind(this));
+ }
+
+ async onLoadFromFile(file: File): Promise<void> {
+ 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) {
+ // TODO(crbug.com/1172300) Ignored during the jsdoc to ts migration)
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ this._harLoadFailed((error as any).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));
+ }
+
+ _harLoadFailed(message: string): void {
+ Common.Console.Console.instance().error('Failed to load HAR file with following error: ' + message);
+ }
+
+ _setGrouping(groupKey: string|null): void {
+ if (this._activeGroupLookup) {
+ this._activeGroupLookup.reset();
+ }
+ const groupLookup = groupKey ? this._groupLookups.get(groupKey) || null : null;
+ this._activeGroupLookup = groupLookup;
+ this._invalidateAllItems();
+ }
+
+ _computeRowHeight(): number {
+ return Math.round(this._rawRowHeight * window.devicePixelRatio) / window.devicePixelRatio;
+ }
+
+ nodeForRequest(request: SDK.NetworkRequest.NetworkRequest): NetworkRequestNode|null {
+ return networkRequestToNode.get(request) || null;
+ }
+
+ headerHeight(): number {
+ return this._headerHeight;
+ }
+
+ setRecording(recording: boolean): void {
+ this._recording = recording;
+ this._updateSummaryBar();
+ }
+
+ modelAdded(networkManager: SDK.NetworkManager.NetworkManager): void {
+ // 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);
+ }
+ }
+
+ modelRemoved(networkManager: SDK.NetworkManager.NetworkManager): void {
+ 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);
+ }
+ }
+ }
+
+ linkifier(): Components.Linkifier.Linkifier {
+ return this._linkifier;
+ }
+
+ setWindow(start: number, end: number): void {
+ 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();
+ }
+
+ resetFocus(): void {
+ this._dataGrid.element.focus();
+ }
+
+ _resetSuggestionBuilder(): void {
+ 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));
+ }
+
+ _filterChanged(_event: Common.EventTarget.EventTargetEvent): void {
+ this.removeAllNodeHighlights();
+ this._parseFilterQuery(this._textFilterUI.value());
+ this._filterRequests();
+ this._textFilterSetting.set(this._textFilterUI.value());
+ }
+
+ async resetFilter(): Promise<void> {
+ this._textFilterUI.clear();
+ }
+
+ _showRecordingHint(): void {
+ 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: Element|null = 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(): void {
+ this._setHidden(false);
+ if (this._recordingHint) {
+ this._recordingHint.remove();
+ }
+ UI.ARIAUtils.alert(i18nString(UIStrings.networkDataAvailable), this._summaryToolbar.element);
+ this._recordingHint = null;
+ }
+
+ _setHidden(value: boolean): void {
+ this._columns.setHidden(value);
+ UI.ARIAUtils.setHidden(this._summaryToolbar.element, value);
+ }
+
+ elementsToRestoreScrollPositionsFor(): Element[] {
+ if (!this._dataGrid) // Not initialized yet.
+ {
+ return [];
+ }
+ return [this._dataGrid.scrollContainer];
+ }
+
+ columnExtensionResolved(): void {
+ this._invalidateAllItems(true);
+ }
+
+ _setupDataGrid(): DataGrid.SortableDataGrid.SortableDataGrid<NetworkNode> {
+ this._dataGrid.setRowContextMenuCallback((contextMenu, node) => {
+ const request = (node as NetworkNode).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;
+ }
+
+ _dataGridMouseMove(event: Event): void {
+ const mouseEvent = (event as MouseEvent);
+ const node = (this._dataGrid.dataGridNodeFromNode((mouseEvent.target as Node)));
+ const highlightInitiatorChain = mouseEvent.shiftKey;
+ this._setHoveredNode(node as NetworkNode, highlightInitiatorChain);
+ }
+
+ hoveredNode(): NetworkNode|null {
+ return this._hoveredNode;
+ }
+
+ _setHoveredNode(node: NetworkNode|null, highlightInitiatorChain?: boolean): void {
+ if (this._hoveredNode) {
+ this._hoveredNode.setHovered(false, false);
+ }
+ this._hoveredNode = node;
+ if (this._hoveredNode) {
+ this._hoveredNode.setHovered(true, Boolean(highlightInitiatorChain));
+ }
+ }
+
+ _dataGridMouseDown(event: Event): void {
+ const mouseEvent = (event as MouseEvent);
+ if (!this._dataGrid.selectedNode && mouseEvent.button) {
+ mouseEvent.consume();
+ }
+ }
+
+ _updateSummaryBar(): void {
+ 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();
+ const appendChunk = (chunk: string, title?: string): HTMLDivElement => {
+ const toolbarText = new UI.Toolbar.ToolbarText(chunk);
+ toolbarText.setTitle(title ? title : chunk);
+ this._summaryToolbar.appendToolbarItem(toolbarText);
+ return toolbarText.element as HTMLDivElement;
+ };
+
+ 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();
+ }
+ }
+ }
+
+ scheduleRefresh(): void {
+ if (this._needsRefresh) {
+ return;
+ }
+
+ this._needsRefresh = true;
+
+ if (this.isShowing() && !this._refreshRequestId) {
+ this._refreshRequestId = this.element.window().requestAnimationFrame(this._refresh.bind(this));
+ }
+ }
+
+ addFilmStripFrames(times: number[]): void {
+ this._columns.addEventDividers(times, 'network-frame-divider');
+ }
+
+ selectFilmStripFrame(time: number): void {
+ this._columns.selectFilmStripFrame(time);
+ }
+
+ clearFilmStripFrame(): void {
+ this._columns.clearFilmStripFrame();
+ }
+
+ _refreshIfNeeded(): void {
+ if (this._needsRefresh) {
+ this._refresh();
+ }
+ }
+
+ _invalidateAllItems(deferUpdate?: boolean): void {
+ this._staleRequests = new Set(SDK.NetworkLog.NetworkLog.instance().requests());
+ if (deferUpdate) {
+ this.scheduleRefresh();
+ } else {
+ this._refresh();
+ }
+ }
+
+ timeCalculator(): NetworkTimeCalculator {
+ return this._timeCalculator;
+ }
+
+ calculator(): NetworkTimeCalculator {
+ return this._calculator;
+ }
+
+ setCalculator(x: NetworkTimeCalculator): void {
+ 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();
+ }
+
+ _loadEventFired(event: Common.EventTarget.EventTargetEvent): void {
+ if (!this._recording) {
+ return;
+ }
+
+ const time = (event.data.loadTime as number);
+ if (time) {
+ this._mainRequestLoadTime = time;
+ this._columns.addEventDividers([time], 'network-load-divider');
+ }
+ }
+
+ _domContentLoadedEventFired(event: Common.EventTarget.EventTargetEvent): void {
+ if (!this._recording) {
+ return;
+ }
+ const data = (event.data as number);
+ if (data) {
+ this._mainRequestDOMContentLoadedTime = data;
+ this._columns.addEventDividers([data], 'network-dcl-divider');
+ }
+ }
+
+ wasShown(): void {
+ this._refreshIfNeeded();
+ this._columns.wasShown();
+ }
+
+ willHide(): void {
+ this._columns.willHide();
+ }
+
+ onResize(): void {
+ this._rowHeight = this._computeRowHeight();
+ }
+
+ flatNodesList(): NetworkNode[] {
+ const rootNode =
+ (this._dataGrid.rootNode() as
+ DataGrid.ViewportDataGrid.ViewportDataGridNode<DataGrid.SortableDataGrid.SortableDataGridNode<NetworkNode>>);
+ return rootNode.flatChildren() as NetworkNode[];
+ }
+
+ _onDataGridFocus(): void {
+ if (this._dataGrid.element.matches(':focus-visible')) {
+ this.element.classList.add('grid-focused');
+ }
+ this.updateNodeBackground();
+ }
+
+ _onDataGridBlur(): void {
+ this.element.classList.remove('grid-focused');
+ this.updateNodeBackground();
+ }
+
+ updateNodeBackground(): void {
+ if (this._dataGrid.selectedNode) {
+ (this._dataGrid.selectedNode as NetworkNode).updateBackgroundColor();
+ }
+ }
+
+ updateNodeSelectedClass(isSelected: boolean): void {
+ if (isSelected) {
+ this.element.classList.remove('no-node-selected');
+ } else {
+ this.element.classList.add('no-node-selected');
+ }
+ }
+
+ stylesChanged(): void {
+ this._columns.scheduleRefresh();
+ }
+
+ _refresh(): void {
+ 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);
+
+ const nodesToInsert = new Map<NetworkNode, NetworkNode>();
+ const nodesToRefresh: NetworkNode[] = [];
+
+ const staleNodes = new Set<NetworkRequestNode>();
+
+ // 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: NetworkNode|
+ (DataGrid.DataGrid.DataGridNode<DataGrid.ViewportDataGrid.ViewportDataGridNode<
+ DataGrid.SortableDataGrid.SortableDataGridNode<NetworkNode>>>|
+ null) = node.parent;
+ if (!parent) {
+ continue;
+ }
+ parent.removeChild(node);
+ while (parent && !parent.hasChildren() && parent.dataGrid && parent.dataGrid.rootNode() !== parent) {
+ const grandparent = (parent.parent as NetworkNode);
+ grandparent.removeChild(parent);
+ parent = grandparent;
+ }
+ }
+
+ if (!newParent || isFilteredOut) {
+ continue;
+ }
+
+ if (!newParent.dataGrid && !nodesToInsert.has(newParent)) {
+ nodesToInsert.set(newParent, (this._dataGrid.rootNode() as NetworkNode));
+ nodesToRefresh.push(newParent);
+ }
+ nodesToInsert.set(node, newParent);
+ }
+
+ for (const node of nodesToInsert.keys()) {
+ (nodesToInsert.get(node) as NetworkNode).appendChild(node);
+ }
+
+ for (const node of nodesToRefresh) {
+ node.refresh();
+ }
+
+ this._updateSummaryBar();
+
+ if (nodesToInsert.size) {
+ this._columns.sortByCurrentColumn();
+ }
+
+ this._dataGrid.updateInstantly();
+ this._didRefreshForTest();
+ }
+
+ _didRefreshForTest(): void {
+ }
+
+ _parentNodeForInsert(node: NetworkRequestNode): NetworkNode|null {
+ if (!this._activeGroupLookup) {
+ return this._dataGrid.rootNode() as NetworkNode;
+ }
+
+ const groupNode = this._activeGroupLookup.groupNodeForRequest(node.request());
+ if (!groupNode) {
+ return this._dataGrid.rootNode() as NetworkNode;
+ }
+ return groupNode;
+ }
+
+ _reset(): void {
+ this.dispatchEventToListeners(Events.RequestActivated, {showPanel: false});
+
+ this._setHoveredNode(null);
+ this._columns.reset();
+
+ this._timeFilter = null;
+ this._calculator.reset();
+
+ this._timeCalculator.setWindow(null);
+ this._linkifier.reset();
+
+ if (this._activeGroupLookup) {
+ this._activeGroupLookup.reset();
+ }
+ this._staleRequests.clear();
+ this._resetSuggestionBuilder();
+
+ this._mainRequestLoadTime = -1;
+ this._mainRequestDOMContentLoadedTime = -1;
+
+ this._dataGrid.rootNode().removeChildren();
+ this._updateSummaryBar();
+ this._dataGrid.setStickToBottom(true);
+ this.scheduleRefresh();
+ }
+
+ setTextFilterValue(filterString: string): void {
+ this._textFilterUI.setValue(filterString);
+ this._dataURLFilterUI.setChecked(false);
+ this._onlyIssuesFilterUI.setChecked(false);
+ this._onlyBlockedRequestsUI.setChecked(false);
+ this._resourceCategoryFilterUI.reset();
+ }
+
+ _createNodeForRequest(request: SDK.NetworkRequest.NetworkRequest): NetworkRequestNode {
+ const node = new NetworkRequestNode(this, request);
+ networkRequestToNode.set(request, node);
+ filteredNetworkRequests.add(node);
+
+ for (let redirect = request.redirectSource(); redirect; redirect = redirect.redirectSource()) {
+ this._refreshRequest(redirect);
+ }
+ return node;
+ }
+
+ _onRequestUpdated(event: Common.EventTarget.EventTargetEvent): void {
+ const request = (event.data as SDK.NetworkRequest.NetworkRequest);
+ this._refreshRequest(request);
+ }
+
+ _refreshRequest(request: SDK.NetworkRequest.NetworkRequest): void {
+ NetworkLogView._subdomains(request.domain)
+ .forEach(this._suggestionBuilder.addItem.bind(this._suggestionBuilder, FilterType.Domain));
+ this._suggestionBuilder.addItem(FilterType.Method, request.requestMethod);
+ this._suggestionBuilder.addItem(FilterType.MimeType, request.mimeType);
+ this._suggestionBuilder.addItem(FilterType.Scheme, String(request.scheme));
+ this._suggestionBuilder.addItem(FilterType.StatusCode, String(request.statusCode));
+ this._suggestionBuilder.addItem(FilterType.ResourceType, request.resourceType().name());
+ this._suggestionBuilder.addItem(FilterType.Url, request.securityOrigin());
+
+ const priority = request.priority();
+ if (priority) {
+ this._suggestionBuilder.addItem(
+ FilterType.Priority, PerfUI.NetworkPriorities.uiLabelForNetworkPriority(priority));
+ }
+
+ if (request.mixedContentType !== Protocol.Security.MixedContentType.None) {
+ this._suggestionBuilder.addItem(FilterType.MixedContent, MixedContentFilterValues.All);
+ }
+
+ if (request.mixedContentType === Protocol.Security.MixedContentType.OptionallyBlockable) {
+ this._suggestionBuilder.addItem(FilterType.MixedContent, MixedContentFilterValues.Displayed);
+ }
+
+ if (request.mixedContentType === Protocol.Security.MixedContentType.Blockable) {
+ const suggestion =
+ request.wasBlocked() ? MixedContentFilterValues.Blocked : MixedContentFilterValues.BlockOverridden;
+ this._suggestionBuilder.addItem(FilterType.MixedContent, suggestion);
+ }
+
+ const responseHeaders = request.responseHeaders;
+ for (let i = 0, l = responseHeaders.length; i < l; ++i) {
+ this._suggestionBuilder.addItem(FilterType.HasResponseHeader, responseHeaders[i].name);
+ }
+
+ for (const cookie of request.responseCookies) {
+ this._suggestionBuilder.addItem(FilterType.SetCookieDomain, cookie.domain());
+ this._suggestionBuilder.addItem(FilterType.SetCookieName, cookie.name());
+ this._suggestionBuilder.addItem(FilterType.SetCookieValue, cookie.value());
+ }
+
+ for (const cookie of request.allCookiesIncludingBlockedOnes()) {
+ this._suggestionBuilder.addItem(FilterType.CookieDomain, cookie.domain());
+ this._suggestionBuilder.addItem(FilterType.CookieName, cookie.name());
+ this._suggestionBuilder.addItem(FilterType.CookiePath, cookie.path());
+ this._suggestionBuilder.addItem(FilterType.CookieValue, cookie.value());
+ }
+
+ this._staleRequests.add(request);
+ this.scheduleRefresh();
+ }
+
+ rowHeight(): number {
+ return this._rowHeight;
+ }
+
+ switchViewMode(gridMode: boolean): void {
+ this._columns.switchViewMode(gridMode);
+ }
+
+ handleContextMenuForRequest(contextMenu: UI.ContextMenu.ContextMenu, request: SDK.NetworkRequest.NetworkRequest):
+ void {
+ contextMenu.appendApplicableItems(request);
+ let copyMenu = contextMenu.clipboardSection().appendSubMenuItem(i18nString(UIStrings.copy));
+ const footerSection = copyMenu.footerSection();
+ if (request) {
+ copyMenu.defaultSection().appendItem(
+ UI.UIUtils.copyLinkAddressLabel(),
+ Host.InspectorFrontendHost.InspectorFrontendHostInstance.copyText.bind(
+ Host.InspectorFrontendHost.InspectorFrontendHostInstance, request.contentURL()));
+ if (request.requestHeadersText()) {
+ copyMenu.defaultSection().appendItem(
+ i18nString(UIStrings.copyRequestHeaders), NetworkLogView._copyRequestHeaders.bind(null, request));
+ }
+
+ if (request.responseHeadersText) {
+ copyMenu.defaultSection().appendItem(
+ i18nString(UIStrings.copyResponseHeaders), NetworkLogView._copyResponseHeaders.bind(null, request));
+ }
+
+ if (request.finished) {
+ copyMenu.defaultSection().appendItem(
+ i18nString(UIStrings.copyResponse), NetworkLogView._copyResponse.bind(null, request));
+ }
+
+ const initiator = request.initiator();
+
+ if (initiator) {
+ const stack = initiator.stack;
+ if (stack) {
+ // We proactively compute the stacktrace text, as we can't determine whether the stacktrace
+ // has any context solely based on the top frame. Sometimes, the top frame does not have
+ // any callFrames, but its parent frames do.
+ const stackTraceText = computeStackTraceText(stack);
+ if (stackTraceText !== '') {
+ copyMenu.defaultSection().appendItem(i18nString(UIStrings.copyStacktrace), () => {
+ Host.InspectorFrontendHost.InspectorFrontendHostInstance.copyText(stackTraceText);
+ });
+ }
+ }
+ }
+
+ const disableIfBlob = request.isBlobRequest();
+ if (Host.Platform.isWin()) {
+ footerSection.appendItem(
+ i18nString(UIStrings.copyAsPowershell), this._copyPowerShellCommand.bind(this, request), disableIfBlob);
+ footerSection.appendItem(
+ i18nString(UIStrings.copyAsFetch), this._copyFetchCall.bind(this, request, false), disableIfBlob);
+ footerSection.appendItem(
+ i18nString(UIStrings.copyAsNodejsFetch), this._copyFetchCall.bind(this, request, true), disableIfBlob);
+ footerSection.appendItem(
+ i18nString(UIStrings.copyAsCurlCmd), this._copyCurlCommand.bind(this, request, 'win'), disableIfBlob);
+ footerSection.appendItem(
+ i18nString(UIStrings.copyAsCurlBash), this._copyCurlCommand.bind(this, request, 'unix'), disableIfBlob);
+ footerSection.appendItem(i18nString(UIStrings.copyAllAsPowershell), this._copyAllPowerShellCommand.bind(this));
+ footerSection.appendItem(i18nString(UIStrings.copyAllAsFetch), this._copyAllFetchCall.bind(this, false));
+ footerSection.appendItem(i18nString(UIStrings.copyAllAsNodejsFetch), this._copyAllFetchCall.bind(this, true));
+ footerSection.appendItem(i18nString(UIStrings.copyAllAsCurlCmd), this._copyAllCurlCommand.bind(this, 'win'));
+ footerSection.appendItem(i18nString(UIStrings.copyAllAsCurlBash), this._copyAllCurlCommand.bind(this, 'unix'));
+ } else {
+ footerSection.appendItem(
+ i18nString(UIStrings.copyAsFetch), this._copyFetchCall.bind(this, request, false), disableIfBlob);
+ footerSection.appendItem(
+ i18nString(UIStrings.copyAsNodejsFetch), this._copyFetchCall.bind(this, request, true), disableIfBlob);
+ footerSection.appendItem(
+ i18nString(UIStrings.copyAsCurl), this._copyCurlCommand.bind(this, request, 'unix'), disableIfBlob);
+ footerSection.appendItem(i18nString(UIStrings.copyAllAsFetch), this._copyAllFetchCall.bind(this, false));
+ footerSection.appendItem(i18nString(UIStrings.copyAllAsNodejsFetch), this._copyAllFetchCall.bind(this, true));
+ footerSection.appendItem(i18nString(UIStrings.copyAllAsCurl), this._copyAllCurlCommand.bind(this, 'unix'));
+ }
+ } else {
+ copyMenu = contextMenu.clipboardSection().appendSubMenuItem(i18nString(UIStrings.copy));
+ }
+ footerSection.appendItem(i18nString(UIStrings.copyAllAsHar), this._copyAll.bind(this));
+
+ contextMenu.saveSection().appendItem(i18nString(UIStrings.saveAllAsHarWithContent), this.exportAll.bind(this));
+
+ contextMenu.editSection().appendItem(i18nString(UIStrings.clearBrowserCache), this._clearBrowserCache.bind(this));
+ contextMenu.editSection().appendItem(
+ i18nString(UIStrings.clearBrowserCookies), this._clearBrowserCookies.bind(this));
+
+ if (request) {
+ const maxBlockedURLLength = 20;
+ const manager = SDK.NetworkManager.MultitargetNetworkManager.instance();
+ let patterns = manager.blockedPatterns();
+
+ function addBlockedURL(url: string): void {
+ patterns.push({enabled: true, url: url});
+ manager.setBlockedPatterns(patterns);
+ manager.setBlockingEnabled(true);
+ UI.ViewManager.ViewManager.instance().showView('network.blocked-urls');
+ }
+
+ function removeBlockedURL(url: string): void {
+ patterns = patterns.filter(pattern => pattern.url !== url);
+ manager.setBlockedPatterns(patterns);
+ UI.ViewManager.ViewManager.instance().showView('network.blocked-urls');
+ }
+
+ const urlWithoutScheme = request.parsedURL.urlWithoutScheme();
+ if (urlWithoutScheme && !patterns.find(pattern => pattern.url === urlWithoutScheme)) {
+ contextMenu.debugSection().appendItem(
+ i18nString(UIStrings.blockRequestUrl), addBlockedURL.bind(null, urlWithoutScheme));
+ } else if (urlWithoutScheme) {
+ const croppedURL = Platform.StringUtilities.trimMiddle(urlWithoutScheme, maxBlockedURLLength);
+ contextMenu.debugSection().appendItem(
+ i18nString(UIStrings.unblockS, {PH1: croppedURL}), removeBlockedURL.bind(null, urlWithoutScheme));
+ }
+
+ const domain = request.parsedURL.domain();
+ if (domain && !patterns.find(pattern => pattern.url === domain)) {
+ contextMenu.debugSection().appendItem(
+ i18nString(UIStrings.blockRequestDomain), addBlockedURL.bind(null, domain));
+ } else if (domain) {
+ const croppedDomain = Platform.StringUtilities.trimMiddle(domain, maxBlockedURLLength);
+ contextMenu.debugSection().appendItem(
+ i18nString(UIStrings.unblockS, {PH1: croppedDomain}), removeBlockedURL.bind(null, domain));
+ }
+
+ if (SDK.NetworkManager.NetworkManager.canReplayRequest(request)) {
+ contextMenu.debugSection().appendItem(
+ i18nString(UIStrings.replayXhr), SDK.NetworkManager.NetworkManager.replayRequest.bind(null, request));
+ }
+ }
+ }
+
+ _harRequests(): SDK.NetworkRequest.NetworkRequest[] {
+ return SDK.NetworkLog.NetworkLog.instance().requests().filter(NetworkLogView.HTTPRequestsFilter).filter(request => {
+ return request.finished ||
+ (request.resourceType() === Common.ResourceType.resourceTypes.WebSocket && request.responseReceivedTime);
+ });
+ }
+
+ async _copyAll(): Promise<void> {
+ const harArchive = {log: await SDK.HARLog.HARLog.build(this._harRequests())};
+ Host.InspectorFrontendHost.InspectorFrontendHostInstance.copyText(JSON.stringify(harArchive, null, 2));
+ }
+
+ async _copyCurlCommand(request: SDK.NetworkRequest.NetworkRequest, platform: string): Promise<void> {
+ const command = await this._generateCurlCommand(request, platform);
+ Host.InspectorFrontendHost.InspectorFrontendHostInstance.copyText(command);
+ }
+
+ async _copyAllCurlCommand(platform: string): Promise<void> {
+ const commands = await this._generateAllCurlCommand(SDK.NetworkLog.NetworkLog.instance().requests(), platform);
+ Host.InspectorFrontendHost.InspectorFrontendHostInstance.copyText(commands);
+ }
+
+ async _copyFetchCall(request: SDK.NetworkRequest.NetworkRequest, includeCookies: boolean): Promise<void> {
+ const command = await this._generateFetchCall(request, includeCookies);
+ Host.InspectorFrontendHost.InspectorFrontendHostInstance.copyText(command);
+ }
+
+ async _copyAllFetchCall(includeCookies: boolean): Promise<void> {
+ const commands = await this._generateAllFetchCall(SDK.NetworkLog.NetworkLog.instance().requests(), includeCookies);
+ Host.InspectorFrontendHost.InspectorFrontendHostInstance.copyText(commands);
+ }
+
+ async _copyPowerShellCommand(request: SDK.NetworkRequest.NetworkRequest): Promise<void> {
+ const command = await this._generatePowerShellCommand(request);
+ Host.InspectorFrontendHost.InspectorFrontendHostInstance.copyText(command);
+ }
+
+ async _copyAllPowerShellCommand(): Promise<void> {
+ const commands = await this._generateAllPowerShellCommand(SDK.NetworkLog.NetworkLog.instance().requests());
+ Host.InspectorFrontendHost.InspectorFrontendHostInstance.copyText(commands);
+ }
+
+ async exportAll(): Promise<void> {
+ const mainTarget = SDK.SDKModel.TargetManager.instance().mainTarget();
+ if (!mainTarget) {
+ return;
+ }
+ const url = mainTarget.inspectedURL();
+ const parsedURL = Common.ParsedURL.ParsedURL.fromString(url);
+ const filename = parsedURL ? parsedURL.host : 'network-log';
+ const stream = new Bindings.FileUtils.FileOutputStream();
+
+ if (!await stream.open(filename + '.har')) {
+ return;
+ }
+
+ const progressIndicator = new UI.ProgressIndicator.ProgressIndicator();
+ this._progressBarContainer.appendChild(progressIndicator.element);
+ await HARWriter.write(stream, this._harRequests(), progressIndicator);
+ progressIndicator.done();
+ stream.close();
+ }
+
+ _clearBrowserCache(): void {
+ if (confirm(i18nString(UIStrings.areYouSureYouWantToClearBrowser))) {
+ SDK.NetworkManager.MultitargetNetworkManager.instance().clearBrowserCache();
+ }
+ }
+
+ _clearBrowserCookies(): void {
+ if (confirm(i18nString(UIStrings.areYouSureYouWantToClearBrowserCookies))) {
+ SDK.NetworkManager.MultitargetNetworkManager.instance().clearBrowserCookies();
+ }
+ }
+
+ _removeAllHighlights(): void {
+ this.removeAllNodeHighlights();
+ for (let i = 0; i < this._highlightedSubstringChanges.length; ++i) {
+ UI.UIUtils.revertDomChanges(this._highlightedSubstringChanges[i]);
+ }
+ this._highlightedSubstringChanges = [];
+ }
+
+ _applyFilter(node: NetworkRequestNode): boolean {
+ const request = node.request();
+ if (this._timeFilter && !this._timeFilter(request)) {
+ return false;
+ }
+ const categoryName = request.resourceType().category().title();
+ if (!this._resourceCategoryFilterUI.accept(categoryName)) {
+ return false;
+ }
+ if (this._dataURLFilterUI.checked() && (request.parsedURL.isDataURL() || request.parsedURL.isBlobURL())) {
+ return false;
+ }
+ if (this._onlyIssuesFilterUI.checked() &&
+ !BrowserSDK.RelatedIssue.hasIssueOfCategory(request, SDK.Issue.IssueCategory.SameSiteCookie)) {
+ return false;
+ }
+ if (this._onlyBlockedRequestsUI.checked() && !request.wasBlocked() && !request.corsErrorStatus()) {
+ return false;
+ }
+ if (request.statusText === 'Service Worker Fallback Required') {
+ return false;
+ }
+ for (let i = 0; i < this._filters.length; ++i) {
+ if (!this._filters[i](request)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ _parseFilterQuery(query: string): void {
+ const descriptors = this._filterParser.parse(query);
+ this._filters = descriptors.map(descriptor => {
+ const key = descriptor.key;
+ const text = descriptor.text || '';
+ const regex = descriptor.regex;
+ let filter;
+ if (key) {
+ const defaultText = Platform.StringUtilities.escapeForRegExp(key + ':' + text);
+ filter = this._createSpecialFilter((key as FilterType), text) ||
+ NetworkLogView._requestPathFilter.bind(null, new RegExp(defaultText, 'i'));
+ } else if (descriptor.regex) {
+ filter = NetworkLogView._requestPathFilter.bind(null, (regex as RegExp));
+ } else {
+ filter = NetworkLogView._requestPathFilter.bind(
+ null, new RegExp(Platform.StringUtilities.escapeForRegExp(text), 'i'));
+ }
+ return descriptor.negative ? NetworkLogView._negativeFilter.bind(null, filter) : filter;
+ });
+ }
+
+ _createSpecialFilter(type: FilterType, value: string): Filter|null {
+ switch (type) {
+ case FilterType.Domain:
+ return NetworkLogView._createRequestDomainFilter(value);
+
+ case FilterType.HasResponseHeader:
+ return NetworkLogView._requestResponseHeaderFilter.bind(null, value);
+
+ case FilterType.Is:
+ if (value.toLowerCase() === IsFilterType.Running) {
+ return NetworkLogView._runningRequestFilter;
+ }
+ if (value.toLowerCase() === IsFilterType.FromCache) {
+ return NetworkLogView._fromCacheRequestFilter;
+ }
+ if (value.toLowerCase() === IsFilterType.ServiceWorkerIntercepted) {
+ return NetworkLogView._interceptedByServiceWorkerFilter;
+ }
+ if (value.toLowerCase() === IsFilterType.ServiceWorkerInitiated) {
+ return NetworkLogView._initiatedByServiceWorkerFilter;
+ }
+ break;
+
+ case FilterType.LargerThan:
+ return this._createSizeFilter(value.toLowerCase());
+
+ case FilterType.Method:
+ return NetworkLogView._requestMethodFilter.bind(null, value);
+
+ case FilterType.MimeType:
+ return NetworkLogView._requestMimeTypeFilter.bind(null, value);
+
+ case FilterType.MixedContent:
+ return NetworkLogView._requestMixedContentFilter.bind(null, (value as MixedContentFilterValues));
+
+ case FilterType.Scheme:
+ return NetworkLogView._requestSchemeFilter.bind(null, value);
+
+ case FilterType.SetCookieDomain:
+ return NetworkLogView._requestSetCookieDomainFilter.bind(null, value);
+
+ case FilterType.SetCookieName:
+ return NetworkLogView._requestSetCookieNameFilter.bind(null, value);
+
+ case FilterType.SetCookieValue:
+ return NetworkLogView._requestSetCookieValueFilter.bind(null, value);
+
+ case FilterType.CookieDomain:
+ return NetworkLogView._requestCookieDomainFilter.bind(null, value);
+
+ case FilterType.CookieName:
+ return NetworkLogView._requestCookieNameFilter.bind(null, value);
+
+ case FilterType.CookiePath:
+ return NetworkLogView._requestCookiePathFilter.bind(null, value);
+
+ case FilterType.CookieValue:
+ return NetworkLogView._requestCookieValueFilter.bind(null, value);
+
+ case FilterType.Priority:
+ return NetworkLogView._requestPriorityFilter.bind(
+ null, PerfUI.NetworkPriorities.uiLabelToNetworkPriority(value));
+
+ case FilterType.StatusCode:
+ return NetworkLogView._statusCodeFilter.bind(null, value);
+
+ case FilterType.ResourceType:
+ return NetworkLogView._resourceTypeFilter.bind(null, value);
+
+ case FilterType.Url:
+ return NetworkLogView._requestUrlFilter.bind(null, value);
+ }
+ return null;
+ }
+
+ _createSizeFilter(value: string): Filter|null {
+ let multiplier = 1;
+ if (value.endsWith('k')) {
+ multiplier = 1000;
+ value = value.substring(0, value.length - 1);
+ } else if (value.endsWith('m')) {
+ multiplier = 1000 * 1000;
+ value = value.substring(0, value.length - 1);
+ }
+ const quantity = Number(value);
+ if (isNaN(quantity)) {
+ return null;
+ }
+ return NetworkLogView._requestSizeLargerThanFilter.bind(null, quantity * multiplier);
+ }
+
+ _filterRequests(): void {
+ this._removeAllHighlights();
+ this._invalidateAllItems();
+ }
+
+ _reveal(request: SDK.NetworkRequest.NetworkRequest): NetworkRequestNode|null {
+ this.removeAllNodeHighlights();
+ const node = networkRequestToNode.get(request);
+ if (!node || !node.dataGrid) {
+ return null;
+ }
+ // Viewport datagrid nodes do not reveal if not in the root node
+ // list of flatChildren. For children of grouped frame nodes:
+ // reveal and expand parent to ensure child is revealable.
+ if (node.parent && node.parent instanceof NetworkGroupNode) {
+ node.parent.reveal();
+ node.parent.expand();
+ }
+ node.reveal();
+ return node;
+ }
+
+ revealAndHighlightRequest(request: SDK.NetworkRequest.NetworkRequest): void {
+ const node = this._reveal(request);
+ if (node) {
+ this._highlightNode(node);
+ }
+ }
+
+ selectRequest(request: SDK.NetworkRequest.NetworkRequest, options?: FilterOptions): void {
+ const defaultOptions = {clearFilter: true};
+ const {clearFilter} = options || defaultOptions;
+ if (clearFilter) {
+ this.setTextFilterValue('');
+ }
+ const node = this._reveal(request);
+ if (node) {
+ node.select();
+ }
+ }
+
+ removeAllNodeHighlights(): void {
+ if (this._highlightedNode) {
+ this._highlightedNode.element().classList.remove('highlighted-row');
+ this._highlightedNode = null;
+ }
+ }
+
+ _highlightNode(node: NetworkRequestNode): void {
+ UI.UIUtils.runCSSAnimationOnce(node.element(), 'highlighted-row');
+ this._highlightedNode = node;
+ }
+
+ _filterOutBlobRequests(requests: SDK.NetworkRequest.NetworkRequest[]): SDK.NetworkRequest.NetworkRequest[] {
+ return requests.filter(request => !request.isBlobRequest());
+ }
+
+ async _generateFetchCall(request: SDK.NetworkRequest.NetworkRequest, includeCookies: boolean): Promise<string> {
+ const ignoredHeaders = new Set<string>([
+ // Internal headers
+ 'method',
+ 'path',
+ 'scheme',
+ 'version',
+
+ // Unsafe headers
+ // Keep this list synchronized with src/net/http/http_util.cc
+ 'accept-charset',
+ 'accept-encoding',
+ 'access-control-request-headers',
+ 'access-control-request-method',
+ 'connection',
+ 'content-length',
+ 'cookie',
+ 'cookie2',
+ 'date',
+ 'dnt',
+ 'expect',
+ 'host',
+ 'keep-alive',
+ 'origin',
+ 'referer',
+ 'te',
+ 'trailer',
+ 'transfer-encoding',
+ 'upgrade',
+ 'via',
+ // TODO(phistuck) - remove this once crbug.com/571722 is fixed.
+ 'user-agent',
+ ]);
+
+ const credentialHeaders = new Set<string>(['cookie', 'authorization']);
+
+ const url = JSON.stringify(request.url());
+
+ const requestHeaders = request.requestHeaders();
+ const headerData: Headers = requestHeaders.reduce((result, header) => {
+ const name = header.name;
+
+ if (!ignoredHeaders.has(name.toLowerCase()) && !name.includes(':')) {
+ result.append(name, header.value);
+ }
+
+ return result;
+ }, new Headers());
+
+ const headers: HeadersInit = {};
+ for (const headerArray of headerData) {
+ headers[headerArray[0]] = headerArray[1];
+ }
+
+ const credentials = request.includedRequestCookies().length ||
+ requestHeaders.some(({name}) => credentialHeaders.has(name.toLowerCase())) ?
+ 'include' :
+ 'omit';
+
+ const referrerHeader = requestHeaders.find(({name}) => name.toLowerCase() === 'referer');
+
+ const referrer = referrerHeader ? referrerHeader.value : void 0;
+
+ const referrerPolicy = request.referrerPolicy() || void 0;
+
+ const requestBody = await request.requestFormData();
+
+ const fetchOptions: RequestInit = {
+ headers: Object.keys(headers).length ? headers : void 0,
+ referrer,
+ referrerPolicy,
+ body: requestBody,
+ method: request.requestMethod,
+ mode: 'cors',
+ };
+
+ if (includeCookies) {
+ const cookieHeader = requestHeaders.find(header => header.name.toLowerCase() === 'cookie');
+ if (cookieHeader) {
+ fetchOptions.headers = {
+ ...headers,
+ 'cookie': cookieHeader.value,
+ };
+ }
+ } else {
+ fetchOptions.credentials = credentials;
+ }
+
+ const options = JSON.stringify(fetchOptions, null, 2);
+ return `fetch(${url}, ${options});`;
+ }
+
+ async _generateAllFetchCall(requests: SDK.NetworkRequest.NetworkRequest[], includeCookies: boolean): Promise<string> {
+ const nonBlobRequests = this._filterOutBlobRequests(requests);
+ const commands =
+ await Promise.all(nonBlobRequests.map(request => this._generateFetchCall(request, includeCookies)));
+ return commands.join(' ;\n');
+ }
+
+ async _generateCurlCommand(request: SDK.NetworkRequest.NetworkRequest, platform: string): Promise<string> {
+ let command: string[] = [];
+ // Most of these headers are derived from the URL and are automatically added by cURL.
+ // The |Accept-Encoding| header is ignored to prevent decompression errors. crbug.com/1015321
+ const ignoredHeaders = new Set<string>(['accept-encoding', 'host', 'method', 'path', 'scheme', 'version']);
+
+ function escapeStringWin(str: string): string {
+ /* If there are no new line characters do not escape the " characters
+ since it only uglifies the command.
+
+ Because cmd.exe parser and MS Crt arguments parsers use some of the
+ same escape characters, they can interact with each other in
+ horrible ways, the order of operations is critical.
+
+ Replace \ with \\ first because it is an escape character for certain
+ conditions in both parsers.
+
+ Replace all " with \" to ensure the first parser does not remove it.
+
+ Then escape all characters we are not sure about with ^ to ensure it
+ gets to MS Crt parser safely.
+
+ The % character is special because MS Crt parser will try and look for
+ ENV variables and fill them in it's place. We cannot escape them with %
+ and cannot escape them with ^ (because it's cmd.exe's escape not MS Crt
+ parser); So we can get cmd.exe parser to escape the character after it,
+ if it is followed by a valid beginning character of an ENV variable.
+ This ensures we do not try and double escape another ^ if it was placed
+ by the previous replace.
+
+ Lastly we replace new lines with ^ and TWO new lines because the first
+ new line is there to enact the escape command the second is the character
+ to escape (in this case new line).
+ */
+ const encapsChars = /[\r\n]/.test(str) ? '^"' : '"';
+ return encapsChars +
+ str.replace(/\\/g, '\\\\')
+ .replace(/"/g, '\\"')
+ .replace(/[^a-zA-Z0-9\s_\-:=+~'\/.',?;()*`&]/g, '^$&')
+ .replace(/%(?=[a-zA-Z0-9_])/g, '%^')
+ .replace(/\r?\n/g, '^\n\n') +
+ encapsChars;
+ }
+
+ function escapeStringPosix(str: string): string {
+ function escapeCharacter(x: string): string {
+ const code = x.charCodeAt(0);
+ let hexString = code.toString(16);
+ // Zero pad to four digits to comply with ANSI-C Quoting:
+ // http://www.gnu.org/software/bash/manual/html_node/ANSI_002dC-Quoting.html
+ while (hexString.length < 4) {
+ hexString = '0' + hexString;
+ }
+
+ return '\\u' + hexString;
+ }
+
+ if (/[\0-\x1F\x7F-\x9F!]|\'/.test(str)) {
+ // Use ANSI-C quoting syntax.
+ return '$\'' +
+ str.replace(/\\/g, '\\\\')
+ .replace(/\'/g, '\\\'')
+ .replace(/\n/g, '\\n')
+ .replace(/\r/g, '\\r')
+ .replace(/[\0-\x1F\x7F-\x9F!]/g, escapeCharacter) +
+ '\'';
+ }
+ // Use single quote syntax.
+ return '\'' + str + '\'';
+ }
+
+ // cURL command expected to run on the same platform that DevTools run
+ // (it may be different from the inspected page platform).
+ const escapeString = platform === 'win' ? escapeStringWin : escapeStringPosix;
+
+ command.push(escapeString(request.url()).replace(/[[{}\]]/g, '\\$&'));
+
+ let inferredMethod = 'GET';
+ const data = [];
+ const requestContentType = request.requestContentType();
+ const formData = await request.requestFormData();
+ if (requestContentType && requestContentType.startsWith('application/x-www-form-urlencoded') && formData) {
+ // Note that formData is not necessarily urlencoded because it might for example
+ // come from a fetch request made with an explicitly unencoded body.
+ data.push('--data-raw ' + escapeString(formData));
+ ignoredHeaders.add('content-length');
+ inferredMethod = 'POST';
+ } else if (formData) {
+ data.push('--data-raw ' + escapeString(formData));
+ ignoredHeaders.add('content-length');
+ inferredMethod = 'POST';
+ }
+
+ if (request.requestMethod !== inferredMethod) {
+ command.push('-X ' + escapeString(request.requestMethod));
+ }
+
+ const requestHeaders = request.requestHeaders();
+ for (let i = 0; i < requestHeaders.length; i++) {
+ const header = requestHeaders[i];
+ const name = header.name.replace(/^:/, ''); // Translate SPDY v3 headers to HTTP headers.
+ if (ignoredHeaders.has(name.toLowerCase())) {
+ continue;
+ }
+ command.push('-H ' + escapeString(name + ': ' + header.value));
+ }
+ command = command.concat(data);
+ command.push('--compressed');
+
+ if (request.securityState() === Protocol.Security.SecurityState.Insecure) {
+ command.push('--insecure');
+ }
+ return 'curl ' + command.join(command.length >= 3 ? (platform === 'win' ? ' ^\n ' : ' \\\n ') : ' ');
+ }
+
+ async _generateAllCurlCommand(requests: SDK.NetworkRequest.NetworkRequest[], platform: string): Promise<string> {
+ const nonBlobRequests = this._filterOutBlobRequests(requests);
+ const commands = await Promise.all(nonBlobRequests.map(request => this._generateCurlCommand(request, platform)));
+ if (platform === 'win') {
+ return commands.join(' &\r\n');
+ }
+ return commands.join(' ;\n');
+ }
+
+ async _generatePowerShellCommand(request: SDK.NetworkRequest.NetworkRequest): Promise<string> {
+ const command = [];
+ const ignoredHeaders = new Set<string>(
+ ['host', 'connection', 'proxy-connection', 'content-length', 'expect', 'range', 'content-type']);
+
+ function escapeString(str: string): string {
+ return '"' +
+ str.replace(/[`\$"]/g, '`$&').replace(/[^\x20-\x7E]/g, char => '$([char]' + char.charCodeAt(0) + ')') + '"';
+ }
+
+ command.push('-Uri ' + escapeString(request.url()));
+
+ if (request.requestMethod !== 'GET') {
+ command.push('-Method ' + escapeString(request.requestMethod));
+ }
+
+ const requestHeaders = request.requestHeaders();
+ const headerNameValuePairs = [];
+ for (const header of requestHeaders) {
+ const name = header.name.replace(/^:/, ''); // Translate h2 headers to HTTP headers.
+ if (ignoredHeaders.has(name.toLowerCase())) {
+ continue;
+ }
+ headerNameValuePairs.push(escapeString(name) + '=' + escapeString(header.value));
+ }
+ if (headerNameValuePairs.length) {
+ command.push('-Headers @{\n' + headerNameValuePairs.join('\n ') + '\n}');
+ }
+
+ const contentTypeHeader = requestHeaders.find(({name}) => name.toLowerCase() === 'content-type');
+ if (contentTypeHeader) {
+ command.push('-ContentType ' + escapeString(contentTypeHeader.value));
+ }
+
+ const formData = await request.requestFormData();
+ if (formData) {
+ const body = escapeString(formData);
+ if (/[^\x20-\x7E]/.test(formData)) {
+ command.push('-Body ([System.Text.Encoding]::UTF8.GetBytes(' + body + '))');
+ } else {
+ command.push('-Body ' + body);
+ }
+ }
+
+ return 'Invoke-WebRequest ' + command.join(command.length >= 3 ? ' `\n' : ' ');
+ }
+
+ async _generateAllPowerShellCommand(requests: SDK.NetworkRequest.NetworkRequest[]): Promise<string> {
+ const nonBlobRequests = this._filterOutBlobRequests(requests);
+ const commands = await Promise.all(nonBlobRequests.map(request => this._generatePowerShellCommand(request)));
+ return commands.join(';\r\n');
+ }
+
+ static getDCLEventColor(): string {
+ if (ThemeSupport.ThemeSupport.instance().themeName() === 'dark') {
+ return '#03A9F4';
+ }
+ return '#0867CB';
+ }
+
+ static getLoadEventColor(): string {
+ return ThemeSupport.ThemeSupport.instance().patchColorText(
+ '#B31412', ThemeSupport.ThemeSupport.ColorUsage.Foreground);
+ }
+}
+
+export function computeStackTraceText(stackTrace: Protocol.Runtime.StackTrace): string {
+ let stackTraceText = '';
+ for (const frame of stackTrace.callFrames) {
+ const functionName = UI.UIUtils.beautifyFunctionName(frame.functionName);
+ stackTraceText += `${functionName} @ ${frame.url}:${frame.lineNumber + 1}\n`;
+ }
+ if (stackTrace.parent) {
+ stackTraceText += computeStackTraceText(stackTrace.parent);
+ }
+ return stackTraceText;
+}
+
+const filteredNetworkRequests = new WeakSet<NetworkRequestNode>();
+const networkRequestToNode = new WeakMap<SDK.NetworkRequest.NetworkRequest, NetworkRequestNode>();
+
+export function isRequestFilteredOut(request: NetworkRequestNode): boolean {
+ return filteredNetworkRequests.has(request);
+}
+
+export const HTTPSchemas = {
+ 'http': true,
+ 'https': true,
+ 'ws': true,
+ 'wss': true,
+};
+
+// TODO(crbug.com/1167717): Make this a const enum again
+// eslint-disable-next-line rulesdir/const_enum
+export enum FilterType {
+ Domain = 'domain',
+ HasResponseHeader = 'has-response-header',
+ Is = 'is',
+ LargerThan = 'larger-than',
+ Method = 'method',
+ MimeType = 'mime-type',
+ MixedContent = 'mixed-content',
+ Priority = 'priority',
+ Scheme = 'scheme',
+ SetCookieDomain = 'set-cookie-domain',
+ SetCookieName = 'set-cookie-name',
+ SetCookieValue = 'set-cookie-value',
+ ResourceType = 'resource-type',
+ CookieDomain = 'cookie-domain',
+ CookieName = 'cookie-name',
+ CookiePath = 'cookie-path',
+ CookieValue = 'cookie-value',
+ StatusCode = 'status-code',
+ Url = 'url',
+}
+
+// TODO(crbug.com/1167717): Make this a const enum again
+// eslint-disable-next-line rulesdir/const_enum
+export enum MixedContentFilterValues {
+ All = 'all',
+ Displayed = 'displayed',
+ Blocked = 'blocked',
+ BlockOverridden = 'block-overridden',
+}
+
+// TODO(crbug.com/1167717): Make this a const enum again
+// eslint-disable-next-line rulesdir/const_enum
+export enum IsFilterType {
+ Running = 'running',
+ FromCache = 'from-cache',
+ ServiceWorkerIntercepted = 'service-worker-intercepted',
+ ServiceWorkerInitiated = 'service-worker-initiated',
+}
+
+// TODO(crbug.com/1172300) Ignored during the jsdoc to ts migration)
+// eslint-disable-next-line @typescript-eslint/naming-convention
+export const _searchKeys: string[] = Object.values(FilterType);
+
+export interface GroupLookupInterface {
+ groupNodeForRequest(request: SDK.NetworkRequest.NetworkRequest): NetworkGroupNode|null;
+ reset(): void;
+}
+
+export type Filter = (request: SDK.NetworkRequest.NetworkRequest) => boolean;