Web Inspector: provide a way to make searches case sensitive or use a regular expression
authordrousso@apple.com <drousso@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 25 Feb 2019 04:27:46 +0000 (04:27 +0000)
committerdrousso@apple.com <drousso@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 25 Feb 2019 04:27:46 +0000 (04:27 +0000)
https://bugs.webkit.org/show_bug.cgi?id=192527
Source/WebInspectorUI:

Reviewed by Joseph Pecoraro.

* UserInterface/Base/SearchUtilities.js: Added.
(WI.SearchUtilities.get defaultSettings):
(WI.SearchUtilities.createSettings):
(WI.SearchUtilities.regExpForString):
(WI.SearchUtilities.createSettingsButton):
(WI.SearchUtilities.createSettingsButton.toggleActive):
* UserInterface/Views/Main.css:
(.search-settings): Added.
(.search-settings > .glyph): Added.
(.search-settings:active > .glyph): Added.
(.search-settings.active > .glyph): Added.
(.search-settings:active.active > .glyph): Added.
Create static utility class for handling settings related to searching/filtering.

* UserInterface/Base/Setting.js:
* UserInterface/Views/SettingsTabContentView.js:
(WI.SettingsTabContentView.prototype._createGeneralSettingsView):
Create default search settings that apply across WebInspector, unless a more specific
setting has been created that overrides it (e.g. the navigation sidebar or Search tab).

* UserInterface/Views/SearchSidebarPanel.js:
(WI.SearchSidebarPanel):
(WI.SearchSidebarPanel.prototype.performSearch.forEachMatch):
(WI.SearchSidebarPanel.prototype.performSearch.resourceCallback):
(WI.SearchSidebarPanel.prototype.performSearch.resourcesCallback):
(WI.SearchSidebarPanel.prototype.performSearch.searchScripts.scriptCallback):
(WI.SearchSidebarPanel.prototype.performSearch.searchScripts):
(WI.SearchSidebarPanel.prototype.performSearch.domSearchResults):
(WI.SearchSidebarPanel.prototype.performSearch.domCallback):
(WI.SearchSidebarPanel.prototype.performSearch):
* UserInterface/Views/SearchSidebarPanel.css:
(.sidebar > .panel.navigation.search > .search-bar):
(.sidebar > .panel.navigation.search > .search-bar > input[type="search"]):
Add a (*) settings "gear" after each `<input type="search">` that shows a contextmenu with
checkboxes for each search setting. Any settings changed for each input take precedence over
the default settings, but will match the corresponding default setting if it's changed.

* UserInterface/Views/SearchResultTreeElement.js:
(WI.SearchResultTreeElement.truncateAndHighlightTitle):
Use the length of the found text, rather than the length of the query.

* UserInterface/Views/DOMTreeElement.js:
(WI.DOMTreeElement.prototype._highlightSearchResults):
* UserInterface/Views/DataGrid.js:
(WI.DataGrid.prototype._updateFilter):
* UserInterface/Views/LogContentView.js:
(WI.LogContentView.prototype.performSearch):
* UserInterface/Views/NetworkTableContentView.js:
(WI.NetworkTableContentView.prototype._urlFilterDidChange):
* UserInterface/Views/ResourceHeadersContentView.js:
(WI.ResourceHeadersContentView.prototype._perfomSearchOnKeyValuePairs):
* UserInterface/Views/ResourceSecurityContentView.js:
(WI.ResourceSecurityContentView.prototype._perfomSearchOnKeyValuePairs):
* UserInterface/Views/SourceCodeTextEditor.js:
(WI.SourceCodeTextEditor.prototype.customPerformSearch.searchResultCallback):
(WI.SourceCodeTextEditor.prototype.customPerformSearch):
* UserInterface/Views/TextEditor.js:
(WI.TextEditor.prototype.performSearch):
Use the default search settings when searching/filtering.

* UserInterface/Views/SearchBar.css:
(.search-bar > input[type="search"]:placeholder-shown::-webkit-search-cancel-button): Added.
Drive-by: prevent the (x) from appearing when no text has been entered.
* Localizations/en.lproj/localizedStrings.js:
* UserInterface/Main.html:

LayoutTests:

<rdar://problem/46800955>

Reviewed by Joseph Pecoraro.

* inspector/debugger/search-scripts.html:
* inspector/debugger/search-scripts-expected.txt:

* inspector/page/searchInResources.html:
* inspector/page/searchInResources-expected.txt:

* inspector/page/resources/search-script.js:
* inspector/page/resources/search-stylesheet.css:
* inspector/page/resources/search-worker.js:
* inspector/page/resources/search-xhr.txt:

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@242018 268f45cc-cd09-0410-ab3c-d52691b4dbfc

28 files changed:
LayoutTests/ChangeLog
LayoutTests/inspector/debugger/search-scripts-expected.txt
LayoutTests/inspector/debugger/search-scripts.html
LayoutTests/inspector/page/resources/search-script.js
LayoutTests/inspector/page/resources/search-stylesheet.css
LayoutTests/inspector/page/resources/search-worker.js
LayoutTests/inspector/page/resources/search-xhr.txt
LayoutTests/inspector/page/searchInResources-expected.txt
LayoutTests/inspector/page/searchInResources.html
Source/WebInspectorUI/ChangeLog
Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js
Source/WebInspectorUI/UserInterface/Base/SearchUtilities.js [new file with mode: 0644]
Source/WebInspectorUI/UserInterface/Base/Setting.js
Source/WebInspectorUI/UserInterface/Main.html
Source/WebInspectorUI/UserInterface/Views/DOMTreeElement.js
Source/WebInspectorUI/UserInterface/Views/DataGrid.js
Source/WebInspectorUI/UserInterface/Views/LogContentView.js
Source/WebInspectorUI/UserInterface/Views/Main.css
Source/WebInspectorUI/UserInterface/Views/NetworkTableContentView.js
Source/WebInspectorUI/UserInterface/Views/ResourceHeadersContentView.js
Source/WebInspectorUI/UserInterface/Views/ResourceSecurityContentView.js
Source/WebInspectorUI/UserInterface/Views/SearchBar.css
Source/WebInspectorUI/UserInterface/Views/SearchResultTreeElement.js
Source/WebInspectorUI/UserInterface/Views/SearchSidebarPanel.css
Source/WebInspectorUI/UserInterface/Views/SearchSidebarPanel.js
Source/WebInspectorUI/UserInterface/Views/SettingsTabContentView.js
Source/WebInspectorUI/UserInterface/Views/SourceCodeTextEditor.js
Source/WebInspectorUI/UserInterface/Views/TextEditor.js

index f805971..e3ddc68 100644 (file)
@@ -1,5 +1,24 @@
 2019-02-24  Devin Rousso  <drousso@apple.com>
 
+        Web Inspector: provide a way to make searches case sensitive or use a regular expression
+        https://bugs.webkit.org/show_bug.cgi?id=192527
+        <rdar://problem/46800955>
+
+        Reviewed by Joseph Pecoraro.
+
+        * inspector/debugger/search-scripts.html:
+        * inspector/debugger/search-scripts-expected.txt:
+
+        * inspector/page/searchInResources.html:
+        * inspector/page/searchInResources-expected.txt:
+
+        * inspector/page/resources/search-script.js:
+        * inspector/page/resources/search-stylesheet.css:
+        * inspector/page/resources/search-worker.js:
+        * inspector/page/resources/search-xhr.txt:
+
+2019-02-24  Devin Rousso  <drousso@apple.com>
+
         Web Inspector: make debounce Proxy into its own class
         https://bugs.webkit.org/show_bug.cgi?id=194721
 
