chrome-devtools-frontend
Version:
Chrome DevTools UI
1,184 lines (1,061 loc) • 97.3 kB
text/typescript
// 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 <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 Common from '../../core/common/common.js';
import * as Host from '../../core/host/host.js';
import * as i18n from '../../core/i18n/i18n.js';
import * as Platform from '../../core/platform/platform.js';
import * as Root from '../../core/root/root.js';
import * as SDK from '../../core/sdk/sdk.js';
import * as Protocol from '../../generated/protocol.js';
import * as Bindings from '../../models/bindings/bindings.js';
import * as HAR from '../../models/har/har.js';
import * as IssuesManager from '../../models/issues_manager/issues_manager.js';
import * as Logs from '../../models/logs/logs.js';
import * as Persistence from '../../models/persistence/persistence.js';
import * as TextUtils from '../../models/text_utils/text_utils.js';
import * as NetworkForward from '../../panels/network/forward/forward.js';
import * as Sources from '../../panels/sources/sources.js';
import * as Coordinator from '../../ui/components/render_coordinator/render_coordinator.js';
import * as DataGrid from '../../ui/legacy/components/data_grid/data_grid.js';
import * as PerfUI from '../../ui/legacy/components/perf_ui/perf_ui.js';
import * as Components from '../../ui/legacy/components/utils/utils.js';
import * as UI from '../../ui/legacy/legacy.js';
import networkLogViewStyles from './networkLogView.css.js';
import {
Events,
NetworkGroupNode,
NetworkRequestNode,
type NetworkLogViewInterface,
type NetworkNode,
type EventTypes,
} from './NetworkDataGridNode.js';
import {NetworkFrameGrouper} from './NetworkFrameGrouper.js';
import {NetworkLogViewColumns} from './NetworkLogViewColumns.js';
import {
NetworkTimeBoundary,
NetworkTransferDurationCalculator,
NetworkTransferTimeCalculator,
type NetworkTimeCalculator,
} from './NetworkTimeCalculator.js';
const UIStrings = {
/**
*@description Text in Network Log View of the Network panel
*/
invertFilter: 'Invert',
/**
*@description Tooltip for the 'invert' checkbox in the Network panel.
*/
invertsFilter: 'Inverts the search filter',
/**
*@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 Label for a filter in the Network panel
*/
thirdParty: '3rd-party requests',
/**
*@description Tooltip for a filter in the Network panel
*/
onlyShowThirdPartyRequests: 'Shows only requests with origin different from page origin',
/**
*@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
*/
recordToDisplayNetworkActivity: 'Record network log ({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?',
/**
*@description A context menu item in the Network Log View of the Network panel
* for creating a header override
*/
overrideHeaders: 'Override headers',
};
const str_ = i18n.i18n.registerUIStrings('panels/network/NetworkLogView.ts', UIStrings);
const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
const enum FetchStyle {
Browser = 0,
NodeJs = 1,
}
const coordinator = Coordinator.RenderCoordinator.RenderCoordinator.instance();
export class NetworkLogView extends Common.ObjectWrapper.eventMixin<EventTypes, typeof UI.Widget.VBox>(UI.Widget.VBox)
implements SDK.TargetManager.SDKModelObserver<SDK.NetworkManager.NetworkManager>, NetworkLogViewInterface {
private readonly networkInvertFilterSetting: Common.Settings.Setting<boolean>;
private readonly networkHideDataURLSetting: Common.Settings.Setting<boolean>;
private readonly networkShowIssuesOnlySetting: Common.Settings.Setting<boolean>;
private readonly networkOnlyBlockedRequestsSetting: Common.Settings.Setting<boolean>;
private readonly networkOnlyThirdPartySetting: Common.Settings.Setting<boolean>;
private readonly networkResourceTypeFiltersSetting: Common.Settings.Setting<{[key: string]: boolean}>;
private rawRowHeight: number;
private readonly progressBarContainer: Element;
private readonly networkLogLargeRowsSetting: Common.Settings.Setting<boolean>;
private rowHeightInternal: number;
private readonly timeCalculatorInternal: NetworkTransferTimeCalculator;
private readonly durationCalculator: NetworkTransferDurationCalculator;
private calculatorInternal: NetworkTransferTimeCalculator;
private readonly columnsInternal: NetworkLogViewColumns;
private staleRequests: Set<SDK.NetworkRequest.NetworkRequest>;
private mainRequestLoadTime: number;
private mainRequestDOMContentLoadedTime: number;
private filters: Filter[];
private timeFilter: Filter|null;
private hoveredNodeInternal: NetworkNode|null;
private recordingHint: Element|null;
private highlightedNode: NetworkRequestNode|null;
private readonly linkifierInternal: Components.Linkifier.Linkifier;
private recording: boolean;
private needsRefresh: boolean;
private readonly headerHeightInternal: number;
private readonly groupLookups: Map<string, GroupLookupInterface>;
private activeGroupLookup: GroupLookupInterface|null;
private readonly textFilterUI: UI.FilterBar.TextFilterUI;
private readonly invertFilterUI: UI.FilterBar.CheckboxFilterUI;
private readonly dataURLFilterUI: UI.FilterBar.CheckboxFilterUI;
private resourceCategoryFilterUI: UI.FilterBar.NamedBitSetFilterUI;
private readonly onlyIssuesFilterUI: UI.FilterBar.CheckboxFilterUI;
private readonly onlyBlockedRequestsUI: UI.FilterBar.CheckboxFilterUI;
private readonly onlyThirdPartyFilterUI: UI.FilterBar.CheckboxFilterUI;
private readonly filterParser: TextUtils.TextUtils.FilterParser;
private readonly suggestionBuilder: UI.FilterSuggestionBuilder.FilterSuggestionBuilder;
private dataGrid: DataGrid.SortableDataGrid.SortableDataGrid<NetworkNode>;
private readonly summaryToolbarInternal: UI.Toolbar.Toolbar;
private readonly filterBar: UI.FilterBar.FilterBar;
private readonly textFilterSetting: Common.Settings.Setting<string>;
constructor(
filterBar: UI.FilterBar.FilterBar, progressBarContainer: Element,
networkLogLargeRowsSetting: Common.Settings.Setting<boolean>) {
super();
this.setMinimumSize(50, 64);
this.element.id = 'network-container';
this.element.classList.add('no-node-selected');
this.networkInvertFilterSetting = Common.Settings.Settings.instance().createSetting('networkInvertFilter', false);
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.networkOnlyThirdPartySetting =
Common.Settings.Settings.instance().createSetting('networkOnlyThirdPartySetting', 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.rowHeightInternal = this.computeRowHeight();
}
this.rawRowHeight = 0;
this.rowHeightInternal = 0;
updateRowHeight.call(this);
this.timeCalculatorInternal = new NetworkTransferTimeCalculator();
this.durationCalculator = new NetworkTransferDurationCalculator();
this.calculatorInternal = this.timeCalculatorInternal;
this.columnsInternal = new NetworkLogViewColumns(
this, this.timeCalculatorInternal, this.durationCalculator, networkLogLargeRowsSetting);
this.columnsInternal.show(this.element);
this.staleRequests = new Set();
this.mainRequestLoadTime = -1;
this.mainRequestDOMContentLoadedTime = -1;
this.filters = [];
this.timeFilter = null;
this.hoveredNodeInternal = null;
this.recordingHint = null;
this.highlightedNode = null;
this.linkifierInternal = new Components.Linkifier.Linkifier();
this.recording = false;
this.needsRefresh = false;
this.headerHeightInternal = 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.FilterUIEvents.FilterChanged, this.filterChanged, this);
filterBar.addFilter(this.textFilterUI);
this.invertFilterUI = new UI.FilterBar.CheckboxFilterUI(
'invert-filter', i18nString(UIStrings.invertFilter), true, this.networkInvertFilterSetting);
this.invertFilterUI.addEventListener(
UI.FilterBar.FilterUIEvents.FilterChanged, this.filterChanged.bind(this), this);
UI.Tooltip.Tooltip.install(this.invertFilterUI.element(), i18nString(UIStrings.invertsFilter));
filterBar.addFilter(this.invertFilterUI);
this.dataURLFilterUI = new UI.FilterBar.CheckboxFilterUI(
'hide-data-url', i18nString(UIStrings.hideDataUrls), true, this.networkHideDataURLSetting);
this.dataURLFilterUI.addEventListener(
UI.FilterBar.FilterUIEvents.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.FilterUIEvents.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.FilterUIEvents.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.FilterUIEvents.FilterChanged, this.filterChanged.bind(this), this);
UI.Tooltip.Tooltip.install(this.onlyBlockedRequestsUI.element(), i18nString(UIStrings.onlyShowBlockedRequests));
filterBar.addFilter(this.onlyBlockedRequestsUI);
this.onlyThirdPartyFilterUI = new UI.FilterBar.CheckboxFilterUI(
'only-show-third-party', i18nString(UIStrings.thirdParty), true, this.networkOnlyThirdPartySetting);
this.onlyThirdPartyFilterUI.addEventListener(
UI.FilterBar.FilterUIEvents.FilterChanged, this.filterChanged.bind(this), this);
UI.Tooltip.Tooltip.install(this.onlyThirdPartyFilterUI.element(), i18nString(UIStrings.onlyShowThirdPartyRequests));
filterBar.addFilter(this.onlyThirdPartyFilterUI);
this.filterParser = new TextUtils.TextUtils.FilterParser(searchKeys);
this.suggestionBuilder =
new UI.FilterSuggestionBuilder.FilterSuggestionBuilder(searchKeys, NetworkLogView.sortSearchValues);
this.resetSuggestionBuilder();
this.dataGrid = this.columnsInternal.dataGrid();
this.setupDataGrid();
this.columnsInternal.sortByCurrentColumn();
filterBar.filterButton().addEventListener(
UI.Toolbar.ToolbarButton.Events.Click, this.dataGrid.scheduleUpdate.bind(this.dataGrid, true /* isFromUser */));
this.summaryToolbarInternal = new UI.Toolbar.Toolbar('network-summary-bar', this.element);
this.summaryToolbarInternal.element.setAttribute('role', 'status');
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.TargetManager.TargetManager.instance().observeModels(SDK.NetworkManager.NetworkManager, this, {scoped: true});
Logs.NetworkLog.NetworkLog.instance().addEventListener(
Logs.NetworkLog.Events.RequestAdded, this.onRequestUpdated, this);
Logs.NetworkLog.NetworkLog.instance().addEventListener(
Logs.NetworkLog.Events.RequestUpdated, this.onRequestUpdated, this);
Logs.NetworkLog.NetworkLog.instance().addEventListener(Logs.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());
}
}
private updateGroupByFrame(): void {
const value = Common.Settings.Settings.instance().moduleSetting('network.group-by-frame').get();
this.setGrouping(value ? 'Frame' : null);
}
private static sortSearchValues(key: string, values: string[]): void {
if (key === NetworkForward.UIFilter.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();
}
}
private static negativeFilter(filter: Filter, request: SDK.NetworkRequest.NetworkRequest): boolean {
return !filter(request);
}
private static requestPathFilter(regex: RegExp|null, request: SDK.NetworkRequest.NetworkRequest): boolean {
if (!regex) {
return false;
}
return regex.test(request.path() + '/' + request.name());
}
private 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;
}
private static createRequestDomainFilter(value: string): Filter {
const escapedPattern = value.split('*').map(Platform.StringUtilities.escapeForRegExp).join('.*');
return NetworkLogView.requestDomainFilter.bind(null, new RegExp('^' + escapedPattern + '$', 'i'));
}
private static requestDomainFilter(regex: RegExp, request: SDK.NetworkRequest.NetworkRequest): boolean {
return regex.test(request.domain);
}
private static runningRequestFilter(request: SDK.NetworkRequest.NetworkRequest): boolean {
return !request.finished;
}
private static fromCacheRequestFilter(request: SDK.NetworkRequest.NetworkRequest): boolean {
return request.cached();
}
private static interceptedByServiceWorkerFilter(request: SDK.NetworkRequest.NetworkRequest): boolean {
return request.fetchedViaServiceWorker;
}
private static initiatedByServiceWorkerFilter(request: SDK.NetworkRequest.NetworkRequest): boolean {
return request.initiatedByServiceWorker();
}
private static requestResponseHeaderFilter(value: string, request: SDK.NetworkRequest.NetworkRequest): boolean {
return request.responseHeaderValue(value) !== undefined;
}
private static requestResponseHeaderSetCookieFilter(value: string, request: SDK.NetworkRequest.NetworkRequest):
boolean {
// Multiple Set-Cookie headers in the request are concatenated via space. Only
// filter via `includes` instead of strict equality.
return Boolean(request.responseHeaderValue('Set-Cookie')?.includes(value));
}
private static requestMethodFilter(value: string, request: SDK.NetworkRequest.NetworkRequest): boolean {
return request.requestMethod === value;
}
private static requestPriorityFilter(value: string, request: SDK.NetworkRequest.NetworkRequest): boolean {
return request.priority() === value;
}
private static requestMimeTypeFilter(value: string, request: SDK.NetworkRequest.NetworkRequest): boolean {
return request.mimeType === value;
}
private static requestMixedContentFilter(
value: NetworkForward.UIFilter.MixedContentFilterValues, request: SDK.NetworkRequest.NetworkRequest): boolean {
if (value === NetworkForward.UIFilter.MixedContentFilterValues.Displayed) {
return request.mixedContentType === Protocol.Security.MixedContentType.OptionallyBlockable;
}
if (value === NetworkForward.UIFilter.MixedContentFilterValues.Blocked) {
return request.mixedContentType === Protocol.Security.MixedContentType.Blockable && request.wasBlocked();
}
if (value === NetworkForward.UIFilter.MixedContentFilterValues.BlockOverridden) {
return request.mixedContentType === Protocol.Security.MixedContentType.Blockable && !request.wasBlocked();
}
if (value === NetworkForward.UIFilter.MixedContentFilterValues.All) {
return request.mixedContentType !== Protocol.Security.MixedContentType.None;
}
return false;
}
private static requestSchemeFilter(value: string, request: SDK.NetworkRequest.NetworkRequest): boolean {
return request.scheme === value;
}
private static requestCookieDomainFilter(value: string, request: SDK.NetworkRequest.NetworkRequest): boolean {
return request.allCookiesIncludingBlockedOnes().some(cookie => cookie.domain() === value);
}
private static requestCookieNameFilter(value: string, request: SDK.NetworkRequest.NetworkRequest): boolean {
return request.allCookiesIncludingBlockedOnes().some(cookie => cookie.name() === value);
}
private static requestCookiePathFilter(value: string, request: SDK.NetworkRequest.NetworkRequest): boolean {
return request.allCookiesIncludingBlockedOnes().some(cookie => cookie.path() === value);
}
private static requestCookieValueFilter(value: string, request: SDK.NetworkRequest.NetworkRequest): boolean {
return request.allCookiesIncludingBlockedOnes().some(cookie => cookie.value() === value);
}
private static requestSetCookieDomainFilter(value: string, request: SDK.NetworkRequest.NetworkRequest): boolean {
return request.responseCookies.some(cookie => cookie.domain() === value);
}
private static requestSetCookieNameFilter(value: string, request: SDK.NetworkRequest.NetworkRequest): boolean {
return request.responseCookies.some(cookie => cookie.name() === value);
}
private static requestSetCookieValueFilter(value: string, request: SDK.NetworkRequest.NetworkRequest): boolean {
return request.responseCookies.some(cookie => cookie.value() === value);
}
private static requestSizeLargerThanFilter(value: number, request: SDK.NetworkRequest.NetworkRequest): boolean {
return request.transferSize >= value;
}
private static statusCodeFilter(value: string, request: SDK.NetworkRequest.NetworkRequest): boolean {
return (String(request.statusCode)) === value;
}
static getHTTPRequestsFilter(request: SDK.NetworkRequest.NetworkRequest): boolean {
return request.parsedURL.isValid && (request.scheme in HTTPSchemas);
}
private static resourceTypeFilter(value: string, request: SDK.NetworkRequest.NetworkRequest): boolean {
return request.resourceType().name() === value;
}
private static requestUrlFilter(value: string, request: SDK.NetworkRequest.NetworkRequest): boolean {
const regex = new RegExp(Platform.StringUtilities.escapeForRegExp(value), 'i');
return regex.test(request.url());
}
private 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;
}
private static copyRequestHeaders(request: SDK.NetworkRequest.NetworkRequest): void {
Host.InspectorFrontendHost.InspectorFrontendHostInstance.copyText(request.requestHeadersText());
}
private static copyResponseHeaders(request: SDK.NetworkRequest.NetworkRequest): void {
Host.InspectorFrontendHost.InspectorFrontendHostInstance.copyText(request.responseHeadersText);
}
private 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);
}
private handleDrop(dataTransfer: DataTransfer): void {
const items = dataTransfer.items;
if (!items.length) {
return;
}
const file = items[0].getAsFile();
if (file) {
void this.onLoadFromFile(file);
}
}
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) {
this.harLoadFailed(error.message);
}
return;
}
let harRoot;
try {
// HARRoot and JSON.parse might throw.
harRoot = new HAR.HARFormat.HARRoot(JSON.parse(outputStream.data()));
} catch (e) {
this.harLoadFailed(e);
return;
}
Logs.NetworkLog.NetworkLog.instance().importRequests(HAR.Importer.Importer.requestsFromHARLog(harRoot.log));
}
private harLoadFailed(message: string): void {
Common.Console.Console.instance().error('Failed to load HAR file with following error: ' + message);
}
private 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();
}
private 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.headerHeightInternal;
}
setRecording(recording: boolean): void {
this.recording = recording;
this.updateSummaryBar();
}
columns(): NetworkLogViewColumns {
return this.columnsInternal;
}
summaryToolbar(): UI.Toolbar.Toolbar {
return this.summaryToolbarInternal;
}
modelAdded(networkManager: SDK.NetworkManager.NetworkManager): void {
// TODO(allada) Remove dependency on networkManager and instead use NetworkLog and PageLoad for needed data.
const target = networkManager.target();
if (target.outermostTarget() !== target) {
return;
}
const resourceTreeModel = 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);
}
const targetManager = SDK.TargetManager.TargetManager.instance();
for (const request of Logs.NetworkLog.NetworkLog.instance().requests()) {
if (targetManager.isInScope(SDK.NetworkManager.NetworkManager.forRequest(request))) {
this.refreshRequest(request);
}
}
}
modelRemoved(networkManager: SDK.NetworkManager.NetworkManager): void {
const target = networkManager.target();
if (target.outermostTarget() !== target) {
return;
}
const resourceTreeModel = 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);
}
const preserveLog = Common.Settings.Settings.instance().moduleSetting('network_log.preserve-log').get();
if (!preserveLog) {
this.reset();
}
}
linkifier(): Components.Linkifier.Linkifier {
return this.linkifierInternal;
}
setWindow(start: number, end: number): void {
if (!start && !end) {
this.timeFilter = null;
this.timeCalculatorInternal.setWindow(null);
} else {
this.timeFilter = NetworkLogView.requestTimeFilter.bind(null, start, end);
this.timeCalculatorInternal.setWindow(new NetworkTimeBoundary(start, end));
}
this.filterRequests();
}
resetFocus(): void {
this.dataGrid.element.focus();
}
private resetSuggestionBuilder(): void {
this.suggestionBuilder.clear();
this.suggestionBuilder.addItem(NetworkForward.UIFilter.FilterType.Is, NetworkForward.UIFilter.IsFilterType.Running);
this.suggestionBuilder.addItem(
NetworkForward.UIFilter.FilterType.Is, NetworkForward.UIFilter.IsFilterType.FromCache);
this.suggestionBuilder.addItem(
NetworkForward.UIFilter.FilterType.Is, NetworkForward.UIFilter.IsFilterType.ServiceWorkerIntercepted);
this.suggestionBuilder.addItem(
NetworkForward.UIFilter.FilterType.Is, NetworkForward.UIFilter.IsFilterType.ServiceWorkerInitiated);
this.suggestionBuilder.addItem(NetworkForward.UIFilter.FilterType.LargerThan, '100');
this.suggestionBuilder.addItem(NetworkForward.UIFilter.FilterType.LargerThan, '10k');
this.suggestionBuilder.addItem(NetworkForward.UIFilter.FilterType.LargerThan, '1M');
this.textFilterUI.setSuggestionProvider(this.suggestionBuilder.completions.bind(this.suggestionBuilder));
}
private filterChanged(): void {
this.removeAllNodeHighlights();
this.parseFilterQuery(this.textFilterUI.value(), this.invertFilterUI.checked());
this.filterRequests();
this.textFilterSetting.set(this.textFilterUI.value());
}
async resetFilter(): Promise<void> {
this.textFilterUI.clear();
}
private 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.recordToDisplayNetworkActivity, {PH1: recordNode}));
}
hintText.createChild('br');
hintText.appendChild(UI.XLink.XLink.create(
'https://developer.chrome.com/docs/devtools/network/?utm_source=devtools&utm_campaign=2019Q1',
i18nString(UIStrings.learnMore)));
this.setHidden(true);
}
private hideRecordingHint(): void {
this.setHidden(false);
if (this.recordingHint) {
this.recordingHint.remove();
}
UI.ARIAUtils.alert(i18nString(UIStrings.networkDataAvailable));
this.recordingHint = null;
}
private setHidden(value: boolean): void {
this.columnsInternal.setHidden(value);
UI.ARIAUtils.setHidden(this.summaryToolbarInternal.element, value);
}
override elementsToRestoreScrollPositionsFor(): Element[] {
if (!this.dataGrid) // Not initialized yet.
{
return [];
}
return [this.dataGrid.scrollContainer];
}
columnExtensionResolved(): void {
this.invalidateAllItems(true);
}
private 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 (event.key === 'ArrowRight' && this.dataGrid.selectedNode) {
const initiatorLink = this.dataGrid.selectedNode.element().querySelector('span.devtools-link');
if (initiatorLink) {
(initiatorLink as HTMLElement).focus();
}
}
if (Platform.KeyboardUtilities.isEnterOrSpaceKey(event)) {
this.dispatchEventToListeners(Events.RequestActivated, {showPanel: true, takeFocus: true});
event.consume(true);
}
});
this.dataGrid.element.addEventListener('keyup', event => {
if ((event.key === 'r' || event.key === 'R') && this.dataGrid.selectedNode) {
const request = (this.dataGrid.selectedNode as NetworkNode).request();
if (!request) {
return;
}
if (SDK.NetworkManager.NetworkManager.canReplayRequest(request)) {
SDK.NetworkManager.NetworkManager.replayRequest(request);
}
}
});
this.dataGrid.element.addEventListener('focus', this.onDataGridFocus.bind(this), true);
this.dataGrid.element.addEventListener('blur', this.onDataGridBlur.bind(this), true);
return this.dataGrid;
}
private 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.hoveredNodeInternal;
}
private setHoveredNode(node: NetworkNode|null, highlightInitiatorChain?: boolean): void {
if (this.hoveredNodeInternal) {
this.hoveredNodeInternal.setHovered(false, false);
}
this.hoveredNodeInternal = node;
if (this.hoveredNodeInternal) {
this.hoveredNodeInternal.setHovered(true, Boolean(highlightInitiatorChain));
}
}
private dataGridMouseDown(event: Event): void {
const mouseEvent = (event as MouseEvent);
if (!this.dataGrid.selectedNode && mouseEvent.button) {
mouseEvent.consume();
}
}
private 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 Logs.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()?.type() !== SDK.Target.Type.Frame) {
baseTime = request.startTime;
}
if (request.endTime > maxTime) {
maxTime = request.endTime;
}
}
if (!nodeCount) {
this.showRecordingHint();
return;
}
this.summaryToolbarInternal.removeToolbarItems();
const appendChunk = (chunk: string, title?: string): HTMLDivElement => {
const toolbarText = new UI.Toolbar.ToolbarText(chunk);
toolbarText.setTitle(title ? title : chunk);
this.summaryToolbarInternal.appendToolbarItem(toolbarText);
return toolbarText.element as HTMLDivElement;
};
if (selectedNodeNumber !== nodeCount) {
appendChunk(i18nString(UIStrings.sSRequests, {PH1: selectedNodeNumber, PH2: nodeCount}));
this.summaryToolbarInternal.appendSeparator();
appendChunk(
i18nString(UIStrings.sSTransferred, {
PH1: Platform.NumberUtilities.bytesToString(selectedTransferSize),
PH2: Platform.NumberUtilities.bytesToString(transferSize),
}),
i18nString(UIStrings.sBSBTransferredOverNetwork, {PH1: selectedTransferSize, PH2: transferSize}));
this.summaryToolbarInternal.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.summaryToolbarInternal.appendSeparator();
appendChunk(
i18nString(UIStrings.sTransferred, {PH1: Platform.NumberUtilities.bytesToString(transferSize)}),
i18nString(UIStrings.sBTransferredOverNetwork, {PH1: transferSize}));
this.summaryToolbarInternal.appendSeparator();
appendChunk(
i18nString(UIStrings.sResources, {PH1: Platform.NumberUtilities.bytesToString(resourceSize)}),
i18nString(UIStrings.sBResourcesLoadedByThePage, {PH1: resourceSize}));
}
if (baseTime !== -1 && maxTime !== -1) {
this.summaryToolbarInternal.appendSeparator();
appendChunk(i18nString(UIStrings.finishS, {PH1: i18n.TimeUtilities.secondsToString(maxTime - baseTime)}));
if (this.mainRequestDOMContentLoadedTime !== -1 && this.mainRequestDOMContentLoadedTime > baseTime) {
this.summaryToolbarInternal.appendSeparator();
const domContentLoadedText = i18nString(
UIStrings.domcontentloadedS,
{PH1: i18n.TimeUtilities.secondsToString(this.mainRequestDOMContentLoadedTime - baseTime)});
appendChunk(domContentLoadedText).style.color = `var(${NetworkLogView.getDCLEventColor()})`;
}
if (this.mainRequestLoadTime !== -1) {
this.summaryToolbarInternal.appendSeparator();
const loadText =
i18nString(UIStrings.loadS, {PH1: i18n.TimeUtilities.secondsToString(this.mainRequestLoadTime - baseTime)});
appendChunk(loadText).style.color = `var(${NetworkLogView.getLoadEventColor()})`;
}
}
}
scheduleRefresh(): void {
if (this.needsRefresh) {
return;
}
this.needsRefresh = true;
if (this.isShowing()) {
void coordinator.write(this.refresh.bind(this));
}
}
addFilmStripFrames(times: number[]): void {
this.columnsInternal.addEventDividers(times, 'network-frame-divider');
}
selectFilmStripFrame(time: number): void {
this.columnsInternal.selectFilmStripFrame(time);
}
clearFilmStripFrame(): void {
this.columnsInternal.clearFilmStripFrame();
}
private refreshIfNeeded(): void {
if (this.needsRefresh) {
this.refresh();
}
}
private invalidateAllItems(deferUpdate?: boolean): void {
const targetManager = SDK.TargetManager.TargetManager.instance();
this.staleRequests = new Set(Logs.NetworkLog.Netwo