Title: [290720] trunk
Revision
290720
Author
[email protected]
Date
2022-03-01 22:42:27 -0800 (Tue, 01 Mar 2022)

Log Message

Web Inspector: add a switch to control whether breakpoint evaluations (condition, ignore count, actions) are also affected by script blackboxing
https://bugs.webkit.org/show_bug.cgi?id=235274

Reviewed by Patrick Angle.

Source/_javascript_Core:

Being able to defer breakpoint evaluations until the next actual pause can sometimes be far
more useful than doing the breakpoint evaluations at the breakpoint's original location.

As an example, configuring the All Events breakpoint with a `console.trace()` action and
auto-continue enabled would not provide much useful information without also blackboxing
breakpoint evaluations as the original pause location of the All Events breakpoint is on the
first line of the event handler, meaning that if the script containing the event handler is
blackboxed then the `console.trace()` would still only show that location even though the
Sources Tab would show the first line of code outside of that script (due to the blackbox).
Being able to also blackbox breakpoint evaluations would instead cause the `console.trace()`
action to have the same output as the Sources Tab, since it's evaluation would be deferred
until execution actually paused, which is further inside the event handler.

* debugger/Debugger.cpp:
(JSC::Debugger::Debugger):
(JSC::Debugger::didHitBreakpoint):
(JSC::Debugger::evaluateBreakpointCondition):
(JSC::Debugger::continueProgram):
(JSC::Debugger::pauseIfNeeded):
(JSC::Debugger::didExecuteProgram):
(JSC::Debugger::setBlackboxBreakpointEvaluations): Added.
Keep track of every `Breakpoint` wants to pause but is deferred due to blackboxing.
Depending on whether breakpoint evaluations are also blackboxed (based on the flag set via
the piping code/logic below), check if the script attempting to be paused in is blackboxed
before or after handling breakpoint evaluations.
Side effects of this change are:
- breakpoint conditions are now evaluated right before that breakpoint's actions, instead of
  evaluating all breakpoint conditions before all breakpoint actions
- if breakpoint evaluations are blackboxed, more than two breakpoints can now be evaluated
during a single pause attempt depending on how may deferrals (with breakpoints) happened

* debugger/Debugger.h:
(JSC::Debugger::Observer::didDeferBreakpointPause): Added.
Add a way to notify `InspectorDebuggerAgent` of deferred breakpoint pauses so that the
correct `originalReason` will be used after a deferred pause.

* inspector/protocol/Debugger.json:
* inspector/agents/InspectorDebuggerAgent.h:
* inspector/agents/InspectorDebuggerAgent.cpp:
(Inspector::InspectorDebuggerAgent::setBlackboxBreakpointEvaluations): Added.
(Inspector::InspectorDebuggerAgent::didDeferBreakpointPause): Added.
Add a `setBlackboxBreakpointEvaluations` command that passes directly to `Debugger`.

Source/WebInspectorUI:

Being able to defer breakpoint evaluations until the next actual pause can sometimes be far
more useful than doing the breakpoint evaluations at the breakpoint's original location.

As an example, configuring the All Events breakpoint with a `console.trace()` action and
auto-continue enabled would not provide much useful information without also blackboxing
breakpoint evaluations as the original pause location of the All Events breakpoint is on the
first line of the event handler, meaning that if the script containing the event handler is
blackboxed then the `console.trace()` would still only show that location even though the
Sources Tab would show the first line of code outside of that script (due to the blackbox).
Being able to also blackbox breakpoint evaluations would instead cause the `console.trace()`
action to have the same output as the Sources Tab, since it's evaluation would be deferred
until execution actually paused, which is further inside the event handler

* UserInterface/Base/Setting.js:
* UserInterface/Controllers/DebuggerManager.js:
(WI.DebuggerManager.supportsBlackboxingBreakpointEvaluations): Added.
(WI.DebuggerManager.prototype.async initializeTarget):
(WI.DebuggerManager.prototype._setBlackboxBreakpointEvaluations): Added.
(WI.DebuggerManager.prototype._handleBlackboxBreakpointEvaluationsChange): Added.
Create a global `WI.Setting` for this feature and handle changes from the code below.

* UserInterface/Views/BlackboxSettingsView.js:
(WI.BlackboxSettingsView.prototype.initialLayout):
* UserInterface/Views/BlackboxSettingsView.css:
(.settings-view.blackbox > p > label): Added.
(.settings-view.blackbox > p > label > input): Added.
(.settings-view.blackbox > p > label > span): Added.
Add a checkbox and explanation of this new feature in the Blackbox pane of the Settings Tab.

* Localizations/en.lproj/localizedStrings.js:

LayoutTests:

* inspector/debugger/setBlackboxBreakpointEvaluations.html: Added.
* inspector/debugger/setBlackboxBreakpointEvaluations-expected.txt: Added.

Modified Paths

Added Paths

Diff

Modified: trunk/LayoutTests/ChangeLog (290719 => 290720)


