Bug 849069 - relative source map URLs should be resolved according to the spec's rules; r=past

This commit is contained in:
Nick Fitzgerald 2013-04-09 11:40:00 +03:00
parent 6de4bef71a
commit c1e0cabf5f
14 changed files with 305 additions and 32 deletions

View File

@ -24,7 +24,6 @@
}).call(this);
// TODO bug 849069: this should just be "binary_search.map", not a full path.
/*
//@ sourceMappingURL=http://example.com/browser/browser/devtools/debugger/test/binary_search.map
//@ sourceMappingURL=binary_search.map
*/

View File

@ -1,8 +1,9 @@
{
"version": 3,
"file": "binary_search.js",
"sourceRoot": "",
"sources": [
"http://example.com/browser/browser/devtools/debugger/test/binary_search.coffee"
"binary_search.coffee"
],
"names": [],
"mappings": ";AACA;CAAA;CAAA,CAAA,CAAuB,EAAA,CAAjB,GAAkB,IAAxB;CAEE,OAAA,UAAA;CAAA,EAAQ,CAAR,CAAA;CAAA,EACQ,CAAR,CAAa,CAAL;CADR,EAEQ,CAAR,CAAA;CAEA,EAA0C,CAAR,CAAtB,MAAN;CAGJ,EAA6B,CAAR,CAAA,CAArB;CAAA,EAAQ,CAAR,CAAQ,GAAR;QAAA;CACA,EAA6B,CAAR,CAAA,CAArB;CAAA,EAAQ,EAAR,GAAA;QADA;CAAA,EAIQ,CAAI,CAAZ,CAAA;CAXF,IAIA;CAUA,GAAA,CAAS;CAAT,YAA8B;MAA9B;AAA0C,CAAD,YAAA;MAhBpB;CAAvB,EAAuB;CAAvB"

View File

@ -525,7 +525,9 @@ ThreadActor.prototype = {
originalLine)
return locationPromise.then((aLocation) => {
let line = aLocation.line;
if (this.dbg.findScripts({ url: aLocation.url }).length == 0 || line < 0) {
if (this.dbg.findScripts({ url: aLocation.url }).length == 0 ||
line < 0 ||
line == null) {
return { error: "noScript" };
}
@ -2405,7 +2407,7 @@ ThreadSources.prototype = {
];
}, (e) => {
reportError(e);
delete this._sourceMaps[aScript.sourceMapURL];
delete this._sourceMaps[this._normalize(aScript.sourceMapURL, aScript.url)];
delete this._sourceMapsByGeneratedSource[aScript.url];
return [this.source(aScript.url)];
})
@ -2422,7 +2424,9 @@ ThreadSources.prototype = {
return this._sourceMapsByGeneratedSource[aScript.url];
}
dbg_assert(aScript.sourceMapURL);
let map = this._fetchSourceMap(aScript.sourceMapURL)
let sourceMapURL = this._normalize(aScript.sourceMapURL,
aScript.url);
let map = this._fetchSourceMap(sourceMapURL)
.then((aSourceMap) => {
for (let s of aSourceMap.sources) {
this._generatedUrlsByOriginalUrl[s] = aScript.url;
@ -2437,14 +2441,21 @@ ThreadSources.prototype = {
/**
* Fetch the source map located at the given url.
*/
_fetchSourceMap: function TS__featchSourceMap(aSourceMapURL) {
if (aSourceMapURL in this._sourceMaps) {
return this._sourceMaps[aSourceMapURL];
_fetchSourceMap: function TS__fetchSourceMap(aAbsSourceMapURL) {
if (aAbsSourceMapURL in this._sourceMaps) {
return this._sourceMaps[aAbsSourceMapURL];
} else {
let promise = fetch(aSourceMapURL).then(function (rawSourceMap) {
return new SourceMapConsumer(rawSourceMap);
let promise = fetch(aAbsSourceMapURL).then((rawSourceMap) => {
let map = new SourceMapConsumer(rawSourceMap);
let base = aAbsSourceMapURL.replace(/\/[^\/]+$/, '/');
if (base.indexOf("data:") !== 0) {
map.sourceRoot = map.sourceRoot
? this._normalize(map.sourceRoot, base)
: base;
}
return map;
});
this._sourceMaps[aSourceMapURL] = promise;
this._sourceMaps[aAbsSourceMapURL] = promise;
return promise;
}
},
@ -2507,6 +2518,19 @@ ThreadSources.prototype = {
});
},
/**
* Normalize multiple relative paths towards the base paths on the right.
*/
_normalize: function TS__normalize(...aURLs) {
dbg_assert(aURLs.length > 1);
let base = Services.io.newURI(aURLs.pop(), null, null);
let url;
while ((url = aURLs.pop())) {
base = Services.io.newURI(url, null, base);
}
return base.spec;
},
iter: function TS_iter() {
for (let url in this._sourceActors) {
yield this._sourceActors[url];

View File

@ -175,6 +175,14 @@ function finishClient(aClient)
});
}
/**
* Takes a relative file path and returns the absolute file url for it.
*/
function getFileUrl(aName) {
let file = do_get_file(aName);
return Services.io.newFileURI(file).spec;
}
/**
* Returns the full path of the file with the specified name in a
* platform-independent and URL-like form.
@ -190,3 +198,20 @@ function getFilePath(aName)
}
return path.slice(filePrePath.length);
}
Cu.import("resource://gre/modules/NetUtil.jsm");
/**
* Returns the full text contents of the given file.
*/
function readFile(aFileName) {
let f = do_get_file(aFileName);
let s = Cc["@mozilla.org/network/file-input-stream;1"]
.createInstance(Ci.nsIFileInputStream);
s.init(f, -1, -1, false);
try {
return NetUtil.readInputStreamToString(s, s.available());
} finally {
s.close();
}
}

View File

@ -0,0 +1,6 @@
foo = (n) ->
return "foo" + i for i in [0...n]
[first, second, third] = foo(3)
debugger

View File

@ -0,0 +1,10 @@
{
"version": 3,
"file": "sourcemapped.js",
"sourceRoot": "",
"sources": [
"sourcemapped.coffee"
],
"names": [],
"mappings": ";AAAA;CAAA,KAAA,yBAAA;CAAA;CAAA,CAAA,CAAA,MAAO;CACL,IAAA,GAAA;AAAA,CAAA,EAAA,MAA0B,qDAA1B;CAAA,EAAe,EAAR,QAAA;CAAP,IADI;CAAN,EAAM;;CAAN,CAGA,CAAyB,IAAA;;CAEzB,UALA;CAAA"
}

View File

@ -0,0 +1,16 @@
// Generated by CoffeeScript 1.6.1
(function() {
var first, foo, second, third, _ref;
foo = function(n) {
var i, _i;
for (i = _i = 0; 0 <= n ? _i < n : _i > n; i = 0 <= n ? ++_i : --_i) {
return "foo" + i;
}
};
_ref = foo(3), first = _ref[0], second = _ref[1], third = _ref[2];
debugger;
}).call(this);

View File

@ -10,8 +10,6 @@ var gThreadClient;
// and that they can communicate over the protocol to fetch the source text for
// a given script.
Cu.import("resource://gre/modules/NetUtil.jsm");
function run_test()
{
initTestDebuggerServer();
@ -60,15 +58,9 @@ function test_source()
do_check_true(!aResponse.error);
do_check_true(!!aResponse.source);
let f = do_get_file("test_source-01.js", false);
let s = Cc["@mozilla.org/network/file-input-stream;1"]
.createInstance(Ci.nsIFileInputStream);
s.init(f, -1, -1, false);
do_check_eq(NetUtil.readInputStreamToString(s, s.available()),
do_check_eq(readFile("test_source-01.js"),
aResponse.source);
s.close();
gThreadClient.resume(function () {
finishClient(gClient);
});

View File

@ -0,0 +1,48 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Check that absolute source map urls work.
*/
var gDebuggee;
var gClient;
var gThreadClient;
Components.utils.import('resource:///modules/devtools/SourceMap.jsm');
function run_test()
{
initTestDebuggerServer();
gDebuggee = addTestGlobal("test-source-map");
gClient = new DebuggerClient(DebuggerServer.connectPipe());
gClient.connect(function() {
attachTestGlobalClientAndResume(gClient, "test-source-map", function(aResponse, aThreadClient) {
gThreadClient = aThreadClient;
test_absolute_source_map();
});
});
do_test_pending();
}
function test_absolute_source_map()
{
gClient.addOneTimeListener("newSource", function _onNewSource(aEvent, aPacket) {
do_check_eq(aEvent, "newSource");
do_check_eq(aPacket.type, "newSource");
do_check_true(!!aPacket.source);
do_check_true(aPacket.source.url.indexOf("sourcemapped.coffee") !== -1,
"The new source should be a coffee file.");
do_check_eq(aPacket.source.url.indexOf("sourcemapped.js"), -1,
"The new source should not be a js file.");
finishClient(gClient);
});
code = readFile("sourcemapped.js")
+ "\n//@ sourceMappingURL=" + getFileUrl("source-map-data/sourcemapped.map");
Components.utils.evalInSandbox(code, gDebuggee, "1.8",
getFileUrl("sourcemapped.js"), 1);
}

View File

@ -0,0 +1,48 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Check that relative source map urls work.
*/
var gDebuggee;
var gClient;
var gThreadClient;
Components.utils.import('resource:///modules/devtools/SourceMap.jsm');
function run_test()
{
initTestDebuggerServer();
gDebuggee = addTestGlobal("test-source-map");
gClient = new DebuggerClient(DebuggerServer.connectPipe());
gClient.connect(function() {
attachTestGlobalClientAndResume(gClient, "test-source-map", function(aResponse, aThreadClient) {
gThreadClient = aThreadClient;
test_relative_source_map();
});
});
do_test_pending();
}
function test_relative_source_map()
{
gClient.addOneTimeListener("newSource", function _onNewSource(aEvent, aPacket) {
do_check_eq(aEvent, "newSource");
do_check_eq(aPacket.type, "newSource");
do_check_true(!!aPacket.source);
do_check_true(aPacket.source.url.indexOf("sourcemapped.coffee") !== -1,
"The new source should be a coffee file.");
do_check_eq(aPacket.source.url.indexOf("sourcemapped.js"), -1,
"The new source should not be a js file.");
finishClient(gClient);
});
code = readFile("sourcemapped.js")
+ "\n//@ sourceMappingURL=source-map-data/sourcemapped.map";
Components.utils.evalInSandbox(code, gDebuggee, "1.8",
getFileUrl("sourcemapped.js"), 1);
}

View File

@ -81,6 +81,12 @@ reason = bug 820380
[test_sourcemaps-01.js]
[test_sourcemaps-02.js]
[test_sourcemaps-03.js]
[test_sourcemaps-04.js]
skip-if = toolkit == "gonk"
reason = bug 820380
[test_sourcemaps-05.js]
skip-if = toolkit == "gonk"
reason = bug 820380
[test_objectgrips-01.js]
[test_objectgrips-02.js]
[test_objectgrips-03.js]

View File

@ -470,10 +470,34 @@ define('source-map/util', ['require', 'exports', 'module' , ], function(require,
}
exports.getArg = getArg;
var urlRegexp = /([\w+\-.]+):\/\/((\w+:\w+)@)?([\w.]+)?(:(\d+))?(\S+)?/;
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]
};
}
function join(aRoot, aPath) {
return aPath.charAt(0) === '/'
? aPath
: aRoot.replace(/\/$/, '') + '/' + aPath;
var url;
if (aPath.match(urlRegexp)) {
return aPath;
}
if (aPath.charAt(0) === '/' && (url = urlParse(aRoot))) {
return aRoot.replace(url.path, '') + aPath;
}
return aRoot.replace(/\/$/, '') + '/' + aPath;
}
exports.join = join;

View File

@ -145,9 +145,21 @@ define('test/source-map/util', ['require', 'exports', 'module' , 'lib/source-ma
assert.equal(origMapping.column, originalColumn,
'Incorrect column, expected ' + JSON.stringify(originalColumn)
+ ', got ' + JSON.stringify(origMapping.column));
assert.equal(origMapping.source,
originalSource ? util.join(map._sourceRoot, originalSource) : null,
'Incorrect source, expected ' + JSON.stringify(originalSource)
var expectedSource;
if (originalSource && map.sourceRoot && originalSource.indexOf(map.sourceRoot) === 0) {
expectedSource = originalSource;
} else if (originalSource) {
expectedSource = map.sourceRoot
? util.join(map.sourceRoot, originalSource)
: originalSource;
} else {
expectedSource = null;
}
assert.equal(origMapping.source, expectedSource,
'Incorrect source, expected ' + JSON.stringify(expectedSource)
+ ', got ' + JSON.stringify(origMapping.source));
}
@ -238,10 +250,34 @@ define('lib/source-map/util', ['require', 'exports', 'module' , ], function(requ
}
exports.getArg = getArg;
var urlRegexp = /([\w+\-.]+):\/\/((\w+:\w+)@)?([\w.]+)?(:(\d+))?(\S+)?/;
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]
};
}
function join(aRoot, aPath) {
return aPath.charAt(0) === '/'
? aPath
: aRoot.replace(/\/$/, '') + '/' + aPath;
var url;
if (aPath.match(urlRegexp)) {
return aPath;
}
if (aPath.charAt(0) === '/' && (url = urlParse(aRoot))) {
return aRoot.replace(url.path, '') + aPath;
}
return aRoot.replace(/\/$/, '') + '/' + aPath;
}
exports.join = join;

View File

@ -251,8 +251,46 @@ define("test/source-map/test-source-map-consumer", ["require", "exports", "modul
map = new SourceMapConsumer(map.toString());
var sources = map.sources;
assert.equal(map.sources.length, 1);
assert.equal(map.sources[0], 'http://www.example.com/original.js');
assert.equal(sources.length, 1);
assert.equal(sources[0], 'http://www.example.com/original.js');
};
exports['test github issue #43'] = function (assert, util) {
var map = new SourceMapGenerator({
sourceRoot: 'http://example.com',
file: 'foo.js'
});
map.addMapping({
original: { line: 1, column: 1 },
generated: { line: 2, column: 2 },
source: 'http://cdn.example.com/original.js'
});
map = new SourceMapConsumer(map.toString());
var sources = map.sources;
assert.equal(sources.length, 1,
'Should only be one source.');
assert.equal(sources[0], 'http://cdn.example.com/original.js',
'Should not be joined with the sourceRoot.');
};
exports['test absolute path, but same host sources'] = function (assert, util) {
var map = new SourceMapGenerator({
sourceRoot: 'http://example.com/foo/bar',
file: 'foo.js'
});
map.addMapping({
original: { line: 1, column: 1 },
generated: { line: 2, column: 2 },
source: '/original.js'
});
map = new SourceMapConsumer(map.toString());
var sources = map.sources;
assert.equal(sources.length, 1,
'Should only be one source.');
assert.equal(sources[0], 'http://example.com/original.js',
'Source should be relative the host of the source root.');
};
});