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 208940 0e85dd2b1635f8ce164936e013ce8424fe3c1bdd
parent 208939 047f52c44212642d3c3910936c14f08775ea8b8d
child 208941 ce042529cac3b1c006171d3ba4e119f8760ab862
push id1
push userroot
push dateMon, 20 Oct 2014 17:29:22 +0000
reviewerspast
bugs1076887
milestone35.0a1
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);
+}