--- trunk/LayoutTests/ChangeLog	2022-03-02 06:05:31 UTC (rev 290719)
+++ trunk/LayoutTests/ChangeLog	2022-03-02 06:42:27 UTC (rev 290720)
@@ -1,3 +1,13 @@
+2022-03-01  Devin Rousso  <[email protected]>
+
+        Web Inspector: add a switch to control whether breakpoint evaluations (condition, ignore count, actions) are also affected by script blackboxing
+        https://bugs.webkit.org/show_bug.cgi?id=235274
+
+        Reviewed by Patrick Angle.
+
+        * inspector/debugger/setBlackboxBreakpointEvaluations.html: Added.
+        * inspector/debugger/setBlackboxBreakpointEvaluations-expected.txt: Added.
+
 2022-03-01  Matteo Flores  <[email protected]>
 
         [ iOS ] pointerevents/mouse/* 10 pointer event tests are constant timeouts or failures on iOS.

Added: trunk/LayoutTests/inspector/debugger/setBlackboxBreakpointEvaluations-expected.txt (0 => 290720)


--- trunk/LayoutTests/inspector/debugger/setBlackboxBreakpointEvaluations-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/inspector/debugger/setBlackboxBreakpointEvaluations-expected.txt	2022-03-02 06:42:27 UTC (rev 290720)
@@ -0,0 +1,124 @@
+Tests Debugger.setBlackboxBreakpointEvaluations.
+
+
+== Running test suite: Debugger.setBlackboxBreakpointEvaluations
+-- Running test case: Debugger.setBlackboxBreakpointEvaluations.UnblackboxBreakpointEvaluations.BreakpointCondition.False
+Evaluating 'createScripts("UnblackboxBreakpointEvaluations_BreakpointCondition_False")'...
+Blackboxing 'unblackboxbreakpointevaluations_breakpointcondition_false_inner.js'...
+Setting breakpoint in 'UnblackboxBreakpointEvaluations_BreakpointCondition_False_Inner.js'...
+Unblackboxing preakpoing evaluations...
+Evaluating 'UnblackboxBreakpointEvaluations_BreakpointCondition_False_Outer(2)'...
+PASS: Should change value of 'x' inside 'UnblackboxBreakpointEvaluations_BreakpointCondition_False_Inner.js'
+PASS: Should not pause in 'UnblackboxBreakpointEvaluations_BreakpointCondition_False_Inner'.
+PASS: Should not pause in 'UnblackboxBreakpointEvaluations_BreakpointCondition_False_Outer'.
+
+-- Running test case: Debugger.setBlackboxBreakpointEvaluations.UnblackboxBreakpointEvaluations.BreakpointCondition.True
+Evaluating 'createScripts("UnblackboxBreakpointEvaluations_BreakpointCondition_True")'...
+Blackboxing 'unblackboxbreakpointevaluations_breakpointcondition_true_inner.js'...
+Setting breakpoint in 'UnblackboxBreakpointEvaluations_BreakpointCondition_True_Inner.js'...
+Unblackboxing preakpoing evaluations...
+Evaluating 'UnblackboxBreakpointEvaluations_BreakpointCondition_True_Outer(2)'...
+
+PAUSED: 'BlackboxedScript' at 'UnblackboxBreakpointEvaluations_BreakpointCondition_True_Outer:3:4'.
+{
+  "originalReason": "Breakpoint",
+  "originalData": {
+    "breakpointId": "UnblackboxBreakpointEvaluations_BreakpointCondition_True_Inner.js:2:0"
+  }
+}
+Resuming...
+PASS: Resumed.
+PASS: Should change value of 'x' inside 'UnblackboxBreakpointEvaluations_BreakpointCondition_True_Inner.js'
+PASS: Should not pause in 'UnblackboxBreakpointEvaluations_BreakpointCondition_True_Inner'.
+PASS: Should pause in 'UnblackboxBreakpointEvaluations_BreakpointCondition_True_Outer'.
+
+-- Running test case: Debugger.setBlackboxBreakpointEvaluations.UnblackboxBreakpointEvaluations.BreakpointAction.Pause
+Evaluating 'createScripts("UnblackboxBreakpointEvaluations_BreakpointAction_Pause")'...
+Blackboxing 'unblackboxbreakpointevaluations_breakpointaction_pause_inner.js'...
+Setting breakpoint in 'UnblackboxBreakpointEvaluations_BreakpointAction_Pause_Inner.js'...
+Unblackboxing preakpoing evaluations...
+Evaluating 'UnblackboxBreakpointEvaluations_BreakpointAction_Pause_Outer(2)'...
+
+PAUSED: 'BlackboxedScript' at 'UnblackboxBreakpointEvaluations_BreakpointAction_Pause_Outer:3:4'.
+{
+  "originalReason": "Breakpoint",
+  "originalData": {
+    "breakpointId": "UnblackboxBreakpointEvaluations_BreakpointAction_Pause_Inner.js:2:0"
+  }
+}
+Resuming...
+PASS: Resumed.
+PASS: Should change value of 'x' inside 'UnblackboxBreakpointEvaluations_BreakpointAction_Pause_Inner.js'
+PASS: Should not pause in 'UnblackboxBreakpointEvaluations_BreakpointAction_Pause_Inner'.
+PASS: Should not pause in 'UnblackboxBreakpointEvaluations_BreakpointAction_Pause_Outer'.
+
+-- Running test case: Debugger.setBlackboxBreakpointEvaluations.UnblackboxBreakpointEvaluations.BreakpointAction.AutoContinue
+Evaluating 'createScripts("UnblackboxBreakpointEvaluations_BreakpointAction_AutoContinue")'...
+Blackboxing 'unblackboxbreakpointevaluations_breakpointaction_autocontinue_inner.js'...
+Setting breakpoint in 'UnblackboxBreakpointEvaluations_BreakpointAction_AutoContinue_Inner.js'...
+Unblackboxing preakpoing evaluations...
+Evaluating 'UnblackboxBreakpointEvaluations_BreakpointAction_AutoContinue_Outer(2)'...
+PASS: Should change value of 'x' inside 'UnblackboxBreakpointEvaluations_BreakpointAction_AutoContinue_Inner.js'
+PASS: Should not pause in 'UnblackboxBreakpointEvaluations_BreakpointAction_AutoContinue_Inner'.
+PASS: Should not pause in 'UnblackboxBreakpointEvaluations_BreakpointAction_AutoContinue_Outer'.
+
+-- Running test case: Debugger.setBlackboxBreakpointEvaluations.BlackboxBreakpointEvaluations.BreakpointCondition.False
+Evaluating 'createScripts("BlackboxBreakpointEvaluations_BreakpointCondition_False")'...
+Blackboxing 'blackboxbreakpointevaluations_breakpointcondition_false_inner.js'...
+Setting breakpoint in 'BlackboxBreakpointEvaluations_BreakpointCondition_False_Inner.js'...
+Blackboxing preakpoing evaluations...
+Evaluating 'BlackboxBreakpointEvaluations_BreakpointCondition_False_Outer(2)'...
+PASS: Should change value of 'x' inside 'BlackboxBreakpointEvaluations_BreakpointCondition_False_Outer.js'
+PASS: Should not pause in 'BlackboxBreakpointEvaluations_BreakpointCondition_False_Inner'.
+PASS: Should not pause in 'BlackboxBreakpointEvaluations_BreakpointCondition_False_Outer'.
+
+-- Running test case: Debugger.setBlackboxBreakpointEvaluations.BlackboxBreakpointEvaluations.BreakpointCondition.True
+Evaluating 'createScripts("BlackboxBreakpointEvaluations_BreakpointCondition_True")'...
+Blackboxing 'blackboxbreakpointevaluations_breakpointcondition_true_inner.js'...
+Setting breakpoint in 'BlackboxBreakpointEvaluations_BreakpointCondition_True_Inner.js'...
+Blackboxing preakpoing evaluations...
+Evaluating 'BlackboxBreakpointEvaluations_BreakpointCondition_True_Outer(2)'...
+
+PAUSED: 'BlackboxedScript' at 'BlackboxBreakpointEvaluations_BreakpointCondition_True_Outer:3:4'.
+{
+  "originalReason": "Breakpoint",
+  "originalData": {
+    "breakpointId": "BlackboxBreakpointEvaluations_BreakpointCondition_True_Inner.js:2:0"
+  }
+}
+Resuming...
+PASS: Resumed.
+PASS: Should change value of 'x' inside 'BlackboxBreakpointEvaluations_BreakpointCondition_True_Outer.js'
+PASS: Should not pause in 'BlackboxBreakpointEvaluations_BreakpointCondition_True_Inner'.
+PASS: Should pause in 'BlackboxBreakpointEvaluations_BreakpointCondition_True_Outer'.
+
+-- Running test case: Debugger.setBlackboxBreakpointEvaluations.BlackboxBreakpointEvaluations.BreakpointAction.Pause
+Evaluating 'createScripts("BlackboxBreakpointEvaluations_BreakpointAction_Pause")'...
+Blackboxing 'blackboxbreakpointevaluations_breakpointaction_pause_inner.js'...
+Setting breakpoint in 'BlackboxBreakpointEvaluations_BreakpointAction_Pause_Inner.js'...
+Blackboxing preakpoing evaluations...
+Evaluating 'BlackboxBreakpointEvaluations_BreakpointAction_Pause_Outer(2)'...
+
+PAUSED: 'BlackboxedScript' at 'BlackboxBreakpointEvaluations_BreakpointAction_Pause_Outer:3:4'.
+{
+  "originalReason": "Breakpoint",
+  "originalData": {
+    "breakpointId": "BlackboxBreakpointEvaluations_BreakpointAction_Pause_Inner.js:2:0"
+  }
+}
+Resuming...
+PASS: Resumed.
+PASS: Should change value of 'x' inside 'BlackboxBreakpointEvaluations_BreakpointAction_Pause_Outer.js'
+PASS: Should not pause in 'BlackboxBreakpointEvaluations_BreakpointAction_Pause_Inner'.
+PASS: Should not pause in 'BlackboxBreakpointEvaluations_BreakpointAction_Pause_Outer'.
+
+-- Running test case: Debugger.setBlackboxBreakpointEvaluations.BlackboxBreakpointEvaluations.BreakpointAction.AutoContinue
+Evaluating 'createScripts("BlackboxBreakpointEvaluations_BreakpointAction_AutoContinue")'...
+Blackboxing 'blackboxbreakpointevaluations_breakpointaction_autocontinue_inner.js'...
+Setting breakpoint in 'BlackboxBreakpointEvaluations_BreakpointAction_AutoContinue_Inner.js'...
+Blackboxing preakpoing evaluations...
+Evaluating 'BlackboxBreakpointEvaluations_BreakpointAction_AutoContinue_Outer(2)'...
+PASS: Should change value of 'x' inside 'BlackboxBreakpointEvaluations_BreakpointAction_AutoContinue_Outer.js'
+PASS: Should not pause in 'BlackboxBreakpointEvaluations_BreakpointAction_AutoContinue_Inner'.
+PASS: Should not pause in 'BlackboxBreakpointEvaluations_BreakpointAction_AutoContinue_Outer'.
+

Added: trunk/LayoutTests/inspector/debugger/setBlackboxBreakpointEvaluations.html (0 => 290720)


--- trunk/LayoutTests/inspector/debugger/setBlackboxBreakpointEvaluations.html	                        (rev 0)
+++ trunk/LayoutTests/inspector/debugger/setBlackboxBreakpointEvaluations.html	2022-03-02 06:42:27 UTC (rev 290720)
@@ -0,0 +1,319 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script src=""
+<script>
+function createScripts(id) {
+    eval(
+`
+window.${id}_Inner = function ${id}_Inner(x) {
+    return x * x;
+};
+//# sourceURL=${id}_Inner.js
+`
+    );
+
+    eval(
+`
+window.${id}_Outer = function ${id}_Outer(x) {
+    let innerResult = ${id}_Inner(x);
+    return innerResult * x;
+};
+//# sourceURL=${id}_Outer.js
+`
+    );
+}
+
+function test()
+{
+    ProtocolTest.debug();
+    let suite = ProtocolTest.createAsyncSuite("Debugger.setBlackboxBreakpointEvaluations");
+
+    let sourceURLRegExpQueries = new Map;
+    let pausedFunctionNames = [];
+
+    InspectorProtocol.sendCommand("Debugger.enable", {});
+    InspectorProtocol.sendCommand("Debugger.setBreakpointsActive", {active: true});
+
+    InspectorProtocol.eventHandler["Debugger.scriptParsed"] = function(message) {
+        let sourceURL = message.params.sourceURL;
+        for (let [regExp, callback] of sourceURLRegExpQueries) {
+            if (regExp.test(sourceURL)) {
+                sourceURLRegExpQueries.delete(regExp);
+                callback(sourceURL);
+            }
+        };
+    };
+
+    InspectorProtocol.eventHandler["Debugger.paused"] = function(message) {
+        ProtocolTest.newline();
+
+        let topCallFrame = message.params.callFrames[0];
+        let functionName = topCallFrame.functionName;
+        if (functionName !== "global code") {
+            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("Resuming...");
+        InspectorProtocol.sendCommand(`Debugger.resume`, {}, InspectorProtocol.checkForError);
+    };
+
+    InspectorProtocol.eventHandler["Debugger.resumed"] = function(message) {
+        ProtocolTest.pass("Resumed.");
+    };
+
+    async function setBlackbox(url, options = {}) {
+        if (!options.caseSensitive)
+            url = ""
+
+        ProtocolTest.log(`Blackboxing ${options.caseSensitive ? "(case sensitive) " : ""}${options.isRegex ? "(regex) " : ""}'${url}'...`);
+        await InspectorProtocol.awaitCommand({
+            method: "Debugger.setShouldBlackboxURL",
+            params: {url, shouldBlackbox: true, ...options},
+        });
+    }
+
+    async function setBreakpoint(url, lineNumber, options) {
+        ProtocolTest.log(`Setting breakpoint in '${url}'...`);
+        await InspectorProtocol.awaitCommand({
+            method: "Debugger.setBreakpointByUrl",
+            params: {url, lineNumber, options},
+        });
+    }
+
+    async function setBlackboxBreakpointEvaluations(blackboxBreakpointEvaluations) {
+        ProtocolTest.log(`${blackboxBreakpointEvaluations ? "Blackboxing" : "Unblackboxing"} preakpoing evaluations...`);
+        return InspectorProtocol.awaitCommand({
+            method: "Debugger.setBlackboxBreakpointEvaluations",
+            params: {blackboxBreakpointEvaluations},
+        });
+    }
+
+    async function listenForSourceParsed(sourceURLRegExp) {
+        return new Promise((resolve, reject) => {
+            sourceURLRegExpQueries.set(sourceURLRegExp, resolve);
+        });
+    }
+
+    async function evaluate(_expression_) {
+        ProtocolTest.log(`Evaluating '${_expression_}'...`);
+        return InspectorProtocol.awaitCommand({
+            method: "Runtime.evaluate",
+            params: {_expression_},
+        });
+    }
+
+    suite.addTestCase({
+        name: "Debugger.setBlackboxBreakpointEvaluations.UnblackboxBreakpointEvaluations.BreakpointCondition.False",
+        async test() {
+            let [innerSourceURL, outerSourceURL] = await Promise.all([
+                listenForSourceParsed(/UnblackboxBreakpointEvaluations_BreakpointCondition_False_Inner\.js$/),
+                listenForSourceParsed(/UnblackboxBreakpointEvaluations_BreakpointCondition_False_Outer\.js$/),
+                evaluate(`createScripts("UnblackboxBreakpointEvaluations_BreakpointCondition_False")`),
+            ]);
+
+            await setBlackbox(innerSourceURL);
+            await setBreakpoint(innerSourceURL, 2, {
+                condition: "!(x = 3)",
+            });
+            await setBlackboxBreakpointEvaluations(false);
+
+            let result = await evaluate(`UnblackboxBreakpointEvaluations_BreakpointCondition_False_Outer(2)`);
+            InspectorProtocol.checkForError(result);
+            ProtocolTest.expectEqual(result.result.value, (3 * 3) * 2, "Should change value of 'x' inside 'UnblackboxBreakpointEvaluations_BreakpointCondition_False_Inner.js'");
+
+            ProtocolTest.expectFalse(pausedFunctionNames.includes("UnblackboxBreakpointEvaluations_BreakpointCondition_False_Inner"), "Should not pause in 'UnblackboxBreakpointEvaluations_BreakpointCondition_False_Inner'.");
+            ProtocolTest.expectFalse(pausedFunctionNames.includes("UnblackboxBreakpointEvaluations_BreakpointCondition_False_Outer"), "Should not pause in 'UnblackboxBreakpointEvaluations_BreakpointCondition_False_Outer'.");
+        },
+    });
+
+    suite.addTestCase({
+        name: "Debugger.setBlackboxBreakpointEvaluations.UnblackboxBreakpointEvaluations.BreakpointCondition.True",
+        async test() {
+            let [innerSourceURL, outerSourceURL] = await Promise.all([
+                listenForSourceParsed(/UnblackboxBreakpointEvaluations_BreakpointCondition_True_Inner\.js$/),
+                listenForSourceParsed(/UnblackboxBreakpointEvaluations_BreakpointCondition_True_Outer\.js$/),
+                evaluate(`createScripts("UnblackboxBreakpointEvaluations_BreakpointCondition_True")`),
+            ]);
+
+            await setBlackbox(innerSourceURL);
+            await setBreakpoint(innerSourceURL, 2, {
+                condition: "(x = 3)",
+            });
+            await setBlackboxBreakpointEvaluations(false);
+
+            let result = await evaluate(`UnblackboxBreakpointEvaluations_BreakpointCondition_True_Outer(2)`);
+            InspectorProtocol.checkForError(result);
+            ProtocolTest.expectEqual(result.result.value, (3 * 3) * 2, "Should change value of 'x' inside 'UnblackboxBreakpointEvaluations_BreakpointCondition_True_Inner.js'");
+
+            ProtocolTest.expectFalse(pausedFunctionNames.includes("UnblackboxBreakpointEvaluations_BreakpointCondition_True_Inner"), "Should not pause in 'UnblackboxBreakpointEvaluations_BreakpointCondition_True_Inner'.");
+            ProtocolTest.expectTrue(pausedFunctionNames.includes("UnblackboxBreakpointEvaluations_BreakpointCondition_True_Outer"), "Should pause in 'UnblackboxBreakpointEvaluations_BreakpointCondition_True_Outer'.");
+
+        },
+    });
+
+    suite.addTestCase({
+        name: "Debugger.setBlackboxBreakpointEvaluations.UnblackboxBreakpointEvaluations.BreakpointAction.Pause",
+        async test() {
+            let [innerSourceURL, outerSourceURL] = await Promise.all([
+                listenForSourceParsed(/UnblackboxBreakpointEvaluations_BreakpointAction_Pause_Inner\.js$/),
+                listenForSourceParsed(/UnblackboxBreakpointEvaluations_BreakpointAction_Pause_Outer\.js$/),
+                evaluate(`createScripts("UnblackboxBreakpointEvaluations_BreakpointAction_Pause")`),
+            ]);
+
+            await setBlackbox(innerSourceURL);
+            await setBreakpoint(innerSourceURL, 2, {
+                actions: [
+                    {type: "evaluate", data: "x = 3"},
+                ],
+            });
+            await setBlackboxBreakpointEvaluations(false);
+
+            let result = await evaluate(`UnblackboxBreakpointEvaluations_BreakpointAction_Pause_Outer(2)`);
+            InspectorProtocol.checkForError(result);
+            ProtocolTest.expectEqual(result.result.value, (3 * 3) * 2, "Should change value of 'x' inside 'UnblackboxBreakpointEvaluations_BreakpointAction_Pause_Inner.js'");
+
+            ProtocolTest.expectFalse(pausedFunctionNames.includes("UnblackboxBreakpointEvaluations_BreakpointAction_Pause_Inner"), "Should not pause in 'UnblackboxBreakpointEvaluations_BreakpointAction_Pause_Inner'.");
+            ProtocolTest.expectTrue(pausedFunctionNames.includes("UnblackboxBreakpointEvaluations_BreakpointAction_Pause_Outer"), "Should not pause in 'UnblackboxBreakpointEvaluations_BreakpointAction_Pause_Outer'.");
+        },
+    });
+
+    suite.addTestCase({
+        name: "Debugger.setBlackboxBreakpointEvaluations.UnblackboxBreakpointEvaluations.BreakpointAction.AutoContinue",
+        async test() {
+            let [innerSourceURL, outerSourceURL] = await Promise.all([
+                listenForSourceParsed(/UnblackboxBreakpointEvaluations_BreakpointAction_AutoContinue_Inner\.js$/),
+                listenForSourceParsed(/UnblackboxBreakpointEvaluations_BreakpointAction_AutoContinue_Outer\.js$/),
+                evaluate(`createScripts("UnblackboxBreakpointEvaluations_BreakpointAction_AutoContinue")`),
+            ]);
+
+            await setBlackbox(innerSourceURL);
+            await setBreakpoint(innerSourceURL, 2, {
+                actions: [
+                    {type: "evaluate", data: "x = 3"},
+                ],
+                autoContinue: true,
+            });
+            await setBlackboxBreakpointEvaluations(false);
+
+            let result = await evaluate(`UnblackboxBreakpointEvaluations_BreakpointAction_AutoContinue_Outer(2)`);
+            InspectorProtocol.checkForError(result);
+            ProtocolTest.expectEqual(result.result.value, (3 * 3) * 2, "Should change value of 'x' inside 'UnblackboxBreakpointEvaluations_BreakpointAction_AutoContinue_Inner.js'");
+
+            ProtocolTest.expectFalse(pausedFunctionNames.includes("UnblackboxBreakpointEvaluations_BreakpointAction_AutoContinue_Inner"), "Should not pause in 'UnblackboxBreakpointEvaluations_BreakpointAction_AutoContinue_Inner'.");
+            ProtocolTest.expectFalse(pausedFunctionNames.includes("UnblackboxBreakpointEvaluations_BreakpointAction_AutoContinue_Outer"), "Should not pause in 'UnblackboxBreakpointEvaluations_BreakpointAction_AutoContinue_Outer'.");
+        },
+    });
+
+    suite.addTestCase({
+        name: "Debugger.setBlackboxBreakpointEvaluations.BlackboxBreakpointEvaluations.BreakpointCondition.False",
+        async test() {
+            let [innerSourceURL, outerSourceURL] = await Promise.all([
+                listenForSourceParsed(/BlackboxBreakpointEvaluations_BreakpointCondition_False_Inner\.js$/),
+                listenForSourceParsed(/BlackboxBreakpointEvaluations_BreakpointCondition_False_Outer\.js$/),
+                evaluate(`createScripts("BlackboxBreakpointEvaluations_BreakpointCondition_False")`),
+            ]);
+
+            await setBlackbox(innerSourceURL);
+            await setBreakpoint(innerSourceURL, 2, {
+                condition: "!(x = 3)",
+            });
+            await setBlackboxBreakpointEvaluations(true);
+
+            let result = await evaluate(`BlackboxBreakpointEvaluations_BreakpointCondition_False_Outer(2)`);
+            InspectorProtocol.checkForError(result);
+            ProtocolTest.expectEqual(result.result.value, (2 * 2) * 3, "Should change value of 'x' inside 'BlackboxBreakpointEvaluations_BreakpointCondition_False_Outer.js'");
+
+            ProtocolTest.expectFalse(pausedFunctionNames.includes("BlackboxBreakpointEvaluations_BreakpointCondition_False_Inner"), "Should not pause in 'BlackboxBreakpointEvaluations_BreakpointCondition_False_Inner'.");
+            ProtocolTest.expectFalse(pausedFunctionNames.includes("BlackboxBreakpointEvaluations_BreakpointCondition_False_Outer"), "Should not pause in 'BlackboxBreakpointEvaluations_BreakpointCondition_False_Outer'.");
+        },
+    });
+
+    suite.addTestCase({
+        name: "Debugger.setBlackboxBreakpointEvaluations.BlackboxBreakpointEvaluations.BreakpointCondition.True",
+        async test() {
+            let [innerSourceURL, outerSourceURL] = await Promise.all([
+                listenForSourceParsed(/BlackboxBreakpointEvaluations_BreakpointCondition_True_Inner\.js$/),
+                listenForSourceParsed(/BlackboxBreakpointEvaluations_BreakpointCondition_True_Outer\.js$/),
+                evaluate(`createScripts("BlackboxBreakpointEvaluations_BreakpointCondition_True")`),
+            ]);
+
+            await setBlackbox(innerSourceURL);
+            await setBreakpoint(innerSourceURL, 2, {
+                condition: "(x = 3)",
+            });
+            await setBlackboxBreakpointEvaluations(true);
+
+            let result = await evaluate(`BlackboxBreakpointEvaluations_BreakpointCondition_True_Outer(2)`);
+            InspectorProtocol.checkForError(result);
+            ProtocolTest.expectEqual(result.result.value, (2 * 2) * 3, "Should change value of 'x' inside 'BlackboxBreakpointEvaluations_BreakpointCondition_True_Outer.js'");
+
+            ProtocolTest.expectFalse(pausedFunctionNames.includes("BlackboxBreakpointEvaluations_BreakpointCondition_True_Inner"), "Should not pause in 'BlackboxBreakpointEvaluations_BreakpointCondition_True_Inner'.");
+            ProtocolTest.expectTrue(pausedFunctionNames.includes("BlackboxBreakpointEvaluations_BreakpointCondition_True_Outer"), "Should pause in 'BlackboxBreakpointEvaluations_BreakpointCondition_True_Outer'.");
+        },
+    });
+
+    suite.addTestCase({
+        name: "Debugger.setBlackboxBreakpointEvaluations.BlackboxBreakpointEvaluations.BreakpointAction.Pause",
+        async test() {
+            let [innerSourceURL, outerSourceURL] = await Promise.all([
+                listenForSourceParsed(/BlackboxBreakpointEvaluations_BreakpointAction_Pause_Inner\.js$/),
+                listenForSourceParsed(/BlackboxBreakpointEvaluations_BreakpointAction_Pause_Outer\.js$/),
+                evaluate(`createScripts("BlackboxBreakpointEvaluations_BreakpointAction_Pause")`),
+            ]);
+
+            await setBlackbox(innerSourceURL);
+            await setBreakpoint(innerSourceURL, 2, {
+                actions: [
+                    {type: "evaluate", data: "x = 3"},
+                ],
+            });
+            await setBlackboxBreakpointEvaluations(true);
+
+            let result = await evaluate(`BlackboxBreakpointEvaluations_BreakpointAction_Pause_Outer(2)`);
+            InspectorProtocol.checkForError(result);
+            ProtocolTest.expectEqual(result.result.value, (2 * 2) * 3, "Should change value of 'x' inside 'BlackboxBreakpointEvaluations_BreakpointAction_Pause_Outer.js'");
+
+            ProtocolTest.expectFalse(pausedFunctionNames.includes("BlackboxBreakpointEvaluations_BreakpointAction_Pause_Inner"), "Should not pause in 'BlackboxBreakpointEvaluations_BreakpointAction_Pause_Inner'.");
+            ProtocolTest.expectTrue(pausedFunctionNames.includes("BlackboxBreakpointEvaluations_BreakpointAction_Pause_Outer"), "Should not pause in 'BlackboxBreakpointEvaluations_BreakpointAction_Pause_Outer'.");
+        },
+    });
+
+    suite.addTestCase({
+        name: "Debugger.setBlackboxBreakpointEvaluations.BlackboxBreakpointEvaluations.BreakpointAction.AutoContinue",
+        async test() {
+            let [innerSourceURL, outerSourceURL] = await Promise.all([
+                listenForSourceParsed(/BlackboxBreakpointEvaluations_BreakpointAction_AutoContinue_Inner\.js$/),
+                listenForSourceParsed(/BlackboxBreakpointEvaluations_BreakpointAction_AutoContinue_Outer\.js$/),
+                evaluate(`createScripts("BlackboxBreakpointEvaluations_BreakpointAction_AutoContinue")`),
+            ]);
+
+            await setBlackbox(innerSourceURL);
+            await setBreakpoint(innerSourceURL, 2, {
+                actions: [
+                    {type: "evaluate", data: "x = 3"},
+                ],
+                autoContinue: true,
+            });
+            await setBlackboxBreakpointEvaluations(true);
+
+            let result = await evaluate(`BlackboxBreakpointEvaluations_BreakpointAction_AutoContinue_Outer(2)`);
+            InspectorProtocol.checkForError(result);
+            ProtocolTest.expectEqual(result.result.value, (2 * 2) * 3, "Should change value of 'x' inside 'BlackboxBreakpointEvaluations_BreakpointAction_AutoContinue_Outer.js'");
+
+            ProtocolTest.expectFalse(pausedFunctionNames.includes("BlackboxBreakpointEvaluations_BreakpointAction_AutoContinue_Inner"), "Should not pause in 'BlackboxBreakpointEvaluations_BreakpointAction_AutoContinue_Inner'.");
+            ProtocolTest.expectFalse(pausedFunctionNames.includes("BlackboxBreakpointEvaluations_BreakpointAction_AutoContinue_Outer"), "Should not pause in 'BlackboxBreakpointEvaluations_BreakpointAction_AutoContinue_Outer'.");
+        },
+    });
+
+    suite.runTestCasesAndFinish();
+}
+</script>
+</head>
+<body _onload_="runTest()">
+<p>Tests Debugger.setBlackboxBreakpointEvaluations.</p>
+</body>
+</html>

Modified: trunk/Source/_javascript_Core/ChangeLog (290719 => 290720)


--- trunk/Source/_javascript_Core/ChangeLog	2022-03-02 06:05:31 UTC (rev 290719)
+++ trunk/Source/_javascript_Core/ChangeLog	2022-03-02 06:42:27 UTC (rev 290720)
@@ -1,3 +1,53 @@
+2022-03-01  Devin Rousso  <[email protected]>
+
+        Web Inspector: add a switch to control whether breakpoint evaluations (condition, ignore count, actions) are also affected by script blackboxing
+        https://bugs.webkit.org/show_bug.cgi?id=235274
+
+        Reviewed by Patrick Angle.
+
+        Being able to defer breakpoint evaluations until the next actual pause can sometimes be far
+        more useful than doing the breakpoint evaluations at the breakpoint's original location.
+
+        As an example, configuring the All Events breakpoint with a `console.trace()` action and
+        auto-continue enabled would not provide much useful information without also blackboxing
+        breakpoint evaluations as the original pause location of the All Events breakpoint is on the
+        first line of the event handler, meaning that if the script containing the event handler is
+        blackboxed then the `console.trace()` would still only show that location even though the
+        Sources Tab would show the first line of code outside of that script (due to the blackbox).
+        Being able to also blackbox breakpoint evaluations would instead cause the `console.trace()`
+        action to have the same output as the Sources Tab, since it's evaluation would be deferred
+        until execution actually paused, which is further inside the event handler.
+
+        * debugger/Debugger.cpp:
+        (JSC::Debugger::Debugger):
+        (JSC::Debugger::didHitBreakpoint):
+        (JSC::Debugger::evaluateBreakpointCondition):
+        (JSC::Debugger::continueProgram):
+        (JSC::Debugger::pauseIfNeeded):
+        (JSC::Debugger::didExecuteProgram):
+        (JSC::Debugger::setBlackboxBreakpointEvaluations): Added.
+        Keep track of every `Breakpoint` wants to pause but is deferred due to blackboxing.
+        Depending on whether breakpoint evaluations are also blackboxed (based on the flag set via
+        the piping code/logic below), check if the script attempting to be paused in is blackboxed
+        before or after handling breakpoint evaluations.
+        Side effects of this change are:
+        - breakpoint conditions are now evaluated right before that breakpoint's actions, instead of
+          evaluating all breakpoint conditions before all breakpoint actions
+        - if breakpoint evaluations are blackboxed, more than two breakpoints can now be evaluated
+        during a single pause attempt depending on how may deferrals (with breakpoints) happened
+
+        * debugger/Debugger.h:
+        (JSC::Debugger::Observer::didDeferBreakpointPause): Added.
+        Add a way to notify `InspectorDebuggerAgent` of deferred breakpoint pauses so that the
+        correct `originalReason` will be used after a deferred pause.
+
+        * inspector/protocol/Debugger.json:
+        * inspector/agents/InspectorDebuggerAgent.h:
+        * inspector/agents/InspectorDebuggerAgent.cpp:
+        (Inspector::InspectorDebuggerAgent::setBlackboxBreakpointEvaluations): Added.
+        (Inspector::InspectorDebuggerAgent::didDeferBreakpointPause): Added.
+        Add a `setBlackboxBreakpointEvaluations` command that passes directly to `Debugger`.
+
 2022-03-01  Saam Barati  <[email protected]>
 
         Add a DeferTraps scope

Modified: trunk/Source/_javascript_Core/debugger/Debugger.cpp (290719 => 290720)


--- trunk/Source/_javascript_Core/debugger/Debugger.cpp	2022-03-02 06:05:31 UTC (rev 290719)
+++ trunk/Source/_javascript_Core/debugger/Debugger.cpp	2022-03-02 06:42:27 UTC (rev 290720)
@@ -113,6 +113,7 @@
 
 Debugger::Debugger(VM& vm)
     : m_vm(vm)
+    , m_blackboxBreakpointEvaluations(false)
     , m_pauseAtNextOpportunity(false)
     , m_pastFirstExpressionInStatement(false)
     , m_isPaused(false)
@@ -543,7 +544,7 @@
     return removed;
 }
 
-RefPtr<Breakpoint> Debugger::didHitBreakpoint(JSGlobalObject* globalObject, SourceID sourceID, const TextPosition& position)
+RefPtr<Breakpoint> Debugger::didHitBreakpoint(SourceID sourceID, const TextPosition& position)
 {
     if (!m_breakpointsActivated)
         return nullptr;
@@ -565,11 +566,8 @@
 
         // Since frontend truncates the indent, the first statement in a line must match the breakpoint (line,0).
         ASSERT(this == m_currentCallFrame->codeBlock()->globalObject()->debugger());
-        if ((line != m_lastExecutedLine && line == breakLine && !breakColumn) || (line == breakLine && column == breakColumn)) {
-            if (breakpoint->shouldPause(*this, globalObject))
-                return breakpoint.copyRef();
-            break;
-        }
+        if ((line != m_lastExecutedLine && line == breakLine && !breakColumn) || (line == breakLine && column == breakColumn))
+            return breakpoint.copyRef();
     }
 
     return nullptr;
@@ -606,14 +604,13 @@
 
 bool Debugger::evaluateBreakpointCondition(Breakpoint& breakpoint, JSGlobalObject* globalObject)
 {
+    ASSERT(m_isPaused);
+    ASSERT(isAttached(globalObject));
+
     const String& condition = breakpoint.condition();
     if (condition.isEmpty())
         return true;
 
-    // We cannot stop in the debugger while executing condition code,
-    // so make it looks like the debugger is already paused.
-    TemporaryPausedState pausedState(*this);
-
     NakedPtr<Exception> exception;
     DebuggerCallFrame& debuggerCallFrame = currentDebuggerCallFrame();
     JSObject* scopeExtensionObject = m_client ? m_client->debuggerScopeExtensionObject(*this, globalObject, debuggerCallFrame) : nullptr;
@@ -781,6 +778,7 @@
 void Debugger::continueProgram()
 {
     clearNextPauseState();
+    m_deferredBreakpoints.clear();
 
     if (!m_isPaused)
         return;
@@ -883,27 +881,34 @@
 
     DebuggerPausedScope debuggerPausedScope(*this);
 
-    bool pauseNow = m_pauseAtNextOpportunity;
-    pauseNow |= (m_pauseOnCallFrame == m_currentCallFrame);
+    bool afterBlackboxedScript = m_afterBlackboxedScript;
+    bool pauseNow = false;
+    bool didPauseForStep = false;
+    if (m_pauseAtNextOpportunity) {
+        pauseNow = true;
+        didPauseForStep = !afterBlackboxedScript;
+    } else if (m_pauseOnStepNext || m_pauseOnStepOut || (m_pauseOnCallFrame == m_currentCallFrame)) {
+        pauseNow = true;
+        didPauseForStep = true;
+    }
 
-    bool didPauseForStep = pauseNow;
-
     TextPosition position = DebuggerCallFrame::positionForCallFrame(vm, m_currentCallFrame);
 
-    auto breakpoint = didHitBreakpoint(globalObject, sourceID, position);
-    if (breakpoint)
+    if (auto breakpoint = didHitBreakpoint(sourceID, position)) {
         pauseNow = true;
+        m_deferredBreakpoints.add(breakpoint.releaseNonNull());
+    }
 
     // Special breakpoints are only given one opportunity to pause.
-    auto specialBreakpoint = WTFMove(m_specialBreakpoint);
-    if (specialBreakpoint && specialBreakpoint->shouldPause(*this, globalObject))
+    if (m_specialBreakpoint) {
         pauseNow = true;
+        m_deferredBreakpoints.add(m_specialBreakpoint.releaseNonNull());
+    }
 
     m_lastExecutedLine = position.m_line.zeroBasedInt();
     if (!pauseNow)
         return;
 
-    bool afterBlackboxedScript = m_afterBlackboxedScript;
     clearNextPauseState();
 
     // Make sure we are not going to pause again on breakpoint actions by
@@ -910,53 +915,78 @@
     // reseting the pause state before executing any breakpoint actions.
     TemporaryPausedState pausedState(*this);
 
-    if (breakpoint || specialBreakpoint) {
-        // Note that the actions can potentially stop the debugger, so we need to check that
-        // we still have a current call frame when we get back.
+    auto shouldDeferPause = [&] () {
+        if (blackboxTypeIterator == m_blackboxedScripts.end())
+            return false;
 
-        bool autoContinue = false;
+        if (blackboxTypeIterator->value != BlackboxType::Deferred)
+            return false;
 
-        if (breakpoint) {
-            evaluateBreakpointActions(*breakpoint, globalObject);
+        m_afterBlackboxedScript = true;
 
-            if (!m_currentCallFrame)
-                return;
+        if (m_pausingBreakpointID != noBreakpointID) {
+            dispatchFunctionToObservers([&] (Observer& observer) {
+                observer.didDeferBreakpointPause(m_pausingBreakpointID);
+            });
 
-            if (breakpoint->isAutoContinue())
-                autoContinue = true;
+            m_pausingBreakpointID = noBreakpointID;
         }
 
-        if (specialBreakpoint) {
-            evaluateBreakpointActions(*specialBreakpoint, globalObject);
+        schedulePauseAtNextOpportunity();
+        return true;
+    };
 
+    if (m_blackboxBreakpointEvaluations && shouldDeferPause())
+        return;
+
+    if (!m_deferredBreakpoints.isEmpty()) {
+        std::optional<BreakpointID> pausingBreakpointID;
+        bool hasEvaluatedSpecialBreakpoint = false;
+        bool shouldContinue = true;
+
+        for (auto&& deferredBreakpoint : std::exchange(m_deferredBreakpoints, { })) {
+            // Note that breakpoint evaluations can potentially stop the debugger, so we need to
+            // check that we still have a current call frame after evaluating them.
+
+            bool shouldPause = deferredBreakpoint->shouldPause(*this, globalObject);
             if (!m_currentCallFrame)
                 return;
+            if (!shouldPause)
+                continue;
 
-            if (specialBreakpoint->isAutoContinue())
-                autoContinue = true;
+            evaluateBreakpointActions(deferredBreakpoint, globalObject);
+            if (!m_currentCallFrame)
+                return;
+
+            if (deferredBreakpoint->isAutoContinue())
+                continue;
+
+            shouldContinue = false;
+
+            // Only propagate `PausedForBreakpoint` to the `InspectorDebuggerAgent` if the first
+            // line:column breakpoint hit was before the first special breakpoint, as the latter
+            // would already have set a unique reason before attempting to pause.
+            if (!deferredBreakpoint->isLinked())
+                hasEvaluatedSpecialBreakpoint = true;
+            else if (!hasEvaluatedSpecialBreakpoint && !pausingBreakpointID)
+                pausingBreakpointID = deferredBreakpoint->id();
         }
 
-        if (autoContinue) {
+        if (shouldContinue) {
             if (!didPauseForStep)
                 return;
-
-            breakpoint = nullptr;
-            specialBreakpoint = nullptr;
-        } else if (breakpoint)
-            m_pausingBreakpointID = breakpoint->id();
+        } else if (pausingBreakpointID)
+            m_pausingBreakpointID = *pausingBreakpointID;
     }
 
-    if (blackboxTypeIterator != m_blackboxedScripts.end() && blackboxTypeIterator->value == BlackboxType::Deferred) {
-        m_afterBlackboxedScript = true;
-        schedulePauseAtNextOpportunity();
+    if (!m_blackboxBreakpointEvaluations && shouldDeferPause())
         return;
-    }
 
     {
         auto reason = m_reasonForPause;
         if (afterBlackboxedScript)
             reason = PausedAfterBlackboxedScript;
-        else if (breakpoint)
+        else if (m_pausingBreakpointID)
             reason = PausedForBreakpoint;
         PauseReasonDeclaration rauseReasonDeclaration(*this, reason);
 
@@ -1154,8 +1184,10 @@
     updateCallFrame(lexicalGlobalObjectForCallFrame(m_vm, callerFrame), callerFrame, NoPause);
 
     // Do not continue stepping into an unknown future program.
-    if (!m_currentCallFrame)
+    if (!m_currentCallFrame) {
         clearNextPauseState();
+        m_deferredBreakpoints.clear();
+    }
 }
 
 void Debugger::clearNextPauseState()
@@ -1211,6 +1243,11 @@
         m_blackboxedScripts.remove(sourceID);
 }
 
+void Debugger::setBlackboxBreakpointEvaluations(bool blackboxBreakpointEvaluations)
+{
+    m_blackboxBreakpointEvaluations = blackboxBreakpointEvaluations;
+}
+
 void Debugger::clearBlackbox()
 {
     m_blackboxedScripts.clear();

Modified: trunk/Source/_javascript_Core/debugger/Debugger.h (290719 => 290720)


--- trunk/Source/_javascript_Core/debugger/Debugger.h	2022-03-02 06:05:31 UTC (rev 290719)
+++ trunk/Source/_javascript_Core/debugger/Debugger.h	2022-03-02 06:42:27 UTC (rev 290720)
@@ -28,6 +28,7 @@
 #include "DebuggerPrimitives.h"
 #include "JSCJSValue.h"
 #include <wtf/Forward.h>
+#include <wtf/ListHashSet.h>
 
 namespace JSC {
 
@@ -115,6 +116,7 @@
 
     enum class BlackboxType { Deferred, Ignored };
     void setBlackboxType(SourceID, std::optional<BlackboxType>);
+    void setBlackboxBreakpointEvaluations(bool);
     void clearBlackbox();
 
     bool isPaused() const { return m_isPaused; }
@@ -181,6 +183,7 @@
         virtual void breakpointActionLog(JSGlobalObject*, const String& /* data */) { }
         virtual void breakpointActionSound(BreakpointActionID) { }
         virtual void breakpointActionProbe(JSGlobalObject*, BreakpointActionID, unsigned /* batchId */, unsigned /* sampleId */, JSValue /* result */) { }
