Bug 897777 - fix source mapped source resolution when there is no source root; r=jimb
authorNick Fitzgerald <fitzgen@gmail.com>
Fri, 26 Jul 2013 22:27:15 -0700
changeset 140212 55ac60d61c0308cb06ea09a0eef4e53ce7cead52
parent 140210 4874fa438b1c9316ae97485a28531959149465a3
child 140213 69c186ac238c4de55dd1d39861a9e9d100191722
push id1946
push usernfitzgerald@mozilla.com
push dateSat, 27 Jul 2013 05:28:57 +0000
treeherderfx-team@69c186ac238c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjimb
bugs897777
milestone25.0a1
Bug 897777 - fix source mapped source resolution when there is no source root; r=jimb
toolkit/devtools/server/actors/script.js
toolkit/devtools/server/tests/unit/test_sourcemaps-06.js
toolkit/devtools/server/tests/unit/test_sourcemaps-09.js
--- a/toolkit/devtools/server/actors/script.js
+++ b/toolkit/devtools/server/actors/script.js
@@ -3184,50 +3184,71 @@ ThreadSources.prototype = {
    * |aScript| must have a non-null sourceMapURL.
    */
   sourceMap: function TS_sourceMap(aScript) {
     if (aScript.url in this._sourceMapsByGeneratedSource) {
       return this._sourceMapsByGeneratedSource[aScript.url];
     }
     dbg_assert(aScript.sourceMapURL, "Script should have a sourceMapURL");
     let sourceMapURL = this._normalize(aScript.sourceMapURL, aScript.url);
-    let map = this._fetchSourceMap(sourceMapURL)
+    let map = this._fetchSourceMap(sourceMapURL, aScript.url)
       .then((aSourceMap) => {
         for (let s of aSourceMap.sources) {
           this._generatedUrlsByOriginalUrl[s] = aScript.url;
           this._sourceMapsByOriginalSource[s] = resolve(aSourceMap);
         }
         return aSourceMap;
       });
     this._sourceMapsByGeneratedSource[aScript.url] = map;
     return map;
   },
 
   /**
    * Return a promise of a SourceMapConsumer for the source map located at
    * |aAbsSourceMapURL|, which must be absolute. If there is already such a
    * promise extant, return it.
+   *
+   * @param string aAbsSourceMapURL
+   *        The source map URL, in absolute form, not relative.
+   * @param string aScriptURL
+   *        When the source map URL is a data URI, there is no sourceRoot on the
+   *        source map, and the source map's sources are relative, we resolve
+   *        them from aScriptURL.
    */
-  _fetchSourceMap: function TS__fetchSourceMap(aAbsSourceMapURL) {
+  _fetchSourceMap: function TS__fetchSourceMap(aAbsSourceMapURL, aScriptURL) {
     if (aAbsSourceMapURL in this._sourceMaps) {
       return this._sourceMaps[aAbsSourceMapURL];
-    } else {
-      let promise = fetch(aAbsSourceMapURL).then(rawSourceMap => {
-        let map = new SourceMapConsumer(rawSourceMap);
-        let base = aAbsSourceMapURL.replace(/\/[^\/]+$/, '/');
-        if (base.indexOf("data:") !== 0) {
-          map.sourceRoot = map.sourceRoot
-            ? this._normalize(map.sourceRoot, base)
-            : base;
-        }
-        return map;
-      });
-      this._sourceMaps[aAbsSourceMapURL] = promise;
-      return promise;
     }
+
+    let promise = fetch(aAbsSourceMapURL).then(rawSourceMap => {
+      let map = new SourceMapConsumer(rawSourceMap);
+      this._setSourceMapRoot(map, aAbsSourceMapURL, aScriptURL);
+      return map;
+    });
+    this._sourceMaps[aAbsSourceMapURL] = promise;
+    return promise;
+  },
+
+  /**
+   * Sets the source map's sourceRoot to be relative to the source map url.
+   */
+  _setSourceMapRoot: function TS__setSourceMapRoot(aSourceMap, aAbsSourceMapURL,
+                                                   aScriptURL) {
+    const base = this._dirname(
+      aAbsSourceMapURL.indexOf("data:") === 0
+        ? aScriptURL
+        : aAbsSourceMapURL);
+    aSourceMap.sourceRoot = aSourceMap.sourceRoot
+      ? this._normalize(aSourceMap.sourceRoot, base)
+      : base;
+  },
+
+  _dirname: function TS__dirname(aPath) {
+    return Services.io.newURI(
+      ".", null, Services.io.newURI(aPath, null, null)).spec;
   },
 
   /**
    * Returns a promise of the location in the original source if the source is
    * source mapped, otherwise a promise of the same location.
    */
   getOriginalLocation:
   function TS_getOriginalLocation(aSourceUrl, aLine, aColumn) {
--- a/toolkit/devtools/server/tests/unit/test_sourcemaps-06.js
+++ b/toolkit/devtools/server/tests/unit/test_sourcemaps-06.js
@@ -46,19 +46,19 @@ function test_source_content()
   });
 
   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");
+  node.setSourceContent("a.js", "content for http://example.com/www/js/a.js");
+  node.setSourceContent("b.js", "content for http://example.com/www/js/b.js");
+  node.setSourceContent("c.js", "content for http://example.com/www/js/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",
--- a/toolkit/devtools/server/tests/unit/test_sourcemaps-09.js
+++ b/toolkit/devtools/server/tests/unit/test_sourcemaps-09.js
@@ -29,30 +29,30 @@ function test_minified()
 {
   let newSourceFired = false;
 
   gClient.addOneTimeListener("newSource", function _onNewSource(aEvent, aPacket) {
     do_check_eq(aEvent, "newSource");
     do_check_eq(aPacket.type, "newSource");
     do_check_true(!!aPacket.source);
 
-    do_check_eq(aPacket.source.url, "foo.js",
+    do_check_eq(aPacket.source.url, "http://example.com/foo.js",
                 "The new source should be foo.js");
     do_check_eq(aPacket.source.url.indexOf("foo.min.js"), -1,
                 "The new source should not be the minified file");
 
     newSourceFired = true;
   });
 
   gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
     do_check_eq(aEvent, "paused");
     do_check_eq(aPacket.why.type, "debuggerStatement");
 
     const location = {
-      url: "foo.js",
+      url: "http://example.com/foo.js",
       line: 5
     };
 
     gThreadClient.setBreakpoint(location, function (aResponse, bpClient) {
       do_check_true(!aResponse.error);
       testHitBreakpoint();
     });
   });