Bug 1455750 - Provide method for server to skip pausing. r=jimb draft
authorJason Laster <jason.laster.11@gmail.com>
Tue, 22 May 2018 14:25:22 -0400
changeset 798367 63e19bf6826c
parent 798084 b75acf965293
push id110737
push userbmo:jlaster@mozilla.com
push dateTue, 22 May 2018 18:45:49 +0000
reviewersjimb
bugs1455750
milestone62.0a1
Bug 1455750 - Provide method for server to skip pausing. r=jimb
devtools/server/actors/breakpoint.js
devtools/server/actors/thread.js
devtools/server/tests/unit/head_dbg.js
devtools/server/tests/unit/test_stepping-with-skip-breakpoints.js
devtools/server/tests/unit/xpcshell.ini
devtools/shared/client/thread-client.js
--- a/devtools/server/actors/breakpoint.js
+++ b/devtools/server/actors/breakpoint.js
@@ -144,16 +144,17 @@ let BreakpointActor = ActorClassWithSpec
       originalSourceActor,
       originalLine,
       originalColumn
     } = this.threadActor.unsafeSynchronize(
       this.threadActor.sources.getOriginalLocation(generatedLocation));
     let url = originalSourceActor.url;
 
     if (this.threadActor.sources.isBlackBoxed(url)
+        || this.threadActor.skipBreakpoints
         || frame.onStep) {
       return undefined;
     }
 
     // If we're trying to pop this frame, and we see a breakpoint at
     // the spot at which popping started, ignore it.  See bug 970469.
     const locationAtFinish = frame.onPop && frame.onPop.originalLocation;
     if (locationAtFinish &&
--- a/devtools/server/actors/thread.js
+++ b/devtools/server/actors/thread.js
@@ -1506,19 +1506,26 @@ const ThreadActor = ActorClassWithSpec(t
   onDebuggerStatement: function(frame) {
     // Don't pause if we are currently stepping (in or over) or the frame is
     // black-boxed.
     const generatedLocation = this.sources.getFrameLocation(frame);
     const { originalSourceActor } = this.unsafeSynchronize(
       this.sources.getOriginalLocation(generatedLocation));
     const url = originalSourceActor ? originalSourceActor.url : null;
 
-    return this.sources.isBlackBoxed(url) || frame.onStep
-      ? undefined
-      : this._pauseAndRespond(frame, { type: "debuggerStatement" });
+    if (this.skipBreakpoints || this.sources.isBlackBoxed(url) || frame.onStep) {
+      return undefined;
+    }
+
+    return this._pauseAndRespond(frame, { type: "debuggerStatement" });
+  },
+
+  onSkipBreakpoints: function({ skip }) {
+    this.skipBreakpoints = skip;
+    return { skip };
   },
 
   /**
    * A function that the engine calls when an exception has been thrown and has
    * propagated to the specified frame.
    *
    * @param youngestFrame Debugger.Frame
    *        The youngest remaining stack frame.
@@ -1547,17 +1554,17 @@ const ThreadActor = ActorClassWithSpec(t
 
     const generatedLocation = this.sources.getFrameLocation(youngestFrame);
     const { originalSourceActor } = this.unsafeSynchronize(
       this.sources.getOriginalLocation(generatedLocation));
     const url = originalSourceActor ? originalSourceActor.url : null;
 
     // We ignore sources without a url because we do not
     // want to pause at console evaluations or watch expressions.
-    if (!url || this.sources.isBlackBoxed(url)) {
+    if (!url || this.skipBreakpoints || this.sources.isBlackBoxed(url)) {
       return undefined;
     }
 
     try {
       let packet = this._paused(youngestFrame);
       if (!packet) {
         return undefined;
       }
@@ -1750,17 +1757,18 @@ Object.assign(ThreadActor.prototype.requ
   "resume": ThreadActor.prototype.onResume,
   "clientEvaluate": ThreadActor.prototype.onClientEvaluate,
   "frames": ThreadActor.prototype.onFrames,
   "interrupt": ThreadActor.prototype.onInterrupt,
   "eventListeners": ThreadActor.prototype.onEventListeners,
   "releaseMany": ThreadActor.prototype.onReleaseMany,
   "sources": ThreadActor.prototype.onSources,
   "threadGrips": ThreadActor.prototype.onThreadGrips,
-  "prototypesAndProperties": ThreadActor.prototype.onPrototypesAndProperties
+  "prototypesAndProperties": ThreadActor.prototype.onPrototypesAndProperties,
+  "skipBreakpoints": ThreadActor.prototype.onSkipBreakpoints
 });
 
 exports.ThreadActor = ThreadActor;
 
 /**
  * Creates a PauseActor.
  *
  * PauseActors exist for the lifetime of a given debuggee pause.  Used to
--- a/devtools/server/tests/unit/head_dbg.js
+++ b/devtools/server/tests/unit/head_dbg.js
@@ -214,16 +214,26 @@ function findSource(sources, url) {
   return null;
 }
 
 function waitForPause(threadClient) {
   dump("Waiting for pause.\n");
   return waitForEvent(threadClient, "paused");
 }
 
+function waitForProperty(dbg, property) {
+  return new Promise(resolve => {
+    Object.defineProperty(dbg, property, {
+      set(newValue) {
+        resolve(newValue);
+      }
+    });
+  });
+}
+
 function setBreakpoint(sourceClient, location) {
   dump("Setting breakpoint.\n");
   return sourceClient.setBreakpoint(location);
 }
 
 function getPrototypeAndProperties(objClient) {
   dump("getting prototype and properties.\n");
 
new file mode 100644
--- /dev/null
+++ b/devtools/server/tests/unit/test_stepping-with-skip-breakpoints.js
@@ -0,0 +1,100 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+/* eslint-disable no-shadow, max-nested-callbacks */
+
+"use strict";
+
+/**
+ * Check basic step-over functionality with pause points
+ * for the first statement and end of the last statement.
+ */
+
+var gDebuggee;
+var gClient;
+var gCallback;
+
+function run_test() {
+  do_test_pending();
+  run_test_with_server(DebuggerServer, function() {
+    run_test_with_server(WorkerDebuggerServer, do_test_finished);
+  });
+}
+
+function run_test_with_server(server, callback) {
+  gCallback = callback;
+  initTestDebuggerServer(server);
+  gDebuggee = addTestGlobal("test-stepping", server);
+  gClient = new DebuggerClient(server.connectPipe());
+  gClient.connect(test_simple_stepping);
+}
+
+async function test_simple_stepping() {
+  const [attachResponse,, threadClient] = await attachTestTabAndResume(gClient,
+                                                                       "test-stepping");
+  ok(!attachResponse.error, "Should not get an error attaching");
+
+  dumpn("Evaluating test code and waiting for first debugger statement");
+  const dbgStmt = await executeOnNextTickAndWaitForPause(evaluateTestCode, gClient);
+  equal(dbgStmt.frame.where.line, 2, "Should be at debugger statement on line 2");
+  equal(gDebuggee.a, undefined);
+  equal(gDebuggee.b, undefined);
+
+  const source = await getSource(threadClient, "test_stepping-01-test-code.js");
+
+  // Add pause points for the first and end of the last statement.
+  // Note: we intentionally ignore the second statement.
+  source.setPausePoints([{
+    location: {line: 3, column: 8},
+    types: {breakpoint: true, stepOver: true}
+  },
+  {
+    location: {line: 4, column: 14},
+    types: {breakpoint: true, stepOver: true}
+  }]);
+
+  dumpn("Step Over to line 3");
+  const step1 = await stepOver(gClient, threadClient);
+  equal(step1.type, "paused");
+  equal(step1.why.type, "resumeLimit");
+  equal(step1.frame.where.line, 3);
+  equal(step1.frame.where.column, 8);
+
+  equal(gDebuggee.a, undefined);
+  equal(gDebuggee.b, undefined);
+
+  dumpn("Step Over to line 4");
+  const step2 = await stepOver(gClient, threadClient);
+  equal(step2.type, "paused");
+  equal(step2.why.type, "resumeLimit");
+  equal(step2.frame.where.line, 4);
+  equal(step2.frame.where.column, 8);
+
+  equal(gDebuggee.a, 1);
+  equal(gDebuggee.b, undefined);
+
+  dumpn("Step Over to the end of line 4");
+  const step3 = await stepOver(gClient, threadClient);
+  equal(step3.type, "paused");
+  equal(step3.why.type, "resumeLimit");
+  equal(step3.frame.where.line, 4);
+  equal(step3.frame.where.column, 14);
+  equal(gDebuggee.a, 1);
+  equal(gDebuggee.b, 2);
+
+  finishClient(gClient, gCallback);
+}
+
+function evaluateTestCode() {
+  /* eslint-disable */
+  Cu.evalInSandbox(
+    `                                   // 1
+    debugger;                           // 2
+    var a = 1;                          // 3
+    var b = 2;`,                        // 4
+    gDebuggee,
+    "1.8",
+    "test_stepping-01-test-code.js",
+    1
+  );
+  /* eslint-disable */
+}
--- a/devtools/server/tests/unit/xpcshell.ini
+++ b/devtools/server/tests/unit/xpcshell.ini
@@ -184,16 +184,17 @@ reason = bug 1104838
 [test_stepping-02.js]
 [test_stepping-03.js]
 [test_stepping-04.js]
 [test_stepping-05.js]
 [test_stepping-06.js]
 [test_stepping-07.js]
 [test_stepping-08.js]
 [test_stepping-with-pause-points.js]
+[test_stepping-with-skip-breakpoints.js]
 [test_framebindings-01.js]
 [test_framebindings-02.js]
 [test_framebindings-03.js]
 [test_framebindings-04.js]
 [test_framebindings-05.js]
 [test_framebindings-06.js]
 [test_framebindings-07.js]
 [test_pause_exceptions-01.js]
--- a/devtools/shared/client/thread-client.js
+++ b/devtools/shared/client/thread-client.js
@@ -391,16 +391,27 @@ ThreadClient.prototype = {
    */
   getFrames: DebuggerClient.requester({
     type: "frames",
     start: arg(0),
     count: arg(1)
   }),
 
   /**
+   * Toggle pausing via breakpoints in the server.
+   *
+   * @param skip boolean
+   *        Whether the server should skip pausing via breakpoints
+   */
+  skipBreakpoints: DebuggerClient.requester({
+    type: "skipBreakpoints",
+    skip: arg(0),
+  }),
+
+  /**
    * An array of cached frames. Clients can observe the framesadded and
    * framescleared event to keep up to date on changes to this cache,
    * and can fill it using the fillFrames method.
    */
   get cachedFrames() {
     return this._frameCache;
   },