+        virtual void didDeferBreakpointPause(BreakpointID) { }
     };
 
     JS_EXPORT_PRIVATE void addObserver(Observer&);
@@ -257,7 +260,7 @@
         Debugger& m_debugger;
     };
 
-    RefPtr<Breakpoint> didHitBreakpoint(JSGlobalObject*, SourceID, const TextPosition&);
+    RefPtr<Breakpoint> didHitBreakpoint(SourceID, const TextPosition&);
 
     DebuggerParseData& debuggerParseData(SourceID, SourceProvider*);
 
@@ -299,6 +302,7 @@
     HashSet<JSGlobalObject*> m_globalObjects;
     HashMap<SourceID, DebuggerParseData, WTF::IntHash<SourceID>, WTF::UnsignedWithZeroKeyHashTraits<SourceID>> m_parseDataMap;
     HashMap<SourceID, BlackboxType, WTF::IntHash<SourceID>, WTF::UnsignedWithZeroKeyHashTraits<SourceID>> m_blackboxedScripts;
+    bool m_blackboxBreakpointEvaluations : 1;
 
     bool m_pauseAtNextOpportunity : 1;
     bool m_pauseOnStepNext : 1;
@@ -322,6 +326,7 @@
     HashMap<SourceID, LineToBreakpointsMap, WTF::IntHash<SourceID>, WTF::UnsignedWithZeroKeyHashTraits<SourceID>> m_breakpointsForSourceID;
     HashSet<Ref<Breakpoint>> m_breakpoints;
     RefPtr<Breakpoint> m_specialBreakpoint;
