Add an 'invert' checkbox to invert all network filters

This functionality adds on top of the existing query mechanism, rather
than modify it.

This is because a query string can have multiple filters, some of them
regular expressions, some not. Each one of those filters could already
be negated with a "-" prefix, including the regular expressions. The
newly added top-level `invert` checkbox therefore inverts each one of
those individual filters, and properly handle the situation where a
query has multiple filters each with its own optional negation.

The drive-by changes to TextUtils.ts are primarily cosmetic, modernising
the JS slightly to help readability.

Change-Id: I3d429a98074eb3517ec891a848ea75c6c96d1ea5
Signed-off-by: Victor Porof <[email protected]>
Bug: 1054464
Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/3034342
Reviewed-by: Mathias Bynens <[email protected]>
diff --git a/front_end/panels/network/NetworkLogView.ts b/front_end/panels/network/NetworkLogView.ts
index 0aa3106..411a050 100644
--- a/front_end/panels/network/NetworkLogView.ts
+++ b/front_end/panels/network/NetworkLogView.ts
@@ -63,6 +63,14 @@
   /**
   *@description Text in Network Log View of the Network panel
   */
+  invertFilter: 'Invert',
+  /**
+  *@description Tooltip for the 'invert' checkbox in the Network panel.
+  */
+  invertsFilter: 'Inverts the search filter',
+  /**
+  *@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
@@ -339,6 +347,7 @@
 const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
 export class NetworkLogView extends UI.Widget.VBox implements
     SDK.TargetManager.SDKModelObserver<SDK.NetworkManager.NetworkManager>, NetworkLogViewInterface {
+  _networkInvertFilterSetting: Common.Settings.Setting<boolean>;
   _networkHideDataURLSetting: Common.Settings.Setting<boolean>;
   _networkShowIssuesOnlySetting: Common.Settings.Setting<boolean>;
   _networkOnlyBlockedRequestsSetting: Common.Settings.Setting<boolean>;
@@ -368,6 +377,7 @@
   _groupLookups: Map<string, GroupLookupInterface>;
   _activeGroupLookup: GroupLookupInterface|null;
   _textFilterUI: UI.FilterBar.TextFilterUI;
+  _invertFilterUI: UI.FilterBar.CheckboxFilterUI;
   _dataURLFilterUI: UI.FilterBar.CheckboxFilterUI;
   _resourceCategoryFilterUI: UI.FilterBar.NamedBitSetFilterUI;
   _onlyIssuesFilterUI: UI.FilterBar.CheckboxFilterUI;
@@ -390,6 +400,7 @@
     this.element.id = 'network-container';
     this.element.classList.add('no-node-selected');
 
+    this._networkInvertFilterSetting = Common.Settings.Settings.instance().createSetting('networkInvertFilter', false);
     this._networkHideDataURLSetting = Common.Settings.Settings.instance().createSetting('networkHideDataURL', false);
     this._networkShowIssuesOnlySetting =
         Common.Settings.Settings.instance().createSetting('networkShowIssuesOnly', false);
@@ -448,6 +459,13 @@
     this._textFilterUI.addEventListener(UI.FilterBar.FilterUI.Events.FilterChanged, this._filterChanged, this);
     filterBar.addFilter(this._textFilterUI);
 
+    this._invertFilterUI = new UI.FilterBar.CheckboxFilterUI(
+        'invert-filter', i18nString(UIStrings.invertFilter), true, this._networkInvertFilterSetting);
+    this._invertFilterUI.addEventListener(
+        UI.FilterBar.FilterUI.Events.FilterChanged, this._filterChanged.bind(this), this);
+    UI.Tooltip.Tooltip.install(this._invertFilterUI.element(), i18nString(UIStrings.invertsFilter));
+    filterBar.addFilter(this._invertFilterUI);
+
     this._dataURLFilterUI = new UI.FilterBar.CheckboxFilterUI(
         'hide-data-url', i18nString(UIStrings.hideDataUrls), true, this._networkHideDataURLSetting);
     this._dataURLFilterUI.addEventListener(
@@ -841,7 +859,7 @@
 
   _filterChanged(_event: Common.EventTarget.EventTargetEvent): void {
     this.removeAllNodeHighlights();
-    this._parseFilterQuery(this._textFilterUI.value());
+    this._parseFilterQuery(this._textFilterUI.value(), this._invertFilterUI.checked());
     this._filterRequests();
     this._textFilterSetting.set(this._textFilterUI.value());
   }
@@ -1678,7 +1696,11 @@
     return true;
   }
 
-  _parseFilterQuery(query: string): void {
+  _parseFilterQuery(query: string, invert: boolean): void {
+    // A query string can have multiple filters, some of them regular
+    // expressions, some not. Each one of those filters can be negated with a
+    // "-" prefix, including the regular expressions. The top-level `invert`
+    // checkbox therefore inverts each one of those individual filters.
     const descriptors = this._filterParser.parse(query);
     this._filters = descriptors.map(descriptor => {
       const key = descriptor.key;
@@ -1695,7 +1717,10 @@
         filter = NetworkLogView._requestPathFilter.bind(
             null, new RegExp(Platform.StringUtilities.escapeForRegExp(text), 'i'));
       }
-      return descriptor.negative ? NetworkLogView._negativeFilter.bind(null, filter) : filter;
+      if ((descriptor.negative && !invert) || (!descriptor.negative && invert)) {
+        return NetworkLogView._negativeFilter.bind(null, filter);
+      }
+      return filter;
     });
   }