Web Inspector: Debugger: support pattern blackboxing
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: http://svn.webkit.org/repository/webkit/trunk@251039 268f45cc-cd09-0410-ab3c-d52691b4dbfc
diff --git a/LayoutTests/ChangeLog b/LayoutTests/ChangeLog
index 217691a..229631e 100644
--- a/LayoutTests/ChangeLog
+++ b/LayoutTests/ChangeLog
@@ -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
 
diff --git a/LayoutTests/inspector/debugger/setShouldBlackboxURL-expected.txt b/LayoutTests/inspector/debugger/setShouldBlackboxURL-expected.txt
index c68ac18..a06d926 100644
--- a/LayoutTests/inspector/debugger/setShouldBlackboxURL-expected.txt
+++ b/LayoutTests/inspector/debugger/setShouldBlackboxURL-expected.txt
@@ -2,80 +2,305 @@
 
 
 == 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": "pauseInCallerInner.js:2:0"
+    "breakpointId": "String_PauseInCaller_Inner.js:2:0"
   }
 }
 Stepping over...
 
-Paused in 'pauseInCallerOuter:3:1'.
-Reason: 'other'
+PAUSED: 'other' at 'String_PauseInCaller_Outer:3:1'.
 Stepping over...
 
 Resuming...
 PASS: Resumed.
-PASS: Should not pause in 'pauseInCallerInner'.
+PASS: Should not pause in 'String_PauseInCaller_Inner'.
 
--- 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.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": "pauseInCalleeOuter.js:2:0"
+    "breakpointId": "String_PauseInCallee_Outer.js:2:0"
   }
 }
 Stepping over...
 
-Paused in 'pauseInCalleeMiddle:3:1'.
-Reason: 'other'
+PAUSED: 'other' at 'String_PauseInCallee_Middle:3:1'.
 Stepping over...
 
 Resuming...
 PASS: Resumed.
-PASS: Should not pause in 'pauseInCalleeOuter'.
+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": "CaseSensitiveString_PauseInCaller_Inner.js:2:0"
+  }
+}
+Stepping over...
+
+PAUSED: 'other' at 'CaseSensitiveString_PauseInCaller_Outer:3:1'.
+Stepping over...
+
+Resuming...
+PASS: Resumed.
+PASS: Should not pause in 'CaseSensitiveString_PauseInCaller_Inner'.
+PASS: Should pause in 'CaseSensitiveString_PauseInCaller_Middle'.
+
+
+-- 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": "CaseSensitiveString_PauseInCallee_Outer.js:2:0"
+  }
+}
+Stepping over...
+
+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 '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.
diff --git a/LayoutTests/inspector/debugger/setShouldBlackboxURL.html b/LayoutTests/inspector/debugger/setShouldBlackboxURL.html
index 9558f75..e718821 100644
--- a/LayoutTests/inspector/debugger/setShouldBlackboxURL.html
+++ b/LayoutTests/inspector/debugger/setShouldBlackboxURL.html
@@ -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 @@
     };
 
     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 @@
             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 @@
         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,26 @@
     }
 
     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("stepOverMiddle"), "Should not pause in 'stepOverMiddle'.");
+                    ProtocolTest.expectThat(!pausedFunctionNames.includes("String_StepOver_Middle"), "Should not pause in 'String_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(/String_StepOver_Inner\.js$/),
+                listenForSourceParsed(/String_StepOver_Middle\.js$/),
+                listenForSourceParsed(/String_StepOver_Outer\.js$/),
+                evaluate(`createScripts("String_StepOver")`),
             ]);
 
-            await setBlackbox(stepOverMiddleSourceURL);
-            await setBreakpoint(stepOverInnerSourceURL, 3); // last line of function, so it only pauses once
-            evaluate(`stepOverOuter(10)`);
+            await setBlackbox(middleSourceURL);
+            await setBreakpoint(innerSourceURL, 3); // last line of function, so it only pauses once
+            await evaluate(`String_StepOver_Outer(10)`);
 
             ProtocolTest.newline();
 
@@ -137,26 +140,26 @@
     });
 
     suite.addTestCase({
-        name: "Debugger.setShouldBlackboxURL.PauseInCaller",
+        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("pauseInCallerInner"), "Should not pause in 'pauseInCallerInner'.");
+                    ProtocolTest.expectThat(!pausedFunctionNames.includes("String_PauseInCaller_Inner"), "Should not pause in 'String_PauseInCaller_Inner'.");
                     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(/String_PauseInCaller_Inner\.js$/),
+                listenForSourceParsed(/String_PauseInCaller_Middle\.js$/),
+                listenForSourceParsed(/String_PauseInCaller_Outer\.js$/),
+                evaluate(`createScripts("String_PauseInCaller")`),
             ]);
 