+    ListHashSet<Ref<Breakpoint>> m_deferredBreakpoints;
     BreakpointID m_pausingBreakpointID;
 
     RefPtr<Breakpoint> m_pauseOnAllExceptionsBreakpoint;

Modified: trunk/Source/_javascript_Core/inspector/agents/InspectorDebuggerAgent.cpp (290719 => 290720)


--- trunk/Source/_javascript_Core/inspector/agents/InspectorDebuggerAgent.cpp	2022-03-02 06:05:31 UTC (rev 290719)
+++ trunk/Source/_javascript_Core/inspector/agents/InspectorDebuggerAgent.cpp	2022-03-02 06:42:27 UTC (rev 290720)
@@ -1069,6 +1069,13 @@
     return false;
 }
 
+Protocol::ErrorStringOr<void> InspectorDebuggerAgent::setBlackboxBreakpointEvaluations(bool blackboxBreakpointEvaluations)
+{
+    m_debugger.setBlackboxBreakpointEvaluations(blackboxBreakpointEvaluations);
+
+    return { };
+}
+
 void InspectorDebuggerAgent::scriptExecutionBlockedByCSP(const String& directiveText)
 {
     if (m_debugger.needsExceptionCallbacks())
@@ -1321,6 +1328,11 @@
         m_frontendDispatcher->resumed();
 }
 
