Bug 1575180 - Support for case sensitive search. r=Honza
authorlloan <lloanalas@outlook.com>
Fri, 06 Sep 2019 08:26:06 +0000
changeset 491967 9f3522137a1f6e47d82ea1789b3ad1bf78ad9a09
parent 491966 85efdc31cf556ed9cda6a4d831177b11779b53ca
child 491968 bb8407a35b116e5b16dd4b806c16514cf2950c50
push id36542
push usernbeleuzu@mozilla.com
push dateFri, 06 Sep 2019 21:50:07 +0000
treeherdermozilla-central@4aed8e10318f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersHonza
bugs1575180
milestone71.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1575180 - Support for case sensitive search. r=Honza Differential Revision: https://phabricator.services.mozilla.com/D44085
devtools/client/jar.mn
devtools/client/locales/en-US/netmonitor.properties
devtools/client/netmonitor/src/actions/search.js
devtools/client/netmonitor/src/assets/styles/Toolbar.css
devtools/client/netmonitor/src/assets/styles/search.css
devtools/client/netmonitor/src/components/search/SearchPanel.js
devtools/client/netmonitor/src/components/search/Toolbar.js
devtools/client/netmonitor/src/constants.js
devtools/client/netmonitor/src/reducers/search.js
devtools/client/netmonitor/src/workers/search/search.js
devtools/client/themes/images/case-match.svg
--- a/devtools/client/jar.mn
+++ b/devtools/client/jar.mn
@@ -187,16 +187,17 @@ devtools.jar:
     skin/images/tool-profiler.svg (themes/images/tool-profiler.svg)
     skin/images/tool-network.svg (themes/images/tool-network.svg)
     skin/images/tool-scratchpad.svg (themes/images/tool-scratchpad.svg)
     skin/images/tool-memory.svg (themes/images/tool-memory.svg)
     skin/images/tool-dom.svg (themes/images/tool-dom.svg)
     skin/images/tool-accessibility.svg (themes/images/tool-accessibility.svg)
     skin/images/tool-application.svg (themes/images/tool-application.svg)
     skin/images/close.svg (themes/images/close.svg)
+    skin/images/case-match.svg (themes/images/case-match.svg)
     skin/images/clear.svg (themes/images/clear.svg)
     skin/images/close-3-pane.svg (themes/images/close-3-pane.svg)
     skin/images/open-3-pane.svg (themes/images/open-3-pane.svg)
     skin/images/vview-delete.svg (themes/images/vview-delete.svg)
     skin/images/vview-edit.svg (themes/images/vview-edit.svg)
     skin/images/vview-lock.png (themes/images/vview-lock.png)
     skin/images/vview-lock@2x.png (themes/images/vview-lock@2x.png)
     skin/images/sort-ascending-arrow.svg (themes/images/sort-ascending-arrow.svg)
--- a/devtools/client/locales/en-US/netmonitor.properties
+++ b/devtools/client/locales/en-US/netmonitor.properties
@@ -754,16 +754,20 @@ netmonitor.search.toolbar.inputPlaceholder=Find in resources…
 # LOCALIZATION NOTE (netmonitor.search.toolbar.close): This is the label
 # displayed in the search toolbar to close the search panel.
 netmonitor.search.toolbar.close=Close Search Panel
 
 # LOCALIZATION NOTE (netmonitor.search.toolbar.clear): This is the label
 # displayed in the search toolbar to clear the search panel.
 netmonitor.search.toolbar.clear=Clear Search Results
 
+# LOCALIZATION NOTE (netmonitor.search.toolbar.caseSensitive): This is the label
+# displayed in the search toolbar to do a case sensitive search.
+netmonitor.search.toolbar.caseSensitive=Case Sensitive
+
 # LOCALIZATION NOTE (netmonitor.search.labels.responseHeaders): This is the label
 # displayed in the search results as the label for the response headers
 netmonitor.search.labels.responseHeaders=Response Header
 
 # LOCALIZATION NOTE (netmonitor.search.labels.requestHeaders): This is the label
 # displayed in the search results as the label for the request headers
 netmonitor.search.labels.requestHeaders=Request Header
 # LOCALIZATION NOTE (messagesTruncated): This is the text displayed
