Web Inspector: Debugger: support pattern blackboxing
authordrousso@apple.com <drousso@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Sat, 12 Oct 2019 03:57:43 +0000 (03:57 +0000)
committerdrousso@apple.com <drousso@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Sat, 12 Oct 2019 03:57:43 +0000 (03:57 +0000)
https://bugs.webkit.org/show_bug.cgi?id=198855

Reviewed by Timothy Hatcher.

Source/JavaScriptCore:

Allow scripts to be blackboxed based on URL patterns (in addition to individual URLs) which
can be extremely useful when trying to step through unminified library/framework code.

* inspector/agents/InspectorDebuggerAgent.h:
* inspector/agents/InspectorDebuggerAgent.cpp:
(Inspector::InspectorDebuggerAgent::enable):
(Inspector::InspectorDebuggerAgent::setShouldBlackboxURL):
(Inspector::InspectorDebuggerAgent::shouldBlackboxURL const): Added.
(Inspector::InspectorDebuggerAgent::didParseSource):

* inspector/protocol/Debugger.json:
Add `caseSensitive` and `isRegex` optional boolean parameters to `setShouldBlackboxURL`.

Source/WebInspectorUI:

Allow scripts to be blackboxed based on URL patterns (in addition to individual URLs) which
can be extremely useful when trying to step through unminified library/framework code.

* UserInterface/Controllers/DebuggerManager.js:
(WI.DebuggerManager):
(WI.DebuggerManager.prototype.initializeTarget):
(WI.DebuggerManager.prototype.blackboxTypeForSourceCode): Added.
(WI.DebuggerManager.prototype.get blackboxPatterns): Added.
(WI.DebuggerManager.prototype.setShouldBlackboxScript):
(WI.DebuggerManager.prototype.setShouldBlackboxPattern): Added.
(WI.DebuggerManager.prototype.isScriptBlackboxed): Deleted.
Provide a separate path for setting URL pattern blackboxes, rather than an exact/given URL.

* UserInterface/Views/SettingsTabContentView.js:
(WI.SettingsTabContentView):
(WI.SettingsTabContentView.prototype.selectBlackboxPattern): Added.
(WI.SettingsTabContentView.prototype.initialLayout):
(WI.SettingsTabContentView.prototype._createSourcesSettingsView):
* UserInterface/Views/BlackboxSettingsView.js: Added.
(WI.BlackboxSettingsView):
(WI.BlackboxSettingsView.prototype.selectBlackboxPattern):
(WI.BlackboxSettingsView.prototype.initialLayout):
(WI.BlackboxSettingsView.prototype._addRow):
* UserInterface/Views/BlackboxSettingsView.css: Added.
(.settings-view.blackbox > :matches(p, table)):
(.settings-view.blackbox > p):
(.settings-view.blackbox > * + p):
(.settings-view.blackbox > p:last-child):
(.settings-view.blackbox > p > .toggle-script-blackbox):
(.settings-view.blackbox > table):
(.settings-view.blackbox > table > thead th):
(.settings-view.blackbox > table > tbody td):
(.settings-view.blackbox > table > tbody td:not(.remove-blackbox)):
(.settings-view.blackbox > table :matches(th, td).url):
(.settings-view.blackbox > table > tbody td.url > .CodeMirror):
(.settings-view.blackbox > table :matches(th, td):matches(.case-sensitive, .remove-blackbox)):
(.settings-view.blackbox > table > tbody > tr:not(:hover) > td.remove-blackbox):
(.settings-view.blackbox > table > tbody td.remove-blackbox > .remove-blackbox-button):
Add a "Blackbox" pane that contains an editable table of pattern blackboxes, each having its
own row (code mirror for the URL regular expression and a checkbox for case sensitivity).

* UserInterface/Views/SourceCodeTreeElement.js:
(WI.SourceCodeTreeElement.prototype.updateStatus):
(WI.SourceCodeTreeElement.prototype._updateSourceCode):
(WI.SourceCodeTreeElement.prototype._updateToggleBlackboxImageElementState):
(WI.SourceCodeTreeElement.prototype._handleToggleBlackboxedImageElementClicked):
* UserInterface/Views/SourceCodeTreeElement.css:
(.tree-outline .item .status > .toggle-script-blackbox, .tree-outline:focus .item.selected .status > .toggle-script-blackbox.pattern-blackboxed): Added.
(.tree-outline .item .status > .toggle-script-blackbox.pattern-blackboxed): Added.
(.tree-outline .item .status > .toggle-script-blackbox): Added.
(.tree-outline .item .status > .toggle-script-blackbox:not(.pattern-blackboxed)): Added.
(.tree-outline .item .status > .toggle-script-blackbox:not(.url-blackboxed, .pattern-blackboxed)): Added.
(.tree-outline:not(.navigation-sidebar-panel-content-tree-outline) .item .status > .toggle-script-blackbox, .tree-outline .item:not(:hover) .status > .toggle-script-blackbox:not(.pattern-blackboxed, .url-blackboxed)): Added.
(@media (prefers-color-scheme: dark) .tree-outline .item .status > .toggle-script-blackbox): Added.
(@media (prefers-color-scheme: dark) .tree-outline .item .status > .toggle-script-blackbox.pattern-blackboxed): Added.
(@media (prefers-color-scheme: dark) .tree-outline .item .status > .toggle-script-blackbox:not(.pattern-blackboxed)): Added.
(.tree-outline .item .status > .toggle-script-blackboxed): Deleted.
(.tree-outline:not(.navigation-sidebar-panel-content-tree-outline) .item .status > .toggle-script-blackboxed, .tree-outline .item:not(:hover) .status > .toggle-script-blackboxed:not(.blackboxed)): Deleted.
(.tree-outline:focus .item.selected .status > .toggle-script-blackboxed): Deleted.
(.tree-outline .item .status > .toggle-script-blackboxed.blackboxed): Deleted.
(@media (prefers-color-scheme: dark) .tree-outline .item .status > .toggle-script-blackboxed): Deleted.
* UserInterface/Views/ShaderProgramTreeElement.css:
(.tree-outline .item.shader-program .status > img):
(.tree-outline .item.shader-program:not(:hover, .disabled) .status > img): Added.
(.tree-outline .item.shader-program:not(.disabled) .status > img): Added.
(@media (prefers-color-scheme: dark) .tree-outline .item.shader-program .status > img):
(.tree-outline .item.shader-program:not(:hover, .selected, .disabled) .status > img): Deleted.
(.tree-outline:focus .item.shader-program.selected .status > img): Deleted.
(.tree-outline .item.shader-program.disabled .status > img): Deleted.
* UserInterface/Views/ContextMenuUtilities.js:
(WI.appendContextMenuItemsForSourceCode):
* UserInterface/Images/Hide.svg:
Use a slightly different style for the blackbox icon if the source code's URL matches a
blackbox pattern. Clicking on the blackbox icon in this state will show the Settings Tab.

* UserInterface/Base/Main.js:
(WI.contentLoaded):
(WI._handleSettingsKeyboardShortcut): Added.
(WI.showSettingsTab): Added.
(WI._showSettingsTab): Deleted.

* Localizations/en.lproj/localizedStrings.js:

LayoutTests:

* inspector/debugger/setShouldBlackboxURL.html:
* inspector/debugger/setShouldBlackboxURL-expected.txt:

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

20 files changed:
LayoutTests/ChangeLog
LayoutTests/inspector/debugger/setShouldBlackboxURL-expected.txt
LayoutTests/inspector/debugger/setShouldBlackboxURL.html
Source/JavaScriptCore/ChangeLog
Source/JavaScriptCore/inspector/agents/InspectorDebuggerAgent.cpp
Source/JavaScriptCore/inspector/agents/InspectorDebuggerAgent.h
Source/JavaScriptCore/inspector/protocol/Debugger.json
Source/WebInspectorUI/ChangeLog
Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js
Source/WebInspectorUI/UserInterface/Base/Main.js
Source/WebInspectorUI/UserInterface/Controllers/DebuggerManager.js
Source/WebInspectorUI/UserInterface/Images/Hide.svg
Source/WebInspectorUI/UserInterface/Main.html
Source/WebInspectorUI/UserInterface/Views/BlackboxSettingsView.css [new file with mode: 0644]
Source/WebInspectorUI/UserInterface/Views/BlackboxSettingsView.js [new file with mode: 0644]
Source/WebInspectorUI/UserInterface/Views/ContextMenuUtilities.js
Source/WebInspectorUI/UserInterface/Views/SettingsTabContentView.js
Source/WebInspectorUI/UserInterface/Views/ShaderProgramTreeElement.css
Source/WebInspectorUI/UserInterface/Views/SourceCodeTreeElement.css
Source/WebInspectorUI/UserInterface/Views/SourceCodeTreeElement.js

index 217691a..229631e 100644 (file)
@@ -1,5 +1,15 @@
 2019-10-11  Devin Rousso  <drousso@apple.com>
 
+        Web Inspector: Debugger: support pattern blackboxing
+        https://bugs.webkit.org/show_bug.cgi?id=198855
+
+        Reviewed by Timothy Hatcher.
+
+        * inspector/debugger/setShouldBlackboxURL.html:
+        * inspector/debugger/setShouldBlackboxURL-expected.txt:
+
+2019-10-11  Devin Rousso  <drousso@apple.com>
+
         Web Inspector: Elements: Computed: show shorthand properties in addition to longhand ones
         https://bugs.webkit.org/show_bug.cgi?id=200554
 
index c68ac18..a06d926 100644 (file)
@@ -2,80 +2,305 @@ Tests Debugger.setShouldBlackboxURL.
 
 
 == Running test suite: Debugger.setShouldBlackboxURL
--- Running test case: Debugger.setShouldBlackboxURL.stepOver
-Evaluating 'createScripts("stepOver")'...
-Blackboxing 'stepOverMiddle.js'...
-Setting breakpoint in 'stepOverInner.js'...
-Evaluating 'stepOverOuter(10)'...
+-- Running test case: Debugger.setShouldBlackboxURL.String.stepOver
+Evaluating 'createScripts("String_StepOver")'...
+Blackboxing 'string_stepover_middle.js'...
+Setting breakpoint in 'String_StepOver_Inner.js'...
+Evaluating 'String_StepOver_Outer(10)'...
 
-Paused in 'stepOverInner:3:1'.
-Reason: 'Breakpoint'
+PAUSED: 'Breakpoint' at 'String_StepOver_Inner:3:1'.
 {
-  "breakpointId": "stepOverInner.js:3:0"
+  "breakpointId": "String_StepOver_Inner.js:3:0"
 }
 Stepping over...
 
-Paused in 'stepOverOuter:3:1'.
-Reason: 'BlackboxedScript'
+PAUSED: 'BlackboxedScript' at 'String_StepOver_Outer:3:1'.
 {
   "originalReason": "other",
   "originalData": {
-    "breakpointId": "stepOverInner.js:3:0"
+    "breakpointId": "String_StepOver_Inner.js:3:0"
   }
 }
 Stepping over...
 
 Resuming...
 PASS: Resumed.
-PASS: Should not pause in 'stepOverMiddle'.
+PASS: Should not pause in 'String_StepOver_Middle'.
 
--- Running test case: Debugger.setShouldBlackboxURL.PauseInCaller
-Evaluating 'createScripts("pauseInCaller")'...
-Blackboxing 'pauseInCallerInner.js'...
-Setting breakpoint in 'pauseInCallerInner.js'...
-Evaluating 'pauseInCallerOuter(10)'...
 
