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;
diff --git a/test/e2e/network/network-filter_test.ts b/test/e2e/network/network-filter_test.ts
index 799e800..104b63c 100644
--- a/test/e2e/network/network-filter_test.ts
+++ b/test/e2e/network/network-filter_test.ts
@@ -2,23 +2,32 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-import {expect} from 'chai';
+import {assert, expect} from 'chai';
+import type * as puppeteer from 'puppeteer-core';
 import {type ElementHandle} from 'puppeteer-core';
 
 import {
+  $textContent,
   click,
-  waitFor,
+  clickElement,
+  enableExperiment,
+  getTestServerPort,
   reloadDevTools,
+  step,
   typeText,
+  waitFor,
   waitForAria,
   waitForMany,
   waitForNone,
-  getTestServerPort,
-  clickElement,
 } from '../../shared/helper.js';
-
 import {describe, it} from '../../shared/mocha-extensions.js';
-import {navigateToNetworkTab, setPersistLog} from '../helpers/network-helpers.js';
+import {
+  getAllRequestNames,
+  navigateToNetworkTab,
+  setCacheDisabled,
+  setPersistLog,
+  waitForSomeRequestsToAppear,
+} from '../helpers/network-helpers.js';
 
 const SIMPLE_PAGE_REQUEST_NUMBER = 10;
 const SIMPLE_PAGE_URL = `requests.html?num=${SIMPLE_PAGE_REQUEST_NUMBER}`;
@@ -44,6 +53,30 @@
   }
 }
 
+async function openRequestTypeDropdown() {
+  const filterDropdown = await waitFor('[aria-label="Request types to include"]');
+  const filterButton = await waitFor('.toolbar-button', filterDropdown);
+  await filterButton.click();
+  return filterButton;
+}
+
+async function getCategoryTypeFilter(label: string) {
+  const categoryTypeFilter = await $textContent(label);
+
+  if (!categoryTypeFilter) {
+    assert.fail(`Could not find the ${label} category filter. Make sure the "Request types" dropdown is open.`);
+  }
+  return categoryTypeFilter;
+}
+
+async function checkOpacityCheckmark(categoryTypeFilter: puppeteer.ElementHandle, opacity: string) {
+  const checkmarkOpacity = await categoryTypeFilter.$eval('.checkmark', element => {
+    return window.getComputedStyle(element).getPropertyValue('opacity');
+  });
+
+  return checkmarkOpacity === opacity;
+}
+
 describe('The Network Tab', async function() {
   // One of these tests reloads panels repeatedly, which can take a longer time.
   this.timeout(20_000);
@@ -256,3 +289,80 @@
     }
   });
 });
+
+describe('The Network Panel filter bar dropdowns', async function() {
+  this.timeout(5000);
+
+  beforeEach(async () => {
+    await enableExperiment('networkPanelFilterBarRedesign');
+    await navigateToNetworkTab('empty.html');
+    await setCacheDisabled(true);
+    await setPersistLog(false);
+  });
+
+  it('persists filters across a reload', async () => {
+    await navigateToNetworkTab(SIMPLE_PAGE_URL);
+    let filterInput = await waitFor('.filter-input-field.text-prompt');
+    filterInput.focus();
+    await typeText('foo');
+
+    await openRequestTypeDropdown();
+
+    let categoryXHRFilter = await getCategoryTypeFilter('XHR and Fetch');
+    assert.isTrue(await checkOpacityCheckmark(categoryXHRFilter, '0'));
+
+    await categoryXHRFilter.click();
+
+    await reloadDevTools({selectedPanel: {name: 'network'}});
+    filterInput = await waitFor('.filter-input-field.text-prompt');
+    const filterText = await filterInput.evaluate(x => (x as HTMLElement).innerText);
+    assert.strictEqual(filterText, 'foo');
+
+    await openRequestTypeDropdown();
+
+    categoryXHRFilter = await getCategoryTypeFilter('XHR and Fetch');
+
+    assert.isTrue(await checkOpacityCheckmark(categoryXHRFilter, '1'));
+  });
+
+  it('unchecks all filters and the all option is checked automatically - by checkmark opacity', async () => {
+    await navigateToNetworkTab(SIMPLE_PAGE_URL);
+    await waitForSomeRequestsToAppear(SIMPLE_PAGE_REQUEST_NUMBER);
+
+    await openRequestTypeDropdown();
+
+    const categoryXHRFilter = await getCategoryTypeFilter('XHR and Fetch');
+    const categoryAllFilter = await getCategoryTypeFilter('All');
+
+    let names = await getAllRequestNames();
+
+    await step('verify the initial state when the "All" filter is selected', async () => {
+      assert.isTrue(await checkOpacityCheckmark(categoryXHRFilter, '0'));
+
+      assert.deepEqual(11, names.length);
+      assert.isTrue(names.includes('requests.html?num=10'));
+    });
+
+    await step('verify the dropdown state and the requests when XHR filter is selected', async () => {
+      await categoryXHRFilter.click();
+
+      assert.isTrue(await checkOpacityCheckmark(categoryXHRFilter, '1'));
+      assert.isTrue(await checkOpacityCheckmark(categoryAllFilter, '0'));
+
+      names = await getAllRequestNames();
+      assert.deepEqual(10, names.length);
+      assert.isFalse(names.includes('requests.html?num=10'));
+    });
+
+    await step('verify the dropdown state and the requests when XHR filter is deselected', async () => {
+      await categoryXHRFilter.click();
+
+      assert.isTrue(await checkOpacityCheckmark(categoryXHRFilter, '0'));
+      assert.isTrue(await checkOpacityCheckmark(categoryAllFilter, '1'));
+
+      names = await getAllRequestNames();
+      assert.deepEqual(11, names.length);
+      assert.isTrue(names.includes('requests.html?num=10'));
+    });
+  });
+});
diff --git a/test/e2e/network/network_test.ts b/test/e2e/network/network_test.ts
index 9bd7762..5f05ffa 100644
--- a/test/e2e/network/network_test.ts
+++ b/test/e2e/network/network_test.ts
@@ -4,7 +4,12 @@
 
 import {assert} from 'chai';
 