+void InspectorDebuggerAgent::didDeferBreakpointPause(JSC::BreakpointID breakpointId)
+{
+    updatePauseReasonAndData(DebuggerFrontendDispatcher::Reason::Breakpoint, buildBreakpointPauseReason(breakpointId));
+}
+
 void InspectorDebuggerAgent::breakProgram(DebuggerFrontendDispatcher::Reason reason, RefPtr<JSON::Object>&& data, RefPtr<JSC::Breakpoint>&& specialBreakpoint)
 {
     updatePauseReasonAndData(reason, WTFMove(data));

Modified: trunk/Source/_javascript_Core/inspector/agents/InspectorDebuggerAgent.h (290719 => 290720)


--- trunk/Source/_javascript_Core/inspector/agents/InspectorDebuggerAgent.h	2022-03-02 06:05:31 UTC (rev 290719)
+++ trunk/Source/_javascript_Core/inspector/agents/InspectorDebuggerAgent.h	2022-03-02 06:42:27 UTC (rev 290720)
@@ -93,6 +93,7 @@
     Protocol::ErrorStringOr<void> setPauseForInternalScripts(bool shouldPause) final;
     Protocol::ErrorStringOr<std::tuple<Ref<Protocol::Runtime::RemoteObject>, std::optional<bool> /* wasThrown */, std::optional<int> /* savedResultIndex */>> evaluateOnCallFrame(const Protocol::Debugger::CallFrameId&, const String& _expression_, const String& objectGroup, std::optional<bool>&& includeCommandLineAPI, std::optional<bool>&& doNotPauseOnExceptionsAndMuteConsole, std::optional<bool>&& returnByValue, std::optional<bool>&& generatePreview, std::optional<bool>&& saveResult, std::optional<bool>&& emulateUserGesture) override;
     Protocol::ErrorStringOr<void> setShouldBlackboxURL(const String& url, bool shouldBlackbox, std::optional<bool>&& caseSensitive, std::optional<bool>&& isRegex) final;
+    Protocol::ErrorStringOr<void> setBlackboxBreakpointEvaluations(bool) final;
 
     // JSC::Debugger::Client
     JSC::JSObject* debuggerScopeExtensionObject(JSC::Debugger&, JSC::JSGlobalObject*, JSC::DebuggerCallFrame&) final;
@@ -106,6 +107,7 @@
     void didContinue() final;
     void breakpointActionSound(JSC::BreakpointActionID) final;
     void breakpointActionProbe(JSC::JSGlobalObject*, JSC::BreakpointActionID, unsigned batchId, unsigned sampleId, JSC::JSValue sample) final;
+    void didDeferBreakpointPause(JSC::BreakpointID) final;
 
     bool isPaused() const;
     bool breakpointsActive() const;

Modified: trunk/Source/_javascript_Core/inspector/protocol/Debugger.json (290719 => 290720)


--- trunk/Source/_javascript_Core/inspector/protocol/Debugger.json	2022-03-02 06:05:31 UTC (rev 290719)
+++ trunk/Source/_javascript_Core/inspector/protocol/Debugger.json	2022-03-02 06:42:27 UTC (rev 290720)
@@ -322,6 +322,13 @@
                 { "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_." }
             ]