-Paused in 'pauseInCallerMiddle:3:1'.
-Reason: 'BlackboxedScript'
+-- Running test case: Debugger.setShouldBlackboxURL.String.PauseInCaller
+Evaluating 'createScripts("String_PauseInCaller")'...
+Blackboxing 'string_pauseincaller_inner.js'...
+Setting breakpoint in 'String_PauseInCaller_Inner.js'...
+Evaluating 'String_PauseInCaller_Outer(10)'...
+
+PAUSED: 'BlackboxedScript' at 'String_PauseInCaller_Middle:3:1'.
+{
+  "originalReason": "Breakpoint",
+  "originalData": {
+    "breakpointId": "String_PauseInCaller_Inner.js:2:0"
+  }
+}
+Stepping over...
+
+PAUSED: 'other' at 'String_PauseInCaller_Outer:3:1'.
+Stepping over...
+
+Resuming...
+PASS: Resumed.
+PASS: Should not pause in 'String_PauseInCaller_Inner'.
+
+
+-- Running test case: Debugger.setShouldBlackboxURL.String.PauseInCallee
+Evaluating 'createScripts("String_PauseInCallee")'...
+Blackboxing 'string_pauseincallee_outer.js'...
+Setting breakpoint in 'String_PauseInCallee_Outer.js'...
+Evaluating 'String_PauseInCallee_Outer(10)'...
+
+PAUSED: 'BlackboxedScript' at 'String_PauseInCallee_Middle:2:4'.
+{
+  "originalReason": "Breakpoint",
+  "originalData": {
+    "breakpointId": "String_PauseInCallee_Outer.js:2:0"
+  }
+}
+Stepping over...
+
+PAUSED: 'other' at 'String_PauseInCallee_Middle:3:1'.
+Stepping over...
+
+Resuming...
+PASS: Resumed.
+PASS: Should not pause in 'String_PauseInCallee_Outer'.
+
+
+-- Running test case: Debugger.setShouldBlackboxURL.CaseSensitiveString.stepOver
+Evaluating 'createScripts("CaseSensitiveString_StepOver")'...
+Blackboxing (case sensitive) 'casesensitivestring_stepover_inner.js'...
+Blackboxing (case sensitive) 'CaseSensitiveString_StepOver_Middle.js'...
+Setting breakpoint in 'CaseSensitiveString_StepOver_Inner.js'...
+Evaluating 'CaseSensitiveString_StepOver_Outer(10)'...
+
+PAUSED: 'Breakpoint' at 'CaseSensitiveString_StepOver_Inner:3:1'.
+{
+  "breakpointId": "CaseSensitiveString_StepOver_Inner.js:3:0"
+}
+Stepping over...
+
+PAUSED: 'BlackboxedScript' at 'CaseSensitiveString_StepOver_Outer:3:1'.
+{
+  "originalReason": "other",
+  "originalData": {
+    "breakpointId": "CaseSensitiveString_StepOver_Inner.js:3:0"
+  }
+}
+Stepping over...
+
+Resuming...
+PASS: Resumed.
+PASS: Should pause in 'CaseSensitiveString_StepOver_Inner'.
+PASS: Should not pause in 'CaseSensitiveString_StepOver_Middle'.
+
+
+-- Running test case: Debugger.setShouldBlackboxURL.CaseSensitiveString.PauseInCaller
+Evaluating 'createScripts("CaseSensitiveString_PauseInCaller")'...
+Blackboxing (case sensitive) 'CaseSensitiveString_PauseInCaller_Inner.js'...
+Blackboxing (case sensitive) 'casesensitivestring_pauseincaller_middle.js'...
+Setting breakpoint in 'CaseSensitiveString_PauseInCaller_Inner.js'...
+Evaluating 'CaseSensitiveString_PauseInCaller_Outer(10)'...
+
+PAUSED: 'BlackboxedScript' at 'CaseSensitiveString_PauseInCaller_Middle:3:1'.
 {
   "originalReason": "Breakpoint",
   "originalData": {
-    "breakpointId": "pauseInCallerInner.js:2:0"
+    "breakpointId": "CaseSensitiveString_PauseInCaller_Inner.js:2:0"
   }
 }
 Stepping over...
 
-Paused in 'pauseInCallerOuter:3:1'.
-Reason: 'other'
+PAUSED: 'other' at 'CaseSensitiveString_PauseInCaller_Outer:3:1'.
 Stepping over...
 
 Resuming...
 PASS: Resumed.
-PASS: Should not pause in 'pauseInCallerInner'.
+PASS: Should not pause in 'CaseSensitiveString_PauseInCaller_Inner'.
+PASS: Should pause in 'CaseSensitiveString_PauseInCaller_Middle'.
 
--- Running test case: Debugger.setShouldBlackboxURL.PauseInCallee
-Evaluating 'createScripts("pauseInCallee")'...
-Blackboxing 'pauseInCalleeOuter.js'...
-Setting breakpoint in 'pauseInCalleeOuter.js'...
-Evaluating 'pauseInCalleeOuter(10)'...
 
-Paused in 'pauseInCalleeMiddle:2:4'.
-Reason: 'BlackboxedScript'
+-- Running test case: Debugger.setShouldBlackboxURL.CaseSensitiveString.PauseInCallee
+Evaluating 'createScripts("CaseSensitiveString_PauseInCallee")'...
+Blackboxing (case sensitive) 'casesensitivestring_pauseincallee_middle.js'...
+Blackboxing (case sensitive) 'CaseSensitiveString_PauseInCallee_Outer.js'...
+Setting breakpoint in 'CaseSensitiveString_PauseInCallee_Outer.js'...
+Evaluating 'CaseSensitiveString_PauseInCallee_Outer(10)'...
+
+PAUSED: 'BlackboxedScript' at 'CaseSensitiveString_PauseInCallee_Middle:2:4'.
 {
   "originalReason": "Breakpoint",
   "originalData": {
-    "breakpointId": "pauseInCalleeOuter.js:2:0"
+    "breakpointId": "CaseSensitiveString_PauseInCallee_Outer.js:2:0"
   }
 }
 Stepping over...
 
-Paused in 'pauseInCalleeMiddle:3:1'.
-Reason: 'other'
+PAUSED: 'other' at 'CaseSensitiveString_PauseInCallee_Middle:3:1'.
+Stepping over...
+
+Resuming...
+PASS: Resumed.
+PASS: Should pause in 'CaseSensitiveString_PauseInCallee_Middle'.
+PASS: Should not pause in 'CaseSensitiveString_PauseInCallee_Outer'.
+
+
+-- Running test case: Debugger.setShouldBlackboxURL.Regex.stepOver
+Evaluating 'createScripts("Regex_StepOver")'...
+Blackboxing (regex) 'regex[ -_]stepover[ -_]middle\.js$'...
+Setting breakpoint in 'Regex_StepOver_Inner.js'...
+Evaluating 'Regex_StepOver_Outer(10)'...
+
+PAUSED: 'Breakpoint' at 'Regex_StepOver_Inner:3:1'.
+{
+  "breakpointId": "Regex_StepOver_Inner.js:3:0"
+}
+Stepping over...
+
+PAUSED: 'BlackboxedScript' at 'Regex_StepOver_Outer:3:1'.
+{
+  "originalReason": "other",
+  "originalData": {
+    "breakpointId": "Regex_StepOver_Inner.js:3:0"
+  }
+}
 Stepping over...
 
 Resuming...
 PASS: Resumed.
-PASS: Should not pause in 'pauseInCalleeOuter'.
+PASS: Should not pause in 'Regex_StepOver_Middle'.
+
+
+-- Running test case: Debugger.setShouldBlackboxURL.Regex.PauseInCaller
+Evaluating 'createScripts("Regex_PauseInCaller")'...
+Blackboxing (regex) 'regex[ -_]pauseincaller[ -_]inner\.js$'...
+Setting breakpoint in 'Regex_PauseInCaller_Inner.js'...
+Evaluating 'Regex_PauseInCaller_Outer(10)'...
+
+PAUSED: 'BlackboxedScript' at 'Regex_PauseInCaller_Middle:3:1'.
+{
+  "originalReason": "Breakpoint",
+  "originalData": {
+    "breakpointId": "Regex_PauseInCaller_Inner.js:2:0"
+  }
+}
+Stepping over...
+
+PAUSED: 'other' at 'Regex_PauseInCaller_Outer:3:1'.
+Stepping over...
+
+Resuming...
+PASS: Resumed.
+PASS: Should not pause in 'Regex_PauseInCaller_Inner'.
+
+
+-- Running test case: Debugger.setShouldBlackboxURL.Regex.PauseInCallee
+Evaluating 'createScripts("Regex_PauseInCallee")'...
+Blackboxing (regex) 'regex[ -_]pauseincallee[ -_]outer\.js$'...
+Setting breakpoint in 'Regex_PauseInCallee_Outer.js'...
+Evaluating 'Regex_PauseInCallee_Outer(10)'...
+
+PAUSED: 'BlackboxedScript' at 'Regex_PauseInCallee_Middle:2:4'.
+{
+  "originalReason": "Breakpoint",
+  "originalData": {
+    "breakpointId": "Regex_PauseInCallee_Outer.js:2:0"
+  }
+}
+Stepping over...
+
+PAUSED: 'other' at 'Regex_PauseInCallee_Middle:3:1'.
+Stepping over...
+
+Resuming...
+PASS: Resumed.
+PASS: Should not pause in 'Regex_PauseInCallee_Outer'.
+
+
+-- Running test case: Debugger.setShouldBlackboxURL.CaseSensitiveRegex.stepOver
+Evaluating 'createScripts("CaseSensitiveRegex_StepOver")'...
+Blackboxing (case sensitive) (regex) 'casesensitiveregex_stepover_inner\.js$'...
+Blackboxing (case sensitive) (regex) 'CaseSensitiveRegex_StepOver_Middle\.js$'...
+Setting breakpoint in 'CaseSensitiveRegex_StepOver_Inner.js'...
+Evaluating 'CaseSensitiveRegex_StepOver_Outer(10)'...
+
+PAUSED: 'Breakpoint' at 'CaseSensitiveRegex_StepOver_Inner:3:1'.
+{
+  "breakpointId": "CaseSensitiveRegex_StepOver_Inner.js:3:0"
+}
+Stepping over...
+
+PAUSED: 'BlackboxedScript' at 'CaseSensitiveRegex_StepOver_Outer:3:1'.
+{
+  "originalReason": "other",
+  "originalData": {
+    "breakpointId": "CaseSensitiveRegex_StepOver_Inner.js:3:0"
+  }
+}
+Stepping over...
+
+Resuming...
+PASS: Resumed.
+PASS: Should pause in 'CaseSensitiveRegex_StepOver_Inner'.
+PASS: Should not pause in 'CaseSensitiveRegex_StepOver_Middle'.
+
+
+-- Running test case: Debugger.setShouldBlackboxURL.CaseSensitiveRegex.PauseInCaller
+Evaluating 'createScripts("CaseSensitiveRegex_PauseInCaller")'...
+Blackboxing (case sensitive) (regex) 'casesensitiveregex_pauseincaller_inner\.js$'...
+Blackboxing (case sensitive) (regex) 'CaseSensitiveRegex_PauseInCaller_middle\.js$'...
+Setting breakpoint in 'CaseSensitiveRegex_PauseInCaller_Inner.js'...
+Evaluating 'CaseSensitiveRegex_PauseInCaller_Outer(10)'...
+
+PAUSED: 'BlackboxedScript' at 'CaseSensitiveRegex_PauseInCaller_Middle:3:1'.
+{
+  "originalReason": "Breakpoint",
+  "originalData": {
+    "breakpointId": "CaseSensitiveRegex_PauseInCaller_Inner.js:2:0"
+  }
+}
+Stepping over...
+
+PAUSED: 'other' at 'CaseSensitiveRegex_PauseInCaller_Outer:3:1'.
+Stepping over...
+
+Resuming...
+PASS: Resumed.
+PASS: Should not pause in 'CaseSensitiveRegex_PauseInCaller_Inner'.
+PASS: Should pause in 'CaseSensitiveRegex_PauseInCaller_Middle'.
+
+
+-- Running test case: Debugger.setShouldBlackboxURL.CaseSensitiveRegex.PauseInCallee
+Evaluating 'createScripts("CaseSensitiveRegex_PauseInCallee")'...
+Blackboxing (case sensitive) (regex) 'casesensitiveregex_pauseincallee_middle\.js$'...
+Blackboxing (case sensitive) (regex) 'CaseSensitiveRegex_PauseInCallee_Outer\.js$'...
+Setting breakpoint in 'CaseSensitiveRegex_PauseInCallee_Outer.js'...
+Evaluating 'CaseSensitiveRegex_PauseInCallee_Outer(10)'...
+
+PAUSED: 'BlackboxedScript' at 'CaseSensitiveRegex_PauseInCallee_Middle:2:4'.
+{
+  "originalReason": "Breakpoint",
+  "originalData": {
+    "breakpointId": "CaseSensitiveRegex_PauseInCallee_Outer.js:2:0"
+  }
+}
+Stepping over...
+
+PAUSED: 'other' at 'CaseSensitiveRegex_PauseInCallee_Middle:3:1'.
+Stepping over...
+
+Resuming...
+PASS: Resumed.
+PASS: Should pause in 'CaseSensitiveRegex_PauseInCallee_Middle'.
+PASS: Should not pause in 'CaseSensitiveRegex_PauseInCallee_Outer'.
+
 
 -- Running test case: Debugger.setShouldBlackboxURL.Invalid.emptyURL
 PASS: Should produce an exception.
