Web Inspector: Debugger: add a Step next that steps by expression
authordrousso@apple.com <drousso@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 15 Apr 2020 02:35:04 +0000 (02:35 +0000)
committerdrousso@apple.com <drousso@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 15 Apr 2020 02:35:04 +0000 (02:35 +0000)
https://bugs.webkit.org/show_bug.cgi?id=210324

Reviewed by Timothy Hatcher.

Source/JavaScriptCore:

Step next is a hybrid of Step over and Step into which continues execution to the next pause
opportunity within the current (or ancestor) call frame. It is especially useful when trying
to debug minified code, such as trying to continue to `c()` in `a() && b() && c();`, where
Step over would continue to the next statement (i.e. after the `;`) and Step in would
continue to the first line inside `a()` (and would require a Step out to get back).

* inspector/protocol/Debugger.json:
* inspector/agents/InspectorDebuggerAgent.h:
* inspector/agents/InspectorDebuggerAgent.cpp:
(Inspector::InspectorDebuggerAgent::stepNext): Added.

* debugger/Debugger.h:
* debugger/Debugger.cpp:
(JSC::Debugger::stepNextExpression): Added.
(JSC::Debugger::atExpression):
(JSC::Debugger::clearNextPauseState):

Source/WebInspectorUI:

Step next is a hybrid of Step over and Step into which continues execution to the next pause
opportunity within the current (or ancestor) call frame. It is especially useful when trying
to debug minified code, such as trying to continue to `c()` in `a() && b() && c();`, where
Step over would continue to the next statement (i.e. after the `;`) and Step in would
continue to the first line inside `a()` (and would require a Step out to get back).

* UserInterface/Controllers/DebuggerManager.js:
(WI.DebuggerManager.prototype.stepNext): Added.

* UserInterface/Base/Main.js:
(WI.contentLoaded):
(WI.debuggerStepNext): Added.
* UserInterface/Views/SourcesNavigationSidebarPanel.js:
(WI.SourcesNavigationSidebarPanel):
(WI.SourcesNavigationSidebarPanel.prototype._handleDebuggerPaused):
(WI.SourcesNavigationSidebarPanel.prototype._handleDebuggerResumed):

* Localizations/en.lproj/localizedStrings.js:
* UserInterface/Images/StepNext.svg: Added.

LayoutTests:

* inspector/debugger/stepping/stepNext.html: Added.
* inspector/debugger/stepping/stepNext-expected.txt: Added.

* inspector/debugger/stepping/stepInto.html:
* inspector/debugger/stepping/stepInto-expected.txt:
* inspector/debugger/stepping/stepOut.html:
* inspector/debugger/stepping/stepOut-expected.txt:
* inspector/debugger/stepping/stepOver.html:
* inspector/debugger/stepping/stepOver-expected.txt:
Renamed functions for clarity and added additional test cases from other commands.

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

21 files changed:
LayoutTests/ChangeLog
LayoutTests/inspector/debugger/stepping/stepInto-expected.txt
LayoutTests/inspector/debugger/stepping/stepInto.html
LayoutTests/inspector/debugger/stepping/stepNext-expected.txt [new file with mode: 0644]
LayoutTests/inspector/debugger/stepping/stepNext.html [new file with mode: 0644]
LayoutTests/inspector/debugger/stepping/stepOut-expected.txt
LayoutTests/inspector/debugger/stepping/stepOut.html
LayoutTests/inspector/debugger/stepping/stepOver-expected.txt
LayoutTests/inspector/debugger/stepping/stepOver.html
Source/JavaScriptCore/ChangeLog
Source/JavaScriptCore/debugger/Debugger.cpp
Source/JavaScriptCore/debugger/Debugger.h
Source/JavaScriptCore/inspector/agents/InspectorDebuggerAgent.cpp
Source/JavaScriptCore/inspector/agents/InspectorDebuggerAgent.h
Source/JavaScriptCore/inspector/protocol/Debugger.json
Source/WebInspectorUI/ChangeLog
Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js
Source/WebInspectorUI/UserInterface/Base/Main.js
Source/WebInspectorUI/UserInterface/Controllers/DebuggerManager.js
Source/WebInspectorUI/UserInterface/Images/StepNext.svg [new file with mode: 0644]
Source/WebInspectorUI/UserInterface/Views/SourcesNavigationSidebarPanel.js

index 007a4ba..110d721 100644 (file)
@@ -1,3 +1,21 @@
+2020-04-14  Devin Rousso  <drousso@apple.com>
+
+        Web Inspector: Debugger: add a Step next that steps by expression
+        https://bugs.webkit.org/show_bug.cgi?id=210324
+
+        Reviewed by Timothy Hatcher.
+
+        * inspector/debugger/stepping/stepNext.html: Added.
+        * inspector/debugger/stepping/stepNext-expected.txt: Added.
+
+        * inspector/debugger/stepping/stepInto.html:
+        * inspector/debugger/stepping/stepInto-expected.txt:
+        * inspector/debugger/stepping/stepOut.html:
+        * inspector/debugger/stepping/stepOut-expected.txt:
+        * inspector/debugger/stepping/stepOver.html:
+        * inspector/debugger/stepping/stepOver-expected.txt:
+        Renamed functions for clarity and added additional test cases from other commands.
+
 2020-04-14  Diego Pino Garcia  <dpino@igalia.com>
 
         [GTK] Gardening, update expectations after r260062
index ce11175..9e5422c 100644 (file)
-ALERT: log 1
 Checking pause locations when stepping with "stepInto".
 
 
 == Running test suite: Debugger.stepInto
 -- Running test case: Debugger.stepInto.statements
 PAUSED (debugger-statement)
-PAUSE AT entry1:12:5
-      8    }
+PAUSE AT testStatements:12:5
+      8    function c() { return "c"; }
       9    