+        },
+        {
+            "name": "setBlackboxBreakpointEvaluations",
+            "description": "Sets whether evaluation of breakpoint conditions, ignore counts, and actions happen at the location of the breakpoint or are deferred due to blackboxing.",
+            "parameters": [
+                { "name": "blackboxBreakpointEvaluations", "type": "boolean" }
+            ]
         }
     ],
     "events": [

Modified: trunk/Source/WebInspectorUI/ChangeLog (290719 => 290720)


--- trunk/Source/WebInspectorUI/ChangeLog	2022-03-02 06:05:31 UTC (rev 290719)
+++ trunk/Source/WebInspectorUI/ChangeLog	2022-03-02 06:42:27 UTC (rev 290720)
@@ -1,3 +1,41 @@
+2022-03-01  Devin Rousso  <[email protected]>
+
+        Web Inspector: add a switch to control whether breakpoint evaluations (condition, ignore count, actions) are also affected by script blackboxing
+        https://bugs.webkit.org/show_bug.cgi?id=235274
+
+        Reviewed by Patrick Angle.
+
+        Being able to defer breakpoint evaluations until the next actual pause can sometimes be far
+        more useful than doing the breakpoint evaluations at the breakpoint's original location.
+
+        As an example, configuring the All Events breakpoint with a `console.trace()` action and
+        auto-continue enabled would not provide much useful information without also blackboxing
+        breakpoint evaluations as the original pause location of the All Events breakpoint is on the
+        first line of the event handler, meaning that if the script containing the event handler is
+        blackboxed then the `console.trace()` would still only show that location even though the
+        Sources Tab would show the first line of code outside of that script (due to the blackbox).
+        Being able to also blackbox breakpoint evaluations would instead cause the `console.trace()`
+        action to have the same output as the Sources Tab, since it's evaluation would be deferred
+        until execution actually paused, which is further inside the event handler
+
+        * UserInterface/Base/Setting.js:
+        * UserInterface/Controllers/DebuggerManager.js:
+        (WI.DebuggerManager.supportsBlackboxingBreakpointEvaluations): Added.
+        (WI.DebuggerManager.prototype.async initializeTarget):
+        (WI.DebuggerManager.prototype._setBlackboxBreakpointEvaluations): Added.
+        (WI.DebuggerManager.prototype._handleBlackboxBreakpointEvaluationsChange): Added.
+        Create a global `WI.Setting` for this feature and handle changes from the code below.
+
+        * UserInterface/Views/BlackboxSettingsView.js:
+        (WI.BlackboxSettingsView.prototype.initialLayout):
+        * UserInterface/Views/BlackboxSettingsView.css:
+        (.settings-view.blackbox > p > label): Added.
+        (.settings-view.blackbox > p > label > input): Added.
+        (.settings-view.blackbox > p > label > span): Added.
+        Add a checkbox and explanation of this new feature in the Blackbox pane of the Settings Tab.
+
+        * Localizations/en.lproj/localizedStrings.js:
+
 2022-02-28  Devin Rousso  <[email protected]>
 
         Web Inspector: [Flexbox] Add options to show each area's CSS `order` and/or DOM index in the parent flex container

Modified: trunk/Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js (290719 => 290720)


--- trunk/Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js	2022-03-02 06:05:31 UTC (rev 290719)
+++ trunk/Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js	2022-03-02 06:42:27 UTC (rev 290720)
@@ -160,6 +160,7 @@
 /* Label for setting that allows the user to inspect the Web Inspector user interface. */
 localizedStrings["Allow Inspecting Web Inspector @ Experimental Settings"] = "Allow Inspecting Web Inspector";
 localizedStrings["Allow Media Capture on Insecure Sites"] = "Allow Media Capture on Insecure Sites";
+localizedStrings["Also defer evaluating breakpoint conditions, ignore counts, and actions until execution has continued outside of the related script instead of at the breakpoint's location."] = "Also defer evaluating breakpoint conditions, ignore counts, and actions until execution has continued outside of the related script instead of at the breakpoint's location.";
 /* Property title for `font-variant-alternates`. */
 localizedStrings["Alternate Glyphs @ Font Details Sidebar Property"] = "Alternate Glyphs";
 localizedStrings["An error occurred trying to load the resource."] = "An error occurred trying to load the resource.";

Modified: trunk/Source/WebInspectorUI/UserInterface/Base/Setting.js (290719 => 290720)


--- trunk/Source/WebInspectorUI/UserInterface/Base/Setting.js	2022-03-02 06:05:31 UTC (rev 290719)
+++ trunk/Source/WebInspectorUI/UserInterface/Base/Setting.js	2022-03-02 06:42:27 UTC (rev 290720)
@@ -184,6 +184,7 @@
 };
 
 WI.settings = {
+    blackboxBreakpointEvaluations: new WI.Setting("blackbox-breakpoint-evaluations", false),
     canvasRecordingAutoCaptureEnabled: new WI.Setting("canvas-recording-auto-capture-enabled", false),
     canvasRecordingAutoCaptureFrameCount: new WI.Setting("canvas-recording-auto-capture-frame-count", 1),
     consoleAutoExpandTrace: new WI.Setting("console-auto-expand-trace", true),

Modified: trunk/Source/WebInspectorUI/UserInterface/Controllers/DebuggerManager.js (290719 => 290720)


--- trunk/Source/WebInspectorUI/UserInterface/Controllers/DebuggerManager.js	2022-03-02 06:05:31 UTC (rev 290719)
+++ trunk/Source/WebInspectorUI/UserInterface/Controllers/DebuggerManager.js	2022-03-02 06:42:27 UTC (rev 290720)
@@ -43,6 +43,8 @@
 
         WI.targetManager.addEventListener(WI.TargetManager.Event.TargetRemoved, this._targetRemoved, this);
 
+        WI.settings.blackboxBreakpointEvaluations.addEventListener(WI.Setting.Event.Changed, this._handleBlackboxBreakpointEvaluationsChange, this);
+
         if (WI.isEngineeringBuild) {
             WI.settings.engineeringShowInternalScripts.addEventListener(WI.Setting.Event.Changed, this._handleEngineeringShowInternalScriptsSettingChanged, this);
             WI.settings.engineeringPauseForInternalScripts.addEventListener(WI.Setting.Event.Changed, this._handleEngineeringPauseForInternalScriptsSettingChanged, this);
@@ -223,6 +225,8 @@
             }
         }
 