index 9558f75..e718821 100644 (file)
@@ -6,28 +6,28 @@
 function createScripts(id) {
     eval(
 `
-window.${id}Inner = function ${id}Inner(x) {
+window.${id}_Inner = function ${id}_Inner(x) {
     return x + 42;
 };
-//# sourceURL=${id}Inner.js
+//# sourceURL=${id}_Inner.js
 `
     );
 
     eval(
 `
-window.${id}Middle = function ${id}Middle(x) {
-    return ${id}Inner(x);
+window.${id}_Middle = function ${id}_Middle(x) {
+    return ${id}_Inner(x);
 };
-//# sourceURL=${id}Middle.js
+//# sourceURL=${id}_Middle.js
 `
     );
 
     eval(
 `
-window.${id}Outer = function ${id}Outer(x) {
-    return ${id}Middle(x);
+window.${id}_Outer = function ${id}_Outer(x) {
+    return ${id}_Middle(x);
 };
-//# sourceURL=${id}Outer.js
+//# sourceURL=${id}_Outer.js
 `
     );
 }
@@ -54,6 +54,8 @@ function test()
     };
 
     InspectorProtocol.eventHandler["Debugger.paused"] = function(message) {
+        ProtocolTest.newline();
+
         let topCallFrame = message.params.callFrames[0];
         let functionName = topCallFrame.functionName;
         if (functionName === "global code") {
@@ -62,14 +64,12 @@ function test()
             return;
         }
 
-        ProtocolTest.log(`Paused in '${functionName}:${topCallFrame.location.lineNumber}:${topCallFrame.location.columnNumber}'.`);
-        ProtocolTest.log(`Reason: '${message.params.reason}'`);
+        ProtocolTest.log(`PAUSED: '${message.params.reason}' at '${functionName}:${topCallFrame.location.lineNumber}:${topCallFrame.location.columnNumber}'.`);
         if (message.params.data)
             ProtocolTest.json(message.params.data);
         pausedFunctionNames.push(functionName);
 
         ProtocolTest.log("Stepping over...");
-        ProtocolTest.newline();
         InspectorProtocol.sendCommand(`Debugger.stepOver`, {}, InspectorProtocol.checkForError);
     };
 
@@ -78,11 +78,14 @@ function test()
         resumeCallback();
     };
 
