| // Copyright 2021 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| /* eslint-disable @devtools/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 Annotations from '../../models/annotations/annotations.js'; |
| import * as Logs from '../../models/logs/logs.js'; |
| import * as NetworkTimeCalculator from '../../models/network_time_calculator/network_time_calculator.js'; |
| import * as Trace from '../../models/trace/trace.js'; |
| import * as Workspace from '../../models/workspace/workspace.js'; |
| import * as PanelCommon from '../../panels/common/common.js'; |
| import * as NetworkForward from '../../panels/network/forward/forward.js'; |
| import * as Tracing from '../../services/tracing/tracing.js'; |
| import * as PerfUI from '../../ui/legacy/components/perf_ui/perf_ui.js'; |
| import * as SettingsUI from '../../ui/legacy/components/settings_ui/settings_ui.js'; |
| import * as UI from '../../ui/legacy/legacy.js'; |
| import * as VisualLogging from '../../ui/visual_logging/visual_logging.js'; |
| import * as MobileThrottling from '../mobile_throttling/mobile_throttling.js'; |
| import * as Search from '../search/search.js'; |
| |
| import {Events, type RequestActivatedEvent, RequestPanelBehavior} from './NetworkDataGridNode.js'; |
| import {NetworkItemView} from './NetworkItemView.js'; |
| import {NetworkLogView} from './NetworkLogView.js'; |
| import {NetworkOverview} from './NetworkOverview.js'; |
| import networkPanelStyles from './networkPanel.css.js'; |
| import {NetworkSearchScope} from './NetworkSearchScope.js'; |
| |
| const UIStrings = { |
| /** |
| * @description Text to close something |
| */ |
| close: 'Close', |
| /** |
| * @description Title of a search bar or tool |
| */ |
| search: 'Search', |
| /** |
| * @description Tooltip text that appears on the setting to preserve log when hovering over the item |
| */ |
| doNotClearLogOnPageReload: 'Do not clear log on page reload / navigation', |
| /** |
| * @description Text to preserve the log after refreshing |
| */ |
| preserveLog: 'Preserve log', |
| /** |
| * @description Text to disable cache while DevTools is open |
| */ |
| disableCacheWhileDevtoolsIsOpen: 'Disable cache while DevTools is open', |
| /** |
| * @description Text in Network Config View of the Network panel |
| */ |
| disableCache: 'Disable cache', |
| /** |
| * @description Tooltip text that appears when hovering over the largeicon settings gear in show settings pane setting in network panel of the network panel |
| */ |
| networkSettings: 'Network settings', |
| /** |
| * @description Tooltip for expanding network request row setting |
| */ |
| showMoreInformationInRequestRows: 'Show more information in request rows', |
| /** |
| * @description Text in Network Panel used to toggle the "big request rows" setting. |
| */ |
| useLargeRequestRows: 'Big request rows', |
| /** |
| * @description Tooltip text for network request overview setting |
| */ |
| showOverviewOfNetworkRequests: 'Show overview of network requests', |
| /** |
| * @description Text in Network Panel used to show the overview for a given network request. |
| */ |
| showOverview: 'Overview', |
| /** |
| * @description Tooltip for group by frame network setting |
| */ |
| groupRequestsByTopLevelRequest: 'Group requests by top level request frame', |
| /** |
| * @description Text for group by frame network setting |
| */ |
| groupByFrame: 'Group by frame', |
| /** |
| * @description Tooltip for capture screenshot network setting |
| */ |
| captureScreenshotsWhenLoadingA: 'Capture screenshots when loading a page', |
| /** |
| * @description Text to take screenshots |
| */ |
| captureScreenshots: 'Screenshots', |
| /** |
| * @description Tooltip text that appears when hovering over the largeicon load button in the |
| * Network Panel. This action prompts the user to select a HAR file to upload to DevTools. |
| */ |
| importHarFile: 'Import `HAR` file…', |
| /** |
| * @description Tooltip text that appears when hovering over the download button in the Network |
| * panel, when the setting to allow generating HAR files with sensitive data is enabled. HAR is |
| * a file format (HTTP Archive) and should not be translated. This action triggers a context |
| * menu with two options, one to download HAR sanitized and one to download HAR with sensitive |
| * data. |
| */ |
| exportHar: 'Export `HAR` (either sanitized or with sensitive data)', |
| /** |
| * @description Tooltip text that appears when hovering over the download button in the Network |
| * panel, when the setting to allow generating HAR files with sensitive data is disabled. HAR is |
| * a file format (HTTP Archive) and should not be translated. This action triggers the download |
| * of a HAR file. |
| * |
| * This string is also used as the first item in the context menu for the download button in |
| * the Network panel, when the setting to allow generating HAR files with sensitive data is |
| * enabled. |
| */ |
| exportHarSanitized: 'Export `HAR` (sanitized)…', |
| /** |
| * @description Context menu item in the context menu for the download button of the Network panel, |
| * which is only available when the Network setting to allow generating HAR with sensitive data |
| * is active. HAR is a file format (HTTP Archive) and should not be translated. This action |
| * triggers the download of a HAR file with sensitive data included. |
| */ |
| exportHarWithSensitiveData: 'Export `HAR` (with sensitive data)…', |
| /** |
| * @description Text for throttling the network |
| */ |
| throttling: 'Throttling', |
| /** |
| * @description Text in Network Panel to tell the user to reload the page to capture screenshots. |
| * @example {Ctrl + R} PH1 |
| */ |
| hitSToReloadAndCaptureFilmstrip: 'Press {PH1} to reload and capture filmstrip.', |
| /** |
| * @description A context menu item that is shown for resources in other panels |
| * to open them in the Network panel. |
| */ |
| openInNetworkPanel: 'Open in Network panel', |
| /** |
| * @description A context menu item that is shown for resources in other panels |
| * to open them in the Network panel, but when there's no associated network |
| * request. This context menu item is always disabled and only provided to give |
| * the developer an idea of why they cannot open the resource in the Network |
| * panel. |
| */ |
| openInNetworkPanelMissingRequest: 'Open in Network panel (missing request)', |
| /** |
| * @description Text in Network Panel that is displayed whilst the recording is in progress. |
| */ |
| recordingFrames: 'Recording frames…', |
| /** |
| * @description Text in Network Panel that is displayed when frames are being fetched. |
| */ |
| fetchingFrames: 'Fetching frames…', |
| /** |
| * @description Text of a button in the Network panel's toolbar that open Network Conditions panel in the drawer. |
| */ |
| moreNetworkConditions: 'More network conditions…', |
| } as const; |
| const str_ = i18n.i18n.registerUIStrings('panels/network/NetworkPanel.ts', UIStrings); |
| const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_); |
| let networkPanelInstance: NetworkPanel; |
| |
| export class NetworkPanel extends UI.Panel.Panel implements |
| UI.ContextMenu |
| .Provider<SDK.NetworkRequest.NetworkRequest|SDK.Resource.Resource|Workspace.UISourceCode.UISourceCode>, |
| UI.View.ViewLocationResolver { |
| private readonly networkLogShowOverviewSetting: Common.Settings.Setting<boolean>; |
| private readonly networkLogLargeRowsSetting: Common.Settings.Setting<boolean>; |
| private readonly networkRecordFilmStripSetting: Common.Settings.Setting<boolean>; |
| private readonly toggleRecordAction: UI.ActionRegistration.Action; |
| private pendingStopTimer!: number|undefined; |
| networkItemView: NetworkItemView|null; |
| private filmStripView: PerfUI.FilmStripView.FilmStripView|null; |
| private filmStripRecorder: FilmStripRecorder|null; |
| private currentRequest: SDK.NetworkRequest.NetworkRequest|null; |
| private readonly panelToolbar: UI.Toolbar.Toolbar; |
| private readonly rightToolbar: UI.Toolbar.Toolbar; |
| private readonly filterBar: UI.FilterBar.FilterBar; |
| private showSettingsPaneSetting: Common.Settings.Setting<boolean>; |
| private readonly filmStripPlaceholderElement: HTMLElement; |
| private readonly overviewPane: PerfUI.TimelineOverviewPane.TimelineOverviewPane; |
| private readonly networkOverview: NetworkOverview; |
| private readonly overviewPlaceholderElement: HTMLElement; |
| private readonly calculator: NetworkTimeCalculator.NetworkTransferTimeCalculator; |
| private splitWidget: UI.SplitWidget.SplitWidget; |
| private readonly sidebarLocation: UI.View.TabbedViewLocation; |
| private readonly progressBarContainer: HTMLDivElement; |
| networkLogView: NetworkLogView; |
| private readonly fileSelectorElement: HTMLElement; |
| private readonly detailsWidget: UI.Widget.VBox; |
| private readonly closeButtonElement: UI.UIUtils.DevToolsCloseButton; |
| private preserveLogSetting: Common.Settings.Setting<boolean>; |
| recordLogSetting: Common.Settings.Setting<boolean>; |
| private readonly throttlingSelect: UI.Toolbar.ToolbarItem; |
| private readonly displayScreenshotDelay: number; |
| |
| constructor(displayScreenshotDelay: number) { |
| super('network'); |
| this.registerRequiredCSS(networkPanelStyles); |
| |
| this.displayScreenshotDelay = displayScreenshotDelay; |
| this.networkLogShowOverviewSetting = |
| Common.Settings.Settings.instance().createSetting('network-log-show-overview', true); |
| this.networkLogLargeRowsSetting = |
| Common.Settings.Settings.instance().createSetting('network-log-large-rows', false); |
| this.networkRecordFilmStripSetting = |
| Common.Settings.Settings.instance().createSetting('network-record-film-strip-setting', false); |
| this.toggleRecordAction = UI.ActionRegistry.ActionRegistry.instance().getAction('network.toggle-recording'); |
| this.networkItemView = null; |
| this.filmStripView = null; |
| this.filmStripRecorder = null; |
| this.currentRequest = null; |
| |
| const panel = new UI.Widget.VBox(); |
| |
| const networkToolbarContainer = panel.contentElement.createChild('div', 'network-toolbar-container'); |
| networkToolbarContainer.role = 'toolbar'; |
| this.panelToolbar = networkToolbarContainer.createChild('devtools-toolbar'); |
| this.panelToolbar.role = 'presentation'; |
| this.panelToolbar.wrappable = true; |
| this.panelToolbar.setAttribute('jslog', `${VisualLogging.toolbar('network-main')}`); |
| this.rightToolbar = networkToolbarContainer.createChild('devtools-toolbar'); |
| this.rightToolbar.role = 'presentation'; |
| |
| this.filterBar = new UI.FilterBar.FilterBar('network-panel', true); |
| this.filterBar.show(panel.contentElement); |
| this.filterBar.addEventListener(UI.FilterBar.FilterBarEvents.CHANGED, this.handleFilterChanged.bind(this)); |
| |
| const settingsPane = panel.contentElement.createChild('div', 'network-settings-pane'); |
| settingsPane.append( |
| SettingsUI.SettingsUI.createSettingCheckbox( |
| i18nString(UIStrings.useLargeRequestRows), this.networkLogLargeRowsSetting, |
| i18nString(UIStrings.showMoreInformationInRequestRows)), |
| SettingsUI.SettingsUI.createSettingCheckbox( |
| i18nString(UIStrings.groupByFrame), |
| Common.Settings.Settings.instance().moduleSetting('network.group-by-frame'), |
| i18nString(UIStrings.groupRequestsByTopLevelRequest)), |
| SettingsUI.SettingsUI.createSettingCheckbox( |
| i18nString(UIStrings.showOverview), this.networkLogShowOverviewSetting, |
| i18nString(UIStrings.showOverviewOfNetworkRequests)), |
| SettingsUI.SettingsUI.createSettingCheckbox( |
| i18nString(UIStrings.captureScreenshots), this.networkRecordFilmStripSetting, |
| i18nString(UIStrings.captureScreenshotsWhenLoadingA)), |
| |
| ); |
| this.showSettingsPaneSetting = |
| Common.Settings.Settings.instance().createSetting('network-show-settings-toolbar', false); |
| settingsPane.classList.toggle('hidden', !this.showSettingsPaneSetting.get()); |
| this.showSettingsPaneSetting.addChangeListener( |
| () => settingsPane.classList.toggle('hidden', !this.showSettingsPaneSetting.get())); |
| |
| this.filmStripPlaceholderElement = panel.contentElement.createChild('div', 'network-film-strip-placeholder'); |
| |
| // Create top overview component. |
| this.overviewPane = new PerfUI.TimelineOverviewPane.TimelineOverviewPane('network'); |
| this.overviewPane.addEventListener( |
| PerfUI.TimelineOverviewPane.Events.OVERVIEW_PANE_WINDOW_CHANGED, this.onWindowChanged.bind(this)); |
| this.overviewPane.element.id = 'network-overview-panel'; |
| this.networkOverview = new NetworkOverview(); |
| this.overviewPane.setOverviewControls([this.networkOverview]); |
| this.overviewPlaceholderElement = panel.contentElement.createChild('div'); |
| |
| this.calculator = new NetworkTimeCalculator.NetworkTransferTimeCalculator(); |
| |
| this.splitWidget = new UI.SplitWidget.SplitWidget(true, false, 'network-panel-split-view-state'); |
| this.splitWidget.hideMain(); |
| this.splitWidget.show(panel.contentElement); |
| |
| panel.setDefaultFocusedChild(this.filterBar); |
| |
| const initialSidebarWidth = 225; |
| const splitWidget = new UI.SplitWidget.SplitWidget(true, false, 'network-panel-sidebar-state', initialSidebarWidth); |
| splitWidget.hideSidebar(); |
| splitWidget.enableShowModeSaving(); |
| splitWidget.show(this.element); |
| this.sidebarLocation = UI.ViewManager.ViewManager.instance().createTabbedLocation(async () => { |
| void UI.ViewManager.ViewManager.instance().showView('network'); |
| splitWidget.showBoth(); |
| }, 'network-sidebar', true); |
| const tabbedPane = this.sidebarLocation.tabbedPane(); |
| tabbedPane.setMinimumSize(100, 25); |
| tabbedPane.element.classList.add('network-tabbed-pane'); |
| tabbedPane.element.addEventListener('keydown', event => { |
| if (event.key !== Platform.KeyboardUtilities.ESCAPE_KEY) { |
| return; |
| } |
| splitWidget.hideSidebar(); |
| event.consume(); |
| void VisualLogging.logKeyDown(event.currentTarget, event, 'hide-sidebar'); |
| }); |
| const closeSidebar = new UI.Toolbar.ToolbarButton(i18nString(UIStrings.close), 'cross'); |
| closeSidebar.addEventListener(UI.Toolbar.ToolbarButton.Events.CLICK, () => splitWidget.hideSidebar()); |
| closeSidebar.element.setAttribute('jslog', `${VisualLogging.close().track({click: true})}`); |
| tabbedPane.rightToolbar().appendToolbarItem(closeSidebar); |
| splitWidget.setSidebarWidget(tabbedPane); |
| splitWidget.setMainWidget(panel); |
| splitWidget.setDefaultFocusedChild(panel); |
| this.setDefaultFocusedChild(splitWidget); |
| |
| this.progressBarContainer = document.createElement('div'); |
| |
| this.networkLogView = |
| new NetworkLogView(this.filterBar, this.progressBarContainer, this.networkLogLargeRowsSetting); |
| this.splitWidget.setSidebarWidget(this.networkLogView); |
| this.fileSelectorElement = |
| (UI.UIUtils.createFileSelectorElement(this.networkLogView.onLoadFromFile.bind(this.networkLogView)) as |
| HTMLElement); |
| panel.element.appendChild(this.fileSelectorElement); |
| |
| if (Annotations.AnnotationRepository.annotationsEnabled()) { |
| const dataGrid = this.networkLogView.getDataGrid(); |
| if (dataGrid) { |
| PanelCommon.AnnotationManager.instance().initializePlacementForAnnotationType( |
| Annotations.AnnotationType.NETWORK_REQUEST, this.resolveInitialState.bind(this), dataGrid.scrollContainer); |
| } |
| } |
| |
| this.detailsWidget = new UI.Widget.VBox(); |
| this.detailsWidget.element.classList.add('network-details-view'); |
| this.splitWidget.setMainWidget(this.detailsWidget); |
| |
| this.closeButtonElement = document.createElement('dt-close-button'); |
| this.closeButtonElement.addEventListener('click', async () => { |
| const action = UI.ActionRegistry.ActionRegistry.instance().getAction('network.hide-request-details'); |
| await action.execute(); |
| }, false); |
| this.closeButtonElement.style.margin = '0 5px'; |
| |
| this.networkLogShowOverviewSetting.addChangeListener(this.toggleShowOverview, this); |
| this.networkLogLargeRowsSetting.addChangeListener(this.toggleLargerRequests, this); |
| this.networkRecordFilmStripSetting.addChangeListener(this.toggleRecordFilmStrip, this); |
| |
| this.preserveLogSetting = Common.Settings.Settings.instance().moduleSetting('network-log.preserve-log'); |
| this.recordLogSetting = Common.Settings.Settings.instance().moduleSetting('network-log.record-log'); |
| this.recordLogSetting.addChangeListener(({data}) => this.toggleRecord(data)); |
| |
| this.throttlingSelect = this.createThrottlingConditionsSelect(); |
| this.setupToolbarButtons(splitWidget); |
| |
| this.toggleRecord(this.recordLogSetting.get()); |
| this.toggleShowOverview(); |
| this.toggleLargerRequests(); |
| this.toggleRecordFilmStrip(); |
| this.updateUI(); |
| |
| SDK.TargetManager.TargetManager.instance().addModelListener( |
| SDK.ResourceTreeModel.ResourceTreeModel, SDK.ResourceTreeModel.Events.WillReloadPage, this.willReloadPage, this, |
| {scoped: true}); |
| SDK.TargetManager.TargetManager.instance().addModelListener( |
| SDK.ResourceTreeModel.ResourceTreeModel, SDK.ResourceTreeModel.Events.Load, this.load, this, {scoped: true}); |
| this.networkLogView.addEventListener(Events.RequestSelected, this.onRequestSelected, this); |
| this.networkLogView.addEventListener(Events.RequestActivated, this.onRequestActivated, this); |
| Logs.NetworkLog.NetworkLog.instance().addEventListener( |
| Logs.NetworkLog.Events.RequestAdded, this.onUpdateRequest, this); |
| Logs.NetworkLog.NetworkLog.instance().addEventListener( |
| Logs.NetworkLog.Events.RequestUpdated, this.onUpdateRequest, this); |
| Logs.NetworkLog.NetworkLog.instance().addEventListener(Logs.NetworkLog.Events.Reset, this.onNetworkLogReset, this); |
| } |
| |
| static instance(opts?: { |
| forceNew: boolean, |
| displayScreenshotDelay?: number, |
| }): NetworkPanel { |
| if (!networkPanelInstance || opts?.forceNew) { |
| networkPanelInstance = new NetworkPanel(opts?.displayScreenshotDelay ?? 1000); |
| } |
| |
| return networkPanelInstance; |
| } |
| |
| static async revealAndFilter(filters: Array<{ |
| filterType: NetworkForward.UIFilter.FilterType | null, |
| filterValue: string, |
| }>): Promise<void> { |
| const panel = NetworkPanel.instance(); |
| let filterString = ''; |
| for (const filter of filters) { |
| if (filter.filterType) { |
| filterString += `${filter.filterType}:${filter.filterValue} `; |
| } else { |
| filterString += `${filter.filterValue} `; |
| } |
| } |
| await UI.ViewManager.ViewManager.instance().showView('network'); |
| panel.networkLogView.setTextFilterValue(filterString); |
| panel.filterBar.setting().set(true); |
| panel.filterBar.focus(); |
| } |
| |
| throttlingSelectForTest(): UI.Toolbar.ToolbarItem { |
| return this.throttlingSelect; |
| } |
| |
| private onWindowChanged( |
| event: Common.EventTarget.EventTargetEvent<PerfUI.TimelineOverviewPane.OverviewPaneWindowChangedEvent>): void { |
| const startTime = Math.max(this.calculator.minimumBoundary(), event.data.startTime / 1000); |
| const endTime = Math.min(this.calculator.maximumBoundary(), event.data.endTime / 1000); |
| if (startTime === this.calculator.minimumBoundary() && endTime === this.calculator.maximumBoundary()) { |
| // Reset the filters for NetworkLogView when the window is reset |
| // to its boundaries. This clears the filters and allows the users |
| // to see the incoming requests after they have updated the curtains |
| // to be in the edges. (ex: by double clicking on the overview grid) |
| this.networkLogView.setWindow(0, 0); |
| } else { |
| this.networkLogView.setWindow(startTime, endTime); |
| } |
| } |
| |
| private async searchToggleClick(): Promise<void> { |
| const action = UI.ActionRegistry.ActionRegistry.instance().getAction('network.search'); |
| await action.execute(); |
| } |
| |
| private setupToolbarButtons(splitWidget: UI.SplitWidget.SplitWidget): void { |
| const searchToggle = new UI.Toolbar.ToolbarToggle(i18nString(UIStrings.search), 'search', undefined, 'search'); |
| function updateSidebarToggle(): void { |
| const isSidebarShowing = splitWidget.showMode() !== UI.SplitWidget.ShowMode.ONLY_MAIN; |
| searchToggle.setToggled(isSidebarShowing); |
| if (!isSidebarShowing) { |
| (searchToggle.element as HTMLElement).focus(); |
| } |
| } |
| this.panelToolbar.appendToolbarItem(UI.Toolbar.Toolbar.createActionButton(this.toggleRecordAction)); |
| this.panelToolbar.appendToolbarItem(UI.Toolbar.Toolbar.createActionButton('network.clear')); |
| this.panelToolbar.appendSeparator(); |
| |
| this.panelToolbar.appendToolbarItem(this.filterBar.filterButton()); |
| updateSidebarToggle(); |
| splitWidget.addEventListener(UI.SplitWidget.Events.SHOW_MODE_CHANGED, updateSidebarToggle); |
| searchToggle.addEventListener(UI.Toolbar.ToolbarButton.Events.CLICK, () => { |
| void this.searchToggleClick(); |
| }); |
| this.panelToolbar.appendToolbarItem(searchToggle); |
| this.panelToolbar.appendSeparator(); |
| |
| this.panelToolbar.appendToolbarItem(new UI.Toolbar.ToolbarSettingCheckbox( |
| this.preserveLogSetting, i18nString(UIStrings.doNotClearLogOnPageReload), i18nString(UIStrings.preserveLog))); |
| |
| this.panelToolbar.appendSeparator(); |
| const disableCacheCheckbox = new UI.Toolbar.ToolbarSettingCheckbox( |
| Common.Settings.Settings.instance().moduleSetting('cache-disabled'), |
| i18nString(UIStrings.disableCacheWhileDevtoolsIsOpen), i18nString(UIStrings.disableCache)); |
| this.panelToolbar.appendToolbarItem(disableCacheCheckbox); |
| |
| this.panelToolbar.appendToolbarItem(this.throttlingSelect); |
| |
| const networkConditionsButton = new UI.Toolbar.ToolbarButton( |
| i18nString(UIStrings.moreNetworkConditions), 'network-settings', undefined, 'network-conditions'); |
| networkConditionsButton.addEventListener(UI.Toolbar.ToolbarButton.Events.CLICK, () => { |
| void UI.ViewManager.ViewManager.instance().showView('network.config'); |
| }, this); |
| this.panelToolbar.appendToolbarItem(networkConditionsButton); |
| |
| this.rightToolbar.appendToolbarItem(new UI.Toolbar.ToolbarItem(this.progressBarContainer)); |
| this.rightToolbar.appendSeparator(); |
| this.rightToolbar.appendToolbarItem(new UI.Toolbar.ToolbarSettingToggle( |
| this.showSettingsPaneSetting, 'gear', i18nString(UIStrings.networkSettings), 'gear-filled', |
| 'network-settings')); |
| |
| const exportHarContextMenu = (contextMenu: UI.ContextMenu.ContextMenu): void => { |
| contextMenu.defaultSection().appendItem( |
| i18nString(UIStrings.exportHarSanitized), |
| this.networkLogView.exportAll.bind(this.networkLogView, {sanitize: true}), |
| {jslogContext: 'export-har'}, |
| ); |
| contextMenu.defaultSection().appendItem( |
| i18nString(UIStrings.exportHarWithSensitiveData), |
| this.networkLogView.exportAll.bind(this.networkLogView, {sanitize: false}), |
| {jslogContext: 'export-har-with-sensitive-data'}, |
| ); |
| }; |
| |
| this.panelToolbar.appendSeparator(); |
| const importHarButton = |
| new UI.Toolbar.ToolbarButton(i18nString(UIStrings.importHarFile), 'import', undefined, 'import-har'); |
| importHarButton.addEventListener( |
| UI.Toolbar.ToolbarButton.Events.CLICK, () => this.fileSelectorElement.click(), this); |
| this.panelToolbar.appendToolbarItem(importHarButton); |
| const exportHarButton = |
| new UI.Toolbar.ToolbarButton(i18nString(UIStrings.exportHarSanitized), 'download', undefined, 'export-har'); |
| exportHarButton.addEventListener( |
| UI.Toolbar.ToolbarButton.Events.CLICK, |
| this.networkLogView.exportAll.bind(this.networkLogView, {sanitize: true}), this); |
| this.panelToolbar.appendToolbarItem(exportHarButton); |
| const exportHarMenuButton = new UI.Toolbar.ToolbarMenuButton( |
| exportHarContextMenu, /* isIconDropdown */ true, /* useSoftMenu */ false, 'export-har-menu', 'download'); |
| exportHarMenuButton.setTitle(i18nString(UIStrings.exportHar)); |
| this.panelToolbar.appendToolbarItem(exportHarMenuButton); |
| |
| const networkShowOptionsToGenerateHarWithSensitiveData = Common.Settings.Settings.instance().createSetting( |
| 'network.show-options-to-generate-har-with-sensitive-data', false); |
| const updateShowOptionsToGenerateHarWithSensitiveData = (): void => { |
| const showOptionsToGenerateHarWithSensitiveData = networkShowOptionsToGenerateHarWithSensitiveData.get(); |
| exportHarButton.setVisible(!showOptionsToGenerateHarWithSensitiveData); |
| exportHarMenuButton.setVisible(showOptionsToGenerateHarWithSensitiveData); |
| }; |
| networkShowOptionsToGenerateHarWithSensitiveData.addChangeListener(updateShowOptionsToGenerateHarWithSensitiveData); |
| updateShowOptionsToGenerateHarWithSensitiveData(); |
| } |
| |
| private createThrottlingConditionsSelect(): UI.Toolbar.ToolbarItem { |
| const toolbarItem = new UI.Toolbar.ToolbarItem(document.createElement('div')); |
| toolbarItem.setMaxWidth(160); |
| |
| MobileThrottling.NetworkThrottlingSelector.NetworkThrottlingSelect.createForGlobalConditions( |
| toolbarItem.element, i18nString(UIStrings.throttling)); |
| return toolbarItem; |
| } |
| |
| toggleRecord(toggled: boolean): void { |
| this.toggleRecordAction.setToggled(toggled); |
| if (this.recordLogSetting.get() !== toggled) { |
| this.recordLogSetting.set(toggled); |
| } |
| |
| this.networkLogView.setRecording(toggled); |
| if (!toggled && this.filmStripRecorder) { |
| this.filmStripRecorder.stopRecording(this.filmStripAvailable.bind(this)); |
| } |
| } |
| |
| private filmStripAvailable(filmStrip: Trace.Extras.FilmStrip.Data): void { |
| if (this.filmStripView) { |
| this.filmStripView.setModel(filmStrip); |
| } |
| const timestamps = filmStrip.frames.map(frame => { |
| // The network view works in seconds. |
| return Trace.Helpers.Timing.microToSeconds(frame.screenshotEvent.ts); |
| }); |
| |
| this.networkLogView.addFilmStripFrames(timestamps); |
| } |
| |
| private onNetworkLogReset(event: Common.EventTarget.EventTargetEvent<Logs.NetworkLog.ResetEvent>): void { |
| const {clearIfPreserved} = event.data; |
| if (!this.preserveLogSetting.get() || clearIfPreserved) { |
| this.calculator.reset(); |
| this.overviewPane.reset(); |
| } |
| if (this.filmStripView) { |
| this.resetFilmStripView(); |
| } |
| } |
| |
| private willReloadPage(): void { |
| if (this.pendingStopTimer) { |
| clearTimeout(this.pendingStopTimer); |
| delete this.pendingStopTimer; |
| } |
| if (this.isShowing() && this.filmStripRecorder) { |
| this.filmStripRecorder.startRecording(); |
| } |
| } |
| |
| private load(): void { |
| if (this.filmStripRecorder?.isRecording()) { |
| if (this.pendingStopTimer) { |
| window.clearTimeout(this.pendingStopTimer); |
| } |
| this.pendingStopTimer = window.setTimeout(this.stopFilmStripRecording.bind(this), this.displayScreenshotDelay); |
| } |
| } |
| |
| private stopFilmStripRecording(): void { |
| if (this.filmStripRecorder) { |
| this.filmStripRecorder.stopRecording(this.filmStripAvailable.bind(this)); |
| } |
| delete this.pendingStopTimer; |
| } |
| |
| private toggleLargerRequests(): void { |
| this.updateUI(); |
| } |
| |
| private toggleShowOverview(): void { |
| const toggled = this.networkLogShowOverviewSetting.get(); |
| if (toggled) { |
| this.overviewPane.show(this.overviewPlaceholderElement); |
| } else { |
| this.overviewPane.detach(); |
| } |
| this.doResize(); |
| } |
| |
| private toggleRecordFilmStrip(): void { |
| const toggled = this.networkRecordFilmStripSetting.get(); |
| if (toggled && !this.filmStripRecorder) { |
| this.filmStripView = new PerfUI.FilmStripView.FilmStripView(); |
| this.filmStripView.element.classList.add('network-film-strip'); |
| this.filmStripView.element.setAttribute('jslog', `${VisualLogging.section('film-strip')}`); |
| this.filmStripRecorder = new FilmStripRecorder(this.networkLogView.timeCalculator(), this.filmStripView); |
| this.filmStripView.show(this.filmStripPlaceholderElement); |
| this.filmStripView.addEventListener(PerfUI.FilmStripView.Events.FRAME_SELECTED, this.onFilmFrameSelected, this); |
| this.filmStripView.addEventListener(PerfUI.FilmStripView.Events.FRAME_ENTER, this.onFilmFrameEnter, this); |
| this.filmStripView.addEventListener(PerfUI.FilmStripView.Events.FRAME_EXIT, this.onFilmFrameExit, this); |
| this.resetFilmStripView(); |
| } |
| |
| if (!toggled && this.filmStripRecorder) { |
| if (this.filmStripView) { |
| this.filmStripView.detach(); |
| } |
| this.filmStripView = null; |
| this.filmStripRecorder = null; |
| } |
| } |
| |
| private resetFilmStripView(): void { |
| const reloadShortcut = |
| UI.ShortcutRegistry.ShortcutRegistry.instance().shortcutsForAction('inspector-main.reload')[0]; |
| |
| if (this.filmStripView) { |
| this.filmStripView.reset(); |
| if (reloadShortcut) { |
| this.filmStripView.setStatusText( |
| i18nString(UIStrings.hitSToReloadAndCaptureFilmstrip, {PH1: reloadShortcut.title()})); |
| } |
| } |
| } |
| |
| override elementsToRestoreScrollPositionsFor(): Element[] { |
| return this.networkLogView.elementsToRestoreScrollPositionsFor(); |
| } |
| |
| override wasShown(): void { |
| super.wasShown(); |
| UI.Context.Context.instance().setFlavor(NetworkPanel, this); |
| |
| // Record the network tool load time after the panel has loaded. |
| Host.userMetrics.panelLoaded('network', 'DevTools.Launch.Network'); |
| |
| if (Annotations.AnnotationRepository.annotationsEnabled()) { |
| void PanelCommon.AnnotationManager.instance().resolveAnnotationsOfType( |
| Annotations.AnnotationType.NETWORK_REQUEST); |
| } |
| } |
| |
| override willHide(): void { |
| UI.Context.Context.instance().setFlavor(NetworkPanel, null); |
| super.willHide(); |
| } |
| |
| revealAndHighlightRequest(request: SDK.NetworkRequest.NetworkRequest): void { |
| this.hideRequestPanel(); |
| if (request) { |
| this.networkLogView.revealAndHighlightRequest(request); |
| } |
| } |
| |
| revealAndHighlightRequestWithId(request: NetworkForward.NetworkRequestId.NetworkRequestId): void { |
| this.hideRequestPanel(); |
| if (request) { |
| this.networkLogView.revealAndHighlightRequestWithId(request); |
| } |
| } |
| |
| async selectAndActivateRequest( |
| request: SDK.NetworkRequest.NetworkRequest, shownTab?: NetworkForward.UIRequestLocation.UIRequestTabs, |
| options?: NetworkForward.UIRequestLocation.FilterOptions): Promise<NetworkItemView|null> { |
| await UI.ViewManager.ViewManager.instance().showView('network'); |
| this.networkLogView.selectRequest(request, options); |
| this.showRequestPanel(shownTab); |
| this.networkLogView.revealAndHighlightRequest(request); |
| return this.networkItemView; |
| } |
| |
| private handleFilterChanged(): void { |
| this.hideRequestPanel(); |
| } |
| |
| private onRequestSelected(event: Common.EventTarget.EventTargetEvent<SDK.NetworkRequest.NetworkRequest|null>): void { |
| const request = event.data; |
| this.currentRequest = request; |
| this.networkOverview.setHighlightedRequest(request); |
| this.updateNetworkItemView(); |
| UI.Context.Context.instance().setFlavor(SDK.NetworkRequest.NetworkRequest, request); |
| } |
| |
| private onRequestActivated(event: Common.EventTarget.EventTargetEvent<RequestActivatedEvent>): void { |
| const {showPanel, tab, takeFocus} = event.data; |
| if (showPanel === RequestPanelBehavior.ShowPanel) { |
| this.showRequestPanel(tab, takeFocus); |
| } else if (showPanel === RequestPanelBehavior.HidePanel) { |
| this.hideRequestPanel(); |
| } |
| } |
| |
| private showRequestPanel(shownTab?: NetworkForward.UIRequestLocation.UIRequestTabs, takeFocus?: boolean): void { |
| if (this.splitWidget.showMode() === UI.SplitWidget.ShowMode.BOTH && !shownTab && !takeFocus) { |
| // If panel is already shown, and we are not forcing a specific tab, return. |
| return; |
| } |
| this.clearNetworkItemView(); |
| if (this.currentRequest) { |
| const networkItemView = this.createNetworkItemView(shownTab); |
| if (networkItemView && takeFocus) { |
| networkItemView.focus(); |
| } |
| } |
| this.updateUI(); |
| } |
| |
| hideRequestPanel(): void { |
| this.clearNetworkItemView(); |
| this.splitWidget.hideMain(); |
| this.updateUI(); |
| } |
| |
| private updateNetworkItemView(): void { |
| if (this.splitWidget.showMode() === UI.SplitWidget.ShowMode.BOTH) { |
| this.clearNetworkItemView(); |
| this.createNetworkItemView(); |
| this.updateUI(); |
| } |
| } |
| |
| private clearNetworkItemView(): void { |
| if (this.networkItemView) { |
| this.networkItemView.detach(); |
| this.networkItemView = null; |
| } |
| } |
| private createNetworkItemView(initialTab?: NetworkForward.UIRequestLocation.UIRequestTabs): NetworkItemView |
| |undefined { |
| if (!this.currentRequest) { |
| return; |
| } |
| this.networkItemView = new NetworkItemView(this.currentRequest, this.networkLogView.timeCalculator(), initialTab); |
| this.networkItemView.leftToolbar().appendToolbarItem(new UI.Toolbar.ToolbarItem(this.closeButtonElement)); |
| this.networkItemView.show(this.detailsWidget.element); |
| this.splitWidget.showBoth(); |
| return this.networkItemView; |
| } |
| |
| async resolveInitialState( |
| parentElement: Element, reveal: boolean, lookupId: string, |
| anchor?: SDK.DOMModel.DOMNode|SDK.NetworkRequest.NetworkRequest): Promise<{x: number, y: number}|null> { |
| let request = anchor as SDK.NetworkRequest.NetworkRequest; |
| if (!this.isShowing()) { |
| return null; |
| } |
| |
| if (!request) { |
| const networkManager = |
| SDK.TargetManager.TargetManager.instance().scopeTarget()?.model(SDK.NetworkManager.NetworkManager); |
| if (!networkManager) { |
| return null; |
| } |
| |
| const requests = Logs.NetworkLog.NetworkLog.instance().requestsForId(lookupId); |
| if (requests.length === 0) { |
| console.warn('Network Request list is empty'); |
| return null; |
| } |
| request = requests[0]; |
| } |
| |
| if (reveal) { |
| await Common.Revealer.reveal(request); |
| await this.selectAndActivateRequest(request); |
| } |
| |
| const requestNode = this.networkLogView?.nodeForRequest(request); |
| if (requestNode?.element()) { |
| const targetRect = requestNode.element().getBoundingClientRect(); |
| const parentRect = parentElement.getBoundingClientRect(); |
| const relativeX = 4; |
| const relativeY = targetRect.y - parentRect.y + parentElement.scrollTop; |
| return {x: relativeX, y: relativeY}; |
| } |
| |
| console.warn('Could not find element for request:', anchor); |
| return null; |
| } |
| |
| private updateUI(): void { |
| if (this.detailsWidget) { |
| this.detailsWidget.element.classList.toggle( |
| 'network-details-view-tall-header', this.networkLogLargeRowsSetting.get()); |
| } |
| if (this.networkLogView) { |
| this.networkLogView.switchViewMode(!this.splitWidget.isResizable()); |
| } |
| } |
| |
| appendApplicableItems( |
| this: NetworkPanel, event: Event, contextMenu: UI.ContextMenu.ContextMenu, |
| target: SDK.NetworkRequest.NetworkRequest|SDK.Resource.Resource|Workspace.UISourceCode.UISourceCode| |
| SDK.TraceObject.RevealableNetworkRequest): void { |
| const appendRevealItem = (request: SDK.NetworkRequest.NetworkRequest): void => { |
| contextMenu.revealSection().appendItem( |
| i18nString(UIStrings.openInNetworkPanel), |
| () => UI.ViewManager.ViewManager.instance() |
| .showView('network') |
| .then(this.networkLogView.resetFilter.bind(this.networkLogView)) |
| .then(this.revealAndHighlightRequest.bind(this, request)), |
| {jslogContext: 'reveal-in-network'}); |
| }; |
| const appendRevealItemMissingData = (): void => { |
| contextMenu.revealSection().appendItem(i18nString(UIStrings.openInNetworkPanelMissingRequest), () => {}, { |
| disabled: true, |
| jslogContext: 'reveal-in-network', |
| }); |
| }; |
| const appendRevealItemAndSelect = (request: SDK.TraceObject.RevealableNetworkRequest): void => { |
| contextMenu.revealSection().appendItem( |
| i18nString(UIStrings.openInNetworkPanel), |
| () => UI.ViewManager.ViewManager.instance() |
| .showView('network') |
| .then(this.networkLogView.resetFilter.bind(this.networkLogView)) |
| .then(this.selectAndActivateRequest.bind( |
| this, request.networkRequest, NetworkForward.UIRequestLocation.UIRequestTabs.HEADERS_COMPONENT, |
| /* FilterOptions= */ undefined)), |
| {jslogContext: 'timeline.reveal-in-network'}); |
| }; |
| |
| if ((event.target as Node).isSelfOrDescendant(this.element)) { |
| return; |
| } |
| |
| if (target instanceof SDK.Resource.Resource) { |
| if (target.request) { |
| appendRevealItem(target.request); |
| } else { |
| appendRevealItemMissingData(); |
| } |
| return; |
| } |
| if (target instanceof Workspace.UISourceCode.UISourceCode) { |
| const resource = SDK.ResourceTreeModel.ResourceTreeModel.resourceForURL(target.url()); |
| if (resource?.request) { |
| appendRevealItem(resource.request); |
| } else { |
| appendRevealItemMissingData(); |
| } |
| return; |
| } |
| if (target instanceof SDK.TraceObject.RevealableNetworkRequest) { |
| appendRevealItemAndSelect(target); |
| return; |
| } |
| |
| if (this.networkItemView && this.networkItemView.isShowing() && this.networkItemView.request() === target) { |
| return; |
| } |
| |
| appendRevealItem(target); |
| } |
| |
| private onFilmFrameSelected(event: Common.EventTarget.EventTargetEvent<number>): void { |
| const timestamp = event.data; |
| this.overviewPane.setWindowTimes(Trace.Types.Timing.Milli(0), Trace.Types.Timing.Milli(timestamp)); |
| } |
| |
| private onFilmFrameEnter(event: Common.EventTarget.EventTargetEvent<number>): void { |
| const timestamp = event.data; |
| this.networkOverview.selectFilmStripFrame(timestamp); |
| this.networkLogView.selectFilmStripFrame(timestamp / 1000); |
| } |
| |
| private onFilmFrameExit(): void { |
| this.networkOverview.clearFilmStripFrame(); |
| this.networkLogView.clearFilmStripFrame(); |
| } |
| |
| private onUpdateRequest(event: Common.EventTarget.EventTargetEvent<{request: SDK.NetworkRequest.NetworkRequest}>): |
| void { |
| const {request} = event.data; |
| this.calculator.updateBoundaries(request); |
| // FIXME: Unify all time units across the frontend! |
| this.overviewPane.setBounds( |
| Trace.Types.Timing.Milli(this.calculator.minimumBoundary() * 1000), |
| Trace.Types.Timing.Milli(this.calculator.maximumBoundary() * 1000)); |
| this.networkOverview.updateRequest(request); |
| |
| if (Annotations.AnnotationRepository.annotationsEnabled()) { |
| requestAnimationFrame(() => { |
| void PanelCommon.AnnotationManager.instance().resolveAnnotationsOfType( |
| Annotations.AnnotationType.NETWORK_REQUEST); |
| }); |
| } |
| } |
| |
| resolveLocation(locationName: string): UI.View.ViewLocation|null { |
| if (locationName === 'network-sidebar') { |
| return this.sidebarLocation; |
| } |
| return null; |
| } |
| } |
| |
| export class RequestRevealer implements Common.Revealer.Revealer<SDK.NetworkRequest.NetworkRequest> { |
| reveal(request: SDK.NetworkRequest.NetworkRequest): Promise<void> { |
| const panel = NetworkPanel.instance(); |
| return UI.ViewManager.ViewManager.instance().showView('network').then( |
| panel.revealAndHighlightRequest.bind(panel, request)); |
| } |
| } |
| |
| export class RequestIdRevealer implements Common.Revealer.Revealer<NetworkForward.NetworkRequestId.NetworkRequestId> { |
| reveal(requestId: NetworkForward.NetworkRequestId.NetworkRequestId): Promise<void> { |
| const panel = NetworkPanel.instance(); |
| return UI.ViewManager.ViewManager.instance().showView('network').then( |
| panel.revealAndHighlightRequestWithId.bind(panel, requestId)); |
| } |
| } |
| |
| export class NetworkLogWithFilterRevealer implements |
| Common.Revealer |
| .Revealer<PanelCommon.ExtensionServer.RevealableNetworkRequestFilter|NetworkForward.UIFilter.UIRequestFilter> { |
| reveal(request: PanelCommon.ExtensionServer.RevealableNetworkRequestFilter|NetworkForward.UIFilter.UIRequestFilter): |
| Promise<void> { |
| if ('filters' in request) { |
| return NetworkPanel.revealAndFilter(request.filters); |
| } |
| return NetworkPanel.revealAndFilter(request.filter ? [{filterType: null, filterValue: request.filter}] : []); |
| } |
| } |
| |
| export class FilmStripRecorder implements Tracing.TracingManager.TracingManagerClient { |
| #tracingManager: Tracing.TracingManager.TracingManager|null = null; |
| #resourceTreeModel: SDK.ResourceTreeModel.ResourceTreeModel|null = null; |
| readonly #timeCalculator: NetworkTimeCalculator.NetworkTimeCalculator; |
| readonly #filmStripView: PerfUI.FilmStripView.FilmStripView; |
| #callback: ((filmStrip: Trace.Extras.FilmStrip.Data) => void)|null = null; |
| // Used to fetch screenshots of the page load and show them in the panel. |
| #traceEngine: Trace.TraceModel.Model = Trace.TraceModel.Model.createWithSubsetOfHandlers({ |
| Screenshots: Trace.Handlers.ModelHandlers.Screenshots, |
| }); |
| #collectedTraceEvents: Trace.Types.Events.Event[] = []; |
| |
| constructor( |
| timeCalculator: NetworkTimeCalculator.NetworkTimeCalculator, |
| filmStripView: PerfUI.FilmStripView.FilmStripView, |
| ) { |
| this.#timeCalculator = timeCalculator; |
| this.#filmStripView = filmStripView; |
| } |
| |
| traceEventsCollected(events: Trace.Types.Events.Event[]): void { |
| this.#collectedTraceEvents.push(...events); |
| } |
| |
| async tracingComplete(): Promise<void> { |
| if (!this.#tracingManager) { |
| return; |
| } |
| this.#tracingManager = null; |
| await this.#traceEngine.parse(this.#collectedTraceEvents); |
| |
| const data = this.#traceEngine.parsedTrace(this.#traceEngine.size() - 1)?.data as |
| Trace.Extras.FilmStrip.HandlerDataWithScreenshots | |
| null; |
| if (!data) { |
| return; |
| } |
| const zeroTimeInSeconds = Trace.Types.Timing.Seconds(this.#timeCalculator.minimumBoundary()); |
| const filmStrip = |
| Trace.Extras.FilmStrip.fromHandlerData(data, Trace.Helpers.Timing.secondsToMicro(zeroTimeInSeconds)); |
| |
| if (this.#callback) { |
| this.#callback(filmStrip); |
| } |
| this.#callback = null; |
| // Now we have created the film strip and stored the data, we need to reset |
| // the trace processor so that it is ready to record again if the user |
| // refreshes the page. |
| this.#traceEngine.resetProcessor(); |
| |
| if (this.#resourceTreeModel) { |
| this.#resourceTreeModel.resumeReload(); |
| } |
| this.#resourceTreeModel = null; |
| } |
| |
| tracingBufferUsage(): void { |
| } |
| |
| eventsRetrievalProgress(_progress: number): void { |
| } |
| |
| startRecording(): void { |
| this.#collectedTraceEvents = []; |
| this.#filmStripView.reset(); |
| this.#filmStripView.setStatusText(i18nString(UIStrings.recordingFrames)); |
| const tracingManager = |
| SDK.TargetManager.TargetManager.instance().scopeTarget()?.model(Tracing.TracingManager.TracingManager); |
| if (this.#tracingManager || !tracingManager) { |
| return; |
| } |
| |
| this.#tracingManager = tracingManager; |
| this.#resourceTreeModel = this.#tracingManager.target().model(SDK.ResourceTreeModel.ResourceTreeModel); |
| void this.#tracingManager.start(this, '-*,disabled-by-default-devtools.screenshot'); |
| |
| Host.userMetrics.actionTaken(Host.UserMetrics.Action.FilmStripStartedRecording); |
| } |
| |
| isRecording(): boolean { |
| return Boolean(this.#tracingManager); |
| } |
| |
| stopRecording(callback: (filmStrip: Trace.Extras.FilmStrip.Data) => void): void { |
| if (!this.#tracingManager) { |
| return; |
| } |
| |
| this.#tracingManager.stop(); |
| if (this.#resourceTreeModel) { |
| this.#resourceTreeModel.suspendReload(); |
| } |
| this.#callback = callback; |
| this.#filmStripView.setStatusText(i18nString(UIStrings.fetchingFrames)); |
| } |
| } |
| |
| export class ActionDelegate implements UI.ActionRegistration.ActionDelegate { |
| handleAction(context: UI.Context.Context, actionId: string): boolean { |
| const panel = context.flavor(NetworkPanel); |
| if (panel === null) { |
| return false; |
| } |
| switch (actionId) { |
| case 'network.toggle-recording': { |
| panel.toggleRecord(!panel.recordLogSetting.get()); |
| return true; |
| } |
| case 'network.hide-request-details': { |
| if (!panel.networkItemView) { |
| return false; |
| } |
| panel.hideRequestPanel(); |
| panel.networkLogView.resetFocus(); |
| return true; |
| } |
| case 'network.search': { |
| const selection = UI.InspectorView.InspectorView.instance().element.window().getSelection(); |
| if (!selection) { |
| return false; |
| } |
| let queryCandidate = ''; |
| if (selection.rangeCount) { |
| queryCandidate = selection.toString().replace(/\r?\n.*/, ''); |
| } |
| void SearchNetworkView.openSearch(queryCandidate); |
| return true; |
| } |
| case 'network.clear': { |
| Logs.NetworkLog.NetworkLog.instance().reset(true); |
| return true; |
| } |
| } |
| return false; |
| } |
| } |
| |
| export class RequestLocationRevealer implements |
| Common.Revealer.Revealer<NetworkForward.UIRequestLocation.UIRequestLocation> { |
| async reveal(location: NetworkForward.UIRequestLocation.UIRequestLocation): Promise<void> { |
| const view = |
| await NetworkPanel.instance().selectAndActivateRequest(location.request, location.tab, location.filterOptions); |
| if (!view) { |
| return; |
| } |
| if (location.searchMatch) { |
| const {lineNumber, columnNumber, matchLength} = location.searchMatch; |
| const revealPosition = { |
| from: {lineNumber, columnNumber}, |
| to: {lineNumber, columnNumber: columnNumber + matchLength}, |
| }; |
| await view.revealResponseBody(revealPosition); |
| } |
| if (location.header) { |
| view.revealHeader(location.header.section, location.header.header?.name); |
| } |
| } |
| } |
| |
| let searchNetworkViewInstance: SearchNetworkView; |
| |
| export class SearchNetworkView extends Search.SearchView.SearchView { |
| private constructor() { |
| super('network'); |
| } |
| |
| static instance(opts: { |
| forceNew: boolean|null, |
| } = {forceNew: null}): SearchNetworkView { |
| const {forceNew} = opts; |
| if (!searchNetworkViewInstance || forceNew) { |
| searchNetworkViewInstance = new SearchNetworkView(); |
| } |
| |
| return searchNetworkViewInstance; |
| } |
| |
| static async openSearch(query: string, searchImmediately?: boolean): Promise<Search.SearchView.SearchView> { |
| await UI.ViewManager.ViewManager.instance().showView('network.search-network-tab'); |
| const searchView = SearchNetworkView.instance(); |
| searchView.toggle(query, Boolean(searchImmediately)); |
| return searchView; |
| } |
| |
| override createScope(): Search.SearchScope.SearchScope { |
| return new NetworkSearchScope(Logs.NetworkLog.NetworkLog.instance()); |
| } |
| } |