+        this._setBlackboxBreakpointEvaluations(target);
+
         if (WI.isEngineeringBuild) {
             // COMPATIBILITY (iOS 12): DebuggerAgent.setPauseForInternalScripts did not exist yet.
             if (target.hasCommand("Debugger.setPauseForInternalScripts"))
@@ -248,9 +252,16 @@
 
     static supportsBlackboxingScripts()
     {
+        // COMPATIBILITY (iOS 13): Debugger.setShouldBlackboxURL did not exist yet.
         return InspectorBackend.hasCommand("Debugger.setShouldBlackboxURL");
     }
 
+    static supportsBlackboxingBreakpointEvaluations()
+    {
+        // COMPATIBILITY (iOS 15.4): Debugger.setBlackboxBreakpointEvaluations did not exist yet.
+        return InspectorBackend.hasCommand("Debugger.setBlackboxBreakpointEvaluations");
+    }
+
     static pauseReasonFromPayload(payload)
     {
         switch (payload) {
@@ -1255,6 +1266,13 @@
         }
     }
 
+    _setBlackboxBreakpointEvaluations(target)
+    {
+        // COMPATIBILITY (iOS 15.4): Debugger.setBlackboxBreakpointEvaluations did not exist yet.
+        if (target.hasCommand("Debugger.setBlackboxBreakpointEvaluations"))
+            target.DebuggerAgent.setBlackboxBreakpointEvaluations(WI.settings.blackboxBreakpointEvaluations.value);
+    }
+
     _breakpointDisplayLocationDidChange(event)
     {
         if (this._ignoreBreakpointDisplayLocationDidChangeEvent)
@@ -1399,6 +1417,12 @@
             this.dispatchEventToListeners(WI.DebuggerManager.Event.Resumed);
     }
 
+    _handleBlackboxBreakpointEvaluationsChange(event)
+    {
+        for (let target of WI.targets)
+            this._setBlackboxBreakpointEvaluations(target);
+    }
+
     _handleEngineeringShowInternalScriptsSettingChanged(event)
     {
         let eventType = WI.settings.engineeringShowInternalScripts.value ? WI.DebuggerManager.Event.ScriptAdded : WI.DebuggerManager.Event.ScriptRemoved;

Modified: trunk/Source/WebInspectorUI/UserInterface/Views/BlackboxSettingsView.css (290719 => 290720)


--- trunk/Source/WebInspectorUI/UserInterface/Views/BlackboxSettingsView.css	2022-03-02 06:05:31 UTC (rev 290719)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/BlackboxSettingsView.css	2022-03-02 06:42:27 UTC (rev 290720)
@@ -48,6 +48,19 @@
     vertical-align: middle;
 }
 
+.settings-view.blackbox > p > label {
+    display: flex;
+}
+
+.settings-view.blackbox > p > label > input {
+    flex-shrink: 0;
+    margin-inline: 0.5px 4px;
+}
+
+.settings-view.blackbox > p > label > span {
+    margin-top: 1.5px;
+}
+
 .settings-view.blackbox > table {
     border-collapse: collapse;
 }

Modified: trunk/Source/WebInspectorUI/UserInterface/Views/BlackboxSettingsView.js (290719 => 290720)


--- trunk/Source/WebInspectorUI/UserInterface/Views/BlackboxSettingsView.js	2022-03-02 06:05:31 UTC (rev 290719)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/BlackboxSettingsView.js	2022-03-02 06:42:27 UTC (rev 290720)
@@ -106,6 +106,20 @@
             a.append(b);
             return a;
         });
