UNPKG

chrome-devtools-frontend

Version:
1,184 lines (1,061 loc) 97.3 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. /* * Copyright (C) 2007, 2008 Apple Inc. All rights reserved. * Copyright (C) 2008, 2009 Anthony Ricaud <rik@webkit.org> * Copyright (C) 2011 Google Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ import * as Common from '../../core/common/common.js'; import * as Host from '../../core/host/host.js'; import * as i18n from '../../core/i18n/i18n.js'; import * as Platform from '../../core/platform/platform.js'; import * as Root from '../../core/root/root.js'; import * as SDK from '../../core/sdk/sdk.js'; import * as Protocol from '../../generated/protocol.js'; import * as Bindings from '../../models/bindings/bindings.js'; import * as HAR from '../../models/har/har.js'; import * as IssuesManager from '../../models/issues_manager/issues_manager.js'; import * as Logs from '../../models/logs/logs.js'; import * as Persistence from '../../models/persistence/persistence.js'; import * as TextUtils from '../../models/text_utils/text_utils.js'; import * as NetworkForward from '../../panels/network/forward/forward.js'; import * as Sources from '../../panels/sources/sources.js'; import * as Coordinator from '../../ui/components/render_coordinator/render_coordinator.js'; import * as DataGrid from '../../ui/legacy/components/data_grid/data_grid.js'; import * as PerfUI from '../../ui/legacy/components/perf_ui/perf_ui.js'; import * as Components from '../../ui/legacy/components/utils/utils.js'; import * as UI from '../../ui/legacy/legacy.js'; import networkLogViewStyles from './networkLogView.css.js'; import { Events, NetworkGroupNode, NetworkRequestNode, type NetworkLogViewInterface, type NetworkNode, type EventTypes, } from './NetworkDataGridNode.js'; import {NetworkFrameGrouper} from './NetworkFrameGrouper.js'; import {NetworkLogViewColumns} from './NetworkLogViewColumns.js'; import { NetworkTimeBoundary, NetworkTransferDurationCalculator, NetworkTransferTimeCalculator, type NetworkTimeCalculator, } from './NetworkTimeCalculator.js'; const UIStrings = { /** *@description Text in Network Log View of the Network panel */ invertFilter: 'Invert', /** *@description Tooltip for the 'invert' checkbox in the Network panel. */ invertsFilter: 'Inverts the search filter', /** *@description Text in Network Log View of the Network panel */ hideDataUrls: 'Hide data URLs', /** *@description Data urlfilter ui element title in Network Log View of the Network panel */ hidesDataAndBlobUrls: 'Hides data: and blob: URLs', /** *@description Aria accessible name in Network Log View of the Network panel */ resourceTypesToInclude: 'Resource types to include', /** *@description Label for a filter in the Network panel */ hasBlockedCookies: 'Has blocked cookies', /** *@description Tooltip for a checkbox in the Network panel. The response to a network request may include a * cookie (https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies). Such response cookies can * be malformed or otherwise invalid and the browser may choose to ignore or not accept invalid cookies. */ onlyShowRequestsWithBlocked: 'Only show requests with blocked response cookies', /** *@description Label for a filter in the Network panel */ blockedRequests: 'Blocked Requests', /** *@description Tooltip for a filter in the Network panel */ onlyShowBlockedRequests: 'Only show blocked requests', /** *@description Label for a filter in the Network panel */ thirdParty: '3rd-party requests', /** *@description Tooltip for a filter in the Network panel */ onlyShowThirdPartyRequests: 'Shows only requests with origin different from page origin', /** *@description Text that appears when user drag and drop something (for example, a file) in Network Log View of the Network panel */ dropHarFilesHere: 'Drop HAR files here', /** *@description Recording text text content in Network Log View of the Network panel */ recordingNetworkActivity: 'Recording network activity…', /** *@description Text in Network Log View of the Network panel *@example {Ctrl + R} PH1 */ performARequestOrHitSToRecordThe: 'Perform a request or hit {PH1} to record the reload.', /** *@description Shown in the Network Log View of the Network panel when the user has not yet * recorded any network activity. This is an instruction to the user to start recording in order to * show network activity in the current UI. *@example {Ctrl + E} PH1 */ recordToDisplayNetworkActivity: 'Record network log ({PH1}) to display network activity.', /** *@description Text that is usually a hyperlink to more documentation */ learnMore: 'Learn more', /** *@description Text to announce to screen readers that network data is available. */ networkDataAvailable: 'Network Data Available', /** *@description Text in Network Log View of the Network panel *@example {3} PH1 *@example {5} PH2 */ sSRequests: '{PH1} / {PH2} requests', /** *@description Message in the summary toolbar at the bottom of the Network log that shows the compressed size of the * resources transferred during a selected time frame over the compressed size of all resources transferred during * the whole network log. *@example {5 B} PH1 *@example {10 B} PH2 */ sSTransferred: '{PH1} / {PH2} transferred', /** *@description Message in a tooltip that shows the compressed size of the resources transferred during a selected * time frame over the compressed size of all resources transferred during the whole network log. *@example {10} PH1 *@example {15} PH2 */ sBSBTransferredOverNetwork: '{PH1} B / {PH2} B transferred over network', /** * @description Text in Network Log View of the Network panel. Appears when a particular network * resource is selected by the user. Shows how large the selected resource was (PH1) out of the * total size (PH2). * @example {40MB} PH1 * @example {50MB} PH2 */ sSResources: '{PH1} / {PH2} resources', /** *@description Text in Network Log View of the Network panel *@example {40} PH1 *@example {50} PH2 */ sBSBResourcesLoadedByThePage: '{PH1} B / {PH2} B resources loaded by the page', /** *@description Text in Network Log View of the Network panel *@example {6} PH1 */ sRequests: '{PH1} requests', /** *@description Message in the summary toolbar at the bottom of the Network log that shows the compressed size of * all resources transferred over network during a network activity log. *@example {4 B} PH1 */ sTransferred: '{PH1} transferred', /** *@description Message in a tooltip that shows the compressed size of all resources transferred over network during * a network activity log. *@example {4} PH1 */ sBTransferredOverNetwork: '{PH1} B transferred over network', /** *@description Text in Network Log View of the Network panel *@example {4} PH1 */ sResources: '{PH1} resources', /** *@description Text in Network Log View of the Network panel *@example {10} PH1 */ sBResourcesLoadedByThePage: '{PH1} B resources loaded by the page', /** *@description Text in Network Log View of the Network panel *@example {120ms} PH1 */ finishS: 'Finish: {PH1}', /** *@description Text in Network Log View of the Network panel *@example {3000ms} PH1 */ domcontentloadedS: 'DOMContentLoaded: {PH1}', /** *@description Text in Network Log View of the Network panel *@example {40ms} PH1 */ loadS: 'Load: {PH1}', /** *@description Text for copying */ copy: 'Copy', /** *@description Text in Network Log View of the Network panel */ copyRequestHeaders: 'Copy request headers', /** *@description Text in Network Log View of the Network panel */ copyResponseHeaders: 'Copy response headers', /** *@description Text in Network Log View of the Network panel */ copyResponse: 'Copy response', /** *@description Text in Network Log View of the Network panel */ copyStacktrace: 'Copy stack trace', /** * @description A context menu command in the Network panel, for copying to the clipboard. * PowerShell refers to the format the data will be copied as. */ copyAsPowershell: 'Copy as `PowerShell`', /** *@description A context menu command in the Network panel, for copying to the clipboard. 'fetch' * refers to the format the data will be copied as, which is compatible with the fetch web API. */ copyAsFetch: 'Copy as `fetch`', /** * @description Text in Network Log View of the Network panel. An action that copies a command to * the developer's clipboard. The command allows the developer to replay this specific network * request in Node.js, a desktop application/framework. 'Node.js fetch' is a noun phrase for the * type of request that will be copied. */ copyAsNodejsFetch: 'Copy as `Node.js` `fetch`', /** *@description Text in Network Log View of the Network panel. An action that copies a command to *the clipboard. It will copy the command in the format compatible with cURL (a program, not *translatable). */ copyAsCurlCmd: 'Copy as `cURL` (`cmd`)', /** *@description Text in Network Log View of the Network panel. An action that copies a command to *the clipboard. It will copy the command in the format compatible with a Bash script. */ copyAsCurlBash: 'Copy as `cURL` (`bash`)', /** *@description Text in Network Log View of the Network panel. An action that copies a command to *the clipboard. It will copy the command in the format compatible with a PowerShell script. */ copyAllAsPowershell: 'Copy all as `PowerShell`', /** *@description Text in Network Log View of the Network panel. An action that copies a command to *the clipboard. It will copy the command in the format compatible with a 'fetch' command (fetch *should not be translated). */ copyAllAsFetch: 'Copy all as `fetch`', /** *@description Text in Network Log View of the Network panel. An action that copies a command to *the clipboard. It will copy the command in the format compatible with a Node.js 'fetch' command *(fetch and Node.js should not be translated). */ copyAllAsNodejsFetch: 'Copy all as `Node.js` `fetch`', /** *@description Text in Network Log View of the Network panel. An action that copies a command to *the clipboard. It will copy the command in the format compatible with cURL (a program, not *translatable). */ copyAllAsCurlCmd: 'Copy all as `cURL` (`cmd`)', /** *@description Text in Network Log View of the Network panel. An action that copies a command to *the clipboard. It will copy the command in the format compatible with a Bash script. */ copyAllAsCurlBash: 'Copy all as `cURL` (`bash`)', /** *@description Text in Network Log View of the Network panel. An action that copies a command to *the clipboard. It will copy the command in the format compatible with cURL (a program, not *translatable). */ copyAsCurl: 'Copy as `cURL`', /** *@description Text in Network Log View of the Network panel. An action that copies a command to *the clipboard. It will copy the command in the format compatible with cURL (a program, not *translatable). */ copyAllAsCurl: 'Copy all as `cURL`', /** * @description Text in Network Log View of the Network panel. An action that copies data to the * clipboard. It will copy the data in the HAR (not translatable) format. 'all' refers to every * network request that is currently shown. */ copyAllAsHar: 'Copy all as `HAR`', /** *@description A context menu item in the Network Log View of the Network panel */ saveAllAsHarWithContent: 'Save all as `HAR` with content', /** *@description A context menu item in the Network Log View of the Network panel */ clearBrowserCache: 'Clear browser cache', /** *@description A context menu item in the Network Log View of the Network panel */ clearBrowserCookies: 'Clear browser cookies', /** *@description A context menu item in the Network Log View of the Network panel */ blockRequestUrl: 'Block request URL', /** *@description A context menu item in the Network Log View of the Network panel *@example {example.com} PH1 */ unblockS: 'Unblock {PH1}', /** *@description A context menu item in the Network Log View of the Network panel */ blockRequestDomain: 'Block request domain', /** *@description Text to replay an XHR request */ replayXhr: 'Replay XHR', /** *@description Text in Network Log View of the Network panel */ areYouSureYouWantToClearBrowser: 'Are you sure you want to clear browser cache?', /** *@description Text in Network Log View of the Network panel */ areYouSureYouWantToClearBrowserCookies: 'Are you sure you want to clear browser cookies?', /** *@description A context menu item in the Network Log View of the Network panel * for creating a header override */ overrideHeaders: 'Override headers', }; const str_ = i18n.i18n.registerUIStrings('panels/network/NetworkLogView.ts', UIStrings); const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_); const enum FetchStyle { Browser = 0, NodeJs = 1, } const coordinator = Coordinator.RenderCoordinator.RenderCoordinator.instance(); export class NetworkLogView extends Common.ObjectWrapper.eventMixin<EventTypes, typeof UI.Widget.VBox>(UI.Widget.VBox) implements SDK.TargetManager.SDKModelObserver<SDK.NetworkManager.NetworkManager>, NetworkLogViewInterface { private readonly networkInvertFilterSetting: Common.Settings.Setting<boolean>; private readonly networkHideDataURLSetting: Common.Settings.Setting<boolean>; private readonly networkShowIssuesOnlySetting: Common.Settings.Setting<boolean>; private readonly networkOnlyBlockedRequestsSetting: Common.Settings.Setting<boolean>; private readonly networkOnlyThirdPartySetting: Common.Settings.Setting<boolean>; private readonly networkResourceTypeFiltersSetting: Common.Settings.Setting<{[key: string]: boolean}>; private rawRowHeight: number; private readonly progressBarContainer: Element; private readonly networkLogLargeRowsSetting: Common.Settings.Setting<boolean>; private rowHeightInternal: number; private readonly timeCalculatorInternal: NetworkTransferTimeCalculator; private readonly durationCalculator: NetworkTransferDurationCalculator; private calculatorInternal: NetworkTransferTimeCalculator; private readonly columnsInternal: NetworkLogViewColumns; private staleRequests: Set<SDK.NetworkRequest.NetworkRequest>; private mainRequestLoadTime: number; private mainRequestDOMContentLoadedTime: number; private filters: Filter[]; private timeFilter: Filter|null; private hoveredNodeInternal: NetworkNode|null; private recordingHint: Element|null; private highlightedNode: NetworkRequestNode|null; private readonly linkifierInternal: Components.Linkifier.Linkifier; private recording: boolean; private needsRefresh: boolean; private readonly headerHeightInternal: number; private readonly groupLookups: Map<string, GroupLookupInterface>; private activeGroupLookup: GroupLookupInterface|null; private readonly textFilterUI: UI.FilterBar.TextFilterUI; private readonly invertFilterUI: UI.FilterBar.CheckboxFilterUI; private readonly dataURLFilterUI: UI.FilterBar.CheckboxFilterUI; private resourceCategoryFilterUI: UI.FilterBar.NamedBitSetFilterUI; private readonly onlyIssuesFilterUI: UI.FilterBar.CheckboxFilterUI; private readonly onlyBlockedRequestsUI: UI.FilterBar.CheckboxFilterUI; private readonly onlyThirdPartyFilterUI: UI.FilterBar.CheckboxFilterUI; private readonly filterParser: TextUtils.TextUtils.FilterParser; private readonly suggestionBuilder: UI.FilterSuggestionBuilder.FilterSuggestionBuilder; private dataGrid: DataGrid.SortableDataGrid.SortableDataGrid<NetworkNode>; private readonly summaryToolbarInternal: UI.Toolbar.Toolbar; private readonly filterBar: UI.FilterBar.FilterBar; private readonly textFilterSetting: Common.Settings.Setting<string>; constructor( filterBar: UI.FilterBar.FilterBar, progressBarContainer: Element, networkLogLargeRowsSetting: Common.Settings.Setting<boolean>) { super(); this.setMinimumSize(50, 64); this.element.id = 'network-container'; this.element.classList.add('no-node-selected'); this.networkInvertFilterSetting = Common.Settings.Settings.instance().createSetting('networkInvertFilter', false); this.networkHideDataURLSetting = Common.Settings.Settings.instance().createSetting('networkHideDataURL', false); this.networkShowIssuesOnlySetting = Common.Settings.Settings.instance().createSetting('networkShowIssuesOnly', false); this.networkOnlyBlockedRequestsSetting = Common.Settings.Settings.instance().createSetting('networkOnlyBlockedRequests', false); this.networkOnlyThirdPartySetting = Common.Settings.Settings.instance().createSetting('networkOnlyThirdPartySetting', false); this.networkResourceTypeFiltersSetting = Common.Settings.Settings.instance().createSetting('networkResourceTypeFilters', {}); this.rawRowHeight = 0; this.progressBarContainer = progressBarContainer; this.networkLogLargeRowsSetting = networkLogLargeRowsSetting; this.networkLogLargeRowsSetting.addChangeListener(updateRowHeight.bind(this), this); function updateRowHeight(this: NetworkLogView): void { this.rawRowHeight = Boolean(this.networkLogLargeRowsSetting.get()) ? 41 : 21; this.rowHeightInternal = this.computeRowHeight(); } this.rawRowHeight = 0; this.rowHeightInternal = 0; updateRowHeight.call(this); this.timeCalculatorInternal = new NetworkTransferTimeCalculator(); this.durationCalculator = new NetworkTransferDurationCalculator(); this.calculatorInternal = this.timeCalculatorInternal; this.columnsInternal = new NetworkLogViewColumns( this, this.timeCalculatorInternal, this.durationCalculator, networkLogLargeRowsSetting); this.columnsInternal.show(this.element); this.staleRequests = new Set(); this.mainRequestLoadTime = -1; this.mainRequestDOMContentLoadedTime = -1; this.filters = []; this.timeFilter = null; this.hoveredNodeInternal = null; this.recordingHint = null; this.highlightedNode = null; this.linkifierInternal = new Components.Linkifier.Linkifier(); this.recording = false; this.needsRefresh = false; this.headerHeightInternal = 0; this.groupLookups = new Map(); this.groupLookups.set('Frame', new NetworkFrameGrouper(this)); this.activeGroupLookup = null; this.textFilterUI = new UI.FilterBar.TextFilterUI(); this.textFilterUI.addEventListener(UI.FilterBar.FilterUIEvents.FilterChanged, this.filterChanged, this); filterBar.addFilter(this.textFilterUI); this.invertFilterUI = new UI.FilterBar.CheckboxFilterUI( 'invert-filter', i18nString(UIStrings.invertFilter), true, this.networkInvertFilterSetting); this.invertFilterUI.addEventListener( UI.FilterBar.FilterUIEvents.FilterChanged, this.filterChanged.bind(this), this); UI.Tooltip.Tooltip.install(this.invertFilterUI.element(), i18nString(UIStrings.invertsFilter)); filterBar.addFilter(this.invertFilterUI); this.dataURLFilterUI = new UI.FilterBar.CheckboxFilterUI( 'hide-data-url', i18nString(UIStrings.hideDataUrls), true, this.networkHideDataURLSetting); this.dataURLFilterUI.addEventListener( UI.FilterBar.FilterUIEvents.FilterChanged, this.filterChanged.bind(this), this); UI.Tooltip.Tooltip.install(this.dataURLFilterUI.element(), i18nString(UIStrings.hidesDataAndBlobUrls)); filterBar.addFilter(this.dataURLFilterUI); const filterItems = Object.values(Common.ResourceType.resourceCategories) .map( category => ({name: category.title(), label: (): string => category.shortTitle(), title: category.title()})); this.resourceCategoryFilterUI = new UI.FilterBar.NamedBitSetFilterUI(filterItems, this.networkResourceTypeFiltersSetting); UI.ARIAUtils.setAccessibleName( this.resourceCategoryFilterUI.element(), i18nString(UIStrings.resourceTypesToInclude)); this.resourceCategoryFilterUI.addEventListener( UI.FilterBar.FilterUIEvents.FilterChanged, this.filterChanged.bind(this), this); filterBar.addFilter(this.resourceCategoryFilterUI); this.onlyIssuesFilterUI = new UI.FilterBar.CheckboxFilterUI( 'only-show-issues', i18nString(UIStrings.hasBlockedCookies), true, this.networkShowIssuesOnlySetting); this.onlyIssuesFilterUI.addEventListener( UI.FilterBar.FilterUIEvents.FilterChanged, this.filterChanged.bind(this), this); UI.Tooltip.Tooltip.install(this.onlyIssuesFilterUI.element(), i18nString(UIStrings.onlyShowRequestsWithBlocked)); filterBar.addFilter(this.onlyIssuesFilterUI); this.onlyBlockedRequestsUI = new UI.FilterBar.CheckboxFilterUI( 'only-show-blocked-requests', i18nString(UIStrings.blockedRequests), true, this.networkOnlyBlockedRequestsSetting); this.onlyBlockedRequestsUI.addEventListener( UI.FilterBar.FilterUIEvents.FilterChanged, this.filterChanged.bind(this), this); UI.Tooltip.Tooltip.install(this.onlyBlockedRequestsUI.element(), i18nString(UIStrings.onlyShowBlockedRequests)); filterBar.addFilter(this.onlyBlockedRequestsUI); this.onlyThirdPartyFilterUI = new UI.FilterBar.CheckboxFilterUI( 'only-show-third-party', i18nString(UIStrings.thirdParty), true, this.networkOnlyThirdPartySetting); this.onlyThirdPartyFilterUI.addEventListener( UI.FilterBar.FilterUIEvents.FilterChanged, this.filterChanged.bind(this), this); UI.Tooltip.Tooltip.install(this.onlyThirdPartyFilterUI.element(), i18nString(UIStrings.onlyShowThirdPartyRequests)); filterBar.addFilter(this.onlyThirdPartyFilterUI); this.filterParser = new TextUtils.TextUtils.FilterParser(searchKeys); this.suggestionBuilder = new UI.FilterSuggestionBuilder.FilterSuggestionBuilder(searchKeys, NetworkLogView.sortSearchValues); this.resetSuggestionBuilder(); this.dataGrid = this.columnsInternal.dataGrid(); this.setupDataGrid(); this.columnsInternal.sortByCurrentColumn(); filterBar.filterButton().addEventListener( UI.Toolbar.ToolbarButton.Events.Click, this.dataGrid.scheduleUpdate.bind(this.dataGrid, true /* isFromUser */)); this.summaryToolbarInternal = new UI.Toolbar.Toolbar('network-summary-bar', this.element); this.summaryToolbarInternal.element.setAttribute('role', 'status'); new UI.DropTarget.DropTarget( this.element, [UI.DropTarget.Type.File], i18nString(UIStrings.dropHarFilesHere), this.handleDrop.bind(this)); Common.Settings.Settings.instance() .moduleSetting('networkColorCodeResourceTypes') .addChangeListener(this.invalidateAllItems.bind(this, false), this); SDK.TargetManager.TargetManager.instance().observeModels(SDK.NetworkManager.NetworkManager, this, {scoped: true}); Logs.NetworkLog.NetworkLog.instance().addEventListener( Logs.NetworkLog.Events.RequestAdded, this.onRequestUpdated, this); Logs.NetworkLog.NetworkLog.instance().addEventListener( Logs.NetworkLog.Events.RequestUpdated, this.onRequestUpdated, this); Logs.NetworkLog.NetworkLog.instance().addEventListener(Logs.NetworkLog.Events.Reset, this.reset, this); this.updateGroupByFrame(); Common.Settings.Settings.instance() .moduleSetting('network.group-by-frame') .addChangeListener(() => this.updateGroupByFrame()); this.filterBar = filterBar; this.textFilterSetting = Common.Settings.Settings.instance().createSetting('networkTextFilter', ''); if (this.textFilterSetting.get()) { this.textFilterUI.setValue(this.textFilterSetting.get()); } } private updateGroupByFrame(): void { const value = Common.Settings.Settings.instance().moduleSetting('network.group-by-frame').get(); this.setGrouping(value ? 'Frame' : null); } private static sortSearchValues(key: string, values: string[]): void { if (key === NetworkForward.UIFilter.FilterType.Priority) { values.sort((a, b) => { const aPriority = (PerfUI.NetworkPriorities.uiLabelToNetworkPriority(a) as Protocol.Network.ResourcePriority); const bPriority = (PerfUI.NetworkPriorities.uiLabelToNetworkPriority(b) as Protocol.Network.ResourcePriority); return PerfUI.NetworkPriorities.networkPriorityWeight(aPriority) - PerfUI.NetworkPriorities.networkPriorityWeight(bPriority); }); } else { values.sort(); } } private static negativeFilter(filter: Filter, request: SDK.NetworkRequest.NetworkRequest): boolean { return !filter(request); } private static requestPathFilter(regex: RegExp|null, request: SDK.NetworkRequest.NetworkRequest): boolean { if (!regex) { return false; } return regex.test(request.path() + '/' + request.name()); } private static subdomains(domain: string): string[] { const result = [domain]; let indexOfPeriod = domain.indexOf('.'); while (indexOfPeriod !== -1) { result.push('*' + domain.substring(indexOfPeriod)); indexOfPeriod = domain.indexOf('.', indexOfPeriod + 1); } return result; } private static createRequestDomainFilter(value: string): Filter { const escapedPattern = value.split('*').map(Platform.StringUtilities.escapeForRegExp).join('.*'); return NetworkLogView.requestDomainFilter.bind(null, new RegExp('^' + escapedPattern + '$', 'i')); } private static requestDomainFilter(regex: RegExp, request: SDK.NetworkRequest.NetworkRequest): boolean { return regex.test(request.domain); } private static runningRequestFilter(request: SDK.NetworkRequest.NetworkRequest): boolean { return !request.finished; } private static fromCacheRequestFilter(request: SDK.NetworkRequest.NetworkRequest): boolean { return request.cached(); } private static interceptedByServiceWorkerFilter(request: SDK.NetworkRequest.NetworkRequest): boolean { return request.fetchedViaServiceWorker; } private static initiatedByServiceWorkerFilter(request: SDK.NetworkRequest.NetworkRequest): boolean { return request.initiatedByServiceWorker(); } private static requestResponseHeaderFilter(value: string, request: SDK.NetworkRequest.NetworkRequest): boolean { return request.responseHeaderValue(value) !== undefined; } private static requestResponseHeaderSetCookieFilter(value: string, request: SDK.NetworkRequest.NetworkRequest): boolean { // Multiple Set-Cookie headers in the request are concatenated via space. Only // filter via `includes` instead of strict equality. return Boolean(request.responseHeaderValue('Set-Cookie')?.includes(value)); } private static requestMethodFilter(value: string, request: SDK.NetworkRequest.NetworkRequest): boolean { return request.requestMethod === value; } private static requestPriorityFilter(value: string, request: SDK.NetworkRequest.NetworkRequest): boolean { return request.priority() === value; } private static requestMimeTypeFilter(value: string, request: SDK.NetworkRequest.NetworkRequest): boolean { return request.mimeType === value; } private static requestMixedContentFilter( value: NetworkForward.UIFilter.MixedContentFilterValues, request: SDK.NetworkRequest.NetworkRequest): boolean { if (value === NetworkForward.UIFilter.MixedContentFilterValues.Displayed) { return request.mixedContentType === Protocol.Security.MixedContentType.OptionallyBlockable; } if (value === NetworkForward.UIFilter.MixedContentFilterValues.Blocked) { return request.mixedContentType === Protocol.Security.MixedContentType.Blockable && request.wasBlocked(); } if (value === NetworkForward.UIFilter.MixedContentFilterValues.BlockOverridden) { return request.mixedContentType === Protocol.Security.MixedContentType.Blockable && !request.wasBlocked(); } if (value === NetworkForward.UIFilter.MixedContentFilterValues.All) { return request.mixedContentType !== Protocol.Security.MixedContentType.None; } return false; } private static requestSchemeFilter(value: string, request: SDK.NetworkRequest.NetworkRequest): boolean { return request.scheme === value; } private static requestCookieDomainFilter(value: string, request: SDK.NetworkRequest.NetworkRequest): boolean { return request.allCookiesIncludingBlockedOnes().some(cookie => cookie.domain() === value); } private static requestCookieNameFilter(value: string, request: SDK.NetworkRequest.NetworkRequest): boolean { return request.allCookiesIncludingBlockedOnes().some(cookie => cookie.name() === value); } private static requestCookiePathFilter(value: string, request: SDK.NetworkRequest.NetworkRequest): boolean { return request.allCookiesIncludingBlockedOnes().some(cookie => cookie.path() === value); } private static requestCookieValueFilter(value: string, request: SDK.NetworkRequest.NetworkRequest): boolean { return request.allCookiesIncludingBlockedOnes().some(cookie => cookie.value() === value); } private static requestSetCookieDomainFilter(value: string, request: SDK.NetworkRequest.NetworkRequest): boolean { return request.responseCookies.some(cookie => cookie.domain() === value); } private static requestSetCookieNameFilter(value: string, request: SDK.NetworkRequest.NetworkRequest): boolean { return request.responseCookies.some(cookie => cookie.name() === value); } private static requestSetCookieValueFilter(value: string, request: SDK.NetworkRequest.NetworkRequest): boolean { return request.responseCookies.some(cookie => cookie.value() === value); } private static requestSizeLargerThanFilter(value: number, request: SDK.NetworkRequest.NetworkRequest): boolean { return request.transferSize >= value; } private static statusCodeFilter(value: string, request: SDK.NetworkRequest.NetworkRequest): boolean { return (String(request.statusCode)) === value; } static getHTTPRequestsFilter(request: SDK.NetworkRequest.NetworkRequest): boolean { return request.parsedURL.isValid && (request.scheme in HTTPSchemas); } private static resourceTypeFilter(value: string, request: SDK.NetworkRequest.NetworkRequest): boolean { return request.resourceType().name() === value; } private static requestUrlFilter(value: string, request: SDK.NetworkRequest.NetworkRequest): boolean { const regex = new RegExp(Platform.StringUtilities.escapeForRegExp(value), 'i'); return regex.test(request.url()); } private static requestTimeFilter(windowStart: number, windowEnd: number, request: SDK.NetworkRequest.NetworkRequest): boolean { if (request.issueTime() > windowEnd) { return false; } if (request.endTime !== -1 && request.endTime < windowStart) { return false; } return true; } private static copyRequestHeaders(request: SDK.NetworkRequest.NetworkRequest): void { Host.InspectorFrontendHost.InspectorFrontendHostInstance.copyText(request.requestHeadersText()); } private static copyResponseHeaders(request: SDK.NetworkRequest.NetworkRequest): void { Host.InspectorFrontendHost.InspectorFrontendHostInstance.copyText(request.responseHeadersText); } private static async copyResponse(request: SDK.NetworkRequest.NetworkRequest): Promise<void> { const contentData = await request.contentData(); let content: (string|null)|string = contentData.content || ''; if (!request.contentType().isTextType()) { content = TextUtils.ContentProvider.contentAsDataURL(content, request.mimeType, contentData.encoded); } else if (contentData.encoded && content) { content = window.atob(content); } Host.InspectorFrontendHost.InspectorFrontendHostInstance.copyText(content); } private handleDrop(dataTransfer: DataTransfer): void { const items = dataTransfer.items; if (!items.length) { return; } const file = items[0].getAsFile(); if (file) { void this.onLoadFromFile(file); } } async onLoadFromFile(file: File): Promise<void> { const outputStream = new Common.StringOutputStream.StringOutputStream(); const reader = new Bindings.FileUtils.ChunkedFileReader(file, /* chunkSize */ 10000000); const success = await reader.read(outputStream); if (!success) { const error = reader.error(); if (error) { this.harLoadFailed(error.message); } return; } let harRoot; try { // HARRoot and JSON.parse might throw. harRoot = new HAR.HARFormat.HARRoot(JSON.parse(outputStream.data())); } catch (e) { this.harLoadFailed(e); return; } Logs.NetworkLog.NetworkLog.instance().importRequests(HAR.Importer.Importer.requestsFromHARLog(harRoot.log)); } private harLoadFailed(message: string): void { Common.Console.Console.instance().error('Failed to load HAR file with following error: ' + message); } private setGrouping(groupKey: string|null): void { if (this.activeGroupLookup) { this.activeGroupLookup.reset(); } const groupLookup = groupKey ? this.groupLookups.get(groupKey) || null : null; this.activeGroupLookup = groupLookup; this.invalidateAllItems(); } private computeRowHeight(): number { return Math.round(this.rawRowHeight * window.devicePixelRatio) / window.devicePixelRatio; } nodeForRequest(request: SDK.NetworkRequest.NetworkRequest): NetworkRequestNode|null { return networkRequestToNode.get(request) || null; } headerHeight(): number { return this.headerHeightInternal; } setRecording(recording: boolean): void { this.recording = recording; this.updateSummaryBar(); } columns(): NetworkLogViewColumns { return this.columnsInternal; } summaryToolbar(): UI.Toolbar.Toolbar { return this.summaryToolbarInternal; } modelAdded(networkManager: SDK.NetworkManager.NetworkManager): void { // TODO(allada) Remove dependency on networkManager and instead use NetworkLog and PageLoad for needed data. const target = networkManager.target(); if (target.outermostTarget() !== target) { return; } const resourceTreeModel = target.model(SDK.ResourceTreeModel.ResourceTreeModel); if (resourceTreeModel) { resourceTreeModel.addEventListener(SDK.ResourceTreeModel.Events.Load, this.loadEventFired, this); resourceTreeModel.addEventListener( SDK.ResourceTreeModel.Events.DOMContentLoaded, this.domContentLoadedEventFired, this); } const targetManager = SDK.TargetManager.TargetManager.instance(); for (const request of Logs.NetworkLog.NetworkLog.instance().requests()) { if (targetManager.isInScope(SDK.NetworkManager.NetworkManager.forRequest(request))) { this.refreshRequest(request); } } } modelRemoved(networkManager: SDK.NetworkManager.NetworkManager): void { const target = networkManager.target(); if (target.outermostTarget() !== target) { return; } const resourceTreeModel = target.model(SDK.ResourceTreeModel.ResourceTreeModel); if (resourceTreeModel) { resourceTreeModel.removeEventListener(SDK.ResourceTreeModel.Events.Load, this.loadEventFired, this); resourceTreeModel.removeEventListener( SDK.ResourceTreeModel.Events.DOMContentLoaded, this.domContentLoadedEventFired, this); } const preserveLog = Common.Settings.Settings.instance().moduleSetting('network_log.preserve-log').get(); if (!preserveLog) { this.reset(); } } linkifier(): Components.Linkifier.Linkifier { return this.linkifierInternal; } setWindow(start: number, end: number): void { if (!start && !end) { this.timeFilter = null; this.timeCalculatorInternal.setWindow(null); } else { this.timeFilter = NetworkLogView.requestTimeFilter.bind(null, start, end); this.timeCalculatorInternal.setWindow(new NetworkTimeBoundary(start, end)); } this.filterRequests(); } resetFocus(): void { this.dataGrid.element.focus(); } private resetSuggestionBuilder(): void { this.suggestionBuilder.clear(); this.suggestionBuilder.addItem(NetworkForward.UIFilter.FilterType.Is, NetworkForward.UIFilter.IsFilterType.Running); this.suggestionBuilder.addItem( NetworkForward.UIFilter.FilterType.Is, NetworkForward.UIFilter.IsFilterType.FromCache); this.suggestionBuilder.addItem( NetworkForward.UIFilter.FilterType.Is, NetworkForward.UIFilter.IsFilterType.ServiceWorkerIntercepted); this.suggestionBuilder.addItem( NetworkForward.UIFilter.FilterType.Is, NetworkForward.UIFilter.IsFilterType.ServiceWorkerInitiated); this.suggestionBuilder.addItem(NetworkForward.UIFilter.FilterType.LargerThan, '100'); this.suggestionBuilder.addItem(NetworkForward.UIFilter.FilterType.LargerThan, '10k'); this.suggestionBuilder.addItem(NetworkForward.UIFilter.FilterType.LargerThan, '1M'); this.textFilterUI.setSuggestionProvider(this.suggestionBuilder.completions.bind(this.suggestionBuilder)); } private filterChanged(): void { this.removeAllNodeHighlights(); this.parseFilterQuery(this.textFilterUI.value(), this.invertFilterUI.checked()); this.filterRequests(); this.textFilterSetting.set(this.textFilterUI.value()); } async resetFilter(): Promise<void> { this.textFilterUI.clear(); } private showRecordingHint(): void { this.hideRecordingHint(); this.recordingHint = this.element.createChild('div', 'network-status-pane fill'); const hintText = this.recordingHint.createChild('div', 'recording-hint'); if (this.recording) { let reloadShortcutNode: Element|null = null; const reloadShortcut = UI.ShortcutRegistry.ShortcutRegistry.instance().shortcutsForAction('inspector_main.reload')[0]; if (reloadShortcut) { reloadShortcutNode = this.recordingHint.createChild('b'); reloadShortcutNode.textContent = reloadShortcut.title(); } const recordingText = hintText.createChild('span'); recordingText.textContent = i18nString(UIStrings.recordingNetworkActivity); if (reloadShortcutNode) { hintText.createChild('br'); hintText.appendChild(i18n.i18n.getFormatLocalizedString( str_, UIStrings.performARequestOrHitSToRecordThe, {PH1: reloadShortcutNode})); } } else { const recordNode = hintText.createChild('b'); recordNode.textContent = UI.ShortcutRegistry.ShortcutRegistry.instance().shortcutTitleForAction('network.toggle-recording') || ''; hintText.appendChild( i18n.i18n.getFormatLocalizedString(str_, UIStrings.recordToDisplayNetworkActivity, {PH1: recordNode})); } hintText.createChild('br'); hintText.appendChild(UI.XLink.XLink.create( 'https://developer.chrome.com/docs/devtools/network/?utm_source=devtools&utm_campaign=2019Q1', i18nString(UIStrings.learnMore))); this.setHidden(true); } private hideRecordingHint(): void { this.setHidden(false); if (this.recordingHint) { this.recordingHint.remove(); } UI.ARIAUtils.alert(i18nString(UIStrings.networkDataAvailable)); this.recordingHint = null; } private setHidden(value: boolean): void { this.columnsInternal.setHidden(value); UI.ARIAUtils.setHidden(this.summaryToolbarInternal.element, value); } override elementsToRestoreScrollPositionsFor(): Element[] { if (!this.dataGrid) // Not initialized yet. { return []; } return [this.dataGrid.scrollContainer]; } columnExtensionResolved(): void { this.invalidateAllItems(true); } private setupDataGrid(): DataGrid.SortableDataGrid.SortableDataGrid<NetworkNode> { this.dataGrid.setRowContextMenuCallback((contextMenu, node) => { const request = (node as NetworkNode).request(); if (request) { this.handleContextMenuForRequest(contextMenu, request); } }); this.dataGrid.setStickToBottom(true); this.dataGrid.setName('networkLog'); this.dataGrid.setResizeMethod(DataGrid.DataGrid.ResizeMethod.Last); this.dataGrid.element.classList.add('network-log-grid'); this.dataGrid.element.addEventListener('mousedown', this.dataGridMouseDown.bind(this), true); this.dataGrid.element.addEventListener('mousemove', this.dataGridMouseMove.bind(this), true); this.dataGrid.element.addEventListener('mouseleave', () => this.setHoveredNode(null), true); this.dataGrid.element.addEventListener('keydown', event => { if (event.key === 'ArrowRight' && this.dataGrid.selectedNode) { const initiatorLink = this.dataGrid.selectedNode.element().querySelector('span.devtools-link'); if (initiatorLink) { (initiatorLink as HTMLElement).focus(); } } if (Platform.KeyboardUtilities.isEnterOrSpaceKey(event)) { this.dispatchEventToListeners(Events.RequestActivated, {showPanel: true, takeFocus: true}); event.consume(true); } }); this.dataGrid.element.addEventListener('keyup', event => { if ((event.key === 'r' || event.key === 'R') && this.dataGrid.selectedNode) { const request = (this.dataGrid.selectedNode as NetworkNode).request(); if (!request) { return; } if (SDK.NetworkManager.NetworkManager.canReplayRequest(request)) { SDK.NetworkManager.NetworkManager.replayRequest(request); } } }); this.dataGrid.element.addEventListener('focus', this.onDataGridFocus.bind(this), true); this.dataGrid.element.addEventListener('blur', this.onDataGridBlur.bind(this), true); return this.dataGrid; } private dataGridMouseMove(event: Event): void { const mouseEvent = (event as MouseEvent); const node = (this.dataGrid.dataGridNodeFromNode((mouseEvent.target as Node))); const highlightInitiatorChain = mouseEvent.shiftKey; this.setHoveredNode(node as NetworkNode, highlightInitiatorChain); } hoveredNode(): NetworkNode|null { return this.hoveredNodeInternal; } private setHoveredNode(node: NetworkNode|null, highlightInitiatorChain?: boolean): void { if (this.hoveredNodeInternal) { this.hoveredNodeInternal.setHovered(false, false); } this.hoveredNodeInternal = node; if (this.hoveredNodeInternal) { this.hoveredNodeInternal.setHovered(true, Boolean(highlightInitiatorChain)); } } private dataGridMouseDown(event: Event): void { const mouseEvent = (event as MouseEvent); if (!this.dataGrid.selectedNode && mouseEvent.button) { mouseEvent.consume(); } } private updateSummaryBar(): void { this.hideRecordingHint(); let transferSize = 0; let resourceSize = 0; let selectedNodeNumber = 0; let selectedTransferSize = 0; let selectedResourceSize = 0; let baseTime = -1; let maxTime = -1; let nodeCount = 0; for (const request of Logs.NetworkLog.NetworkLog.instance().requests()) { const node = networkRequestToNode.get(request); if (!node) { continue; } nodeCount++; const requestTransferSize = request.transferSize; transferSize += requestTransferSize; const requestResourceSize = request.resourceSize; resourceSize += requestResourceSize; if (!filteredNetworkRequests.has(node)) { selectedNodeNumber++; selectedTransferSize += requestTransferSize; selectedResourceSize += requestResourceSize; } const networkManager = SDK.NetworkManager.NetworkManager.forRequest(request); // TODO(allada) inspectedURL should be stored in PageLoad used instead of target so HAR requests can have an // inspected url. if (networkManager && request.url() === networkManager.target().inspectedURL() && request.resourceType() === Common.ResourceType.resourceTypes.Document && networkManager.target().parentTarget()?.type() !== SDK.Target.Type.Frame) { baseTime = request.startTime; } if (request.endTime > maxTime) { maxTime = request.endTime; } } if (!nodeCount) { this.showRecordingHint(); return; } this.summaryToolbarInternal.removeToolbarItems(); const appendChunk = (chunk: string, title?: string): HTMLDivElement => { const toolbarText = new UI.Toolbar.ToolbarText(chunk); toolbarText.setTitle(title ? title : chunk); this.summaryToolbarInternal.appendToolbarItem(toolbarText); return toolbarText.element as HTMLDivElement; }; if (selectedNodeNumber !== nodeCount) { appendChunk(i18nString(UIStrings.sSRequests, {PH1: selectedNodeNumber, PH2: nodeCount})); this.summaryToolbarInternal.appendSeparator(); appendChunk( i18nString(UIStrings.sSTransferred, { PH1: Platform.NumberUtilities.bytesToString(selectedTransferSize), PH2: Platform.NumberUtilities.bytesToString(transferSize), }), i18nString(UIStrings.sBSBTransferredOverNetwork, {PH1: selectedTransferSize, PH2: transferSize})); this.summaryToolbarInternal.appendSeparator(); appendChunk( i18nString(UIStrings.sSResources, { PH1: Platform.NumberUtilities.bytesToString(selectedResourceSize), PH2: Platform.NumberUtilities.bytesToString(resourceSize), }), i18nString(UIStrings.sBSBResourcesLoadedByThePage, {PH1: selectedResourceSize, PH2: resourceSize})); } else { appendChunk(i18nString(UIStrings.sRequests, {PH1: nodeCount})); this.summaryToolbarInternal.appendSeparator(); appendChunk( i18nString(UIStrings.sTransferred, {PH1: Platform.NumberUtilities.bytesToString(transferSize)}), i18nString(UIStrings.sBTransferredOverNetwork, {PH1: transferSize})); this.summaryToolbarInternal.appendSeparator(); appendChunk( i18nString(UIStrings.sResources, {PH1: Platform.NumberUtilities.bytesToString(resourceSize)}), i18nString(UIStrings.sBResourcesLoadedByThePage, {PH1: resourceSize})); } if (baseTime !== -1 && maxTime !== -1) { this.summaryToolbarInternal.appendSeparator(); appendChunk(i18nString(UIStrings.finishS, {PH1: i18n.TimeUtilities.secondsToString(maxTime - baseTime)})); if (this.mainRequestDOMContentLoadedTime !== -1 && this.mainRequestDOMContentLoadedTime > baseTime) { this.summaryToolbarInternal.appendSeparator(); const domContentLoadedText = i18nString( UIStrings.domcontentloadedS, {PH1: i18n.TimeUtilities.secondsToString(this.mainRequestDOMContentLoadedTime - baseTime)}); appendChunk(domContentLoadedText).style.color = `var(${NetworkLogView.getDCLEventColor()})`; } if (this.mainRequestLoadTime !== -1) { this.summaryToolbarInternal.appendSeparator(); const loadText = i18nString(UIStrings.loadS, {PH1: i18n.TimeUtilities.secondsToString(this.mainRequestLoadTime - baseTime)}); appendChunk(loadText).style.color = `var(${NetworkLogView.getLoadEventColor()})`; } } } scheduleRefresh(): void { if (this.needsRefresh) { return; } this.needsRefresh = true; if (this.isShowing()) { void coordinator.write(this.refresh.bind(this)); } } addFilmStripFrames(times: number[]): void { this.columnsInternal.addEventDividers(times, 'network-frame-divider'); } selectFilmStripFrame(time: number): void { this.columnsInternal.selectFilmStripFrame(time); } clearFilmStripFrame(): void { this.columnsInternal.clearFilmStripFrame(); } private refreshIfNeeded(): void { if (this.needsRefresh) { this.refresh(); } } private invalidateAllItems(deferUpdate?: boolean): void { const targetManager = SDK.TargetManager.TargetManager.instance(); this.staleRequests = new Set(Logs.NetworkLog.Netwo