-            await setBlackbox(pauseInCallerInnerSourceURL);
-            await setBreakpoint(pauseInCallerInnerSourceURL, 2);
-            evaluate(`pauseInCallerOuter(10)`);
+            await setBlackbox(innerSourceURL);
+            await setBreakpoint(innerSourceURL, 2);
+            await evaluate(`String_PauseInCaller_Outer(10)`);
 
             ProtocolTest.newline();
 
@@ -165,26 +168,290 @@
     });
 
     suite.addTestCase({
-        name: "Debugger.setShouldBlackboxURL.PauseInCallee",
+        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("pauseInCalleeOuter"), "Should not pause in 'pauseInCalleeOuter'.");
+                    ProtocolTest.expectThat(!pausedFunctionNames.includes("String_PauseInCallee_Outer"), "Should not pause in 'String_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(/String_PauseInCallee_Inner\.js$/),
+                listenForSourceParsed(/String_PauseInCallee_Middle\.js$/),
+                listenForSourceParsed(/String_PauseInCallee_Outer\.js$/),
+                evaluate(`createScripts("String_PauseInCallee")`),
             ]);
 
-            await setBlackbox(pauseInCalleeOuterSourceURL);
-            await setBreakpoint(pauseInCalleeOuterSourceURL, 2);
-            evaluate(`pauseInCalleeOuter(10)`);
+            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("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 [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("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();
+
+            await resumePromise;
+        },
+    });
+
+    suite.addTestCase({
+        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("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 [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("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();
+
+            await resumePromise;
+        },
+    });
+
+    suite.addTestCase({
+        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("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 [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("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();
 
diff --git a/Source/JavaScriptCore/ChangeLog b/Source/JavaScriptCore/ChangeLog
index edc205b..2e82865 100644
--- a/Source/JavaScriptCore/ChangeLog
+++ b/Source/JavaScriptCore/ChangeLog
@@ -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
diff --git a/Source/JavaScriptCore/inspector/agents/InspectorDebuggerAgent.cpp b/Source/JavaScriptCore/inspector/agents/InspectorDebuggerAgent.cpp
index 82a005a..9c8c21b 100644
--- a/Source/JavaScriptCore/inspector/agents/InspectorDebuggerAgent.cpp
+++ b/Source/JavaScriptCore/inspector/agents/InspectorDebuggerAgent.cpp
@@ -103,7 +103,7 @@
         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::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 @@
     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;
diff --git a/Source/JavaScriptCore/inspector/agents/InspectorDebuggerAgent.h b/Source/JavaScriptCore/inspector/agents/InspectorDebuggerAgent.h
index 9246d6c..7e872766 100644
--- a/Source/JavaScriptCore/inspector/agents/InspectorDebuggerAgent.h
+++ b/Source/JavaScriptCore/inspector/agents/InspectorDebuggerAgent.h
@@ -85,7 +85,7 @@
     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 @@
     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 @@
     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;
 
diff --git a/Source/JavaScriptCore/inspector/protocol/Debugger.json b/Source/JavaScriptCore/inspector/protocol/Debugger.json
index 16d4110..8ddc4fd 100644
--- a/Source/JavaScriptCore/inspector/protocol/Debugger.json
+++ b/Source/JavaScriptCore/inspector/protocol/Debugger.json
@@ -300,7 +300,9 @@
             "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." }
             ]
         }
     ],
diff --git a/Source/WebInspectorUI/ChangeLog b/Source/WebInspectorUI/ChangeLog
index 0e783c1..1e57fba 100644
--- a/Source/WebInspectorUI/ChangeLog
+++ b/Source/WebInspectorUI/ChangeLog
@@ -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
 
diff --git a/Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js b/Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js
index 33ccb91..78a889b 100644
--- a/Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js
+++ b/Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js
@@ -92,6 +92,7 @@
 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["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 @@
 /* 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 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 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["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 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 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 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 */
diff --git a/Source/WebInspectorUI/UserInterface/Base/Main.js b/Source/WebInspectorUI/UserInterface/Base/Main.js
index dc85adf..6fb357f 100644
--- a/Source/WebInspectorUI/UserInterface/Base/Main.js
+++ b/Source/WebInspectorUI/UserInterface/Base/Main.js
@@ -322,7 +322,7 @@
 
     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.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 @@
     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)
diff --git a/Source/WebInspectorUI/UserInterface/Controllers/DebuggerManager.js b/Source/WebInspectorUI/UserInterface/Controllers/DebuggerManager.js
index 835a31f..94b4c9b 100644
--- a/Source/WebInspectorUI/UserInterface/Controllers/DebuggerManager.js
+++ b/Source/WebInspectorUI/UserInterface/Controllers/DebuggerManager.js
@@ -89,7 +89,9 @@
 
         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 @@
 
         // 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 @@
         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 @@
         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 @@
     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 @@
     // COMPATIBILITY (iOS 13): DOMDebugger.EventBreakpointType.EventListener was replaced by DOMDebugger.EventBreakpointType.Listener.
     EventListener: "event-listener",
 };