-    async function setBlackbox(url) {
-        ProtocolTest.log(`Blackboxing '${url}'...`);
+    async function setBlackbox(url, options = {}) {
+        if (!options.caseSensitive)
+            url = url.toLowerCase();
+
+        ProtocolTest.log(`Blackboxing ${options.caseSensitive ? "(case sensitive) " : ""}${options.isRegex ? "(regex) " : ""}'${url}'...`);
         await InspectorProtocol.awaitCommand({
             method: "Debugger.setShouldBlackboxURL",
-            params: {url, shouldBlackbox: true},
+            params: {url, shouldBlackbox: true, ...options},
         });
     }
 
@@ -109,26 +112,286 @@ function test()
     }
 
     suite.addTestCase({
-        name: "Debugger.setShouldBlackboxURL.stepOver",
+        name: "Debugger.setShouldBlackboxURL.String.stepOver",
+        description: "Check that stepping through a blackboxed script doesn't pause.",
+        async test() {
+            let resumePromise = new Promise((resolve, reject) => {
+                resumeCallback = function() {
+                    ProtocolTest.expectThat(!pausedFunctionNames.includes("String_StepOver_Middle"), "Should not pause in 'String_StepOver_Middle'.");
+                    resolve();
+                };
+            });
+
+            let [innerSourceURL, middleSourceURL, outerSourceURL] = await Promise.all([
+                listenForSourceParsed(/String_StepOver_Inner\.js$/),
+                listenForSourceParsed(/String_StepOver_Middle\.js$/),
+                listenForSourceParsed(/String_StepOver_Outer\.js$/),
+                evaluate(`createScripts("String_StepOver")`),
+            ]);
+
+            await setBlackbox(middleSourceURL);
+            await setBreakpoint(innerSourceURL, 3); // last line of function, so it only pauses once
+            await evaluate(`String_StepOver_Outer(10)`);
+
+            ProtocolTest.newline();
+
+            await resumePromise;
+        },
+    });
+
+    suite.addTestCase({
+        name: "Debugger.setShouldBlackboxURL.String.PauseInCaller",
+        description: "Check that the debugger will pause in the caller if a breakpoint is set in a blackboxed script.",
+        async test() {
+            let resumePromise = new Promise((resolve, reject) => {
+                resumeCallback = function() {
+                    ProtocolTest.expectThat(!pausedFunctionNames.includes("String_PauseInCaller_Inner"), "Should not pause in 'String_PauseInCaller_Inner'.");
+                    resolve();
+                };
+            });
+
+            let [innerSourceURL, middleSourceURL, outerSourceURL] = await Promise.all([
+                listenForSourceParsed(/String_PauseInCaller_Inner\.js$/),
+                listenForSourceParsed(/String_PauseInCaller_Middle\.js$/),
+                listenForSourceParsed(/String_PauseInCaller_Outer\.js$/),
+                evaluate(`createScripts("String_PauseInCaller")`),
+            ]);
+
+            await setBlackbox(innerSourceURL);
+            await setBreakpoint(innerSourceURL, 2);
+            await evaluate(`String_PauseInCaller_Outer(10)`);
+
+            ProtocolTest.newline();
+
+            await resumePromise;
+        },
+    });
+
+    suite.addTestCase({
+        name: "Debugger.setShouldBlackboxURL.String.PauseInCallee",
+        description: "Check that the debugger will pause in the callee if a breakpoint is set in a blackboxed script.",
+        async test() {
+            let resumePromise = new Promise((resolve, reject) => {
+                resumeCallback = function() {
+                    ProtocolTest.expectThat(!pausedFunctionNames.includes("String_PauseInCallee_Outer"), "Should not pause in 'String_PauseInCallee_Outer'.");
+                    resolve();
+                };
+            });
+
+            let [innerSourceURL, middleSourceURL, outerSourceURL] = await Promise.all([
+                listenForSourceParsed(/String_PauseInCallee_Inner\.js$/),
+                listenForSourceParsed(/String_PauseInCallee_Middle\.js$/),
+                listenForSourceParsed(/String_PauseInCallee_Outer\.js$/),
+                evaluate(`createScripts("String_PauseInCallee")`),
+            ]);
+
+            await setBlackbox(outerSourceURL);
+            await setBreakpoint(outerSourceURL, 2);
+            await evaluate(`String_PauseInCallee_Outer(10)`);
+
+            ProtocolTest.newline();
+
+            await resumePromise;
+        },
+    });
+
+    suite.addTestCase({
+        name: "Debugger.setShouldBlackboxURL.CaseSensitiveString.stepOver",
+        description: "Check that stepping through a blackboxed script doesn't pause.",
+        async test() {
+            let resumePromise = new Promise((resolve, reject) => {
+                resumeCallback = function() {
+                    ProtocolTest.expectThat(pausedFunctionNames.includes("CaseSensitiveString_StepOver_Inner"), "Should pause in 'CaseSensitiveString_StepOver_Inner'.");
+                    ProtocolTest.expectThat(!pausedFunctionNames.includes("CaseSensitiveString_StepOver_Middle"), "Should not pause in 'CaseSensitiveString_StepOver_Middle'.");
+                    resolve();
+                };
+            });
+
+            let [innerSourceURL, middleSourceURL, outerSourceURL] = await Promise.all([
+                listenForSourceParsed(/CaseSensitiveString_StepOver_Inner\.js$/),
+                listenForSourceParsed(/CaseSensitiveString_StepOver_Middle\.js$/),
+                listenForSourceParsed(/CaseSensitiveString_StepOver_Outer\.js$/),
+                evaluate(`createScripts("CaseSensitiveString_StepOver")`),
+            ]);
+
+            await setBlackbox(innerSourceURL.toLowerCase(), {caseSensitive: true});
+            await setBlackbox(middleSourceURL, {caseSensitive: true});
+            await setBreakpoint(innerSourceURL, 3); // last line of function, so it only pauses once
+            await evaluate(`CaseSensitiveString_StepOver_Outer(10)`);
+
+            ProtocolTest.newline();
+
+            await resumePromise;
+        },
+    });
+
+    suite.addTestCase({
+        name: "Debugger.setShouldBlackboxURL.CaseSensitiveString.PauseInCaller",
+        description: "Check that the debugger will pause in the caller if a breakpoint is set in a blackboxed script.",
+        async test() {
+            let resumePromise = new Promise((resolve, reject) => {
+                resumeCallback = function() {
+                    ProtocolTest.expectThat(!pausedFunctionNames.includes("CaseSensitiveString_PauseInCaller_Inner"), "Should not pause in 'CaseSensitiveString_PauseInCaller_Inner'.");
+                    ProtocolTest.expectThat(pausedFunctionNames.includes("CaseSensitiveString_PauseInCaller_Middle"), "Should pause in 'CaseSensitiveString_PauseInCaller_Middle'.");
+                    resolve();
+                };
+            });
+
+            let [innerSourceURL, middleSourceURL, outerSourceURL] = await Promise.all([
+                listenForSourceParsed(/CaseSensitiveString_PauseInCaller_Inner\.js$/),
+                listenForSourceParsed(/CaseSensitiveString_PauseInCaller_Middle\.js$/),
+                listenForSourceParsed(/CaseSensitiveString_PauseInCaller_Outer\.js$/),
+                evaluate(`createScripts("CaseSensitiveString_PauseInCaller")`),
+            ]);
+
+            await setBlackbox(innerSourceURL, {caseSensitive: true});
+            await setBlackbox(middleSourceURL.toLowerCase(), {caseSensitive: true});
+            await setBreakpoint(innerSourceURL, 2);
+            await evaluate(`CaseSensitiveString_PauseInCaller_Outer(10)`);
+
+            ProtocolTest.newline();
+
+            await resumePromise;
+        },
+    });
+
+    suite.addTestCase({
+        name: "Debugger.setShouldBlackboxURL.CaseSensitiveString.PauseInCallee",
+        description: "Check that the debugger will pause in the callee if a breakpoint is set in a blackboxed script.",
+        async test() {
+            let resumePromise = new Promise((resolve, reject) => {
+                resumeCallback = function() {
+                    ProtocolTest.expectThat(pausedFunctionNames.includes("CaseSensitiveString_PauseInCallee_Middle"), "Should pause in 'CaseSensitiveString_PauseInCallee_Middle'.");
+                    ProtocolTest.expectThat(!pausedFunctionNames.includes("CaseSensitiveString_PauseInCallee_Outer"), "Should not pause in 'CaseSensitiveString_PauseInCallee_Outer'.");
+                    resolve();
+                };
+            });
+
+            let [innerSourceURL, middleSourceURL, outerSourceURL] = await Promise.all([
+                listenForSourceParsed(/CaseSensitiveString_PauseInCallee_Inner\.js$/),
+                listenForSourceParsed(/CaseSensitiveString_PauseInCallee_Middle\.js$/),
+                listenForSourceParsed(/CaseSensitiveString_PauseInCallee_Outer\.js$/),
+                evaluate(`createScripts("CaseSensitiveString_PauseInCallee")`),
+            ]);
+
+            await setBlackbox(middleSourceURL.toLowerCase(), {caseSensitive: true});
+            await setBlackbox(outerSourceURL, {caseSensitive: true});
+            await setBreakpoint(outerSourceURL, 2);
+            await evaluate(`CaseSensitiveString_PauseInCallee_Outer(10)`);
+
+            ProtocolTest.newline();
+
+            await resumePromise;
+        },
+    });
+
+    suite.addTestCase({
+        name: "Debugger.setShouldBlackboxURL.Regex.stepOver",
+        description: "Check that stepping through a blackboxed script doesn't pause.",
+        async test() {
+            let resumePromise = new Promise((resolve, reject) => {
+                resumeCallback = function() {
+                    ProtocolTest.expectThat(!pausedFunctionNames.includes("Regex_StepOver_Middle"), "Should not pause in 'Regex_StepOver_Middle'.");
+                    resolve();
+                };
+            });
+
+            let [innerSourceURL, middleSourceURL, outerSourceURL] = await Promise.all([
+                listenForSourceParsed(/Regex_StepOver_Inner\.js$/),
+                listenForSourceParsed(/Regex_StepOver_Middle\.js$/),
+                listenForSourceParsed(/Regex_StepOver_Outer\.js$/),
+                evaluate(`createScripts("Regex_StepOver")`),
+            ]);
+
+            await setBlackbox("Regex[ -_]StepOver[ -_]Middle\\.js$", {isRegex: true});
+            await setBreakpoint(innerSourceURL, 3); // last line of function, so it only pauses once
+            await evaluate(`Regex_StepOver_Outer(10)`);
+
+            ProtocolTest.newline();
+
+            await resumePromise;
+        },
+    });
+
+    suite.addTestCase({
+        name: "Debugger.setShouldBlackboxURL.Regex.PauseInCaller",
+        description: "Check that the debugger will pause in the caller if a breakpoint is set in a blackboxed script.",
+        async test() {
+            let resumePromise = new Promise((resolve, reject) => {
+                resumeCallback = function() {
+                    ProtocolTest.expectThat(!pausedFunctionNames.includes("Regex_PauseInCaller_Inner"), "Should not pause in 'Regex_PauseInCaller_Inner'.");
+                    resolve();
+                };
+            });
+
+            let [innerSourceURL, middleSourceURL, outerSourceURL] = await Promise.all([
+                listenForSourceParsed(/Regex_PauseInCaller_Inner\.js$/),
+                listenForSourceParsed(/Regex_PauseInCaller_Middle\.js$/),
+                listenForSourceParsed(/Regex_PauseInCaller_Outer\.js$/),
+                evaluate(`createScripts("Regex_PauseInCaller")`),
+            ]);
+
+            await setBlackbox("Regex[ -_]PauseInCaller[ -_]Inner\\.js$", {isRegex: true});
+            await setBreakpoint(innerSourceURL, 2);
+            await evaluate(`Regex_PauseInCaller_Outer(10)`);
+
+            ProtocolTest.newline();
+
+            await resumePromise;
+        },
+    });
+
+    suite.addTestCase({
+        name: "Debugger.setShouldBlackboxURL.Regex.PauseInCallee",
+        description: "Check that the debugger will pause in the callee if a breakpoint is set in a blackboxed script.",
+        async test() {
+            let resumePromise = new Promise((resolve, reject) => {
+                resumeCallback = function() {
+                    ProtocolTest.expectThat(!pausedFunctionNames.includes("Regex_PauseInCallee_Outer"), "Should not pause in 'Regex_PauseInCallee_Outer'.");
+                    resolve();
+                };
+            });
+
+            let [innerSourceURL, middleSourceURL, outerSourceURL] = await Promise.all([
+                listenForSourceParsed(/Regex_PauseInCallee_Inner\.js$/),
+                listenForSourceParsed(/Regex_PauseInCallee_Middle\.js$/),
+                listenForSourceParsed(/Regex_PauseInCallee_Outer\.js$/),
+                evaluate(`createScripts("Regex_PauseInCallee")`),
+            ]);
+
+            await setBlackbox("Regex[ -_]PauseInCallee[ -_]Outer\\.js$", {isRegex: true});
+            await setBreakpoint(outerSourceURL, 2);
+            await evaluate(`Regex_PauseInCallee_Outer(10)`);
+
+            ProtocolTest.newline();
+
+            await resumePromise;
+        },
+    });
+
+    suite.addTestCase({
+        name: "Debugger.setShouldBlackboxURL.CaseSensitiveRegex.stepOver",
         description: "Check that stepping through a blackboxed script doesn't pause.",
         async test() {
             let resumePromise = new Promise((resolve, reject) => {
                 resumeCallback = function() {
-                    ProtocolTest.expectThat(!pausedFunctionNames.includes("stepOverMiddle"), "Should not pause in 'stepOverMiddle'.");
+                    ProtocolTest.expectThat(pausedFunctionNames.includes("CaseSensitiveRegex_StepOver_Inner"), "Should pause in 'CaseSensitiveRegex_StepOver_Inner'.");
+                    ProtocolTest.expectThat(!pausedFunctionNames.includes("CaseSensitiveRegex_StepOver_Middle"), "Should not pause in 'CaseSensitiveRegex_StepOver_Middle'.");
                     resolve();
                 };
             });
 
-            let [stepOverInnerSourceURL, stepOverMiddleSourceURL] = await Promise.all([
-                listenForSourceParsed(/stepOverInner\.js$/),
-                listenForSourceParsed(/stepOverMiddle\.js$/),
-                listenForSourceParsed(/stepOverOuter\.js$/),
-                evaluate(`createScripts("stepOver")`),
+            let [innerSourceURL, middleSourceURL, outerSourceURL] = await Promise.all([
+                listenForSourceParsed(/CaseSensitiveRegex_StepOver_Inner\.js$/),
+                listenForSourceParsed(/CaseSensitiveRegex_StepOver_Middle\.js$/),
+                listenForSourceParsed(/CaseSensitiveRegex_StepOver_Outer\.js$/),
+                evaluate(`createScripts("CaseSensitiveRegex_StepOver")`),
             ]);
 
-            await setBlackbox(stepOverMiddleSourceURL);
-            await setBreakpoint(stepOverInnerSourceURL, 3); // last line of function, so it only pauses once
-            evaluate(`stepOverOuter(10)`);
+            await setBlackbox("casesensitiveregex_stepover_inner\\.js$", {caseSensitive: true, isRegex: true});
+            await setBlackbox("CaseSensitiveRegex_StepOver_Middle\\.js$", {caseSensitive: true, isRegex: true});
+            await setBreakpoint(innerSourceURL, 3); // last line of function, so it only pauses once
+            await evaluate(`CaseSensitiveRegex_StepOver_Outer(10)`);
 
             ProtocolTest.newline();
 
@@ -137,26 +400,28 @@ function test()
     });
 
     suite.addTestCase({
-        name: "Debugger.setShouldBlackboxURL.PauseInCaller",
+        name: "Debugger.setShouldBlackboxURL.CaseSensitiveRegex.PauseInCaller",
         description: "Check that the debugger will pause in the caller if a breakpoint is set in a blackboxed script.",
         async test() {
             let resumePromise = new Promise((resolve, reject) => {
                 resumeCallback = function() {
-                    ProtocolTest.expectThat(!pausedFunctionNames.includes("pauseInCallerInner"), "Should not pause in 'pauseInCallerInner'.");
+                    ProtocolTest.expectThat(!pausedFunctionNames.includes("CaseSensitiveRegex_PauseInCaller_Inner"), "Should not pause in 'CaseSensitiveRegex_PauseInCaller_Inner'.");
+                    ProtocolTest.expectThat(pausedFunctionNames.includes("CaseSensitiveRegex_PauseInCaller_Middle"), "Should pause in 'CaseSensitiveRegex_PauseInCaller_Middle'.");
                     resolve();
                 };
             });
 
-            let [pauseInCallerInnerSourceURL] = await Promise.all([
-                listenForSourceParsed(/pauseInCallerInner\.js$/),
-                listenForSourceParsed(/pauseInCallerMiddle\.js$/),
-                listenForSourceParsed(/pauseInCallerOuter\.js$/),
-                evaluate(`createScripts("pauseInCaller")`),
+            let [innerSourceURL, middleSourceURL, outerSourceURL] = await Promise.all([
+                listenForSourceParsed(/CaseSensitiveRegex_PauseInCaller_Inner\.js$/),
+                listenForSourceParsed(/CaseSensitiveRegex_PauseInCaller_Middle\.js$/),
+                listenForSourceParsed(/CaseSensitiveRegex_PauseInCaller_Outer\.js$/),
+                evaluate(`createScripts("CaseSensitiveRegex_PauseInCaller")`),
             ]);
 
-            await setBlackbox(pauseInCallerInnerSourceURL);
-            await setBreakpoint(pauseInCallerInnerSourceURL, 2);
-            evaluate(`pauseInCallerOuter(10)`);
+            await setBlackbox("casesensitiveregex_pauseincaller_inner\\.js$", {caseSensitive: true, isRegex: true});
+            await setBlackbox("CaseSensitiveRegex_PauseInCaller_middle\\.js$", {caseSensitive: true, isRegex: true});
+            await setBreakpoint(innerSourceURL, 2);
+            await evaluate(`CaseSensitiveRegex_PauseInCaller_Outer(10)`);
 
             ProtocolTest.newline();
 
@@ -165,26 +430,28 @@ function test()
     });
 
     suite.addTestCase({
-        name: "Debugger.setShouldBlackboxURL.PauseInCallee",
+        name: "Debugger.setShouldBlackboxURL.CaseSensitiveRegex.PauseInCallee",
         description: "Check that the debugger will pause in the callee if a breakpoint is set in a blackboxed script.",
         async test() {
             let resumePromise = new Promise((resolve, reject) => {
                 resumeCallback = function() {
-                    ProtocolTest.expectThat(!pausedFunctionNames.includes("pauseInCalleeOuter"), "Should not pause in 'pauseInCalleeOuter'.");
+                    ProtocolTest.expectThat(pausedFunctionNames.includes("CaseSensitiveRegex_PauseInCallee_Middle"), "Should pause in 'CaseSensitiveRegex_PauseInCallee_Middle'.");
+                    ProtocolTest.expectThat(!pausedFunctionNames.includes("CaseSensitiveRegex_PauseInCallee_Outer"), "Should not pause in 'CaseSensitiveRegex_PauseInCallee_Outer'.");
                     resolve();
                 };
             });
 
-            let [pauseInCalleeInnerSourceURL, pauseInCalleeMiddleSourceURL, pauseInCalleeOuterSourceURL] = await Promise.all([
-                listenForSourceParsed(/pauseInCalleeInner\.js$/),
-                listenForSourceParsed(/pauseInCalleeMiddle\.js$/),
-                listenForSourceParsed(/pauseInCalleeOuter\.js$/),
-                evaluate(`createScripts("pauseInCallee")`),
+            let [innerSourceURL, middleSourceURL, outerSourceURL] = await Promise.all([
+                listenForSourceParsed(/CaseSensitiveRegex_PauseInCallee_Inner\.js$/),
+                listenForSourceParsed(/CaseSensitiveRegex_PauseInCallee_Middle\.js$/),
+                listenForSourceParsed(/CaseSensitiveRegex_PauseInCallee_Outer\.js$/),
+                evaluate(`createScripts("CaseSensitiveRegex_PauseInCallee")`),
             ]);
 
-            await setBlackbox(pauseInCalleeOuterSourceURL);
-            await setBreakpoint(pauseInCalleeOuterSourceURL, 2);
-            evaluate(`pauseInCalleeOuter(10)`);
+            await setBlackbox("casesensitiveregex_pauseincallee_middle\\.js$", {caseSensitive: true, isRegex: true});
+            await setBlackbox("CaseSensitiveRegex_PauseInCallee_Outer\\.js$", {caseSensitive: true, isRegex: true});
+            await setBreakpoint(outerSourceURL, 2);
+            await evaluate(`CaseSensitiveRegex_PauseInCallee_Outer(10)`);
 
             ProtocolTest.newline();
 
index edc205b..2e82865 100644 (file)
@@ -1,3 +1,23 @@
+2019-10-11  Devin Rousso  <drousso@apple.com>
+
+        Web Inspector: Debugger: support pattern blackboxing
+        https://bugs.webkit.org/show_bug.cgi?id=198855
+
+        Reviewed by Timothy Hatcher.
+
+        Allow scripts to be blackboxed based on URL patterns (in addition to individual URLs) which
+        can be extremely useful when trying to step through unminified library/framework code.
+
+        * inspector/agents/InspectorDebuggerAgent.h:
+        * inspector/agents/InspectorDebuggerAgent.cpp:
+        (Inspector::InspectorDebuggerAgent::enable):
+        (Inspector::InspectorDebuggerAgent::setShouldBlackboxURL):
+        (Inspector::InspectorDebuggerAgent::shouldBlackboxURL const): Added.
+        (Inspector::InspectorDebuggerAgent::didParseSource):
+
+        * inspector/protocol/Debugger.json:
+        Add `caseSensitive` and `isRegex` optional boolean parameters to `setShouldBlackboxURL`.
+
 2019-10-08  Ryosuke Niwa  <rniwa@webkit.org>
 
         Make WebInspector's remote debug EventLoop code into RunLoop
index 82a005a..9c8c21b 100644 (file)
@@ -103,7 +103,7 @@ void InspectorDebuggerAgent::enable()
         if (isWebKitInjectedScript(script.sourceURL)) {
             if (!m_pauseForInternalScripts)
                 blackboxType = JSC::Debugger::BlackboxType::Ignored;
-        } else if ((!script.sourceURL.isEmpty() && m_blackboxedURLs.contains(script.sourceURL)) || (!script.url.isEmpty() && m_blackboxedURLs.contains(script.url)))
+        } else if (shouldBlackboxURL(script.sourceURL) || shouldBlackboxURL(script.url))
             blackboxType = JSC::Debugger::BlackboxType::Deferred;
         m_scriptDebugServer.setBlackboxType(sourceID, blackboxType);
     }
@@ -890,31 +890,52 @@ void InspectorDebuggerAgent::evaluateOnCallFrame(ErrorString& errorString, const
     }
 }
 
-void InspectorDebuggerAgent::setShouldBlackboxURL(ErrorString& errorString, const String& url, bool shouldBlackbox)
+void InspectorDebuggerAgent::setShouldBlackboxURL(ErrorString& errorString, const String& url, bool shouldBlackbox, const bool* optionalCaseSensitive, const bool* optionalIsRegex)
 {
     if (url.isEmpty()) {
         errorString = "URL must not be empty"_s;
         return;
     }
 
-    if (isWebKitInjectedScript(url)) {
+    bool caseSensitive = optionalCaseSensitive && *optionalCaseSensitive;
+    bool isRegex = optionalIsRegex && *optionalIsRegex;
+
+    if (!caseSensitive && !isRegex && isWebKitInjectedScript(url)) {
         errorString = "Blackboxing of internal scripts is controlled by 'Debugger.setPauseForInternalScripts'"_s;
         return;
     }
 
     if (shouldBlackbox)
-        m_blackboxedURLs.add(url);
-    else
-        m_blackboxedURLs.remove(url);
+        m_blackboxedURLs.append({ url, caseSensitive, isRegex });
 
     auto blackboxType = shouldBlackbox ? Optional<JSC::Debugger::BlackboxType>(JSC::Debugger::BlackboxType::Deferred) : WTF::nullopt;
     for (auto& [sourceID, script] : m_scripts) {
         if (isWebKitInjectedScript(script.sourceURL))
             continue;
-        if (script.sourceURL != url && script.url != url)
+        if (!shouldBlackboxURL(script.sourceURL) && !shouldBlackboxURL(script.url))
             continue;
         m_scriptDebugServer.setBlackboxType(sourceID, blackboxType);
     }
+
+    if (!shouldBlackbox) {
+        m_blackboxedURLs.removeAllMatching([&] (const auto& blackboxConfig) {
+            return blackboxConfig.url == url
+                && blackboxConfig.caseSensitive == caseSensitive
+                && blackboxConfig.isRegex == isRegex;
+        });
+    }
+}
+
+bool InspectorDebuggerAgent::shouldBlackboxURL(const String& url) const
+{
+    if (!url.isEmpty()) {
+        for (const auto& blackboxConfig : m_blackboxedURLs) {
+            auto regex = ContentSearchUtilities::createSearchRegex(blackboxConfig.url, blackboxConfig.caseSensitive, blackboxConfig.isRegex);
+            if (regex.match(url) != -1)
+                return true;
+        }
+    }
+    return false;
 }
 
 void InspectorDebuggerAgent::scriptExecutionBlockedByCSP(const String& directiveText)
@@ -971,7 +992,7 @@ void InspectorDebuggerAgent::didParseSource(JSC::SourceID sourceID, const Script
     if (isWebKitInjectedScript(sourceURL)) {
         if (!m_pauseForInternalScripts)
             m_scriptDebugServer.setBlackboxType(sourceID, JSC::Debugger::BlackboxType::Ignored);
-    } else if ((hasSourceURL && m_blackboxedURLs.contains(sourceURL)) || (!script.url.isEmpty() && m_blackboxedURLs.contains(script.url)))
+    } else if (shouldBlackboxURL(sourceURL) || shouldBlackboxURL(script.url))
         m_scriptDebugServer.setBlackboxType(sourceID, JSC::Debugger::BlackboxType::Deferred);
 
     String scriptURLForBreakpoints = hasSourceURL ? script.sourceURL : script.url;
index 9246d6c..7e87276 100644 (file)
@@ -85,7 +85,7 @@ public:
     void setPauseOnMicrotasks(ErrorString&, bool enabled) final;
     void setPauseForInternalScripts(ErrorString&, bool shouldPause) final;
     void evaluateOnCallFrame(ErrorString&, const String& callFrameId, const String& expression, const String* objectGroup, const bool* includeCommandLineAPI, const bool* doNotPauseOnExceptionsAndMuteConsole, const bool* returnByValue, const bool* generatePreview, const bool* saveResult, const bool* emulateUserGesture, RefPtr<Protocol::Runtime::RemoteObject>& result, Optional<bool>& wasThrown, Optional<int>& savedResultIndex) override;
-    void setShouldBlackboxURL(ErrorString&, const String& url, bool shouldBlackbox) final;
+    void setShouldBlackboxURL(ErrorString&, const String& url, bool shouldBlackbox, const bool* caseSensitive, const bool* isRegex) final;
 
     // ScriptDebugListener
     void didParseSource(JSC::SourceID, const Script&) final;
@@ -151,10 +151,12 @@ protected:
     virtual void didClearAsyncStackTraceData() { }
 
 private:
+    bool shouldBlackboxURL(const String&) const;
+
     Ref<JSON::ArrayOf<Protocol::Debugger::CallFrame>> currentCallFrames(const InjectedScript&);
 
     void resolveBreakpoint(const Script&, JSC::Breakpoint&);
-    void setBreakpoint(JSC::Breakpoint&, bool& existing);    
+    void setBreakpoint(JSC::Breakpoint&, bool& existing);
     void didSetBreakpoint(const JSC::Breakpoint&, const String&, const ScriptBreakpoint&);
 
     bool assertPaused(ErrorString&);
@@ -185,7 +187,13 @@ private:
     ScriptDebugServer& m_scriptDebugServer;
     InjectedScriptManager& m_injectedScriptManager;
     HashMap<JSC::SourceID, Script> m_scripts;
-    HashSet<String> m_blackboxedURLs;
+
+    struct BlackboxConfig {
+        String url;
+        bool caseSensitive { false };
+        bool isRegex { false };
+    };
+    Vector<BlackboxConfig> m_blackboxedURLs;
 
     HashSet<Listener*> m_listeners;
 
index 16d4110..8ddc4fd 100644 (file)
             "description": "Sets whether the given URL should be in the list of blackboxed scripts, which are ignored when pausing/stepping/debugging.",
             "parameters": [
                 { "name": "url", "type": "string" },
-                { "name": "shouldBlackbox", "type": "boolean" }
+                { "name": "shouldBlackbox", "type": "boolean" },
+                { "name": "caseSensitive", "type": "boolean", "optional": true, "description": "If true, <code>url</code> is case sensitive." },
+                { "name": "isRegex", "type": "boolean", "optional": true, "description": "If true, treat <code>url</code> as regular expression." }
             ]
         }
     ],
index 0e783c1..1e57fba 100644 (file)
@@ -1,5 +1,95 @@
 2019-10-11  Devin Rousso  <drousso@apple.com>
 
+        Web Inspector: Debugger: support pattern blackboxing
+        https://bugs.webkit.org/show_bug.cgi?id=198855
+
+        Reviewed by Timothy Hatcher.
+
+        Allow scripts to be blackboxed based on URL patterns (in addition to individual URLs) which
+        can be extremely useful when trying to step through unminified library/framework code.
+
+        * UserInterface/Controllers/DebuggerManager.js:
+        (WI.DebuggerManager):
+        (WI.DebuggerManager.prototype.initializeTarget):
+        (WI.DebuggerManager.prototype.blackboxTypeForSourceCode): Added.
+        (WI.DebuggerManager.prototype.get blackboxPatterns): Added.
+        (WI.DebuggerManager.prototype.setShouldBlackboxScript):
+        (WI.DebuggerManager.prototype.setShouldBlackboxPattern): Added.
+        (WI.DebuggerManager.prototype.isScriptBlackboxed): Deleted.
+        Provide a separate path for setting URL pattern blackboxes, rather than an exact/given URL.
+
+        * UserInterface/Views/SettingsTabContentView.js:
+        (WI.SettingsTabContentView):
+        (WI.SettingsTabContentView.prototype.selectBlackboxPattern): Added.
+        (WI.SettingsTabContentView.prototype.initialLayout):
+        (WI.SettingsTabContentView.prototype._createSourcesSettingsView):
+        * UserInterface/Views/BlackboxSettingsView.js: Added.
+        (WI.BlackboxSettingsView):
+        (WI.BlackboxSettingsView.prototype.selectBlackboxPattern):
+        (WI.BlackboxSettingsView.prototype.initialLayout):
+        (WI.BlackboxSettingsView.prototype._addRow):
+        * UserInterface/Views/BlackboxSettingsView.css: Added.
+        (.settings-view.blackbox > :matches(p, table)):
+        (.settings-view.blackbox > p):
+        (.settings-view.blackbox > * + p):
+        (.settings-view.blackbox > p:last-child):
+        (.settings-view.blackbox > p > .toggle-script-blackbox):
+        (.settings-view.blackbox > table):
+        (.settings-view.blackbox > table > thead th):
+        (.settings-view.blackbox > table > tbody td):
+        (.settings-view.blackbox > table > tbody td:not(.remove-blackbox)):
+        (.settings-view.blackbox > table :matches(th, td).url):
+        (.settings-view.blackbox > table > tbody td.url > .CodeMirror):
+        (.settings-view.blackbox > table :matches(th, td):matches(.case-sensitive, .remove-blackbox)):
+        (.settings-view.blackbox > table > tbody > tr:not(:hover) > td.remove-blackbox):
+        (.settings-view.blackbox > table > tbody td.remove-blackbox > .remove-blackbox-button):
+        Add a "Blackbox" pane that contains an editable table of pattern blackboxes, each having its
+        own row (code mirror for the URL regular expression and a checkbox for case sensitivity).
+
+        * UserInterface/Views/SourceCodeTreeElement.js:
+        (WI.SourceCodeTreeElement.prototype.updateStatus):
+        (WI.SourceCodeTreeElement.prototype._updateSourceCode):
+        (WI.SourceCodeTreeElement.prototype._updateToggleBlackboxImageElementState):
+        (WI.SourceCodeTreeElement.prototype._handleToggleBlackboxedImageElementClicked):
+        * UserInterface/Views/SourceCodeTreeElement.css:
+        (.tree-outline .item .status > .toggle-script-blackbox, .tree-outline:focus .item.selected .status > .toggle-script-blackbox.pattern-blackboxed): Added.
+        (.tree-outline .item .status > .toggle-script-blackbox.pattern-blackboxed): Added.
+        (.tree-outline .item .status > .toggle-script-blackbox): Added.
+        (.tree-outline .item .status > .toggle-script-blackbox:not(.pattern-blackboxed)): Added.
+        (.tree-outline .item .status > .toggle-script-blackbox:not(.url-blackboxed, .pattern-blackboxed)): Added.
+        (.tree-outline:not(.navigation-sidebar-panel-content-tree-outline) .item .status > .toggle-script-blackbox, .tree-outline .item:not(:hover) .status > .toggle-script-blackbox:not(.pattern-blackboxed, .url-blackboxed)): Added.
+        (@media (prefers-color-scheme: dark) .tree-outline .item .status > .toggle-script-blackbox): Added.
+        (@media (prefers-color-scheme: dark) .tree-outline .item .status > .toggle-script-blackbox.pattern-blackboxed): Added.
+        (@media (prefers-color-scheme: dark) .tree-outline .item .status > .toggle-script-blackbox:not(.pattern-blackboxed)): Added.
+        (.tree-outline .item .status > .toggle-script-blackboxed): Deleted.
+        (.tree-outline:not(.navigation-sidebar-panel-content-tree-outline) .item .status > .toggle-script-blackboxed, .tree-outline .item:not(:hover) .status > .toggle-script-blackboxed:not(.blackboxed)): Deleted.
+        (.tree-outline:focus .item.selected .status > .toggle-script-blackboxed): Deleted.
+        (.tree-outline .item .status > .toggle-script-blackboxed.blackboxed): Deleted.
+        (@media (prefers-color-scheme: dark) .tree-outline .item .status > .toggle-script-blackboxed): Deleted.
+        * UserInterface/Views/ShaderProgramTreeElement.css:
+        (.tree-outline .item.shader-program .status > img):
+        (.tree-outline .item.shader-program:not(:hover, .disabled) .status > img): Added.
+        (.tree-outline .item.shader-program:not(.disabled) .status > img): Added.
+        (@media (prefers-color-scheme: dark) .tree-outline .item.shader-program .status > img):
+        (.tree-outline .item.shader-program:not(:hover, .selected, .disabled) .status > img): Deleted.
+        (.tree-outline:focus .item.shader-program.selected .status > img): Deleted.
+        (.tree-outline .item.shader-program.disabled .status > img): Deleted.
+        * UserInterface/Views/ContextMenuUtilities.js:
+        (WI.appendContextMenuItemsForSourceCode):
+        * UserInterface/Images/Hide.svg:
+        Use a slightly different style for the blackbox icon if the source code's URL matches a
+        blackbox pattern. Clicking on the blackbox icon in this state will show the Settings Tab.
+
+        * UserInterface/Base/Main.js:
+        (WI.contentLoaded):
+        (WI._handleSettingsKeyboardShortcut): Added.
+        (WI.showSettingsTab): Added.
+        (WI._showSettingsTab): Deleted.
+
+        * Localizations/en.lproj/localizedStrings.js:
+
+2019-10-11  Devin Rousso  <drousso@apple.com>
+
         Web Inspector: Elements: Computed: show shorthand properties in addition to longhand ones
         https://bugs.webkit.org/show_bug.cgi?id=200554
 
index 33ccb91..78a889b 100644 (file)
@@ -92,6 +92,7 @@ localizedStrings["Add New"] = "Add New";
 localizedStrings["Add New Class"] = "Add New Class";
 localizedStrings["Add New Probe Expression"] = "Add New Probe Expression";
 localizedStrings["Add New Watch Expression"] = "Add New Watch Expression";
+localizedStrings["Add Pattern"] = "Add Pattern";
 localizedStrings["Add a Class"] = "Add a Class";
 localizedStrings["Add new breakpoint action after this action"] = "Add new breakpoint action after this action";
 localizedStrings["Add new rule"] = "Add new rule";
@@ -170,6 +171,7 @@ localizedStrings["Beacon"] = "Beacon";
 localizedStrings["Beacons"] = "Beacons";
 localizedStrings["Binary Frame"] = "Binary Frame";
 localizedStrings["Binding"] = "Binding";
+localizedStrings["Blackbox"] = "Blackbox";
 localizedStrings["Block Variables"] = "Block Variables";
 localizedStrings["Body:"] = "Body:";
 localizedStrings["Boundary"] = "Boundary";
@@ -212,6 +214,7 @@ localizedStrings["Canvases"] = "Canvases";
 /* Capture screenshot of the selected DOM node */
 localizedStrings["Capture Screenshot"] = "Capture Screenshot";
 localizedStrings["Capturing"] = "Capturing";
+localizedStrings["Case Sensitive"] = "Case Sensitive";
 /* 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. */
@@ -333,7 +336,7 @@ localizedStrings["Debugger Paused"] = "Debugger Paused";
 localizedStrings["Debugger Statement"] = "Debugger Statement";
 localizedStrings["Debugger disabled during Audit"] = "Debugger disabled during Audit";
 localizedStrings["Debugger disabled during Timeline recording"] = "Debugger disabled during Timeline recording";
-localizedStrings["Debugger:"] = "Debugger:";
+localizedStrings["Debugging:"] = "Debugging:";
 localizedStrings["Debugs"] = "Debugs";
 localizedStrings["Decoded"] = "Decoded";
 localizedStrings["Default"] = "Default";
@@ -593,6 +596,7 @@ localizedStrings["IP"] = "IP";
 localizedStrings["IP Address"] = "IP Address";
 localizedStrings["Identity"] = "Identity";
 localizedStrings["Idle"] = "Idle";
+localizedStrings["If the URL of any script matches one of the regular expression patterns below, any pauses that would have happened in that script will be deferred until execution has continued to outside of that script."] = "If the URL of any script matches one of the regular expression patterns below, any pauses that would have happened in that script will be deferred until execution has continued to outside of that script.";
 localizedStrings["Ignore"] = "Ignore";
 localizedStrings["Ignore script when debugging"] = "Ignore script when debugging";
 localizedStrings["Ignore the resource cache when loading resources"] = "Ignore the resource cache when loading resources";
@@ -879,6 +883,7 @@ 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";
+localizedStrings["Remove Blackbox"] = "Remove Blackbox";
 localizedStrings["Remove Local Override"] = "Remove Local Override";
 localizedStrings["Remove Watch Expression"] = "Remove Watch Expression";
 localizedStrings["Remove probe"] = "Remove probe";
@@ -928,6 +933,7 @@ localizedStrings["Return type for function: %s"] = "Return type for function: %s
 localizedStrings["Return value is not an object, string, or boolean"] = "Return value is not an object, string, or boolean";
 localizedStrings["Reveal"] = "Reveal";
 localizedStrings["Reveal Descendant Breakpoints"] = "Reveal Descendant Breakpoints";
+localizedStrings["Reveal blackbox pattern"] = "Reveal blackbox pattern";
 /* Open Elements tab and select this node in DOM tree */
 localizedStrings["Reveal in DOM Tree"] = "Reveal in DOM Tree";
 localizedStrings["Reveal in Elements Tab"] = "Reveal in Elements Tab";
@@ -960,7 +966,9 @@ localizedStrings["Script"] = "Script";
 localizedStrings["Script Element %d"] = "Script Element %d";
 localizedStrings["Script Entries:"] = "Script Entries:";
 localizedStrings["Script Evaluated"] = "Script Evaluated";
+localizedStrings["Script ignored when debugging due to previously set blackbox URL pattern"] = "Script ignored when debugging due to previously set blackbox URL pattern";
 localizedStrings["Scripts"] = "Scripts";
+localizedStrings["Scripts can also be individually blackboxed by clicking on the %s icon that is shown on hover."] = "Scripts can also be individually blackboxed by clicking on the %s icon that is shown on hover.";
 /* Scroll selected DOM node into view on the inspected web page */
 localizedStrings["Scroll into View"] = "Scroll into View";
 localizedStrings["Search"] = "Search";
@@ -1196,6 +1204,7 @@ localizedStrings["Type Issue"] = "Type Issue";
 localizedStrings["Type information for variable: %s"] = "Type information for variable: %s";
 localizedStrings["URL"] = "URL";
 localizedStrings["URL Breakpoint\u2026"] = "URL Breakpoint\u2026";
+localizedStrings["URL Pattern"] = "URL Pattern";
 localizedStrings["Unable to determine path to property from root"] = "Unable to determine path to property from root";
 localizedStrings["Unable to show certificate for \u201C%s\u201D"] = "Unable to show certificate for \u201C%s\u201D";
 /* Break (pause) on uncaught (unhandled) exceptions */
index dc85adf..6fb357f 100644 (file)
@@ -322,7 +322,7 @@ WI.contentLoaded = function()
 
     WI.settingsTabContentView = new WI.SettingsTabContentView;
 
-    WI._settingsKeyboardShortcut = new WI.KeyboardShortcut(WI.KeyboardShortcut.Modifier.CommandOrControl, WI.KeyboardShortcut.Key.Comma, WI._showSettingsTab);
+    WI._settingsKeyboardShortcut = new WI.KeyboardShortcut(WI.KeyboardShortcut.Modifier.CommandOrControl, WI.KeyboardShortcut.Key.Comma, WI._handleSettingsKeyboardShortcut);
 
     // Create the user interface elements.
     WI.toolbar = new WI.Toolbar(document.getElementById("toolbar"));
@@ -714,7 +714,7 @@ WI._openDefaultTab = function(event)
     WI.showNewTabTab({suppressAnimations: true});
 };
 
-WI._showSettingsTab = function(event)
+WI._handleSettingsKeyboardShortcut = function(event)
 {
     if (event.keyIdentifier === "U+002C") // ","
         WI.tabBrowser.showTabForContentView(WI.settingsTabContentView);
@@ -1145,6 +1145,14 @@ WI.isShowingLayersTab = function()
     return WI.tabBrowser.selectedTabContentView instanceof WI.LayersTabContentView;
 };
 
+WI.showSettingsTab = function(options = {})
+{
+    WI.tabBrowser.showTabForContentView(WI.settingsTabContentView);
+
+    if (options.blackboxPatternToSelect)
+        WI.settingsTabContentView.selectBlackboxPattern(options.blackboxPatternToSelect);
+};
+
 WI.indentString = function()
 {
     if (WI.settings.indentWithTabs.value)
index 835a31f..94b4c9b 100644 (file)
@@ -89,7 +89,9 @@ WI.DebuggerManager = class DebuggerManager extends WI.Object
 
         this._nextBreakpointActionIdentifier = 1;
 
-        this._blackboxURLsSetting = new WI.Setting("debugger-blackbox-urls", []);
+        this._blackboxedURLsSetting = new WI.Setting("debugger-blackboxed-urls", []);
+        this._blackboxedPatternsSetting = new WI.Setting("debugger-blackboxed-patterns", []);
+        this._blackboxedPatternDataMap = new Map;
 
         this._activeCallFrame = null;
 
@@ -161,8 +163,21 @@ WI.DebuggerManager = class DebuggerManager extends WI.Object
 
         // COMPATIBILITY (iOS 13): Debugger.setShouldBlackboxURL did not exist yet.
         if (target.DebuggerAgent.setShouldBlackboxURL) {
-            for (let url of this._blackboxURLsSetting.value)
-                target.DebuggerAgent.setShouldBlackboxURL(url, true);
+            const shouldBlackbox = true;
+
+            {
+                const caseSensitive = true;
+                for (let url of this._blackboxedURLsSetting.value)
+                    target.DebuggerAgent.setShouldBlackboxURL(url, shouldBlackbox, caseSensitive);
+            }
+
+            {
+                const isRegex = true;
+                for (let data of this._blackboxedPatternsSetting.value) {
+                    this._blackboxedPatternDataMap.set(new RegExp(data.url, !data.caseSensitive ? "i" : ""), data);
+                    target.DebuggerAgent.setShouldBlackboxURL(data.url, shouldBlackbox, data.caseSensitive, isRegex);
+                }
+            }
         }
 
         if (WI.isEngineeringBuild) {
@@ -406,9 +421,22 @@ WI.DebuggerManager = class DebuggerManager extends WI.Object
         return knownScripts;
     }
 
-    isScriptBlackboxed(sourceCode)
+    blackboxDataForSourceCode(sourceCode)
     {
-        return this._blackboxURLsSetting.value.includes(sourceCode.contentIdentifier);
+        for (let regex of this._blackboxedPatternDataMap.keys()) {
+            if (regex.test(sourceCode.contentIdentifier))
+                return {type: DebuggerManager.BlackboxType.Pattern, regex};
+        }
+
+        if (this._blackboxedURLsSetting.value.includes(sourceCode.contentIdentifier))
+            return {type: DebuggerManager.BlackboxType.URL};
+
+        return null;
+    }
+
+    get blackboxPatterns()
+    {
+        return Array.from(this._blackboxedPatternDataMap.keys());
     }
 
     setShouldBlackboxScript(sourceCode, shouldBlackbox)
@@ -417,17 +445,50 @@ WI.DebuggerManager = class DebuggerManager extends WI.Object
         console.assert(sourceCode instanceof WI.SourceCode);
         console.assert(sourceCode.contentIdentifier);
         console.assert(!isWebKitInjectedScript(sourceCode.contentIdentifier));
+        console.assert(shouldBlackbox !== ((this.blackboxDataForSourceCode(sourceCode) || {}).type === DebuggerManager.BlackboxType.URL));
 
-        this._blackboxURLsSetting.value.toggleIncludes(sourceCode.contentIdentifier, shouldBlackbox);
-        this._blackboxURLsSetting.save();
+        this._blackboxedURLsSetting.value.toggleIncludes(sourceCode.contentIdentifier, shouldBlackbox);
+        this._blackboxedURLsSetting.save();
 
+        const caseSensitive = true;
         for (let target of WI.targets) {
             // COMPATIBILITY (iOS 13): Debugger.setShouldBlackboxURL did not exist yet.
             if (target.DebuggerAgent.setShouldBlackboxURL)
-                target.DebuggerAgent.setShouldBlackboxURL(sourceCode.contentIdentifier, !!shouldBlackbox);
+                target.DebuggerAgent.setShouldBlackboxURL(sourceCode.contentIdentifier, !!shouldBlackbox, caseSensitive);
         }
 
-        this.dispatchEventToListeners(DebuggerManager.Event.BlackboxedURLsChanged);
+        this.dispatchEventToListeners(DebuggerManager.Event.BlackboxChanged);
+    }
+
+    setShouldBlackboxPattern(regex, shouldBlackbox)
+    {
+        console.assert(DebuggerManager.supportsBlackboxingScripts());
+        console.assert(regex instanceof RegExp);
+
+        if (shouldBlackbox) {
+            console.assert(!this._blackboxedPatternDataMap.has(regex));
+
+            let data = {
+                url: regex.source,
+                caseSensitive: !regex.ignoreCase,
+            };
+            this._blackboxedPatternDataMap.set(regex, data);
+            this._blackboxedPatternsSetting.value.push(data);
+        } else {
+            console.assert(this._blackboxedPatternDataMap.has(regex));
+            this._blackboxedPatternsSetting.value.remove(this._blackboxedPatternDataMap.take(regex));
+        }
+
+        this._blackboxedPatternsSetting.save();
+
+        const isRegex = true;
+        for (let target of WI.targets) {
+            // COMPATIBILITY (iOS 13): Debugger.setShouldBlackboxURL did not exist yet.
+            if (target.DebuggerAgent.setShouldBlackboxURL)
+                target.DebuggerAgent.setShouldBlackboxURL(regex.source, !!shouldBlackbox, !regex.ignoreCase, isRegex);
+        }
+
+        this.dispatchEventToListeners(DebuggerManager.Event.BlackboxChanged);
     }
 
     get asyncStackTraceDepth()
@@ -1430,7 +1491,7 @@ WI.DebuggerManager.Event = {
     BreakpointsEnabledDidChange: "debugger-manager-breakpoints-enabled-did-change",
     ProbeSetAdded: "debugger-manager-probe-set-added",
     ProbeSetRemoved: "debugger-manager-probe-set-removed",
-    BlackboxedURLsChanged: "blackboxed-urls-changed",
+    BlackboxChanged: "blackboxed-urls-changed",
 };
 
 WI.DebuggerManager.PauseReason = {
@@ -1457,3 +1518,8 @@ WI.DebuggerManager.PauseReason = {
     // COMPATIBILITY (iOS 13): DOMDebugger.EventBreakpointType.EventListener was replaced by DOMDebugger.EventBreakpointType.Listener.
     EventListener: "event-listener",
 };
+
+WI.DebuggerManager.BlackboxType = {
+    Pattern: "pattern",
+    URL: "url",
+};
index 96eb4ed..50bd709 100644 (file)
@@ -1,7 +1,37 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!-- Copyright © 2019 Apple Inc. All rights reserved. -->
-<svg xmlns="http://www.w3.org/2000/svg" id="root" version="1.1" viewBox="0 0 16 11">
-    <path fill="none" stroke="currentColor" d="M 7.942 8.124 C 6.53 8.124 5.385 6.95 5.385 5.499 C 5.385 4.05 6.53 2.875 7.942 2.875 C 9.355 2.875 10.5 4.05 10.5 5.499 C 10.5 6.95 9.355 8.124 7.942 8.124 M 7.942 1.0 C 3.923 1.0 1.001 5.499 1.001 5.499 C 1.001 5.499 3.923 9.999 7.942 9.999 C 11.962 9.999 14.884 5.499 14.884 5.499 C 14.884 5.499 11.962 1.0 7.942 1.0"/>
-    <path fill="currentColor" d="M 7.942 4.375 C 7.337 4.375 6.846 4.878 6.846 5.5 C 6.846 6.121 7.337 6.625 7.942 6.625 C 8.548 6.625 9.038 6.121 9.038 5.5 C 9.038 4.878 8.548 4.375 7.942 4.375"/>
-    <path stroke="currentColor" d="M 15.5 2 L 0.5 9"></path>
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" viewBox="0 0 16 11">
+    <style>
+        svg {
+            display: none;
+        }
+
+        svg:target {
+            display: initial;
+        }
+
+        #currentColor {
+            --color: currentColor;
+        }
+
+        #white {
+            --color: white;
+        }
+
+        #black {
+            --color: black;
+        }
+    </style>
+
+    <svg>
+        <symbol id="content">
+            <path fill="none" stroke="var(--color)" d="M 7.942 8.124 C 6.53 8.124 5.385 6.95 5.385 5.499 C 5.385 4.05 6.53 2.875 7.942 2.875 C 9.355 2.875 10.5 4.05 10.5 5.499 C 10.5 6.95 9.355 8.124 7.942 8.124 M 7.942 1.0 C 3.923 1.0 1.001 5.499 1.001 5.499 C 1.001 5.499 3.923 9.999 7.942 9.999 C 11.962 9.999 14.884 5.499 14.884 5.499 C 14.884 5.499 11.962 1.0 7.942 1.0"/>
+            <path fill="var(--color)" d="M 7.942 4.375 C 7.337 4.375 6.846 4.878 6.846 5.5 C 6.846 6.121 7.337 6.625 7.942 6.625 C 8.548 6.625 9.038 6.121 9.038 5.5 C 9.038 4.878 8.548 4.375 7.942 4.375"/>
+            <path stroke="var(--color)" d="M 15.5 2 L 0.5 9"></path>
+        </symbol>
+    </svg>
+
+    <svg id="currentColor"><use xlink:href="#content"/></svg>
+    <svg id="white"><use xlink:href="#content"/></svg>
+    <svg id="black"><use xlink:href="#content"/></svg>
 </svg>
index 9bb49b1..8f85d51 100644 (file)
@@ -37,6 +37,7 @@
     <link rel="stylesheet" href="Views/AuditTestGroupContentView.css">
     <link rel="stylesheet" href="Views/AuditTreeElement.css">
     <link rel="stylesheet" href="Views/BezierEditor.css">
+    <link rel="stylesheet" href="Views/BlackboxSettingsView.css">
     <link rel="stylesheet" href="Views/BoxModelDetailsSectionRow.css">
     <link rel="stylesheet" href="Views/BreakpointActionView.css">
     <link rel="stylesheet" href="Views/BreakpointPopoverController.css">
     <script src="Views/AuditTestGroupContentView.js"></script>
     <script src="Views/AuditTreeElement.js"></script>
     <script src="Views/BezierEditor.js"></script>
+    <script src="Views/BlackboxSettingsView.js"></script>
     <script src="Views/BoxModelDetailsSectionRow.js"></script>
     <script src="Views/BreakpointActionView.js"></script>
     <script src="Views/BreakpointTreeElement.js"></script>
diff --git a/Source/WebInspectorUI/UserInterface/Views/BlackboxSettingsView.css b/Source/WebInspectorUI/UserInterface/Views/BlackboxSettingsView.css
new file mode 100644 (file)
index 0000000..458fcd7
--- /dev/null
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+
+.settings-view.blackbox > :matches(p, table) {
+    width: 100%;
+    max-width: calc(min(50em, 100% - 40px));
+    margin: 1em auto 2em;
+}
+
+.settings-view.blackbox > p {
+    font-size: 12px;
+}
+
+.settings-view.blackbox > * + p {
+    margin-top: 2em;
+}
+
+.settings-view.blackbox > p:last-child {
+    margin-bottom: 0;
+}
+
+.settings-view.blackbox > p > .toggle-script-blackbox {
+    display: inline-block;
+    width: 15px;
+    height: 15px;
+    vertical-align: bottom;
+}
+
+.settings-view.blackbox > table {
+    border-collapse: collapse;
+}
+
+.settings-view.blackbox > table > thead th {
+    padding: 0 4px 4px;
+}
+
+.settings-view.blackbox > table > tbody td {
+    padding: 2px 0;
+}
+
+.settings-view.blackbox > table > tbody td:not(.remove-blackbox) {
+    border: 1px solid var(--border-color);
+}
+
+.settings-view.blackbox > table :matches(th, td).url {
+    text-align: start;
+}
+
+.settings-view.blackbox > table > tbody td.url > .CodeMirror {
+    min-width: 110px;
+    height: auto;
+}
+
+.settings-view.blackbox > table :matches(th, td):matches(.case-sensitive, .remove-blackbox) {
+    width: 1px;
+    white-space: nowrap;
+    text-align: center;
+}
+
+.settings-view.blackbox > table > tbody > tr:not(:hover) > td.remove-blackbox {
+    opacity: 0;
+}
+
+.settings-view.blackbox > table > tbody td.remove-blackbox > .remove-blackbox-button {
+    width: 15px;
+    height: 15px;
+    -webkit-margin-start: 4px;
+}
diff --git a/Source/WebInspectorUI/UserInterface/Views/BlackboxSettingsView.js b/Source/WebInspectorUI/UserInterface/Views/BlackboxSettingsView.js
new file mode 100644 (file)
index 0000000..5c0c542
--- /dev/null
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) 2019 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.BlackboxSettingsView = class BlackboxSettingsView extends WI.SettingsView
+{
+    constructor()
+    {
+        super("blackbox", WI.UIString("Blackbox"));
+
+        this._blackboxPatternCodeMirrorMap = new Map;
+    }
+
+    // Public
+
+    selectBlackboxPattern(regex)
+    {
+        console.assert(regex instanceof RegExp);
+        console.assert(this.didInitialLayout);
+
+        let codeMirror = this._blackboxPatternCodeMirrorMap.get(regex);
+        console.assert(codeMirror);
+        if (!codeMirror)
+            return;
+
+        codeMirror.focus();
+    }
+
+    // Protected
+
+    initialLayout()
+    {
+        super.initialLayout();
+
+        let patternBlackboxExplanationElement = this.element.appendChild(document.createElement("p"));
+        patternBlackboxExplanationElement.textContent = WI.UIString("If the URL of any script matches one of the regular expression patterns below, any pauses that would have happened in that script will be deferred until execution has continued to outside of that script.");
+
+        let table = this.element.appendChild(document.createElement("table"));
+
+        let tableHead = table.appendChild(document.createElement("thead"));
+
+        let tableHeadRow = tableHead.appendChild(document.createElement("tr"));
+
+        let urlHeaderCell = tableHeadRow.appendChild(document.createElement("th"));
+        urlHeaderCell.classList.add("url");
+        urlHeaderCell.textContent = WI.UIString("URL Pattern");
+
+        let caseSensitiveHeaderCell = tableHeadRow.appendChild(document.createElement("th"));
+        caseSensitiveHeaderCell.classList.add("case-sensitive");
+        caseSensitiveHeaderCell.textContent = WI.UIString("Case Sensitive");
+
+        let removeBlackboxHeaderCell = tableHeadRow.appendChild(document.createElement("th"));
+        removeBlackboxHeaderCell.classList.add("remove-blackbox");
+
+        this._tableBody = table.appendChild(document.createElement("tbody"));
+
+        for (let regex of WI.debuggerManager.blackboxPatterns)
+            this._addRow(regex);
+
+        if (!this._tableBody.children.length)
+            this._addRow(null);
+
+        let tableFoot = table.appendChild(document.createElement("tfoot"));
+
+        let tableFooterRow = tableFoot.appendChild(document.createElement("tr"));
+
+        let addBlackboxCell = tableFooterRow.appendChild(document.createElement("td"));
+
+        let addBlackboxButton = addBlackboxCell.appendChild(document.createElement("button"));
+        addBlackboxButton.textContent = WI.UIString("Add Pattern");
+        addBlackboxButton.addEventListener("click", (event) => {
+            for (let [regex, codeMirror] of this._blackboxPatternCodeMirrorMap) {
+                if (!regex) {
+                    codeMirror.focus();
+                    return;
+                }
+            }
+
+            this._addRow(null);
+        });
+
+        let individualBlackboxExplanationElement = this.element.appendChild(document.createElement("p"));
+        let blackboxIconElement = WI.ImageUtilities.useSVGSymbol("Images/Hide.svg#currentColor", "toggle-script-blackbox", WI.UIString("Ignore script when debugging"));
+        String.format(WI.UIString("Scripts can also be individually blackboxed by clicking on the %s icon that is shown on hover."), [blackboxIconElement], String.standardFormatters, individualBlackboxExplanationElement, (a, b) => {
+            a.append(b);
+            return a;
+        });
+    }
+
+    // Private
+
+    _addRow(regex)
+    {
+        let tableBodyRow = this._tableBody.appendChild(document.createElement("tr"));
+
+        let urlBodyCell = tableBodyRow.appendChild(document.createElement("td"));
+        urlBodyCell.classList.add("url");
+
+        let urlCodeMirror = WI.CodeMirrorEditor.create(urlBodyCell, {
+            extraKeys: {"Tab": false, "Shift-Tab": false},
+            lineWrapping: false,
+            matchBrackets: false,
+            mode: "text/x-regex",
+            placeholder: WI.UIString("Regular Expression"),
+            scrollbarStyle: null,
+            value: regex ? regex.source : "",
+        });
+        this._blackboxPatternCodeMirrorMap.set(regex, urlCodeMirror);
+
+        this.needsLayout();
+
+        let caseSensitiveBodyCell = tableBodyRow.appendChild(document.createElement("td"));
+        caseSensitiveBodyCell.classList.add("case-sensitive");
+
+        let caseSensitiveCheckbox = caseSensitiveBodyCell.appendChild(document.createElement("input"));
+        caseSensitiveCheckbox.type = "checkbox";
+        caseSensitiveCheckbox.checked = regex ? !regex.ignoreCase : true;
+
+        let removeBlackboxBodyCell = tableBodyRow.appendChild(document.createElement("td"));
+        removeBlackboxBodyCell.classList.add("remove-blackbox");
+
+        let removeBlackboxButton = removeBlackboxBodyCell.appendChild(WI.ImageUtilities.useSVGSymbol("Images/NavigationItemTrash.svg", "remove-blackbox-button", WI.UIString("Remove Blackbox")));
+        removeBlackboxButton.addEventListener("click", (event) => {
+            if (regex)
+                WI.debuggerManager.setShouldBlackboxPattern(regex, false);
+            regex = null;
+
+            this._blackboxPatternCodeMirrorMap.delete(regex);
+
+            tableBodyRow.remove();
+
+            if (!this._tableBody.children.length)
+                this._addRow(null);
+        });
+
+        let update = () => {
+            let url = urlCodeMirror.getValue();
+
+            if (regex) {
+                if (regex.source === url && regex.ignoreCase !== caseSensitiveCheckbox.checked)
+                    return;
+
+                WI.debuggerManager.setShouldBlackboxPattern(regex, false);
+            }
+
+            this._blackboxPatternCodeMirrorMap.delete(regex);
+
+            regex = url ? new RegExp(url, !caseSensitiveCheckbox.checked ? "i" : "") : null;
+            if (regex)
+                WI.debuggerManager.setShouldBlackboxPattern(regex, true);
+
+            console.assert(regex || !this._blackboxPatternCodeMirrorMap.has(regex));
+            this._blackboxPatternCodeMirrorMap.set(regex, urlCodeMirror);
+        };
+        urlCodeMirror.addKeyMap({
+            "Enter": update,
+            "Esc": update,
+        });
+        urlCodeMirror.on("blur", update);
+        caseSensitiveCheckbox.addEventListener("change", update);
+
+        if (!regex)
+            urlCodeMirror.focus();
+    }
+};
index 1798d0a..157cf36 100644 (file)
@@ -85,10 +85,18 @@ WI.appendContextMenuItemsForSourceCode = function(contextMenu, sourceCodeOrLocat
     contextMenu.appendSeparator();
 
     if (sourceCode.supportsScriptBlackboxing) {
-        let isBlackboxed = WI.debuggerManager.isScriptBlackboxed(sourceCode);
-        contextMenu.appendItem(isBlackboxed ? WI.UIString("Include script when debugging") : WI.UIString("Ignore script when debugging"), () => {
-            WI.debuggerManager.setShouldBlackboxScript(sourceCode, !isBlackboxed);
-        });
+        let blackboxData = WI.debuggerManager.blackboxDataForSourceCode(sourceCode);
+        if (blackboxData && blackboxData.type === WI.DebuggerManager.BlackboxType.Pattern) {
+            contextMenu.appendItem(WI.UIString("Reveal blackbox pattern"), () => {
+                WI.showSettingsTab({
+                    blackboxPatternToSelect: blackboxData.regex,
+                });
+            });
+        } else {
+            contextMenu.appendItem(blackboxData ? WI.UIString("Include script when debugging") : WI.UIString("Ignore script when debugging"), () => {
+                WI.debuggerManager.setShouldBlackboxScript(sourceCode, !blackboxData);
+            });
+        }
     }
 
     contextMenu.appendSeparator();
index 9f9850a..d138539 100644 (file)
@@ -153,10 +153,22 @@ WI.SettingsTabContentView = class SettingsTabContentView extends WI.TabContentVi
         }
     }
 
+    selectBlackboxPattern(regex)
+    {
+        console.assert(regex instanceof RegExp);
+
+        console.assert(this._blackboxSettingsView);
+        this.selectedSettingsView = this._blackboxSettingsView;
+
+        this._blackboxSettingsView.selectBlackboxPattern(regex);
+    }
+
     // Protected
 
     initialLayout()
     {
+        super.initialLayout();
+
         this._navigationBar = new WI.NavigationBar;
         this._navigationBar.addEventListener(WI.NavigationBar.Event.NavigationItemSelected, this._navigationItemSelected, this);
         this.addSubview(this._navigationBar);
@@ -165,6 +177,12 @@ WI.SettingsTabContentView = class SettingsTabContentView extends WI.TabContentVi
         this._createElementsSettingsView();
         this._createSourcesSettingsView();
         this._createConsoleSettingsView();
+
+        if (WI.DebuggerManager.supportsBlackboxingScripts()) {
+            this._blackboxSettingsView = new WI.BlackboxSettingsView;
+            this.addSettingsView(this._blackboxSettingsView);
+        }
+
         this._createExperimentalSettingsView();
 
         if (WI.isEngineeringBuild) {
@@ -251,7 +269,7 @@ WI.SettingsTabContentView = class SettingsTabContentView extends WI.TabContentVi
     {
         let sourcesSettingsView = new WI.SettingsView("sources", WI.UIString("Sources"));
 
-        sourcesSettingsView.addSetting(WI.UIString("Debugger:"), WI.settings.showScopeChainOnPause, WI.UIString("Show Scope Chain on pause"));
+        sourcesSettingsView.addSetting(WI.UIString("Debugging:"), WI.settings.showScopeChainOnPause, WI.UIString("Show Scope Chain on pause"));
 
         sourcesSettingsView.addSeparator();
 
index 8d23ea9..1103b9c 100644 (file)
 
  .tree-outline .item.shader-program .status > img {
     width: 18px;
-    margin-top: 2px;
-    content: url(../Images/Hide.svg);
+    height: 16px;
+    padding: 0 2px;
+    content: url(../Images/Hide.svg#white);
+    background-color: grey;
+    border-radius: 3px;
 }
 
-.tree-outline .item.shader-program:not(:hover, .selected, .disabled) .status > img {
+.tree-outline .item.shader-program:not(:hover, .disabled) .status > img {
     display: none;
 }
 
-.tree-outline:focus .item.shader-program.selected .status > img {
-    filter: invert();
-}
-
-.tree-outline .item.shader-program.disabled .status > img {
-    opacity: 0.5;
+.tree-outline .item.shader-program:not(.disabled) .status > img {
+    opacity: 0.7;
 }
 
 @media (prefers-color-scheme: dark) {
     .tree-outline .item.shader-program .status > img {
-        filter: invert();
+        content: url(../Images/Hide.svg#black);
+        background-color: hsl(0, 0%, 70%);
     }
 }
+
index 842d987..9513a80 100644 (file)
  * THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-.tree-outline .item .status > .toggle-script-blackboxed {
+.tree-outline .item .status > .toggle-script-blackbox,
+.tree-outline:focus .item.selected .status > .toggle-script-blackbox.pattern-blackboxed {
+    content: url(../Images/Hide.svg#white);
+}
+
+.tree-outline .item .status > .toggle-script-blackbox.pattern-blackboxed {
+    content: url(../Images/Hide.svg#black);
+}
+
+.tree-outline .item .status > .toggle-script-blackbox {
     width: 18px;
     margin-top: 2px;
-    content: url(../Images/Hide.svg);
 }
 
-.tree-outline:not(.navigation-sidebar-panel-content-tree-outline) .item .status > .toggle-script-blackboxed,
-.tree-outline .item:not(:hover) .status > .toggle-script-blackboxed:not(.blackboxed) {
-    display: none;
+.tree-outline .item .status > .toggle-script-blackbox:not(.pattern-blackboxed) {
+    height: 16px;
+    margin-top: 0;
+    padding: 0 2px;
+    background-color: grey;
+    border-radius: 3px;
 }
 
-.tree-outline:focus .item.selected .status > .toggle-script-blackboxed {
-    filter: invert();
+.tree-outline .item .status > .toggle-script-blackbox:not(.url-blackboxed, .pattern-blackboxed) {
+    opacity: 0.7;
 }
 
-.tree-outline .item .status > .toggle-script-blackboxed.blackboxed {
-    opacity: 0.5;
+.tree-outline:not(.navigation-sidebar-panel-content-tree-outline) .item .status > .toggle-script-blackbox,
+.tree-outline .item:not(:hover) .status > .toggle-script-blackbox:not(.pattern-blackboxed, .url-blackboxed) {
+    display: none;
 }
 
 @media (prefers-color-scheme: dark) {
-    .tree-outline .item .status > .toggle-script-blackboxed {
-        filter: invert();
+    .tree-outline .item .status > .toggle-script-blackbox {
+        content: url(../Images/Hide.svg#black);
+    }
+
+    .tree-outline .item .status > .toggle-script-blackbox.pattern-blackboxed {
+        content: url(../Images/Hide.svg#white);
+    }
+
+    .tree-outline .item .status > .toggle-script-blackbox:not(.pattern-blackboxed) {
+        background-color: hsl(0, 0%, 70%);
     }
 }
index 2150479..3097aa9 100644 (file)
@@ -162,7 +162,7 @@ WI.SourceCodeTreeElement = class SourceCodeTreeElement extends WI.FolderizedTree
         if (this._sourceCode.supportsScriptBlackboxing) {
             if (!this._toggleBlackboxedImageElement) {
                 this._toggleBlackboxedImageElement = document.createElement("img");
-                this._toggleBlackboxedImageElement.classList.add("toggle-script-blackboxed");
+                this._toggleBlackboxedImageElement.classList.add("toggle-script-blackbox");
                 this._toggleBlackboxedImageElement.addEventListener("click", this._handleToggleBlackboxedImageElementClicked.bind(this));
             }
 
@@ -195,9 +195,9 @@ WI.SourceCodeTreeElement = class SourceCodeTreeElement extends WI.FolderizedTree
         let newSupportsScriptBlackboxing = this._sourceCode.supportsScriptBlackboxing;
         if (oldSupportsScriptBlackboxing !== newSupportsScriptBlackboxing) {
             if (newSupportsScriptBlackboxing)
-                WI.debuggerManager.addEventListener(WI.DebuggerManager.Event.BlackboxedURLsChanged, this._updateToggleBlackboxImageElementState, this);
+                WI.debuggerManager.addEventListener(WI.DebuggerManager.Event.BlackboxChanged, this._updateToggleBlackboxImageElementState, this);
             else
-                WI.debuggerManager.removeEventListener(WI.DebuggerManager.Event.BlackboxedURLsChanged, this._updateToggleBlackboxImageElementState, this);
+                WI.debuggerManager.removeEventListener(WI.DebuggerManager.Event.BlackboxChanged, this._updateToggleBlackboxImageElementState, this);
         }
 
         this.updateSourceMapResources();
@@ -209,15 +209,38 @@ WI.SourceCodeTreeElement = class SourceCodeTreeElement extends WI.FolderizedTree
 
     _updateToggleBlackboxImageElementState()
     {
-        let isBlackboxed = WI.debuggerManager.isScriptBlackboxed(this._sourceCode);
-        this._toggleBlackboxedImageElement.classList.toggle("blackboxed", isBlackboxed);
-        this._toggleBlackboxedImageElement.title = isBlackboxed ? WI.UIString("Include script when debugging") : WI.UIString("Ignore script when debugging");
+        let blackboxData = WI.debuggerManager.blackboxDataForSourceCode(this._sourceCode);
+
+        this._toggleBlackboxedImageElement.classList.toggle("pattern-blackboxed", blackboxData && blackboxData.type === WI.DebuggerManager.BlackboxType.Pattern);
+        this._toggleBlackboxedImageElement.classList.toggle("url-blackboxed", blackboxData && blackboxData.type === WI.DebuggerManager.BlackboxType.URL);
+
+        if (blackboxData) {
+            switch (blackboxData.type) {
+            case WI.DebuggerManager.BlackboxType.Pattern:
+                this._toggleBlackboxedImageElement.title = WI.UIString("Script ignored when debugging due to previously set blackbox URL pattern");
+                break;
+
+            case WI.DebuggerManager.BlackboxType.URL:
+                this._toggleBlackboxedImageElement.title = WI.UIString("Include script when debugging");
+                break;
+            }
+
+            console.assert(this._toggleBlackboxedImageElement.title);
+        } else
+            this._toggleBlackboxedImageElement.title = WI.UIString("Ignore script when debugging");
     }
 
     _handleToggleBlackboxedImageElementClicked(event)
     {
-        let isBlackboxed = WI.debuggerManager.isScriptBlackboxed(this._sourceCode);
-        WI.debuggerManager.setShouldBlackboxScript(this._sourceCode, !isBlackboxed);
+        let blackboxData = WI.debuggerManager.blackboxDataForSourceCode(this._sourceCode);
+        if (blackboxData && blackboxData.type === WI.DebuggerManager.BlackboxType.Pattern) {
+            WI.showSettingsTab({
+                blackboxPatternToSelect: blackboxData.regex,
+            });
+            return;
+        }
+
+        WI.debuggerManager.setShouldBlackboxScript(this._sourceCode, !blackboxData);
 
         this._updateToggleBlackboxImageElementState();
     }