UNPKG

chrome-devtools-frontend

Version:
1,145 lines (1,037 loc) • 114 kB
// 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