index a0a1bfd..91b2781 100644 (file)
 Test DebuggerAgent.searchInContent to search script content.
 
+Script added: inspector/debugger/search-scripts.html
+Script added: inspector/debugger/search-scripts.html
+Script added: eval1.js
+Script added: eval2.js
 
-SCRIPT: LayoutTests/http/tests/inspector/resources/inspector-test.js
+QUERY: searchtest {}
+SCRIPT: inspector/debugger/search-scripts.html
+RESULTS: 1
+  LINE: 1
+  CONTENT: runTest(); // SEARCHTEST: onload attribute string // OTHERTEST: onload attribute string
+
+QUERY: searchtest {"caseSensitive":true}
+SCRIPT: inspector/debugger/search-scripts.html
 RESULTS: 0
 
-SCRIPT: LayoutTests/inspector/debugger/search-scripts.html
-RESULTS: 2
+QUERY: searchtest {"isRegex":true}
+SCRIPT: inspector/debugger/search-scripts.html
+RESULTS: 1
+  LINE: 1
+  CONTENT: runTest(); // SEARCHTEST: onload attribute string // OTHERTEST: onload attribute string
+
+QUERY: searchtest {"caseSensitive":true,"isRegex":true}
+SCRIPT: inspector/debugger/search-scripts.html
+RESULTS: 0
+
+QUERY: SEARCHtest {}
+SCRIPT: inspector/debugger/search-scripts.html
+RESULTS: 1
+  LINE: 1
+  CONTENT: runTest(); // SEARCHTEST: onload attribute string // OTHERTEST: onload attribute string
+
+QUERY: SEARCHtest {"caseSensitive":true}
+SCRIPT: inspector/debugger/search-scripts.html
+RESULTS: 0
+
+QUERY: SEARCHtest {"isRegex":true}
+SCRIPT: inspector/debugger/search-scripts.html
+RESULTS: 1
+  LINE: 1
+  CONTENT: runTest(); // SEARCHTEST: onload attribute string // OTHERTEST: onload attribute string
+
+QUERY: SEARCHtest {"caseSensitive":true,"isRegex":true}
+SCRIPT: inspector/debugger/search-scripts.html
+RESULTS: 0
+
+QUERY: (search|OTHER)TEST {}
+SCRIPT: inspector/debugger/search-scripts.html
+RESULTS: 0
+
+QUERY: (search|OTHER)TEST {"caseSensitive":true}
+SCRIPT: inspector/debugger/search-scripts.html
+RESULTS: 0
+
+QUERY: (search|OTHER)TEST {"isRegex":true}
+SCRIPT: inspector/debugger/search-scripts.html
+RESULTS: 1
   LINE: 1