-import {$textContent, goTo, reloadDevTools, typeText, waitFor, waitForFunction} from '../../shared/helper.js';
+import {
+  $textContent,
+  goTo,
+  waitFor,
+  waitForFunction,
+} from '../../shared/helper.js';
 import {describe, it} from '../../shared/mocha-extensions.js';
 import {
   clearTimeWindow,
@@ -23,15 +28,6 @@
 const SIMPLE_PAGE_REQUEST_NUMBER = 10;
 const SIMPLE_PAGE_URL = `requests.html?num=${SIMPLE_PAGE_REQUEST_NUMBER}`;
 
-async function getCategoryXHRFilter() {
-  const filters = await waitFor('.filter-bitset-filter');
-  const categoryXHRFilter = await $textContent('Fetch/XHR', filters);
-  if (!categoryXHRFilter) {
-    assert.fail('Could not find category XHR filter to click.');
-  }
-  return categoryXHRFilter;
-}
-
 async function getThirdPartyFilter() {
   const filters = await waitFor('.filter-bar');
   const thirdPartyFilter = await $textContent('3rd-party requests', filters);
@@ -109,24 +105,6 @@
     assert.deepStrictEqual(secondPageRequestNames, firstPageRequestNames, 'The requests were persisted');
   });
 
-  it('persists filters across a reload', async () => {
-    await navigateToNetworkTab(SIMPLE_PAGE_URL);
-    let filterInput = await waitFor('.filter-input-field.text-prompt');
-    filterInput.focus();
-    await typeText('foo');
-    let categoryXHRFilter = await getCategoryXHRFilter();
-    await categoryXHRFilter.click();
-
-    await reloadDevTools({selectedPanel: {name: 'network'}});
-    filterInput = await waitFor('.filter-input-field.text-prompt');
-    const filterText = await filterInput.evaluate(x => (x as HTMLElement).innerText);
-    assert.strictEqual(filterText, 'foo');
-
-    categoryXHRFilter = await getCategoryXHRFilter();
-    const xhrHasSelectedClass = await categoryXHRFilter.evaluate(x => x.classList.contains('selected'));
-    assert.isTrue(xhrHasSelectedClass);
-  });
-
   it('can show only third-party requests', async () => {
     await navigateToNetworkTab('third-party-resources.html');
     await waitForSomeRequestsToAppear(3);
diff --git a/test/unittests/front_end/helpers/EnvironmentHelpers.ts b/test/unittests/front_end/helpers/EnvironmentHelpers.ts
index d7050e7..3eb2a76 100644
--- a/test/unittests/front_end/helpers/EnvironmentHelpers.ts
+++ b/test/unittests/front_end/helpers/EnvironmentHelpers.ts
@@ -126,6 +126,7 @@
   'evaluateExpressionsWithSourceMaps',
   'useSourceMapScopes',
   'fontEditor',
+  'networkPanelFilterBarRedesign',
 ];
 
 export async function initializeGlobalVars({reset = true} = {}) {
diff --git a/test/unittests/front_end/panels/network/NetworkLogView_test.ts b/test/unittests/front_end/panels/network/NetworkLogView_test.ts
index 12b8863..a475422 100644
--- a/test/unittests/front_end/panels/network/NetworkLogView_test.ts
+++ b/test/unittests/front_end/panels/network/NetworkLogView_test.ts
@@ -3,19 +3,18 @@
 // found in the LICENSE file.
 
 import * as Common from '../../../../../front_end/core/common/common.js';
-import * as Protocol from '../../../../../front_end/generated/protocol.js';
-import * as Network from '../../../../../front_end/panels/network/network.js';
-
 import type * as Platform from '../../../../../front_end/core/platform/platform.js';
-import * as SDK from '../../../../../front_end/core/sdk/sdk.js';
-import * as UI from '../../../../../front_end/ui/legacy/legacy.js';
-import * as Workspace from '../../../../../front_end/models/workspace/workspace.js';
-import * as Logs from '../../../../../front_end/models/logs/logs.js';
-import * as HAR from '../../../../../front_end/models/har/har.js';
-import * as Coordinator from '../../../../../front_end/ui/components/render_coordinator/render_coordinator.js';
-import {assertElement} from '../../helpers/DOMHelpers.js';
-
 import {assertNotNullOrUndefined} from '../../../../../front_end/core/platform/platform.js';
+import * as Root from '../../../../../front_end/core/root/root.js';
+import * as SDK from '../../../../../front_end/core/sdk/sdk.js';
+import * as Protocol from '../../../../../front_end/generated/protocol.js';
+import * as HAR from '../../../../../front_end/models/har/har.js';
+import * as Logs from '../../../../../front_end/models/logs/logs.js';
+import * as Workspace from '../../../../../front_end/models/workspace/workspace.js';
+import * as Network from '../../../../../front_end/panels/network/network.js';
+import * as Coordinator from '../../../../../front_end/ui/components/render_coordinator/render_coordinator.js';
+import * as UI from '../../../../../front_end/ui/legacy/legacy.js';
+import {assertElement, dispatchClickEvent, dispatchMouseUpEvent, raf} from '../../helpers/DOMHelpers.js';
 import {createTarget} from '../../helpers/EnvironmentHelpers.js';
 import {describeWithMockConnection, dispatchEvent} from '../../helpers/MockConnection.js';
 
@@ -295,6 +294,77 @@
       networkLogView.detach();
     });
 