-     10    function entry1() {
+     10    function testStatements() {
  ->  11        |debugger;
      12        let x = 1;
      13        let y = 2;
      14    }
 
-PAUSE AT entry1:13:5
+PAUSE AT testStatements:13:5
       9    
-     10    function entry1() {
+     10    function testStatements() {
      11        debugger;
  ->  12        |let x = 1;
      13        let y = 2;
      14    }
      15    
 
-PAUSE AT entry1:14:5
-     10    function entry1() {
+PAUSE AT testStatements:14:5
+     10    function testStatements() {
      11        debugger;
      12        let x = 1;
  ->  13        |let y = 2;
      14    }
      15    
-     16    function entry2() {
+     16    function testFunctions() {
 
-PAUSE AT entry1:15:2
+PAUSE AT testStatements:15:2
      11        debugger;
      12        let x = 1;
      13        let y = 2;
  ->  14    }|
      15    
-     16    function entry2() {
+     16    function testFunctions() {
      17        debugger;
 
 RESUMED
 
 -- Running test case: Debugger.stepInto.function
 PAUSED (debugger-statement)
-PAUSE AT entry2:18:5
+PAUSE AT testFunctions:18:5
      14    }
      15    
-     16    function entry2() {
+     16    function testFunctions() {
  ->  17        |debugger;
      18        let before = 1;
-     19        testAlert("log 1");
+     19        a();
      20        let after = 2;
 
-PAUSE AT entry2:19:5
+PAUSE AT testFunctions:19:5
      15    
-     16    function entry2() {
+     16    function testFunctions() {
      17        debugger;
  ->  18        |let before = 1;
-     19        testAlert("log 1");
+     19        a();
      20        let after = 2;
      21    }
 
-PAUSE AT entry2:20:5
-     16    function entry2() {
+PAUSE AT testFunctions:20:5
+     16    function testFunctions() {
      17        debugger;
      18        let before = 1;
- ->  19        |testAlert("log 1");
+ ->  19        |a();
      20        let after = 2;
      21    }
      22    
 
-PAUSE AT testAlert:8:5
+PAUSE AT a:7:16
+      3    <script src="../../../http/tests/inspector/resources/inspector-test.js"></script>
       4    <script src="../resources/log-pause-location.js"></script>
       5    <script>
-      6    function testAlert(str) {
- ->   7        |alert(str);
-      8    }
+ ->   6    function a() { |return "a"; }
+      7    function b() { return "b"; }
+      8    function c() { return "c"; }
       9    
-     10    function entry1() {
 
-PAUSE AT testAlert:9:2
+PAUSE AT a:7:29
+      3    <script src="../../../http/tests/inspector/resources/inspector-test.js"></script>
+      4    <script src="../resources/log-pause-location.js"></script>
       5    <script>
-      6    function testAlert(str) {
-      7        alert(str);
- ->   8    }|
+ ->   6    function a() { return "a"; }|
+      7    function b() { return "b"; }
+      8    function c() { return "c"; }
       9    
-     10    function entry1() {
-     11        debugger;
 
-PAUSE AT entry2:21:5
+PAUSE AT testFunctions:21:5
      17        debugger;
      18        let before = 1;
-     19        testAlert("log 1");
+     19        a();
  ->  20        |let after = 2;
      21    }
      22    
-     23    function entry3() {
+     23    function testEval() {
 
-PAUSE AT entry2:22:2
+PAUSE AT testFunctions:22:2
      18        let before = 1;
-     19        testAlert("log 1");
+     19        a();
      20        let after = 2;
  ->  21    }|
      22    
-     23    function entry3() {
+     23    function testEval() {
      24        debugger;
 
 RESUMED
 
 -- Running test case: Debugger.stepInto.eval
 PAUSED (debugger-statement)
-PAUSE AT entry3:25:5
+PAUSE AT testEval:25:5
      21    }
      22    
-     23    function entry3() {
+     23    function testEval() {
  ->  24        |debugger;
      25        let before = 1;
      26        eval("1 + 1");
      27        let after = 2;
 
-PAUSE AT entry3:26:5
+PAUSE AT testEval:26:5
      22    
-     23    function entry3() {
+     23    function testEval() {
      24        debugger;
  ->  25        |let before = 1;
      26        eval("1 + 1");
      27        let after = 2;
      28    }
 
-PAUSE AT entry3:27:5
-     23    function entry3() {
+PAUSE AT testEval:27:5
+     23    function testEval() {
      24        debugger;
      25        let before = 1;
  ->  26        |eval("1 + 1");
@@ -145,22 +144,22 @@ PAUSE AT Eval Code:1:1
 PAUSE AT Eval Code:1:6
 --- Source Unavailable ---
 
-PAUSE AT entry3:28:5
+PAUSE AT testEval:28:5
      24        debugger;
      25        let before = 1;
      26        eval("1 + 1");
  ->  27        |let after = 2;
      28    }
      29    
-     30    function entry4() {
+     30    function testInnerFunction() {
 
-PAUSE AT entry3:29:2
+PAUSE AT testEval:29:2
      25        let before = 1;
      26        eval("1 + 1");
      27        let after = 2;
  ->  28    }|
      29    
-     30    function entry4() {
+     30    function testInnerFunction() {
      31        (function() {
 
 RESUMED
@@ -169,7 +168,7 @@ RESUMED
 PAUSED (debugger-statement)
 PAUSE AT <anonymous>:33:9
      29    
-     30    function entry4() {
+     30    function testInnerFunction() {
      31        (function() {
  ->  32            |debugger;
      33            let inner = 1;
@@ -177,7 +176,7 @@ PAUSE AT <anonymous>:33:9
      35        let outer = 2;
 
 PAUSE AT <anonymous>:34:9
-     30    function entry4() {
+     30    function testInnerFunction() {
      31        (function() {
      32            debugger;
  ->  33            |let inner = 1;
@@ -194,23 +193,346 @@ PAUSE AT <anonymous>:35:6
      36    }
      37    
 
-PAUSE AT entry4:36:5
+PAUSE AT testInnerFunction:36:5
      32            debugger;
      33            let inner = 1;
      34        })();
  ->  35        |let outer = 2;
      36    }
      37    
-     38    // ---------
+     38    function testCommas() {
 
-PAUSE AT entry4:37:2
+PAUSE AT testInnerFunction:37:2
      33            let inner = 1;
      34        })();
      35        let outer = 2;
  ->  36    }|
      37    
-     38    // ---------
-     39    
+     38    function testCommas() {
+     39        debugger;
+
+RESUMED
+
+-- Running test case: Debugger.stepInto.commas
+PAUSED (debugger-statement)
+PAUSE AT testCommas:40:5
+     36    }
+     37    
+     38    function testCommas() {
+ ->  39        |debugger;
+     40        let x = 1,
+     41            y = 2,
+     42            z = 3;
+
+PAUSE AT testCommas:41:5
+     37    
+     38    function testCommas() {
+     39        debugger;
+ ->  40        |let x = 1,
+     41            y = 2,
+     42            z = 3;
+     43        a(), b(), c();
+
+PAUSE AT testCommas:42:9
+     38    function testCommas() {
+     39        debugger;
+     40        let x = 1,
+ ->  41            |y = 2,
+     42            z = 3;
+     43        a(), b(), c();
+     44        true && (a(), b(), c());
+
+PAUSE AT testCommas:43:9
+     39        debugger;
+     40        let x = 1,
+     41            y = 2,
+ ->  42            |z = 3;
+     43        a(), b(), c();
+     44        true && (a(), b(), c());
+     45    }
+
+PAUSE AT testCommas:44:5
+     40        let x = 1,
+     41            y = 2,
+     42            z = 3;
+ ->  43        |a(), b(), c();
+     44        true && (a(), b(), c());
+     45    }
+     46    
+
+PAUSE AT a:7:16
+      3    <script src="../../../http/tests/inspector/resources/inspector-test.js"></script>
+      4    <script src="../resources/log-pause-location.js"></script>
+      5    <script>
+ ->   6    function a() { |return "a"; }
+      7    function b() { return "b"; }
+      8    function c() { return "c"; }
+      9    
+
+PAUSE AT a:7:29
+      3    <script src="../../../http/tests/inspector/resources/inspector-test.js"></script>
+      4    <script src="../resources/log-pause-location.js"></script>
+      5    <script>
+ ->   6    function a() { return "a"; }|
+      7    function b() { return "b"; }
+      8    function c() { return "c"; }
+      9    
+
+PAUSE AT testCommas:44:10
+     40        let x = 1,
+     41            y = 2,
+     42            z = 3;
+ ->  43        a(), |b(), c();
+     44        true && (a(), b(), c());
+     45    }
+     46    
+
+PAUSE AT b:8:16
+      4    <script src="../resources/log-pause-location.js"></script>
+      5    <script>
+      6    function a() { return "a"; }
+ ->   7    function b() { |return "b"; }
+      8    function c() { return "c"; }
+      9    
+     10    function testStatements() {
+
+PAUSE AT b:8:29
+      4    <script src="../resources/log-pause-location.js"></script>
+      5    <script>
+      6    function a() { return "a"; }
+ ->   7    function b() { return "b"; }|
+      8    function c() { return "c"; }
+      9    
+     10    function testStatements() {
+
+PAUSE AT testCommas:44:15
+     40        let x = 1,
+     41            y = 2,
+     42            z = 3;
+ ->  43        a(), b(), |c();
+     44        true && (a(), b(), c());
+     45    }
+     46    
+
+PAUSE AT c:9:16
+      5    <script>
+      6    function a() { return "a"; }
+      7    function b() { return "b"; }
+ ->   8    function c() { |return "c"; }
+      9    
+     10    function testStatements() {
+     11        debugger;
+
+PAUSE AT c:9:29
+      5    <script>
+      6    function a() { return "a"; }
+      7    function b() { return "b"; }
+ ->   8    function c() { return "c"; }|
+      9    
+     10    function testStatements() {
+     11        debugger;
+
+PAUSE AT testCommas:45:5
+     41            y = 2,
+     42            z = 3;
+     43        a(), b(), c();
+ ->  44        |true && (a(), b(), c());
+     45    }
+     46    
+     47    function testChainedExpressions() {
+
+PAUSE AT testCommas:45:14
+     41            y = 2,
+     42            z = 3;
+     43        a(), b(), c();
+ ->  44        true && (|a(), b(), c());
+     45    }
+     46    
+     47    function testChainedExpressions() {
+
+PAUSE AT a:7:16
+      3    <script src="../../../http/tests/inspector/resources/inspector-test.js"></script>
+      4    <script src="../resources/log-pause-location.js"></script>
+      5    <script>
+ ->   6    function a() { |return "a"; }
+      7    function b() { return "b"; }
+      8    function c() { return "c"; }
+      9    
+
+PAUSE AT a:7:29
+      3    <script src="../../../http/tests/inspector/resources/inspector-test.js"></script>
+      4    <script src="../resources/log-pause-location.js"></script>
+      5    <script>
+ ->   6    function a() { return "a"; }|
+      7    function b() { return "b"; }
+      8    function c() { return "c"; }
+      9    
+
+PAUSE AT testCommas:45:19
+     41            y = 2,
+     42            z = 3;
+     43        a(), b(), c();
+ ->  44        true && (a(), |b(), c());
+     45    }
+     46    
+     47    function testChainedExpressions() {
+
+PAUSE AT b:8:16
+      4    <script src="../resources/log-pause-location.js"></script>
+      5    <script>
+      6    function a() { return "a"; }
+ ->   7    function b() { |return "b"; }
+      8    function c() { return "c"; }
+      9    
+     10    function testStatements() {
+
+PAUSE AT b:8:29
+      4    <script src="../resources/log-pause-location.js"></script>
+      5    <script>
+      6    function a() { return "a"; }
+ ->   7    function b() { return "b"; }|
+      8    function c() { return "c"; }
+      9    
+     10    function testStatements() {
+
+PAUSE AT testCommas:45:24
+     41            y = 2,
+     42            z = 3;
+     43        a(), b(), c();
+ ->  44        true && (a(), b(), |c());
+     45    }
+     46    
+     47    function testChainedExpressions() {
+
+PAUSE AT c:9:16
+      5    <script>
+      6    function a() { return "a"; }
+      7    function b() { return "b"; }
+ ->   8    function c() { |return "c"; }
+      9    
+     10    function testStatements() {
+     11        debugger;
+
+PAUSE AT c:9:29
+      5    <script>
+      6    function a() { return "a"; }
+      7    function b() { return "b"; }
+ ->   8    function c() { return "c"; }|
+      9    
+     10    function testStatements() {
+     11        debugger;
+
+PAUSE AT testCommas:46:2
+     42            z = 3;
+     43        a(), b(), c();
+     44        true && (a(), b(), c());
+ ->  45    }|
+     46    
+     47    function testChainedExpressions() {
+     48        debugger;
+
+RESUMED
+
+-- Running test case: Debugger.stepInto.chainedExpressions
+PAUSED (debugger-statement)
+PAUSE AT testChainedExpressions:49:5
+     45    }
+     46    
+     47    function testChainedExpressions() {
+ ->  48        |debugger;
+     49        a() && b() && c();
+     50    }
+     51    
+
+PAUSE AT testChainedExpressions:50:5
+     46    
+     47    function testChainedExpressions() {
+     48        debugger;
+ ->  49        |a() && b() && c();
+     50    }
+     51    
+     52    // ---------
+
+PAUSE AT a:7:16
+      3    <script src="../../../http/tests/inspector/resources/inspector-test.js"></script>
+      4    <script src="../resources/log-pause-location.js"></script>
+      5    <script>
+ ->   6    function a() { |return "a"; }
+      7    function b() { return "b"; }
+      8    function c() { return "c"; }
+      9    
+
+PAUSE AT a:7:29
+      3    <script src="../../../http/tests/inspector/resources/inspector-test.js"></script>
+      4    <script src="../resources/log-pause-location.js"></script>
+      5    <script>
+ ->   6    function a() { return "a"; }|
+      7    function b() { return "b"; }
+      8    function c() { return "c"; }
+      9    
+
+PAUSE AT testChainedExpressions:50:12
+     46    
+     47    function testChainedExpressions() {
+     48        debugger;
+ ->  49        a() && |b() && c();
+     50    }
+     51    
+     52    // ---------
+
+PAUSE AT b:8:16
+      4    <script src="../resources/log-pause-location.js"></script>
+      5    <script>
+      6    function a() { return "a"; }
+ ->   7    function b() { |return "b"; }
+      8    function c() { return "c"; }
+      9    
+     10    function testStatements() {
+
+PAUSE AT b:8:29
+      4    <script src="../resources/log-pause-location.js"></script>
+      5    <script>
+      6    function a() { return "a"; }
+ ->   7    function b() { return "b"; }|
+      8    function c() { return "c"; }
+      9    
+     10    function testStatements() {
+
+PAUSE AT testChainedExpressions:50:19
+     46    
+     47    function testChainedExpressions() {
+     48        debugger;
+ ->  49        a() && b() && |c();
+     50    }
+     51    
+     52    // ---------
+
+PAUSE AT c:9:16
+      5    <script>
+      6    function a() { return "a"; }
+      7    function b() { return "b"; }
+ ->   8    function c() { |return "c"; }
+      9    
+     10    function testStatements() {
+     11        debugger;
+
+PAUSE AT c:9:29
+      5    <script>
+      6    function a() { return "a"; }
+      7    function b() { return "b"; }
+ ->   8    function c() { return "c"; }|
+      9    
+     10    function testStatements() {
+     11        debugger;
+
+PAUSE AT testChainedExpressions:51:2
+     47    function testChainedExpressions() {
+     48        debugger;
+     49        a() && b() && c();
+ ->  50    }|
+     51    
+     52    // ---------
+     53    
 
 RESUMED
 
index 0ba7a5d..2623213 100644 (file)
@@ -4,31 +4,31 @@
 <script src="../../../http/tests/inspector/resources/inspector-test.js"></script>
 <script src="../resources/log-pause-location.js"></script>
 <script>
-function testAlert(str) {
-    alert(str);
-}
+function a() { return "a"; }
+function b() { return "b"; }
+function c() { return "c"; }
 
-function entry1() {
+function testStatements() {
     debugger;
     let x = 1;
     let y = 2;
 }
 
-function entry2() {
+function testFunctions() {
     debugger;
     let before = 1;
-    testAlert("log 1");
+    a();
     let after = 2;
 }
 
-function entry3() {
+function testEval() {
     debugger;
     let before = 1;
     eval("1 + 1");
     let after = 2;
 }
 
-function entry4() {
+function testInnerFunction() {
     (function() {
         debugger;
         let inner = 1;
@@ -36,6 +36,20 @@ function entry4() {
     let outer = 2;
 }
 
+function testCommas() {
+    debugger;
+    let x = 1,
+        y = 2,
+        z = 3;
+    a(), b(), c();
+    true && (a(), b(), c());
+}
+
+function testChainedExpressions() {
+    debugger;
+    a() && b() && c();
+}
+
 // ---------
 
 function test()
@@ -69,25 +83,37 @@ function test()
     addTestCase({
         name: "Debugger.stepInto.statements",
         description: "step-into should step over statements.",
-        expression: "setTimeout(entry1)",
+        expression: "setTimeout(testStatements)",
     });
 
     addTestCase({
         name: "Debugger.stepInto.function",
         description: "step-into should step into function calls.",
-        expression: "setTimeout(entry2)",
+        expression: "setTimeout(testFunctions)",
     });
 
     addTestCase({
         name: "Debugger.stepInto.eval",
         description: "step-into should step into an eval program.",
-        expression: "setTimeout(entry3)",
+        expression: "setTimeout(testEval)",
     });
 
     addTestCase({
         name: "Debugger.stepInto.innerFunction",
         description: "step-into should step out of a function to its caller.",
-        expression: "setTimeout(entry4)",
+        expression: "setTimeout(testInnerFunction)",
+    });
+
+    addTestCase({
+        name: "Debugger.stepInto.commas",
+        description: "step-into should step into each sub-expression within comma expressions.",
+        expression: "setTimeout(testCommas)",
+    });
+
+    addTestCase({
+        name: "Debugger.stepInto.chainedExpressions",
+        description: "step-into should step into each sub-expression within chained expressions.",
+        expression: "setTimeout(testChainedExpressions)",
     });
 
     loadMainPageContent().then(() => {
diff --git a/LayoutTests/inspector/debugger/stepping/stepNext-expected.txt b/LayoutTests/inspector/debugger/stepping/stepNext-expected.txt
new file mode 100644 (file)
index 0000000..ddb1ea2
--- /dev/null
@@ -0,0 +1,352 @@
+Checking pause locations when stepping with "stepNext".
+
+
+== Running test suite: Debugger.stepNext
+-- Running test case: Debugger.stepNext.statements
+PAUSED (debugger-statement)
+PAUSE AT testStatements:12:5
+      8    function c() { return "c"; }
+      9    
+     10    function testStatements() {
+ ->  11        |debugger;
+     12        let x = 1;
+     13        let y = 2;
+     14    }
+
+PAUSE AT testStatements:13:5
+      9    
+     10    function testStatements() {
+     11        debugger;
+ ->  12        |let x = 1;
+     13        let y = 2;
+     14    }
+     15    
+
+PAUSE AT testStatements:14:5
+     10    function testStatements() {
+     11        debugger;
+     12        let x = 1;
+ ->  13        |let y = 2;
+     14    }
+     15    
+     16    function testFunctions() {
+
+PAUSE AT testStatements:15:2
+     11        debugger;
+     12        let x = 1;
+     13        let y = 2;
+ ->  14    }|
+     15    
+     16    function testFunctions() {
+     17        debugger;
+
+RESUMED
+
+-- Running test case: Debugger.stepNext.function
+PAUSED (debugger-statement)
+PAUSE AT testFunctions:18:5
+     14    }
+     15    
+     16    function testFunctions() {
+ ->  17        |debugger;
+     18        let before = 1;
+     19        a();
+     20        let after = 2;
+
+PAUSE AT testFunctions:19:5
+     15    
+     16    function testFunctions() {
+     17        debugger;
+ ->  18        |let before = 1;
+     19        a();
+     20        let after = 2;
+     21    }
+
+PAUSE AT testFunctions:20:5
+     16    function testFunctions() {
+     17        debugger;
+     18        let before = 1;
+ ->  19        |a();
+     20        let after = 2;
+     21    }
+     22    
+
+PAUSE AT testFunctions:21:5
+     17        debugger;
+     18        let before = 1;
+     19        a();
+ ->  20        |let after = 2;
+     21    }
+     22    
+     23    function testEval() {
+
+PAUSE AT testFunctions:22:2
+     18        let before = 1;
+     19        a();
+     20        let after = 2;
+ ->  21    }|
+     22    
+     23    function testEval() {
+     24        debugger;
+
+RESUMED
+
+-- Running test case: Debugger.stepNext.eval
+PAUSED (debugger-statement)
+PAUSE AT testEval:25:5
+     21    }
+     22    
+     23    function testEval() {
+ ->  24        |debugger;
+     25        let before = 1;
+     26        eval("1 + 1");
+     27        let after = 2;
+
+PAUSE AT testEval:26:5
+     22    
+     23    function testEval() {
+     24        debugger;
+ ->  25        |let before = 1;
+     26        eval("1 + 1");
+     27        let after = 2;
+     28    }
+
+PAUSE AT testEval:27:5
+     23    function testEval() {
+     24        debugger;
+     25        let before = 1;
+ ->  26        |eval("1 + 1");
+     27        let after = 2;
+     28    }
+     29    
+
+PAUSE AT testEval:28:5
+     24        debugger;
+     25        let before = 1;
+     26        eval("1 + 1");
+ ->  27        |let after = 2;
+     28    }
+     29    
+     30    function testInnerFunction() {
+
+PAUSE AT testEval:29:2
+     25        let before = 1;
+     26        eval("1 + 1");
+     27        let after = 2;
+ ->  28    }|
+     29    
+     30    function testInnerFunction() {
+     31        (function() {
+
+RESUMED
+
+-- Running test case: Debugger.stepNext.innerFunction
+PAUSED (debugger-statement)
+PAUSE AT <anonymous>:33:9
+     29    
+     30    function testInnerFunction() {
+     31        (function() {
+ ->  32            |debugger;
+     33            let inner = 1;
+     34        })();
+     35        let outer = 2;
+
+PAUSE AT <anonymous>:34:9
+     30    function testInnerFunction() {
+     31        (function() {
+     32            debugger;
+ ->  33            |let inner = 1;
+     34        })();
+     35        let outer = 2;
+     36    }
+
+PAUSE AT <anonymous>:35:6
+     31        (function() {
+     32            debugger;
+     33            let inner = 1;
+ ->  34        }|)();
+     35        let outer = 2;
+     36    }
+     37    
+
+PAUSE AT testInnerFunction:36:5
+     32            debugger;
+     33            let inner = 1;
+     34        })();
+ ->  35        |let outer = 2;
+     36    }
+     37    
+     38    function testCommas() {
+
+PAUSE AT testInnerFunction:37:2
+     33            let inner = 1;
+     34        })();
+     35        let outer = 2;
+ ->  36    }|
+     37    
+     38    function testCommas() {
+     39        debugger;
+
+RESUMED
+
+-- Running test case: Debugger.stepNext.commas
+PAUSED (debugger-statement)
+PAUSE AT testCommas:40:5
+     36    }
+     37    
+     38    function testCommas() {
+ ->  39        |debugger;
+     40        let x = 1,
+     41            y = 2,
+     42            z = 3;
+
+PAUSE AT testCommas:41:5
+     37    
+     38    function testCommas() {
+     39        debugger;
+ ->  40        |let x = 1,
+     41            y = 2,
+     42            z = 3;
+     43        a(), b(), c();
+
+PAUSE AT testCommas:42:9
+     38    function testCommas() {
+     39        debugger;
+     40        let x = 1,
+ ->  41            |y = 2,
+     42            z = 3;
+     43        a(), b(), c();
+     44        true && (a(), b(), c());
+
+PAUSE AT testCommas:43:9
+     39        debugger;
+     40        let x = 1,
+     41            y = 2,
+ ->  42            |z = 3;
+     43        a(), b(), c();
+     44        true && (a(), b(), c());
+     45    }
+
+PAUSE AT testCommas:44:5
+     40        let x = 1,
+     41            y = 2,
+     42            z = 3;
+ ->  43        |a(), b(), c();
+     44        true && (a(), b(), c());
+     45    }
+     46    
+
+PAUSE AT testCommas:44:10
+     40        let x = 1,
+     41            y = 2,
+     42            z = 3;
+ ->  43        a(), |b(), c();
+     44        true && (a(), b(), c());
+     45    }
+     46    
+
+PAUSE AT testCommas:44:15
+     40        let x = 1,
+     41            y = 2,
+     42            z = 3;
+ ->  43        a(), b(), |c();
+     44        true && (a(), b(), c());
+     45    }
+     46    
+
+PAUSE AT testCommas:45:5
+     41            y = 2,
+     42            z = 3;
+     43        a(), b(), c();
+ ->  44        |true && (a(), b(), c());
+     45    }
+     46    
+     47    function testChainedExpressions() {
+
+PAUSE AT testCommas:45:14
+     41            y = 2,
+     42            z = 3;
+     43        a(), b(), c();
+ ->  44        true && (|a(), b(), c());
+     45    }
+     46    
+     47    function testChainedExpressions() {
+
+PAUSE AT testCommas:45:19
+     41            y = 2,
+     42            z = 3;
+     43        a(), b(), c();
+ ->  44        true && (a(), |b(), c());
+     45    }
+     46    
+     47    function testChainedExpressions() {
+
+PAUSE AT testCommas:45:24
+     41            y = 2,
+     42            z = 3;
+     43        a(), b(), c();
+ ->  44        true && (a(), b(), |c());
+     45    }
+     46    
+     47    function testChainedExpressions() {
+
+PAUSE AT testCommas:46:2
+     42            z = 3;
+     43        a(), b(), c();
+     44        true && (a(), b(), c());
+ ->  45    }|
+     46    
+     47    function testChainedExpressions() {
+     48        debugger;
+
+RESUMED
+
+-- Running test case: Debugger.stepNext.chainedExpressions
+PAUSED (debugger-statement)
+PAUSE AT testChainedExpressions:49:5
+     45    }
+     46    
+     47    function testChainedExpressions() {
+ ->  48        |debugger;
+     49        a() && b() && c();
+     50    }
+     51    
+
+PAUSE AT testChainedExpressions:50:5
+     46    
+     47    function testChainedExpressions() {
+     48        debugger;
+ ->  49        |a() && b() && c();
+     50    }
+     51    
+     52    // ---------
+
+PAUSE AT testChainedExpressions:50:12
+     46    
+     47    function testChainedExpressions() {
+     48        debugger;
+ ->  49        a() && |b() && c();
+     50    }
+     51    
+     52    // ---------
+
+PAUSE AT testChainedExpressions:50:19
+     46    
+     47    function testChainedExpressions() {
+     48        debugger;
+ ->  49        a() && b() && |c();
+     50    }
+     51    
+     52    // ---------
+
+PAUSE AT testChainedExpressions:51:2
+     47    function testChainedExpressions() {
+     48        debugger;
+     49        a() && b() && c();
+ ->  50    }|
+     51    
+     52    // ---------
+     53    
+
+RESUMED
+
diff --git a/LayoutTests/inspector/debugger/stepping/stepNext.html b/LayoutTests/inspector/debugger/stepping/stepNext.html
new file mode 100644 (file)
index 0000000..2eddda1
--- /dev/null
@@ -0,0 +1,128 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script src="../../../http/tests/inspector/resources/inspector-test.js"></script>
+<script src="../resources/log-pause-location.js"></script>
+<script>
+function a() { return "a"; }
+function b() { return "b"; }
+function c() { return "c"; }
+
+function testStatements() {
+    debugger;
+    let x = 1;
+    let y = 2;
+}
+
+function testFunctions() {
+    debugger;
+    let before = 1;
+    a();
+    let after = 2;
+}
+
+function testEval() {
+    debugger;
+    let before = 1;
+    eval("1 + 1");
+    let after = 2;
+}
+
+function testInnerFunction() {
+    (function() {
+        debugger;
+        let inner = 1;
+    })();
+    let outer = 2;
+}
+
+function testCommas() {
+    debugger;
+    let x = 1,
+        y = 2,
+        z = 3;
+    a(), b(), c();
+    true && (a(), b(), c());
+}
+
+function testChainedExpressions() {
+    debugger;
+    a() && b() && c();
+}
+
+// ---------
+
+function test()
+{
+    let suite = InspectorTest.createAsyncSuite("Debugger.stepNext");
+
+    // Always step-next when call frames change.
+    WI.debuggerManager.addEventListener(WI.DebuggerManager.Event.CallFramesDidChange, (event) => {
+        if (!WI.debuggerManager.activeCallFrame)
+            return;
+        logPauseLocation();
+        WI.debuggerManager.stepNext();
+    });
+
+    function addTestCase({name, description, expression}) {
+        suite.addTestCase({
+            name, description,
+            test(resolve, reject) {
+                InspectorTest.evaluateInPage(expression);
+                WI.debuggerManager.singleFireEventListener(WI.DebuggerManager.Event.Paused, (event) => {
+                    InspectorTest.log(`PAUSED (${WI.debuggerManager.dataForTarget(WI.debuggerManager.activeCallFrame.target).pauseReason})`);
+                });
+                WI.debuggerManager.singleFireEventListener(WI.DebuggerManager.Event.Resumed, (event) => {
+                    InspectorTest.log("RESUMED");
+                    resolve();
+                });
+            }
+        });
+    }
+
+    addTestCase({
+        name: "Debugger.stepNext.statements",
+        description: "step-next should step over statements.",
+        expression: "setTimeout(testStatements)",
+    });
+
+    addTestCase({
+        name: "Debugger.stepNext.function",
+        description: "step-next should step over function calls.",
+        expression: "setTimeout(testFunctions)",
+    });
+
+    addTestCase({
+        name: "Debugger.stepNext.eval",
+        description: "step-next should step over an eval program.",
+        expression: "setTimeout(testEval)",
+    });
+
+    addTestCase({
+        name: "Debugger.stepNext.innerFunction",
+        description: "step-next should step out of a function to its caller.",
+        expression: "setTimeout(testInnerFunction)",
+    });
+
+    addTestCase({
+        name: "Debugger.stepNext.commas",
+        description: "step-next should step to each sub-expression within comma expressions.",
+        expression: "setTimeout(testCommas)",
+    });
+
+    addTestCase({
+        name: "Debugger.stepNext.chainedExpressions",
+        description: "step-next should step to each sub-expression within chained expressions.",
+        expression: "setTimeout(testChainedExpressions)",
+    });
+
+    loadMainPageContent().then(() => {
+        suite.runTestCasesAndFinish();
+    });
+}
+</script>
+</head>
+<body onload="runTest()">
+<p>Checking pause locations when stepping with "stepNext".</p>
+</body>
+</html>
index 3ef3f3e..16c1a2a 100644 (file)
@@ -4,14 +4,14 @@ Checking pause locations when stepping with "stepOut".
 == Running test suite: Debugger.stepOut
 -- Running test case: Debugger.stepOut.function
 PAUSED (debugger-statement)
-PAUSE AT entry1:8:5
-      4    <script src="../resources/log-pause-location.js"></script>
-      5    <script>
-      6    function entry1() {
- ->   7        |debugger;
-      8        let x = 1;
-      9        let y = 2;
-     10    }
+PAUSE AT testFunctions:12:5
+      8    function c() { return "c"; }
+      9    
+     10    function testFunctions() {
+ ->  11        |debugger;
+     12        let x = 1;
+     13        let y = 2;
+     14    }
 
 RESUMED
 
@@ -24,54 +24,80 @@ RESUMED
 
 -- Running test case: Debugger.stepOut.innerFunction
 PAUSED (debugger-statement)
-PAUSE AT beta:24:9
-     20            beta();
-     21        }
-     22        function beta() {
- ->  23            |debugger;
-     24        }
-     25        alpha();
-     26    }
-
-PAUSE AT alpha:22:6
-     18    function entry3() {
-     19        function alpha() {
-     20            beta();
- ->  21        }|
-     22        function beta() {
-     23            debugger;
-     24        }
-
-PAUSE AT entry3:27:2
-     23            debugger;
-     24        }
-     25        alpha();
- ->  26    }|
-     27    
-     28    function entry4() {
-     29        (function() {
+PAUSE AT beta:28:9
+     24            beta();
+     25        }
+     26        function beta() {
+ ->  27            |debugger;
+     28        }
+     29        alpha();
+     30    }
+
+PAUSE AT alpha:26:6
+     22    function testInnerFunction() {
+     23        function alpha() {
+     24            beta();
+ ->  25        }|
+     26        function beta() {
+     27            debugger;
+     28        }
+
+PAUSE AT testInnerFunction:31:2
+     27            debugger;
+     28        }
+     29        alpha();
+ ->  30    }|
+     31    
+     32    function testAnonymousFunction() {
+     33        (function() {
 
 RESUMED
 
 -- Running test case: Debugger.stepOut.anonymousFunction
 PAUSED (debugger-statement)
-PAUSE AT <anonymous>:31:9
-     27    
-     28    function entry4() {
-     29        (function() {
- ->  30            |debugger;
-     31            let inner = 1;
-     32        })();
-     33        let outer = 2;
-
-PAUSE AT entry4:34:5
-     30            debugger;
-     31            let inner = 1;
-     32        })();
- ->  33        |let outer = 2;
-     34    }
-     35    
-     36    // ---------
+PAUSE AT <anonymous>:35:9
+     31    
+     32    function testAnonymousFunction() {
+     33        (function() {
+ ->  34            |debugger;
+     35            let inner = 1;
+     36        })();
+     37        let outer = 2;
+
+PAUSE AT testAnonymousFunction:38:5
+     34            debugger;
+     35            let inner = 1;
+     36        })();
+ ->  37        |let outer = 2;
+     38    }
+     39    
+     40    function testCommas() {
+
+RESUMED
+
+-- Running test case: Debugger.stepOut.commas
+PAUSED (debugger-statement)
+PAUSE AT testCommas:42:5
+     38    }
+     39    
+     40    function testCommas() {
+ ->  41        |debugger;
+     42        let x = 1,
+     43            y = 2,
+     44            z = 3;
+
+RESUMED
+
+-- Running test case: Debugger.stepOut.chainedExpressions
+PAUSED (debugger-statement)
+PAUSE AT testChainedExpressions:51:5
+     47    }
+     48    
+     49    function testChainedExpressions() {
+ ->  50        |debugger;
+     51        a() && b() && c();
+     52    }
+     53    
 
 RESUMED
 
index bcfa65c..076f73e 100644 (file)
@@ -4,19 +4,23 @@
 <script src="../../../http/tests/inspector/resources/inspector-test.js"></script>
 <script src="../resources/log-pause-location.js"></script>
 <script>
-function entry1() {
+function a() { return "a"; }
+function b() { return "b"; }
+function c() { return "c"; }
+
+function testFunctions() {
     debugger;
     let x = 1;
     let y = 2;
 }
 
-function entry2() {
+function testEval() {
     let before = 1;
     eval("debugger");
     let after = 2;
 }
 
-function entry3() {
+function testInnerFunction() {
     function alpha() {
         beta();
     }
@@ -26,7 +30,7 @@ function entry3() {
     alpha();
 }
 
-function entry4() {
+function testAnonymousFunction() {
     (function() {
         debugger;
         let inner = 1;
@@ -34,6 +38,20 @@ function entry4() {
     let outer = 2;
 }
 
+function testCommas() {
+    debugger;
+    let x = 1,
+        y = 2,
+        z = 3;
+    a(), b(), c();
+    true && (a(), b(), c());
+}
+
+function testChainedExpressions() {
+    debugger;
+    a() && b() && c();
+}
+
 // ---------
 
 function test()
@@ -67,25 +85,37 @@ function test()
     addTestCase({
         name: "Debugger.stepOut.function",
         description: "step-out should leave a function.",
-        expression: "setTimeout(entry1)",
+        expression: "setTimeout(testFunctions)",
     });
 
     addTestCase({
         name: "Debugger.stepOut.eval",
         description: "step-out should step leave an eval program.",
-        expression: "setTimeout(entry2)",
+        expression: "setTimeout(testEval)",
     });
 
     addTestCase({
         name: "Debugger.stepOut.innerFunction",
         description: "step-out should leave a function and end up after its callsite.",
-        expression: "setTimeout(entry3)",
+        expression: "setTimeout(testInnerFunction)",
     });
 
     addTestCase({
         name: "Debugger.stepOut.anonymousFunction",
         description: "step-out should leave an anonymous function and end up after its callsite.",
-        expression: "setTimeout(entry4)",
+        expression: "setTimeout(testAnonymousFunction)",
+    });
+
+    addTestCase({
+        name: "Debugger.stepOut.commas",
+        description: "step-out should not enter any comma separated function calls.",
+        expression: "setTimeout(testCommas)",
+    });
+
+    addTestCase({
+        name: "Debugger.stepOut.chainedExpressions",
+        description: "step-out should not enter any function calls in a chained expression.",
+        expression: "setTimeout(testChainedExpressions)",
     });
 
     loadMainPageContent().then(() => {
index 8840e1e..7c1e58f 100644 (file)
@@ -5,7 +5,7 @@ Checking pause locations when stepping with "stepOver".
 -- Running test case: Debugger.stepOver.statements
 PAUSED (debugger-statement)
 PAUSE AT testStatements:12:5
-      8    }
+      8    function c() { return "c"; }
       9    
      10    function testStatements() {
  ->  11        |debugger;
@@ -127,7 +127,7 @@ PAUSE AT testEval:28:5
  ->  27        |let after = 2;
      28    }
      29    
-     30    function testLocalFunction() {
+     30    function testInnerFunction() {
 
 PAUSE AT testEval:29:2
      25        let before = 1;
@@ -135,7 +135,7 @@ PAUSE AT testEval:29:2
      27        let after = 2;
  ->  28    }|
      29    
-     30    function testLocalFunction() {
+     30    function testInnerFunction() {
      31        (function() {
 
 RESUMED
@@ -144,7 +144,7 @@ RESUMED
 PAUSED (debugger-statement)
 PAUSE AT <anonymous>:33:9
      29    
-     30    function testLocalFunction() {
+     30    function testInnerFunction() {
      31        (function() {
  ->  32            |debugger;
      33            let inner = 1;
@@ -152,7 +152,7 @@ PAUSE AT <anonymous>:33:9
      35        let outer = 2;
 
 PAUSE AT <anonymous>:34:9
-     30    function testLocalFunction() {
+     30    function testInnerFunction() {
      31        (function() {
      32            debugger;
  ->  33            |let inner = 1;
@@ -169,7 +169,7 @@ PAUSE AT <anonymous>:35:6
      36    }
      37    
 
-PAUSE AT testLocalFunction:36:5
+PAUSE AT testInnerFunction:36:5
      32            debugger;
      33            let inner = 1;
      34        })();
@@ -178,7 +178,7 @@ PAUSE AT testLocalFunction:36:5
      37    
      38    function testCommas() {
 
-PAUSE AT testLocalFunction:37:2
+PAUSE AT testInnerFunction:37:2
      33            let inner = 1;
      34        })();
      35        let outer = 2;
@@ -261,7 +261,7 @@ PAUSE AT testCommas:45:5
  ->  44        |true && (a(), b(), c());
      45    }
      46    
-     47    function a() { }
+     47    function testChainedExpressions() {
 
 PAUSE AT testCommas:45:14
      41            y = 2,
@@ -270,7 +270,7 @@ PAUSE AT testCommas:45:14
  ->  44        true && (|a(), b(), c());
      45    }
      46    
-     47    function a() { }
+     47    function testChainedExpressions() {
 
 PAUSE AT testCommas:45:19
      41            y = 2,
@@ -279,7 +279,7 @@ PAUSE AT testCommas:45:19
  ->  44        true && (a(), |b(), c());
      45    }
      46    
-     47    function a() { }
+     47    function testChainedExpressions() {
 
 PAUSE AT testCommas:45:24
      41            y = 2,
@@ -288,7 +288,7 @@ PAUSE AT testCommas:45:24
  ->  44        true && (a(), b(), |c());
      45    }
      46    
-     47    function a() { }
+     47    function testChainedExpressions() {
 
 PAUSE AT testCommas:46:2
      42            z = 3;
@@ -296,8 +296,39 @@ PAUSE AT testCommas:46:2
      44        true && (a(), b(), c());
  ->  45    }|
      46    
-     47    function a() { }
-     48    function b() { }
+     47    function testChainedExpressions() {
+     48        debugger;
+
+RESUMED
+
+-- Running test case: Debugger.stepOver.chainedExpressions
+PAUSED (debugger-statement)
+PAUSE AT testChainedExpressions:49:5
+     45    }
+     46    
+     47    function testChainedExpressions() {
+ ->  48        |debugger;
+     49        a() && b() && c();
+     50    }
+     51    
+
+PAUSE AT testChainedExpressions:50:5
+     46    
+     47    function testChainedExpressions() {
+     48        debugger;
+ ->  49        |a() && b() && c();
+     50    }
+     51    
+     52    // ---------
+
+PAUSE AT testChainedExpressions:51:2
+     47    function testChainedExpressions() {
+     48        debugger;
+     49        a() && b() && c();
+ ->  50    }|
+     51    
+     52    // ---------
+     53    
 
 RESUMED
 
index 6979c76..3bfa162 100644 (file)
@@ -4,9 +4,9 @@
 <script src="../../../http/tests/inspector/resources/inspector-test.js"></script>
 <script src="../resources/log-pause-location.js"></script>
 <script>
-function testAlert(str) {
-    alert(str);
-}
+function a() { return "a"; }
+function b() { return "b"; }
+function c() { return "c"; }
 
 function testStatements() {
     debugger;
@@ -28,7 +28,7 @@ function testEval() {
     let after = 2;
 }
 
-function testLocalFunction() {
+function testInnerFunction() {
     (function() {
         debugger;
         let inner = 1;
@@ -45,9 +45,10 @@ function testCommas() {
     true && (a(), b(), c());
 }
 
-function a() { }
-function b() { }
-function c() { }
+function testChainedExpressions() {
+    debugger;
+    a() && b() && c();
+}
 
 // ---------
 
@@ -100,7 +101,7 @@ function test()
     addTestCase({
         name: "Debugger.stepOver.innerFunction",
         description: "step-over should step out of a function to its caller.",
-        expression: "setTimeout(testLocalFunction)",
+        expression: "setTimeout(testInnerFunction)",
     });
 
     addTestCase({
@@ -109,6 +110,12 @@ function test()
         expression: "setTimeout(testCommas)",
     });
 
+    addTestCase({
+        name: "Debugger.stepOver.chainedExpressions",
+        description: "step-over should step over chained expressions as a single statement.",
+        expression: "setTimeout(testChainedExpressions)",
+    });
+
     loadMainPageContent().then(() => {
         suite.runTestCasesAndFinish();
     });
index 398e932..81b82cb 100644 (file)
@@ -1,3 +1,27 @@
+2020-04-14  Devin Rousso  <drousso@apple.com>
+
+        Web Inspector: Debugger: add a Step next that steps by expression
+        https://bugs.webkit.org/show_bug.cgi?id=210324
+
+        Reviewed by Timothy Hatcher.
+
+        Step next is a hybrid of Step over and Step into which continues execution to the next pause
+        opportunity within the current (or ancestor) call frame. It is especially useful when trying
+        to debug minified code, such as trying to continue to `c()` in `a() && b() && c();`, where
+        Step over would continue to the next statement (i.e. after the `;`) and Step in would
+        continue to the first line inside `a()` (and would require a Step out to get back).
+
+        * inspector/protocol/Debugger.json:
+        * inspector/agents/InspectorDebuggerAgent.h:
+        * inspector/agents/InspectorDebuggerAgent.cpp:
+        (Inspector::InspectorDebuggerAgent::stepNext): Added.
+
+        * debugger/Debugger.h:
+        * debugger/Debugger.cpp:
+        (JSC::Debugger::stepNextExpression): Added.
+        (JSC::Debugger::atExpression):
+        (JSC::Debugger::clearNextPauseState):
+
 2020-04-13  Alexey Shvayka  <shvaikalesh@gmail.com>
 
         REGRESSION (r259587): bterlson/eshost throws during init in strict mode
index d3d3bd1..f474ddb 100644 (file)
@@ -609,6 +609,17 @@ void Debugger::continueProgram()
     notifyDoneProcessingDebuggerEvents();
 }
 
+void Debugger::stepNextExpression()
+{
+    if (!m_isPaused)
+        return;
+
+    m_pauseOnCallFrame = m_currentCallFrame;
+    m_pauseOnStepNext = true;
+    setSteppingMode(SteppingModeEnabled);
+    notifyDoneProcessingDebuggerEvents();
+}
+
 void Debugger::stepIntoStatement()
 {
     if (!m_isPaused)
@@ -803,8 +814,8 @@ void Debugger::atExpression(CallFrame* callFrame)
         return;
     }
 
-    // Only pause at the next expression with step-in and step-out, not step-over.
-    bool shouldAttemptPause = m_pauseAtNextOpportunity || m_pauseOnStepOut;
+    // Only pause at the next expression with step-in, step-next, and step-out.
+    bool shouldAttemptPause = m_pauseAtNextOpportunity || m_pauseOnStepNext || m_pauseOnStepOut;
 
     PauseReasonDeclaration reason(*this, PausedAtExpression);
     updateCallFrame(lexicalGlobalObjectForCallFrame(m_vm, callFrame), callFrame, shouldAttemptPause ? AttemptPause : NoPause);
@@ -910,6 +921,7 @@ void Debugger::clearNextPauseState()
 {
     m_pauseOnCallFrame = nullptr;
     m_pauseAtNextOpportunity = false;
+    m_pauseOnStepNext = false;
     m_pauseOnStepOut = false;
     m_afterBlackboxedScript = false;
 }
index ff7a219..fba44c6 100644 (file)
@@ -108,6 +108,7 @@ public:
     void setPauseOnNextStatement(bool);
     void breakProgram();
     void continueProgram();
+    void stepNextExpression();
     void stepIntoStatement();
     void stepOverStatement();
     void stepOutOfFunction();
@@ -230,6 +231,7 @@ private:
     PauseOnExceptionsState m_pauseOnExceptionsState;
     bool m_pauseOnDebuggerStatements : 1;
     bool m_pauseAtNextOpportunity : 1;
+    bool m_pauseOnStepNext : 1;
     bool m_pauseOnStepOut : 1;
     bool m_pastFirstExpressionInStatement : 1;
     bool m_isPaused : 1;
index baae8b9..5acc3e6 100644 (file)
@@ -767,6 +767,15 @@ void InspectorDebuggerAgent::resume(ErrorString& errorString)
     m_conditionToDispatchResumed = ShouldDispatchResumed::WhenContinued;
 }
 
+void InspectorDebuggerAgent::stepNext(ErrorString& errorString)
+{
+    if (!assertPaused(errorString))
+        return;
+
+    willStepAndMayBecomeIdle();
+    m_scriptDebugServer.stepNextExpression();
+}
+
 void InspectorDebuggerAgent::stepOver(ErrorString& errorString)
 {
     if (!assertPaused(errorString))
index 16a9727..190c9ba 100644 (file)
@@ -72,6 +72,7 @@ public:
     void removeBreakpoint(ErrorString&, const String& breakpointIdentifier) final;
     void continueUntilNextRunLoop(ErrorString&) final;
     void continueToLocation(ErrorString&, const JSON::Object& location) final;
+    void stepNext(ErrorString&) final;
     void stepOver(ErrorString&) final;
     void stepInto(ErrorString&) final;
     void stepOut(ErrorString&) final;
index 2490a45..d1dce57 100644 (file)
             ]
         },
         {
+            "name": "stepNext",
+            "description": "Steps over the expression. This will trigger either a Debugger.paused or Debugger.resumed event."
+        },
+        {
             "name": "stepOver",
             "description": "Steps over the statement. This will trigger either a Debugger.paused or Debugger.resumed event."
         },
index d839ec3..ce27c62 100644 (file)
@@ -1,3 +1,30 @@
+2020-04-14  Devin Rousso  <drousso@apple.com>
+
+        Web Inspector: Debugger: add a Step next that steps by expression
+        https://bugs.webkit.org/show_bug.cgi?id=210324
+
+        Reviewed by Timothy Hatcher.
+
+        Step next is a hybrid of Step over and Step into which continues execution to the next pause
+        opportunity within the current (or ancestor) call frame. It is especially useful when trying
+        to debug minified code, such as trying to continue to `c()` in `a() && b() && c();`, where
+        Step over would continue to the next statement (i.e. after the `;`) and Step in would
+        continue to the first line inside `a()` (and would require a Step out to get back).
+
+        * UserInterface/Controllers/DebuggerManager.js:
+        (WI.DebuggerManager.prototype.stepNext): Added.
+
+        * UserInterface/Base/Main.js:
+        (WI.contentLoaded):
+        (WI.debuggerStepNext): Added.
+        * UserInterface/Views/SourcesNavigationSidebarPanel.js:
+        (WI.SourcesNavigationSidebarPanel):
+        (WI.SourcesNavigationSidebarPanel.prototype._handleDebuggerPaused):
+        (WI.SourcesNavigationSidebarPanel.prototype._handleDebuggerResumed):
+
+        * Localizations/en.lproj/localizedStrings.js:
+        * UserInterface/Images/StepNext.svg: Added.
+
 2020-04-14  Nikita Vasilyev  <nvasilyev@apple.com>
 
         Web Inspector: Don't show tooltips for tabs
index 5759785..6c89f83 100644 (file)
@@ -1179,6 +1179,7 @@ localizedStrings["State"] = "State";
 localizedStrings["Statistics"] = "Statistics";
 localizedStrings["Status"] = "Status";
 localizedStrings["Step"] = "Step";
+localizedStrings["Step (%s or %s)"] = "Step (%s or %s)";
 localizedStrings["Step into (%s or %s)"] = "Step into (%s or %s)";
 localizedStrings["Step out (%s or %s)"] = "Step out (%s or %s)";
 localizedStrings["Step over (%s or %s)"] = "Step over (%s or %s)";
index ac399ab..fed4a86 100644 (file)
@@ -362,6 +362,12 @@ WI.contentLoaded = function()
     WI.stepIntoAlternateKeyboardShortcut = new WI.KeyboardShortcut(WI.KeyboardShortcut.Modifier.CommandOrControl, WI.KeyboardShortcut.Key.Semicolon, WI.debuggerStepInto);
     WI.stepOutAlternateKeyboardShortcut = new WI.KeyboardShortcut(WI.KeyboardShortcut.Modifier.Shift | WI.KeyboardShortcut.Modifier.CommandOrControl, WI.KeyboardShortcut.Key.Semicolon, WI.debuggerStepOut);
 
+    // COMPATIBILITY (iOS 13.4): Debugger.stepNext did not exist yet.
+    if (InspectorBackend.hasCommand("Debugger.stepNext")) {
+        WI.stepNextKeyboardShortcut = new WI.KeyboardShortcut(null, WI.KeyboardShortcut.Key.F9, WI.debuggerStepNext);
+        WI.stepNextAlternateKeyboardShortcut = new WI.KeyboardShortcut(WI.KeyboardShortcut.Modifier.Shift | WI.KeyboardShortcut.Modifier.CommandOrControl, WI.KeyboardShortcut.Key.SingleQuote, WI.debuggerStepNext);
+    }
+
     WI.settingsKeyboardShortcut = new WI.KeyboardShortcut(WI.KeyboardShortcut.Modifier.CommandOrControl, WI.KeyboardShortcut.Key.Comma, WI._handleSettingsKeyboardShortcut);
 
     WI._togglePreviousDockConfigurationKeyboardShortcut = new WI.KeyboardShortcut(WI.KeyboardShortcut.Modifier.CommandOrControl | WI.KeyboardShortcut.Modifier.Shift, "D", WI._togglePreviousDockConfiguration);
@@ -1525,6 +1531,11 @@ WI.debuggerPauseResumeToggle = function(event)
         WI.debuggerManager.pause();
 };
 
+WI.debuggerStepNext = function(event)
+{
+    WI.debuggerManager.stepNext();
+};
+
 WI.debuggerStepOver = function(event)
 {
     WI.debuggerManager.stepOver();
index 409f64f..fb4a954 100644 (file)
@@ -568,6 +568,27 @@ WI.DebuggerManager = class DebuggerManager extends WI.Object
         return Promise.all([managerResult, ...promises]);
     }
 
+    stepNext()
+    {
+        if (!this.paused)
+            return Promise.reject(new Error("Cannot step next because debugger is not paused."));
+
+        let listener = new WI.EventListener(this, true);
+
+        let managerResult = new Promise(function(resolve, reject) {
+            listener.connect(WI.debuggerManager, WI.DebuggerManager.Event.ActiveCallFrameDidChange, resolve);
+        });
+
+        let protocolResult = this._activeCallFrame.target.DebuggerAgent.stepNext()
+            .catch(function(error) {
+                listener.disconnect();
+                console.error("DebuggerManager.stepNext failed: ", error);
+                throw error;
+            });
+
+        return Promise.all([managerResult, protocolResult]);
+    }
+
     stepOver()
     {
         if (!this.paused)
diff --git a/Source/WebInspectorUI/UserInterface/Images/StepNext.svg b/Source/WebInspectorUI/UserInterface/Images/StepNext.svg
new file mode 100644 (file)
index 0000000..db7b8f3
--- /dev/null
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright © 2020 Apple Inc. All rights reserved. -->
+<svg xmlns="http://www.w3.org/2000/svg" id="root" version="1.1" viewBox="0 0 15 15">
+    <rect fill="none" stroke="currentColor" x="3.5" y="10.5" width="8" height="2"/>
+    <path fill="none" stroke="currentColor" d="M 2 5 L 13 5"/>
+    <path fill="none" stroke="currentColor" d="M 10 2 L 13 5 L 10 8"/>
+</svg>
index 094a677..edde47f 100644 (file)
@@ -106,6 +106,17 @@ WI.SourcesNavigationSidebarPanel = class SourcesNavigationSidebarPanel extends W
         this._debuggerStepOutButtonItem.addEventListener(WI.ButtonNavigationItem.Event.Clicked, WI.debuggerStepOut, this);
         this._debuggerStepOutButtonItem.enabled = false;
 
+        // COMPATIBILITY (iOS 13.4): Debugger.stepNext did not exist yet.
+        if (InspectorBackend.hasCommand("Debugger.stepNext")) {
+            this._debuggerStepNextButtonItem = createButtonNavigationitem({
+                identifier: "debugger-step-next",
+                toolTipOrLabel: WI.UIString("Step (%s or %s)").format(WI.stepNextKeyboardShortcut.displayName, WI.stepNextAlternateKeyboardShortcut.displayName),
+                image: "Images/StepNext.svg",
+            });
+            this._debuggerStepNextButtonItem.addEventListener(WI.ButtonNavigationItem.Event.Clicked, WI.debuggerStepNext, this);
+            this._debuggerStepNextButtonItem.enabled = false;
+        }
+
         this._timelineRecordingWarningElement = null;
         this._auditTestWarningElement = null;
         this._breakpointsDisabledWarningElement = null;
@@ -2345,6 +2356,8 @@ WI.SourcesNavigationSidebarPanel = class SourcesNavigationSidebarPanel extends W
         this._debuggerStepOverButtonItem.enabled = true;
         this._debuggerStepIntoButtonItem.enabled = true;
         this._debuggerStepOutButtonItem.enabled = true;
+        if (this._debuggerStepNextButtonItem)
+            this._debuggerStepNextButtonItem.enabled = true;
 
         this.element.classList.add("paused");
     }
@@ -2360,6 +2373,8 @@ WI.SourcesNavigationSidebarPanel = class SourcesNavigationSidebarPanel extends W
         this._debuggerStepOverButtonItem.enabled = false;
         this._debuggerStepIntoButtonItem.enabled = false;
         this._debuggerStepOutButtonItem.enabled = false;
+        if (this._debuggerStepNextButtonItem)
+            this._debuggerStepNextButtonItem.enabled = false;
 
         this.element.classList.remove("paused");
     }