-  CONTENT: // SEARCHTEST: Comment in inline <script>.
-  LINE: 28
-  CONTENT:         DebuggerAgent.searchInContent(script.id, "SEARCHTEST", false, false, function(error, results) {
+  CONTENT: runTest(); // SEARCHTEST: onload attribute string // OTHERTEST: onload attribute string
 
-SCRIPT: LayoutTests/inspector/debugger/search-scripts.html
+QUERY: (search|OTHER)TEST {"caseSensitive":true,"isRegex":true}
+SCRIPT: inspector/debugger/search-scripts.html
 RESULTS: 1
   LINE: 1
-  CONTENT: runTest(); // SEARCHTEST: onload attribute string
+  CONTENT: runTest(); // SEARCHTEST: onload attribute string // OTHERTEST: onload attribute string
 
+QUERY: searchtest {}
 SCRIPT: eval1.js
 RESULTS: 1
   LINE: 0
   CONTENT: // SEARCHTEST: Eval 1
 
+QUERY: searchtest {"caseSensitive":true}
+SCRIPT: eval1.js
+RESULTS: 0
+
+QUERY: searchtest {"isRegex":true}
+SCRIPT: eval1.js
+RESULTS: 1
+  LINE: 0
+  CONTENT: // SEARCHTEST: Eval 1
+
+QUERY: searchtest {"caseSensitive":true,"isRegex":true}
+SCRIPT: eval1.js
+RESULTS: 0
+
+QUERY: SEARCHtest {}
+SCRIPT: eval1.js
+RESULTS: 1
+  LINE: 0
+  CONTENT: // SEARCHTEST: Eval 1
+
+QUERY: SEARCHtest {"caseSensitive":true}
+SCRIPT: eval1.js
+RESULTS: 0
+
+QUERY: SEARCHtest {"isRegex":true}
+SCRIPT: eval1.js
+RESULTS: 1
+  LINE: 0
+  CONTENT: // SEARCHTEST: Eval 1
+
+QUERY: SEARCHtest {"caseSensitive":true,"isRegex":true}
+SCRIPT: eval1.js
+RESULTS: 0
+
+QUERY: (search|OTHER)TEST {}
+SCRIPT: eval1.js
+RESULTS: 0
+
+QUERY: (search|OTHER)TEST {"caseSensitive":true}
+SCRIPT: eval1.js
+RESULTS: 0
+
+QUERY: (search|OTHER)TEST {"isRegex":true}
+SCRIPT: eval1.js
+RESULTS: 2
+  LINE: 0
+  CONTENT: // SEARCHTEST: Eval 1
+  LINE: 1
+  CONTENT: // OTHERTEST: Eval 1
+
+QUERY: (search|OTHER)TEST {"caseSensitive":true,"isRegex":true}
+SCRIPT: eval1.js
+RESULTS: 1
+  LINE: 1
+  CONTENT: // OTHERTEST: Eval 1
+
+QUERY: searchtest {}
+SCRIPT: eval2.js
+RESULTS: 1
+  LINE: 0
+  CONTENT: var SEARCHTEST = "SEARCHTEST";
+
+QUERY: searchtest {"caseSensitive":true}
+SCRIPT: eval2.js
+RESULTS: 0
+
+QUERY: searchtest {"isRegex":true}
+SCRIPT: eval2.js
+RESULTS: 1
+  LINE: 0
+  CONTENT: var SEARCHTEST = "SEARCHTEST";
+
+QUERY: searchtest {"caseSensitive":true,"isRegex":true}
+SCRIPT: eval2.js
+RESULTS: 0
+
+QUERY: SEARCHtest {}
 SCRIPT: eval2.js
 RESULTS: 1
   LINE: 0
   CONTENT: var SEARCHTEST = "SEARCHTEST";
 
+QUERY: SEARCHtest {"caseSensitive":true}
+SCRIPT: eval2.js
+RESULTS: 0
+
+QUERY: SEARCHtest {"isRegex":true}
+SCRIPT: eval2.js
+RESULTS: 1
+  LINE: 0
+  CONTENT: var SEARCHTEST = "SEARCHTEST";
+
+QUERY: SEARCHtest {"caseSensitive":true,"isRegex":true}
+SCRIPT: eval2.js
+RESULTS: 0
+
+QUERY: (search|OTHER)TEST {}
+SCRIPT: eval2.js
+RESULTS: 0
+
+QUERY: (search|OTHER)TEST {"caseSensitive":true}
+SCRIPT: eval2.js
+RESULTS: 0
+
+QUERY: (search|OTHER)TEST {"isRegex":true}
+SCRIPT: eval2.js
+RESULTS: 2
+  LINE: 0
+  CONTENT: var SEARCHTEST = "SEARCHTEST";
+  LINE: 1
+  CONTENT: var OTHERTEST = "OTHERTEST";
+
+QUERY: (search|OTHER)TEST {"caseSensitive":true,"isRegex":true}
+SCRIPT: eval2.js
+RESULTS: 1
+  LINE: 1
+  CONTENT: var OTHERTEST = "OTHERTEST";
+
index c4c8161..e4c35bb 100644 (file)
@@ -7,41 +7,61 @@
 function performEvals()
 {
     // Find the line with the search term.
-    eval("// SEARCH" + "TEST: Eval 1\n//# sourceURL=eval1.js");
+    eval("// SEARCH" + "TEST: Eval 1\n// OTHER" + "TEST: Eval 1\n//# sourceURL=eval1.js");
     // If the search term shows up multiple times on a single line, the line is returned once.
-    eval("var SEARCH" + "TEST = \"SEARCH" + "TEST\";\n//# sourceURL=eval2.js");
+    eval("var SEARCH" + "TEST = \"SEARCH" + "TEST\";\nvar OTHER" + "TEST = \"OTHER" + "TEST\";\n//# sourceURL=eval2.js");
 };
 
 function test()
 {
-    function sanitizeScriptURL(url) {
-        return url.substring(url.indexOf("LayoutTests"));
-    }
-
     function chomp(line) {
         return line.replace(/\n$/, "");
     }
 
-    var scriptsCount = 0;
-    const expectedScriptsCount = 5;
+    function searchInContent(script, query, options = {}) {
+        return DebuggerAgent.searchInContent(script.id, query, options.caseSensitive, options.isRegex)
+        .then(({result}) => {
+            InspectorTest.log("");
+            InspectorTest.log("QUERY: " + query + " " + JSON.stringify(options));
+            InspectorTest.log("SCRIPT: " + sanitizeURL(script.sourceURL || script.url));
+            InspectorTest.log("RESULTS: " + result.length);
+            for (let item of result) {
+                InspectorTest.log("  LINE: " + item.lineNumber);
+                InspectorTest.log("  CONTENT: " + chomp(item.lineContent));
+            }
+        });
+    }
+
+    let scripts = new Map;
 
     WI.debuggerManager.addEventListener(WI.DebuggerManager.Event.ScriptAdded, function(event) {
-        var script = event.data.script;
-        if (!/LayoutTests/.test(script.url) && !/eval\d\.js/.test(script.sourceURL))
+        let {script} = event.data;
+        let url = script.sourceURL || script.url;
+        if (!/LayoutTests/.test(url) && !/eval\d\.js/.test(url))
             return;
 
-        DebuggerAgent.searchInContent(script.id, "SEARCHTEST", false, false, function(error, results) {
-            InspectorTest.log("");
-            InspectorTest.log("SCRIPT: " + sanitizeScriptURL(script.sourceURL || script.url));
-            InspectorTest.log("RESULTS: " + results.length);
-            for (var result of results) {
-                InspectorTest.log("  LINE: " + result.lineNumber);
-                InspectorTest.log("  CONTENT: " + chomp(result.lineContent));
+        if (url.endsWith("inspector-test.js"))
+            return;
+
+        InspectorTest.log("Script added: " + sanitizeURL(url));
+
+        scripts.set(url, script);
+
+        if (url === "eval2.js") {
+            let promiseCallbacks = [];
+            for (let item of scripts.values()) {
+                for (let query of ["search" + "test", "SEARCH" + "test", "(search|OTHER)" + "TEST"]) {
+                    promiseCallbacks.push(() => searchInContent(item, query));
+                    promiseCallbacks.push(() => searchInContent(item, query, {caseSensitive: true}));
+                    promiseCallbacks.push(() => searchInContent(item, query, {isRegex: true}));
+                    promiseCallbacks.push(() => searchInContent(item, query, {caseSensitive: true, isRegex: true}));
+                }
             }
 
-            if (++scriptsCount === expectedScriptsCount)
+            Promise.chain(promiseCallbacks).then(() => {
                 InspectorTest.completeTest();
-        });
+            });
+        }
     });
 
     InspectorTest.addEventListener(FrontendTestHarness.Event.TestPageDidLoad, () => {
@@ -52,7 +72,7 @@ function test()
 }
 </script>
 </head>
-<body onload="runTest(); // SEARCHTEST: onload attribute string">
+<body onload="runTest(); // SEARCHTEST: onload attribute string // OTHERTEST: onload attribute string">
     <p>Test <code>DebuggerAgent.searchInContent</code> to search script content.</p>
 </body>
 </html>
index bf72621..c8f12a5 100644 (file)
@@ -1 +1,3 @@
 // Script resource with the SEARCH-STRING.
+
+// Script resource with the OTHER-STRING.
index 06ead77..10f42c2 100644 (file)
@@ -1,4 +1,7 @@
 /* Stylesheet resource with the SEARCH-STRING */
+
+/* Stylesheet resource with the OTHER-STRING */
+
 body {
     color: black;
 }
index 0f6a666..2b2e0ea 100644 (file)
@@ -1,5 +1,7 @@
 // Worker resource with the SEARCH-STRING.
 
+// Worker resource with the OTHER-STRING.
+
 self.addEventListener("message", (event) => {
     self.postMessage("echo: " + event.data);
 });
index 941d23f..e0ca3c8 100644 (file)
@@ -1,2 +1,5 @@
 XHR Resource with the SEARCH-STRING.
 XHR Resource with the SEARCH-STRING again!
+
+XHR Resource with the OTHER-STRING.
+XHR Resource with the OTHER-STRING again!
index 5c5c9d4..68964e4 100644 (file)
@@ -10,12 +10,64 @@ FOUND: inspector/page/resources/search-worker.js (1)
 FOUND: inspector/page/resources/search-xhr.txt (2)
 FOUND: inspector/page/searchInResources.html (1)
 
+-- Running test case: SearchAllResources.CaseSensitive
+PASS: Should find results in multiple resources.
+FOUND: inspector/page/resources/search-script.js (1)
+FOUND: inspector/page/resources/search-stylesheet.css (1)
+FOUND: inspector/page/resources/search-worker.js (1)
+FOUND: inspector/page/resources/search-xhr.txt (2)
+
+-- Running test case: SearchAllResources.IsRegex
+PASS: Should find results in multiple resources.
+FOUND: inspector/page/resources/search-script.js (2)
+FOUND: inspector/page/resources/search-stylesheet.css (2)
+FOUND: inspector/page/resources/search-worker.js (2)
+FOUND: inspector/page/resources/search-xhr.txt (4)
+FOUND: inspector/page/searchInResources.html (1)
+
+-- Running test case: SearchAllResources.CaseSensitiveIsRegex
+PASS: Should find results in multiple resources.
+FOUND: inspector/page/resources/search-script.js (1)
+FOUND: inspector/page/resources/search-stylesheet.css (1)
+FOUND: inspector/page/resources/search-worker.js (1)
+FOUND: inspector/page/resources/search-xhr.txt (2)
+
 -- Running test case: SearchInScriptResource
-PASS: Should find previously mentioned number of matches.
+PASS: Should find 1 matches.
+MATCH: {"lineNumber":0,"lineContent":"// Script resource with the SEARCH-STRING.\n"}
+
+-- Running test case: SearchInScriptResource.CaseSensitive
+PASS: Should find 1 matches.
 MATCH: {"lineNumber":0,"lineContent":"// Script resource with the SEARCH-STRING.\n"}
 
+-- Running test case: SearchInScriptResource.IsRegex
+PASS: Should find 2 matches.
+MATCH: {"lineNumber":0,"lineContent":"// Script resource with the SEARCH-STRING.\n"}
+MATCH: {"lineNumber":2,"lineContent":"// Script resource with the OTHER-STRING.\n"}
+
+-- Running test case: SearchInScriptResource.CaseSensitiveIsRegex
+PASS: Should find 1 matches.
+MATCH: {"lineNumber":2,"lineContent":"// Script resource with the OTHER-STRING.\n"}
+
 -- Running test case: SearchInXHRResource
-PASS: Should find previously mentioned number of matches.
+PASS: Should find 2 matches.
 MATCH: {"lineNumber":0,"lineContent":"XHR Resource with the SEARCH-STRING.\n"}
 MATCH: {"lineNumber":1,"lineContent":"XHR Resource with the SEARCH-STRING again!\n"}
 
+-- Running test case: SearchInXHRResource.CaseSensitive
+PASS: Should find 2 matches.
+MATCH: {"lineNumber":0,"lineContent":"XHR Resource with the SEARCH-STRING.\n"}
+MATCH: {"lineNumber":1,"lineContent":"XHR Resource with the SEARCH-STRING again!\n"}
+
+-- Running test case: SearchInXHRResource.IsRegex
+PASS: Should find 4 matches.
+MATCH: {"lineNumber":0,"lineContent":"XHR Resource with the SEARCH-STRING.\n"}
+MATCH: {"lineNumber":1,"lineContent":"XHR Resource with the SEARCH-STRING again!\n"}
+MATCH: {"lineNumber":3,"lineContent":"XHR Resource with the OTHER-STRING.\n"}
+MATCH: {"lineNumber":4,"lineContent":"XHR Resource with the OTHER-STRING again!\n"}
+
+-- Running test case: SearchInXHRResource.CaseSensitiveIsRegex
+PASS: Should find 2 matches.
+MATCH: {"lineNumber":3,"lineContent":"XHR Resource with the OTHER-STRING.\n"}
+MATCH: {"lineNumber":4,"lineContent":"XHR Resource with the OTHER-STRING again!\n"}
+
index c48d8b4..17f7092 100644 (file)
@@ -26,8 +26,14 @@ function test()
 
     let suite = InspectorTest.createAsyncSuite("Page.searchInResources and Page.searchInResource");
 
-    let searchResults;
-    const searchString = "SEARCH-STRING";
+    const searchString = "SeArCh-StRiNg";
+    const searchStringCaseSensitive = searchString.toUpperCase();
+    const searchStringRegex = "(search|OTHER)-STRING";
+
+    let searchResults = null;
+    let searchResultsCaseSensitive = null;
+    let searchResultsIsRegex = null;
+    let searchResultsCaseSensitiveIsRegex = null;
 
     suite.addTestCase({
         name: "SearchAllResources",
@@ -45,6 +51,56 @@ function test()
     });
 
     suite.addTestCase({
+        name: "SearchAllResources.CaseSensitive",
+        description: "Able to find text results in different resources.",
+        test(resolve, reject) {
+            const caseSensitive = true;
+            PageAgent.searchInResources(searchStringCaseSensitive, caseSensitive, (error, results) => {
+                InspectorTest.assert(!error, "Should not be a protocol error.");
+                InspectorTest.expectThat(results.length > 0, "Should find results in multiple resources.");
+                searchResultsCaseSensitive = results.sort((a, b) => a.url.localeCompare(b.url));
+                for (let result of searchResultsCaseSensitive)
+                    InspectorTest.log(`FOUND: ${sanitizeURL(result.url)} (${result.matchesCount})`);
+                resolve();
+            });
+        }
+    });
+
+    suite.addTestCase({
+        name: "SearchAllResources.IsRegex",
+        description: "Able to find text results in different resources.",
+        test(resolve, reject) {
+            const caseSensitive = undefined;
+            const isRegex = true;
+            PageAgent.searchInResources(searchStringRegex, caseSensitive, isRegex, (error, results) => {
+                InspectorTest.assert(!error, "Should not be a protocol error.");
+                InspectorTest.expectThat(results.length > 0, "Should find results in multiple resources.");
+                searchResultsIsRegex = results.sort((a, b) => a.url.localeCompare(b.url));
+                for (let result of searchResultsIsRegex)
+                    InspectorTest.log(`FOUND: ${sanitizeURL(result.url)} (${result.matchesCount})`);
+                resolve();
+            });
+        }
+    });
+
+    suite.addTestCase({
+        name: "SearchAllResources.CaseSensitiveIsRegex",
+        description: "Able to find text results in different resources.",
+        test(resolve, reject) {
+            const caseSensitive = true;
+            const isRegex = true;
+            PageAgent.searchInResources(searchStringRegex, caseSensitive, isRegex, (error, results) => {
+                InspectorTest.assert(!error, "Should not be a protocol error.");
+                InspectorTest.expectThat(results.length > 0, "Should find results in multiple resources.");
+                searchResultsCaseSensitiveIsRegex = results.sort((a, b) => a.url.localeCompare(b.url));
+                for (let result of searchResultsCaseSensitiveIsRegex)
+                    InspectorTest.log(`FOUND: ${sanitizeURL(result.url)} (${result.matchesCount})`);
+                resolve();
+            });
+        }
+    });
+
+    suite.addTestCase({
         name: "SearchInScriptResource",
         description: "Able to find text results in an individual Script resource.",
         test(resolve, reject) {
@@ -54,7 +110,66 @@ function test()
 
             PageAgent.searchInResource(result.frameId, result.url, searchString, (error, matches) => {
                 InspectorTest.assert(!error, "Should not be a protocol error.");
-                InspectorTest.expectThat(matches.length === result.matchesCount, "Should find previously mentioned number of matches.");
+                InspectorTest.expectThat(matches.length === result.matchesCount, `Should find ${result.matchesCount} matches.`);
+                for (let match of matches)
+                    InspectorTest.log(`MATCH: ${JSON.stringify(match)}`);
+                resolve();
+            });
+        }
+    });
+
+    suite.addTestCase({
+        name: "SearchInScriptResource.CaseSensitive",
+        description: "Able to find text results in an individual Script resource.",
+        test(resolve, reject) {
+            let result = searchResultsCaseSensitive.find((result) => /search-script\.js$/.test(result.url));
+            if (!result)
+                reject();
+
+            const caseSensitive = true;
+            PageAgent.searchInResource(result.frameId, result.url, searchStringCaseSensitive, caseSensitive, (error, matches) => {
+                InspectorTest.assert(!error, "Should not be a protocol error.");
+                InspectorTest.expectThat(matches.length === result.matchesCount, `Should find ${result.matchesCount} matches.`);
+                for (let match of matches)
+                    InspectorTest.log(`MATCH: ${JSON.stringify(match)}`);
+                resolve();
+            });
+        }
+    });
+
+    suite.addTestCase({
+        name: "SearchInScriptResource.IsRegex",
+        description: "Able to find text results in an individual Script resource.",
+        test(resolve, reject) {
+            let result = searchResultsIsRegex.find((result) => /search-script\.js$/.test(result.url));
+            if (!result)
+                reject();
+
+            const caseSensitive = undefined;
+            const isRegex = true;
+            PageAgent.searchInResource(result.frameId, result.url, searchStringRegex, caseSensitive, isRegex, (error, matches) => {
+                InspectorTest.assert(!error, "Should not be a protocol error.");
+                InspectorTest.expectThat(matches.length === result.matchesCount, `Should find ${result.matchesCount} matches.`);
+                for (let match of matches)
+                    InspectorTest.log(`MATCH: ${JSON.stringify(match)}`);
+                resolve();
+            });
+        }
+    });
+
+    suite.addTestCase({
+        name: "SearchInScriptResource.CaseSensitiveIsRegex",
+        description: "Able to find text results in an individual Script resource.",
+        test(resolve, reject) {
+            let result = searchResultsCaseSensitiveIsRegex.find((result) => /search-script\.js$/.test(result.url));
+            if (!result)
+                reject();
+
+            const caseSensitive = true;
+            const isRegex = true;
+            PageAgent.searchInResource(result.frameId, result.url, searchStringRegex, caseSensitive, isRegex, (error, matches) => {
+                InspectorTest.assert(!error, "Should not be a protocol error.");
+                InspectorTest.expectThat(matches.length === result.matchesCount, `Should find ${result.matchesCount} matches.`);
                 for (let match of matches)
                     InspectorTest.log(`MATCH: ${JSON.stringify(match)}`);
                 resolve();
@@ -75,7 +190,70 @@ function test()
 
             PageAgent.searchInResource(result.frameId, result.url, searchString, caseSensitive, isRegex, result.requestId, (error, matches) => {
                 InspectorTest.assert(!error, "Should not be a protocol error.");
-                InspectorTest.expectThat(matches.length === result.matchesCount, "Should find previously mentioned number of matches.");
+                InspectorTest.expectThat(matches.length === result.matchesCount, `Should find ${result.matchesCount} matches.`);
+                for (let match of matches)
+                    InspectorTest.log(`MATCH: ${JSON.stringify(match)}`);
+                resolve();
+            });
+        }
+    });
+
+    suite.addTestCase({
+        name: "SearchInXHRResource.CaseSensitive",
+        description: "Able to find text results in an individual XHR resource.",
+        test(resolve, reject) {
+            let result = searchResultsCaseSensitive.find((result) => /search-xhr\.txt$/.test(result.url));
+            if (!result)
+                reject();
+
+            const isRegex = undefined;
+            const caseSensitive = true;
+
+            PageAgent.searchInResource(result.frameId, result.url, searchStringCaseSensitive, caseSensitive, isRegex, result.requestId, (error, matches) => {
+                InspectorTest.assert(!error, "Should not be a protocol error.");
+                InspectorTest.expectThat(matches.length === result.matchesCount, `Should find ${result.matchesCount} matches.`);
+                for (let match of matches)
+                    InspectorTest.log(`MATCH: ${JSON.stringify(match)}`);
+                resolve();
+            });
+        }
+    });
+
+    suite.addTestCase({
+        name: "SearchInXHRResource.IsRegex",
+        description: "Able to find text results in an individual XHR resource.",
+        test(resolve, reject) {
+            let result = searchResultsIsRegex.find((result) => /search-xhr\.txt$/.test(result.url));
+            if (!result)
+                reject();
+
+            const isRegex = true;
+            const caseSensitive = undefined;
+
+            PageAgent.searchInResource(result.frameId, result.url, searchStringRegex, caseSensitive, isRegex, result.requestId, (error, matches) => {
+                InspectorTest.assert(!error, "Should not be a protocol error.");
+                InspectorTest.expectThat(matches.length === result.matchesCount, `Should find ${result.matchesCount} matches.`);
+                for (let match of matches)
+                    InspectorTest.log(`MATCH: ${JSON.stringify(match)}`);
+                resolve();
+            });
+        }
+    });
+
+    suite.addTestCase({
+        name: "SearchInXHRResource.CaseSensitiveIsRegex",
+        description: "Able to find text results in an individual XHR resource.",
+        test(resolve, reject) {
+            let result = searchResultsCaseSensitiveIsRegex.find((result) => /search-xhr\.txt$/.test(result.url));
+            if (!result)
+                reject();
+
+            const isRegex = true;
+            const caseSensitive = true;
+
+            PageAgent.searchInResource(result.frameId, result.url, searchStringRegex, caseSensitive, isRegex, result.requestId, (error, matches) => {
+                InspectorTest.assert(!error, "Should not be a protocol error.");
+                InspectorTest.expectThat(matches.length === result.matchesCount, `Should find ${result.matchesCount} matches.`);
                 for (let match of matches)
                     InspectorTest.log(`MATCH: ${JSON.stringify(match)}`);
                 resolve();
index 1f50aa0..623fe33 100644 (file)
@@ -1,5 +1,79 @@
 2019-02-24  Devin Rousso  <drousso@apple.com>
 
+        Web Inspector: provide a way to make searches case sensitive or use a regular expression
+        https://bugs.webkit.org/show_bug.cgi?id=192527
+
+        Reviewed by Joseph Pecoraro.
+
+        * UserInterface/Base/SearchUtilities.js: Added.
+        (WI.SearchUtilities.get defaultSettings):
+        (WI.SearchUtilities.createSettings):
+        (WI.SearchUtilities.regExpForString):
+        (WI.SearchUtilities.createSettingsButton):
+        (WI.SearchUtilities.createSettingsButton.toggleActive):
+        * UserInterface/Views/Main.css:
+        (.search-settings): Added.
+        (.search-settings > .glyph): Added.
+        (.search-settings:active > .glyph): Added.
+        (.search-settings.active > .glyph): Added.
+        (.search-settings:active.active > .glyph): Added.
+        Create static utility class for handling settings related to searching/filtering.
+
+        * UserInterface/Base/Setting.js:
+        * UserInterface/Views/SettingsTabContentView.js:
+        (WI.SettingsTabContentView.prototype._createGeneralSettingsView):
+        Create default search settings that apply across WebInspector, unless a more specific
+        setting has been created that overrides it (e.g. the navigation sidebar or Search tab).
+
+        * UserInterface/Views/SearchSidebarPanel.js:
+        (WI.SearchSidebarPanel):
+        (WI.SearchSidebarPanel.prototype.performSearch.forEachMatch):
+        (WI.SearchSidebarPanel.prototype.performSearch.resourceCallback):
+        (WI.SearchSidebarPanel.prototype.performSearch.resourcesCallback):
+        (WI.SearchSidebarPanel.prototype.performSearch.searchScripts.scriptCallback):
+        (WI.SearchSidebarPanel.prototype.performSearch.searchScripts):
+        (WI.SearchSidebarPanel.prototype.performSearch.domSearchResults):
+        (WI.SearchSidebarPanel.prototype.performSearch.domCallback):
+        (WI.SearchSidebarPanel.prototype.performSearch):
+        * UserInterface/Views/SearchSidebarPanel.css:
+        (.sidebar > .panel.navigation.search > .search-bar):
+        (.sidebar > .panel.navigation.search > .search-bar > input[type="search"]):
+        Add a (*) settings "gear" after each `<input type="search">` that shows a contextmenu with
+        checkboxes for each search setting. Any settings changed for each input take precedence over
+        the default settings, but will match the corresponding default setting if it's changed.
+
+        * UserInterface/Views/SearchResultTreeElement.js:
+        (WI.SearchResultTreeElement.truncateAndHighlightTitle):
+        Use the length of the found text, rather than the length of the query.
+
+        * UserInterface/Views/DOMTreeElement.js:
+        (WI.DOMTreeElement.prototype._highlightSearchResults):
+        * UserInterface/Views/DataGrid.js:
+        (WI.DataGrid.prototype._updateFilter):
+        * UserInterface/Views/LogContentView.js:
+        (WI.LogContentView.prototype.performSearch):
+        * UserInterface/Views/NetworkTableContentView.js:
+        (WI.NetworkTableContentView.prototype._urlFilterDidChange):
+        * UserInterface/Views/ResourceHeadersContentView.js:
+        (WI.ResourceHeadersContentView.prototype._perfomSearchOnKeyValuePairs):
+        * UserInterface/Views/ResourceSecurityContentView.js:
+        (WI.ResourceSecurityContentView.prototype._perfomSearchOnKeyValuePairs):
+        * UserInterface/Views/SourceCodeTextEditor.js:
+        (WI.SourceCodeTextEditor.prototype.customPerformSearch.searchResultCallback):
+        (WI.SourceCodeTextEditor.prototype.customPerformSearch):
+        * UserInterface/Views/TextEditor.js:
+        (WI.TextEditor.prototype.performSearch):
+        Use the default search settings when searching/filtering.
+
+        * UserInterface/Views/SearchBar.css:
+        (.search-bar > input[type="search"]:placeholder-shown::-webkit-search-cancel-button): Added.
+        Drive-by: prevent the (x) from appearing when no text has been entered.
+
+        * Localizations/en.lproj/localizedStrings.js:
+        * UserInterface/Main.html:
+
+2019-02-24  Devin Rousso  <drousso@apple.com>
+
         Web Inspector: make debounce Proxy into its own class
         https://bugs.webkit.org/show_bug.cgi?id=194721
 
index d59ade4..d3f4d21 100644 (file)
@@ -187,6 +187,10 @@ localizedStrings["Canvas Element"] = "Canvas Element";
 localizedStrings["Canvases"] = "Canvases";
 localizedStrings["Capture Screenshot"] = "Capture Screenshot";
 localizedStrings["Capturing"] = "Capturing";
+/* Context menu label for whether searches should be case sensitive. */
+localizedStrings["Case Sensitive @ Context Menu"] = "Case Sensitive";
+/* Settings tab checkbox label for whether searches should be case sensitive. */
+localizedStrings["Case Sensitive @ Settings"] = "Case Sensitive";
 localizedStrings["Catch Variables"] = "Catch Variables";
 localizedStrings["Categories"] = "Categories";
 localizedStrings["Certificate"] = "Certificate";
@@ -760,6 +764,10 @@ localizedStrings["Refresh all"] = "Refresh all";
 localizedStrings["Refresh watch expressions"] = "Refresh watch expressions";
 localizedStrings["Region announced in its entirety."] = "Region announced in its entirety.";
 localizedStrings["Regular Expression"] = "Regular Expression";
+/* Context menu label for whether searches should be treated as regular expressions. */
+localizedStrings["Regular Expression @ Context Menu"] = "Regular Expression";
+/* Settings tab checkbox label for whether searches should be treated as regular expressions. */
+localizedStrings["Regular Expression @ Settings"] = "Regular Expression";
 localizedStrings["Reload Web Inspector"] = "Reload Web Inspector";
 localizedStrings["Reload page (%s)\nReload page ignoring cache (%s)"] = "Reload page (%s)\nReload page ignoring cache (%s)";
 localizedStrings["Removals"] = "Removals";
@@ -834,6 +842,8 @@ localizedStrings["Scroll Into View"] = "Scroll Into View";
 localizedStrings["Search"] = "Search";
 localizedStrings["Search Again"] = "Search Again";
 localizedStrings["Search Resource Content"] = "Search Resource Content";
+/* Settings tab label for search related settings */
+localizedStrings["Search: @ Settings"] = "Search:";
 localizedStrings["Secure"] = "Secure";
 localizedStrings["Security"] = "Security";
 localizedStrings["Security Issue"] = "Security Issue";
diff --git a/Source/WebInspectorUI/UserInterface/Base/SearchUtilities.js b/Source/WebInspectorUI/UserInterface/Base/SearchUtilities.js
new file mode 100644 (file)
index 0000000..5ec3965
--- /dev/null
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2018 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+WI.SearchUtilities = class SearchUtilities {
+    static get defaultSettings()
+    {
+        return {
+            caseSensitive: WI.settings.searchCaseSensitive,
+            regularExpression: WI.settings.searchRegularExpression,
+        };
+    }
+
+    static createSettings(namePrefix, options = {})
+    {
+        let settings = {};
+        for (let [key, defaultSetting] of Object.entries(WI.SearchUtilities.defaultSettings)) {
+            let setting = new WI.Setting(namePrefix + "-" + defaultSetting.name, defaultSetting.value);
+            defaultSetting.addEventListener(WI.Setting.Event.Changed, (event) => {
+                setting.value = defaultSetting.value;
+            });
+            settings[key] = setting;
+
+            if (options.handleChanged)
+                setting.addEventListener(WI.Setting.Event.Changed, options.handleChanged);
+        }
+        return settings;
+    }
+
+    static regExpForString(query, settings = {})
+    {
+        function checkSetting(setting) {
+            return setting instanceof WI.Setting ? setting.value : !!setting;
+        }
+
+        if (!checkSetting(settings.regularExpression))
+            query = simpleGlobStringToRegExp(query);
+
+        let flags = "g";
+        if (!checkSetting(settings.caseSensitive))
+            flags += "i";
+
+        return new RegExp(query, flags);
+    }
+
+    static createSettingsButton(settings)
+    {
+        console.assert(!isEmptyObject(settings));
+
+        let button = document.createElement("button");
+        button.addEventListener("click", (event) => {
+            event.stop();
+
+            let contextMenu = WI.ContextMenu.createFromEvent(event);
+
+            if (settings.caseSensitive) {
+                contextMenu.appendCheckboxItem(WI.UIString("Case Sensitive", "Case Sensitive @ Context Menu", "Context menu label for whether searches should be case sensitive."), () => {
+                    settings.caseSensitive.value = !settings.caseSensitive.value;
+                }, settings.caseSensitive.value);
+            }
+
+            if (settings.regularExpression) {
+                contextMenu.appendCheckboxItem(WI.UIString("Regular Expression", "Regular Expression @ Context Menu", "Context menu label for whether searches should be treated as regular expressions."), () => {
+                    settings.regularExpression.value = !settings.regularExpression.value;
+                }, settings.regularExpression.value);
+            }
+
+            contextMenu.show();
+        });
+        button.classList.add("search-settings");
+        button.tabIndex = -1;
+
+        button.appendChild(WI.ImageUtilities.useSVGSymbol("Images/Gear.svg", "glyph"));
+
+        function toggleActive() {
+            button.classList.toggle("active", Object.values(settings).some((setting) => !!setting.value));
+        }
+        settings.caseSensitive.addEventListener(WI.Setting.Event.Changed, toggleActive);
+        settings.regularExpression.addEventListener(WI.Setting.Event.Changed, toggleActive);
+        toggleActive();
+
+        return button;
+    }
+};
index 2b02ae4..312654c 100644 (file)
@@ -136,6 +136,8 @@ WI.settings = {
     indentUnit: new WI.Setting("indent-unit", 4),
     indentWithTabs: new WI.Setting("indent-with-tabs", false),
     resourceCachingDisabled: new WI.Setting("disable-resource-caching", false),
+    searchCaseSensitive: new WI.Setting("search-case-sensitive", false),
+    searchRegularExpression: new WI.Setting("search-regular-expression", false),
     selectedNetworkDetailContentViewIdentifier: new WI.Setting("network-detail-content-view-identifier", "preview"),
     sourceMapsEnabled: new WI.Setting("source-maps-enabled", true),
     showAllRequestsBreakpoint: new WI.Setting("show-all-requests-breakpoint", true),
index 3409604..a89d66d 100644 (file)
     <script src="Base/ObjectStore.js"></script>
     <script src="Base/URLUtilities.js"></script>
     <script src="Base/Utilities.js"></script>
+    <script src="Base/SearchUtilities.js"></script>
     <script src="Base/Setting.js"></script>
     <script src="Base/YieldableTask.js"></script>
 
index 4d22f6b..8c45a54 100644 (file)
@@ -1707,7 +1707,7 @@ WI.DOMTreeElement = class DOMTreeElement extends WI.TreeElement
         }
 
         var text = this.title.textContent;
-        var searchRegex = new RegExp(this._searchQuery.escapeForRegExp(), "gi");
+        let searchRegex = WI.SearchUtilities.regExpForString(this._searchQuery, WI.SearchUtilities.defaultSettings);
 
         var match = searchRegex.exec(text);
         var matchRanges = [];
index 1c0ffb4..2531427 100644 (file)
@@ -1843,7 +1843,7 @@ WI.DataGrid = class DataGrid extends WI.View
         if (!this._rows.length)
             return;
 
-        this._textFilterRegex = simpleGlobStringToRegExp(this._filterText, "i");
+        this._textFilterRegex = WI.SearchUtilities.regExpForString(this._filterText, WI.SearchUtilities.defaultSettings);
 
         if (this._applyFilterToNodesTask && this._applyFilterToNodesTask.processing)
             this._applyFilterToNodesTask.cancel();
index c002a19..5a86a92 100644 (file)
@@ -1062,7 +1062,7 @@ WI.LogContentView = class LogContentView extends WI.ContentView
 
         this.element.classList.add(WI.LogContentView.SearchInProgressStyleClassName);
 
-        let searchRegex = new RegExp(this._currentSearchQuery.escapeForRegExp(), "gi");
+        let searchRegex = WI.SearchUtilities.regExpForString(this._currentSearchQuery, WI.SearchUtilities.defaultSettings);
         this._unfilteredMessageElements().forEach(function(message) {
             let matchRanges = [];
             let text = message.textContent;
index 31a70cb..2977eb3 100644 (file)
@@ -293,6 +293,33 @@ body[dir=rtl] .go-to-arrow {
     background-image: url(../Images/GoToArrow.svg#selected-active);
 }
 
+.search-settings {
+    display: inline-block;
+    margin: 0;
+    padding: 0;
+    background-color: transparent;
+    border: none;
+    -webkit-appearance: none;
+}
+
+.search-settings > .glyph {
+    width: 16px;
+    height: 16px;
+    color: var(--glyph-color);
+}
+
+.search-settings:active > .glyph {
+    color: var(--glyph-color-pressed);
+}
+
+.search-settings.active > .glyph {
+    color: var(--glyph-color-active);
+}
+
+.search-settings:active.active > .glyph {
+    color: var(--glyph-color-active-pressed);
+}
+
 .hidden {
     display: none !important;
 }
index 99e901d..36b7d0e 100644 (file)
@@ -1870,7 +1870,7 @@ WI.NetworkTableContentView = class NetworkTableContentView extends WI.ContentVie
 
         this._urlFilterIsActive = true;
         this._urlFilterSearchText = searchQuery;
-        this._urlFilterSearchRegex = new RegExp(searchQuery.escapeForRegExp(), "i");
+        this._urlFilterSearchRegex = WI.SearchUtilities.regExpForString(searchQuery, WI.SearchUtilities.defaultSettings);
 
         this._activeURLFilterResources.clear();
 
index 3ca8415..bcc777f 100644 (file)
@@ -419,7 +419,7 @@ WI.ResourceHeadersContentView = class ResourceHeadersContentView extends WI.Cont
 
     _perfomSearchOnKeyValuePairs()
     {
-        let searchRegex = new RegExp(this._searchQuery.escapeForRegExp(), "gi");
+        let searchRegex = WI.SearchUtilities.regExpForString(this._searchQuery, WI.SearchUtilities.defaultSettings);
 
         let elements = this.element.querySelectorAll(".key, .value");
         for (let element of elements) {
index ff6426e..1280841 100644 (file)
@@ -286,7 +286,7 @@ WI.ResourceSecurityContentView = class ResourceSecurityContentView extends WI.Co
 
     _perfomSearchOnKeyValuePairs()
     {
-        let searchRegex = new RegExp(this._searchQuery.escapeForRegExp(), "gi");
+        let searchRegex = WI.SearchUtilities.regExpForString(this._searchQuery, WI.SearchUtilities.defaultSettings);
 
         let elements = this.element.querySelectorAll(".key, .value");
         for (let element of elements) {
index d923aa1..613a199 100644 (file)
     background-color: white;
 }
 
+.search-bar > input[type="search"]:placeholder-shown::-webkit-search-cancel-button {
+    display: none;
+}
+
 @media (prefers-color-scheme: dark) {
     :matches(.search-bar, .filter-bar) > input[type="search"],
     .search-bar > input[type="search"]:not(:placeholder-shown) {
index a40825f..8f5018f 100644 (file)
@@ -45,7 +45,8 @@ WI.SearchResultTreeElement = class SearchResultTreeElement extends WI.GeneralTre
         // Use the original location, since those line/column offsets match the line text in title.
         var textRange = sourceCodeTextRange.textRange;
 
-        var searchTermIndex = textRange.startColumn;
+        let searchTermIndex = textRange.startColumn;
+        let searchTermLength = textRange.endColumn - textRange.startColumn;
 
         // We should only have one line text ranges, so make sure that is the case.
         console.assert(textRange.startLine === textRange.endLine);
@@ -59,9 +60,7 @@ WI.SearchResultTreeElement = class SearchResultTreeElement extends WI.GeneralTre
         } else
             modifiedTitle = title;
 
-        modifiedTitle = modifiedTitle.truncateEnd(searchTermIndex + searchTerm.length + charactersToShowAfterSearchMatch);
-
-        console.assert(modifiedTitle.substring(searchTermIndex, searchTermIndex + searchTerm.length).toLowerCase() === searchTerm.toLowerCase());
+        modifiedTitle = modifiedTitle.truncateEnd(searchTermIndex + searchTermLength + charactersToShowAfterSearchMatch);
 
         var highlightedTitle = document.createDocumentFragment();
 
@@ -69,10 +68,10 @@ WI.SearchResultTreeElement = class SearchResultTreeElement extends WI.GeneralTre
 
         var highlightSpan = document.createElement("span");
         highlightSpan.className = "highlighted";
-        highlightSpan.append(modifiedTitle.substring(searchTermIndex, searchTermIndex + searchTerm.length));
+        highlightSpan.append(modifiedTitle.substring(searchTermIndex, searchTermIndex + searchTermLength));
         highlightedTitle.appendChild(highlightSpan);
 
-        highlightedTitle.append(modifiedTitle.substring(searchTermIndex + searchTerm.length));
+        highlightedTitle.append(modifiedTitle.substring(searchTermIndex + searchTermLength));
 
         return highlightedTitle;
     }
index 7bfe2d0..25d66be 100644 (file)
 }
 
 .sidebar > .panel.navigation.search > .search-bar {
+    display: flex;
+    align-items: center;
     position: absolute;
     top: 0;
     left: 0;
     right: 0;
-
-    display: flex;
-
     height: calc(var(--navigation-bar-height) - 1px);
-
+    margin: 0 6px;
     white-space: nowrap;
     overflow: hidden;
 }
@@ -44,8 +43,9 @@
 .sidebar > .panel.navigation.search > .search-bar > input[type="search"] {
     display: flex;
     flex: 1;
-
-    margin: 3px 6px;
+    width: 100%;
+    margin: 0;
+    -webkit-margin-end: 4px;
 }
 
 .sidebar > .panel.navigation.search > .search-bar > input[type="search"]::-webkit-search-results-button {
index bce2c71..90921c1 100644 (file)
@@ -29,6 +29,12 @@ WI.SearchSidebarPanel = class SearchSidebarPanel extends WI.NavigationSidebarPan
     {
         super("search", WI.UIString("Search"), true, true);
 
+        this._searchInputSettings = WI.SearchUtilities.createSettings("search-sidebar", {
+            handleChanged: (event) => {
+                this.focusSearchField(true);
+            },
+        });
+
         var searchElement = document.createElement("div");
         searchElement.classList.add("search-bar");
         this.element.appendChild(searchElement);
@@ -43,6 +49,8 @@ WI.SearchSidebarPanel = class SearchSidebarPanel extends WI.NavigationSidebarPan
         this._inputElement.setAttribute("placeholder", WI.UIString("Search Resource Content"));
         searchElement.appendChild(this._inputElement);
 
+        searchElement.appendChild(WI.SearchUtilities.createSettingsButton(this._searchInputSettings));
+
         this._searchQuerySetting = new WI.Setting("search-sidebar-query", "");
         this._inputElement.value = this._searchQuerySetting.value;
 
@@ -89,13 +97,11 @@ WI.SearchSidebarPanel = class SearchSidebarPanel extends WI.NavigationSidebarPan
         if (this._changedBanner)
             this._changedBanner.remove();
 
-        searchQuery = searchQuery.trim();
         if (!searchQuery.length)
             return;
 
-        // FIXME: Provide UI to toggle regex and case sensitive searches.
-        var isCaseSensitive = false;
-        var isRegex = false;
+        let isCaseSensitive = !!this._searchInputSettings.caseSensitive.value;
+        let isRegex = !!this._searchInputSettings.regularExpression.value;
 
         var updateEmptyContentPlaceholderTimeout = null;
 
@@ -130,7 +136,10 @@ WI.SearchSidebarPanel = class SearchSidebarPanel extends WI.NavigationSidebarPan
         function forEachMatch(searchQuery, lineContent, callback)
         {
             var lineMatch;
-            var searchRegex = new RegExp(searchQuery.escapeForRegExp(), "gi");
+            let searchRegex = WI.SearchUtilities.regExpForString(searchQuery, {
+                caseSensitive: isCaseSensitive,
+                regularExpression: isRegex,
+            });
             while ((searchRegex.lastIndex < lineContent.length) && (lineMatch = searchRegex.exec(lineContent)))
                 callback(lineMatch, searchRegex.lastIndex);
         }
@@ -167,6 +176,9 @@ WI.SearchSidebarPanel = class SearchSidebarPanel extends WI.NavigationSidebarPan
                     });
                 }
 
+                if (!resourceTreeElement.children.length)
+                    this.contentTreeOutline.removeChild(resourceTreeElement);
+
                 updateEmptyContentPlaceholder.call(this);
             }
 
@@ -220,6 +232,9 @@ WI.SearchSidebarPanel = class SearchSidebarPanel extends WI.NavigationSidebarPan
                     });
                 }
 
+                if (!scriptTreeElement.children.length)
+                    this.contentTreeOutline.removeChild(scriptTreeElement);
+
                 updateEmptyContentPlaceholder.call(this);
             }
 
@@ -280,6 +295,9 @@ WI.SearchSidebarPanel = class SearchSidebarPanel extends WI.NavigationSidebarPan
                         createTreeElementForMatchObject.call(this, matchObject, resourceTreeElement);
                     }
 
+                    if (!resourceTreeElement.children.length)
+                        this.contentTreeOutline.removeChild(resourceTreeElement);
+
                     updateEmptyContentPlaceholder.call(this);
                 }
             }
index e0beda9..c9270e0 100644 (file)
@@ -211,6 +211,12 @@ WI.SettingsTabContentView = class SettingsTabContentView extends WI.TabContentVi
 
         generalSettingsView.addSeparator();
 
+        let searchGroup = generalSettingsView.addGroup(WI.UIString("Search:", "Search: @ Settings", "Settings tab label for search related settings"));
+        searchGroup.addSetting(WI.settings.searchCaseSensitive, WI.UIString("Case Sensitive", "Case Sensitive @ Settings", "Settings tab checkbox label for whether searches should be case sensitive."));
+        searchGroup.addSetting(WI.settings.searchRegularExpression, WI.UIString("Regular Expression", "Regular Expression @ Settings", "Settings tab checkbox label for whether searches should be treated as regular expressions."));
+
+        generalSettingsView.addSeparator();
+
         const zoomLevels = [0.6, 0.8, 1, 1.2, 1.4, 1.6, 1.8, 2, 2.2, 2.4];
         const zoomValues = zoomLevels.map((level) => [level, Number.percentageString(level, 0)]);
 
index 7f286ae..742c495 100644 (file)
@@ -224,7 +224,7 @@ WI.SourceCodeTextEditor = class SourceCodeTextEditor extends WI.TextEditor
                 return;
             }
 
-            var queryRegex = new RegExp(query.escapeForRegExp(), "gi");
+            let queryRegex = WI.SearchUtilities.regExpForString(query, WI.SearchUtilities.defaultSettings);
             var searchResults = [];
 
             for (var i = 0; i < matches.length; ++i) {
index ab96d39..7eac1ef 100644 (file)
@@ -338,7 +338,7 @@ WI.TextEditor = class TextEditor extends WI.View
         }
 
         // Go down the slow patch for all other text content.
-        var queryRegex = new RegExp(query.escapeForRegExp(), "gi");
+        let queryRegex = WI.SearchUtilities.regExpForString(query, WI.SearchUtilities.defaultSettings);
         var searchCursor = this._codeMirror.getSearchCursor(queryRegex, {line: 0, ch: 0}, false);
         var boundBatchSearch = batchSearch.bind(this);
         var numberOfSearchResultsDidChangeTimeout = null;