Add 'Request types' dropdown in the filter bar of the Network Panel.
This dropdown contains the filtering options for requests by their type.
Bug: 1475578
Change-Id: I8fda007ad3c32a41ede9454e2c59c19ce11592c9
Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/4793630
Reviewed-by: Wolfgang Beyer <[email protected]>
Commit-Queue: Ioana Forfotă <[email protected]>
diff --git a/front_end/core/common/ResourceType.ts b/front_end/core/common/ResourceType.ts
index f6bb33d..a322a0e 100644
--- a/front_end/core/common/ResourceType.ts
+++ b/front_end/core/common/ResourceType.ts
@@ -372,16 +372,16 @@
}
export const resourceCategories = {
- XHR: new ResourceCategory(i18nLazyString(UIStrings.xhrAndFetch), i18n.i18n.lockedLazyString('Fetch/XHR')),
+ Document: new ResourceCategory(i18nLazyString(UIStrings.documents), i18nLazyString(UIStrings.doc)),
Script: new ResourceCategory(i18nLazyString(UIStrings.scripts), i18nLazyString(UIStrings.js)),
+ XHR: new ResourceCategory(i18nLazyString(UIStrings.xhrAndFetch), i18n.i18n.lockedLazyString('Fetch/XHR')),
Stylesheet: new ResourceCategory(i18nLazyString(UIStrings.stylesheets), i18nLazyString(UIStrings.css)),
+ Font: new ResourceCategory(i18nLazyString(UIStrings.fonts), i18nLazyString(UIStrings.font)),
Image: new ResourceCategory(i18nLazyString(UIStrings.images), i18nLazyString(UIStrings.img)),
Media: new ResourceCategory(i18nLazyString(UIStrings.media), i18nLazyString(UIStrings.media)),
- Font: new ResourceCategory(i18nLazyString(UIStrings.fonts), i18nLazyString(UIStrings.font)),
- Document: new ResourceCategory(i18nLazyString(UIStrings.documents), i18nLazyString(UIStrings.doc)),
+ Manifest: new ResourceCategory(i18nLazyString(UIStrings.manifest), i18nLazyString(UIStrings.manifest)),
WebSocket: new ResourceCategory(i18nLazyString(UIStrings.websockets), i18nLazyString(UIStrings.ws)),
Wasm: new ResourceCategory(i18nLazyString(UIStrings.webassembly), i18nLazyString(UIStrings.wasm)),
- Manifest: new ResourceCategory(i18nLazyString(UIStrings.manifest), i18nLazyString(UIStrings.manifest)),
Other: new ResourceCategory(i18nLazyString(UIStrings.other), i18nLazyString(UIStrings.other)),
};
diff --git a/front_end/core/host/UserMetrics.ts b/front_end/core/host/UserMetrics.ts
index 194c157..c83a67f 100644
--- a/front_end/core/host/UserMetrics.ts
+++ b/front_end/core/host/UserMetrics.ts
@@ -929,9 +929,10 @@
'useSourceMapScopes' = 76,
'storageBucketsTree' = 77,
'deleteOverridesTemporarilyEnable' = 78,
+ 'networkPanelFilterBarRedesign' = 79,
// Increment this when new experiments are added.
- 'MaxValue' = 79,
+ 'MaxValue' = 80,
}
/* eslint-enable @typescript-eslint/naming-convention */
diff --git a/front_end/core/root/Runtime.ts b/front_end/core/root/Runtime.ts
index 576b152..2cad0b3 100644
--- a/front_end/core/root/Runtime.ts
+++ b/front_end/core/root/Runtime.ts
@@ -313,6 +313,7 @@
USE_SOURCE_MAP_SCOPES = 'useSourceMapScopes',
STORAGE_BUCKETS_TREE = 'storageBucketsTree',
DELETE_OVERRIDES_TEMP_ENABLE = 'deleteOverridesTemporarilyEnable',
+ NETWORK_PANEL_FILTER_BAR_REDESIGN = 'networkPanelFilterBarRedesign',
}
// TODO(crbug.com/1167717): Make this a const enum again
diff --git a/front_end/entrypoints/main/MainImpl.ts b/front_end/entrypoints/main/MainImpl.ts
index a2fad75..23fd311 100644
--- a/front_end/entrypoints/main/MainImpl.ts
+++ b/front_end/entrypoints/main/MainImpl.ts
@@ -419,6 +419,11 @@
Root.Runtime.experiments.register(
Root.Runtime.ExperimentName.STORAGE_BUCKETS_TREE, 'Enable Storage Buckets Tree in Application panel', true);
+ Root.Runtime.experiments.register(
+ Root.Runtime.ExperimentName.NETWORK_PANEL_FILTER_BAR_REDESIGN,
+ 'Redesign of the filter bar in the Network Panel',
+ );
+
Root.Runtime.experiments.enableExperimentsByDefault([
'sourceOrderViewer',
'cssTypeComponentLength',
diff --git a/front_end/panels/network/NetworkLogView.ts b/front_end/panels/network/NetworkLogView.ts
index f2cc8ca..554c440 100644
--- a/front_end/panels/network/NetworkLogView.ts
+++ b/front_end/panels/network/NetworkLogView.ts
@@ -52,24 +52,22 @@
import * as Components from '../../ui/legacy/components/utils/utils.js';
import * as UI from '../../ui/legacy/legacy.js';
-import networkLogViewStyles from './networkLogView.css.js';
-
import {
Events,
+ type EventTypes,
NetworkGroupNode,
- NetworkRequestNode,
type NetworkLogViewInterface,
type NetworkNode,
- type EventTypes,
+ NetworkRequestNode,
} from './NetworkDataGridNode.js';
import {NetworkFrameGrouper} from './NetworkFrameGrouper.js';
+import networkLogViewStyles from './networkLogView.css.js';
import {NetworkLogViewColumns} from './NetworkLogViewColumns.js';
-
import {
NetworkTimeBoundary,
+ type NetworkTimeCalculator,
NetworkTransferDurationCalculator,
NetworkTransferTimeCalculator,
- type NetworkTimeCalculator,
} from './NetworkTimeCalculator.js';
const UIStrings = {
@@ -82,6 +80,10 @@
*/
invertsFilter: 'Inverts the search filter',
/**
+ *@description Text for everything
+ */
+ allStrings: 'All',
+ /**
*@description Text in Network Log View of the Network panel
*/
hideDataUrls: 'Hide data URLs',
@@ -100,7 +102,15 @@
/**
*@description Aria accessible name in Network Log View of the Network panel
*/
- resourceTypesToInclude: 'Resource types to include',
+ requestTypesToInclude: 'Request types to include',
+ /**
+ * @description Tooltip for the `Request types` dropdown in the Network Panel
+ */
+ requestTypesTooltip: 'Filter requests by type',
+ /**
+ * @description: Label for the dropdown in the Network Panel
+ */
+ requestTypes: 'Request Types',
/**
*@description Label for a checkbox in the Network panel. When checked, only requests with
* blocked response cookies are shown.
@@ -414,11 +424,11 @@
private readonly textFilterUI: UI.FilterBar.TextFilterUI;
private readonly invertFilterUI: UI.FilterBar.CheckboxFilterUI;
private readonly dataURLFilterUI: UI.FilterBar.CheckboxFilterUI;
- private resourceCategoryFilterUI: UI.FilterBar.NamedBitSetFilterUI;
private readonly onlyBlockedResponseCookiesFilterUI: UI.FilterBar.CheckboxFilterUI;
private readonly onlyBlockedRequestsUI: UI.FilterBar.CheckboxFilterUI;
private readonly onlyThirdPartyFilterUI: UI.FilterBar.CheckboxFilterUI;
private readonly hideChromeExtensionsUI: UI.FilterBar.CheckboxFilterUI;
+ private readonly resourceCategoryFilterUI: DropDownTypesUI|UI.FilterBar.NamedBitSetFilterUI;
private readonly filterParser: TextUtils.TextUtils.FilterParser;
private readonly suggestionBuilder: UI.FilterSuggestionBuilder.FilterSuggestionBuilder;
private dataGrid: DataGrid.SortableDataGrid.SortableDataGrid<NetworkNode>;
@@ -521,9 +531,15 @@
.map(
category =>
({name: category.title(), label: (): string => category.shortTitle(), title: category.title()}));
- this.resourceCategoryFilterUI =
- new UI.FilterBar.NamedBitSetFilterUI(filterItems, this.networkResourceTypeFiltersSetting);
- UI.ARIAUtils.setLabel(this.resourceCategoryFilterUI.element(), i18nString(UIStrings.resourceTypesToInclude));
+
+ if (Root.Runtime.experiments.isEnabled(Root.Runtime.ExperimentName.NETWORK_PANEL_FILTER_BAR_REDESIGN)) {
+ this.resourceCategoryFilterUI =
+ new DropDownTypesUI(filterItems, this.filterChanged.bind(this), this.networkResourceTypeFiltersSetting);
+ } else {
+ this.resourceCategoryFilterUI =
+ new UI.FilterBar.NamedBitSetFilterUI(filterItems, this.networkResourceTypeFiltersSetting);
+ }
+ UI.ARIAUtils.setLabel(this.resourceCategoryFilterUI.element(), i18nString(UIStrings.requestTypesToInclude));
this.resourceCategoryFilterUI.addEventListener(
UI.FilterBar.FilterUIEvents.FilterChanged, this.filterChanged.bind(this), this);
filterBar.addFilter(this.resourceCategoryFilterUI);
@@ -2453,3 +2469,141 @@
};
export type Filter = (request: SDK.NetworkRequest.NetworkRequest) => boolean;
+
+export class DropDownTypesUI extends Common.ObjectWrapper.ObjectWrapper<UI.FilterBar.FilterUIEventTypes> implements
+ UI.FilterBar.FilterUI {
+ private readonly filterElement: HTMLDivElement;
+ private readonly dropDownButton: UI.Toolbar.ToolbarButton;
+ private readonly filterChanged: () => void;
+ private displayedTypes: Set<string>;
+ private readonly setting: Common.Settings.Setting<{[key: string]: boolean}>;
+ private readonly items: UI.FilterBar.Item[];
+ private contextMenu?: UI.ContextMenu.ContextMenu;
+
+ constructor(
+ items: UI.FilterBar.Item[], filterChangedCallback: () => void,
+ setting: Common.Settings.Setting<{[key: string]: boolean}>) {
+ super();
+ this.items = items;
+ this.filterChanged = filterChangedCallback;
+
+ this.filterElement = document.createElement('div');
+ this.dropDownButton = new UI.Toolbar.ToolbarButton(UIStrings.requestTypesTooltip);
+ this.dropDownButton.setText(UIStrings.requestTypes);
+ this.filterElement.appendChild(this.dropDownButton.element);
+ this.dropDownButton.turnIntoSelect();
+ this.dropDownButton.element.classList.add('dropdown-filterbar');
+
+ this.dropDownButton.addEventListener(UI.Toolbar.ToolbarButton.Events.Click, this.showContextMenu.bind(this));
+ UI.ARIAUtils.markAsMenuButton(this.dropDownButton.element);
+
+ this.displayedTypes = new Set();
+
+ this.setting = setting;
+ setting.addChangeListener(this.settingChanged.bind(this));
+ this.setting.addChangeListener(this.filterChanged.bind(this));
+ this.settingChanged();
+ }
+
+ discard(): void {
+ this.contextMenu?.discard();
+ }
+
+ showContextMenu(event: Common.EventTarget.EventTargetEvent<Event>): void {
+ const mouseEvent = event.data;
+ this.contextMenu = new UI.ContextMenu.ContextMenu(mouseEvent, {
+ useSoftMenu: true,
+ keepOpen: true,
+ x: this.dropDownButton.element.getBoundingClientRect().left,
+ y: this.dropDownButton.element.getBoundingClientRect().top +
+ (this.dropDownButton.element as HTMLElement).offsetHeight,
+ });
+
+ this.addRequestType(this.contextMenu, DropDownTypesUI.ALL_TYPES, i18nString(UIStrings.allStrings));
+ this.contextMenu.defaultSection().appendSeparator();
+
+ for (const item of this.items) {
+ this.addRequestType(this.contextMenu, item.name, item.name);
+ }
+
+ this.update();
+ void this.contextMenu.show();
+ }
+
+ private addRequestType(contextMenu: UI.ContextMenu.ContextMenu, name: string, label: string): void {
+ contextMenu.defaultSection().appendCheckboxItem(label, () => {
+ this.setting.get()[name] = !this.setting.get()[name];
+ this.toggleTypeFilter(name);
+ }, this.setting.get()[name]);
+ }
+
+ private toggleTypeFilter(typeName: string): void {
+ if (typeName !== DropDownTypesUI.ALL_TYPES) {
+ this.displayedTypes.delete(DropDownTypesUI.ALL_TYPES);
+ } else {
+ this.displayedTypes = new Set();
+ }
+
+ if (this.displayedTypes.has(typeName)) {
+ this.displayedTypes.delete(typeName);
+ } else {
+ this.displayedTypes.add(typeName);
+ }
+
+ if (this.displayedTypes.size === 0) {
+ this.displayedTypes.add(DropDownTypesUI.ALL_TYPES);
+ }
+
+ // Settings do not support `Sets` so convert it back to the Map-like object.
+ const updatedSetting = {} as {[key: string]: boolean};
+ for (const type of this.displayedTypes) {
+ updatedSetting[type] = true;
+ }
+
+ this.setting.set(updatedSetting);
+
+ // For the feature of keeping the dropdown open while choosing its options:
+ // this code provides the dinamic changes of the checkboxes' state in this dropdown
+ const menuItems = this.contextMenu?.getItems() || [];
+ for (const i of menuItems) {
+ if (i.label) {
+ this.contextMenu?.setChecked(i, this.displayedTypes.has(i.label));
+ }
+ }
+ this.contextMenu?.setChecked(menuItems[0], this.displayedTypes.has('all'));
+ }
+
+ private settingChanged(): void {
+ this.displayedTypes = new Set();
+
+ for (const s in this.setting.get()) {
+ this.displayedTypes.add(s);
+ }
+ this.update();
+ }
+
+ private update(): void {
+ if (this.displayedTypes.size === 0 || this.displayedTypes.has(DropDownTypesUI.ALL_TYPES)) {
+ this.displayedTypes = new Set();
+ this.displayedTypes.add(DropDownTypesUI.ALL_TYPES);
+ }
+ }
+
+ isActive(): boolean {
+ return !this.displayedTypes.has(DropDownTypesUI.ALL_TYPES);
+ }
+
+ element(): HTMLDivElement {
+ return this.filterElement;
+ }
+
+ reset(): void {
+ this.toggleTypeFilter(DropDownTypesUI.ALL_TYPES);
+ }
+
+ accept(typeName: string): boolean {
+ return this.displayedTypes.has(DropDownTypesUI.ALL_TYPES) || this.displayedTypes.has(typeName);
+ }
+
+ static readonly ALL_TYPES = 'all';
+}
diff --git a/front_end/ui/legacy/ContextMenu.ts b/front_end/ui/legacy/ContextMenu.ts
index 1849610..c97bb78 100644
--- a/front_end/ui/legacy/ContextMenu.ts
+++ b/front_end/ui/legacy/ContextMenu.ts
@@ -199,8 +199,9 @@
}
appendCheckboxItem(
- label: string, handler: () => void, checked?: boolean, disabled?: boolean, additionalElement?: Element): Item {
- const item = new Item(this.contextMenu, 'checkbox', label, disabled, checked);
+ label: string, handler: () => void, checked?: boolean, disabled?: boolean, additionalElement?: Element,
+ tooltip?: Platform.UIString.LocalizedString): Item {
+ const item = new Item(this.contextMenu, 'checkbox', label, disabled, checked, tooltip);
this.items.push(item);
if (this.contextMenu) {
this.contextMenu.setHandler(item.id(), handler);
@@ -375,6 +376,7 @@
private softMenu?: SoftContextMenu;
private contextMenuLabel?: string;
private hostedMenuOpened: boolean;
+ private eventTarget: EventTarget|null;
constructor(event: Event, options: ContextMenuOptions = {}) {
super(null);
@@ -385,6 +387,7 @@
this.pendingPromises = [];
this.pendingTargets = [];
this.event = mouseEvent;
+ this.eventTarget = this.event.target;
this.useSoftMenu = Boolean(options.useSoftMenu);
this.keepOpen = Boolean(options.keepOpen);
this.x = options.x === undefined ? mouseEvent.x : options.x;
@@ -467,11 +470,11 @@
private innerShow(): void {
const menuObject = this.buildMenuDescriptors();
- const eventTarget = this.event.target;
- if (!eventTarget) {
+
+ if (!this.eventTarget) {
return;
}
- const ownerDocument = (eventTarget as HTMLElement).ownerDocument;
+ const ownerDocument = (this.eventTarget as HTMLElement).ownerDocument;
if (this.useSoftMenu || ContextMenu.useSoftMenu ||
Host.InspectorFrontendHost.InspectorFrontendHostInstance.isHostedMode()) {
this.softMenu = new SoftContextMenu(
diff --git a/front_end/ui/legacy/filter.css b/front_end/ui/legacy/filter.css
index a0ee745..b2a2aa1 100644
--- a/front_end/ui/legacy/filter.css
+++ b/front_end/ui/legacy/filter.css
@@ -113,6 +113,33 @@
margin: auto 0;
}
+.toolbar-has-dropdown-shrinkable {
+ flex-shrink: 1;
+}
+
+.toolbar-text {
+ margin: 0 4px 0 0;
+ text-overflow: ellipsis;
+ flex: auto;
+ overflow: hidden;
+ text-align: right;
+}
+
+.dropdown-filterbar {
+ justify-content: space-between;
+ padding: 0 3px 0 5px;
+ border: 1px solid transparent;
+ margin: 0 7px;
+ border-radius: 7px;
+ display: flex;
+ background-color: transparent;
+ color: var(--sys-color-on-surface-subtle);
+}
+
+.dropdown-filterbar:hover {
+ color: var(--sys-color-on-surface);
+}
+
.filter-input-field {
padding-left: 3px;
width: 163px;