+
+        if (WI.DebuggerManager.supportsBlackboxingBreakpointEvaluations()) {
+            let blackboxBreakpointEvaluationsExplanationElement = this.element.insertBefore(document.createElement("p"), this.element.lastChild);
+            let blackboxBreakpointEvaluationsExplanationLabel = blackboxBreakpointEvaluationsExplanationElement.appendChild(document.createElement("label"));
+
+            let blackboxBreakpointEvaluationsExplanationCheckbox = blackboxBreakpointEvaluationsExplanationLabel.appendChild(document.createElement("input"));
+            blackboxBreakpointEvaluationsExplanationCheckbox.type = "checkbox";
+            blackboxBreakpointEvaluationsExplanationCheckbox.checked = WI.settings.blackboxBreakpointEvaluations.value;
+            blackboxBreakpointEvaluationsExplanationCheckbox.addEventListener("change", (event) => {
+                WI.settings.blackboxBreakpointEvaluations.value = blackboxBreakpointEvaluationsExplanationCheckbox.checked;
+            });
+
+            blackboxBreakpointEvaluationsExplanationLabel.append(WI.UIString("Also defer evaluating breakpoint conditions, ignore counts, and actions until execution has continued outside of the related script instead of at the breakpoint's location."));
+        }
     }
 
     // Private
_______________________________________________
webkit-changes mailing list
[email protected]
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to