Bug 852792 - load sources from the 'sourcesContent' field in a source map, if available; r=rcampbell
authorNick Fitzgerald <fitzgen@gmail.com>
Mon, 13 May 2013 14:16:00 +0300
changeset 139521 fa63467ea33034052826d7fd9f5f85c07e037d0f
parent 139520 69598cd0437c2a8d72483402fd8e7862d3fac247
child 139522 0c312bf9446a310f5f6080a214ba12e9589b862b
push id3911
push userakeybl@mozilla.com
push dateMon, 24 Jun 2013 20:17:26 +0000
treeherdermozilla-aurora@7e26ca8db92b [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersrcampbell
bugs852792
milestone24.0a1
Bug 852792 - load sources from the 'sourcesContent' field in a source map, if available; r=rcampbell
toolkit/devtools/debugger/server/dbg-script-actors.js
toolkit/devtools/debugger/tests/unit/test_sourcemaps-06.js
toolkit/devtools/debugger/tests/unit/xpcshell.ini
--- a/toolkit/devtools/debugger/server/dbg-script-actors.js
+++ b/toolkit/devtools/debugger/server/dbg-script-actors.js
@@ -1376,20 +1376,23 @@ PauseScopedActor.prototype = {
 
 /**
  * A SourceActor provides information about the source of a script.
  *
  * @param aUrl String
  *        The url of the source we are representing.
  * @param aThreadActor ThreadActor
  *        The current thread actor.
+ * @param aSourceContent String
+ *        Optional. The contents of the source, if we already know it.
  */
-function SourceActor(aUrl, aThreadActor) {
+function SourceActor(aUrl, aThreadActor, aSourceContent=null) {
   this._threadActor = aThreadActor;
   this._url = aUrl;
+  this._sourceContent = aSourceContent;
 }
 
 SourceActor.prototype = {
   constructor: SourceActor,
   actorPrefix: "source",
 
   get threadActor() this._threadActor,
   get url() this._url,
@@ -1407,16 +1410,24 @@ SourceActor.prototype = {
       delete this.registeredPool.sourceActors[this.actorID];
     }
   },
 
   /**
    * Handler for the "source" packet.
    */
   onSource: function SA_onSource(aRequest) {
+    if (this._sourceContent) {
+      return {
+        from: this.actorID,
+        source: this.threadActor.createValueGrip(
+          this._sourceContent, this.threadActor.threadLifetimePool)
+      };
+    }
+
     return fetch(this._url)
       .then(function(aSource) {
         return this.threadActor.createValueGrip(
           aSource, this.threadActor.threadLifetimePool);
       }.bind(this))
       .then(function (aSourceGrip) {
         return {
           from: this.actorID,
@@ -2430,29 +2441,32 @@ function ThreadSources(aThreadActor, aUs
 
 ThreadSources.prototype = {
   /**
    * Add a source to the current set of sources.
    *
    * Right now this takes a URL, but in the future it should
    * take a Debugger.Source. See bug 637572.
    *
-   * @param string the source URL.
+   * @param String aURL
+   *        The source URL.
+   * @param String aSourceContent
+   *        Optional. The content of the source, if we already know it.
    * @returns a SourceActor representing the source or null.
    */
-  source: function TS_source(aURL) {
+  source: function TS_source(aURL, aSourceContent=null) {
     if (!this._allow(aURL)) {
       return null;
     }
 
     if (aURL in this._sourceActors) {
       return this._sourceActors[aURL];
     }
 
-    let actor = new SourceActor(aURL, this._thread);
+    let actor = new SourceActor(aURL, this._thread, aSourceContent);
     this._thread.threadLifetimePool.addActor(actor);
     this._sourceActors[aURL] = actor;
     try {
       this._onNewSource(actor);
     } catch (e) {
       reportError(e);
     }
     return actor;
@@ -2464,17 +2478,18 @@ ThreadSources.prototype = {
   sourcesForScript: function TS_sourcesForScript(aScript) {
     if (!this._useSourceMaps || !aScript.sourceMapURL) {
       return resolve([this.source(aScript.url)].filter(isNotNull));
     }
 
     return this.sourceMap(aScript)
       .then((aSourceMap) => {
         return [
-          this.source(s) for (s of aSourceMap.sources)
+          this.source(s, aSourceMap.sourceContentFor(s))
+          for (s of aSourceMap.sources)
         ];
       }, (e) => {
         reportError(e);
         delete this._sourceMaps[this._normalize(aScript.sourceMapURL, aScript.url)];
         delete this._sourceMapsByGeneratedSource[aScript.url];
         return [this.source(aScript.url)];
       })
       .then(function (aSources) {
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/debugger/tests/unit/test_sourcemaps-06.js
@@ -0,0 +1,86 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Check that we can load sources whose content is embedded in the
+ * "sourcesContent" field of a source map.
+ */
+
+var gDebuggee;
+var gClient;
+var gThreadClient;
+
+Components.utils.import("resource:///modules/devtools/SourceMap.jsm");
+
+function run_test()
+{
+  initTestDebuggerServer();
+  gDebuggee = addTestGlobal("test-source-map");
+  gClient = new DebuggerClient(DebuggerServer.connectPipe());
+  gClient.connect(function() {
+    attachTestTabAndResume(gClient, "test-source-map", function(aResponse, aTabClient, aThreadClient) {
+      gThreadClient = aThreadClient;
+      test_source_content();
+    });
+  });
+  do_test_pending();
+}
+
+function test_source_content()
+{
+  let numNewSources = 0;
+
+  gClient.addListener("newSource", function _onNewSource(aEvent, aPacket) {
+    if (++numNewSources !== 3) {
+      return;
+    }
+    gClient.removeListener("newSource", _onNewSource);
+
+    gThreadClient.getSources(function (aResponse) {
+      do_check_true(!aResponse.error, "Should not get an error");
+
+      testContents(aResponse.sources, () => {
+        finishClient(gClient);
+      });
+    });
+  });
+
+  let node = new SourceNode(null, null, null, [
+    new SourceNode(1, 0, "a.js", "function a() { return 'a'; }\n"),
+    new SourceNode(1, 0, "b.js", "function b() { return 'b'; }\n"),
+    new SourceNode(1, 0, "c.js", "function c() { return 'c'; }\n"),
+  ]);
+
+  node.setSourceContent("a.js", "content for a.js");
+  node.setSourceContent("b.js", "content for b.js");
+  node.setSourceContent("c.js", "content for c.js");
+
+  let { code, map } = node.toStringWithSourceMap({
+    file: "abc.js"
+  });
+
+  code += "//@ sourceMappingURL=data:text/json;base64," + btoa(map.toString());
+
+  Components.utils.evalInSandbox(code, gDebuggee, "1.8",
+                                 "http://example.com/www/js/abc.js", 1);
+}
+
+function testContents(aSources, aCallback) {
+  if (aSources.length === 0) {
+    return aCallback();
+  }
+
+  let source = aSources[0];
+  let sourceClient = gThreadClient.source(aSources[0]);
+
+  sourceClient.source((aResponse) => {
+    do_check_true(!aResponse.error,
+                  "Should not get an error loading the source from sourcesContent");
+
+    let expectedContent = "content for " + source.url;
+    do_check_eq(aResponse.source, expectedContent,
+                "Should have the expected source content");
+
+    testContents(aSources.slice(1), aCallback);
+  });
+}
--- a/toolkit/devtools/debugger/tests/unit/xpcshell.ini
+++ b/toolkit/devtools/debugger/tests/unit/xpcshell.ini
@@ -82,16 +82,17 @@ reason = bug 820380
 [test_sourcemaps-02.js]
 [test_sourcemaps-03.js]
 [test_sourcemaps-04.js]
 skip-if = toolkit == "gonk"
 reason = bug 820380
 [test_sourcemaps-05.js]
 skip-if = toolkit == "gonk"
 reason = bug 820380
+[test_sourcemaps-06.js]
 [test_objectgrips-01.js]
 [test_objectgrips-02.js]
 [test_objectgrips-03.js]
 [test_objectgrips-04.js]
 [test_interrupt.js]
 [test_stepping-01.js]
 [test_stepping-02.js]
 [test_stepping-03.js]