Move network to panels/network

[email protected]

Bug: 1187573
Change-Id: Id4dbc7537828a72eb58c53428b814155d6ffec8c
Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/2794604
Commit-Queue: Tim van der Lippe <[email protected]>
Reviewed-by: Paul Lewis <[email protected]>
diff --git a/front_end/panels/network/NetworkLogView.ts b/front_end/panels/network/NetworkLogView.ts
new file mode 100644
index 0000000..2c7807d
--- /dev/null
+++ b/front_end/panels/network/NetworkLogView.ts
@@ -0,0 +1,2193 @@
+// 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 <[email protected]>
+ * 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.
+ */
+
+/* eslint-disable rulesdir/no_underscored_properties */
+
+import * as Bindings from '../../bindings/bindings.js';
+import * as BrowserSDK from '../../browser_sdk/browser_sdk.js';
+import * as Common from '../../common/common.js';
+import * as Components from '../../components/components.js';
+import * as DataGrid from '../../data_grid/data_grid.js';
+import * as HARImporter from '../../har_importer/har_importer.js';
+import * as Host from '../../host/host.js';
+import * as i18n from '../../i18n/i18n.js';
+import * as PerfUI from '../../perf_ui/perf_ui.js';
+import * as Platform from '../../platform/platform.js';
+import * as SDK from '../../sdk/sdk.js';
+import * as TextUtils from '../../text_utils/text_utils.js';
+import * as ThemeSupport from '../../theme_support/theme_support.js';
+import * as UI from '../../ui/ui.js';
+
+import {HARWriter} from './HARWriter.js';
+import {Events, NetworkGroupNode, NetworkLogViewInterface, NetworkNode, NetworkRequestNode} from './NetworkDataGridNode.js';  // eslint-disable-line no-unused-vars
+import {NetworkFrameGrouper} from './NetworkFrameGrouper.js';
+import {NetworkLogViewColumns} from './NetworkLogViewColumns.js';
+import {FilterOptions} from './NetworkPanel.js';  // eslint-disable-line no-unused-vars
+import {NetworkTimeBoundary, NetworkTimeCalculator, NetworkTransferDurationCalculator, NetworkTransferTimeCalculator} from './NetworkTimeCalculator.js';  // eslint-disable-line no-unused-vars
+
+const UIStrings = {
+  /**
+  *@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 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
+  */
+  recordSToDisplayNetworkActivity: 'Record ({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?',
+};
+const str_ = i18n.i18n.registerUIStrings('panels/network/NetworkLogView.ts', UIStrings);
+const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
+export class NetworkLogView extends UI.Widget.VBox implements
+    SDK.SDKModel.SDKModelObserver<SDK.NetworkManager.NetworkManager>, NetworkLogViewInterface {
+  _networkHideDataURLSetting: Common.Settings.Setting<boolean>;
+  _networkShowIssuesOnlySetting: Common.Settings.Setting<boolean>;
+  _networkOnlyBlockedRequestsSetting: Common.Settings.Setting<boolean>;
+  // TODO(crbug.com/1172300) Ignored during the jsdoc to ts migration)
+  // eslint-disable-next-line @typescript-eslint/no-explicit-any
+  _networkResourceTypeFiltersSetting: Common.Settings.Setting<any>;
+  _rawRowHeight: number;
+  _progressBarContainer: Element;
+  _networkLogLargeRowsSetting: Common.Settings.Setting<boolean>;
+  _rowHeight: number;
+  _timeCalculator: NetworkTransferTimeCalculator;
+  _durationCalculator: NetworkTransferDurationCalculator;
+  _calculator: NetworkTransferTimeCalculator;
+  _columns: NetworkLogViewColumns;
+  _staleRequests: Set<SDK.NetworkRequest.NetworkRequest>;
+  _mainRequestLoadTime: number;
+  _mainRequestDOMContentLoadedTime: number;
+  // TODO(crbug.com/1172300) Ignored during the jsdoc to ts migration)
+  // eslint-disable-next-line @typescript-eslint/no-explicit-any
+  _highlightedSubstringChanges: any;
+  _filters: Filter[];
+  _timeFilter: Filter|null;
+  _hoveredNode: NetworkNode|null;
+  _recordingHint: Element|null;
+  _refreshRequestId: number|null;
+  _highlightedNode: NetworkRequestNode|null;
+  _linkifier: Components.Linkifier.Linkifier;
+  _recording: boolean;
+  _needsRefresh: boolean;
+  _headerHeight: number;
+  _groupLookups: Map<string, GroupLookupInterface>;
+  _activeGroupLookup: GroupLookupInterface|null;
+  _textFilterUI: UI.FilterBar.TextFilterUI;
+  _dataURLFilterUI: UI.FilterBar.CheckboxFilterUI;
+  _resourceCategoryFilterUI: UI.FilterBar.NamedBitSetFilterUI;
+  _onlyIssuesFilterUI: UI.FilterBar.CheckboxFilterUI;
+  _onlyBlockedRequestsUI: UI.FilterBar.CheckboxFilterUI;
+  _filterParser: TextUtils.TextUtils.FilterParser;
+  _suggestionBuilder: UI.FilterSuggestionBuilder.FilterSuggestionBuilder;
+  _dataGrid: DataGrid.SortableDataGrid.SortableDataGrid<NetworkNode>;
+  _summaryToolbar: UI.Toolbar.Toolbar;
+  _filterBar: UI.FilterBar.FilterBar;
+  _textFilterSetting: Common.Settings.Setting<string>;
+
+  constructor(
+      filterBar: UI.FilterBar.FilterBar, progressBarContainer: Element,
+      networkLogLargeRowsSetting: Common.Settings.Setting<boolean>) {
+    super();
+    this.setMinimumSize(50, 64);
+    this.registerRequiredCSS('panels/network/networkLogView.css', {enableLegacyPatching: true});
+
+    this.element.id = 'network-container';
+    this.element.classList.add('no-node-selected');
+
+    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._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._rowHeight = this._computeRowHeight();
+    }
+    this._rawRowHeight = 0;
+    this._rowHeight = 0;
+    updateRowHeight.call(this);
+
+    this._timeCalculator = new NetworkTransferTimeCalculator();
+    this._durationCalculator = new NetworkTransferDurationCalculator();
+    this._calculator = this._timeCalculator;
+
+    this._columns =
+        // TODO(crbug.com/1172300) Ignored during the jsdoc to ts migration
+        // @ts-expect-error
+        new NetworkLogViewColumns(this, this._timeCalculator, this._durationCalculator, networkLogLargeRowsSetting);
+    this._columns.show(this.element);
+
+    this._staleRequests = new Set();
+    this._mainRequestLoadTime = -1;
+    this._mainRequestDOMContentLoadedTime = -1;
+    this._highlightedSubstringChanges = [];
+
+    this._filters = [];
+    this._timeFilter = null;
+    this._hoveredNode = null;
+    this._recordingHint = null;
+    this._refreshRequestId = null;
+    this._highlightedNode = null;
+
+    this._linkifier = new Components.Linkifier.Linkifier();
+
+    this._recording = false;
+    this._needsRefresh = false;
+
+    this._headerHeight = 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.FilterUI.Events.FilterChanged, this._filterChanged, this);
+    filterBar.addFilter(this._textFilterUI);
+
+    this._dataURLFilterUI = new UI.FilterBar.CheckboxFilterUI(
+        'hide-data-url', i18nString(UIStrings.hideDataUrls), true, this._networkHideDataURLSetting);
+    this._dataURLFilterUI.addEventListener(
+        UI.FilterBar.FilterUI.Events.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.FilterUI.Events.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.FilterUI.Events.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.FilterUI.Events.FilterChanged, this._filterChanged.bind(this), this);
+    UI.Tooltip.Tooltip.install(this._onlyBlockedRequestsUI.element(), i18nString(UIStrings.onlyShowBlockedRequests));
+    filterBar.addFilter(this._onlyBlockedRequestsUI);
+
+    this._filterParser = new TextUtils.TextUtils.FilterParser(_searchKeys);
+    this._suggestionBuilder =
+        new UI.FilterSuggestionBuilder.FilterSuggestionBuilder(_searchKeys, NetworkLogView._sortSearchValues);
+    this._resetSuggestionBuilder();
+
+    this._dataGrid = this._columns.dataGrid();
+    this._setupDataGrid();
+    this._columns.sortByCurrentColumn();
+    filterBar.filterButton().addEventListener(
+        UI.Toolbar.ToolbarButton.Events.Click,
+        this._dataGrid.scheduleUpdate.bind(this._dataGrid, true /* isFromUser */));
+
+    this._summaryToolbar = new UI.Toolbar.Toolbar('network-summary-bar', this.element);
+
+    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.SDKModel.TargetManager.instance().observeModels(SDK.NetworkManager.NetworkManager, this);
+    SDK.NetworkLog.NetworkLog.instance().addEventListener(
+        SDK.NetworkLog.Events.RequestAdded, this._onRequestUpdated, this);
+    SDK.NetworkLog.NetworkLog.instance().addEventListener(
+        SDK.NetworkLog.Events.RequestUpdated, this._onRequestUpdated, this);
+    SDK.NetworkLog.NetworkLog.instance().addEventListener(SDK.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());
+    }
+  }
+
+  _updateGroupByFrame(): void {
+    const value = Common.Settings.Settings.instance().moduleSetting('network.group-by-frame').get();
+    this._setGrouping(value ? 'Frame' : null);
+  }
+
+  static _sortSearchValues(key: string, values: string[]): void {
+    if (key === 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();
+    }
+  }
+
+  static _negativeFilter(filter: Filter, request: SDK.NetworkRequest.NetworkRequest): boolean {
+    return !filter(request);
+  }
+
+  static _requestPathFilter(regex: RegExp|null, request: SDK.NetworkRequest.NetworkRequest): boolean {
+    if (!regex) {
+      return false;
+    }
+
+    return regex.test(request.path() + '/' + request.name());
+  }
+
+  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;
+  }
+
+  static _createRequestDomainFilter(value: string): Filter {
+    const escapedPattern = value.split('*').map(Platform.StringUtilities.escapeForRegExp).join('.*');
+    return NetworkLogView._requestDomainFilter.bind(null, new RegExp('^' + escapedPattern + '$', 'i'));
+  }
+
+  static _requestDomainFilter(regex: RegExp, request: SDK.NetworkRequest.NetworkRequest): boolean {
+    return regex.test(request.domain);
+  }
+
+  static _runningRequestFilter(request: SDK.NetworkRequest.NetworkRequest): boolean {
+    return !request.finished;
+  }
+
+  static _fromCacheRequestFilter(request: SDK.NetworkRequest.NetworkRequest): boolean {
+    return request.cached();
+  }
+
+  static _interceptedByServiceWorkerFilter(request: SDK.NetworkRequest.NetworkRequest): boolean {
+    return request.fetchedViaServiceWorker;
+  }
+
+  static _initiatedByServiceWorkerFilter(request: SDK.NetworkRequest.NetworkRequest): boolean {
+    return request.initiatedByServiceWorker();
+  }
+
+  static _requestResponseHeaderFilter(value: string, request: SDK.NetworkRequest.NetworkRequest): boolean {
+    return request.responseHeaderValue(value) !== undefined;
+  }
+
+  static _requestMethodFilter(value: string, request: SDK.NetworkRequest.NetworkRequest): boolean {
+    return request.requestMethod === value;
+  }
+
+  static _requestPriorityFilter(value: string, request: SDK.NetworkRequest.NetworkRequest): boolean {
+    return request.priority() === value;
+  }
+
+  static _requestMimeTypeFilter(value: string, request: SDK.NetworkRequest.NetworkRequest): boolean {
+    return request.mimeType === value;
+  }
+
+  static _requestMixedContentFilter(value: MixedContentFilterValues, request: SDK.NetworkRequest.NetworkRequest):
+      boolean {
+    if (value === MixedContentFilterValues.Displayed) {
+      return request.mixedContentType === Protocol.Security.MixedContentType.OptionallyBlockable;
+    }
+    if (value === MixedContentFilterValues.Blocked) {
+      return request.mixedContentType === Protocol.Security.MixedContentType.Blockable && request.wasBlocked();
+    }
+    if (value === MixedContentFilterValues.BlockOverridden) {
+      return request.mixedContentType === Protocol.Security.MixedContentType.Blockable && !request.wasBlocked();
+    }
+    if (value === MixedContentFilterValues.All) {
+      return request.mixedContentType !== Protocol.Security.MixedContentType.None;
+    }
+
+    return false;
+  }
+
+  static _requestSchemeFilter(value: string, request: SDK.NetworkRequest.NetworkRequest): boolean {
+    return request.scheme === value;
+  }
+
+  static _requestCookieDomainFilter(value: string, request: SDK.NetworkRequest.NetworkRequest): boolean {
+    return request.allCookiesIncludingBlockedOnes().some(cookie => cookie.domain() === value);
+  }
+
+  static _requestCookieNameFilter(value: string, request: SDK.NetworkRequest.NetworkRequest): boolean {
+    return request.allCookiesIncludingBlockedOnes().some(cookie => cookie.name() === value);
+  }
+
+  static _requestCookiePathFilter(value: string, request: SDK.NetworkRequest.NetworkRequest): boolean {
+    return request.allCookiesIncludingBlockedOnes().some(cookie => cookie.path() === value);
+  }
+
+  static _requestCookieValueFilter(value: string, request: SDK.NetworkRequest.NetworkRequest): boolean {
+    return request.allCookiesIncludingBlockedOnes().some(cookie => cookie.value() === value);
+  }
+
+  static _requestSetCookieDomainFilter(value: string, request: SDK.NetworkRequest.NetworkRequest): boolean {
+    return request.responseCookies.some(cookie => cookie.domain() === value);
+  }
+
+  static _requestSetCookieNameFilter(value: string, request: SDK.NetworkRequest.NetworkRequest): boolean {
+    return request.responseCookies.some(cookie => cookie.name() === value);
+  }
+
+  static _requestSetCookieValueFilter(value: string, request: SDK.NetworkRequest.NetworkRequest): boolean {
+    return request.responseCookies.some(cookie => cookie.value() === value);
+  }
+
+  static _requestSizeLargerThanFilter(value: number, request: SDK.NetworkRequest.NetworkRequest): boolean {
+    return request.transferSize >= value;
+  }
+
+  static _statusCodeFilter(value: string, request: SDK.NetworkRequest.NetworkRequest): boolean {
+    return (String(request.statusCode)) === value;
+  }
+
+  // TODO(crbug.com/1172300) Ignored during the jsdoc to ts migration)
+  // eslint-disable-next-line @typescript-eslint/naming-convention
+  static HTTPRequestsFilter(request: SDK.NetworkRequest.NetworkRequest): boolean {
+    return request.parsedURL.isValid && (request.scheme in HTTPSchemas);
+  }
+
+  static _resourceTypeFilter(value: string, request: SDK.NetworkRequest.NetworkRequest): boolean {
+    return request.resourceType().name() === value;
+  }
+
+  static _requestUrlFilter(value: string, request: SDK.NetworkRequest.NetworkRequest): boolean {
+    const regex = new RegExp(Platform.StringUtilities.escapeForRegExp(value), 'i');
+    return regex.test(request.url());
+  }
+
+  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;
+  }
+
+  static _copyRequestHeaders(request: SDK.NetworkRequest.NetworkRequest): void {
+    Host.InspectorFrontendHost.InspectorFrontendHostInstance.copyText(request.requestHeadersText());
+  }
+
+  static _copyResponseHeaders(request: SDK.NetworkRequest.NetworkRequest): void {
+    Host.InspectorFrontendHost.InspectorFrontendHostInstance.copyText(request.responseHeadersText);
+  }
+
+  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);
+  }
+
+  _handleDrop(dataTransfer: DataTransfer): void {
+    const items = dataTransfer.items;
+    if (!items.length) {
+      return;
+    }
+    const entry = items[0].webkitGetAsEntry();
+    if (entry.isDirectory) {
+      return;
+    }
+
+    entry.file(this.onLoadFromFile.bind(this));
+  }
+
+  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) {
+        // TODO(crbug.com/1172300) Ignored during the jsdoc to ts migration)
+        // eslint-disable-next-line @typescript-eslint/no-explicit-any
+        this._harLoadFailed((error as any).message);
+      }
+      return;
+    }
+    let harRoot;
+    try {
+      // HARRoot and JSON.parse might throw.
+      harRoot = new HARImporter.HARFormat.HARRoot(JSON.parse(outputStream.data()));
+    } catch (e) {
+      this._harLoadFailed(e);
+      return;
+    }
+    SDK.NetworkLog.NetworkLog.instance().importRequests(
+        HARImporter.HARImporter.Importer.requestsFromHARLog(harRoot.log));
+  }
+
+  _harLoadFailed(message: string): void {
+    Common.Console.Console.instance().error('Failed to load HAR file with following error: ' + message);
+  }
+
+  _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();
+  }
+
+  _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._headerHeight;
+  }
+
+  setRecording(recording: boolean): void {
+    this._recording = recording;
+    this._updateSummaryBar();
+  }
+
+  modelAdded(networkManager: SDK.NetworkManager.NetworkManager): void {
+    // TODO(allada) Remove dependency on networkManager and instead use NetworkLog and PageLoad for needed data.
+    if (networkManager.target().parentTarget()) {
+      return;
+    }
+    const resourceTreeModel = networkManager.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);
+    }
+  }
+
+  modelRemoved(networkManager: SDK.NetworkManager.NetworkManager): void {
+    if (!networkManager.target().parentTarget()) {
+      const resourceTreeModel = networkManager.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);
+      }
+    }
+  }
+
+  linkifier(): Components.Linkifier.Linkifier {
+    return this._linkifier;
+  }
+
+  setWindow(start: number, end: number): void {
+    if (!start && !end) {
+      this._timeFilter = null;
+      this._timeCalculator.setWindow(null);
+    } else {
+      this._timeFilter = NetworkLogView._requestTimeFilter.bind(null, start, end);
+      this._timeCalculator.setWindow(new NetworkTimeBoundary(start, end));
+    }
+    this._filterRequests();
+  }
+
+  resetFocus(): void {
+    this._dataGrid.element.focus();
+  }
+
+  _resetSuggestionBuilder(): void {
+    this._suggestionBuilder.clear();
+    this._suggestionBuilder.addItem(FilterType.Is, IsFilterType.Running);
+    this._suggestionBuilder.addItem(FilterType.Is, IsFilterType.FromCache);
+    this._suggestionBuilder.addItem(FilterType.Is, IsFilterType.ServiceWorkerIntercepted);
+    this._suggestionBuilder.addItem(FilterType.Is, IsFilterType.ServiceWorkerInitiated);
+    this._suggestionBuilder.addItem(FilterType.LargerThan, '100');
+    this._suggestionBuilder.addItem(FilterType.LargerThan, '10k');
+    this._suggestionBuilder.addItem(FilterType.LargerThan, '1M');
+    this._textFilterUI.setSuggestionProvider(this._suggestionBuilder.completions.bind(this._suggestionBuilder));
+  }
+
+  _filterChanged(_event: Common.EventTarget.EventTargetEvent): void {
+    this.removeAllNodeHighlights();
+    this._parseFilterQuery(this._textFilterUI.value());
+    this._filterRequests();
+    this._textFilterSetting.set(this._textFilterUI.value());
+  }
+
+  async resetFilter(): Promise<void> {
+    this._textFilterUI.clear();
+  }
+
+  _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.recordSToDisplayNetworkActivity, {PH1: recordNode}));
+    }
+    hintText.createChild('br');
+    hintText.appendChild(UI.XLink.XLink.create(
+        'https://developers.google.com/web/tools/chrome-devtools/network/?utm_source=devtools&utm_campaign=2019Q1',
+        i18nString(UIStrings.learnMore)));
+
+    this._setHidden(true);
+  }
+
+  _hideRecordingHint(): void {
+    this._setHidden(false);
+    if (this._recordingHint) {
+      this._recordingHint.remove();
+    }
+    UI.ARIAUtils.alert(i18nString(UIStrings.networkDataAvailable), this._summaryToolbar.element);
+    this._recordingHint = null;
+  }
+
+  _setHidden(value: boolean): void {
+    this._columns.setHidden(value);
+    UI.ARIAUtils.setHidden(this._summaryToolbar.element, value);
+  }
+
+  elementsToRestoreScrollPositionsFor(): Element[] {
+    if (!this._dataGrid)  // Not initialized yet.
+    {
+      return [];
+    }
+    return [this._dataGrid.scrollContainer];
+  }
+
+  columnExtensionResolved(): void {
+    this._invalidateAllItems(true);
+  }
+
+  _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 (isEnterOrSpaceKey(event)) {
+        this.dispatchEventToListeners(Events.RequestActivated, {showPanel: true, takeFocus: true});
+        event.consume(true);
+      }
+    });
+    this._dataGrid.element.addEventListener('focus', this._onDataGridFocus.bind(this), true);
+    this._dataGrid.element.addEventListener('blur', this._onDataGridBlur.bind(this), true);
+    return this._dataGrid;
+  }
+
+  _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._hoveredNode;
+  }
+
+  _setHoveredNode(node: NetworkNode|null, highlightInitiatorChain?: boolean): void {
+    if (this._hoveredNode) {
+      this._hoveredNode.setHovered(false, false);
+    }
+    this._hoveredNode = node;
+    if (this._hoveredNode) {
+      this._hoveredNode.setHovered(true, Boolean(highlightInitiatorChain));
+    }
+  }
+
+  _dataGridMouseDown(event: Event): void {
+    const mouseEvent = (event as MouseEvent);
+    if (!this._dataGrid.selectedNode && mouseEvent.button) {
+      mouseEvent.consume();
+    }
+  }
+
+  _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 SDK.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()) {
+        baseTime = request.startTime;
+      }
+      if (request.endTime > maxTime) {
+        maxTime = request.endTime;
+      }
+    }
+
+    if (!nodeCount) {
+      this._showRecordingHint();
+      return;
+    }
+
+    this._summaryToolbar.removeToolbarItems();
+    const appendChunk = (chunk: string, title?: string): HTMLDivElement => {
+      const toolbarText = new UI.Toolbar.ToolbarText(chunk);
+      toolbarText.setTitle(title ? title : chunk);
+      this._summaryToolbar.appendToolbarItem(toolbarText);
+      return toolbarText.element as HTMLDivElement;
+    };
+
+    if (selectedNodeNumber !== nodeCount) {
+      appendChunk(i18nString(UIStrings.sSRequests, {PH1: selectedNodeNumber, PH2: nodeCount}));
+      this._summaryToolbar.appendSeparator();
+      appendChunk(
+          i18nString(UIStrings.sSTransferred, {
+            PH1: Platform.NumberUtilities.bytesToString(selectedTransferSize),
+            PH2: Platform.NumberUtilities.bytesToString(transferSize),
+          }),
+          i18nString(UIStrings.sBSBTransferredOverNetwork, {PH1: selectedTransferSize, PH2: transferSize}));
+      this._summaryToolbar.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._summaryToolbar.appendSeparator();
+      appendChunk(
+          i18nString(UIStrings.sTransferred, {PH1: Platform.NumberUtilities.bytesToString(transferSize)}),
+          i18nString(UIStrings.sBTransferredOverNetwork, {PH1: transferSize}));
+      this._summaryToolbar.appendSeparator();
+      appendChunk(
+          i18nString(UIStrings.sResources, {PH1: Platform.NumberUtilities.bytesToString(resourceSize)}),
+          i18nString(UIStrings.sBResourcesLoadedByThePage, {PH1: resourceSize}));
+    }
+
+    if (baseTime !== -1 && maxTime !== -1) {
+      this._summaryToolbar.appendSeparator();
+      appendChunk(i18nString(UIStrings.finishS, {PH1: Number.secondsToString(maxTime - baseTime)}));
+      if (this._mainRequestDOMContentLoadedTime !== -1 && this._mainRequestDOMContentLoadedTime > baseTime) {
+        this._summaryToolbar.appendSeparator();
+        const domContentLoadedText = i18nString(
+            UIStrings.domcontentloadedS,
+            {PH1: Number.secondsToString(this._mainRequestDOMContentLoadedTime - baseTime)});
+        appendChunk(domContentLoadedText).style.color = NetworkLogView.getDCLEventColor();
+      }
+      if (this._mainRequestLoadTime !== -1) {
+        this._summaryToolbar.appendSeparator();
+        const loadText =
+            i18nString(UIStrings.loadS, {PH1: Number.secondsToString(this._mainRequestLoadTime - baseTime)});
+        appendChunk(loadText).style.color = NetworkLogView.getLoadEventColor();
+      }
+    }
+  }
+
+  scheduleRefresh(): void {
+    if (this._needsRefresh) {
+      return;
+    }
+
+    this._needsRefresh = true;
+
+    if (this.isShowing() && !this._refreshRequestId) {
+      this._refreshRequestId = this.element.window().requestAnimationFrame(this._refresh.bind(this));
+    }
+  }
+
+  addFilmStripFrames(times: number[]): void {
+    this._columns.addEventDividers(times, 'network-frame-divider');
+  }
+
+  selectFilmStripFrame(time: number): void {
+    this._columns.selectFilmStripFrame(time);
+  }
+
+  clearFilmStripFrame(): void {
+    this._columns.clearFilmStripFrame();
+  }
+
+  _refreshIfNeeded(): void {
+    if (this._needsRefresh) {
+      this._refresh();
+    }
+  }
+
+  _invalidateAllItems(deferUpdate?: boolean): void {
+    this._staleRequests = new Set(SDK.NetworkLog.NetworkLog.instance().requests());
+    if (deferUpdate) {
+      this.scheduleRefresh();
+    } else {
+      this._refresh();
+    }
+  }
+
+  timeCalculator(): NetworkTimeCalculator {
+    return this._timeCalculator;
+  }
+
+  calculator(): NetworkTimeCalculator {
+    return this._calculator;
+  }
+
+  setCalculator(x: NetworkTimeCalculator): void {
+    if (!x || this._calculator === x) {
+      return;
+    }
+
+    if (this._calculator !== x) {
+      this._calculator = x;
+      this._columns.setCalculator(this._calculator);
+    }
+    this._calculator.reset();
+
+    if (this._calculator.startAtZero) {
+      this._columns.hideEventDividers();
+    } else {
+      this._columns.showEventDividers();
+    }
+
+    this._invalidateAllItems();
+  }
+
+  _loadEventFired(event: Common.EventTarget.EventTargetEvent): void {
+    if (!this._recording) {
+      return;
+    }
+
+    const time = (event.data.loadTime as number);
+    if (time) {
+      this._mainRequestLoadTime = time;
+      this._columns.addEventDividers([time], 'network-load-divider');
+    }
+  }
+
+  _domContentLoadedEventFired(event: Common.EventTarget.EventTargetEvent): void {
+    if (!this._recording) {
+      return;
+    }
+    const data = (event.data as number);
+    if (data) {
+      this._mainRequestDOMContentLoadedTime = data;
+      this._columns.addEventDividers([data], 'network-dcl-divider');
+    }
+  }
+
+  wasShown(): void {
+    this._refreshIfNeeded();
+    this._columns.wasShown();
+  }
+
+  willHide(): void {
+    this._columns.willHide();
+  }
+
+  onResize(): void {
+    this._rowHeight = this._computeRowHeight();
+  }
+
+  flatNodesList(): NetworkNode[] {
+    const rootNode =
+        (this._dataGrid.rootNode() as
+         DataGrid.ViewportDataGrid.ViewportDataGridNode<DataGrid.SortableDataGrid.SortableDataGridNode<NetworkNode>>);
+    return rootNode.flatChildren() as NetworkNode[];
+  }
+
+  _onDataGridFocus(): void {
+    if (this._dataGrid.element.matches(':focus-visible')) {
+      this.element.classList.add('grid-focused');
+    }
+    this.updateNodeBackground();
+  }
+
+  _onDataGridBlur(): void {
+    this.element.classList.remove('grid-focused');
+    this.updateNodeBackground();
+  }
+
+  updateNodeBackground(): void {
+    if (this._dataGrid.selectedNode) {
+      (this._dataGrid.selectedNode as NetworkNode).updateBackgroundColor();
+    }
+  }
+
+  updateNodeSelectedClass(isSelected: boolean): void {
+    if (isSelected) {
+      this.element.classList.remove('no-node-selected');
+    } else {
+      this.element.classList.add('no-node-selected');
+    }
+  }
+
+  stylesChanged(): void {
+    this._columns.scheduleRefresh();
+  }
+
+  _refresh(): void {
+    this._needsRefresh = false;
+
+    if (this._refreshRequestId) {
+      this.element.window().cancelAnimationFrame(this._refreshRequestId);
+      this._refreshRequestId = null;
+    }
+
+    this.removeAllNodeHighlights();
+
+    this._timeCalculator.updateBoundariesForEventTime(this._mainRequestLoadTime);
+    this._durationCalculator.updateBoundariesForEventTime(this._mainRequestLoadTime);
+    this._timeCalculator.updateBoundariesForEventTime(this._mainRequestDOMContentLoadedTime);
+    this._durationCalculator.updateBoundariesForEventTime(this._mainRequestDOMContentLoadedTime);
+
+    const nodesToInsert = new Map<NetworkNode, NetworkNode>();
+    const nodesToRefresh: NetworkNode[] = [];
+
+    const staleNodes = new Set<NetworkRequestNode>();
+
+    // While creating nodes it may add more entries into _staleRequests because redirect request nodes update the parent
+    // node so we loop until we have no more stale requests.
+    while (this._staleRequests.size) {
+      const request = this._staleRequests.values().next().value;
+      this._staleRequests.delete(request);
+      let node = networkRequestToNode.get(request);
+      if (!node) {
+        node = this._createNodeForRequest(request);
+      }
+      staleNodes.add(node);
+    }
+
+    for (const node of staleNodes) {
+      const isFilteredOut = !this._applyFilter(node);
+      if (isFilteredOut && node === this._hoveredNode) {
+        this._setHoveredNode(null);
+      }
+
+      if (!isFilteredOut) {
+        nodesToRefresh.push(node);
+      }
+      const request = node.request();
+      this._timeCalculator.updateBoundaries(request);
+      this._durationCalculator.updateBoundaries(request);
+      const newParent = this._parentNodeForInsert(node);
+      const wasAlreadyFiltered = filteredNetworkRequests.has(node);
+      if (wasAlreadyFiltered === isFilteredOut && node.parent === newParent) {
+        continue;
+      }
+      if (isFilteredOut) {
+        filteredNetworkRequests.add(node);
+      } else {
+        filteredNetworkRequests.delete(node);
+      }
+      const removeFromParent = node.parent && (isFilteredOut || node.parent !== newParent);
+      if (removeFromParent) {
+        let parent: NetworkNode|
+            (DataGrid.DataGrid.DataGridNode<DataGrid.ViewportDataGrid.ViewportDataGridNode<
+                 DataGrid.SortableDataGrid.SortableDataGridNode<NetworkNode>>>|
+             null) = node.parent;
+        if (!parent) {
+          continue;
+        }
+        parent.removeChild(node);
+        while (parent && !parent.hasChildren() && parent.dataGrid && parent.dataGrid.rootNode() !== parent) {
+          const grandparent = (parent.parent as NetworkNode);
+          grandparent.removeChild(parent);
+          parent = grandparent;
+        }
+      }
+
+      if (!newParent || isFilteredOut) {
+        continue;
+      }
+
+      if (!newParent.dataGrid && !nodesToInsert.has(newParent)) {
+        nodesToInsert.set(newParent, (this._dataGrid.rootNode() as NetworkNode));
+        nodesToRefresh.push(newParent);
+      }
+      nodesToInsert.set(node, newParent);
+    }
+
+    for (const node of nodesToInsert.keys()) {
+      (nodesToInsert.get(node) as NetworkNode).appendChild(node);
+    }
+
+    for (const node of nodesToRefresh) {
+      node.refresh();
+    }
+
+    this._updateSummaryBar();
+
+    if (nodesToInsert.size) {
+      this._columns.sortByCurrentColumn();
+    }
+
+    this._dataGrid.updateInstantly();
+    this._didRefreshForTest();
+  }
+
+  _didRefreshForTest(): void {
+  }
+
+  _parentNodeForInsert(node: NetworkRequestNode): NetworkNode|null {
+    if (!this._activeGroupLookup) {
+      return this._dataGrid.rootNode() as NetworkNode;
+    }
+
+    const groupNode = this._activeGroupLookup.groupNodeForRequest(node.request());
+    if (!groupNode) {
+      return this._dataGrid.rootNode() as NetworkNode;
+    }
+    return groupNode;
+  }
+
+  _reset(): void {
+    this.dispatchEventToListeners(Events.RequestActivated, {showPanel: false});
+
+    this._setHoveredNode(null);
+    this._columns.reset();
+
+    this._timeFilter = null;
+    this._calculator.reset();
+
+    this._timeCalculator.setWindow(null);
+    this._linkifier.reset();
+
+    if (this._activeGroupLookup) {
+      this._activeGroupLookup.reset();
+    }
+    this._staleRequests.clear();
+    this._resetSuggestionBuilder();
+
+    this._mainRequestLoadTime = -1;
+    this._mainRequestDOMContentLoadedTime = -1;
+
+    this._dataGrid.rootNode().removeChildren();
+    this._updateSummaryBar();
+    this._dataGrid.setStickToBottom(true);
+    this.scheduleRefresh();
+  }
+
+  setTextFilterValue(filterString: string): void {
+    this._textFilterUI.setValue(filterString);
+    this._dataURLFilterUI.setChecked(false);
+    this._onlyIssuesFilterUI.setChecked(false);
+    this._onlyBlockedRequestsUI.setChecked(false);
+    this._resourceCategoryFilterUI.reset();
+  }
+
+  _createNodeForRequest(request: SDK.NetworkRequest.NetworkRequest): NetworkRequestNode {
+    const node = new NetworkRequestNode(this, request);
+    networkRequestToNode.set(request, node);
+    filteredNetworkRequests.add(node);
+
+    for (let redirect = request.redirectSource(); redirect; redirect = redirect.redirectSource()) {
+      this._refreshRequest(redirect);
+    }
+    return node;
+  }
+
+  _onRequestUpdated(event: Common.EventTarget.EventTargetEvent): void {
+    const request = (event.data as SDK.NetworkRequest.NetworkRequest);
+    this._refreshRequest(request);
+  }
+
+  _refreshRequest(request: SDK.NetworkRequest.NetworkRequest): void {
+    NetworkLogView._subdomains(request.domain)
+        .forEach(this._suggestionBuilder.addItem.bind(this._suggestionBuilder, FilterType.Domain));
+    this._suggestionBuilder.addItem(FilterType.Method, request.requestMethod);
+    this._suggestionBuilder.addItem(FilterType.MimeType, request.mimeType);
+    this._suggestionBuilder.addItem(FilterType.Scheme, String(request.scheme));
+    this._suggestionBuilder.addItem(FilterType.StatusCode, String(request.statusCode));
+    this._suggestionBuilder.addItem(FilterType.ResourceType, request.resourceType().name());
+    this._suggestionBuilder.addItem(FilterType.Url, request.securityOrigin());
+
+    const priority = request.priority();
+    if (priority) {
+      this._suggestionBuilder.addItem(
+          FilterType.Priority, PerfUI.NetworkPriorities.uiLabelForNetworkPriority(priority));
+    }
+
+    if (request.mixedContentType !== Protocol.Security.MixedContentType.None) {
+      this._suggestionBuilder.addItem(FilterType.MixedContent, MixedContentFilterValues.All);
+    }
+
+    if (request.mixedContentType === Protocol.Security.MixedContentType.OptionallyBlockable) {
+      this._suggestionBuilder.addItem(FilterType.MixedContent, MixedContentFilterValues.Displayed);
+    }
+
+    if (request.mixedContentType === Protocol.Security.MixedContentType.Blockable) {
+      const suggestion =
+          request.wasBlocked() ? MixedContentFilterValues.Blocked : MixedContentFilterValues.BlockOverridden;
+      this._suggestionBuilder.addItem(FilterType.MixedContent, suggestion);
+    }
+
+    const responseHeaders = request.responseHeaders;
+    for (let i = 0, l = responseHeaders.length; i < l; ++i) {
+      this._suggestionBuilder.addItem(FilterType.HasResponseHeader, responseHeaders[i].name);
+    }
+
+    for (const cookie of request.responseCookies) {
+      this._suggestionBuilder.addItem(FilterType.SetCookieDomain, cookie.domain());
+      this._suggestionBuilder.addItem(FilterType.SetCookieName, cookie.name());
+      this._suggestionBuilder.addItem(FilterType.SetCookieValue, cookie.value());
+    }
+
+    for (const cookie of request.allCookiesIncludingBlockedOnes()) {
+      this._suggestionBuilder.addItem(FilterType.CookieDomain, cookie.domain());
+      this._suggestionBuilder.addItem(FilterType.CookieName, cookie.name());
+      this._suggestionBuilder.addItem(FilterType.CookiePath, cookie.path());
+      this._suggestionBuilder.addItem(FilterType.CookieValue, cookie.value());
+    }
+
+    this._staleRequests.add(request);
+    this.scheduleRefresh();
+  }
+
+  rowHeight(): number {
+    return this._rowHeight;
+  }
+
+  switchViewMode(gridMode: boolean): void {
+    this._columns.switchViewMode(gridMode);
+  }
+
+  handleContextMenuForRequest(contextMenu: UI.ContextMenu.ContextMenu, request: SDK.NetworkRequest.NetworkRequest):
+      void {
+    contextMenu.appendApplicableItems(request);
+    let copyMenu = contextMenu.clipboardSection().appendSubMenuItem(i18nString(UIStrings.copy));
+    const footerSection = copyMenu.footerSection();
+    if (request) {
+      copyMenu.defaultSection().appendItem(
+          UI.UIUtils.copyLinkAddressLabel(),
+          Host.InspectorFrontendHost.InspectorFrontendHostInstance.copyText.bind(
+              Host.InspectorFrontendHost.InspectorFrontendHostInstance, request.contentURL()));
+      if (request.requestHeadersText()) {
+        copyMenu.defaultSection().appendItem(
+            i18nString(UIStrings.copyRequestHeaders), NetworkLogView._copyRequestHeaders.bind(null, request));
+      }
+
+      if (request.responseHeadersText) {
+        copyMenu.defaultSection().appendItem(
+            i18nString(UIStrings.copyResponseHeaders), NetworkLogView._copyResponseHeaders.bind(null, request));
+      }
+
+      if (request.finished) {
+        copyMenu.defaultSection().appendItem(
+            i18nString(UIStrings.copyResponse), NetworkLogView._copyResponse.bind(null, request));
+      }
+
+      const initiator = request.initiator();
+
+      if (initiator) {
+        const stack = initiator.stack;
+        if (stack) {
+          // We proactively compute the stacktrace text, as we can't determine whether the stacktrace
+          // has any context solely based on the top frame. Sometimes, the top frame does not have
+          // any callFrames, but its parent frames do.
+          const stackTraceText = computeStackTraceText(stack);
+          if (stackTraceText !== '') {
+            copyMenu.defaultSection().appendItem(i18nString(UIStrings.copyStacktrace), () => {
+              Host.InspectorFrontendHost.InspectorFrontendHostInstance.copyText(stackTraceText);
+            });
+          }
+        }
+      }
+
+      const disableIfBlob = request.isBlobRequest();
+      if (Host.Platform.isWin()) {
+        footerSection.appendItem(
+            i18nString(UIStrings.copyAsPowershell), this._copyPowerShellCommand.bind(this, request), disableIfBlob);
+        footerSection.appendItem(
+            i18nString(UIStrings.copyAsFetch), this._copyFetchCall.bind(this, request, false), disableIfBlob);
+        footerSection.appendItem(
+            i18nString(UIStrings.copyAsNodejsFetch), this._copyFetchCall.bind(this, request, true), disableIfBlob);
+        footerSection.appendItem(
+            i18nString(UIStrings.copyAsCurlCmd), this._copyCurlCommand.bind(this, request, 'win'), disableIfBlob);
+        footerSection.appendItem(
+            i18nString(UIStrings.copyAsCurlBash), this._copyCurlCommand.bind(this, request, 'unix'), disableIfBlob);
+        footerSection.appendItem(i18nString(UIStrings.copyAllAsPowershell), this._copyAllPowerShellCommand.bind(this));
+        footerSection.appendItem(i18nString(UIStrings.copyAllAsFetch), this._copyAllFetchCall.bind(this, false));
+        footerSection.appendItem(i18nString(UIStrings.copyAllAsNodejsFetch), this._copyAllFetchCall.bind(this, true));
+        footerSection.appendItem(i18nString(UIStrings.copyAllAsCurlCmd), this._copyAllCurlCommand.bind(this, 'win'));
+        footerSection.appendItem(i18nString(UIStrings.copyAllAsCurlBash), this._copyAllCurlCommand.bind(this, 'unix'));
+      } else {
+        footerSection.appendItem(
+            i18nString(UIStrings.copyAsFetch), this._copyFetchCall.bind(this, request, false), disableIfBlob);
+        footerSection.appendItem(
+            i18nString(UIStrings.copyAsNodejsFetch), this._copyFetchCall.bind(this, request, true), disableIfBlob);
+        footerSection.appendItem(
+            i18nString(UIStrings.copyAsCurl), this._copyCurlCommand.bind(this, request, 'unix'), disableIfBlob);
+        footerSection.appendItem(i18nString(UIStrings.copyAllAsFetch), this._copyAllFetchCall.bind(this, false));
+        footerSection.appendItem(i18nString(UIStrings.copyAllAsNodejsFetch), this._copyAllFetchCall.bind(this, true));
+        footerSection.appendItem(i18nString(UIStrings.copyAllAsCurl), this._copyAllCurlCommand.bind(this, 'unix'));
+      }
+    } else {
+      copyMenu = contextMenu.clipboardSection().appendSubMenuItem(i18nString(UIStrings.copy));
+    }
+    footerSection.appendItem(i18nString(UIStrings.copyAllAsHar), this._copyAll.bind(this));
+
+    contextMenu.saveSection().appendItem(i18nString(UIStrings.saveAllAsHarWithContent), this.exportAll.bind(this));
+
+    contextMenu.editSection().appendItem(i18nString(UIStrings.clearBrowserCache), this._clearBrowserCache.bind(this));
+    contextMenu.editSection().appendItem(
+        i18nString(UIStrings.clearBrowserCookies), this._clearBrowserCookies.bind(this));
+
+    if (request) {
+      const maxBlockedURLLength = 20;
+      const manager = SDK.NetworkManager.MultitargetNetworkManager.instance();
+      let patterns = manager.blockedPatterns();
+
+      function addBlockedURL(url: string): void {
+        patterns.push({enabled: true, url: url});
+        manager.setBlockedPatterns(patterns);
+        manager.setBlockingEnabled(true);
+        UI.ViewManager.ViewManager.instance().showView('network.blocked-urls');
+      }
+
+      function removeBlockedURL(url: string): void {
+        patterns = patterns.filter(pattern => pattern.url !== url);
+        manager.setBlockedPatterns(patterns);
+        UI.ViewManager.ViewManager.instance().showView('network.blocked-urls');
+      }
+
+      const urlWithoutScheme = request.parsedURL.urlWithoutScheme();
+      if (urlWithoutScheme && !patterns.find(pattern => pattern.url === urlWithoutScheme)) {
+        contextMenu.debugSection().appendItem(
+            i18nString(UIStrings.blockRequestUrl), addBlockedURL.bind(null, urlWithoutScheme));
+      } else if (urlWithoutScheme) {
+        const croppedURL = Platform.StringUtilities.trimMiddle(urlWithoutScheme, maxBlockedURLLength);
+        contextMenu.debugSection().appendItem(
+            i18nString(UIStrings.unblockS, {PH1: croppedURL}), removeBlockedURL.bind(null, urlWithoutScheme));
+      }
+
+      const domain = request.parsedURL.domain();
+      if (domain && !patterns.find(pattern => pattern.url === domain)) {
+        contextMenu.debugSection().appendItem(
+            i18nString(UIStrings.blockRequestDomain), addBlockedURL.bind(null, domain));
+      } else if (domain) {
+        const croppedDomain = Platform.StringUtilities.trimMiddle(domain, maxBlockedURLLength);
+        contextMenu.debugSection().appendItem(
+            i18nString(UIStrings.unblockS, {PH1: croppedDomain}), removeBlockedURL.bind(null, domain));
+      }
+
+      if (SDK.NetworkManager.NetworkManager.canReplayRequest(request)) {
+        contextMenu.debugSection().appendItem(
+            i18nString(UIStrings.replayXhr), SDK.NetworkManager.NetworkManager.replayRequest.bind(null, request));
+      }
+    }
+  }
+
+  _harRequests(): SDK.NetworkRequest.NetworkRequest[] {
+    return SDK.NetworkLog.NetworkLog.instance().requests().filter(NetworkLogView.HTTPRequestsFilter).filter(request => {
+      return request.finished ||
+          (request.resourceType() === Common.ResourceType.resourceTypes.WebSocket && request.responseReceivedTime);
+    });
+  }
+
+  async _copyAll(): Promise<void> {
+    const harArchive = {log: await SDK.HARLog.HARLog.build(this._harRequests())};
+    Host.InspectorFrontendHost.InspectorFrontendHostInstance.copyText(JSON.stringify(harArchive, null, 2));
+  }
+
+  async _copyCurlCommand(request: SDK.NetworkRequest.NetworkRequest, platform: string): Promise<void> {
+    const command = await this._generateCurlCommand(request, platform);
+    Host.InspectorFrontendHost.InspectorFrontendHostInstance.copyText(command);
+  }
+
+  async _copyAllCurlCommand(platform: string): Promise<void> {
+    const commands = await this._generateAllCurlCommand(SDK.NetworkLog.NetworkLog.instance().requests(), platform);
+    Host.InspectorFrontendHost.InspectorFrontendHostInstance.copyText(commands);
+  }
+
+  async _copyFetchCall(request: SDK.NetworkRequest.NetworkRequest, includeCookies: boolean): Promise<void> {
+    const command = await this._generateFetchCall(request, includeCookies);
+    Host.InspectorFrontendHost.InspectorFrontendHostInstance.copyText(command);
+  }
+
+  async _copyAllFetchCall(includeCookies: boolean): Promise<void> {
+    const commands = await this._generateAllFetchCall(SDK.NetworkLog.NetworkLog.instance().requests(), includeCookies);
+    Host.InspectorFrontendHost.InspectorFrontendHostInstance.copyText(commands);
+  }
+
+  async _copyPowerShellCommand(request: SDK.NetworkRequest.NetworkRequest): Promise<void> {
+    const command = await this._generatePowerShellCommand(request);
+    Host.InspectorFrontendHost.InspectorFrontendHostInstance.copyText(command);
+  }
+
+  async _copyAllPowerShellCommand(): Promise<void> {
+    const commands = await this._generateAllPowerShellCommand(SDK.NetworkLog.NetworkLog.instance().requests());
+    Host.InspectorFrontendHost.InspectorFrontendHostInstance.copyText(commands);
+  }
+
+  async exportAll(): Promise<void> {
+    const mainTarget = SDK.SDKModel.TargetManager.instance().mainTarget();
+    if (!mainTarget) {
+      return;
+    }
+    const url = mainTarget.inspectedURL();
+    const parsedURL = Common.ParsedURL.ParsedURL.fromString(url);
+    const filename = parsedURL ? parsedURL.host : 'network-log';
+    const stream = new Bindings.FileUtils.FileOutputStream();
+
+    if (!await stream.open(filename + '.har')) {
+      return;
+    }
+
+    const progressIndicator = new UI.ProgressIndicator.ProgressIndicator();
+    this._progressBarContainer.appendChild(progressIndicator.element);
+    await HARWriter.write(stream, this._harRequests(), progressIndicator);
+    progressIndicator.done();
+    stream.close();
+  }
+
+  _clearBrowserCache(): void {
+    if (confirm(i18nString(UIStrings.areYouSureYouWantToClearBrowser))) {
+      SDK.NetworkManager.MultitargetNetworkManager.instance().clearBrowserCache();
+    }
+  }
+
+  _clearBrowserCookies(): void {
+    if (confirm(i18nString(UIStrings.areYouSureYouWantToClearBrowserCookies))) {
+      SDK.NetworkManager.MultitargetNetworkManager.instance().clearBrowserCookies();
+    }
+  }
+
+  _removeAllHighlights(): void {
+    this.removeAllNodeHighlights();
+    for (let i = 0; i < this._highlightedSubstringChanges.length; ++i) {
+      UI.UIUtils.revertDomChanges(this._highlightedSubstringChanges[i]);
+    }
+    this._highlightedSubstringChanges = [];
+  }
+
+  _applyFilter(node: NetworkRequestNode): boolean {
+    const request = node.request();
+    if (this._timeFilter && !this._timeFilter(request)) {
+      return false;
+    }
+    const categoryName = request.resourceType().category().title();
+    if (!this._resourceCategoryFilterUI.accept(categoryName)) {
+      return false;
+    }
+    if (this._dataURLFilterUI.checked() && (request.parsedURL.isDataURL() || request.parsedURL.isBlobURL())) {
+      return false;
+    }
+    if (this._onlyIssuesFilterUI.checked() &&
+        !BrowserSDK.RelatedIssue.hasIssueOfCategory(request, SDK.Issue.IssueCategory.SameSiteCookie)) {
+      return false;
+    }
+    if (this._onlyBlockedRequestsUI.checked() && !request.wasBlocked() && !request.corsErrorStatus()) {
+      return false;
+    }
+    if (request.statusText === 'Service Worker Fallback Required') {
+      return false;
+    }
+    for (let i = 0; i < this._filters.length; ++i) {
+      if (!this._filters[i](request)) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  _parseFilterQuery(query: string): void {
+    const descriptors = this._filterParser.parse(query);
+    this._filters = descriptors.map(descriptor => {
+      const key = descriptor.key;
+      const text = descriptor.text || '';
+      const regex = descriptor.regex;
+      let filter;
+      if (key) {
+        const defaultText = Platform.StringUtilities.escapeForRegExp(key + ':' + text);
+        filter = this._createSpecialFilter((key as FilterType), text) ||
+            NetworkLogView._requestPathFilter.bind(null, new RegExp(defaultText, 'i'));
+      } else if (descriptor.regex) {
+        filter = NetworkLogView._requestPathFilter.bind(null, (regex as RegExp));
+      } else {
+        filter = NetworkLogView._requestPathFilter.bind(
+            null, new RegExp(Platform.StringUtilities.escapeForRegExp(text), 'i'));
+      }
+      return descriptor.negative ? NetworkLogView._negativeFilter.bind(null, filter) : filter;
+    });
+  }
+
+  _createSpecialFilter(type: FilterType, value: string): Filter|null {
+    switch (type) {
+      case FilterType.Domain:
+        return NetworkLogView._createRequestDomainFilter(value);
+
+      case FilterType.HasResponseHeader:
+        return NetworkLogView._requestResponseHeaderFilter.bind(null, value);
+
+      case FilterType.Is:
+        if (value.toLowerCase() === IsFilterType.Running) {
+          return NetworkLogView._runningRequestFilter;
+        }
+        if (value.toLowerCase() === IsFilterType.FromCache) {
+          return NetworkLogView._fromCacheRequestFilter;
+        }
+        if (value.toLowerCase() === IsFilterType.ServiceWorkerIntercepted) {
+          return NetworkLogView._interceptedByServiceWorkerFilter;
+        }
+        if (value.toLowerCase() === IsFilterType.ServiceWorkerInitiated) {
+          return NetworkLogView._initiatedByServiceWorkerFilter;
+        }
+        break;
+
+      case FilterType.LargerThan:
+        return this._createSizeFilter(value.toLowerCase());
+
+      case FilterType.Method:
+        return NetworkLogView._requestMethodFilter.bind(null, value);
+
+      case FilterType.MimeType:
+        return NetworkLogView._requestMimeTypeFilter.bind(null, value);
+
+      case FilterType.MixedContent:
+        return NetworkLogView._requestMixedContentFilter.bind(null, (value as MixedContentFilterValues));
+
+      case FilterType.Scheme:
+        return NetworkLogView._requestSchemeFilter.bind(null, value);
+
+      case FilterType.SetCookieDomain:
+        return NetworkLogView._requestSetCookieDomainFilter.bind(null, value);
+
+      case FilterType.SetCookieName:
+        return NetworkLogView._requestSetCookieNameFilter.bind(null, value);
+
+      case FilterType.SetCookieValue:
+        return NetworkLogView._requestSetCookieValueFilter.bind(null, value);
+
+      case FilterType.CookieDomain:
+        return NetworkLogView._requestCookieDomainFilter.bind(null, value);
+
+      case FilterType.CookieName:
+        return NetworkLogView._requestCookieNameFilter.bind(null, value);
+
+      case FilterType.CookiePath:
+        return NetworkLogView._requestCookiePathFilter.bind(null, value);
+
+      case FilterType.CookieValue:
+        return NetworkLogView._requestCookieValueFilter.bind(null, value);
+
+      case FilterType.Priority:
+        return NetworkLogView._requestPriorityFilter.bind(
+            null, PerfUI.NetworkPriorities.uiLabelToNetworkPriority(value));
+
+      case FilterType.StatusCode:
+        return NetworkLogView._statusCodeFilter.bind(null, value);
+
+      case FilterType.ResourceType:
+        return NetworkLogView._resourceTypeFilter.bind(null, value);
+
+      case FilterType.Url:
+        return NetworkLogView._requestUrlFilter.bind(null, value);
+    }
+    return null;
+  }
+
+  _createSizeFilter(value: string): Filter|null {
+    let multiplier = 1;
+    if (value.endsWith('k')) {
+      multiplier = 1000;
+      value = value.substring(0, value.length - 1);
+    } else if (value.endsWith('m')) {
+      multiplier = 1000 * 1000;
+      value = value.substring(0, value.length - 1);
+    }
+    const quantity = Number(value);
+    if (isNaN(quantity)) {
+      return null;
+    }
+    return NetworkLogView._requestSizeLargerThanFilter.bind(null, quantity * multiplier);
+  }
+
+  _filterRequests(): void {
+    this._removeAllHighlights();
+    this._invalidateAllItems();
+  }
+
+  _reveal(request: SDK.NetworkRequest.NetworkRequest): NetworkRequestNode|null {
+    this.removeAllNodeHighlights();
+    const node = networkRequestToNode.get(request);
+    if (!node || !node.dataGrid) {
+      return null;
+    }
+    // Viewport datagrid nodes do not reveal if not in the root node
+    // list of flatChildren. For children of grouped frame nodes:
+    // reveal and expand parent to ensure child is revealable.
+    if (node.parent && node.parent instanceof NetworkGroupNode) {
+      node.parent.reveal();
+      node.parent.expand();
+    }
+    node.reveal();
+    return node;
+  }
+
+  revealAndHighlightRequest(request: SDK.NetworkRequest.NetworkRequest): void {
+    const node = this._reveal(request);
+    if (node) {
+      this._highlightNode(node);
+    }
+  }
+
+  selectRequest(request: SDK.NetworkRequest.NetworkRequest, options?: FilterOptions): void {
+    const defaultOptions = {clearFilter: true};
+    const {clearFilter} = options || defaultOptions;
+    if (clearFilter) {
+      this.setTextFilterValue('');
+    }
+    const node = this._reveal(request);
+    if (node) {
+      node.select();
+    }
+  }
+
+  removeAllNodeHighlights(): void {
+    if (this._highlightedNode) {
+      this._highlightedNode.element().classList.remove('highlighted-row');
+      this._highlightedNode = null;
+    }
+  }
+
+  _highlightNode(node: NetworkRequestNode): void {
+    UI.UIUtils.runCSSAnimationOnce(node.element(), 'highlighted-row');
+    this._highlightedNode = node;
+  }
+
+  _filterOutBlobRequests(requests: SDK.NetworkRequest.NetworkRequest[]): SDK.NetworkRequest.NetworkRequest[] {
+    return requests.filter(request => !request.isBlobRequest());
+  }
+
+  async _generateFetchCall(request: SDK.NetworkRequest.NetworkRequest, includeCookies: boolean): Promise<string> {
+    const ignoredHeaders = new Set<string>([
+      // Internal headers
+      'method',
+      'path',
+      'scheme',
+      'version',
+
+      // Unsafe headers
+      // Keep this list synchronized with src/net/http/http_util.cc
+      'accept-charset',
+      'accept-encoding',
+      'access-control-request-headers',
+      'access-control-request-method',
+      'connection',
+      'content-length',
+      'cookie',
+      'cookie2',
+      'date',
+      'dnt',
+      'expect',
+      'host',
+      'keep-alive',
+      'origin',
+      'referer',
+      'te',
+      'trailer',
+      'transfer-encoding',
+      'upgrade',
+      'via',
+      // TODO(phistuck) - remove this once crbug.com/571722 is fixed.
+      'user-agent',
+    ]);
+
+    const credentialHeaders = new Set<string>(['cookie', 'authorization']);
+
+    const url = JSON.stringify(request.url());
+
+    const requestHeaders = request.requestHeaders();
+    const headerData: Headers = requestHeaders.reduce((result, header) => {
+      const name = header.name;
+
+      if (!ignoredHeaders.has(name.toLowerCase()) && !name.includes(':')) {
+        result.append(name, header.value);
+      }
+
+      return result;
+    }, new Headers());
+
+    const headers: HeadersInit = {};
+    for (const headerArray of headerData) {
+      headers[headerArray[0]] = headerArray[1];
+    }
+
+    const credentials = request.includedRequestCookies().length ||
+            requestHeaders.some(({name}) => credentialHeaders.has(name.toLowerCase())) ?
+        'include' :
+        'omit';
+
+    const referrerHeader = requestHeaders.find(({name}) => name.toLowerCase() === 'referer');
+
+    const referrer = referrerHeader ? referrerHeader.value : void 0;
+
+    const referrerPolicy = request.referrerPolicy() || void 0;
+
+    const requestBody = await request.requestFormData();
+
+    const fetchOptions: RequestInit = {
+      headers: Object.keys(headers).length ? headers : void 0,
+      referrer,
+      referrerPolicy,
+      body: requestBody,
+      method: request.requestMethod,
+      mode: 'cors',
+    };
+
+    if (includeCookies) {
+      const cookieHeader = requestHeaders.find(header => header.name.toLowerCase() === 'cookie');
+      if (cookieHeader) {
+        fetchOptions.headers = {
+          ...headers,
+          'cookie': cookieHeader.value,
+        };
+      }
+    } else {
+      fetchOptions.credentials = credentials;
+    }
+
+    const options = JSON.stringify(fetchOptions, null, 2);
+    return `fetch(${url}, ${options});`;
+  }
+
+  async _generateAllFetchCall(requests: SDK.NetworkRequest.NetworkRequest[], includeCookies: boolean): Promise<string> {
+    const nonBlobRequests = this._filterOutBlobRequests(requests);
+    const commands =
+        await Promise.all(nonBlobRequests.map(request => this._generateFetchCall(request, includeCookies)));
+    return commands.join(' ;\n');
+  }
+
+  async _generateCurlCommand(request: SDK.NetworkRequest.NetworkRequest, platform: string): Promise<string> {
+    let command: string[] = [];
+    // Most of these headers are derived from the URL and are automatically added by cURL.
+    // The |Accept-Encoding| header is ignored to prevent decompression errors. crbug.com/1015321
+    const ignoredHeaders = new Set<string>(['accept-encoding', 'host', 'method', 'path', 'scheme', 'version']);
+
+    function escapeStringWin(str: string): string {
+      /* If there are no new line characters do not escape the " characters
+         since it only uglifies the command.
+
+         Because cmd.exe parser and MS Crt arguments parsers use some of the
+         same escape characters, they can interact with each other in
+         horrible ways, the order of operations is critical.
+
+         Replace \ with \\ first because it is an escape character for certain
+         conditions in both parsers.
+
+         Replace all " with \" to ensure the first parser does not remove it.
+
+         Then escape all characters we are not sure about with ^ to ensure it
+         gets to MS Crt parser safely.
+
+         The % character is special because MS Crt parser will try and look for
+         ENV variables and fill them in it's place. We cannot escape them with %
+         and cannot escape them with ^ (because it's cmd.exe's escape not MS Crt
+         parser); So we can get cmd.exe parser to escape the character after it,
+         if it is followed by a valid beginning character of an ENV variable.
+         This ensures we do not try and double escape another ^ if it was placed
+         by the previous replace.
+
+         Lastly we replace new lines with ^ and TWO new lines because the first
+         new line is there to enact the escape command the second is the character
+         to escape (in this case new line).
+        */
+      const encapsChars = /[\r\n]/.test(str) ? '^"' : '"';
+      return encapsChars +
+          str.replace(/\\/g, '\\\\')
+              .replace(/"/g, '\\"')
+              .replace(/[^a-zA-Z0-9\s_\-:=+~'\/.',?;()*`&]/g, '^$&')
+              .replace(/%(?=[a-zA-Z0-9_])/g, '%^')
+              .replace(/\r?\n/g, '^\n\n') +
+          encapsChars;
+    }
+
+    function escapeStringPosix(str: string): string {
+      function escapeCharacter(x: string): string {
+        const code = x.charCodeAt(0);
+        let hexString = code.toString(16);
+        // Zero pad to four digits to comply with ANSI-C Quoting:
+        // http://www.gnu.org/software/bash/manual/html_node/ANSI_002dC-Quoting.html
+        while (hexString.length < 4) {
+          hexString = '0' + hexString;
+        }
+
+        return '\\u' + hexString;
+      }
+
+      if (/[\0-\x1F\x7F-\x9F!]|\'/.test(str)) {
+        // Use ANSI-C quoting syntax.
+        return '$\'' +
+            str.replace(/\\/g, '\\\\')
+                .replace(/\'/g, '\\\'')
+                .replace(/\n/g, '\\n')
+                .replace(/\r/g, '\\r')
+                .replace(/[\0-\x1F\x7F-\x9F!]/g, escapeCharacter) +
+            '\'';
+      }
+      // Use single quote syntax.
+      return '\'' + str + '\'';
+    }
+
+    // cURL command expected to run on the same platform that DevTools run
+    // (it may be different from the inspected page platform).
+    const escapeString = platform === 'win' ? escapeStringWin : escapeStringPosix;
+
+    command.push(escapeString(request.url()).replace(/[[{}\]]/g, '\\$&'));
+
+    let inferredMethod = 'GET';
+    const data = [];
+    const requestContentType = request.requestContentType();
+    const formData = await request.requestFormData();
+    if (requestContentType && requestContentType.startsWith('application/x-www-form-urlencoded') && formData) {
+      // Note that formData is not necessarily urlencoded because it might for example
+      // come from a fetch request made with an explicitly unencoded body.
+      data.push('--data-raw ' + escapeString(formData));
+      ignoredHeaders.add('content-length');
+      inferredMethod = 'POST';
+    } else if (formData) {
+      data.push('--data-raw ' + escapeString(formData));
+      ignoredHeaders.add('content-length');
+      inferredMethod = 'POST';
+    }
+
+    if (request.requestMethod !== inferredMethod) {
+      command.push('-X ' + escapeString(request.requestMethod));
+    }
+
+    const requestHeaders = request.requestHeaders();
+    for (let i = 0; i < requestHeaders.length; i++) {
+      const header = requestHeaders[i];
+      const name = header.name.replace(/^:/, '');  // Translate SPDY v3 headers to HTTP headers.
+      if (ignoredHeaders.has(name.toLowerCase())) {
+        continue;
+      }
+      command.push('-H ' + escapeString(name + ': ' + header.value));
+    }
+    command = command.concat(data);
+    command.push('--compressed');
+
+    if (request.securityState() === Protocol.Security.SecurityState.Insecure) {
+      command.push('--insecure');
+    }
+    return 'curl ' + command.join(command.length >= 3 ? (platform === 'win' ? ' ^\n  ' : ' \\\n  ') : ' ');
+  }
+
+  async _generateAllCurlCommand(requests: SDK.NetworkRequest.NetworkRequest[], platform: string): Promise<string> {
+    const nonBlobRequests = this._filterOutBlobRequests(requests);
+    const commands = await Promise.all(nonBlobRequests.map(request => this._generateCurlCommand(request, platform)));
+    if (platform === 'win') {
+      return commands.join(' &\r\n');
+    }
+    return commands.join(' ;\n');
+  }
+
+  async _generatePowerShellCommand(request: SDK.NetworkRequest.NetworkRequest): Promise<string> {
+    const command = [];
+    const ignoredHeaders = new Set<string>(
+        ['host', 'connection', 'proxy-connection', 'content-length', 'expect', 'range', 'content-type']);
+
+    function escapeString(str: string): string {
+      return '"' +
+          str.replace(/[`\$"]/g, '`$&').replace(/[^\x20-\x7E]/g, char => '$([char]' + char.charCodeAt(0) + ')') + '"';
+    }
+
+    command.push('-Uri ' + escapeString(request.url()));
+
+    if (request.requestMethod !== 'GET') {
+      command.push('-Method ' + escapeString(request.requestMethod));
+    }
+
+    const requestHeaders = request.requestHeaders();
+    const headerNameValuePairs = [];
+    for (const header of requestHeaders) {
+      const name = header.name.replace(/^:/, '');  // Translate h2 headers to HTTP headers.
+      if (ignoredHeaders.has(name.toLowerCase())) {
+        continue;
+      }
+      headerNameValuePairs.push(escapeString(name) + '=' + escapeString(header.value));
+    }
+    if (headerNameValuePairs.length) {
+      command.push('-Headers @{\n' + headerNameValuePairs.join('\n  ') + '\n}');
+    }
+
+    const contentTypeHeader = requestHeaders.find(({name}) => name.toLowerCase() === 'content-type');
+    if (contentTypeHeader) {
+      command.push('-ContentType ' + escapeString(contentTypeHeader.value));
+    }
+
+    const formData = await request.requestFormData();
+    if (formData) {
+      const body = escapeString(formData);
+      if (/[^\x20-\x7E]/.test(formData)) {
+        command.push('-Body ([System.Text.Encoding]::UTF8.GetBytes(' + body + '))');
+      } else {
+        command.push('-Body ' + body);
+      }
+    }
+
+    return 'Invoke-WebRequest ' + command.join(command.length >= 3 ? ' `\n' : ' ');
+  }
+
+  async _generateAllPowerShellCommand(requests: SDK.NetworkRequest.NetworkRequest[]): Promise<string> {
+    const nonBlobRequests = this._filterOutBlobRequests(requests);
+    const commands = await Promise.all(nonBlobRequests.map(request => this._generatePowerShellCommand(request)));
+    return commands.join(';\r\n');
+  }
+
+  static getDCLEventColor(): string {
+    if (ThemeSupport.ThemeSupport.instance().themeName() === 'dark') {
+      return '#03A9F4';
+    }
+    return '#0867CB';
+  }
+
+  static getLoadEventColor(): string {
+    return ThemeSupport.ThemeSupport.instance().patchColorText(
+        '#B31412', ThemeSupport.ThemeSupport.ColorUsage.Foreground);
+  }
+}
+
+export function computeStackTraceText(stackTrace: Protocol.Runtime.StackTrace): string {
+  let stackTraceText = '';
+  for (const frame of stackTrace.callFrames) {
+    const functionName = UI.UIUtils.beautifyFunctionName(frame.functionName);
+    stackTraceText += `${functionName} @ ${frame.url}:${frame.lineNumber + 1}\n`;
+  }
+  if (stackTrace.parent) {
+    stackTraceText += computeStackTraceText(stackTrace.parent);
+  }
+  return stackTraceText;
+}
+
+const filteredNetworkRequests = new WeakSet<NetworkRequestNode>();
+const networkRequestToNode = new WeakMap<SDK.NetworkRequest.NetworkRequest, NetworkRequestNode>();
+
+export function isRequestFilteredOut(request: NetworkRequestNode): boolean {
+  return filteredNetworkRequests.has(request);
+}
+
+export const HTTPSchemas = {
+  'http': true,
+  'https': true,
+  'ws': true,
+  'wss': true,
+};
+
+// TODO(crbug.com/1167717): Make this a const enum again
+// eslint-disable-next-line rulesdir/const_enum
+export enum FilterType {
+  Domain = 'domain',
+  HasResponseHeader = 'has-response-header',
+  Is = 'is',
+  LargerThan = 'larger-than',
+  Method = 'method',
+  MimeType = 'mime-type',
+  MixedContent = 'mixed-content',
+  Priority = 'priority',
+  Scheme = 'scheme',
+  SetCookieDomain = 'set-cookie-domain',
+  SetCookieName = 'set-cookie-name',
+  SetCookieValue = 'set-cookie-value',
+  ResourceType = 'resource-type',
+  CookieDomain = 'cookie-domain',
+  CookieName = 'cookie-name',
+  CookiePath = 'cookie-path',
+  CookieValue = 'cookie-value',
+  StatusCode = 'status-code',
+  Url = 'url',
+}
+
+// TODO(crbug.com/1167717): Make this a const enum again
+// eslint-disable-next-line rulesdir/const_enum
+export enum MixedContentFilterValues {
+  All = 'all',
+  Displayed = 'displayed',
+  Blocked = 'blocked',
+  BlockOverridden = 'block-overridden',
+}
+
+// TODO(crbug.com/1167717): Make this a const enum again
+// eslint-disable-next-line rulesdir/const_enum
+export enum IsFilterType {
+  Running = 'running',
+  FromCache = 'from-cache',
+  ServiceWorkerIntercepted = 'service-worker-intercepted',
+  ServiceWorkerInitiated = 'service-worker-initiated',
+}
+
+// TODO(crbug.com/1172300) Ignored during the jsdoc to ts migration)
+// eslint-disable-next-line @typescript-eslint/naming-convention
+export const _searchKeys: string[] = Object.values(FilterType);
+
+export interface GroupLookupInterface {
+  groupNodeForRequest(request: SDK.NetworkRequest.NetworkRequest): NetworkGroupNode|null;
+  reset(): void;
+}
+
+export type Filter = (request: SDK.NetworkRequest.NetworkRequest) => boolean;