Bug 1076887 - Update the tree's copy of the mozilla/source-map library to 0.1.40. r=past
authorNick Fitzgerald <fitzgen@gmail.com>
Thu, 02 Oct 2014 08:44:00 +0200
changeset 233475 0e85dd2b1635f8ce164936e013ce8424fe3c1bdd
parent 233474 047f52c44212642d3c3910936c14f08775ea8b8d
child 233476 ce042529cac3b1c006171d3ba4e119f8760ab862
push id611
push userraliiev@mozilla.com
push dateMon, 05 Jan 2015 23:23:16 +0000
treeherdermozilla-release@345cd3b9c445 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerspast
bugs1076887
milestone35.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 1076887 - Update the tree's copy of the mozilla/source-map library to 0.1.40. r=past
toolkit/devtools/sourcemap/SourceMap.jsm
toolkit/devtools/sourcemap/tests/unit/Utils.jsm
toolkit/devtools/sourcemap/tests/unit/test_base64_vlq.js
toolkit/devtools/sourcemap/tests/unit/test_dog_fooding.js
toolkit/devtools/sourcemap/tests/unit/test_source_map_consumer.js
toolkit/devtools/sourcemap/tests/unit/test_source_map_generator.js
toolkit/devtools/sourcemap/tests/unit/test_source_node.js
toolkit/devtools/sourcemap/tests/unit/test_util.js
--- a/toolkit/devtools/sourcemap/SourceMap.jsm
+++ b/toolkit/devtools/sourcemap/SourceMap.jsm
@@ -1,9 +1,9 @@
-/* -*- js-indent-level: 2; indent-tabs-mode: nil -*- */
+/* -*- Mode: js; js-indent-level: 2; -*- */
 /*
  * Copyright 2011 Mozilla Foundation and contributors
  * Licensed under the New BSD license. See LICENSE or:
  * http://opensource.org/licenses/BSD-3-Clause
  */
 
 /*
  * WARNING!
@@ -41,17 +41,17 @@ define('source-map/source-map-consumer',
    * following attributes:
    *
    *   - version: Which version of the source map spec this map is following.
    *   - sources: An array of URLs to the original source files.
    *   - names: An array of identifiers which can be referrenced by individual mappings.
    *   - sourceRoot: Optional. The URL root from which all sources are relative.
    *   - sourcesContent: Optional. An array of contents of the original source files.
    *   - mappings: A string of base64 VLQs which contain the actual mappings.
-   *   - file: The generated file this source map is associated with.
+   *   - file: Optional. The generated file this source map is associated with.
    *
    * Here is an example source map, taken from the source map spec[0]:
    *
    *     {
    *       version : 3,
    *       file: "out.js",
    *       sourceRoot : "",
    *       sources: ["foo.js", "bar.js"],
@@ -128,17 +128,17 @@ define('source-map/source-map-consumer',
   SourceMapConsumer.prototype._version = 3;
 
   /**
    * The list of original sources.
    */
   Object.defineProperty(SourceMapConsumer.prototype, 'sources', {
     get: function () {
       return this._sources.toArray().map(function (s) {
-        return this.sourceRoot ? util.join(this.sourceRoot, s) : s;
+        return this.sourceRoot != null ? util.join(this.sourceRoot, s) : s;
       }, this);
     }
   });
 
   // `__generatedMappings` and `__originalMappings` are arrays that hold the
   // parsed mapping coordinates from the source map's "mappings" attribute. They
   // are lazily instantiated, accessed via the `_generatedMappings` and
   // `_originalMappings` getters respectively, and we only parse the mappings
@@ -189,96 +189,102 @@ define('source-map/source-map-consumer',
         this.__originalMappings = [];
         this._parseMappings(this._mappings, this.sourceRoot);
       }
 
       return this.__originalMappings;
     }
   });
 
