chrome-devtools-frontend
Version:
Chrome DevTools UI
1,145 lines (1,037 loc) • 114 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.
/* eslint-disable rulesdir/no-imperative-dom-api */
/*
* 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 '../../ui/legacy/legacy.js';
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 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 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 Adorners from '../../ui/components/adorners/adorners.js';
import * as Buttons from '../../ui/components/buttons/buttons.js';
import * as RenderCoordinator 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 * as VisualLogging from '../../ui/visual_logging/visual_logging.js';
import {
Events,
type EventTypes,
NetworkGroupNode,
type NetworkLogViewInterface,
type NetworkNode,
NetworkRequestNode,
} from './NetworkDataGridNode.js';
import {NetworkFrameGrouper} from './NetworkFrameGrouper.js';
import networkLogViewStyles from './networkLogView.css.js';
import {NetworkLogViewColumns} from './NetworkLogViewColumns.js';
import {
NetworkTimeBoundary,
type NetworkTimeCalculator,
NetworkTransferDurationCalculator,
NetworkTransferTimeCalculator,
} 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: 'Hide \'data:\' and \'blob:\' URLs',
/**
* @description Label for a filter in the Network panel
*/
chromeExtensions: 'Hide extension URLs',
/**
* @description Tooltip for a filter in the Network panel
*/
hideChromeExtension: 'Hide \'chrome-extension://\' URLs',
/**
*@description Aria accessible name in Network Log View of the Network panel
*/
requestTypesToInclude: 'Request types to include',
/**
*@description Label for a checkbox in the Network panel. When checked, only requests with
* blocked response cookies are shown.
*/
hasBlockedCookies: 'Blocked response 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.
*/
onlyShowRequestsWithBlockedCookies: 'Show only 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: 'Show only 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: 'Show 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 content in Network Log View of the Network panel
*/
recordingNetworkActivity: 'Currently recording network activity',
/**
*@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 reload the page in order to
* show network activity in the current UI.
*@example {Reload page} PH1
*@example {Ctrl + R} PH2
*/
performARequestOrHitSToRecordThe:
'Perform a request or reload the page by using the "{PH1}" button or by pressing {PH2}.',
/**
*@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 {Start recording} PH1
* @example {Ctrl + E} PH2
*/
recordToDisplayNetworkActivity:
'Record network log to display network activity by using the "{PH1}" button or by pressing {PH2}.',
/**
*@description Label of a button in the Network Log View of the Network panel.
*/
reloadPage: 'Reload page',
/**
*@description Label of a button in the Network Log View of the Network panel.
*/
startRecording: 'Start recording',
/**
*@description Shown in the Network Log View of the Network panel when the user has not yet
* recorded any network activity.
*/
noNetworkActivityRecorded: 'No network activity recorded',
/**
*@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 A context menu command in the Network panel, for copying the URL of the selected request to the clipboard.
*/
copyURL: 'Copy URL',
/**
*@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 `fetch` (`Node.js`)',
/**
*@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 A context menu command in the Network panel, for copying the URLs of all requestes to the clipboard.
*/
copyAllURLs: 'Copy all URLs',
/**
*@description A context menu command in the Network panel, for copying the URLs of all requestes
(after applying the Network filter) to the clipboard.
*/
copyAllListedURLs: 'Copy all listed URLs',
/**
*@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 to
*represent all network requests.
*/
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 PowerShell script to
*represent all network requests (after applying the Network filter).
*/
copyAllListedAsPowershell: 'Copy all listed 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) to represent all network requests.
*/
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 'fetch' command (fetch
*should not be translated) to represent all network requests (after applying the Network filter).
*/
copyAllListedAsFetch: 'Copy all listed 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) to represent all network requests.
*/
copyAllAsNodejsFetch: 'Copy all as `fetch` (`Node.js`)',
/**
*@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) to represent all network requests (after applying
*the Network filter).
*/
copyAllListedAsNodejsFetch: 'Copy all listed as `fetch` (`Node.js`)',
/**
*@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) to represent all network requests.
*/
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 cURL (a program, not
*translatable) to represent all network requests (after applying the Network filter).
*/
copyAllListedAsCurlCmd: 'Copy all listed 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 to represent
*all network requests.
*/
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 a Bash script to represent
*all network requests (after applying the Network filter).
*/
copyAllListedAsCurlBash: 'Copy all listed 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) to represent all network requests.
*/
copyAllAsCurl: 'Copy all 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) to represent all network requests (after applying the Network filter).
*/
copyAllListedAsCurl: 'Copy all listed 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 and scrub all potentially
* sensitive data from the network requests. 'all' refers to every network request that is currently
* shown.
*/
copyAllAsHarSanitized: 'Copy all as `HAR` (sanitized)',
/**
* @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 and include potentially
* sensitive data from the network requests. 'all' refers to every network request that is currently
* shown.
*/
copyAllAsHarWithSensitiveData: 'Copy all as `HAR` (with sensitive data)',
/**
* @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 and scrub all potentially
* sensitive data from the network requests. 'all' refers to every network request that is currently
* shown (after applying the Network filter).
*/
copyAllListedAsHarSanitized: 'Copy all listed as `HAR` (sanitized)',
/**
* @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 and include potentially
* sensitive data from the network requests. 'all' refers to every network request that is currently
* shown (after applying the Network filter).
*/
copyAllListedAsHarWithSensitiveData: 'Copy all listed as `HAR` (with sensitive data)',
/**
*@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',
/**
* @description Tooltip for the Show only/Hide requests dropdown of the filterbar
*/
showOnlyHideRequests: 'Show only/hide requests',
/**
* @description Text for the Show only/Hide requests dropdown button of the filterbar
*/
moreFilters: 'More filters',
} as const;
const str_ = i18n.i18n.registerUIStrings('panels/network/NetworkLogView.ts', UIStrings);
const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
const enum FetchStyle {
BROWSER = 0,
NODE_JS = 1,
}
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 networkHideChromeExtensions: Common.Settings.Setting<boolean>;
private readonly networkShowBlockedCookiesOnlySetting: Common.Settings.Setting<boolean>;
private readonly networkOnlyBlockedRequestsSetting: Common.Settings.Setting<boolean>;
private readonly networkOnlyThirdPartySetting: Common.Settings.Setting<boolean>;
private readonly networkResourceTypeFiltersSetting: Common.Settings.Setting<Record<string, boolean>>;
private readonly networkShowOptionsToGenerateHarWithSensitiveData: Common.Settings.Setting<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: UI.EmptyWidget.EmptyWidget|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 moreFiltersDropDownUI: MoreFiltersDropDownUI|undefined;
private readonly resourceCategoryFilterUI: UI.FilterBar.NamedBitSetFilterUI;
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.registerRequiredCSS(networkLogViewStyles);
this.setMinimumSize(50, 64);
this.element.id = 'network-container';
this.element.classList.add('no-node-selected');
this.networkInvertFilterSetting = Common.Settings.Settings.instance().createSetting('network-invert-filter', false);
this.networkHideDataURLSetting = Common.Settings.Settings.instance().createSetting('network-hide-data-url', false);
this.networkHideChromeExtensions =
Common.Settings.Settings.instance().createSetting('network-hide-chrome-extensions', false);
this.networkShowBlockedCookiesOnlySetting =
Common.Settings.Settings.instance().createSetting('network-show-blocked-cookies-only-setting', false);
this.networkOnlyBlockedRequestsSetting =
Common.Settings.Settings.instance().createSetting('network-only-blocked-requests', false);
this.networkOnlyThirdPartySetting =
Common.Settings.Settings.instance().createSetting('network-only-third-party-setting', false);
this.networkResourceTypeFiltersSetting =
Common.Settings.Settings.instance().createSetting('network-resource-type-filters', {});
this.networkShowOptionsToGenerateHarWithSensitiveData = Common.Settings.Settings.instance().createSetting(
'network.show-options-to-generate-har-with-sensitive-data', false);
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.FILTER_CHANGED, this.filterChanged, this);
filterBar.addFilter(this.textFilterUI);
this.invertFilterUI = new UI.FilterBar.CheckboxFilterUI(
i18nString(UIStrings.invertFilter), true, this.networkInvertFilterSetting, 'invert-filter');
this.invertFilterUI.addEventListener(
UI.FilterBar.FilterUIEvents.FILTER_CHANGED, this.filterChanged.bind(this), this);
UI.Tooltip.Tooltip.install(this.invertFilterUI.element(), i18nString(UIStrings.invertsFilter));
filterBar.addFilter(this.invertFilterUI);
filterBar.addDivider();
const filterItems =
Object.entries(Common.ResourceType.resourceCategories).map(([key, category]) => ({
name: category.name,
label: () => category.shortTitle(),
title: category.title(),
jslogContext:
Platform.StringUtilities.toKebabCase(key),
}));
this.moreFiltersDropDownUI = new MoreFiltersDropDownUI();
this.moreFiltersDropDownUI.addEventListener(UI.FilterBar.FilterUIEvents.FILTER_CHANGED, this.filterChanged, this);
filterBar.addFilter(this.moreFiltersDropDownUI);
this.resourceCategoryFilterUI =
new UI.FilterBar.NamedBitSetFilterUI(filterItems, this.networkResourceTypeFiltersSetting);
UI.ARIAUtils.setLabel(this.resourceCategoryFilterUI.element(), i18nString(UIStrings.requestTypesToInclude));
this.resourceCategoryFilterUI.addEventListener(
UI.FilterBar.FilterUIEvents.FILTER_CHANGED, this.filterChanged.bind(this), this);
filterBar.addFilter(this.resourceCategoryFilterUI);
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 = this.element.createChild('devtools-toolbar', 'network-summary-bar');
this.summaryToolbarInternal.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('network-color-code-resource-types')
.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.RequestRemoved, this.onRequestRemoved, 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('network-text-filter', '');
if (this.textFilterSetting.get()) {
this.textFilterUI.setValue(this.textFilterSetting.get());
}
}
getMoreFiltersDropdown(): MoreFiltersDropDownUI|undefined {
return this.moreFiltersDropDownUI;
}
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);
const bPriority = PerfUI.NetworkPriorities.uiLabelToNetworkPriority(b);
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 requestRequestHeaderFilter(headerName: string, request: SDK.NetworkRequest.NetworkRequest): boolean {
return request.requestHeaders().some(header => header.name.toLowerCase() === headerName.toLowerCase());
}
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.BLOCK_OVERRIDDEN) {
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;
}
private static hasOverridesFilter(value: string, request: SDK.NetworkRequest.NetworkRequest): boolean {
if (!value) {
return false;
}
if (value === overrideFilter.no) {
return request.overrideTypes.length === 0;
}
if (value === overrideFilter.yes) {
return request.overrideTypes.length > 0;
}
if (value === overrideFilter.content) {
return request.overrideTypes.includes('content');
}
if (value === overrideFilter.headers) {
return request.overrideTypes.includes('headers');
}
return request.overrideTypes.join(',').includes(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.requestContentData();
let content: string;
if (TextUtils.ContentData.ContentData.isError(contentData)) {
content = '';
} else if (!contentData.isTextContent) {
content = contentData.asDataUrl() ?? '';
} else {
content = contentData.text;
}
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 this.rawRowHeight;
}
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);
}
for (const request of Logs.NetworkLog.NetworkLog.instance().requests()) {
if (this.isInScope(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.FROM_CACHE);
this.suggestionBuilder.addItem(
NetworkForward.UIFilter.FilterType.Is, NetworkForward.UIFilter.IsFilterType.SERVICE_WORKER_INTERCEPTED);
this.suggestionBuilder.addItem(
NetworkForward.UIFilter.FilterType.Is, NetworkForward.UIFilter.IsFilterType.SERVICE_WORKER_INITIATED);
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));
this.suggestionBuilder.addItem(NetworkForward.UIFilter.FilterType.HasOverrides, overrideFilter.yes);
this.suggestionBuilder.addItem(NetworkForward.UIFilter.FilterType.HasOverrides, overrideFilter.no);
this.suggestionBuilder.addItem(NetworkForward.UIFilter.FilterType.HasOverrides, overrideFilter.content);
this.suggestionBuilder.addItem(NetworkForward.UIFilter.FilterType.HasOverrides, overrideFilter.headers);
}
private filterChanged(): void {
this.removeAllNodeHighlights();
this.parseFilterQuery(this.textFilterUI.value(), this.invertFilterUI.checked());
this.filterRequests();
this.textFilterSetting.set(this.textFilterUI.value());
this.moreFiltersDropDownUI?.updateActiveFiltersCount();
this.moreFiltersDropDownUI?.updateTooltip();
this.columnsInternal.filterChanged();
}
async resetFilter(): Promise<void> {
this.textFilterUI.clear();
}
private showRecordingHint(): void {
this.hideRecordingHint();
const actionRegistry = UI.ActionRegistry.ActionRegistry.instance();
const actionName = this.recording ? 'inspector-main.reload' : 'network.toggle-recording';
const action = actionRegistry.hasAction(actionName) ? actionRegistry.getAction(actionName) : null;
const shortcutTitle = UI.ShortcutRegistry.ShortcutRegistry.instance().shortcutTitleForAction(actionName) ?? '';
const header = this.recording ? i18nString(UIStrings.recordingNetworkActivity) :
i18nString(UIStrings.noNetworkActivityRecorded);
const instruction =
this.recording ? UIStrings.performARequestOrHitSToRecordThe : UIStrings.recordToDisplayNetworkActivity;
const buttonText = this.recording ? i18nString(UIStrings.reloadPage) : i18nString(UIStrings.startRecording);
// eslint-disable-next-line rulesdir/l10n-i18nString-call-only-with-uistrings
const description = i18nString(instruction, {
PH1: buttonText,
PH2: shortcutTitle,
});
this.recordingHint = new UI.EmptyWidget.EmptyWidget(header, shortcutTitle ? description : '');
this.recordingHint.element.classList.add('network-status-pane');
this.recordingHint.link = 'https://developer.chrome.com/docs/devtools/network/' as Platform.DevToolsPath.UrlString;
if (shortcutTitle && action) {
const button = UI.UIUtils.createTextButton(buttonText, () => action.execute(), {
jslogContext: actionName,
variant: Buttons.Button.Variant.TONAL,
});
this.recordingHint.contentElement.appendChild(button);
}
this.recordingHint.show(this.element);
this.setHidden(true);
}
private hideRecordingHint(): void {
this.setHidden(false);
if (this.recordingHint) {
this.recordingHint.detach();
this.recordingHint = null;
}
UI.ARIAUtils.alert(i18nString(UIStrings.networkDataAvailable));
}
private setHidden(value: boolean): void {
this.columnsInternal.setHidden(value);
this.dataGrid.setInert(value);
UI.ARIAUtils.setHidden(this.summaryToolbarInternal, 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.setEnableAutoScrollToBottom(true);
this.dataGrid.setName('network-log');
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('button.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);
void VisualLogging.logKeyDown(this.dataGrid.selectedNode.element(), event, 'replay-xhr');
}
}
});
this.dataGrid.element.addEventListener('focus', this.onDataGridFocus.bind(this), tr