Bug 795917 - Get executable lines through the remote debugging protocol;r=ejpbruel
authorSeris <seerriss@gmail.com>
Wed, 01 Oct 2014 17:57:49 +0100
changeset 208179 81f24d931d8926cf2aab90499c66958c60558962
parent 208178 a1d7b16101c718acb913416bd36608c098c29b87
child 208180 e8b947548ce64607f1dbfc952a4c9d61d619485e
push id9100
push userejpbruel@mozilla.com
push dateWed, 01 Oct 2014 16:57:32 +0000
treeherderfx-team@81f24d931d89 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersejpbruel
bugs795917
milestone35.0a1
Bug 795917 - Get executable lines through the remote debugging protocol;r=ejpbruel
toolkit/components/telemetry/Histograms.json
toolkit/devtools/client/dbg-client.jsm
toolkit/devtools/server/actors/script.js
toolkit/devtools/server/tests/unit/test_get-executable-lines-source-map.js
toolkit/devtools/server/tests/unit/test_get-executable-lines.js
toolkit/devtools/server/tests/unit/xpcshell.ini
--- a/toolkit/components/telemetry/Histograms.json
+++ b/toolkit/components/telemetry/Histograms.json
@@ -5724,16 +5724,30 @@
   "COOKIES_3RDPARTY_NUM_ATTEMPTS_BLOCKED": {
     "expires_in_version": "default",
     "kind": "linear",
     "low": "10",
     "high": "500",
     "n_buckets": "50",
     "description": "The total number of distinct attempts by third-party sites to place cookies which have been rejected.  Measures are normalized per 24h."
   },