+    function getOptionFromDropdown(option: string, dropdownArray: Element[]) {
+      return dropdownArray.find(el => {
+        return el.textContent?.includes(option);
+      }) ||
+          null;
+    }
+
+    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 filterItems =
+             Object.values(Common.ResourceType.resourceCategories).map(category => ({
+                                                                         name: category.title(),
+                                                                         label: (): string => category.shortTitle(),
+                                                                         title: category.title(),
+                                                                       }));
+
+         const setting = Common.Settings.Settings.instance().createSetting('networkResourceTypeFilters', {});
+         const emptyFunction = () => {};
+
+         const updatedSetting = ({} as {[key: string]: boolean});
+         updatedSetting['all'] = true;
+         setting.set(updatedSetting);
+
+         const dropdown = new Network.NetworkLogView.DropDownTypesUI(filterItems, emptyFunction, setting);
+         const button = dropdown.element().querySelector('.toolbar-button');
+
+         assertElement(button, HTMLElement);
+         dispatchClickEvent(button, {bubbles: true, composed: true});
+         await raf();
+
+         const dropDownVbox =
+             document.querySelector('.vbox')?.shadowRoot?.querySelectorAll('.soft-context-menu-item') || [];
+         const dropdownOptions = Array.from(dropDownVbox);
+
+         const optionImg = getOptionFromDropdown('Images', dropdownOptions);
+         const optionImgCheckmark = optionImg?.querySelector('.checkmark') || null;
+         const optionAll = getOptionFromDropdown('All', dropdownOptions);
+         const optionAllCheckmark = optionAll?.querySelector('.checkmark') || null;
+
+         assertElement(optionImg, HTMLElement);
+         assertElement(optionImgCheckmark, HTMLElement);
+         assertElement(optionAll, HTMLElement);
+         assertElement(optionAllCheckmark, HTMLElement);
+
+         assert.isTrue(optionAll.ariaLabel === 'All, checked');
+         assert.isTrue(optionImg.ariaLabel === 'Images, unchecked');
+         assert.isTrue(window.getComputedStyle(optionAllCheckmark).getPropertyValue('opacity') === '1');
+         assert.isTrue(window.getComputedStyle(optionImgCheckmark).getPropertyValue('opacity') === '0');
+
+         dispatchMouseUpEvent(optionImg, {bubbles: true, composed: true});
+         await raf();
+
+         assert.isTrue(optionAll.ariaLabel === 'All, unchecked');
+         assert.isTrue(optionImg.ariaLabel === 'Images, checked');
+         assert.isTrue(window.getComputedStyle(optionAllCheckmark).getPropertyValue('opacity') === '0');
+         assert.isTrue(window.getComputedStyle(optionImgCheckmark).getPropertyValue('opacity') === '1');
+
+         dispatchMouseUpEvent(optionImg, {bubbles: true, composed: true});
+         await raf();
+
+         assert.isTrue(optionAll.ariaLabel === 'All, checked');
+         assert.isTrue(optionImg.ariaLabel === 'Images, unchecked');
+         assert.isTrue(window.getComputedStyle(optionAllCheckmark).getPropertyValue('opacity') === '1');
+         assert.isTrue(window.getComputedStyle(optionImgCheckmark).getPropertyValue('opacity') === '0');
+
+         dropdown.discard();
+         await raf();
+       });
+
     it('can filter requests with blocked response cookies', async () => {
       const request1 = createNetworkRequest('url1', {target});
       request1.blockedResponseCookies = () => [{