--- a/devtools/client/netmonitor/src/actions/search.js
+++ b/devtools/client/netmonitor/src/actions/search.js
@@ -9,16 +9,17 @@ const {
   ADD_SEARCH_RESULT,
   CLEAR_SEARCH_RESULTS,
   ADD_ONGOING_SEARCH,
   OPEN_SEARCH,
   CLOSE_SEARCH,
   UPDATE_SEARCH_STATUS,
   SEARCH_STATUS,
   SET_TARGET_SEARCH_RESULT,
+  TOGGLE_SEARCH_CASE_SENSITIVE_SEARCH,
 } = require("../constants");
 
 const {
   getDisplayedRequests,
   getOngoingSearch,
   getSearchStatus,
   getRequestById,
 } = require("../selectors/index");
@@ -100,19 +101,25 @@ async function loadResource(connector, r
   return fetchNetworkUpdatePacket(connector.requestData, resource, updateTypes);
 }
 
 /**
  * Search through all data within the specified resource.
  */
 function searchResource(resource, query) {
   return async (dispatch, getState) => {
+    const state = getState();
+
+    const modifiers = {
+      caseSensitive: state.search.caseSensitive,
+    };
+
     // Run search in a worker and wait for the results. The return
     // value is an array with search occurrences.
-    const result = await searchInResource(resource, query);
+    const result = await searchInResource(resource, query, modifiers);
 
     if (!result.length) {
       return;
     }
 
     dispatch(addSearchResult(resource, result));
   };
 }
@@ -162,23 +169,37 @@ function updateSearchStatus(status) {
  */
 function closeSearch() {
   return (dispatch, getState) => {
     dispatch(stopOngoingSearch());
     dispatch({ type: CLOSE_SEARCH });
   };
 }
 
+/**
+ * Open the entire search panel
+ * @returns {Function}
+ */
 function openSearch() {
   return (dispatch, getState) => {
     dispatch({ type: OPEN_SEARCH });
   };
 }
 
 /**
+ * Toggles case sensitive search
+ * @returns {Function}
+ */
+function toggleCaseSensitiveSearch() {
+  return (dispatch, getState) => {
+    dispatch({ type: TOGGLE_SEARCH_CASE_SENSITIVE_SEARCH });
+  };
+}
+
+/**
  * Toggle visibility of search panel in network panel
  */
 function toggleSearchPanel() {
   return (dispatch, getState) => {
     const state = getState();
     state.search.panelOpen
       ? dispatch({ type: CLOSE_SEARCH })
       : dispatch({ type: OPEN_SEARCH });
@@ -244,9 +265,10 @@ module.exports = {
   search,
   closeSearch,
   openSearch,
   clearSearchResults,
   addSearchQuery,
   toggleSearchPanel,
   navigate,
   setTargetSearchResult,
+  toggleCaseSensitiveSearch,
 };
--- a/devtools/client/netmonitor/src/assets/styles/Toolbar.css
+++ b/devtools/client/netmonitor/src/assets/styles/Toolbar.css
@@ -28,20 +28,18 @@
   background-image: url("chrome://devtools/skin/images/pause.svg");
 }
 
 .devtools-button.devtools-play-icon::before {
   background-image: url("chrome://devtools/content/netmonitor/src/assets/icons/play.svg");
 }
 
 /* Search button */
-
 .devtools-button.devtools-search-icon::before {
   background-image: url("chrome://devtools/skin/images/search.svg");
-  background-size: 13px;
 }
 
 /* HAR button */
 
 #devtools-har-button {
   margin-inline-end: 10px;
   padding-left: 2px;
   padding-right: 10px;
--- a/devtools/client/netmonitor/src/assets/styles/search.css
+++ b/devtools/client/netmonitor/src/assets/styles/search.css
@@ -34,27 +34,79 @@
 
 .search-panel .treeTable .treeLabelCell {
   text-overflow: ellipsis;
   max-width: 0;
   overflow: hidden;
   white-space: nowrap;
 }
 
+/* Icon for close button */
 #devtools-network-search-close::before {
   background-image: url("chrome://devtools/skin/images/close.svg");
 }
 
+/* Case Sensitive button */
+#devtools-network-search-caseSensitive::before {
+  background-image: url("chrome://devtools/skin/images/case-match.svg");
+}
+
+
 #devtools-network-search-close > button {
   margin: 0 !important;
   border-radius: 0 !important;
   position: relative;
   min-width: 26px;
 }
 