+  "DEVTOOLS_DEBUGGER_RDP_LOCAL_GET_EXECUTABLE_LINES_MS": {
+    "expires_in_version": "never",
+    "kind": "exponential",
+    "high": "10000",
+    "n_buckets": "1000",
+    "description": "The time (in milliseconds) that it took a 'getExecutableLines' request to go round trip."
+  },
+  "DEVTOOLS_DEBUGGER_RDP_REMOTE_GET_EXECUTABLE_LINES_MS": {
+    "expires_in_version": "never",
+    "kind": "exponential",
+    "high": "10000",
+    "n_buckets": "1000",
+    "description": "The time (in milliseconds) that it took a 'getExecutableLines' request to go round trip."
+  },
   "DEVTOOLS_DEBUGGER_RDP_LOCAL_BLACKBOX_MS": {
     "expires_in_version": "never",
     "kind": "exponential",
     "high": "10000",
     "n_buckets": "1000",
     "description": "The time (in milliseconds) that it took a 'blackbox' request to go round trip."
   },
   "DEVTOOLS_DEBUGGER_RDP_REMOTE_BLACKBOX_MS": {
--- a/toolkit/devtools/client/dbg-client.jsm
+++ b/toolkit/devtools/client/dbg-client.jsm
@@ -2421,16 +2421,33 @@ SourceClient.prototype = {
           this._activeThread.emit("blackboxchange", this);
         }
       }
       return aResponse;
     }
   }),
 
   /**
+   * Get Executable Lines from a source
+   *
+   * @param aCallback Function
+   *        The callback function called when we receive the response from the server.
+   */
+  getExecutableLines: function(cb){
+    let packet = {
+      to: this._form.actor,
+      type: "getExecutableLines"
+    };
+
+    this._client.request(packet, res => {
+      cb(res.lines);
+    });
+  },
+
+  /**
    * Get a long string grip for this SourceClient's source.
    */
   source: function (aCallback) {
     let packet = {
       to: this._form.actor,
       type: "source"
     };
     this._client.request(packet, aResponse => {
--- a/toolkit/devtools/server/actors/script.js
+++ b/toolkit/devtools/server/actors/script.js
@@ -2638,16 +2638,76 @@ SourceActor.prototype = {
     sourceFetched.then(({ contentType }) => {
       this._contentType = contentType;
     });
 
     return sourceFetched;
   },
 
   /**
+   * Get all executable lines from the current source
+   * @return Array - Executable lines of the current script
+   **/
+  getExecutableLines: function () {
+    // Check if the original source is source mapped
+    let packet = {
+      from: this.actorID
+    };
+
+    let lines;
+
+    if (this._sourceMap) {
+      lines = new Set();
+
+      // Position of executable lines in the generated source
+      let offsets = this.getExecutableOffsets(this._generatedSource, false);
+      for (let offset of offsets) {
+        let {line, source} = this._sourceMap.originalPositionFor({
+          line: offset.lineNumber,
+          column: offset.columnNumber
+        });
+
+        if (source === this._url) {
+          lines.add(line);
+        }
+      }
+    } else {
+      // Converting the set given by getExecutableOffsets to an array
+      lines = this.getExecutableOffsets(this._url, true);
+    }
+
+    // Converting the Set into an array
+    packet.lines = [line for (line of lines)];
+    packet.lines.sort((a, b) => {
+      return a - b;
+    });
+
+    return packet;
+  },
+
+  /**
+   * Extract all executable offsets from the given script
+   * @param String url - extract offsets of the script with this url
+   * @param Boolean onlyLine - will return only the line number
+   * @return Set - Executable offsets/lines of the script
+   **/
+  getExecutableOffsets: function (url, onlyLine) {
+    let offsets = new Set();
+    for (let s of this.threadActor.dbg.findScripts(this.threadActor.global)) {
+      if (s.url === url) {
+        for (let offset of s.getAllColumnOffsets()) {
+          offsets.add(onlyLine ? offset.lineNumber : offset);
+        }
+      }
+    }
+
+    return offsets;
+  },
+
+  /**
    * Handler for the "source" packet.
    */
   onSource: function () {
     return resolve(this._init)
       .then(this._getSourceText)
       .then(({ content, contentType }) => {
         return {
           from: this.actorID,
@@ -2853,17 +2913,18 @@ SourceActor.prototype = {
   }
 };
 
 SourceActor.prototype.requestTypes = {
   "source": SourceActor.prototype.onSource,
   "blackbox": SourceActor.prototype.onBlackBox,
   "unblackbox": SourceActor.prototype.onUnblackBox,
   "prettyPrint": SourceActor.prototype.onPrettyPrint,
-  "disablePrettyPrint": SourceActor.prototype.onDisablePrettyPrint
+  "disablePrettyPrint": SourceActor.prototype.onDisablePrettyPrint,
+  "getExecutableLines": SourceActor.prototype.getExecutableLines
 };
 
 
 /**
  * Determine if a given value is non-primitive.
  *
  * @param Any aValue
  *        The value to test.
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/server/tests/unit/test_get-executable-lines-source-map.js
@@ -0,0 +1,56 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * Test if getExecutableLines return correct information
+ */
+
+let gDebuggee;
+let gClient;
+let gThreadClient;
+
+const SOURCE_MAPPED_FILE = getFileUrl("sourcemapped.js");
+
+function run_test() {
+  initTestDebuggerServer();
+  gDebuggee = addTestGlobal("test-get-executable-lines");
+  gClient = new DebuggerClient(DebuggerServer.connectPipe());
+  gClient.connect(function _onConnect() {
+    attachTestTabAndResume(
+      gClient,
+      "test-get-executable-lines",
+      function (aResponse, aTabClient, aThreadClient) {
+        gThreadClient = aThreadClient;
+        test_executable_lines();
+      }
+    );
+  });
+
+  do_test_pending();
+}
+
+function test_executable_lines() {
+  gClient.addOneTimeListener("newSource", function _onNewSource(evt, packet) {
+    do_check_eq(evt, "newSource");
+
+    gThreadClient.getSources(function ({error, sources}) {
+      do_check_true(!error);
+      let source = gThreadClient.source(sources[0]);
+      source.getExecutableLines(function(lines){
+        do_check_true(arrays_equal([1, 2, 4, 6], lines));
+        finishClient(gClient);
+      });
+    });
+  });
+
+  let code = readFile("sourcemapped.js") + "\n//# sourceMappingURL=" +
+    getFileUrl("source-map-data/sourcemapped.map");
+
+  Components.utils.evalInSandbox(code, gDebuggee, "1.8",
+    SOURCE_MAPPED_FILE, 1);
+}
+
+function arrays_equal(a,b) {
+  return !(a<b || b<a);
+}
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/server/tests/unit/test_get-executable-lines.js
@@ -0,0 +1,55 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * Test if getExecutableLines return correct information
+ */
+
+let gDebuggee;
+let gClient;
+let gThreadClient;
+
+const SOURCE_MAPPED_FILE = getFileUrl("sourcemapped.js");
+
+function run_test() {
+  initTestDebuggerServer();
+  gDebuggee = addTestGlobal("test-get-executable-lines");
+  gClient = new DebuggerClient(DebuggerServer.connectPipe());
+  gClient.connect(function _onConnect() {
+    attachTestTabAndResume(
+      gClient,
+      "test-get-executable-lines",
+      function (aResponse, aTabClient, aThreadClient) {
+        gThreadClient = aThreadClient;
+        test_executable_lines();
+      }
+    );
+  });
+
+  do_test_pending();
+}
+
+function test_executable_lines() {
+  gClient.addOneTimeListener("newSource", function _onNewSource(evt, packet) {
+    do_check_eq(evt, "newSource");
+
+    gThreadClient.getSources(function ({error, sources}) {
+      do_check_true(!error);
+      let source = gThreadClient.source(sources[0]);
+      source.getExecutableLines(function(lines){
+        do_check_true(arrays_equal([2, 3, 5, 6, 7, 8, 12, 14, 16], lines));
+        finishClient(gClient);
+      });
+    });
+  });
+
+  let code = readFile("sourcemapped.js");
+
+  Components.utils.evalInSandbox(code, gDebuggee, "1.8",
+    SOURCE_MAPPED_FILE, 1);
+}
+
+function arrays_equal(a,b) {
+  return !(a<b || b<a);
+}
\ No newline at end of file
--- a/toolkit/devtools/server/tests/unit/xpcshell.ini
+++ b/toolkit/devtools/server/tests/unit/xpcshell.ini
@@ -208,12 +208,10 @@ reason = bug 820380
 reason = bug 937197
 [test_layout-reflows-observer.js]
 [test_protocolSpec.js]
 [test_registerClient.js]
 [test_client_request.js]
 [test_monitor_actor.js]
 [test_symbols-01.js]
 [test_symbols-02.js]
-[test_memory_footprint.js]
-run-sequentially = measure memory, has to be run solo
-skip-if = os != 'linux' || debug || asan
-reason = bug 1014071
+[test_get-executable-lines.js]
+[test_get-executable-lines-source-map.js]