+  SourceMapConsumer.prototype._nextCharIsMappingSeparator =
+    function SourceMapConsumer_nextCharIsMappingSeparator(aStr) {
+      var c = aStr.charAt(0);
+      return c === ";" || c === ",";
+    };
+
   /**
    * Parse the mappings in a string in to a data structure which we can easily
    * query (the ordered arrays in the `this.__generatedMappings` and
    * `this.__originalMappings` properties).
    */
   SourceMapConsumer.prototype._parseMappings =
     function SourceMapConsumer_parseMappings(aStr, aSourceRoot) {
       var generatedLine = 1;
       var previousGeneratedColumn = 0;
       var previousOriginalLine = 0;
       var previousOriginalColumn = 0;
       var previousSource = 0;
       var previousName = 0;
-      var mappingSeparator = /^[,;]/;
       var str = aStr;
+      var temp = {};
       var mapping;
-      var temp;
 
       while (str.length > 0) {
         if (str.charAt(0) === ';') {
           generatedLine++;
           str = str.slice(1);
           previousGeneratedColumn = 0;
         }
         else if (str.charAt(0) === ',') {
           str = str.slice(1);
         }
         else {
           mapping = {};
           mapping.generatedLine = generatedLine;
 
           // Generated column.
-          temp = base64VLQ.decode(str);
+          base64VLQ.decode(str, temp);
           mapping.generatedColumn = previousGeneratedColumn + temp.value;
           previousGeneratedColumn = mapping.generatedColumn;
           str = temp.rest;
 
-          if (str.length > 0 && !mappingSeparator.test(str.charAt(0))) {
+          if (str.length > 0 && !this._nextCharIsMappingSeparator(str)) {
             // Original source.
-            temp = base64VLQ.decode(str);
+            base64VLQ.decode(str, temp);
             mapping.source = this._sources.at(previousSource + temp.value);
             previousSource += temp.value;
             str = temp.rest;
-            if (str.length === 0 || mappingSeparator.test(str.charAt(0))) {
+            if (str.length === 0 || this._nextCharIsMappingSeparator(str)) {
               throw new Error('Found a source, but no line and column');
             }
 
             // Original line.
-            temp = base64VLQ.decode(str);
+            base64VLQ.decode(str, temp);
             mapping.originalLine = previousOriginalLine + temp.value;
             previousOriginalLine = mapping.originalLine;
             // Lines are stored 0-based
             mapping.originalLine += 1;
             str = temp.rest;
-            if (str.length === 0 || mappingSeparator.test(str.charAt(0))) {
+            if (str.length === 0 || this._nextCharIsMappingSeparator(str)) {
               throw new Error('Found a source and line, but no column');
             }
 
             // Original column.
-            temp = base64VLQ.decode(str);
+            base64VLQ.decode(str, temp);
             mapping.originalColumn = previousOriginalColumn + temp.value;
             previousOriginalColumn = mapping.originalColumn;
             str = temp.rest;
 
-            if (str.length > 0 && !mappingSeparator.test(str.charAt(0))) {
+            if (str.length > 0 && !this._nextCharIsMappingSeparator(str)) {
               // Original name.
-              temp = base64VLQ.decode(str);
+              base64VLQ.decode(str, temp);
               mapping.name = this._names.at(previousName + temp.value);
               previousName += temp.value;
               str = temp.rest;
             }
           }
 
           this.__generatedMappings.push(mapping);
           if (typeof mapping.originalLine === 'number') {
             this.__originalMappings.push(mapping);
           }
         }
       }
 
+      this.__generatedMappings.sort(util.compareByGeneratedPositions);
       this.__originalMappings.sort(util.compareByOriginalPositions);
     };
 
   /**
    * Find the mapping that best matches the hypothetical "needle" mapping that
    * we are searching for in the given "haystack" of mappings.
    */
   SourceMapConsumer.prototype._findMapping =
@@ -324,19 +330,19 @@ define('source-map/source-map-consumer',
       };
 
       var mapping = this._findMapping(needle,
                                       this._generatedMappings,
                                       "generatedLine",
                                       "generatedColumn",
                                       util.compareByGeneratedPositions);
 
-      if (mapping) {
+      if (mapping && mapping.generatedLine === needle.generatedLine) {
         var source = util.getArg(mapping, 'source', null);
-        if (source && this.sourceRoot) {
+        if (source != null && this.sourceRoot != null) {
           source = util.join(this.sourceRoot, source);
         }
         return {
           source: source,
           line: util.getArg(mapping, 'originalLine', null),
           column: util.getArg(mapping, 'originalColumn', null),
           name: util.getArg(mapping, 'name', null)
         };
@@ -356,26 +362,26 @@ define('source-map/source-map-consumer',
    * availible.
    */
   SourceMapConsumer.prototype.sourceContentFor =
     function SourceMapConsumer_sourceContentFor(aSource) {
       if (!this.sourcesContent) {
         return null;
       }
 
-      if (this.sourceRoot) {
+      if (this.sourceRoot != null) {
         aSource = util.relative(this.sourceRoot, aSource);
       }
 
       if (this._sources.has(aSource)) {
         return this.sourcesContent[this._sources.indexOf(aSource)];
       }
 
       var url;
-      if (this.sourceRoot
+      if (this.sourceRoot != null
           && (url = util.urlParse(this.sourceRoot))) {
         // XXX: file:// URIs and absolute paths lead to unexpected behavior for
         // many users. We can help them out when they expect file:// URIs to
         // behave like it would if they were running a local HTTP server. See
         // https://bugzilla.mozilla.org/show_bug.cgi?id=885597.
         var fileUriAbsPath = aSource.replace(/^file:\/\//, "");
         if (url.scheme == "file"
             && this._sources.has(fileUriAbsPath)) {
@@ -408,17 +414,17 @@ define('source-map/source-map-consumer',
   SourceMapConsumer.prototype.generatedPositionFor =
     function SourceMapConsumer_generatedPositionFor(aArgs) {
       var needle = {
         source: util.getArg(aArgs, 'source'),
         originalLine: util.getArg(aArgs, 'line'),
         originalColumn: util.getArg(aArgs, 'column')
       };
 
-      if (this.sourceRoot) {
+      if (this.sourceRoot != null) {
         needle.source = util.relative(this.sourceRoot, needle.source);
       }
 
       var mapping = this._findMapping(needle,
                                       this._originalMappings,
                                       "originalLine",
                                       "originalColumn",
                                       util.compareByOriginalPositions);
@@ -470,17 +476,17 @@ define('source-map/source-map-consumer',
         break;
       default:
         throw new Error("Unknown order of iteration.");
       }
 
       var sourceRoot = this.sourceRoot;
       mappings.map(function (mapping) {
         var source = mapping.source;
-        if (source && sourceRoot) {
+        if (source != null && sourceRoot != null) {
           source = util.join(sourceRoot, source);
         }
         return {
           source: source,
           generatedLine: mapping.generatedLine,
           generatedColumn: mapping.generatedColumn,
           originalLine: mapping.originalLine,
           originalColumn: mapping.originalColumn,
@@ -516,69 +522,197 @@ define('source-map/util', ['require', 'e
     } else if (arguments.length === 3) {
       return aDefaultValue;
     } else {
       throw new Error('"' + aName + '" is a required argument.');
     }
   }
   exports.getArg = getArg;
 
-  var urlRegexp = /([\w+\-.]+):\/\/((\w+:\w+)@)?([\w.]+)?(:(\d+))?(\S+)?/;
-  var dataUrlRegexp = /^data:.+\,.+/;
+  var urlRegexp = /^(?:([\w+\-.]+):)?\/\/(?:(\w+:\w+)@)?([\w.]*)(?::(\d+))?(\S*)$/;
+  var dataUrlRegexp = /^data:.+\,.+$/;
 
   function urlParse(aUrl) {
     var match = aUrl.match(urlRegexp);
     if (!match) {
       return null;
     }
     return {
       scheme: match[1],
-      auth: match[3],
-      host: match[4],
-      port: match[6],
-      path: match[7]
+      auth: match[2],
+      host: match[3],
+      port: match[4],
+      path: match[5]
     };
   }
   exports.urlParse = urlParse;
 
   function urlGenerate(aParsedUrl) {
-    var url = aParsedUrl.scheme + "://";
+    var url = '';
+    if (aParsedUrl.scheme) {
+      url += aParsedUrl.scheme + ':';
+    }
+    url += '//';
     if (aParsedUrl.auth) {
-      url += aParsedUrl.auth + "@"
+      url += aParsedUrl.auth + '@';
     }
     if (aParsedUrl.host) {
       url += aParsedUrl.host;
     }
     if (aParsedUrl.port) {
       url += ":" + aParsedUrl.port
     }
     if (aParsedUrl.path) {
       url += aParsedUrl.path;
     }
     return url;
   }
   exports.urlGenerate = urlGenerate;
 
+  /**
+   * Normalizes a path, or the path portion of a URL:
+   *
+   * - Replaces consequtive slashes with one slash.
+   * - Removes unnecessary '.' parts.
+   * - Removes unnecessary '<dir>/..' parts.
+   *
+   * Based on code in the Node.js 'path' core module.
+   *
+   * @param aPath The path or url to normalize.
+   */
+  function normalize(aPath) {
+    var path = aPath;
+    var url = urlParse(aPath);
+    if (url) {
+      if (!url.path) {
+        return aPath;
+      }
+      path = url.path;
+    }
+    var isAbsolute = (path.charAt(0) === '/');
+
+    var parts = path.split(/\/+/);
+    for (var part, up = 0, i = parts.length - 1; i >= 0; i--) {
+      part = parts[i];
+      if (part === '.') {
+        parts.splice(i, 1);
+      } else if (part === '..') {
+        up++;
+      } else if (up > 0) {
+        if (part === '') {
+          // The first part is blank if the path is absolute. Trying to go
+          // above the root is a no-op. Therefore we can remove all '..' parts
+          // directly after the root.
+          parts.splice(i + 1, up);
+          up = 0;
+        } else {
+          parts.splice(i, 2);
+          up--;
+        }
+      }
+    }
+    path = parts.join('/');
+
+    if (path === '') {
+      path = isAbsolute ? '/' : '.';
+    }
+
+    if (url) {
+      url.path = path;
+      return urlGenerate(url);
+    }
+    return path;
+  }
+  exports.normalize = normalize;
+
+  /**
+   * Joins two paths/URLs.
+   *
+   * @param aRoot The root path or URL.
+   * @param aPath The path or URL to be joined with the root.
+   *
+   * - If aPath is a URL or a data URI, aPath is returned, unless aPath is a
+   *   scheme-relative URL: Then the scheme of aRoot, if any, is prepended
+   *   first.
+   * - Otherwise aPath is a path. If aRoot is a URL, then its path portion
+   *   is updated with the result and aRoot is returned. Otherwise the result
+   *   is returned.
+   *   - If aPath is absolute, the result is aPath.
+   *   - Otherwise the two paths are joined with a slash.
+   * - Joining for example 'http://' and 'www.example.com' is also supported.
+   */
   function join(aRoot, aPath) {
-    var url;
+    if (aRoot === "") {
+      aRoot = ".";
+    }
+    if (aPath === "") {
+      aPath = ".";
+    }
+    var aPathUrl = urlParse(aPath);
+    var aRootUrl = urlParse(aRoot);
+    if (aRootUrl) {
+      aRoot = aRootUrl.path || '/';
+    }
 
-    if (aPath.match(urlRegexp) || aPath.match(dataUrlRegexp)) {
+    // `join(foo, '//www.example.org')`
+    if (aPathUrl && !aPathUrl.scheme) {
+      if (aRootUrl) {
+        aPathUrl.scheme = aRootUrl.scheme;
+      }
+      return urlGenerate(aPathUrl);
+    }
+
+    if (aPathUrl || aPath.match(dataUrlRegexp)) {
       return aPath;
     }
 
-    if (aPath.charAt(0) === '/' && (url = urlParse(aRoot))) {
-      url.path = aPath;
-      return urlGenerate(url);
+    // `join('http://', 'www.example.com')`
+    if (aRootUrl && !aRootUrl.host && !aRootUrl.path) {
+      aRootUrl.host = aPath;
+      return urlGenerate(aRootUrl);
     }
 
-    return aRoot.replace(/\/$/, '') + '/' + aPath;
+    var joined = aPath.charAt(0) === '/'
+      ? aPath
+      : normalize(aRoot.replace(/\/+$/, '') + '/' + aPath);
+
+    if (aRootUrl) {
+      aRootUrl.path = joined;
+      return urlGenerate(aRootUrl);
+    }
+    return joined;
   }
   exports.join = join;
 
   /**
+   * Make a path relative to a URL or another path.
+   *
+   * @param aRoot The root path or URL.
+   * @param aPath The path or URL to be made relative to aRoot.
+   */
+  function relative(aRoot, aPath) {
+    if (aRoot === "") {
+      aRoot = ".";
+    }
+
+    aRoot = aRoot.replace(/\/$/, '');
+
+    // XXX: It is possible to remove this block, and the tests still pass!
+    var url = urlParse(aRoot);
+    if (aPath.charAt(0) == "/" && url && url.path == "/") {
+      return aPath.slice(1);
+    }
+
+    return aPath.indexOf(aRoot + '/') === 0
+      ? aPath.substr(aRoot.length + 1)
+      : aPath;
+  }
+  exports.relative = relative;
+
+  /**
    * Because behavior goes wacky when you set `__proto__` on objects, we
    * have to prefix all the strings in our set with an arbitrary character.
    *
    * See https://github.com/mozilla/source-map/pull/31 and
    * https://github.com/mozilla/source-map/issues/30
    *
    * @param String aStr
    */
@@ -587,30 +721,16 @@ define('source-map/util', ['require', 'e
   }
   exports.toSetString = toSetString;
 
   function fromSetString(aStr) {
     return aStr.substr(1);
   }
   exports.fromSetString = fromSetString;
 
-  function relative(aRoot, aPath) {
-    aRoot = aRoot.replace(/\/$/, '');
-
-    var url = urlParse(aRoot);
-    if (aPath.charAt(0) == "/" && url && url.path == "/") {
-      return aPath.slice(1);
-    }
-
-    return aPath.indexOf(aRoot + '/') === 0
-      ? aPath.substr(aRoot.length + 1)
-      : aPath;
-  }
-  exports.relative = relative;
-
   function strcmp(aStr1, aStr2) {
     var s1 = aStr1 || "";
     var s2 = aStr2 || "";
     return (s1 > s2) - (s1 < s2);
   }
 
   /**
    * Comparator between two mappings where the original positions are compared.
@@ -975,19 +1095,19 @@ define('source-map/base64-vlq', ['requir
       encoded += base64.encode(digit);
     } while (vlq > 0);
 
     return encoded;
   };
 
   /**
    * Decodes the next base 64 VLQ value from the given string and returns the
-   * value and the rest of the string.
+   * value and the rest of the string via the out parameter.
    */
-  exports.decode = function base64VLQ_decode(aStr) {
+  exports.decode = function base64VLQ_decode(aStr, aOutParam) {
     var i = 0;
     var strLen = aStr.length;
     var result = 0;
     var shift = 0;
     var continuation, digit;
 
     do {
       if (i >= strLen) {
@@ -995,20 +1115,18 @@ define('source-map/base64-vlq', ['requir
       }
       digit = base64.decode(aStr.charAt(i++));
       continuation = !!(digit & VLQ_CONTINUATION_BIT);
       digit &= VLQ_BASE_MASK;
       result = result + (digit << shift);
       shift += VLQ_BASE_SHIFT;
     } while (continuation);
 
-    return {
-      value: fromVLQSigned(result),
-      rest: aStr.slice(i)
-    };
+    aOutParam.value = fromVLQSigned(result);
+    aOutParam.rest = aStr.slice(i);
   };
 
 });
 /* -*- Mode: js; js-indent-level: 2; -*- */
 /*
  * Copyright 2011 Mozilla Foundation and contributors
  * Licensed under the New BSD license. See LICENSE or:
  * http://opensource.org/licenses/BSD-3-Clause
@@ -1055,24 +1173,27 @@ define('source-map/base64', ['require', 
 define('source-map/source-map-generator', ['require', 'exports', 'module' ,  'source-map/base64-vlq', 'source-map/util', 'source-map/array-set'], function(require, exports, module) {
 
   var base64VLQ = require('source-map/base64-vlq');
   var util = require('source-map/util');
   var ArraySet = require('source-map/array-set').ArraySet;
 
   /**
    * An instance of the SourceMapGenerator represents a source map which is
-   * being built incrementally. To create a new one, you must pass an object
-   * with the following properties:
+   * being built incrementally. You may pass an object with the following
+   * properties:
    *
    *   - file: The filename of the generated source.
-   *   - sourceRoot: An optional root for all URLs in this source map.
+   *   - sourceRoot: A root for all relative URLs in this source map.
    */
   function SourceMapGenerator(aArgs) {
-    this._file = util.getArg(aArgs, 'file');
+    if (!aArgs) {
+      aArgs = {};
+    }
+    this._file = util.getArg(aArgs, 'file', null);
     this._sourceRoot = util.getArg(aArgs, 'sourceRoot', null);
     this._sources = new ArraySet();
     this._names = new ArraySet();
     this._mappings = [];
     this._sourcesContents = null;
   }
 
   SourceMapGenerator.prototype._version = 3;
@@ -1092,37 +1213,37 @@ define('source-map/source-map-generator'
       aSourceMapConsumer.eachMapping(function (mapping) {
         var newMapping = {
           generated: {
             line: mapping.generatedLine,
             column: mapping.generatedColumn
           }
         };
 
-        if (mapping.source) {
+        if (mapping.source != null) {
           newMapping.source = mapping.source;
-          if (sourceRoot) {
+          if (sourceRoot != null) {
             newMapping.source = util.relative(sourceRoot, newMapping.source);
           }
 
           newMapping.original = {
             line: mapping.originalLine,
             column: mapping.originalColumn
           };
 
-          if (mapping.name) {
+          if (mapping.name != null) {
             newMapping.name = mapping.name;
           }
         }
 
         generator.addMapping(newMapping);
       });
       aSourceMapConsumer.sources.forEach(function (sourceFile) {
         var content = aSourceMapConsumer.sourceContentFor(sourceFile);
-        if (content) {
+        if (content != null) {
           generator.setSourceContent(sourceFile, content);
         }
       });
       return generator;
     };
 
   /**
    * Add a single mapping from original source line and column to the generated
@@ -1138,21 +1259,21 @@ define('source-map/source-map-generator'
     function SourceMapGenerator_addMapping(aArgs) {
       var generated = util.getArg(aArgs, 'generated');
       var original = util.getArg(aArgs, 'original', null);
       var source = util.getArg(aArgs, 'source', null);
       var name = util.getArg(aArgs, 'name', null);
 
       this._validateMapping(generated, original, source, name);
 
-      if (source && !this._sources.has(source)) {
+      if (source != null && !this._sources.has(source)) {
         this._sources.add(source);
       }
 
-      if (name && !this._names.has(name)) {
+      if (name != null && !this._names.has(name)) {
         this._names.add(name);
       }
 
       this._mappings.push({
         generatedLine: generated.line,
         generatedColumn: generated.column,
         originalLine: original != null && original.line,
         originalColumn: original != null && original.column,
@@ -1162,28 +1283,28 @@ define('source-map/source-map-generator'
     };
 
   /**
    * Set the source content for a source file.
    */
   SourceMapGenerator.prototype.setSourceContent =
     function SourceMapGenerator_setSourceContent(aSourceFile, aSourceContent) {
       var source = aSourceFile;
-      if (this._sourceRoot) {
+      if (this._sourceRoot != null) {
         source = util.relative(this._sourceRoot, source);
       }
 
-      if (aSourceContent !== null) {
+      if (aSourceContent != null) {
         // Add the source content to the _sourcesContents map.
         // Create a new _sourcesContents map if the property is null.
         if (!this._sourcesContents) {
           this._sourcesContents = {};
         }
         this._sourcesContents[util.toSetString(source)] = aSourceContent;
-      } else {
+      } else if (this._sourcesContents) {
         // Remove the source file from the _sourcesContents map.
         // If the _sourcesContents map is empty, set the property to null.
         delete this._sourcesContents[util.toSetString(source)];
         if (Object.keys(this._sourcesContents).length === 0) {
           this._sourcesContents = null;
         }
       }
     };
@@ -1192,77 +1313,93 @@ define('source-map/source-map-generator'
    * Applies the mappings of a sub-source-map for a specific source file to the
    * source map being generated. Each mapping to the supplied source file is
    * rewritten using the supplied source map. Note: The resolution for the
    * resulting mappings is the minimium of this map and the supplied map.
    *
    * @param aSourceMapConsumer The source map to be applied.
    * @param aSourceFile Optional. The filename of the source file.
    *        If omitted, SourceMapConsumer's file property will be used.
+   * @param aSourceMapPath Optional. The dirname of the path to the source map
+   *        to be applied. If relative, it is relative to the SourceMapConsumer.
+   *        This parameter is needed when the two source maps aren't in the same
+   *        directory, and the source map to be applied contains relative source
+   *        paths. If so, those relative source paths need to be rewritten
+   *        relative to the SourceMapGenerator.
    */
   SourceMapGenerator.prototype.applySourceMap =
-    function SourceMapGenerator_applySourceMap(aSourceMapConsumer, aSourceFile) {
+    function SourceMapGenerator_applySourceMap(aSourceMapConsumer, aSourceFile, aSourceMapPath) {
+      var sourceFile = aSourceFile;
       // If aSourceFile is omitted, we will use the file property of the SourceMap
-      if (!aSourceFile) {
-        aSourceFile = aSourceMapConsumer.file;
+      if (aSourceFile == null) {
+        if (aSourceMapConsumer.file == null) {
+          throw new Error(
+            'SourceMapGenerator.prototype.applySourceMap requires either an explicit source file, ' +
+            'or the source map\'s "file" property. Both were omitted.'
+          );
+        }
+        sourceFile = aSourceMapConsumer.file;
       }
       var sourceRoot = this._sourceRoot;
-      // Make "aSourceFile" relative if an absolute Url is passed.
-      if (sourceRoot) {
-        aSourceFile = util.relative(sourceRoot, aSourceFile);
+      // Make "sourceFile" relative if an absolute Url is passed.
+      if (sourceRoot != null) {
+        sourceFile = util.relative(sourceRoot, sourceFile);
       }
       // Applying the SourceMap can add and remove items from the sources and
       // the names array.
       var newSources = new ArraySet();
       var newNames = new ArraySet();
 
-      // Find mappings for the "aSourceFile"
+      // Find mappings for the "sourceFile"
       this._mappings.forEach(function (mapping) {
-        if (mapping.source === aSourceFile && mapping.originalLine) {
+        if (mapping.source === sourceFile && mapping.originalLine != null) {
           // Check if it can be mapped by the source map, then update the mapping.
           var original = aSourceMapConsumer.originalPositionFor({
             line: mapping.originalLine,
             column: mapping.originalColumn
           });
-          if (original.source !== null) {
+          if (original.source != null) {
             // Copy mapping
-            if (sourceRoot) {
-              mapping.source = util.relative(sourceRoot, original.source);
-            } else {
-              mapping.source = original.source;
+            mapping.source = original.source;
+            if (aSourceMapPath != null) {
+              mapping.source = util.join(aSourceMapPath, mapping.source)
+            }
+            if (sourceRoot != null) {
+              mapping.source = util.relative(sourceRoot, mapping.source);
             }
             mapping.originalLine = original.line;
             mapping.originalColumn = original.column;
-            if (original.name !== null && mapping.name !== null) {
-              // Only use the identifier name if it's an identifier
-              // in both SourceMaps
+            if (original.name != null) {
               mapping.name = original.name;
             }
           }
         }
 
         var source = mapping.source;
-        if (source && !newSources.has(source)) {
+        if (source != null && !newSources.has(source)) {
           newSources.add(source);
         }
 
         var name = mapping.name;
-        if (name && !newNames.has(name)) {
+        if (name != null && !newNames.has(name)) {
           newNames.add(name);
         }
 
       }, this);
       this._sources = newSources;
       this._names = newNames;
 
       // Copy sourcesContents of applied map.
       aSourceMapConsumer.sources.forEach(function (sourceFile) {
         var content = aSourceMapConsumer.sourceContentFor(sourceFile);
-        if (content) {
-          if (sourceRoot) {
+        if (content != null) {
+          if (aSourceMapPath != null) {
+            sourceFile = util.join(aSourceMapPath, sourceFile);
+          }
+          if (sourceRoot != null) {
             sourceFile = util.relative(sourceRoot, sourceFile);
           }
           this.setSourceContent(sourceFile, content);
         }
       }, this);
     };
 
   /**
@@ -1343,48 +1480,48 @@ define('source-map/source-map-generator'
             result += ',';
           }
         }
 
         result += base64VLQ.encode(mapping.generatedColumn
                                    - previousGeneratedColumn);
         previousGeneratedColumn = mapping.generatedColumn;
 
-        if (mapping.source) {
+        if (mapping.source != null) {
           result += base64VLQ.encode(this._sources.indexOf(mapping.source)
                                      - previousSource);
           previousSource = this._sources.indexOf(mapping.source);
 
           // lines are stored 0-based in SourceMap spec version 3
           result += base64VLQ.encode(mapping.originalLine - 1
                                      - previousOriginalLine);
           previousOriginalLine = mapping.originalLine - 1;
 
           result += base64VLQ.encode(mapping.originalColumn
                                      - previousOriginalColumn);
           previousOriginalColumn = mapping.originalColumn;
 
-          if (mapping.name) {
+          if (mapping.name != null) {
             result += base64VLQ.encode(this._names.indexOf(mapping.name)
                                        - previousName);
             previousName = this._names.indexOf(mapping.name);
           }
         }
       }
 
       return result;
     };
 
   SourceMapGenerator.prototype._generateSourcesContent =
     function SourceMapGenerator_generateSourcesContent(aSources, aSourceRoot) {
       return aSources.map(function (source) {
         if (!this._sourcesContents) {
           return null;
         }
-        if (aSourceRoot) {
+        if (aSourceRoot != null) {
           source = util.relative(aSourceRoot, source);
         }
         var key = util.toSetString(source);
         return Object.prototype.hasOwnProperty.call(this._sourcesContents,
                                                     key)
           ? this._sourcesContents[key]
           : null;
       }, this);
@@ -1392,27 +1529,28 @@ define('source-map/source-map-generator'
 
   /**
    * Externalize the source map.
    */
   SourceMapGenerator.prototype.toJSON =
     function SourceMapGenerator_toJSON() {
       var map = {
         version: this._version,
-        file: this._file,
         sources: this._sources.toArray(),
         names: this._names.toArray(),
         mappings: this._serializeMappings()
       };
-      if (this._sourceRoot) {
+      if (this._file != null) {
+        map.file = this._file;
+      }
+      if (this._sourceRoot != null) {
         map.sourceRoot = this._sourceRoot;
       }
       if (this._sourcesContents) {
-        map.sourcesContent = this._generateSourcesContent(map.sources,
-                                                          map.sourceRoot || undefined);
+        map.sourcesContent = this._generateSourcesContent(map.sources, map.sourceRoot);
       }
 
       return map;
     };
 
   /**
    * Render the source map being generated to a string.
    */
@@ -1430,135 +1568,154 @@ define('source-map/source-map-generator'
  * Licensed under the New BSD license. See LICENSE or:
  * http://opensource.org/licenses/BSD-3-Clause
  */
 define('source-map/source-node', ['require', 'exports', 'module' ,  'source-map/source-map-generator', 'source-map/util'], function(require, exports, module) {
 
   var SourceMapGenerator = require('source-map/source-map-generator').SourceMapGenerator;
   var util = require('source-map/util');
 
+  // Matches a Windows-style `\r\n` newline or a `\n` newline used by all other
+  // operating systems these days (capturing the result).
+  var REGEX_NEWLINE = /(\r?\n)/;
+
+  // Matches a Windows-style newline, or any character.
+  var REGEX_CHARACTER = /\r\n|[\s\S]/g;
+
   /**
    * SourceNodes provide a way to abstract over interpolating/concatenating
    * snippets of generated JavaScript source code while maintaining the line and
    * column information associated with the original source code.
    *
    * @param aLine The original line number.
    * @param aColumn The original column number.
    * @param aSource The original source's filename.
    * @param aChunks Optional. An array of strings which are snippets of
    *        generated JS, or other SourceNodes.
    * @param aName The original identifier.
    */
   function SourceNode(aLine, aColumn, aSource, aChunks, aName) {
     this.children = [];
     this.sourceContents = {};
-    this.line = aLine === undefined ? null : aLine;
-    this.column = aColumn === undefined ? null : aColumn;
-    this.source = aSource === undefined ? null : aSource;
-    this.name = aName === undefined ? null : aName;
+    this.line = aLine == null ? null : aLine;
+    this.column = aColumn == null ? null : aColumn;
+    this.source = aSource == null ? null : aSource;
+    this.name = aName == null ? null : aName;
     if (aChunks != null) this.add(aChunks);
   }
 
   /**
    * Creates a SourceNode from generated code and a SourceMapConsumer.
    *
    * @param aGeneratedCode The generated code
    * @param aSourceMapConsumer The SourceMap for the generated code
+   * @param aRelativePath Optional. The path that relative sources in the
+   *        SourceMapConsumer should be relative to.
    */
   SourceNode.fromStringWithSourceMap =
-    function SourceNode_fromStringWithSourceMap(aGeneratedCode, aSourceMapConsumer) {
+    function SourceNode_fromStringWithSourceMap(aGeneratedCode, aSourceMapConsumer, aRelativePath) {
       // The SourceNode we want to fill with the generated code
       // and the SourceMap
       var node = new SourceNode();
 
-      // The generated code
-      // Processed fragments are removed from this array.
-      var remainingLines = aGeneratedCode.split('\n');
+      // All even indices of this array are one line of the generated code,
+      // while all odd indices are the newlines between two adjacent lines
+      // (since `REGEX_NEWLINE` captures its match).
+      // Processed fragments are removed from this array, by calling `shiftNextLine`.
+      var remainingLines = aGeneratedCode.split(REGEX_NEWLINE);
+      var shiftNextLine = function() {
+        var lineContents = remainingLines.shift();
+        // The last line of a file might not have a newline.
+        var newLine = remainingLines.shift() || "";
+        return lineContents + newLine;
+      };
 
       // We need to remember the position of "remainingLines"
       var lastGeneratedLine = 1, lastGeneratedColumn = 0;
 
       // The generate SourceNodes we need a code range.
       // To extract it current and last mapping is used.
       // Here we store the last mapping.
       var lastMapping = null;
 
       aSourceMapConsumer.eachMapping(function (mapping) {
-        if (lastMapping === null) {
-          // We add the generated code until the first mapping
-          // to the SourceNode without any mapping.
-          // Each line is added as separate string.
-          while (lastGeneratedLine < mapping.generatedLine) {
-            node.add(remainingLines.shift() + "\n");
-            lastGeneratedLine++;
-          }
-          if (lastGeneratedColumn < mapping.generatedColumn) {
-            var nextLine = remainingLines[0];
-            node.add(nextLine.substr(0, mapping.generatedColumn));
-            remainingLines[0] = nextLine.substr(mapping.generatedColumn);
-            lastGeneratedColumn = mapping.generatedColumn;
-          }
-        } else {
+        if (lastMapping !== null) {
           // We add the code from "lastMapping" to "mapping":
           // First check if there is a new line in between.
           if (lastGeneratedLine < mapping.generatedLine) {
             var code = "";
-            // Associate full lines with "lastMapping"
-            do {
-              code += remainingLines.shift() + "\n";
-              lastGeneratedLine++;
-              lastGeneratedColumn = 0;
-            } while (lastGeneratedLine < mapping.generatedLine);
-            // When we reached the correct line, we add code until we
-            // reach the correct column too.
-            if (lastGeneratedColumn < mapping.generatedColumn) {
-              var nextLine = remainingLines[0];
-              code += nextLine.substr(0, mapping.generatedColumn);
-              remainingLines[0] = nextLine.substr(mapping.generatedColumn);
-              lastGeneratedColumn = mapping.generatedColumn;
-            }
-            // Create the SourceNode.
-            addMappingWithCode(lastMapping, code);
+            // Associate first line with "lastMapping"
+            addMappingWithCode(lastMapping, shiftNextLine());
+            lastGeneratedLine++;
+            lastGeneratedColumn = 0;
+            // The remaining code is added without mapping
           } else {
             // There is no new line in between.
             // Associate the code between "lastGeneratedColumn" and
             // "mapping.generatedColumn" with "lastMapping"
             var nextLine = remainingLines[0];
             var code = nextLine.substr(0, mapping.generatedColumn -
                                           lastGeneratedColumn);
             remainingLines[0] = nextLine.substr(mapping.generatedColumn -
                                                 lastGeneratedColumn);
             lastGeneratedColumn = mapping.generatedColumn;
             addMappingWithCode(lastMapping, code);
+            // No more remaining code, continue
+            lastMapping = mapping;
+            return;
           }
         }
+        // We add the generated code until the first mapping
+        // to the SourceNode without any mapping.
+        // Each line is added as separate string.
+        while (lastGeneratedLine < mapping.generatedLine) {
+          node.add(shiftNextLine());
+          lastGeneratedLine++;
+        }
+        if (lastGeneratedColumn < mapping.generatedColumn) {
+          var nextLine = remainingLines[0];
+          node.add(nextLine.substr(0, mapping.generatedColumn));
+          remainingLines[0] = nextLine.substr(mapping.generatedColumn);
+          lastGeneratedColumn = mapping.generatedColumn;
+        }
         lastMapping = mapping;
       }, this);
       // We have processed all mappings.
-      // Associate the remaining code in the current line with "lastMapping"
-      // and add the remaining lines without any mapping
-      addMappingWithCode(lastMapping, remainingLines.join("\n"));
+      if (remainingLines.length > 0) {
+        if (lastMapping) {
+          // Associate the remaining code in the current line with "lastMapping"
+          addMappingWithCode(lastMapping, shiftNextLine());
+        }
+        // and add the remaining lines without any mapping
+        node.add(remainingLines.join(""));
+      }
 
       // Copy sourcesContent into SourceNode
       aSourceMapConsumer.sources.forEach(function (sourceFile) {
         var content = aSourceMapConsumer.sourceContentFor(sourceFile);
-        if (content) {
+        if (content != null) {
+          if (aRelativePath != null) {
+            sourceFile = util.join(aRelativePath, sourceFile);
+          }
           node.setSourceContent(sourceFile, content);
         }
       });
 
       return node;
 
       function addMappingWithCode(mapping, code) {
         if (mapping === null || mapping.source === undefined) {
           node.add(code);
         } else {
+          var source = aRelativePath
+            ? util.join(aRelativePath, mapping.source)
+            : mapping.source;
           node.add(new SourceNode(mapping.originalLine,
                                   mapping.originalColumn,
-                                  mapping.source,
+                                  source,
                                   code,
                                   mapping.name));
         }
       }
     };
 
   /**
    * Add a chunk of generated JS to this source node.
@@ -1768,22 +1925,40 @@ define('source-map/source-node', ['requi
           generated: {
             line: generated.line,
             column: generated.column
           }
         });
         lastOriginalSource = null;
         sourceMappingActive = false;
       }
-      chunk.split('').forEach(function (ch) {
-        if (ch === '\n') {
+      chunk.match(REGEX_CHARACTER).forEach(function (ch, idx, array) {
+        if (REGEX_NEWLINE.test(ch)) {
           generated.line++;
           generated.column = 0;
+          // Mappings end at eol
+          if (idx + 1 === array.length) {
+            lastOriginalSource = null;
+            sourceMappingActive = false;
+          } else if (sourceMappingActive) {
+            map.addMapping({
+              source: original.source,
+              original: {
+                line: original.line,
+                column: original.column
+              },
+              generated: {
+                line: generated.line,
+                column: generated.column
+              },
+              name: original.name
+            });
+          }
         } else {
-          generated.column++;
+          generated.column += ch.length;
         }
       });
     });
     this.walkSourceContents(function (sourceFile, sourceContent) {
       map.setSourceContent(sourceFile, sourceContent);
     });
 
     return { code: generated.code, map: map };
--- a/toolkit/devtools/sourcemap/tests/unit/Utils.jsm
+++ b/toolkit/devtools/sourcemap/tests/unit/Utils.jsm
@@ -1,9 +1,9 @@
-/* -*- js-indent-level: 2; indent-tabs-mode: nil -*- */
+/* -*- Mode: js; js-indent-level: 2; -*- */
 /*
  * Copyright 2011 Mozilla Foundation and contributors
  * Licensed under the New BSD license. See LICENSE or:
  * http://opensource.org/licenses/BSD-3-Clause
  */
 
 /*
  * WARNING!
@@ -106,16 +106,31 @@ define('test/source-map/util', ['require
   exports.testMap = {
     version: 3,
     file: 'min.js',
     names: ['bar', 'baz', 'n'],
     sources: ['one.js', 'two.js'],
     sourceRoot: '/the/root',
     mappings: 'CAAC,IAAI,IAAM,SAAUA,GAClB,OAAOC,IAAID;CCDb,IAAI,IAAM,SAAUE,GAClB,OAAOA'
   };
+  exports.testMapNoSourceRoot = {
+    version: 3,
+    file: 'min.js',
+    names: ['bar', 'baz', 'n'],
+    sources: ['one.js', 'two.js'],
+    mappings: 'CAAC,IAAI,IAAM,SAAUA,GAClB,OAAOC,IAAID;CCDb,IAAI,IAAM,SAAUE,GAClB,OAAOA'
+  };
+  exports.testMapEmptySourceRoot = {
+    version: 3,
+    file: 'min.js',
+    names: ['bar', 'baz', 'n'],
+    sources: ['one.js', 'two.js'],
+    sourceRoot: '',
+    mappings: 'CAAC,IAAI,IAAM,SAAUA,GAClB,OAAOC,IAAID;CCDb,IAAI,IAAM,SAAUE,GAClB,OAAOA'
+  };
   exports.testMapWithSourcesContent = {
     version: 3,
     file: 'min.js',
     names: ['bar', 'baz', 'n'],
     sources: ['one.js', 'two.js'],
     sourcesContent: [
       ' ONE.foo = function (bar) {\n' +
       '   return baz(bar);\n' +
@@ -254,69 +269,197 @@ define('lib/source-map/util', ['require'
     } else if (arguments.length === 3) {
       return aDefaultValue;
     } else {
       throw new Error('"' + aName + '" is a required argument.');
     }
   }
   exports.getArg = getArg;
 
-  var urlRegexp = /([\w+\-.]+):\/\/((\w+:\w+)@)?([\w.]+)?(:(\d+))?(\S+)?/;
-  var dataUrlRegexp = /^data:.+\,.+/;
+  var urlRegexp = /^(?:([\w+\-.]+):)?\/\/(?:(\w+:\w+)@)?([\w.]*)(?::(\d+))?(\S*)$/;
+  var dataUrlRegexp = /^data:.+\,.+$/;
 
   function urlParse(aUrl) {
     var match = aUrl.match(urlRegexp);
     if (!match) {
       return null;
     }
     return {
       scheme: match[1],
-      auth: match[3],
-      host: match[4],
-      port: match[6],
-      path: match[7]
+      auth: match[2],
+      host: match[3],
+      port: match[4],
+      path: match[5]
     };
   }
   exports.urlParse = urlParse;
 
   function urlGenerate(aParsedUrl) {
-    var url = aParsedUrl.scheme + "://";
+    var url = '';
+    if (aParsedUrl.scheme) {
+      url += aParsedUrl.scheme + ':';
+    }
+    url += '//';
     if (aParsedUrl.auth) {
-      url += aParsedUrl.auth + "@"
+      url += aParsedUrl.auth + '@';
     }
     if (aParsedUrl.host) {
       url += aParsedUrl.host;
     }
     if (aParsedUrl.port) {
       url += ":" + aParsedUrl.port
     }
     if (aParsedUrl.path) {
       url += aParsedUrl.path;
     }
     return url;
   }
   exports.urlGenerate = urlGenerate;
 
+  /**
+   * Normalizes a path, or the path portion of a URL:
+   *
+   * - Replaces consequtive slashes with one slash.
+   * - Removes unnecessary '.' parts.
+   * - Removes unnecessary '<dir>/..' parts.
+   *
+   * Based on code in the Node.js 'path' core module.
+   *
+   * @param aPath The path or url to normalize.
+   */
+  function normalize(aPath) {
+    var path = aPath;
+    var url = urlParse(aPath);
+    if (url) {
+      if (!url.path) {
+        return aPath;
+      }
+      path = url.path;
+    }
+    var isAbsolute = (path.charAt(0) === '/');
+
+    var parts = path.split(/\/+/);
+    for (var part, up = 0, i = parts.length - 1; i >= 0; i--) {
+      part = parts[i];
+      if (part === '.') {
+        parts.splice(i, 1);
+      } else if (part === '..') {
+        up++;
+      } else if (up > 0) {
+        if (part === '') {
+          // The first part is blank if the path is absolute. Trying to go
+          // above the root is a no-op. Therefore we can remove all '..' parts
+          // directly after the root.
+          parts.splice(i + 1, up);
+          up = 0;
+        } else {
+          parts.splice(i, 2);
+          up--;
+        }
+      }
+    }
+    path = parts.join('/');
+
+    if (path === '') {
+      path = isAbsolute ? '/' : '.';
+    }
+
+    if (url) {
+      url.path = path;
+      return urlGenerate(url);
+    }
+    return path;
+  }
+  exports.normalize = normalize;
+
+  /**
+   * Joins two paths/URLs.
+   *
+   * @param aRoot The root path or URL.
+   * @param aPath The path or URL to be joined with the root.
+   *
+   * - If aPath is a URL or a data URI, aPath is returned, unless aPath is a
+   *   scheme-relative URL: Then the scheme of aRoot, if any, is prepended
+   *   first.
+   * - Otherwise aPath is a path. If aRoot is a URL, then its path portion
+   *   is updated with the result and aRoot is returned. Otherwise the result
+   *   is returned.
+   *   - If aPath is absolute, the result is aPath.
+   *   - Otherwise the two paths are joined with a slash.
+   * - Joining for example 'http://' and 'www.example.com' is also supported.
+   */
   function join(aRoot, aPath) {
-    var url;
+    if (aRoot === "") {
+      aRoot = ".";
+    }
+    if (aPath === "") {
+      aPath = ".";
+    }
+    var aPathUrl = urlParse(aPath);
+    var aRootUrl = urlParse(aRoot);
+    if (aRootUrl) {
+      aRoot = aRootUrl.path || '/';
+    }
 
-    if (aPath.match(urlRegexp) || aPath.match(dataUrlRegexp)) {
+    // `join(foo, '//www.example.org')`
+    if (aPathUrl && !aPathUrl.scheme) {
+      if (aRootUrl) {
+        aPathUrl.scheme = aRootUrl.scheme;
+      }
+      return urlGenerate(aPathUrl);
+    }
+
+    if (aPathUrl || aPath.match(dataUrlRegexp)) {
       return aPath;
     }
 
-    if (aPath.charAt(0) === '/' && (url = urlParse(aRoot))) {
-      url.path = aPath;
-      return urlGenerate(url);
+    // `join('http://', 'www.example.com')`
+    if (aRootUrl && !aRootUrl.host && !aRootUrl.path) {
+      aRootUrl.host = aPath;
+      return urlGenerate(aRootUrl);
     }
 
-    return aRoot.replace(/\/$/, '') + '/' + aPath;
+    var joined = aPath.charAt(0) === '/'
+      ? aPath
+      : normalize(aRoot.replace(/\/+$/, '') + '/' + aPath);
+
+    if (aRootUrl) {
+      aRootUrl.path = joined;
+      return urlGenerate(aRootUrl);
+    }
+    return joined;
   }
   exports.join = join;
 
   /**
+   * Make a path relative to a URL or another path.
+   *
+   * @param aRoot The root path or URL.
+   * @param aPath The path or URL to be made relative to aRoot.
+   */
+  function relative(aRoot, aPath) {
+    if (aRoot === "") {
+      aRoot = ".";
+    }
+
+    aRoot = aRoot.replace(/\/$/, '');
+
+    // XXX: It is possible to remove this block, and the tests still pass!
+    var url = urlParse(aRoot);
+    if (aPath.charAt(0) == "/" && url && url.path == "/") {
+      return aPath.slice(1);
+    }
+
+    return aPath.indexOf(aRoot + '/') === 0
+      ? aPath.substr(aRoot.length + 1)
+      : aPath;
+  }
+  exports.relative = relative;
+
+  /**
    * Because behavior goes wacky when you set `__proto__` on objects, we
    * have to prefix all the strings in our set with an arbitrary character.
    *
    * See https://github.com/mozilla/source-map/pull/31 and
    * https://github.com/mozilla/source-map/issues/30
    *
    * @param String aStr
    */
@@ -325,30 +468,16 @@ define('lib/source-map/util', ['require'
   }
   exports.toSetString = toSetString;
 
   function fromSetString(aStr) {
     return aStr.substr(1);
   }
   exports.fromSetString = fromSetString;
 
-  function relative(aRoot, aPath) {
-    aRoot = aRoot.replace(/\/$/, '');
-
-    var url = urlParse(aRoot);
-    if (aPath.charAt(0) == "/" && url && url.path == "/") {
-      return aPath.slice(1);
-    }
-
-    return aPath.indexOf(aRoot + '/') === 0
-      ? aPath.substr(aRoot.length + 1)
-      : aPath;
-  }
-  exports.relative = relative;
-
   function strcmp(aStr1, aStr2) {
     var s1 = aStr1 || "";
     var s2 = aStr2 || "";
     return (s1 > s2) - (s1 < s2);
   }
 
   /**
    * Comparator between two mappings where the original positions are compared.
--- a/toolkit/devtools/sourcemap/tests/unit/test_base64_vlq.js
+++ b/toolkit/devtools/sourcemap/tests/unit/test_base64_vlq.js
@@ -12,20 +12,19 @@ Components.utils.import('resource://test
  * Licensed under the New BSD license. See LICENSE or:
  * http://opensource.org/licenses/BSD-3-Clause
  */
 define("test/source-map/test-base64-vlq", ["require", "exports", "module"], function (require, exports, module) {
 
   var base64VLQ = require('source-map/base64-vlq');
 
   exports['test normal encoding and decoding'] = function (assert, util) {
-    var result;
+    var result = {};
     for (var i = -255; i < 256; i++) {
-      result = base64VLQ.decode(base64VLQ.encode(i));
-      assert.ok(result);
+      base64VLQ.decode(base64VLQ.encode(i), result);
       assert.equal(result.value, i);
       assert.equal(result.rest, "");
     }
   };
 
 });
 function run_test() {
   runSourceMapTests('test/source-map/test-base64-vlq', do_throw);
--- a/toolkit/devtools/sourcemap/tests/unit/test_dog_fooding.js
+++ b/toolkit/devtools/sourcemap/tests/unit/test_dog_fooding.js
@@ -42,39 +42,51 @@ define("test/source-map/test-dog-fooding
     });
 
     smg.addMapping({
       source: 'gza.coffee',
       original: { line: 4, column: 0 },
       generated: { line: 5, column: 2 }
     });
 
+    smg.addMapping({
+      source: 'gza.coffee',
+      original: { line: 5, column: 10 },
+      generated: { line: 6, column: 12 }
+    });
+
     var smc = new SourceMapConsumer(smg.toString());
 
     // Exact
     util.assertMapping(2, 2, '/wu/tang/gza.coffee', 1, 0, null, smc, assert);
     util.assertMapping(3, 2, '/wu/tang/gza.coffee', 2, 0, null, smc, assert);
     util.assertMapping(4, 2, '/wu/tang/gza.coffee', 3, 0, null, smc, assert);
     util.assertMapping(5, 2, '/wu/tang/gza.coffee', 4, 0, null, smc, assert);
+    util.assertMapping(6, 12, '/wu/tang/gza.coffee', 5, 10, null, smc, assert);
 
     // Fuzzy
 
-    // Original to generated
+    // Generated to original
     util.assertMapping(2, 0, null, null, null, null, smc, assert, true);
     util.assertMapping(2, 9, '/wu/tang/gza.coffee', 1, 0, null, smc, assert, true);
-    util.assertMapping(3, 0, '/wu/tang/gza.coffee', 1, 0, null, smc, assert, true);
+    util.assertMapping(3, 0, null, null, null, null, smc, assert, true);
     util.assertMapping(3, 9, '/wu/tang/gza.coffee', 2, 0, null, smc, assert, true);
-    util.assertMapping(4, 0, '/wu/tang/gza.coffee', 2, 0, null, smc, assert, true);
+    util.assertMapping(4, 0, null, null, null, null, smc, assert, true);
     util.assertMapping(4, 9, '/wu/tang/gza.coffee', 3, 0, null, smc, assert, true);
-    util.assertMapping(5, 0, '/wu/tang/gza.coffee', 3, 0, null, smc, assert, true);
+    util.assertMapping(5, 0, null, null, null, null, smc, assert, true);
     util.assertMapping(5, 9, '/wu/tang/gza.coffee', 4, 0, null, smc, assert, true);
+    util.assertMapping(6, 0, null, null, null, null, smc, assert, true);
+    util.assertMapping(6, 9, null, null, null, null, smc, assert, true);
+    util.assertMapping(6, 13, '/wu/tang/gza.coffee', 5, 10, null, smc, assert, true);
 
-    // Generated to original
+    // Original to generated
     util.assertMapping(2, 2, '/wu/tang/gza.coffee', 1, 1, null, smc, assert, null, true);
     util.assertMapping(3, 2, '/wu/tang/gza.coffee', 2, 3, null, smc, assert, null, true);
     util.assertMapping(4, 2, '/wu/tang/gza.coffee', 3, 6, null, smc, assert, null, true);
     util.assertMapping(5, 2, '/wu/tang/gza.coffee', 4, 9, null, smc, assert, null, true);
+    util.assertMapping(5, 2, '/wu/tang/gza.coffee', 5, 9, null, smc, assert, null, true);
+    util.assertMapping(6, 12, '/wu/tang/gza.coffee', 6, 19, null, smc, assert, null, true);
   };
 
 });
 function run_test() {
   runSourceMapTests('test/source-map/test-dog-fooding', do_throw);
 }
--- a/toolkit/devtools/sourcemap/tests/unit/test_source_map_consumer.js
+++ b/toolkit/devtools/sourcemap/tests/unit/test_source_map_consumer.js
@@ -12,49 +12,95 @@ Components.utils.import('resource://test
  * Licensed under the New BSD license. See LICENSE or:
  * http://opensource.org/licenses/BSD-3-Clause
  */
 define("test/source-map/test-source-map-consumer", ["require", "exports", "module"], function (require, exports, module) {
 
   var SourceMapConsumer = require('source-map/source-map-consumer').SourceMapConsumer;
   var SourceMapGenerator = require('source-map/source-map-generator').SourceMapGenerator;
 
-  exports['test that we can instantiate with a string or an objects'] = function (assert, util) {
+  exports['test that we can instantiate with a string or an object'] = function (assert, util) {
     assert.doesNotThrow(function () {
       var map = new SourceMapConsumer(util.testMap);
     });
     assert.doesNotThrow(function () {
       var map = new SourceMapConsumer(JSON.stringify(util.testMap));
     });
   };
 
   exports['test that the `sources` field has the original sources'] = function (assert, util) {
-    var map = new SourceMapConsumer(util.testMap);
-    var sources = map.sources;
+    var map;
+    var sources;
 
+    map = new SourceMapConsumer(util.testMap);
+    sources = map.sources;
     assert.equal(sources[0], '/the/root/one.js');
     assert.equal(sources[1], '/the/root/two.js');
     assert.equal(sources.length, 2);
+
+    map = new SourceMapConsumer(util.testMapNoSourceRoot);
+    sources = map.sources;
+    assert.equal(sources[0], 'one.js');
+    assert.equal(sources[1], 'two.js');
+    assert.equal(sources.length, 2);
+
+    map = new SourceMapConsumer(util.testMapEmptySourceRoot);
+    sources = map.sources;
+    assert.equal(sources[0], 'one.js');
+    assert.equal(sources[1], 'two.js');
+    assert.equal(sources.length, 2);
   };
 
   exports['test that the source root is reflected in a mapping\'s source field'] = function (assert, util) {
-    var map = new SourceMapConsumer(util.testMap);
+    var map;
     var mapping;
 
+    map = new SourceMapConsumer(util.testMap);
+
     mapping = map.originalPositionFor({
       line: 2,
       column: 1
     });
     assert.equal(mapping.source, '/the/root/two.js');
 
     mapping = map.originalPositionFor({
       line: 1,
       column: 1
     });
     assert.equal(mapping.source, '/the/root/one.js');
+
+
+    map = new SourceMapConsumer(util.testMapNoSourceRoot);
+
+    mapping = map.originalPositionFor({
+      line: 2,
+      column: 1
+    });
+    assert.equal(mapping.source, 'two.js');
+
+    mapping = map.originalPositionFor({
+      line: 1,
+      column: 1
+    });
+    assert.equal(mapping.source, 'one.js');
+
+
+    map = new SourceMapConsumer(util.testMapEmptySourceRoot);
+
+    mapping = map.originalPositionFor({
+      line: 2,
+      column: 1
+    });
+    assert.equal(mapping.source, 'two.js');
+
+    mapping = map.originalPositionFor({
+      line: 1,
+      column: 1
+    });
+    assert.equal(mapping.source, 'one.js');
   };
 
   exports['test mapping tokens back exactly'] = function (assert, util) {
     var map = new SourceMapConsumer(util.testMap);
 
     util.assertMapping(1, 1, '/the/root/one.js', 1, 1, null, map, assert);
     util.assertMapping(1, 5, '/the/root/one.js', 1, 5, null, map, assert);
     util.assertMapping(1, 9, '/the/root/one.js', 1, 11, null, map, assert);
@@ -80,42 +126,76 @@ define("test/source-map/test-source-map-
     util.assertMapping(2, 12, '/the/root/two.js', 1, 11, null, map, assert, true);
 
     // Finding generated positions
     util.assertMapping(1, 18, '/the/root/one.js', 1, 22, 'bar', map, assert, null, true);
     util.assertMapping(1, 28, '/the/root/one.js', 2, 13, 'baz', map, assert, null, true);
     util.assertMapping(2, 9, '/the/root/two.js', 1, 16, null, map, assert, null, true);
   };
 
+  exports['test mappings and end of lines'] = function (assert, util) {
+    var smg = new SourceMapGenerator({
+      file: 'foo.js'
+    });
+    smg.addMapping({
+      original: { line: 1, column: 1 },
+      generated: { line: 1, column: 1 },
+      source: 'bar.js'
+    });
+    smg.addMapping({
+      original: { line: 2, column: 2 },
+      generated: { line: 2, column: 2 },
+      source: 'bar.js'
+    });
+
+    var map = SourceMapConsumer.fromSourceMap(smg);
+
+    // When finding original positions, mappings end at the end of the line.
+    util.assertMapping(2, 1, null, null, null, null, map, assert, true)
+
+    // When finding generated positions, mappings do not end at the end of the line.
+    util.assertMapping(1, 1, 'bar.js', 2, 1, null, map, assert, null, true);
+  };
+
   exports['test creating source map consumers with )]}\' prefix'] = function (assert, util) {
     assert.doesNotThrow(function () {
       var map = new SourceMapConsumer(")]}'" + JSON.stringify(util.testMap));
     });
   };
 
   exports['test eachMapping'] = function (assert, util) {
-    var map = new SourceMapConsumer(util.testMap);
+    var map;
+
+    map = new SourceMapConsumer(util.testMap);
     var previousLine = -Infinity;
     var previousColumn = -Infinity;
     map.eachMapping(function (mapping) {
       assert.ok(mapping.generatedLine >= previousLine);
 
-      if (mapping.source) {
-        assert.equal(mapping.source.indexOf(util.testMap.sourceRoot), 0);
-      }
+      assert.ok(mapping.source === '/the/root/one.js' || mapping.source === '/the/root/two.js');
 
       if (mapping.generatedLine === previousLine) {
         assert.ok(mapping.generatedColumn >= previousColumn);
         previousColumn = mapping.generatedColumn;
       }
       else {
         previousLine = mapping.generatedLine;
         previousColumn = -Infinity;
       }
     });
+
+    map = new SourceMapConsumer(util.testMapNoSourceRoot);
+    map.eachMapping(function (mapping) {
+      assert.ok(mapping.source === 'one.js' || mapping.source === 'two.js');
+    });
+
+    map = new SourceMapConsumer(util.testMapEmptySourceRoot);
+    map.eachMapping(function (mapping) {
+      assert.ok(mapping.source === 'one.js' || mapping.source === 'two.js');
+    });
   };
 
   exports['test iterating over mappings in a different order'] = function (assert, util) {
     var map = new SourceMapConsumer(util.testMap);
     var previousLine = -Infinity;
     var previousColumn = -Infinity;
     var previousSource = "";
     map.eachMapping(function (mapping) {
--- a/toolkit/devtools/sourcemap/tests/unit/test_source_map_generator.js
+++ b/toolkit/devtools/sourcemap/tests/unit/test_source_map_generator.js
@@ -20,16 +20,20 @@ define("test/source-map/test-source-map-
   var util = require('source-map/util');
 
   exports['test some simple stuff'] = function (assert, util) {
     var map = new SourceMapGenerator({
       file: 'foo.js',
       sourceRoot: '.'
     });
     assert.ok(true);
+
+    var map = new SourceMapGenerator().toJSON();
+    assert.ok(!('file' in map));
+    assert.ok(!('sourceRoot' in map));
   };
 
   exports['test JSON serialization'] = function (assert, util) {
     var map = new SourceMapGenerator({
       file: 'foo.js',
       sourceRoot: '.'
     });
     assert.equal(map.toString(), JSON.stringify(map));
@@ -177,16 +181,34 @@ define("test/source-map/test-source-map-
       name: 'n'
     });
 
     map = JSON.parse(map.toString());
 
     util.assertEqualMaps(assert, map, util.testMap);
   };
 
+  exports['test that adding a mapping with an empty string name does not break generation'] = function (assert, util) {
+    var map = new SourceMapGenerator({
+      file: 'generated-foo.js',
+      sourceRoot: '.'
+    });
+
+    map.addMapping({
+      generated: { line: 1, column: 1 },
+      source: 'bar.js',
+      original: { line: 1, column: 1 },
+      name: ''
+    });
+
+    assert.doesNotThrow(function () {
+      JSON.parse(map.toString());
+    });
+  };
+
   exports['test that source content can be set'] = function (assert, util) {
     var map = new SourceMapGenerator({
       file: 'min.js',
       sourceRoot: '/the/root'
     });
     map.addMapping({
       generated: { line: 1, column: 1 },
       original: { line: 1, column: 1 },
@@ -269,16 +291,228 @@ define("test/source-map/test-source-map-
     // apply source map "mapStep1" to "mapStep2"
     var generator = SourceMapGenerator.fromSourceMap(new SourceMapConsumer(mapStep2));
     generator.applySourceMap(new SourceMapConsumer(mapStep1));
     var actualMap = generator.toJSON();
 
     util.assertEqualMaps(assert, actualMap, expectedMap);
   };
 
+  exports['test applySourceMap throws when file is missing'] = function (assert, util) {
+    var map = new SourceMapGenerator({
+      file: 'test.js'
+    });
+    var map2 = new SourceMapGenerator();
+    assert.throws(function() {
+      map.applySourceMap(new SourceMapConsumer(map2.toJSON()));
+    });
+  };
+
+  exports['test the two additional parameters of applySourceMap'] = function (assert, util) {
+    // Assume the following directory structure:
+    //
+    // http://foo.org/
+    //   bar.coffee
+    //   app/
+    //     coffee/
+    //       foo.coffee
+    //     temp/
+    //       bundle.js
+    //       temp_maps/
+    //         bundle.js.map
+    //     public/
+    //       bundle.min.js
+    //       bundle.min.js.map
+    //
+    // http://www.example.com/
+    //   baz.coffee
+
+    var bundleMap = new SourceMapGenerator({
+      file: 'bundle.js'
+    });
+    bundleMap.addMapping({
+      generated: { line: 3, column: 3 },
+      original: { line: 2, column: 2 },
+      source: '../../coffee/foo.coffee'
+    });
+    bundleMap.setSourceContent('../../coffee/foo.coffee', 'foo coffee');
+    bundleMap.addMapping({
+      generated: { line: 13, column: 13 },
+      original: { line: 12, column: 12 },
+      source: '/bar.coffee'
+    });
+    bundleMap.setSourceContent('/bar.coffee', 'bar coffee');
+    bundleMap.addMapping({
+      generated: { line: 23, column: 23 },
+      original: { line: 22, column: 22 },
+      source: 'http://www.example.com/baz.coffee'
+    });
+    bundleMap.setSourceContent(
+      'http://www.example.com/baz.coffee',
+      'baz coffee'
+    );
+    bundleMap = new SourceMapConsumer(bundleMap.toJSON());
+
+    var minifiedMap = new SourceMapGenerator({
+      file: 'bundle.min.js',
+      sourceRoot: '..'
+    });
+    minifiedMap.addMapping({
+      generated: { line: 1, column: 1 },
+      original: { line: 3, column: 3 },
+      source: 'temp/bundle.js'
+    });
+    minifiedMap.addMapping({
+      generated: { line: 11, column: 11 },
+      original: { line: 13, column: 13 },
+      source: 'temp/bundle.js'
+    });
+    minifiedMap.addMapping({
+      generated: { line: 21, column: 21 },
+      original: { line: 23, column: 23 },
+      source: 'temp/bundle.js'
+    });
+    minifiedMap = new SourceMapConsumer(minifiedMap.toJSON());
+
+    var expectedMap = function (sources) {
+      var map = new SourceMapGenerator({
+        file: 'bundle.min.js',
+        sourceRoot: '..'
+      });
+      map.addMapping({
+        generated: { line: 1, column: 1 },
+        original: { line: 2, column: 2 },
+        source: sources[0]
+      });
+      map.setSourceContent(sources[0], 'foo coffee');
+      map.addMapping({
+        generated: { line: 11, column: 11 },
+        original: { line: 12, column: 12 },
+        source: sources[1]
+      });
+      map.setSourceContent(sources[1], 'bar coffee');
+      map.addMapping({
+        generated: { line: 21, column: 21 },
+        original: { line: 22, column: 22 },
+        source: sources[2]
+      });
+      map.setSourceContent(sources[2], 'baz coffee');
+      return map.toJSON();
+    }
+
+    var actualMap = function (aSourceMapPath) {
+      var map = SourceMapGenerator.fromSourceMap(minifiedMap);
+      // Note that relying on `bundleMap.file` (which is simply 'bundle.js')
+      // instead of supplying the second parameter wouldn't work here.
+      map.applySourceMap(bundleMap, '../temp/bundle.js', aSourceMapPath);
+      return map.toJSON();
+    }
+
+    util.assertEqualMaps(assert, actualMap('../temp/temp_maps'), expectedMap([
+      'coffee/foo.coffee',
+      '/bar.coffee',
+      'http://www.example.com/baz.coffee'
+    ]));
+
+    util.assertEqualMaps(assert, actualMap('/app/temp/temp_maps'), expectedMap([
+      '/app/coffee/foo.coffee',
+      '/bar.coffee',
+      'http://www.example.com/baz.coffee'
+    ]));
+
+    util.assertEqualMaps(assert, actualMap('http://foo.org/app/temp/temp_maps'), expectedMap([
+      'http://foo.org/app/coffee/foo.coffee',
+      'http://foo.org/bar.coffee',
+      'http://www.example.com/baz.coffee'
+    ]));
+
+    // If the third parameter is omitted or set to the current working
+    // directory we get incorrect source paths:
+
+    util.assertEqualMaps(assert, actualMap(), expectedMap([
+      '../coffee/foo.coffee',
+      '/bar.coffee',
+      'http://www.example.com/baz.coffee'
+    ]));
+
+    util.assertEqualMaps(assert, actualMap(''), expectedMap([
+      '../coffee/foo.coffee',
+      '/bar.coffee',
+      'http://www.example.com/baz.coffee'
+    ]));
+
+    util.assertEqualMaps(assert, actualMap('.'), expectedMap([
+      '../coffee/foo.coffee',
+      '/bar.coffee',
+      'http://www.example.com/baz.coffee'
+    ]));
+
+    util.assertEqualMaps(assert, actualMap('./'), expectedMap([
+      '../coffee/foo.coffee',
+      '/bar.coffee',
+      'http://www.example.com/baz.coffee'
+    ]));
+  };
+
+  exports['test applySourceMap name handling'] = function (assert, util) {
+    // Imagine some CoffeeScript code being compiled into JavaScript and then
+    // minified.
+
+    var assertName = function(coffeeName, jsName, expectedName) {
+      var minifiedMap = new SourceMapGenerator({
+        file: 'test.js.min'
+      });
+      minifiedMap.addMapping({
+        generated: { line: 1, column: 4 },
+        original: { line: 1, column: 4 },
+        source: 'test.js',
+        name: jsName
+      });
+
+      var coffeeMap = new SourceMapGenerator({
+        file: 'test.js'
+      });
+      coffeeMap.addMapping({
+        generated: { line: 1, column: 4 },
+        original: { line: 1, column: 0 },
+        source: 'test.coffee',
+        name: coffeeName
+      });
+
+      minifiedMap.applySourceMap(new SourceMapConsumer(coffeeMap.toJSON()));
+
+      new SourceMapConsumer(minifiedMap.toJSON()).eachMapping(function(mapping) {
+        assert.equal(mapping.name, expectedName);
+      });
+    };
+
+    // `foo = 1` -> `var foo = 1;` -> `var a=1`
+    // CoffeeScript doesn’t rename variables, so there’s no need for it to
+    // provide names in its source maps. Minifiers do rename variables and
+    // therefore do provide names in their source maps. So that name should be
+    // retained if the original map lacks names.
+    assertName(null, 'foo', 'foo');
+
+    // `foo = 1` -> `var coffee$foo = 1;` -> `var a=1`
+    // Imagine that CoffeeScript prefixed all variables with `coffee$`. Even
+    // though the minifier then also provides a name, the original name is
+    // what corresponds to the source.
+    assertName('foo', 'coffee$foo', 'foo');
+
+    // `foo = 1` -> `var coffee$foo = 1;` -> `var coffee$foo=1`
+    // Minifiers can turn off variable mangling. Then there’s no need to
+    // provide names in the source map, but the names from the original map are
+    // still needed.
+    assertName('foo', null, 'foo');
+
+    // `foo = 1` -> `var foo = 1;` -> `var foo=1`
+    // No renaming at all.
+    assertName(null, null, null);
+  };
+
   exports['test sorting with duplicate generated mappings'] = function (assert, util) {
     var map = new SourceMapGenerator({
       file: 'test.js'
     });
     map.addMapping({
       generated: { line: 3, column: 0 },
       original: { line: 2, column: 0 },
       source: 'a.js'
@@ -414,12 +648,19 @@ define("test/source-map/test-source-map-
       version: 3,
       file: 'test.js',
       sources: ['a.js'],
       names: ['foo'],
       mappings: 'CACEA;;GAEEA'
     });
   };
 
+  exports['test setting sourcesContent to null when already null'] = function (assert, util) {
+    var smg = new SourceMapGenerator({ file: "foo.js" });
+    assert.doesNotThrow(function() {
+      smg.setSourceContent("bar.js", null);
+    });
+  };
+
 });
 function run_test() {
   runSourceMapTests('test/source-map/test-source-map-generator', do_throw);
 }
--- a/toolkit/devtools/sourcemap/tests/unit/test_source_node.js
+++ b/toolkit/devtools/sourcemap/tests/unit/test_source_node.js
@@ -13,16 +13,22 @@ Components.utils.import('resource://test
  * http://opensource.org/licenses/BSD-3-Clause
  */
 define("test/source-map/test-source-node", ["require", "exports", "module"], function (require, exports, module) {
 
   var SourceMapGenerator = require('source-map/source-map-generator').SourceMapGenerator;
   var SourceMapConsumer = require('source-map/source-map-consumer').SourceMapConsumer;
   var SourceNode = require('source-map/source-node').SourceNode;
 
+  function forEachNewline(fn) {
+    return function (assert, util) {
+      ['\n', '\r\n'].forEach(fn.bind(null, assert, util));
+    }
+  }
+
   exports['test .add()'] = function (assert, util) {
     var node = new SourceNode(null, null, null);
 
     // Adding a string works.
     node.add('function noop() {}');
 
     // Adding another source node works.
     node.add(new SourceNode(null, null, null));
@@ -128,30 +134,45 @@ define("test/source-map/test-source-node
     // Nested
     node = new SourceNode(null, null, null,
                           [new SourceNode(null, null, null, 'hey sexy mama, '),
                            new SourceNode(null, null, null, 'want to kill all humans?')]);
     node.replaceRight(/kill all humans/, 'watch Futurama');
     assert.equal(node.toString(), 'hey sexy mama, want to watch Futurama?');
   };
 
-  exports['test .toStringWithSourceMap()'] = function (assert, util) {
+  exports['test .toStringWithSourceMap()'] = forEachNewline(function (assert, util, nl) {
     var node = new SourceNode(null, null, null,
-                              ['(function () {\n',
+                              ['(function () {' + nl,
                                '  ',
                                  new SourceNode(1, 0, 'a.js', 'someCall', 'originalCall'),
                                  new SourceNode(1, 8, 'a.js', '()'),
-                                 ';\n',
-                               '  ', new SourceNode(2, 0, 'b.js', ['if (foo) bar()']), ';\n',
+                                 ';' + nl,
+                               '  ', new SourceNode(2, 0, 'b.js', ['if (foo) bar()']), ';' + nl,
                                '}());']);
-    var map = node.toStringWithSourceMap({
+    var result = node.toStringWithSourceMap({
       file: 'foo.js'
-    }).map;
+    });
+
+    assert.equal(result.code, [
+      '(function () {',
+      '  someCall();',
+      '  if (foo) bar();',
+      '}());'
+    ].join(nl));
+
+    var map = result.map;
+    var mapWithoutOptions = node.toStringWithSourceMap().map;
 
     assert.ok(map instanceof SourceMapGenerator, 'map instanceof SourceMapGenerator');
+    assert.ok(mapWithoutOptions instanceof SourceMapGenerator, 'mapWithoutOptions instanceof SourceMapGenerator');
+    assert.ok(!('file' in mapWithoutOptions));
+    mapWithoutOptions._file = 'foo.js';
+    util.assertEqualMaps(assert, map.toJSON(), mapWithoutOptions.toJSON());
+
     map = new SourceMapConsumer(map.toString());
 
     var actual;
 
     actual = map.originalPositionFor({
       line: 1,
       column: 4
     });
@@ -186,63 +207,64 @@ define("test/source-map/test-source-node
 
     actual = map.originalPositionFor({
       line: 4,
       column: 2
     });
     assert.equal(actual.source, null);
     assert.equal(actual.line, null);
     assert.equal(actual.column, null);
-  };
+  });
 
-  exports['test .fromStringWithSourceMap()'] = function (assert, util) {
+  exports['test .fromStringWithSourceMap()'] = forEachNewline(function (assert, util, nl) {
+    var testCode = util.testGeneratedCode.replace(/\n/g, nl);
     var node = SourceNode.fromStringWithSourceMap(
-                              util.testGeneratedCode,
+                              testCode,
                               new SourceMapConsumer(util.testMap));
 
     var result = node.toStringWithSourceMap({
       file: 'min.js'
     });
     var map = result.map;
     var code = result.code;
 
-    assert.equal(code, util.testGeneratedCode);
+    assert.equal(code, testCode);
     assert.ok(map instanceof SourceMapGenerator, 'map instanceof SourceMapGenerator');
     map = map.toJSON();
     assert.equal(map.version, util.testMap.version);
     assert.equal(map.file, util.testMap.file);
     assert.equal(map.mappings, util.testMap.mappings);
-  };
+  });
 
-  exports['test .fromStringWithSourceMap() empty map'] = function (assert, util) {
+  exports['test .fromStringWithSourceMap() empty map'] = forEachNewline(function (assert, util, nl) {
     var node = SourceNode.fromStringWithSourceMap(
-                              util.testGeneratedCode,
+                              util.testGeneratedCode.replace(/\n/g, nl),
                               new SourceMapConsumer(util.emptyMap));
     var result = node.toStringWithSourceMap({
       file: 'min.js'
     });
     var map = result.map;
     var code = result.code;
 
-    assert.equal(code, util.testGeneratedCode);
+    assert.equal(code, util.testGeneratedCode.replace(/\n/g, nl));
     assert.ok(map instanceof SourceMapGenerator, 'map instanceof SourceMapGenerator');
     map = map.toJSON();
     assert.equal(map.version, util.emptyMap.version);
     assert.equal(map.file, util.emptyMap.file);
     assert.equal(map.mappings.length, util.emptyMap.mappings.length);
     assert.equal(map.mappings, util.emptyMap.mappings);
-  };
+  });
 
-  exports['test .fromStringWithSourceMap() complex version'] = function (assert, util) {
+  exports['test .fromStringWithSourceMap() complex version'] = forEachNewline(function (assert, util, nl) {
     var input = new SourceNode(null, null, null, [
-      "(function() {\n",
-        "  var Test = {};\n",
-        "  ", new SourceNode(1, 0, "a.js", "Test.A = { value: 1234 };\n"),
-        "  ", new SourceNode(2, 0, "a.js", "Test.A.x = 'xyz';"), "\n",
-        "}());\n",
+      "(function() {" + nl,
+        "  var Test = {};" + nl,
+        "  ", new SourceNode(1, 0, "a.js", "Test.A = { value: 1234 };" + nl),
+        "  ", new SourceNode(2, 0, "a.js", "Test.A.x = 'xyz';"), nl,
+        "}());" + nl,
         "/* Generated Source */"]);
     input = input.toStringWithSourceMap({
       file: 'foo.js'
     });
 
     var node = SourceNode.fromStringWithSourceMap(
                               input.code,
                               new SourceMapConsumer(input.map.toString()));
@@ -253,46 +275,143 @@ define("test/source-map/test-source-node
     var map = result.map;
     var code = result.code;
 
     assert.equal(code, input.code);
     assert.ok(map instanceof SourceMapGenerator, 'map instanceof SourceMapGenerator');
     map = map.toJSON();
     var inputMap = input.map.toJSON();
     util.assertEqualMaps(assert, map, inputMap);
+  });
+
+  exports['test .fromStringWithSourceMap() third argument'] = function (assert, util) {
+    // Assume the following directory structure:
+    //
+    // http://foo.org/
+    //   bar.coffee
+    //   app/
+    //     coffee/
+    //       foo.coffee
+    //       coffeeBundle.js # Made from {foo,bar,baz}.coffee
+    //       maps/
+    //         coffeeBundle.js.map
+    //     js/
+    //       foo.js
+    //     public/
+    //       app.js # Made from {foo,coffeeBundle}.js
+    //       app.js.map
+    //
+    // http://www.example.com/
+    //   baz.coffee
+
+    var coffeeBundle = new SourceNode(1, 0, 'foo.coffee', 'foo(coffee);\n');
+    coffeeBundle.setSourceContent('foo.coffee', 'foo coffee');
+    coffeeBundle.add(new SourceNode(2, 0, '/bar.coffee', 'bar(coffee);\n'));
+    coffeeBundle.add(new SourceNode(3, 0, 'http://www.example.com/baz.coffee', 'baz(coffee);'));
+    coffeeBundle = coffeeBundle.toStringWithSourceMap({
+      file: 'foo.js',
+      sourceRoot: '..'
+    });
+
+    var foo = new SourceNode(1, 0, 'foo.js', 'foo(js);');
+
+    var test = function(relativePath, expectedSources) {
+      var app = new SourceNode();
+      app.add(SourceNode.fromStringWithSourceMap(
+                                coffeeBundle.code,
+                                new SourceMapConsumer(coffeeBundle.map.toString()),
+                                relativePath));
+      app.add(foo);
+      var i = 0;
+      app.walk(function (chunk, loc) {
+        assert.equal(loc.source, expectedSources[i]);
+        i++;
+      });
+      app.walkSourceContents(function (sourceFile, sourceContent) {
+        assert.equal(sourceFile, expectedSources[0]);
+        assert.equal(sourceContent, 'foo coffee');
+      })
+    };
+
+    test('../coffee/maps', [
+      '../coffee/foo.coffee',
+      '/bar.coffee',
+      'http://www.example.com/baz.coffee',
+      'foo.js'
+    ]);
+
+    // If the third parameter is omitted or set to the current working
+    // directory we get incorrect source paths:
+
+    test(undefined, [
+      '../foo.coffee',
+      '/bar.coffee',
+      'http://www.example.com/baz.coffee',
+      'foo.js'
+    ]);
+
+    test('', [
+      '../foo.coffee',
+      '/bar.coffee',
+      'http://www.example.com/baz.coffee',
+      'foo.js'
+    ]);
+
+    test('.', [
+      '../foo.coffee',
+      '/bar.coffee',
+      'http://www.example.com/baz.coffee',
+      'foo.js'
+    ]);
+
+    test('./', [
+      '../foo.coffee',
+      '/bar.coffee',
+      'http://www.example.com/baz.coffee',
+      'foo.js'
+    ]);
   };
 
-  exports['test .fromStringWithSourceMap() merging duplicate mappings'] = function (assert, util) {
+  exports['test .toStringWithSourceMap() merging duplicate mappings'] = forEachNewline(function (assert, util, nl) {
     var input = new SourceNode(null, null, null, [
       new SourceNode(1, 0, "a.js", "(function"),
-      new SourceNode(1, 0, "a.js", "() {\n"),
+      new SourceNode(1, 0, "a.js", "() {" + nl),
       "  ",
       new SourceNode(1, 0, "a.js", "var Test = "),
-      new SourceNode(1, 0, "b.js", "{};\n"),
+      new SourceNode(1, 0, "b.js", "{};" + nl),
       new SourceNode(2, 0, "b.js", "Test"),
       new SourceNode(2, 0, "b.js", ".A", "A"),
-      new SourceNode(2, 20, "b.js", " = { value: 1234 };\n", "A"),
-      "}());\n",
+      new SourceNode(2, 20, "b.js", " = { value: ", "A"),
+      "1234",
+      new SourceNode(2, 40, "b.js", " };" + nl, "A"),
+      "}());" + nl,
       "/* Generated Source */"
     ]);
     input = input.toStringWithSourceMap({
       file: 'foo.js'
     });
 
+    assert.equal(input.code, [
+      "(function() {",
+      "  var Test = {};",
+      "Test.A = { value: 1234 };",
+      "}());",
+      "/* Generated Source */"
+    ].join(nl))
+
     var correctMap = new SourceMapGenerator({
       file: 'foo.js'
     });
     correctMap.addMapping({
       generated: { line: 1, column: 0 },
       source: 'a.js',
       original: { line: 1, column: 0 }
     });
-    correctMap.addMapping({
-      generated: { line: 2, column: 0 }
-    });
+    // Here is no need for a empty mapping,
+    // because mappings ends at eol
     correctMap.addMapping({
       generated: { line: 2, column: 2 },
       source: 'a.js',
       original: { line: 1, column: 0 }
     });
     correctMap.addMapping({
       generated: { line: 2, column: 13 },
       source: 'b.js',
@@ -310,25 +429,153 @@ define("test/source-map/test-source-node
       original: { line: 2, column: 0 }
     });
     correctMap.addMapping({
       generated: { line: 3, column: 6 },
       source: 'b.js',
       name: 'A',
       original: { line: 2, column: 20 }
     });
+    // This empty mapping is required,
+    // because there is a hole in the middle of the line
     correctMap.addMapping({
-      generated: { line: 4, column: 0 }
+      generated: { line: 3, column: 18 }
+    });
+    correctMap.addMapping({
+      generated: { line: 3, column: 22 },
+      source: 'b.js',
+      name: 'A',
+      original: { line: 2, column: 40 }
+    });
+    // Here is no need for a empty mapping,
+    // because mappings ends at eol
+
+    var inputMap = input.map.toJSON();
+    correctMap = correctMap.toJSON();
+    util.assertEqualMaps(assert, inputMap, correctMap);
+  });
+
+  exports['test .toStringWithSourceMap() multi-line SourceNodes'] = forEachNewline(function (assert, util, nl) {
+    var input = new SourceNode(null, null, null, [
+      new SourceNode(1, 0, "a.js", "(function() {" + nl + "var nextLine = 1;" + nl + "anotherLine();" + nl),
+      new SourceNode(2, 2, "b.js", "Test.call(this, 123);" + nl),
+      new SourceNode(2, 2, "b.js", "this['stuff'] = 'v';" + nl),
+      new SourceNode(2, 2, "b.js", "anotherLine();" + nl),
+      "/*" + nl + "Generated" + nl + "Source" + nl + "*/" + nl,
+      new SourceNode(3, 4, "c.js", "anotherLine();" + nl),
+      "/*" + nl + "Generated" + nl + "Source" + nl + "*/"
+    ]);
+    input = input.toStringWithSourceMap({
+      file: 'foo.js'
+    });
+
+    assert.equal(input.code, [
+      "(function() {",
+      "var nextLine = 1;",
+      "anotherLine();",
+      "Test.call(this, 123);",
+      "this['stuff'] = 'v';",
+      "anotherLine();",
+      "/*",
+      "Generated",
+      "Source",
+      "*/",
+      "anotherLine();",
+      "/*",
+      "Generated",
+      "Source",
+      "*/"
+    ].join(nl));
+
+    var correctMap = new SourceMapGenerator({
+      file: 'foo.js'
+    });
+    correctMap.addMapping({
+      generated: { line: 1, column: 0 },
+      source: 'a.js',
+      original: { line: 1, column: 0 }
+    });
+    correctMap.addMapping({
+      generated: { line: 2, column: 0 },
+      source: 'a.js',
+      original: { line: 1, column: 0 }
+    });
+    correctMap.addMapping({
+      generated: { line: 3, column: 0 },
+      source: 'a.js',
+      original: { line: 1, column: 0 }
+    });
+    correctMap.addMapping({
+      generated: { line: 4, column: 0 },
+      source: 'b.js',
+      original: { line: 2, column: 2 }
+    });
+    correctMap.addMapping({
+      generated: { line: 5, column: 0 },
+      source: 'b.js',
+      original: { line: 2, column: 2 }
+    });
+    correctMap.addMapping({
+      generated: { line: 6, column: 0 },
+      source: 'b.js',
+      original: { line: 2, column: 2 }
+    });
+    correctMap.addMapping({
+      generated: { line: 11, column: 0 },
+      source: 'c.js',
+      original: { line: 3, column: 4 }
     });
 
     var inputMap = input.map.toJSON();
     correctMap = correctMap.toJSON();
-    util.assertEqualMaps(assert, correctMap, inputMap);
+    util.assertEqualMaps(assert, inputMap, correctMap);
+  });
+
+  exports['test .toStringWithSourceMap() with empty string'] = function (assert, util) {
+    var node = new SourceNode(1, 0, 'empty.js', '');
+    var result = node.toStringWithSourceMap();
+    assert.equal(result.code, '');
   };
 
+  exports['test .toStringWithSourceMap() with consecutive newlines'] = forEachNewline(function (assert, util, nl) {
+    var input = new SourceNode(null, null, null, [
+      "/***/" + nl + nl,
+      new SourceNode(1, 0, "a.js", "'use strict';" + nl),
+      new SourceNode(2, 0, "a.js", "a();"),
+    ]);
+    input = input.toStringWithSourceMap({
+      file: 'foo.js'
+    });
+
+    assert.equal(input.code, [
+      "/***/",
+      "",
+      "'use strict';",
+      "a();",
+    ].join(nl));
+
+    var correctMap = new SourceMapGenerator({
+      file: 'foo.js'
+    });
+    correctMap.addMapping({
+      generated: { line: 3, column: 0 },
+      source: 'a.js',
+      original: { line: 1, column: 0 }
+    });
+    correctMap.addMapping({
+      generated: { line: 4, column: 0 },
+      source: 'a.js',
+      original: { line: 2, column: 0 }
+    });
+
+    var inputMap = input.map.toJSON();
+    correctMap = correctMap.toJSON();
+    util.assertEqualMaps(assert, inputMap, correctMap);
+  });
+
   exports['test setSourceContent with toStringWithSourceMap'] = function (assert, util) {
     var aNode = new SourceNode(1, 1, 'a.js', 'a');
     aNode.setSourceContent('a.js', 'someContent');
     var node = new SourceNode(null, null, null,
                               ['(function () {\n',
                                '  ', aNode,
                                '  ', new SourceNode(1, 1, 'b.js', 'b'),
                                '}());']);
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/sourcemap/tests/unit/test_util.js
@@ -0,0 +1,224 @@
+/*
+ * WARNING!
+ *
+ * Do not edit this file directly, it is built from the sources at
+ * https://github.com/mozilla/source-map/
+ */
+
+Components.utils.import('resource://test/Utils.jsm');
+/* -*- Mode: js; js-indent-level: 2; -*- */
+/*
+ * Copyright 2014 Mozilla Foundation and contributors
+ * Licensed under the New BSD license. See LICENSE or:
+ * http://opensource.org/licenses/BSD-3-Clause
+ */
+define("test/source-map/test-util", ["require", "exports", "module"], function (require, exports, module) {
+
+  var libUtil = require('source-map/util');
+
+  exports['test urls'] = function (assert, util) {
+    var assertUrl = function (url) {
+      assert.equal(url, libUtil.urlGenerate(libUtil.urlParse(url)));
+    };
+    assertUrl('http://');
+    assertUrl('http://www.example.com');
+    assertUrl('http://user:pass@www.example.com');
+    assertUrl('http://www.example.com:80');
+    assertUrl('http://www.example.com/');
+    assertUrl('http://www.example.com/foo/bar');
+    assertUrl('http://www.example.com/foo/bar/');
+    assertUrl('http://user:pass@www.example.com:80/foo/bar/');
+
+    assertUrl('//');
+    assertUrl('//www.example.com');
+    assertUrl('file:///www.example.com');
+
+    assert.equal(libUtil.urlParse(''), null);
+    assert.equal(libUtil.urlParse('.'), null);
+    assert.equal(libUtil.urlParse('..'), null);
+    assert.equal(libUtil.urlParse('a'), null);
+    assert.equal(libUtil.urlParse('a/b'), null);
+    assert.equal(libUtil.urlParse('a//b'), null);
+    assert.equal(libUtil.urlParse('/a'), null);
+    assert.equal(libUtil.urlParse('data:foo,bar'), null);
+  };
+
+  exports['test normalize()'] = function (assert, util) {
+    assert.equal(libUtil.normalize('/..'), '/');
+    assert.equal(libUtil.normalize('/../'), '/');
+    assert.equal(libUtil.normalize('/../../../..'), '/');
+    assert.equal(libUtil.normalize('/../../../../a/b/c'), '/a/b/c');
+    assert.equal(libUtil.normalize('/a/b/c/../../../d/../../e'), '/e');
+
+    assert.equal(libUtil.normalize('..'), '..');
+    assert.equal(libUtil.normalize('../'), '../');
+    assert.equal(libUtil.normalize('../../a/'), '../../a/');
+    assert.equal(libUtil.normalize('a/..'), '.');
+    assert.equal(libUtil.normalize('a/../../..'), '../..');
+
+    assert.equal(libUtil.normalize('/.'), '/');
+    assert.equal(libUtil.normalize('/./'), '/');
+    assert.equal(libUtil.normalize('/./././.'), '/');
+    assert.equal(libUtil.normalize('/././././a/b/c'), '/a/b/c');
+    assert.equal(libUtil.normalize('/a/b/c/./././d/././e'), '/a/b/c/d/e');
+
+    assert.equal(libUtil.normalize(''), '.');
+    assert.equal(libUtil.normalize('.'), '.');
+    assert.equal(libUtil.normalize('./'), '.');
+    assert.equal(libUtil.normalize('././a'), 'a');
+    assert.equal(libUtil.normalize('a/./'), 'a/');
+    assert.equal(libUtil.normalize('a/././.'), 'a');
+
+    assert.equal(libUtil.normalize('/a/b//c////d/////'), '/a/b/c/d/');
+    assert.equal(libUtil.normalize('///a/b//c////d/////'), '///a/b/c/d/');
+    assert.equal(libUtil.normalize('a/b//c////d'), 'a/b/c/d');
+
+    assert.equal(libUtil.normalize('.///.././../a/b//./..'), '../../a')
+
+    assert.equal(libUtil.normalize('http://www.example.com'), 'http://www.example.com');
+    assert.equal(libUtil.normalize('http://www.example.com/'), 'http://www.example.com/');
+    assert.equal(libUtil.normalize('http://www.example.com/./..//a/b/c/.././d//'), 'http://www.example.com/a/b/d/');
+  };
+
+  exports['test join()'] = function (assert, util) {
+    assert.equal(libUtil.join('a', 'b'), 'a/b');
+    assert.equal(libUtil.join('a/', 'b'), 'a/b');
+    assert.equal(libUtil.join('a//', 'b'), 'a/b');
+    assert.equal(libUtil.join('a', 'b/'), 'a/b/');
+    assert.equal(libUtil.join('a', 'b//'), 'a/b/');
+    assert.equal(libUtil.join('a/', '/b'), '/b');
+    assert.equal(libUtil.join('a//', '//b'), '//b');
+
+    assert.equal(libUtil.join('a', '..'), '.');
+    assert.equal(libUtil.join('a', '../b'), 'b');
+    assert.equal(libUtil.join('a/b', '../c'), 'a/c');
+
+    assert.equal(libUtil.join('a', '.'), 'a');
+    assert.equal(libUtil.join('a', './b'), 'a/b');
+    assert.equal(libUtil.join('a/b', './c'), 'a/b/c');
+
+    assert.equal(libUtil.join('a', 'http://www.example.com'), 'http://www.example.com');
+    assert.equal(libUtil.join('a', 'data:foo,bar'), 'data:foo,bar');
+
+
+    assert.equal(libUtil.join('', 'b'), 'b');
+    assert.equal(libUtil.join('.', 'b'), 'b');
+    assert.equal(libUtil.join('', 'b/'), 'b/');
+    assert.equal(libUtil.join('.', 'b/'), 'b/');
+    assert.equal(libUtil.join('', 'b//'), 'b/');
+    assert.equal(libUtil.join('.', 'b//'), 'b/');
+
+    assert.equal(libUtil.join('', '..'), '..');
+    assert.equal(libUtil.join('.', '..'), '..');
+    assert.equal(libUtil.join('', '../b'), '../b');
+    assert.equal(libUtil.join('.', '../b'), '../b');
+
+    assert.equal(libUtil.join('', '.'), '.');
+    assert.equal(libUtil.join('.', '.'), '.');
+    assert.equal(libUtil.join('', './b'), 'b');
+    assert.equal(libUtil.join('.', './b'), 'b');
+
+    assert.equal(libUtil.join('', 'http://www.example.com'), 'http://www.example.com');
+    assert.equal(libUtil.join('.', 'http://www.example.com'), 'http://www.example.com');
+    assert.equal(libUtil.join('', 'data:foo,bar'), 'data:foo,bar');
+    assert.equal(libUtil.join('.', 'data:foo,bar'), 'data:foo,bar');
+
+
+    assert.equal(libUtil.join('..', 'b'), '../b');
+    assert.equal(libUtil.join('..', 'b/'), '../b/');
+    assert.equal(libUtil.join('..', 'b//'), '../b/');
+
+    assert.equal(libUtil.join('..', '..'), '../..');
+    assert.equal(libUtil.join('..', '../b'), '../../b');
+
+    assert.equal(libUtil.join('..', '.'), '..');
+    assert.equal(libUtil.join('..', './b'), '../b');
+
+    assert.equal(libUtil.join('..', 'http://www.example.com'), 'http://www.example.com');
+    assert.equal(libUtil.join('..', 'data:foo,bar'), 'data:foo,bar');
+
+
+    assert.equal(libUtil.join('a', ''), 'a');
+    assert.equal(libUtil.join('a', '.'), 'a');
+    assert.equal(libUtil.join('a/', ''), 'a');
+    assert.equal(libUtil.join('a/', '.'), 'a');
+    assert.equal(libUtil.join('a//', ''), 'a');
+    assert.equal(libUtil.join('a//', '.'), 'a');
+    assert.equal(libUtil.join('/a', ''), '/a');
+    assert.equal(libUtil.join('/a', '.'), '/a');
+    assert.equal(libUtil.join('', ''), '.');
+    assert.equal(libUtil.join('.', ''), '.');
+    assert.equal(libUtil.join('.', ''), '.');
+    assert.equal(libUtil.join('.', '.'), '.');
+    assert.equal(libUtil.join('..', ''), '..');
+    assert.equal(libUtil.join('..', '.'), '..');
+    assert.equal(libUtil.join('http://foo.org/a', ''), 'http://foo.org/a');
+    assert.equal(libUtil.join('http://foo.org/a', '.'), 'http://foo.org/a');
+    assert.equal(libUtil.join('http://foo.org/a/', ''), 'http://foo.org/a');
+    assert.equal(libUtil.join('http://foo.org/a/', '.'), 'http://foo.org/a');
+    assert.equal(libUtil.join('http://foo.org/a//', ''), 'http://foo.org/a');
+    assert.equal(libUtil.join('http://foo.org/a//', '.'), 'http://foo.org/a');
+    assert.equal(libUtil.join('http://foo.org', ''), 'http://foo.org/');
+    assert.equal(libUtil.join('http://foo.org', '.'), 'http://foo.org/');
+    assert.equal(libUtil.join('http://foo.org/', ''), 'http://foo.org/');
+    assert.equal(libUtil.join('http://foo.org/', '.'), 'http://foo.org/');
+    assert.equal(libUtil.join('http://foo.org//', ''), 'http://foo.org/');
+    assert.equal(libUtil.join('http://foo.org//', '.'), 'http://foo.org/');
+    assert.equal(libUtil.join('//www.example.com', ''), '//www.example.com/');
+    assert.equal(libUtil.join('//www.example.com', '.'), '//www.example.com/');
+
+
+    assert.equal(libUtil.join('http://foo.org/a', 'b'), 'http://foo.org/a/b');
+    assert.equal(libUtil.join('http://foo.org/a/', 'b'), 'http://foo.org/a/b');
+    assert.equal(libUtil.join('http://foo.org/a//', 'b'), 'http://foo.org/a/b');
+    assert.equal(libUtil.join('http://foo.org/a', 'b/'), 'http://foo.org/a/b/');
+    assert.equal(libUtil.join('http://foo.org/a', 'b//'), 'http://foo.org/a/b/');
+    assert.equal(libUtil.join('http://foo.org/a/', '/b'), 'http://foo.org/b');
+    assert.equal(libUtil.join('http://foo.org/a//', '//b'), 'http://b');
+
+    assert.equal(libUtil.join('http://foo.org/a', '..'), 'http://foo.org/');
+    assert.equal(libUtil.join('http://foo.org/a', '../b'), 'http://foo.org/b');
+    assert.equal(libUtil.join('http://foo.org/a/b', '../c'), 'http://foo.org/a/c');
+
+    assert.equal(libUtil.join('http://foo.org/a', '.'), 'http://foo.org/a');
+    assert.equal(libUtil.join('http://foo.org/a', './b'), 'http://foo.org/a/b');
+    assert.equal(libUtil.join('http://foo.org/a/b', './c'), 'http://foo.org/a/b/c');
+
+    assert.equal(libUtil.join('http://foo.org/a', 'http://www.example.com'), 'http://www.example.com');
+    assert.equal(libUtil.join('http://foo.org/a', 'data:foo,bar'), 'data:foo,bar');
+
+
+    assert.equal(libUtil.join('http://foo.org', 'a'), 'http://foo.org/a');
+    assert.equal(libUtil.join('http://foo.org/', 'a'), 'http://foo.org/a');
+    assert.equal(libUtil.join('http://foo.org//', 'a'), 'http://foo.org/a');
+    assert.equal(libUtil.join('http://foo.org', '/a'), 'http://foo.org/a');
+    assert.equal(libUtil.join('http://foo.org/', '/a'), 'http://foo.org/a');
+    assert.equal(libUtil.join('http://foo.org//', '/a'), 'http://foo.org/a');
+
+
+    assert.equal(libUtil.join('http://', 'www.example.com'), 'http://www.example.com');
+    assert.equal(libUtil.join('file:///', 'www.example.com'), 'file:///www.example.com');
+    assert.equal(libUtil.join('http://', 'ftp://example.com'), 'ftp://example.com');
+
+    assert.equal(libUtil.join('http://www.example.com', '//foo.org/bar'), 'http://foo.org/bar');
+    assert.equal(libUtil.join('//www.example.com', '//foo.org/bar'), '//foo.org/bar');
+  };
+
+  // TODO Issue #128: Define and test this function properly.
+  exports['test relative()'] = function (assert, util) {
+    assert.equal(libUtil.relative('/the/root', '/the/root/one.js'), 'one.js');
+    assert.equal(libUtil.relative('/the/root', '/the/rootone.js'), '/the/rootone.js');
+
+    assert.equal(libUtil.relative('', '/the/root/one.js'), '/the/root/one.js');
+    assert.equal(libUtil.relative('.', '/the/root/one.js'), '/the/root/one.js');
+    assert.equal(libUtil.relative('', 'the/root/one.js'), 'the/root/one.js');
+    assert.equal(libUtil.relative('.', 'the/root/one.js'), 'the/root/one.js');
+
+    assert.equal(libUtil.relative('/', '/the/root/one.js'), 'the/root/one.js');
+    assert.equal(libUtil.relative('/', 'the/root/one.js'), 'the/root/one.js');
+  };
+
+});
+function run_test() {
+  runSourceMapTests('test/source-map/test-util', do_throw);
+}