+button.case-sensitive-btn {
+  padding: 2px;
+  margin: 0 3px;
+  border: none;
+  background: none;
+  width: 20px;
+  height: 20px;
+  border-radius: 2px;
+}
+
 /* Color for query matches */
 .search-panel .resultCell .query-match {
   background-color: var(--theme-selection-background);
   color: white;
   padding: 1px 4px;
   margin: 0 2px 0 2px;
   border-radius: 2px;
 }
+
+.search-modifiers {
+  display: flex;
+  align-items: center;
+}
+
+.search-modifiers * {
+  -moz-user-select: none;
+  user-select: none;
+}
+
+.pipe-divider {
+  flex: none;
+  align-self: stretch;
+  width: 1px;
+  vertical-align: middle;
+  margin: 4px;
+  background-color: var(--theme-splitter-color);
+}
+
+.search-type-name {
+  margin: 0 4px;
+  border: none;
+  background: transparent;
+  color: var(--theme-comment);
+}
+
+.search-modifiers button {
+  margin: 0 3px;
+  border: none;
+  background: none;
+  width: 25px;
+  height: 20px;
+  border-radius: 2px;
+}
--- a/devtools/client/netmonitor/src/components/search/SearchPanel.js
+++ b/devtools/client/netmonitor/src/components/search/SearchPanel.js
@@ -36,16 +36,17 @@ const SEARCH_RESULT_LEVEL = 1;
  */
 class SearchPanel extends Component {
   static get propTypes() {
     return {
       clearSearchResults: PropTypes.func.isRequired,
       openSearch: PropTypes.func.isRequired,
       closeSearch: PropTypes.func.isRequired,
       search: PropTypes.func.isRequired,
+      caseSensitive: PropTypes.bool,
       connector: PropTypes.object.isRequired,
       addSearchQuery: PropTypes.func.isRequired,
       query: PropTypes.string.isRequired,
       results: PropTypes.array,
       navigate: PropTypes.func.isRequired,
     };
   }
 
@@ -116,17 +117,17 @@ class SearchPanel extends Component {
 
   /**
    * Custom tree value rendering. This method is responsible for
    * rendering highlighted query string within the search result
    * result tree.
    */
   renderValue(props) {
     const { member } = props;
-    const { query } = this.props;
+    const { query, caseSensitive } = this.props;
 
     // Handle only second level (zero based) that displays
     // the search result. Find the query string inside the
     // search result value (`props.object`) and render it
     // within a span element with proper class name.
     // level 0 = resource name
     if (member.level === SEARCH_RESULT_LEVEL) {
       const { object } = member;
@@ -156,17 +157,19 @@ class SearchPanel extends Component {
           indexStart = match;
 
           return highlightedMatch;
         });
 
         return span({ title: object.value }, allMatches);
       }
 
-      const indexStart = object.value.indexOf(query);
+      const indexStart = caseSensitive
+        ? object.value.indexOf(query)
+        : object.value.toLowerCase().indexOf(query.toLowerCase());
       const indexEnd = indexStart + query.length;
 
       // Handles a match in a string
       if (indexStart > 0) {
         return span(
           { title: object.value },
           span({}, object.value.substring(0, indexStart)),
           span(
@@ -211,16 +214,17 @@ class SearchPanel extends Component {
       )
     );
   }
 }
 
 module.exports = connect(
   state => ({
     query: state.search.query,
+    caseSensitive: state.search.caseSensitive,
     results: state.search.results,
     ongoingSearch: state.search.ongoingSearch,
     status: state.search.status,
   }),
   dispatch => ({
     closeSearch: () => dispatch(Actions.closeSearch()),
     openSearch: () => dispatch(Actions.openSearch()),
     search: () => dispatch(Actions.search()),
--- a/devtools/client/netmonitor/src/components/search/Toolbar.js
+++ b/devtools/client/netmonitor/src/components/search/Toolbar.js
@@ -31,16 +31,18 @@ const SearchBox = createFactory(
 class Toolbar extends Component {
   static get propTypes() {
     return {
       searchboxRef: PropTypes.object.isRequired,
       clearSearchResults: PropTypes.func.isRequired,
       search: PropTypes.func.isRequired,
       closeSearch: PropTypes.func.isRequired,
       addSearchQuery: PropTypes.func.isRequired,
+      caseSensitive: PropTypes.bool.isRequired,
+      toggleCaseSensitiveSearch: PropTypes.func.isRequired,
       connector: PropTypes.object.isRequired,
     };
   }
 
   /**
    * Render a separator.
    */
   renderSeparator() {
@@ -71,32 +73,55 @@ class Toolbar extends Component {
     return button({
       id: "devtools-network-search-close",
       className: "devtools-button",
       title: L10N.getStr("netmonitor.search.toolbar.close"),
       onClick: () => closeSearch(),
     });
   }
 
+  renderModifiers() {
+    return div(
+      { className: "search-modifiers" },
+      span({ className: "pipe-divider" }),
+      this.renderCaseSensitiveButton()
+    );
+  }
+
   /**
    * Render a clear button to clear search results.
    */
   renderClearButton() {
     return button({
       className:
         "devtools-button devtools-clear-icon ws-frames-list-clear-button",
       title: L10N.getStr("netmonitor.search.toolbar.clear"),
       onClick: () => {
         this.props.clearSearchResults();
       },
     });
   }
 
   /**
-   * Render filter Search box.
+   * Render the case sensitive search modifier button
+   */
+  renderCaseSensitiveButton() {
+    const { caseSensitive, toggleCaseSensitiveSearch } = this.props;
+    const active = caseSensitive ? "checked" : "";
+
+    return button({
+      id: "devtools-network-search-caseSensitive",
+      className: `devtools-button ${active}`,
+      title: L10N.getStr("netmonitor.search.toolbar.caseSensitive"),
+      onClick: toggleCaseSensitiveSearch,
+    });
+  }
+
+  /**
+   * Render Search box.
    */
   renderFilterBox() {
     const { addSearchQuery, clearSearchResults, connector } = this.props;
     return SearchBox({
       keyShortcut: "CmdOrCtrl+Shift+F",
       placeholder: L10N.getStr("netmonitor.search.toolbar.inputPlaceholder"),
       type: "search",
       delay: FILTER_SEARCH_DELAY,
@@ -109,23 +134,28 @@ class Toolbar extends Component {
 
   render() {
     return div(
       {
         id: "netmonitor-toolbar-container",
         className: "devtools-toolbar devtools-input-toolbar",
       },
       this.renderFilterBox(),
+      this.renderModifiers(),
       this.renderCloseButton()
     );
   }
 }
 
 module.exports = connect(
-  state => ({}),
+  state => ({
+    caseSensitive: state.search.caseSensitive,
+  }),
   dispatch => ({
     closeSearch: () => dispatch(Actions.closeSearch()),
     openSearch: () => dispatch(Actions.openSearch()),
     clearSearchResults: () => dispatch(Actions.clearSearchResults()),
+    toggleCaseSensitiveSearch: () =>
+      dispatch(Actions.toggleCaseSensitiveSearch()),
     search: (connector, query) => dispatch(Actions.search(connector, query)),
     addSearchQuery: query => dispatch(Actions.addSearchQuery(query)),
   })
 )(Toolbar);
--- a/devtools/client/netmonitor/src/constants.js
+++ b/devtools/client/netmonitor/src/constants.js
@@ -45,16 +45,17 @@ const actionTypes = {
 
   // Search
   ADD_SEARCH_QUERY: "ADD_SEARCH_QUERY",
   ADD_SEARCH_RESULT: "ADD_SEARCH_RESULT",
   CLEAR_SEARCH_RESULTS: "CLEAR_SEARCH_RESULTS",
   ADD_ONGOING_SEARCH: "ADD_ONGOING_SEARCH",
   OPEN_SEARCH: "OPEN_SEARCH",
   CLOSE_SEARCH: "CLOSE_SEARCH",
+  TOGGLE_SEARCH_CASE_SENSITIVE_SEARCH: "TOGGLE_SEARCH_CASE_SENSITIVE_SEARCH",
   UPDATE_SEARCH_STATUS: "UPDATE_SEARCH_STATUS",
   SET_TARGET_SEARCH_RESULT: "SET_TARGET_SEARCH_RESULT",
 };
 
 // Search status types
 const SEARCH_STATUS = {
   INITIAL: "INITIAL",
   FETCHING: "FETCHING",
--- a/devtools/client/netmonitor/src/reducers/search.js
+++ b/devtools/client/netmonitor/src/reducers/search.js
@@ -7,16 +7,17 @@
 const {
   ADD_SEARCH_QUERY,
   ADD_SEARCH_RESULT,
   CLEAR_SEARCH_RESULTS,
   ADD_ONGOING_SEARCH,
   OPEN_SEARCH,
   CLOSE_SEARCH,
   SEARCH_STATUS,
+  TOGGLE_SEARCH_CASE_SENSITIVE_SEARCH,
   UPDATE_SEARCH_STATUS,
   SET_TARGET_SEARCH_RESULT,
 } = require("../constants");
 
 /**
  * Search reducer stores the following data:
  * - query [String]: the string the user is looking for
  * - results [Object]: the list of search results
@@ -25,16 +26,17 @@ const {
  */
 function Search(overrideParams = {}) {
   return Object.assign(
     {
       query: "",
       results: [],
       ongoingSearch: null,
       status: SEARCH_STATUS.INITIAL,
+      caseSensitive: false,
       panelOpen: false,
       targetSearchResult: null,
     },
     overrideParams
   );
 }
 
 function search(state = new Search(), action) {
@@ -46,16 +48,18 @@ function search(state = new Search(), ac
     case CLEAR_SEARCH_RESULTS:
       return onClearSearchResults(state);
     case ADD_ONGOING_SEARCH:
       return onAddOngoingSearch(state, action);
     case CLOSE_SEARCH:
       return onCloseSearch(state);
     case OPEN_SEARCH:
       return onOpenSearch(state);
+    case TOGGLE_SEARCH_CASE_SENSITIVE_SEARCH:
+      return onToggleCaseSensitiveSearch(state);
     case UPDATE_SEARCH_STATUS:
       return onUpdateSearchStatus(state, action);
     case SET_TARGET_SEARCH_RESULT:
       return onSetTargetSearchResult(state, action);
   }
   return state;
 }
 
@@ -103,16 +107,23 @@ function onCloseSearch(state) {
 
 function onOpenSearch(state) {
   return {
     ...state,
     panelOpen: true,
   };
 }
 
+function onToggleCaseSensitiveSearch(state) {
+  return {
+    ...state,
+    caseSensitive: !state.caseSensitive,
+  };
+}
+
 function onUpdateSearchStatus(state, action) {
   return {
     ...state,
     status: action.status,
   };
 }
 
 function onSetTargetSearchResult(state, action) {
--- a/devtools/client/netmonitor/src/workers/search/search.js
+++ b/devtools/client/netmonitor/src/workers/search/search.js
@@ -4,105 +4,105 @@
 /* eslint-disable no-unused-vars */
 
 "use strict";
 
 /**
  * Search within specified resource. Note that this function runs
  * within a worker thread.
  */
-function searchInResource(resource, query) {
+function searchInResource(resource, query, modifiers) {
   const results = [];
 
   if (resource.url) {
     results.push(
-      findMatches(resource, query, {
+      findMatches(resource, query, modifiers, {
         key: "url",
         label: "Url",
         type: "url",
         panel: "headers",
       })
     );
   }
 
   if (resource.responseHeaders) {
     results.push(
-      findMatches(resource, query, {
+      findMatches(resource, query, modifiers, {
         key: "responseHeaders.headers",
         type: "responseHeaders",
         panel: "headers",
       })
     );
   }
 
   if (resource.requestHeaders) {
     results.push(
-      findMatches(resource, query, {
+      findMatches(resource, query, modifiers, {
         key: "requestHeaders.headers",
         type: "requestHeaders",
         panel: "headers",
       })
     );
   }
 
   if (resource.requestHeadersFromUploadStream) {
     results.push(
-      findMatches(resource, query, {
+      findMatches(resource, query, modifiers, {
         key: "requestHeadersFromUploadStream.headers",
         type: "requestHeadersFromUploadStream",
         panel: "headers",
       })
     );
   }
 
   if (resource.responseCookies) {
     let key = "responseCookies";
 
     if (resource.responseCookies.cookies) {
       key = "responseCookies.cookies";
     }
 
     results.push(
-      findMatches(resource, query, {
+      findMatches(resource, query, modifiers, {
         key,
         type: "responseCookies",
         panel: "cookies",
       })
     );
   }
 
   if (resource.requestCookies) {
     let key = "requestCookies";
 
     if (resource.requestCookies.cookies) {
       key = "requestCookies.cookies";
     }
 
     results.push(
-      findMatches(resource, query, {
+      findMatches(resource, query, modifiers, {
         key,
         type: "requestCookies",
         panel: "cookies",
       })
     );
   }
 
   if (resource.responseContent) {
     results.push(
-      findMatches(resource, query, {
+      findMatches(resource, query, modifiers, {
         key: "responseContent.content.text",
         type: "responseContent",
         panel: "response",
       })
     );
   }
 
   if (resource.requestPostData) {
     results.push(
-      findMatches(resource, query, {
+      findMatches(resource, query, modifiers, {
         key: "requestPostData.postData.text",
         type: "requestPostData",
         panel: "params",
       })
     );
   }
 
   return getResults(results, resource);
@@ -120,59 +120,69 @@ function getResults(results, resource) {
   tempResults.forEach((result, index) => {
     result.key = index;
     result.parentResource = resource;
   });
 
   return tempResults;
 }
 
+function find(query, modifiers, source) {
+  const { caseSensitive } = modifiers;
+  const value = caseSensitive ? source : source.toLowerCase();
+  const q = caseSensitive ? query : query.toLowerCase();
+
+  return value.includes(q);
+}
+
 /**
  * Find query matches in arrays, objects and strings.
  * @param resource
  * @param query
+ * @param modifiers
  * @param data
- * @returns {*}
+ * @returns {*[]|[]|Array|*}
  */
-function findMatches(resource, query, data) {
-  if (!resource || !query || !data) {
+function findMatches(resource, query, modifiers, data) {
+  if (!resource || !query || !modifiers || !data) {
     return [];
   }
 
   const resourceValue = getValue(data.key, resource);
   const resourceType = getType(resourceValue);
 
   if (resource.hasOwnProperty("name") && resource.hasOwnProperty("value")) {
-    return searchInProperties(query, resource, data);
+    return searchInProperties(query, modifiers, resource, data);
   }
 
   switch (resourceType) {
     case "string":
-      return searchInText(query, resourceValue, data);
+      return searchInText(query, modifiers, resourceValue, data);
     case "array":
-      return searchInArray(query, resourceValue, data);
+      return searchInArray(query, modifiers, resourceValue, data);
     case "object":
-      return searchInObject(query, resourceValue, data);
+      return searchInObject(query, modifiers, resourceValue, data);
     default:
       return [];
   }
 }
 
-function searchInProperties(query, obj, data) {
+function searchInProperties(query, modifiers, obj, data) {
+  const { name, value } = obj;
   const match = {
     ...data,
   };
 
-  if (obj.name.includes(query)) {
-    match.label = obj.name;
+  if (find(query, modifiers, name)) {
+    match.label = name;
   }
 
-  if (obj.name.includes(query) || obj.value.includes(query)) {
-    match.value = obj.value;
-    match.startIndex = obj.value.indexOf(query);
+  if (find(query, modifiers, name) || find(query, modifiers, value)) {
+    match.value = value;
+    match.startIndex = value.indexOf(query);
 
     return match;
   }
 
   return [];
 }
 
 /**
@@ -193,28 +203,34 @@ function getType(resource) {
 function getValue(path, obj) {
   const properties = Array.isArray(path) ? path : path.split(".");
   return properties.reduce((prev, curr) => prev && prev[curr], obj);
 }
 
 /**
  * Search text for specific string and return all matches found
  * @param query
+ * @param modifiers
  * @param text
  * @param data
- * @returns {Array}
+ * @returns {*}
  */
-function searchInText(query, text, data) {
+function searchInText(query, modifiers, text, data) {
   const { type } = data;
   const lines = text.split(/\r\n|\r|\n/);
   const matches = [];
 
   // iterate through each line
   lines.forEach((curr, i) => {
-    const regexQuery = RegExp(query, "gmi");
+    const { caseSensitive } = modifiers;
+    const flags = caseSensitive ? "g" : "gi";
+    const regexQuery = RegExp(
+      caseSensitive ? query : query.toLowerCase(),
+      flags
+    );
     const lineMatches = [];
     let singleMatch;
 
     while ((singleMatch = regexQuery.exec(lines[i])) !== null) {
       const startIndex = regexQuery.lastIndex;
       lineMatches.push(startIndex);
     }
 
@@ -238,31 +254,30 @@ function searchInText(query, text, data)
 
   return matches.length === 0 ? [] : matches;
 }
 
 /**
  * Search for query in array.
  * Iterates through each array item and handles item based on type.
  * @param query
+ * @param modifiers
  * @param arr
  * @param data
- * @returns {*}
+ * @returns {*[]}
  */
-function searchInArray(query, arr, data) {
+function searchInArray(query, modifiers, arr, data) {
   const { key, label } = data;
-  const matches = arr
-    .filter(match => JSON.stringify(match).includes(query))
-    .map((match, i) =>
-      findMatches(match, query, {
-        ...data,
-        label: match.hasOwnProperty("name") ? match.name : label,
-        key: key + ".[" + i + "]",
-      })
-    );
+  const matches = arr.map((match, i) =>
+    findMatches(match, query, modifiers, {
+      ...data,
+      label: match.hasOwnProperty("name") ? match.name : label,
+      key: key + ".[" + i + "]",
+    })
+  );
 
   return getResults(matches);
 }
 
 /**
  * Return query match and up to 50 characters on left and right.
  * (50) + [matched query] + (50)
  * @param value
@@ -281,47 +296,54 @@ function getTruncatedValue(value, query,
   const start = value.substring(startIndex, startIndex - 50);
   const end = value.substring(indexEnd, indexEnd + 50);
 
   return start + end;
 }
 
 /**
  * Iterates through object, including nested objects, returns all
- * found matches.
  * @param query
+ * @param modifiers
  * @param obj
  * @param data
- * @returns {*}
+ * @returns {*|[]}
  */
-function searchInObject(query, obj, data) {
+function searchInObject(query, modifiers, obj, data) {
   const matches = data.hasOwnProperty("collector") ? data.collector : [];
+  const { caseSensitive } = modifiers;
 
   for (const objectKey in obj) {
     const objectKeyType = getType(obj[objectKey]);
 
     // if the value is an object, send to search in object
     if (objectKeyType === "object" && Object.keys(obj[objectKey].length > 1)) {
       searchInObject(query, obj[objectKey], {
         ...data,
         collector: matches,
       });
-    } else if (
-      (objectKeyType === "string" && obj[objectKey].includes(query)) ||
-      objectKey.includes(query)
-    ) {
+
+      continue;
+    }
+
+    const value = !caseSensitive
+      ? obj[objectKey].toLowerCase()
+      : obj[objectKey];
+    const key = !caseSensitive ? objectKey.toLowerCase() : objectKey;
+    const q = !caseSensitive ? query.toLowerCase() : query;
+
+    if ((objectKeyType === "string" && value.includes(q)) || key.includes(q)) {
       const match = {
         ...data,
       };
 
-      const value = obj[objectKey];
-      const startIndex = value.indexOf(query);
+      const startIndex = value.indexOf(q);
 
       match.label = objectKey;
       match.startIndex = startIndex;
-      match.value = getTruncatedValue(value, query, startIndex);
+      match.value = getTruncatedValue(obj[objectKey], query, startIndex);
 
       matches.push(match);
     }
   }
 
   return matches;
 }
new file mode 100644
--- /dev/null
+++ b/devtools/client/themes/images/case-match.svg
@@ -0,0 +1,6 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+   - License, v. 2.0. If a copy of the MPL was not distributed with this
+   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 20 16" fill="context-fill #0c0c0d">
+  <path d="M10.919,13 L9.463,13 C9.29966585,13 9.16550052,12.9591671 9.0605,12.8775 C8.95549947,12.7958329 8.8796669,12.6943339 8.833,12.573 L8.077,10.508 L3.884,10.508 L3.128,12.573 C3.09066648,12.6803339 3.01716722,12.7783329 2.9075,12.867 C2.79783279,12.9556671 2.66366746,13 2.505,13 L1.042,13 L5.018,2.878 L6.943,2.878 L10.919,13 Z M4.367,9.178 L7.594,9.178 L6.362,5.811 C6.30599972,5.66166592 6.24416701,5.48550102 6.1765,5.2825 C6.108833,5.07949898 6.04233366,4.85900119 5.977,4.621 C5.91166634,4.85900119 5.84750032,5.08066564 5.7845,5.286 C5.72149969,5.49133436 5.65966697,5.67099923 5.599,5.825 L4.367,9.178 Z M18.892,13 L18.115,13 C17.9516658,13 17.8233338,12.9755002 17.73,12.9265 C17.6366662,12.8774998 17.5666669,12.7783341 17.52,12.629 L17.366,12.118 C17.1839991,12.2813341 17.0055009,12.4248327 16.8305,12.5485 C16.6554991,12.6721673 16.4746676,12.7759996 16.288,12.86 C16.1013324,12.9440004 15.903001,13.0069998 15.693,13.049 C15.4829989,13.0910002 15.2496679,13.112 14.993,13.112 C14.6896651,13.112 14.4096679,13.0711671 14.153,12.9895 C13.896332,12.9078329 13.6758342,12.7853342 13.4915,12.622 C13.3071657,12.4586658 13.1636672,12.2556679 13.061,12.013 C12.9583328,11.7703321 12.907,11.4880016 12.907,11.166 C12.907,10.895332 12.9781659,10.628168 13.1205,10.3645 C13.262834,10.100832 13.499665,9.8628344 13.831,9.6505 C14.162335,9.43816561 14.6033306,9.2620007 15.154,9.122 C15.7046694,8.9819993 16.3883292,8.90266676 17.205,8.884 L17.205,8.464 C17.205,7.98333093 17.103501,7.62750116 16.9005,7.3965 C16.697499,7.16549885 16.4023352,7.05 16.015,7.05 C15.7349986,7.05 15.5016676,7.08266634 15.315,7.148 C15.1283324,7.21333366 14.9661673,7.28683292 14.8285,7.3685 C14.6908326,7.45016707 14.5636672,7.52366634 14.447,7.589 C14.3303327,7.65433366 14.2020007,7.687 14.062,7.687 C13.9453327,7.687 13.8450004,7.65666697 13.761,7.596 C13.6769996,7.53533303 13.6093336,7.46066711 13.558,7.372 L13.243,6.819 C14.0690041,6.06299622 15.0653275,5.685 16.232,5.685 C16.6520021,5.685 17.0264983,5.75383264 17.3555,5.8915 C17.6845016,6.02916736 17.9633322,6.22049877 18.192,6.4655 C18.4206678,6.71050122 18.5944994,7.00333163 18.7135,7.344 C18.8325006,7.68466837 18.892,8.05799797 18.892,8.464 L18.892,13 Z M15.532,11.922 C15.7093342,11.922 15.8726659,11.9056668 16.022,11.873 C16.1713341,11.8403332 16.3124993,11.7913337 16.4455,11.726 C16.5785006,11.6606663 16.7068327,11.5801671 16.8305,11.4845 C16.9541673,11.3888329 17.0789993,11.2756673 17.205,11.145 L17.205,9.934 C16.7009975,9.95733345 16.279835,10.0004997 15.9415,10.0635 C15.603165,10.1265003 15.3313343,10.2069995 15.126,10.305 C14.9206656,10.4030005 14.7748337,10.5173327 14.6885,10.648 C14.6021662,10.7786673 14.559,10.9209992 14.559,11.075 C14.559,11.3783349 14.6488324,11.5953327 14.8285,11.726 C15.0081675,11.8566673 15.2426652,11.922 15.532,11.922 L15.532,11.922 Z"/>
+</svg>
\ No newline at end of file