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 140256 55ac60d61c0308cb06ea09a0eef4e53ce7cead52
parent 140254 4874fa438b1c9316ae97485a28531959149465a3
child 140257 69c186ac238c4de55dd1d39861a9e9d100191722
push id25021
push userryanvm@gmail.com
push dateSun, 28 Jul 2013 01:51:56 +0000
treeherdermozilla-central@015135250e06 [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();
     });
   });