+
+WI.DebuggerManager.BlackboxType = {
+    Pattern: "pattern",
+    URL: "url",
+};
diff --git a/Source/WebInspectorUI/UserInterface/Images/Hide.svg b/Source/WebInspectorUI/UserInterface/Images/Hide.svg
index 96eb4ed..50bd709 100644
--- a/Source/WebInspectorUI/UserInterface/Images/Hide.svg
+++ b/Source/WebInspectorUI/UserInterface/Images/Hide.svg
@@ -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>
diff --git a/Source/WebInspectorUI/UserInterface/Main.html b/Source/WebInspectorUI/UserInterface/Main.html
index 9bb49b1..8f85d51 100644
--- a/Source/WebInspectorUI/UserInterface/Main.html
+++ b/Source/WebInspectorUI/UserInterface/Main.html
@@ -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">
@@ -599,6 +600,7 @@
     <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
index 0000000..458fcd7
--- /dev/null
+++ b/Source/WebInspectorUI/UserInterface/Views/BlackboxSettingsView.css
@@ -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
index 0000000..5c0c542
--- /dev/null
+++ b/Source/WebInspectorUI/UserInterface/Views/BlackboxSettingsView.js
@@ -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();
+    }
+};
diff --git a/Source/WebInspectorUI/UserInterface/Views/ContextMenuUtilities.js b/Source/WebInspectorUI/UserInterface/Views/ContextMenuUtilities.js
index 1798d0a..157cf36 100644
--- a/Source/WebInspectorUI/UserInterface/Views/ContextMenuUtilities.js
+++ b/Source/WebInspectorUI/UserInterface/Views/ContextMenuUtilities.js
@@ -85,10 +85,18 @@
     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();
diff --git a/Source/WebInspectorUI/UserInterface/Views/SettingsTabContentView.js b/Source/WebInspectorUI/UserInterface/Views/SettingsTabContentView.js
index 9f9850a..d138539 100644
--- a/Source/WebInspectorUI/UserInterface/Views/SettingsTabContentView.js
+++ b/Source/WebInspectorUI/UserInterface/Views/SettingsTabContentView.js
@@ -153,10 +153,22 @@
         }
     }
 
+    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 @@
         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 @@
     {
         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();
 
diff --git a/Source/WebInspectorUI/UserInterface/Views/ShaderProgramTreeElement.css b/Source/WebInspectorUI/UserInterface/Views/ShaderProgramTreeElement.css
index 8d23ea9..1103b9c 100644
--- a/Source/WebInspectorUI/UserInterface/Views/ShaderProgramTreeElement.css
+++ b/Source/WebInspectorUI/UserInterface/Views/ShaderProgramTreeElement.css
@@ -25,24 +25,25 @@
 
  .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%);
     }
 }
+
diff --git a/Source/WebInspectorUI/UserInterface/Views/SourceCodeTreeElement.css b/Source/WebInspectorUI/UserInterface/Views/SourceCodeTreeElement.css
index 842d987..9513a80 100644
--- a/Source/WebInspectorUI/UserInterface/Views/SourceCodeTreeElement.css
+++ b/Source/WebInspectorUI/UserInterface/Views/SourceCodeTreeElement.css
@@ -23,27 +23,47 @@
  * THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-.tree-outline .item .status > .toggle-script-blackboxed {
-    width: 18px;
-    margin-top: 2px;
-    content: url(../Images/Hide.svg);
+.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:not(.navigation-sidebar-panel-content-tree-outline) .item .status > .toggle-script-blackboxed,
-.tree-outline .item:not(:hover) .status > .toggle-script-blackboxed:not(.blackboxed) {
+.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;
+}
+
+.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 .item .status > .toggle-script-blackbox:not(.url-blackboxed, .pattern-blackboxed) {
+    opacity: 0.7;
+}
+
+.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;
 }
 
-.tree-outline:focus .item.selected .status > .toggle-script-blackboxed {
-    filter: invert();
-}
-
-.tree-outline .item .status > .toggle-script-blackboxed.blackboxed {
-    opacity: 0.5;
-}
-
 @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%);
     }
 }
diff --git a/Source/WebInspectorUI/UserInterface/Views/SourceCodeTreeElement.js b/Source/WebInspectorUI/UserInterface/Views/SourceCodeTreeElement.js
index 2150479..3097aa9 100644
--- a/Source/WebInspectorUI/UserInterface/Views/SourceCodeTreeElement.js
+++ b/Source/WebInspectorUI/UserInterface/Views/SourceCodeTreeElement.js
@@ -162,7 +162,7 @@
         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 @@
         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 @@
 
     _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();
     }