Change the Network filter bar redesign and enable it by default
Bug: 362672528
Change-Id: Ia486a175f14b0c896ba990f41f9074f0a58c2cca
Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/5822071
Auto-Submit: Kateryna Prokopenko <[email protected]>
Reviewed-by: Wolfgang Beyer <[email protected]>
Commit-Queue: Wolfgang Beyer <[email protected]>
diff --git a/front_end/entrypoints/main/MainImpl.ts b/front_end/entrypoints/main/MainImpl.ts
index 9e551f1..94aa595 100644
--- a/front_end/entrypoints/main/MainImpl.ts
+++ b/front_end/entrypoints/main/MainImpl.ts
@@ -431,6 +431,7 @@
Root.Runtime.ExperimentName.PRELOADING_STATUS_PANEL,
Root.Runtime.ExperimentName.AUTOFILL_VIEW,
Root.Runtime.ExperimentName.TIMELINE_OBSERVATIONS,
+ Root.Runtime.ExperimentName.NETWORK_PANEL_FILTER_BAR_REDESIGN,
...(Root.Runtime.Runtime.queryParam('isChromeForTesting') ? ['protocol-monitor'] : []),
]);
diff --git a/front_end/panels/network/NetworkLogView.test.ts b/front_end/panels/network/NetworkLogView.test.ts
index 6de54a8..a08d8af 100644
--- a/front_end/panels/network/NetworkLogView.test.ts
+++ b/front_end/panels/network/NetworkLogView.test.ts
@@ -15,7 +15,6 @@
getMenuItemLabels,
} from '../../testing/ContextMenuHelpers.js';
import {
- dispatchClickEvent,
dispatchMouseUpEvent,
raf,
} from '../../testing/DOMHelpers.js';
@@ -423,154 +422,6 @@
networkLogView.detach();
});
- it('can automatically check the `All` option in the `Request Type` when the only type checked becomes unchecked',
- async () => {
- Root.Runtime.experiments.enableForTest(Root.Runtime.ExperimentName.NETWORK_PANEL_FILTER_BAR_REDESIGN);
-
- const dropdown = setupRequestTypesDropdown();
- const button = dropdown.element().querySelector('.toolbar-button');
-
- assert.instanceOf(button, HTMLElement);
- dispatchClickEvent(button, {bubbles: true, composed: true});
- await raf();
-
- const optionImg = getRequestTypeDropdownOption('Image');
- const optionImgCheckmark = optionImg?.querySelector('.checkmark') || null;
- const optionAll = getRequestTypeDropdownOption('All');
- const optionAllCheckmark = optionAll?.querySelector('.checkmark') || null;
-
- assert.instanceOf(optionImg, HTMLElement);
- assert.instanceOf(optionImgCheckmark, HTMLElement);
- assert.instanceOf(optionAll, HTMLElement);
- assert.instanceOf(optionAllCheckmark, HTMLElement);
-
- assert.isTrue(optionAll.ariaLabel === 'All, checked');
- assert.isTrue(optionImg.ariaLabel === 'Image, unchecked');
- assert.isTrue(window.getComputedStyle(optionAllCheckmark).getPropertyValue('opacity') === '1');
- assert.isTrue(window.getComputedStyle(optionImgCheckmark).getPropertyValue('opacity') === '0');
-
- await selectRequestTypesOption('Image');
-
- assert.isTrue(optionAll.ariaLabel === 'All, unchecked');
- assert.isTrue(optionImg.ariaLabel === 'Image, checked');
- assert.isTrue(window.getComputedStyle(optionAllCheckmark).getPropertyValue('opacity') === '0');
- assert.isTrue(window.getComputedStyle(optionImgCheckmark).getPropertyValue('opacity') === '1');
-
- await selectRequestTypesOption('Image');
-
- assert.isTrue(optionAll.ariaLabel === 'All, checked');
- assert.isTrue(optionImg.ariaLabel === 'Image, unchecked');
- assert.isTrue(window.getComputedStyle(optionAllCheckmark).getPropertyValue('opacity') === '1');
- assert.isTrue(window.getComputedStyle(optionImgCheckmark).getPropertyValue('opacity') === '0');
-
- dropdown.discard();
- await raf();
- });
-
- it('shows correct selected request types count', async () => {
- Root.Runtime.experiments.enableForTest(Root.Runtime.ExperimentName.NETWORK_PANEL_FILTER_BAR_REDESIGN);
- const umaCountSpy = sinon.spy(Host.userMetrics, 'resourceTypeFilterNumberOfSelectedChanged');
- const umaTypeSpy = sinon.spy(Host.userMetrics, 'resourceTypeFilterItemSelected');
-
- const dropdown = setupRequestTypesDropdown();
- const button = dropdown.element().querySelector('.toolbar-button');
- assert.instanceOf(button, HTMLElement);
-
- let countAdorner = button.querySelector('.active-filters-count');
- assert.isTrue(countAdorner?.classList.contains('hidden'));
-
- dispatchClickEvent(button, {bubbles: true, composed: true});
- await raf();
- await selectRequestTypesOption('Image');
-
- countAdorner = button.querySelector('.active-filters-count');
- assert.isFalse(countAdorner?.classList.contains('hidden'));
- assert.strictEqual(countAdorner?.querySelector('[slot="content"]')?.textContent, '1');
-
- dropdown.discard();
- await raf();
- assert.isTrue(umaCountSpy.calledOnceWith(1));
- assert.isTrue(umaTypeSpy.calledOnceWith('Image'));
- });
-
- it('adjusts request types label dynamically', async () => {
- Root.Runtime.experiments.enableForTest(Root.Runtime.ExperimentName.NETWORK_PANEL_FILTER_BAR_REDESIGN);
-
- const dropdown = setupRequestTypesDropdown();
- const button = dropdown.element().querySelector('.toolbar-button');
- assert.instanceOf(button, HTMLElement);
-
- let toolbarText = button.querySelector('.toolbar-text')?.textContent;
- assert.strictEqual(toolbarText, 'Request types');
-
- dispatchClickEvent(button, {bubbles: true, composed: true});
- await raf();
- await selectRequestTypesOption('Image');
- await selectRequestTypesOption('JavaScript');
-
- toolbarText = button.querySelector('.toolbar-text')?.textContent;
- assert.strictEqual(toolbarText, 'JS, Img');
-
- await selectRequestTypesOption('CSS');
-
- toolbarText = button.querySelector('.toolbar-text')?.textContent;
- assert.strictEqual(toolbarText, 'CSS, JS...');
-
- dropdown.discard();
- await raf();
- });
-
- it('lists selected types in requests types tooltip', async () => {
- Root.Runtime.experiments.enableForTest(Root.Runtime.ExperimentName.NETWORK_PANEL_FILTER_BAR_REDESIGN);
- const umaCountSpy = sinon.spy(Host.userMetrics, 'resourceTypeFilterNumberOfSelectedChanged');
- const umaTypeSpy = sinon.spy(Host.userMetrics, 'resourceTypeFilterItemSelected');
-
- const dropdown = setupRequestTypesDropdown();
- const button = dropdown.element().querySelector('.toolbar-button');
- assert.instanceOf(button, HTMLElement);
-
- let tooltipText = button.title;
- assert.strictEqual(tooltipText, 'Filter requests by type');
-
- dispatchClickEvent(button, {bubbles: true, composed: true});
- await raf();
- await selectRequestTypesOption('Image');
- await selectRequestTypesOption('JavaScript');
-
- tooltipText = button.title;
- assert.strictEqual(tooltipText, 'Show only JavaScript, Image');
-
- dropdown.discard();
- await raf();
- assert.isTrue(umaCountSpy.calledOnceWith(2));
- assert.isTrue(umaTypeSpy.calledTwice);
- assert.isTrue(umaTypeSpy.calledWith('Image'));
- assert.isTrue(umaTypeSpy.calledWith('JavaScript'));
- });
-
- it('updates tooltip to default when request type deselected', async () => {
- Root.Runtime.experiments.enableForTest(Root.Runtime.ExperimentName.NETWORK_PANEL_FILTER_BAR_REDESIGN);
-
- const dropdown = setupRequestTypesDropdown();
- const button = dropdown.element().querySelector('.toolbar-button');
- assert.instanceOf(button, HTMLElement);
-
- dispatchClickEvent(button, {bubbles: true, composed: true});
- await raf();
- await selectRequestTypesOption('Image');
-
- let tooltipText = button.title;
- assert.strictEqual(tooltipText, 'Show only Image');
-
- await selectRequestTypesOption('Image');
-
- tooltipText = button.title;
- assert.strictEqual(tooltipText, 'Filter requests by type');
-
- dropdown.discard();
- await raf();
- });
-
it('can filter requests with blocked response cookies from checkbox', async () => {
const request1 = createNetworkRequest('url1', {target});
request1.blockedResponseCookies = () => [{
@@ -1024,19 +875,6 @@
return checkbox;
}
-function getRequestTypeDropdownOption(requestType: string): Element|null {
- const dropDownVbox = document.querySelector('.vbox')?.shadowRoot?.querySelectorAll('.soft-context-menu-item') || [];
- const dropdownOptions = Array.from(dropDownVbox);
- return dropdownOptions.find(el => el.textContent?.includes(requestType)) || null;
-}
-
-async function selectRequestTypesOption(option: string) {
- const item = getRequestTypeDropdownOption(option);
- assert.instanceOf(item, HTMLElement);
- dispatchMouseUpEvent(item, {bubbles: true, composed: true});
- await raf();
-}
-
async function openMoreTypesDropdown(
filterBar: UI.FilterBar.FilterBar, networkLogView: Network.NetworkLogView.NetworkLogView):
Promise<Network.NetworkLogView.MoreFiltersDropDownUI|undefined> {
@@ -1048,19 +886,6 @@
return dropdown;
}
-function setupRequestTypesDropdown() {
- const filterItems = Object.entries(Common.ResourceType.resourceCategories).map(([key, category]) => ({
- name: category.title(),
- label: () => category.shortTitle(),
- title: category.title(),
- jslogContext: key,
- }));
-
- const setting = Common.Settings.Settings.instance().createSetting('network-resource-type-filters', {all: true});
- const dropdown = new Network.NetworkLogView.DropDownTypesUI(filterItems, setting);
- return dropdown;
-}
-
function getCountAdorner(filterBar: UI.FilterBar.FilterBar): HTMLElement|null {
const button = filterBar.element.querySelector('[aria-label="Show only/hide requests dropdown"]')
?.querySelector('.toolbar-button');
diff --git a/front_end/panels/network/NetworkLogView.ts b/front_end/panels/network/NetworkLogView.ts
index d40af1e..a1f5e41 100644
--- a/front_end/panels/network/NetworkLogView.ts
+++ b/front_end/panels/network/NetworkLogView.ts
@@ -82,10 +82,6 @@
*/
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',
@@ -106,26 +102,6 @@
*/
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 Dynamic label for the `Request types` dropdown in the Network panel
- * @example {Doc} PH1
- * @example {CSS} PH2
- */
- twoTypesSelected: '{PH1}, {PH2}',
- /**
- * @description: Dynamic label for the `Request types` dropdown in the Network panel
- * @example {Doc} PH1
- * @example {CSS} PH2
- */
- overTwoTypesSelected: '{PH1}, {PH2}...',
- /**
*@description Label for a checkbox in the Network panel. When checked, only requests with
* blocked response cookies are shown.
*/
@@ -459,11 +435,6 @@
* @description Text for the Show only/Hide requests dropdown button of the filterbar
*/
moreFilters: 'More filters',
- /**
- * @description Text for the Request types dropdown button tooltip
- * @example {Media, Images} PH1
- */
- showOnly: 'Show only {PH1}',
};
const str_ = i18n.i18n.registerUIStrings('panels/network/NetworkLogView.ts', UIStrings);
const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
@@ -514,7 +485,7 @@
private readonly onlyBlockedRequestsUI: UI.FilterBar.CheckboxFilterUI|undefined;
private readonly onlyThirdPartyFilterUI: UI.FilterBar.CheckboxFilterUI|undefined;
private readonly hideChromeExtensionsUI: UI.FilterBar.CheckboxFilterUI|undefined;
- private readonly resourceCategoryFilterUI: DropDownTypesUI|UI.FilterBar.NamedBitSetFilterUI;
+ private readonly resourceCategoryFilterUI: UI.FilterBar.NamedBitSetFilterUI;
private readonly filterParser: TextUtils.TextUtils.FilterParser;
private readonly suggestionBuilder: UI.FilterSuggestionBuilder.FilterSuggestionBuilder;
private dataGrid: DataGrid.SortableDataGrid.SortableDataGrid<NetworkNode>;
@@ -609,18 +580,16 @@
}));
if (Root.Runtime.experiments.isEnabled(Root.Runtime.ExperimentName.NETWORK_PANEL_FILTER_BAR_REDESIGN)) {
- this.resourceCategoryFilterUI = new DropDownTypesUI(filterItems, this.networkResourceTypeFiltersSetting);
- this.resourceCategoryFilterUI.addEventListener(
- UI.FilterBar.FilterUIEvents.FilterChanged, this.filterChanged, this);
+ this.moreFiltersDropDownUI = new MoreFiltersDropDownUI();
+ this.moreFiltersDropDownUI.addEventListener(UI.FilterBar.FilterUIEvents.FilterChanged, this.filterChanged, this);
+ filterBar.addFilter(this.moreFiltersDropDownUI);
+
+ this.resourceCategoryFilterUI =
+ new UI.FilterBar.NamedBitSetFilterUI(filterItems, this.networkResourceTypeFiltersSetting);
UI.ARIAUtils.setLabel(this.resourceCategoryFilterUI.element(), i18nString(UIStrings.requestTypesToInclude));
this.resourceCategoryFilterUI.addEventListener(
UI.FilterBar.FilterUIEvents.FilterChanged, this.filterChanged.bind(this), this);
filterBar.addFilter(this.resourceCategoryFilterUI);
- filterBar.addDivider();
-
- this.moreFiltersDropDownUI = new MoreFiltersDropDownUI();
- this.moreFiltersDropDownUI.addEventListener(UI.FilterBar.FilterUIEvents.FilterChanged, this.filterChanged, this);
- filterBar.addFilter(this.moreFiltersDropDownUI);
} else {
this.dataURLFilterUI = new UI.FilterBar.CheckboxFilterUI(
'hide-data-url', i18nString(UIStrings.hideDataUrls), true, this.networkHideDataURLSetting, 'hide-data-urls');
@@ -2644,217 +2613,6 @@
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.ToolbarCombobox;
- private displayedTypes: Set<string>;
- private readonly setting: Common.Settings.Setting<{[key: string]: boolean}>;
- private readonly items: UI.FilterBar.Item[];
- private contextMenu?: UI.ContextMenu.ContextMenu;
- private selectedTypesCount: HTMLElement;
- private typesCountAdorner: Adorners.Adorner.Adorner;
- private hasChanged = false;
-
- constructor(items: UI.FilterBar.Item[], setting: Common.Settings.Setting<{[key: string]: boolean}>) {
- super();
- this.items = items;
-
- this.filterElement = document.createElement('div');
- this.filterElement.setAttribute('jslog', `${VisualLogging.dropDown('request-types').track({click: true})}`);
-
- this.typesCountAdorner = new Adorners.Adorner.Adorner();
- this.selectedTypesCount = document.createElement('span');
- this.typesCountAdorner.data = {
- name: 'countWrapper',
- content: this.selectedTypesCount,
- };
- this.typesCountAdorner.classList.add('active-filters-count');
-
- this.dropDownButton = new UI.Toolbar.ToolbarCombobox(i18nString(UIStrings.requestTypesTooltip));
- this.dropDownButton.setAdorner(this.typesCountAdorner);
- this.dropDownButton.setText(i18nString(UIStrings.requestTypes));
- this.filterElement.appendChild(this.dropDownButton.element);
- 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();
- }
-
- emitUMA(): void {
- if (this.hasChanged) {
- Host.userMetrics.resourceTypeFilterNumberOfSelectedChanged(this.displayedTypes.size);
- for (const displayedType of this.displayedTypes) {
- Host.userMetrics.resourceTypeFilterItemSelected(displayedType);
- }
- }
- }
-
- showContextMenu(event: Common.EventTarget.EventTargetEvent<Event>): void {
- const mouseEvent = event.data;
- this.hasChanged = false;
- 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,
- onSoftMenuClosed: this.emitUMA.bind(this),
- });
-
- 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 {
- const jslogContext = name.toLowerCase().replace(/\s/g, '-');
- contextMenu.defaultSection().appendCheckboxItem(label, () => {
- this.setting.get()[name] = !this.setting.get()[name];
- this.toggleTypeFilter(name);
- }, {checked: this.setting.get()[name], jslogContext});
- }
-
- 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 filterChanged(): void {
- this.dispatchEventToListeners(UI.FilterBar.FilterUIEvents.FilterChanged);
- }
-
- private settingChanged(): void {
- this.hasChanged = true;
- 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);
- }
- this.updateSelectedTypesCount();
- this.updateLabel();
- this.updateTooltip();
- }
-
- updateSelectedTypesCount(): void {
- if (!this.displayedTypes.has(DropDownTypesUI.ALL_TYPES)) {
- this.selectedTypesCount.textContent = this.displayedTypes.size.toString();
- this.typesCountAdorner.classList.remove('hidden');
- } else {
- this.typesCountAdorner.classList.add('hidden');
- }
- }
-
- updateLabel(): void {
- if (this.displayedTypes.has(DropDownTypesUI.ALL_TYPES)) {
- this.dropDownButton.setText(i18nString(UIStrings.requestTypes));
- return;
- }
-
- let newLabel;
- if (this.displayedTypes.size === 1) {
- const type = this.displayedTypes.values().next().value;
- newLabel = Common.ResourceType.ResourceCategory.categoryByTitle(type)?.shortTitle() || '';
- } else {
- // show up to two last selected types
- const twoLastSelected = [...this.displayedTypes].slice(-2).reverse();
- const shortNames =
- twoLastSelected.map(type => Common.ResourceType.ResourceCategory.categoryByTitle(type)?.shortTitle() || '');
- const valuesToDisplay = {PH1: shortNames[0], PH2: shortNames[1]};
- newLabel = this.displayedTypes.size === 2 ? i18nString(UIStrings.twoTypesSelected, valuesToDisplay) :
- i18nString(UIStrings.overTwoTypesSelected, valuesToDisplay);
- }
- this.dropDownButton.setText(newLabel);
- }
-
- updateTooltip(): void {
- let tooltipText = i18nString(UIStrings.requestTypesTooltip);
- if (!this.displayedTypes.has(DropDownTypesUI.ALL_TYPES)) {
- // reverse the order to match the button label
- const selectedTypes = [...this.displayedTypes].reverse();
- const localized =
- selectedTypes.map(type => Common.ResourceType.ResourceCategory.categoryByTitle(type)?.title() || '')
- .join(', ');
- tooltipText = i18nString(UIStrings.showOnly, {PH1: localized});
- }
- this.dropDownButton.setTitle(tooltipText);
- }
-
- 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';
-}
-
export class MoreFiltersDropDownUI extends
Common.ObjectWrapper.ObjectWrapper<UI.FilterBar.FilterUIEventTypes> implements UI.FilterBar.FilterUI {
private readonly filterElement: HTMLDivElement;