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:22:32 -0700
changeset 152533 aed16c07879306958f006cbd272ff752ed297cdc
parent 152255 9f0ec57368c0782770ff55e304bd26a22036a1ef
child 152535 69c186ac238c4de55dd1d39861a9e9d100191722
push id2859
push userakeybl@mozilla.com
push dateMon, 16 Sep 2013 19:14:59 +0000
treeherdermozilla-beta@87d3c51cd2bf [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjimb
bugs897777
milestone25.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
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();
     });
   });