Merge m-c to birch.

This commit is contained in:
Ryan VanderMeulen 2013-04-16 16:03:26 -04:00
commit 1f4f27488e
40 changed files with 2838 additions and 478 deletions

View File

@ -42,6 +42,7 @@ pref("extensions.getAddons.getWithPerformance.url", "https://services.addons.moz
pref("extensions.getAddons.search.browseURL", "https://addons.mozilla.org/%LOCALE%/firefox/search?q=%TERMS%&platform=%OS%&appver=%VERSION%");
pref("extensions.getAddons.search.url", "https://services.addons.mozilla.org/%LOCALE%/firefox/api/%API_VERSION%/search/%TERMS%/all/%MAX_RESULTS%/%OS%/%VERSION%/%COMPATIBILITY_MODE%?src=firefox");
pref("extensions.webservice.discoverURL", "https://services.addons.mozilla.org/%LOCALE%/firefox/discovery/pane/%VERSION%/%OS%/%COMPATIBILITY_MODE%");
pref("extensions.getAddons.recommended.url", "https://services.addons.mozilla.org/%LOCALE%/%APP%/api/%API_VERSION%/list/recommended/all/%MAX_RESULTS%/%OS%/%VERSION%?src=firefox");
// Blocklist preferences
pref("extensions.blocklist.enabled", true);
@ -1059,6 +1060,7 @@ pref("devtools.debugger.remote-host", "localhost");
pref("devtools.debugger.remote-autoconnect", false);
pref("devtools.debugger.remote-connection-retries", 3);
pref("devtools.debugger.remote-timeout", 20000);
pref("devtools.debugger.source-maps-enabled", false);
// The default Debugger UI settings
pref("devtools.debugger.ui.win-x", 0);

View File

@ -253,6 +253,8 @@ let DebuggerController = {
if (aCallback) {
aCallback();
}
}, {
useSourceMaps: Services.prefs.getBoolPref("devtools.debugger.source-maps-enabled")
});
},
@ -1102,7 +1104,7 @@ SourceScripts.prototype = {
*/
_onSourcesAdded: function SS__onSourcesAdded(aResponse) {
if (aResponse.error) {
Cu.reportError("Error getting sources: " + aResponse.message);
Cu.reportError(new Error("Error getting sources: " + aResponse.message));
return;
}

View File

@ -96,6 +96,7 @@ MOCHITEST_BROWSER_TESTS = \
browser_dbg_bfcache.js \
browser_dbg_progress-listener-bug.js \
browser_dbg_chrome-debugging.js \
browser_dbg_source_maps-01.js \
head.js \
helpers.js \
$(NULL)
@ -127,6 +128,10 @@ MOCHITEST_BROWSER_PAGES = \
test-function-search-01.js \
test-function-search-02.js \
test-function-search-03.js \
binary_search.html \
binary_search.coffee \
binary_search.js \
binary_search.map \
$(NULL)
MOCHITEST_BROWSER_FILES_PARTS = MOCHITEST_BROWSER_TESTS MOCHITEST_BROWSER_PAGES

View File

@ -0,0 +1,18 @@
# Uses a binary search algorithm to locate a value in the specified array.
window.binary_search = (items, value) ->
start = 0
stop = items.length - 1
pivot = Math.floor (start + stop) / 2
while items[pivot] isnt value and start < stop
# Adjust the search area.
stop = pivot - 1 if value < items[pivot]
start = pivot + 1 if value > items[pivot]
# Recalculate the pivot.
pivot = Math.floor (stop + start) / 2
# Make sure we've found the correct value.
if items[pivot] is value then pivot else -1

View File

@ -0,0 +1,12 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset='utf-8'/>
<title>Browser Debugger Source Map Test</title>
<!-- Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ -->
<script type="text/javascript" src="binary_search.js"></script>
</head>
<body>
</body>
</html>

View File

@ -0,0 +1,29 @@
// Generated by CoffeeScript 1.6.1
(function() {
window.binary_search = function(items, value) {
var pivot, start, stop;
start = 0;
stop = items.length - 1;
pivot = Math.floor((start + stop) / 2);
while (items[pivot] !== value && start < stop) {
if (value < items[pivot]) {
stop = pivot - 1;
}
if (value > items[pivot]) {
start = pivot + 1;
}
pivot = Math.floor((stop + start) / 2);
}
if (items[pivot] === value) {
return pivot;
} else {
return -1;
}
};
}).call(this);
/*
//@ sourceMappingURL=binary_search.map
*/

View File

@ -0,0 +1,10 @@
{
"version": 3,
"file": "binary_search.js",
"sourceRoot": "",
"sources": [
"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

@ -27,19 +27,22 @@ function test()
gDebugger = gPane.panelWin;
resumed = true;
gDebugger.addEventListener("Debugger:SourceShown", onScriptShown);
gDebugger.addEventListener("Debugger:SourceShown", onSourceShown);
gDebugger.DebuggerController.activeThread.addOneTimeListener("framesadded", function() {
framesAdded = true;
executeSoon(startTest);
});
gDebugger.DebuggerController.activeThread.addOneTimeListener("framesadded",
onFramesAdded);
executeSoon(function() {
gDebuggee.firstCall();
});
});
function onScriptShown(aEvent) {
function onFramesAdded(aEvent) {
framesAdded = true;
executeSoon(startTest);
}
function onSourceShown(aEvent) {
scriptShown = aEvent.detail.url.indexOf("-02.js") != -1;
executeSoon(startTest);
}
@ -48,8 +51,8 @@ function test()
{
if (scriptShown && framesAdded && resumed && !testStarted) {
testStarted = true;
gDebugger.removeEventListener("Debugger:SourceShown", onScriptShown);
Services.tm.currentThread.dispatch({ run: performTest }, 0);
gDebugger.removeEventListener("Debugger:SourceShown", onSourceShown);
executeSoon(performTest);
}
}

View File

@ -0,0 +1,156 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Test that we can set breakpoints and step through source mapped coffee
* script.
*/
const TAB_URL = EXAMPLE_URL + "binary_search.html";
var gPane = null;
var gTab = null;
var gDebuggee = null;
var gDebugger = null;
function test()
{
let scriptShown = false;
let framesAdded = false;
let resumed = false;
let testStarted = false;
Services.prefs.setBoolPref("devtools.debugger.source-maps-enabled", true);
debug_tab_pane(TAB_URL, function(aTab, aDebuggee, aPane) {
resumed = true;
gTab = aTab;
gDebuggee = aDebuggee;
gPane = aPane;
gDebugger = gPane.panelWin;
gDebugger.addEventListener("Debugger:SourceShown", function _onSourceShown(aEvent) {
gDebugger.removeEventListener("Debugger:SourceShown", _onSourceShown);
ok(aEvent.detail.url.indexOf(".coffee") != -1,
"The debugger should show the source mapped coffee script file.");
ok(gDebugger.editor.getText().search(/isnt/) != -1,
"The debugger's editor should have the coffee script source displayed.");
testSetBreakpoint();
});
});
}
function testSetBreakpoint() {
let { activeThread } = gDebugger.DebuggerController;
activeThread.interrupt(function (aResponse) {
activeThread.setBreakpoint({
url: EXAMPLE_URL + "binary_search.coffee",
line: 5
}, function (aResponse, bpClient) {
ok(!aResponse.error,
"Should be able to set a breakpoint in a coffee script file.");
testSetBreakpointBlankLine();
});
});
}
function testSetBreakpointBlankLine() {
let { activeThread } = gDebugger.DebuggerController;
activeThread.setBreakpoint({
url: EXAMPLE_URL + "binary_search.coffee",
line: 3
}, function (aResponse, bpClient) {
ok(aResponse.actualLocation,
"Because 3 is empty, we should have an actualLocation");
is(aResponse.actualLocation.url, EXAMPLE_URL + "binary_search.coffee",
"actualLocation.url should be source mapped to the coffee file");
is(aResponse.actualLocation.line, 2,
"actualLocation.line should be source mapped back to 2");
testHitBreakpoint();
});
}
function testHitBreakpoint() {
let { activeThread } = gDebugger.DebuggerController;
activeThread.resume(function (aResponse) {
ok(!aResponse.error, "Shouldn't get an error resuming");
is(aResponse.type, "resumed", "Type should be 'resumed'");
activeThread.addOneTimeListener("paused", function (aEvent, aPacket) {
is(aPacket.type, "paused",
"We should now be paused again");
is(aPacket.why.type, "breakpoint",
"and the reason we should be paused is because we hit a breakpoint");
// Check that we stopped at the right place, by making sure that the
// environment is in the state that we expect.
is(aPacket.frame.environment.bindings.variables.start.value, 0,
"'start' is 0");
is(aPacket.frame.environment.bindings.variables.stop.value.type, "undefined",
"'stop' hasn't been assigned to yet");
is(aPacket.frame.environment.bindings.variables.pivot.value.type, "undefined",
"'pivot' hasn't been assigned to yet");
waitForCaretPos(4, testStepping);
});
// This will cause the breakpoint to be hit, and put us back in the paused
// state.
executeSoon(function() {
gDebuggee.binary_search([0, 2, 3, 5, 7, 10], 5);
});
});
}
function testStepping() {
let { activeThread } = gDebugger.DebuggerController;
activeThread.stepIn(function (aResponse) {
ok(!aResponse.error, "Shouldn't get an error resuming");
is(aResponse.type, "resumed", "Type should be 'resumed'");
// After stepping, we will pause again, so listen for that.
activeThread.addOneTimeListener("paused", function (aEvent, aPacket) {
// Check that we stopped at the right place, by making sure that the
// environment is in the state that we expect.
is(aPacket.frame.environment.bindings.variables.start.value, 0,
"'start' is 0");
is(aPacket.frame.environment.bindings.variables.stop.value, 5,
"'stop' hasn't been assigned to yet");
is(aPacket.frame.environment.bindings.variables.pivot.value.type, "undefined",
"'pivot' hasn't been assigned to yet");
waitForCaretPos(5, closeDebuggerAndFinish);
});
});
}
function waitForCaretPos(number, callback)
{
// Poll every few milliseconds until the source editor line is active.
let count = 0;
let intervalID = window.setInterval(function() {
info("count: " + count + " ");
if (++count > 50) {
ok(false, "Timed out while polling for the line.");
window.clearInterval(intervalID);
return closeDebuggerAndFinish();
}
if (gDebugger.DebuggerView.editor.getCaretPosition().line != number) {
return;
}
// We got the source editor at the expected line, it's safe to callback.
window.clearInterval(intervalID);
callback();
}, 100);
}
registerCleanupFunction(function() {
Services.prefs.setBoolPref("devtools.debugger.source-maps-enabled", false);
removeTab(gTab);
gPane = null;
gTab = null;
gDebuggee = null;
gDebugger = null;
});

View File

@ -322,13 +322,15 @@ TabTarget.prototype = {
event.title = aPacket.title;
// Send any stored event payload (DOMWindow or nsIRequest) for backwards
// compatibility with non-remotable tools.
event._navPayload = this._navPayload;
if (aPacket.state == "start") {
event._navPayload = this._navRequest;
this.emit("will-navigate", event);
this._navRequest = null;
} else {
event._navPayload = this._navWindow;
this.emit("navigate", event);
this._navWindow = null;
}
this._navPayload = null;
}.bind(this);
this.client.addListener("tabNavigated", this._onTabNavigated);
},
@ -467,7 +469,7 @@ TabWebProgressListener.prototype = {
// Emit the event if the target is not remoted or store the payload for
// later emission otherwise.
if (this.target._client) {
this.target._navPayload = request;
this.target._navRequest = request;
} else {
this.target.emit("will-navigate", request);
}
@ -485,7 +487,7 @@ TabWebProgressListener.prototype = {
// Emit the event if the target is not remoted or store the payload for
// later emission otherwise.
if (this.target._client) {
this.target._navPayload = window;
this.target._navWindow = window;
} else {
this.target.emit("navigate", window);
}
@ -500,6 +502,8 @@ TabWebProgressListener.prototype = {
this.target.tab.linkedBrowser.removeProgressListener(this);
}
this.target._webProgressListener = null;
this.target._navRequest = null;
this.target._navWindow = null;
this.target = null;
}
};

View File

@ -121,6 +121,7 @@ MOCHITEST_BROWSER_FILES = \
browser_console_variables_view_while_debugging.js \
browser_console.js \
browser_longstring_hang.js \
browser_console_consolejsm_output.js \
head.js \
$(NULL)

View File

@ -0,0 +1,118 @@
/*
* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/
*/
// Test that Console.jsm outputs messages to the Browser Console, bug 851231.
function test()
{
HUDConsoleUI.toggleBrowserConsole().then(consoleOpened);
let hud = null;
function consoleOpened(aHud)
{
hud = aHud;
hud.jsterm.clearOutput(true);
let console = Cu.import("resource://gre/modules/devtools/Console.jsm", {}).console;
console.time("foobarTimer");
let foobar = { bug851231prop: "bug851231value" };
console.log("bug851231-log");
console.info("bug851231-info");
console.warn("bug851231-warn");
console.error("bug851231-error", foobar);
console.debug("bug851231-debug");
console.trace();
console.dir(document);
console.timeEnd("foobarTimer");
info("wait for the Console.jsm messages");
waitForMessages({
webconsole: hud,
messages: [
{
name: "console.log output",
text: "bug851231-log",
category: CATEGORY_WEBDEV,
severity: SEVERITY_LOG,
},
{
name: "console.info output",
text: "bug851231-info",
category: CATEGORY_WEBDEV,
severity: SEVERITY_INFO,
},
{
name: "console.warn output",
text: "bug851231-warn",
category: CATEGORY_WEBDEV,
severity: SEVERITY_WARNING,
},
{
name: "console.error output",
text: /\bbug851231-error\b.+\[object Object\]/,
category: CATEGORY_WEBDEV,
severity: SEVERITY_ERROR,
objects: true,
},
{
name: "console.debug output",
text: "bug851231-debug",
category: CATEGORY_WEBDEV,
severity: SEVERITY_LOG,
},
{
name: "console.trace output",
consoleTrace: {
file: "browser_console_consolejsm_output.js",
fn: "consoleOpened",
},
},
{
name: "console.dir output",
consoleDir: "[object XULDocument]",
},
{
name: "console.time output",
consoleTime: "foobarTimer",
},
{
name: "console.timeEnd output",
consoleTimeEnd: "foobarTimer",
},
],
}).then((aResults) => {
let consoleErrorMsg = aResults[3];
ok(consoleErrorMsg, "console.error message element found");
let clickable = consoleErrorMsg.clickableElements[0];
ok(clickable, "clickable object found for console.error");
let onFetch = (aEvent, aVar) => {
// Skip the notification from console.dir variablesview-fetched.
if (aVar._variablesView != hud.jsterm._variablesView) {
return;
}
hud.jsterm.off("variablesview-fetched", onFetch);
ok(aVar, "object inspector opened on click");
findVariableViewProperties(aVar, [{
name: "bug851231prop",
value: "bug851231value",
}], { webconsole: hud }).then(finishTest);
};
hud.jsterm.on("variablesview-fetched", onFetch);
scrollOutputToNode(clickable);
info("wait for variablesview-fetched");
executeSoon(() =>
EventUtils.synthesizeMouse(clickable, 2, 2, {}, hud.iframeWindow));
});
}
}

View File

@ -42,13 +42,10 @@ function test()
function onInitialString(aResults)
{
let msg = [...aResults[0].matched][0];
ok(msg, "console.log result message element");
let clickable = msg.querySelector(".longStringEllipsis");
let clickable = aResults[0].longStrings[0];
ok(clickable, "long string ellipsis is shown");
scrollToVisible(clickable);
scrollOutputToNode(clickable);
executeSoon(() => {
EventUtils.synthesizeMouse(clickable, 2, 2, {}, hud.iframeWindow);
@ -73,16 +70,4 @@ function test()
}).then(finishTest);
});
}
function scrollToVisible(aNode)
{
let richListBoxNode = aNode.parentNode;
while (richListBoxNode.tagName != "richlistbox") {
richListBoxNode = richListBoxNode.parentNode;
}
let boxObject = richListBoxNode.scrollBoxObject;
let nsIScrollBoxObject = boxObject.QueryInterface(Ci.nsIScrollBoxObject);
nsIScrollBoxObject.ensureElementIsVisible(aNode);
}
}

View File

@ -252,7 +252,7 @@ function waitForOpenContextMenu(aContextMenu, aOptions) {
function dumpConsoles()
{
if (gPendingOutputTest) {
console.log("dumpConsoles");
console.log("dumpConsoles start");
for each (let hud in HUDService.hudReferences) {
if (!hud.outputNode) {
console.debug("no output content for", hud.hudId);
@ -261,25 +261,38 @@ function dumpConsoles()
console.debug("output content for", hud.hudId);
for (let elem of hud.outputNode.childNodes) {
let text = getMessageElementText(elem);
let repeats = elem.querySelector(".webconsole-msg-repeat");
if (repeats) {
repeats = repeats.getAttribute("value");
}
console.debug("date", elem.timestamp,
"class", elem.className,
"category", elem.category,
"severity", elem.severity,
"repeats", repeats,
"clipboardText", elem.clipboardText,
"text", text);
dumpMessageElement(elem);
}
}
console.log("dumpConsoles end");
gPendingOutputTest = 0;
}
}
/**
* Dump to output debug information for the given webconsole message.
*
* @param nsIDOMNode aMessage
* The message element you want to display.
*/
function dumpMessageElement(aMessage)
{
let text = getMessageElementText(aMessage);
let repeats = aMessage.querySelector(".webconsole-msg-repeat");
if (repeats) {
repeats = repeats.getAttribute("value");
}
console.debug("id", aMessage.getAttribute("id"),
"date", aMessage.timestamp,
"class", aMessage.className,
"category", aMessage.category,
"severity", aMessage.severity,
"repeats", repeats,
"clipboardText", aMessage.clipboardText,
"text", text);
}
function finishTest()
{
browser = hudId = hud = filterBox = outputNode = cs = null;
@ -888,6 +901,104 @@ function waitForMessages(aOptions)
return result;
}
function checkConsoleTrace(aRule, aElement)
{
let elemText = getMessageElementText(aElement);
let trace = aRule.consoleTrace;
if (!checkText("Stack trace from ", elemText)) {
return false;
}
let clickable = aElement.querySelector(".hud-clickable");
if (!clickable) {
ok(false, "console.trace() message is missing .hud-clickable");
displayErrorContext(aRule, aElement);
return false;
}
aRule.clickableElements = [clickable];
if (trace.file &&
!checkText("from " + trace.file + ", ", elemText)) {
ok(false, "console.trace() message is missing the file name: " +
trace.file);
displayErrorContext(aRule, aElement);
return false;
}
if (trace.fn &&
!checkText(", function " + trace.fn + ", ", elemText)) {
ok(false, "console.trace() message is missing the function name: " +
trace.fn);
displayErrorContext(aRule, aElement);
return false;
}
if (trace.line &&
!checkText(", line " + trace.line + ".", elemText)) {
ok(false, "console.trace() message is missing the line number: " +
trace.line);
displayErrorContext(aRule, aElement);
return false;
}
aRule.category = CATEGORY_WEBDEV;
aRule.severity = SEVERITY_LOG;
return true;
}
function checkConsoleTime(aRule, aElement)
{
let elemText = getMessageElementText(aElement);
let time = aRule.consoleTime;
if (!checkText(time + ": timer started", elemText)) {
return false;
}
aRule.category = CATEGORY_WEBDEV;
aRule.severity = SEVERITY_LOG;
return true;
}
function checkConsoleTimeEnd(aRule, aElement)
{
let elemText = getMessageElementText(aElement);
let time = aRule.consoleTimeEnd;
let regex = new RegExp(time + ": \\d+ms");
if (!checkText(regex, elemText)) {
return false;
}
aRule.category = CATEGORY_WEBDEV;
aRule.severity = SEVERITY_LOG;
return true;
}
function checkConsoleDir(aRule, aElement)
{
if (!aElement.classList.contains("webconsole-msg-inspector")) {
return false;
}
let elemText = getMessageElementText(aElement);
if (!checkText(aRule.consoleDir, elemText)) {
return false;
}
let iframe = aElement.querySelector("iframe");
if (!iframe) {
ok(false, "console.dir message has no iframe");
return false;
}
return true;
}
function checkMessage(aRule, aElement)
{
let elemText = getMessageElementText(aElement);
@ -900,16 +1011,41 @@ function waitForMessages(aOptions)
return false;
}
if (aRule.category) {
if (aElement.category != aRule.category) {
return false;
}
if (aRule.consoleTrace && !checkConsoleTrace(aRule, aElement)) {
return false;
}
if (aRule.severity) {
if (aElement.severity != aRule.severity) {
return false;
if (aRule.consoleTime && !checkConsoleTime(aRule, aElement)) {
return false;
}
if (aRule.consoleTimeEnd && !checkConsoleTimeEnd(aRule, aElement)) {
return false;
}
if (aRule.consoleDir && !checkConsoleDir(aRule, aElement)) {
return false;
}
let partialMatch = !!(aRule.consoleTrace || aRule.consoleTime ||
aRule.consoleTimeEnd);
if (aRule.category && aElement.category != aRule.category) {
if (partialMatch) {
is(aElement.category, aRule.category,
"message category for rule: " + displayRule(aRule));
displayErrorContext(aRule, aElement);
}
return false;
}
if (aRule.severity && aElement.severity != aRule.severity) {
if (partialMatch) {
is(aElement.severity, aRule.severity,
"message severity for rule: " + displayRule(aRule));
displayErrorContext(aRule, aElement);
}
return false;
}
if (aRule.repeats) {
@ -919,9 +1055,32 @@ function waitForMessages(aOptions)
}
}
let longString = !!aElement.querySelector(".longStringEllipsis");
if ("longString" in aRule && aRule.longString != longString) {
return false;
if ("longString" in aRule) {
let longStrings = aElement.querySelectorAll(".longStringEllipsis");
if (aRule.longString != !!longStrings[0]) {
if (partialMatch) {
is(!!longStrings[0], aRule.longString,
"long string existence check failed for message rule: " +
displayRule(aRule));
displayErrorContext(aRule, aElement);
}
return false;
}
aRule.longStrings = longStrings;
}
if ("objects" in aRule) {
let clickables = aElement.querySelectorAll(".hud-clickable");
if (aRule.objects != !!clickables[0]) {
if (partialMatch) {
is(!!clickables[0], aRule.objects,
"objects existence check failed for message rule: " +
displayRule(aRule));
displayErrorContext(aRule, aElement);
}
return false;
}
aRule.clickableElements = clickables;
}
let count = aRule.count || 1;
@ -936,6 +1095,16 @@ function waitForMessages(aOptions)
function onMessagesAdded(aEvent, aNewElements)
{
for (let elem of aNewElements) {
let location = elem.querySelector(".webconsole-location");
if (location) {
let url = location.getAttribute("title");
// Prevent recursion with the browser console and any potential
// messages coming from head.js.
if (url.indexOf("browser/devtools/webconsole/test/head.js") != -1) {
continue;
}
}
for (let rule of rules) {
if (rule._ruleMatched) {
continue;
@ -989,6 +1158,13 @@ function waitForMessages(aOptions)
return aRule.name || aRule.text;
}
function displayErrorContext(aRule, aElement)
{
console.log("error occured during rule " + displayRule(aRule));
console.log("while checking the following message");
dumpMessageElement(aElement);
}
executeSoon(() => {
onMessagesAdded("messages-added", webconsole.outputNode.childNodes);
if (rulesMatched != rules.length) {
@ -1001,3 +1177,22 @@ function waitForMessages(aOptions)
return deferred.promise;
}
/**
* Scroll the Web Console output to the given node.
*
* @param nsIDOMNode aNode
* The node to scroll to.
*/
function scrollOutputToNode(aNode)
{
let richListBoxNode = aNode.parentNode;
while (richListBoxNode.tagName != "richlistbox") {
richListBoxNode = richListBoxNode.parentNode;
}
let boxObject = richListBoxNode.scrollBoxObject;
let nsIScrollBoxObject = boxObject.QueryInterface(Ci.nsIScrollBoxObject);
nsIScrollBoxObject.ensureElementIsVisible(aNode);
}

View File

@ -24,6 +24,11 @@ this.EXPORTED_SYMBOLS = [ "console" ];
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Services",
"resource://gre/modules/Services.jsm");
let gTimerRegistry = new Map();
/**
* String utility to ensure that strings are a specified length. Strings
* that are too long are truncated to the max length and the last char is
@ -250,7 +255,7 @@ function logProperty(aProp, aValue) {
/**
* Parse a stack trace, returning an array of stack frame objects, where
* each has file/line/call members
* each has filename/lineNumber/functionName members
*
* @param {string} aStack
* The serialized stack trace
@ -266,35 +271,42 @@ function parseStack(aStack) {
let at = line.lastIndexOf("@");
let posn = line.substring(at + 1);
trace.push({
file: posn.split(":")[0],
line: posn.split(":")[1],
call: line.substring(0, at)
filename: posn.split(":")[0],
lineNumber: posn.split(":")[1],
functionName: line.substring(0, at)
});
});
return trace;
}
/**
* parseStack() takes output from an exception from which it creates the an
* array of stack frame objects, this has the same output but using data from
* Components.stack
* Format a frame coming from Components.stack such that it can be used by the
* Browser Console, via console-api-log-event notifications.
*
* @param {string} aFrame
* The stack frame from which to begin the walk
* @param {object} aFrame
* The stack frame from which to begin the walk.
* @param {number=0} aMaxDepth
* Maximum stack trace depth. Default is 0 - no depth limit.
* @return {object[]}
* Array of { file: "...", line: NNN, call: "..." } objects
* An array of {filename, lineNumber, functionName, language} objects.
* These objects follow the same format as other console-api-log-event
* messages.
*/
function getStack(aFrame) {
function getStack(aFrame, aMaxDepth = 0) {
if (!aFrame) {
aFrame = Components.stack.caller;
}
let trace = [];
while (aFrame) {
trace.push({
file: aFrame.filename,
line: aFrame.lineNumber,
call: aFrame.name
filename: aFrame.filename,
lineNumber: aFrame.lineNumber,
functionName: aFrame.name,
language: aFrame.language,
});
if (aMaxDepth == trace.length) {
break;
}
aFrame = aFrame.caller;
}
return trace;
@ -311,13 +323,52 @@ function getStack(aFrame) {
function formatTrace(aTrace) {
let reply = "";
aTrace.forEach(function(frame) {
reply += fmt(frame.file, 20, 20, { truncate: "start" }) + " " +
fmt(frame.line, 5, 5) + " " +
fmt(frame.call, 75, 75) + "\n";
reply += fmt(frame.filename, 20, 20, { truncate: "start" }) + " " +
fmt(frame.lineNumber, 5, 5) + " " +
fmt(frame.functionName, 75, 75) + "\n";
});
return reply;
}
/**
* Create a new timer by recording the current time under the specified name.
*
* @param {string} aName
* The name of the timer.
* @param {number} [aTimestamp=Date.now()]
* Optional timestamp that tells when the timer was originally started.
* @return {object}
* The name property holds the timer name and the started property
* holds the time the timer was started. In case of error, it returns
* an object with the single property "error" that contains the key
* for retrieving the localized error message.
*/
function startTimer(aName, aTimestamp) {
let key = aName.toString();
if (!gTimerRegistry.has(key)) {
gTimerRegistry.set(key, aTimestamp || Date.now());
}
return { name: aName, started: gTimerRegistry.get(key) };
}
/**
* Stop the timer with the specified name and retrieve the elapsed time.
*
* @param {string} aName
* The name of the timer.
* @param {number} [aTimestamp=Date.now()]
* Optional timestamp that tells when the timer was originally stopped.
* @return {object}
* The name property holds the timer name and the duration property
* holds the number of milliseconds since the timer was started.
*/
function stopTimer(aName, aTimestamp) {
let key = aName.toString();
let duration = (aTimestamp || Date.now()) - gTimerRegistry.get(key);
gTimerRegistry.delete(key);
return { name: aName, duration: duration };
}
/**
* Create a function which will output a concise level of output when used
* as a logging function
@ -332,6 +383,8 @@ function formatTrace(aTrace) {
function createDumper(aLevel) {
return function() {
let args = Array.prototype.slice.call(arguments, 0);
let frame = getStack(Components.stack.caller, 1)[0];
sendConsoleAPIMessage(aLevel, frame, args);
let data = args.map(function(arg) {
return stringify(arg);
});
@ -354,12 +407,72 @@ function createMultiLineDumper(aLevel) {
return function() {
dump("console." + aLevel + ": \n");
let args = Array.prototype.slice.call(arguments, 0);
let frame = getStack(Components.stack.caller, 1)[0];
sendConsoleAPIMessage(aLevel, frame, args);
args.forEach(function(arg) {
dump(log(arg));
});
};
}
/**
* Send a Console API message. This function will send a console-api-log-event
* notification through the nsIObserverService.
*
* @param {string} aLevel
* Message severity level. This is usually the name of the console method
* that was called.
* @param {object} aFrame
* The youngest stack frame coming from Components.stack, as formatted by
* getStack().
* @param {array} aArgs
* The arguments given to the console method.
* @param {object} aOptions
* Object properties depend on the console method that was invoked:
* - timer: for time() and timeEnd(). Holds the timer information.
* - groupName: for group(), groupCollapsed() and groupEnd().
* - stacktrace: for trace(). Holds the array of stack frames as given by
* getStack().
*/
function sendConsoleAPIMessage(aLevel, aFrame, aArgs, aOptions = {})
{
let consoleEvent = {
ID: aFrame.filename,
level: aLevel,
filename: aFrame.filename,
lineNumber: aFrame.lineNumber,
functionName: aFrame.functionName,
timeStamp: Date.now(),
arguments: aArgs,
};
consoleEvent.wrappedJSObject = consoleEvent;
switch (aLevel) {
case "trace":
consoleEvent.stacktrace = aOptions.stacktrace;
break;
case "time":
case "timeEnd":
consoleEvent.timer = aOptions.timer;
break;
case "group":
case "groupCollapsed":
case "groupEnd":
try {
consoleEvent.groupName = Array.prototype.join.call(aArgs, " ");
}
catch (ex) {
Cu.reportError(ex);
Cu.reportError(ex.stack);
return;
}
break;
}
Services.obs.notifyObservers(consoleEvent, "console-api-log-event", null);
}
/**
* This creates a console object that somewhat replicates Firebug's console
* object. It currently writes to dump(), but should write to the web
@ -373,13 +486,32 @@ this.console = {
error: createMultiLineDumper("error"),
trace: function Console_trace() {
let args = Array.prototype.slice.call(arguments, 0);
let trace = getStack(Components.stack.caller);
dump(formatTrace(trace) + "\n");
sendConsoleAPIMessage("trace", trace[0], args,
{ stacktrace: trace });
dump("console.trace:\n" + formatTrace(trace) + "\n");
},
clear: function Console_clear() {},
dir: createMultiLineDumper("dir"),
dirxml: createMultiLineDumper("dirxml"),
group: createDumper("group"),
groupEnd: createDumper("groupEnd")
groupEnd: createDumper("groupEnd"),
time: function Console_time() {
let args = Array.prototype.slice.call(arguments, 0);
let frame = getStack(Components.stack.caller, 1)[0];
let timer = startTimer(args[0]);
sendConsoleAPIMessage("time", frame, args, { timer: timer });
dump("console.time: '" + timer.name + "' @ " + (new Date()) + "\n");
},
timeEnd: function Console_timeEnd() {
let args = Array.prototype.slice.call(arguments, 0);
let frame = getStack(Components.stack.caller, 1)[0];
let timer = stopTimer(args[0]);
sendConsoleAPIMessage("timeEnd", frame, args, { timer: timer });
dump("console.timeEnd: '" + timer.name + "' " + timer.duration + "ms\n");
},
};

View File

@ -245,7 +245,7 @@ DebuggerClient.requester = function DC_requester(aPacketSkeleton, { telemetry,
return function (...args) {
let histogram, startTime;
if (telemetry) {
let transportType = this._transport instanceof LocalDebuggerTransport
let transportType = this._transport.onOutputStreamReady === undefined
? "LOCAL_"
: "REMOTE_";
let histogramId = "DEVTOOLS_DEBUGGER_RDP_"
@ -253,7 +253,6 @@ DebuggerClient.requester = function DC_requester(aPacketSkeleton, { telemetry,
histogram = Services.telemetry.getHistogramById(histogramId);
startTime = +new Date;
}
let outgoingPacket = {
to: aPacketSkeleton.to || this.actor
};
@ -413,7 +412,10 @@ DebuggerClient.prototype = {
*/
attachTab: function DC_attachTab(aTabActor, aOnResponse) {
let self = this;
let packet = { to: aTabActor, type: "attach" };
let packet = {
to: aTabActor,
type: "attach"
};
this.request(packet, function(aResponse) {
let tabClient;
if (!aResponse.error) {
@ -463,10 +465,17 @@ DebuggerClient.prototype = {
* @param function aOnResponse
* Called with the response packet and a ThreadClient
* (which will be undefined on error).
* @param object aOptions
* Configuration options.
* - useSourceMaps: whether to use source maps or not.
*/
attachThread: function DC_attachThread(aThreadActor, aOnResponse) {
attachThread: function DC_attachThread(aThreadActor, aOnResponse, aOptions={}) {
let self = this;
let packet = { to: aThreadActor, type: "attach" };
let packet = {
to: aThreadActor,
type: "attach",
options: aOptions
};
this.request(packet, function(aResponse) {
if (!aResponse.error) {
var threadClient = new ThreadClient(self, aThreadActor);
@ -523,7 +532,7 @@ DebuggerClient.prototype = {
*/
_sendRequests: function DC_sendRequests() {
let self = this;
this._pendingRequests = this._pendingRequests.filter(function(request) {
this._pendingRequests = this._pendingRequests.filter(function (request) {
if (request.to in self._activeRequests) {
return true;
}
@ -550,7 +559,7 @@ DebuggerClient.prototype = {
? aPacket
: this.compat.onPacket(aPacket);
resolve(packet).then(function (aPacket) {
resolve(packet).then((aPacket) => {
if (!this._connected) {
// Hello packet.
this._connected = true;
@ -560,55 +569,53 @@ DebuggerClient.prototype = {
return;
}
try {
if (!aPacket.from) {
let msg = "Server did not specify an actor, dropping packet: " +
JSON.stringify(aPacket);
Cu.reportError(msg);
dumpn(msg);
return;
}
if (!aPacket.from) {
let msg = "Server did not specify an actor, dropping packet: " +
JSON.stringify(aPacket);
Cu.reportError(msg);
dumpn(msg);
return;
}
let onResponse;
// Don't count unsolicited notifications or pauses as responses.
if (aPacket.from in this._activeRequests &&
!(aPacket.type in UnsolicitedNotifications) &&
!(aPacket.type == ThreadStateTypes.paused &&
aPacket.why.type in UnsolicitedPauses)) {
onResponse = this._activeRequests[aPacket.from].onResponse;
delete this._activeRequests[aPacket.from];
}
let onResponse;
// Don't count unsolicited notifications or pauses as responses.
if (aPacket.from in this._activeRequests &&
!(aPacket.type in UnsolicitedNotifications) &&
!(aPacket.type == ThreadStateTypes.paused &&
aPacket.why.type in UnsolicitedPauses)) {
onResponse = this._activeRequests[aPacket.from].onResponse;
delete this._activeRequests[aPacket.from];
}
// Packets that indicate thread state changes get special treatment.
if (aPacket.type in ThreadStateTypes &&
aPacket.from in this._threadClients) {
this._threadClients[aPacket.from]._onThreadState(aPacket);
}
// On navigation the server resumes, so the client must resume as well.
// We achieve that by generating a fake resumption packet that triggers
// the client's thread state change listeners.
if (this.activeThread &&
aPacket.type == UnsolicitedNotifications.tabNavigated &&
aPacket.from in this._tabClients) {
let resumption = { from: this.activeThread._actor, type: "resumed" };
this.activeThread._onThreadState(resumption);
}
// Only try to notify listeners on events, not responses to requests
// that lack a packet type.
if (aPacket.type) {
this.notify(aPacket.type, aPacket);
}
// Packets that indicate thread state changes get special treatment.
if (aPacket.type in ThreadStateTypes &&
aPacket.from in this._threadClients) {
this._threadClients[aPacket.from]._onThreadState(aPacket);
}
// On navigation the server resumes, so the client must resume as well.
// We achieve that by generating a fake resumption packet that triggers
// the client's thread state change listeners.
if (this.activeThread &&
aPacket.type == UnsolicitedNotifications.tabNavigated &&
aPacket.from in this._tabClients) {
let resumption = { from: this.activeThread._actor, type: "resumed" };
this.activeThread._onThreadState(resumption);
}
// Only try to notify listeners on events, not responses to requests
// that lack a packet type.
if (aPacket.type) {
this.notify(aPacket.type, aPacket);
}
if (onResponse) {
onResponse(aPacket);
}
} catch(ex) {
dumpn("Error handling response: " + ex + " - stack:\n" + ex.stack);
Cu.reportError(ex + "\n" + ex.stack);
if (onResponse) {
onResponse(aPacket);
}
this._sendRequests();
}.bind(this));
}, function (ex) {
dumpn("Error handling response: " + ex + " - stack:\n" + ex.stack);
Cu.reportError(ex.message + "\n" + ex.stack);
});
},
/**
@ -831,6 +838,7 @@ function TabClient(aClient, aActor) {
TabClient.prototype = {
get actor() { return this._actor },
get _transport() { return this._client._transport; },
/**
* Detach the client from the tab actor.
@ -885,6 +893,7 @@ ThreadClient.prototype = {
get actor() { return this._actor; },
get compat() { return this._client.compat; },
get _transport() { return this._client._transport; },
_assertPaused: function TC_assertPaused(aCommand) {
if (!this.paused) {
@ -1410,6 +1419,7 @@ function GripClient(aClient, aGrip)
GripClient.prototype = {
get actor() { return this._grip.actor },
get _transport() { return this._client._transport; },
valid: true,
@ -1500,6 +1510,7 @@ LongStringClient.prototype = {
get actor() { return this._grip.actor; },
get length() { return this._grip.length; },
get initial() { return this._grip.initial; },
get _transport() { return this._client._transport; },
valid: true,
@ -1536,6 +1547,8 @@ function SourceClient(aClient, aForm) {
}
SourceClient.prototype = {
get _transport() { return this._client._transport; },
/**
* Get a long string grip for this SourceClient's source.
*/
@ -1593,6 +1606,7 @@ BreakpointClient.prototype = {
_actor: null,
get actor() { return this._actor; },
get _transport() { return this._client._transport; },
/**
* Remove the breakpoint from the server.

View File

@ -30,7 +30,6 @@ function ThreadActor(aHooks, aGlobal)
this._frameActors = [];
this._environmentActors = [];
this._hooks = aHooks;
this._sources = {};
this.global = aGlobal;
// A cache of prototype chains for objects that have received a
@ -46,6 +45,11 @@ function ThreadActor(aHooks, aGlobal)
this.findGlobals = this.globalManager.findGlobals.bind(this);
this.onNewGlobal = this.globalManager.onNewGlobal.bind(this);
this.onNewSource = this.onNewSource.bind(this);
this._options = {
useSourceMaps: false
};
}
/**
@ -73,13 +77,21 @@ ThreadActor.prototype = {
return this._threadLifetimePool;
},
get sources() {
if (!this._sources) {
this._sources = new ThreadSources(this, this._options.useSourceMaps,
this._allowSource, this.onNewSource);
}
return this._sources;
},
clearDebuggees: function TA_clearDebuggees() {
if (this.dbg) {
this.dbg.removeAllDebuggees();
}
this.conn.removeActorPool(this._threadLifetimePool || undefined);
this._threadLifetimePool = null;
this._sources = {};
this._sources = null;
},
/**
@ -202,6 +214,8 @@ ThreadActor.prototype = {
this._state = "attached";
update(this._options, aRequest.options || {});
if (!this.dbg) {
this._initDebugger();
}
@ -226,15 +240,17 @@ ThreadActor.prototype = {
// We already sent a response to this request, don't send one
// now.
return null;
} catch(e) {
Cu.reportError(e);
} catch (e) {
reportError(e);
return { error: "notAttached", message: e.toString() };
}
},
onDetach: function TA_onDetach(aRequest) {
this.disconnect();
return { type: "detached" };
return {
type: "detached"
};
},
/**
@ -245,15 +261,19 @@ ThreadActor.prototype = {
* The newest debuggee frame in the stack.
* @param object aReason
* An object with a 'type' property containing the reason for the pause.
* @param function onPacket
* Hook to modify the packet before it is sent. Feel free to return a
* promise.
*/
_pauseAndRespond: function TA__pauseAndRespond(aFrame, aReason) {
_pauseAndRespond: function TA__pauseAndRespond(aFrame, aReason,
onPacket=function (k) k) {
try {
let packet = this._paused(aFrame);
if (!packet) {
return undefined;
}
packet.why = aReason;
this.conn.send(packet);
resolve(onPacket(packet)).then(this.conn.send.bind(this.conn));
return this._nest();
} catch(e) {
let msg = "Got an exception during TA__pauseAndRespond: " + e +
@ -268,6 +288,14 @@ ThreadActor.prototype = {
* Handle a protocol request to resume execution of the debuggee.
*/
onResume: function TA_onResume(aRequest) {
if (this._state !== "paused") {
return {
error: "wrongState",
message: "Can't resume when debuggee isn't paused. Current state is '"
+ this._state + "'"
};
}
// In case of multiple nested event loops (due to multiple debuggers open in
// different tabs or multiple debugger clients connected to the same tab)
// only allow resumption in a LIFO order.
@ -439,14 +467,23 @@ ThreadActor.prototype = {
// Return request.count frames, or all remaining
// frames if count is not defined.
let frames = [];
for (; frame && (!count || i < (start + count)); i++) {
let promises = [];
for (; frame && (!count || i < (start + count)); i++, frame=frame.older) {
let form = this._createFrameActor(frame).form();
form.depth = i;
frames.push(form);
frame = frame.older;
let promise = this.sources.getOriginalLocation(form.where.url,
form.where.line)
.then(function (aOrigLocation) {
form.where = aOrigLocation;
});
promises.push(promise);
}
return { frames: frames };
return resolveAll(promises).then(function () {
return { frames: frames };
});
},
onReleaseMany: function TA_onReleaseMany(aRequest) {
@ -479,25 +516,60 @@ ThreadActor.prototype = {
message: "Breakpoints can only be set while the debuggee is paused."};
}
let location = aRequest.location;
let line = location.line;
if (this.dbg.findScripts({ url: location.url }).length == 0 || line < 0) {
return { error: "noScript" };
}
// XXX: `originalColumn` is never used. See bug 827639.
let { url: originalSource,
line: originalLine,
column: originalColumn } = aRequest.location;
// Add the breakpoint to the store for later reuse, in case it belongs to a
// script that hasn't appeared yet.
if (!this._breakpointStore[location.url]) {
this._breakpointStore[location.url] = [];
}
let scriptBreakpoints = this._breakpointStore[location.url];
scriptBreakpoints[line] = {
url: location.url,
line: line,
column: location.column
};
let locationPromise = this.sources.getGeneratedLocation(originalSource,
originalLine)
return locationPromise.then((aLocation) => {
let line = aLocation.line;
if (this.dbg.findScripts({ url: aLocation.url }).length == 0 ||
line < 0 ||
line == null) {
return { error: "noScript" };
}
return this._setBreakpoint(location);
// Add the breakpoint to the store for later reuse, in case it belongs to a
// script that hasn't appeared yet.
if (!this._breakpointStore[aLocation.url]) {
this._breakpointStore[aLocation.url] = [];
}
let scriptBreakpoints = this._breakpointStore[aLocation.url];
scriptBreakpoints[line] = {
url: aLocation.url,
line: line,
column: aLocation.column
};
let response = this._setBreakpoint(aLocation);
// If the original location of our generated location is different from
// the original location we attempted to set the breakpoint on, we will
// need to know so that we can set actualLocation on the response.
let originalLocation = this.sources.getOriginalLocation(aLocation.url,
aLocation.line);
return resolveAll([response, originalLocation])
.then(([aResponse, {url, line}]) => {
if (aResponse.actualLocation) {
let actualOrigLocation = this.sources.getOriginalLocation(
aResponse.actualLocation.url, aResponse.actualLocation.line);
return actualOrigLocation.then(function ({ url, line }) {
if (url !== originalSource || line !== originalLine) {
aResponse.actualLocation = { url: url, line: line };
}
return aResponse;
});
}
if (url !== originalSource || line !== originalLine) {
aResponse.actualLocation = { url: url, line: line };
}
return aResponse;
});
});
},
/**
@ -608,17 +680,16 @@ ThreadActor.prototype = {
* Get the script and source lists from the debugger.
*/
_discoverScriptsAndSources: function TA__discoverScriptsAndSources() {
for (let s of this.dbg.findScripts()) {
this._addScript(s);
}
return resolveAll([this._addScript(s)
for (s of this.dbg.findScripts())]);
},
onSources: function TA_onSources(aRequest) {
this._discoverScriptsAndSources();
let urls = Object.getOwnPropertyNames(this._sources);
return {
sources: [this._getSource(url).form() for (url of urls)]
};
return this._discoverScriptsAndSources().then(() => {
return {
sources: [s.form() for (s of this.sources.iter())]
};
});
},
/**
@ -655,8 +726,8 @@ ThreadActor.prototype = {
// We already sent a response to this request, don't send one
// now.
return null;
} catch(e) {
Cu.reportError(e);
} catch (e) {
reportError(e);
return { error: "notInterrupted", message: e.toString() };
}
},
@ -1029,26 +1100,6 @@ ThreadActor.prototype = {
return aString.length >= DebuggerServer.LONG_STRING_LENGTH;
},
/**
* Create a source grip for the given script.
*/
sourceGrip: function TA_sourceGrip(aScript) {
// TODO: Once we have Debugger.Source, this should be replaced with a
// weakmap mapping Debugger.Source instances to SourceActor instances.
if (!this.threadLifetimePool.sourceActors) {
this.threadLifetimePool.sourceActors = {};
}
if (this.threadLifetimePool.sourceActors[aScript.url]) {
return this.threadLifetimePool.sourceActors[aScript.url].grip();
}
let actor = new SourceActor(aScript.url, this);
this.threadLifetimePool.addActor(actor);
this.threadLifetimePool.sourceActors[aScript.url] = actor;
return actor.form();
},
// JS Debugger API hooks.
/**
@ -1114,6 +1165,14 @@ ThreadActor.prototype = {
this._addScript(aScript);
},
onNewSource: function TA_onNewSource(aSource) {
this.conn.send({
from: this.actorID,
type: "newSource",
source: aSource.form()
});
},
/**
* Check if scripts from the provided source URL are allowed to be stored in
* the cache.
@ -1122,7 +1181,7 @@ ThreadActor.prototype = {
* The url of the script's source that will be stored.
* @returns true, if the script can be added, false otherwise.
*/
_allowSource: function TA__allowScript(aSourceUrl) {
_allowSource: function TA__allowSource(aSourceUrl) {
// Ignore anything we don't have a URL for (eval scripts, for example).
if (!aSourceUrl)
return false;
@ -1146,28 +1205,30 @@ ThreadActor.prototype = {
*/
_addScript: function TA__addScript(aScript) {
if (!this._allowSource(aScript.url)) {
return false;
return resolve(false);
}
// TODO bug 637572: we should be dealing with sources directly, not
// inferring them through scripts.
this._addSource(aScript.url);
return this.sources.sourcesForScript(aScript).then(() => {
// Set any stored breakpoints.
let existing = this._breakpointStore[aScript.url];
if (existing) {
let endLine = aScript.startLine + aScript.lineCount - 1;
// Iterate over the lines backwards, so that sliding breakpoints don't
// affect the loop.
for (let line = existing.length - 1; line >= 0; line--) {
let bp = existing[line];
// Limit search to the line numbers contained in the new script.
if (bp && line >= aScript.startLine && line <= endLine) {
this._setBreakpoint(bp);
// Set any stored breakpoints.
let existing = this._breakpointStore[aScript.url];
if (existing) {
let endLine = aScript.startLine + aScript.lineCount - 1;
// Iterate over the lines backwards, so that sliding breakpoints don't
// affect the loop.
for (let line = existing.length - 1; line >= 0; line--) {
let bp = existing[line];
// Limit search to the line numbers contained in the new script.
if (bp && line >= aScript.startLine && line <= endLine) {
this._setBreakpoint(bp);
}
}
}
}
return true;
return true;
});
},
/**
@ -1215,48 +1276,6 @@ ThreadActor.prototype = {
return retval;
},
/**
* Add a source to the current set of sources.
*
* Right now this takes an url, but in the future it should
* take a Debugger.Source.
*
* @param string the source URL.
* @returns a SourceActor representing the source.
*/
_addSource: function TA__addSource(aURL) {
if (!this._allowSource(aURL)) {
return false;
}
if (aURL in this._sources) {
return true;
}
let actor = new SourceActor(aURL, this);
this.threadLifetimePool.addActor(actor);
this._sources[aURL] = actor;
this.conn.send({
from: this.actorID,
type: "newSource",
source: actor.form()
});
return true;
},
/**
* Get the source actor for the given URL.
*/
_getSource: function TA__getSource(aUrl) {
let source = this._sources[aUrl];
if (!source) {
throw new Error("No source for '" + aUrl + "'");
}
return source;
},
};
ThreadActor.prototype.requestTypes = {
@ -1340,7 +1359,7 @@ PauseScopedActor.prototype = {
error: "wrongState",
message: this.constructor.name +
" actors can only be accessed while the thread is paused."
}
};
}
};
@ -1362,7 +1381,8 @@ SourceActor.prototype = {
constructor: SourceActor,
actorPrefix: "source",
get threadActor() { return this._threadActor; },
get threadActor() this._threadActor,
get url() this._url,
form: function SA_form() {
return {
@ -1382,10 +1402,9 @@ SourceActor.prototype = {
* Handler for the "source" packet.
*/
onSource: function SA_onSource(aRequest) {
return this
._loadSource()
return fetch(this._url)
.then(function(aSource) {
return this._threadActor.createValueGrip(
return this.threadActor.createValueGrip(
aSource, this.threadActor.threadLifetimePool);
}.bind(this))
.then(function (aSourceGrip) {
@ -1394,121 +1413,17 @@ SourceActor.prototype = {
source: aSourceGrip
};
}.bind(this), function (aError) {
let msg = "Got an exception during SA_onSource: " + aError +
"\n" + aError.stack;
Cu.reportError(msg);
dumpn(msg);
return {
"from": this.actorID,
"error": "loadSourceError",
"message": "Could not load the source for " + this._url + "."
};
}.bind(this));
},
/**
* Convert a given string, encoded in a given character set, to unicode.
* @param string aString
* A string.
* @param string aCharset
* A character set.
* @return string
* A unicode string.
*/
_convertToUnicode: function SS__convertToUnicode(aString, aCharset) {
// Decoding primitives.
let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
.createInstance(Ci.nsIScriptableUnicodeConverter);
try {
converter.charset = aCharset || "UTF-8";
return converter.ConvertToUnicode(aString);
} catch(e) {
return aString;
}
},
/**
* Performs a request to load the desired URL and returns a promise.
*
* @param aURL String
* The URL we will request.
* @returns Promise
*
* XXX: It may be better to use nsITraceableChannel to get to the sources
* without relying on caching when we can (not for eval, etc.):
* http://www.softwareishard.com/blog/firebug/nsitraceablechannel-intercept-http-traffic/
*/
_loadSource: function SA__loadSource() {
let deferred = defer();
let scheme;
let url = this._url.split(" -> ").pop();
try {
scheme = Services.io.extractScheme(url);
} catch (e) {
// In the xpcshell tests, the script url is the absolute path of the test
// file, which will make a malformed URI error be thrown. Add the file
// scheme prefix ourselves.
url = "file://" + url;
scheme = Services.io.extractScheme(url);
}
switch (scheme) {
case "file":
case "chrome":
case "resource":
try {
NetUtil.asyncFetch(url, function onFetch(aStream, aStatus) {
if (!Components.isSuccessCode(aStatus)) {
deferred.reject(new Error("Request failed"));
return;
}
let source = NetUtil.readInputStreamToString(aStream, aStream.available());
deferred.resolve(this._convertToUnicode(source));
aStream.close();
}.bind(this));
} catch (ex) {
deferred.reject(new Error("Request failed"));
}
break;
default:
let channel;
try {
channel = Services.io.newChannel(url, null, null);
} catch (e if e.name == "NS_ERROR_UNKNOWN_PROTOCOL") {
// On Windows xpcshell tests, c:/foo/bar can pass as a valid URL, but
// newChannel won't be able to handle it.
url = "file:///" + url;
channel = Services.io.newChannel(url, null, null);
}
let chunks = [];
let streamListener = {
onStartRequest: function(aRequest, aContext, aStatusCode) {
if (!Components.isSuccessCode(aStatusCode)) {
deferred.reject("Request failed");
}
},
onDataAvailable: function(aRequest, aContext, aStream, aOffset, aCount) {
chunks.push(NetUtil.readInputStreamToString(aStream, aCount));
},
onStopRequest: function(aRequest, aContext, aStatusCode) {
if (!Components.isSuccessCode(aStatusCode)) {
deferred.reject("Request failed");
return;
}
deferred.resolve(this._convertToUnicode(chunks.join(""),
channel.contentCharset));
}.bind(this)
};
channel.loadFlags = channel.LOAD_FROM_CACHE;
channel.asyncOpen(streamListener, null);
break;
}
return deferred.promise;
}
};
SourceActor.prototype.requestTypes = {
@ -2095,7 +2010,14 @@ BreakpointActor.prototype = {
hit: function BA_hit(aFrame) {
// TODO: add the rest of the breakpoints on that line (bug 676602).
let reason = { type: "breakpoint", actors: [ this.actorID ] };
return this.threadActor._pauseAndRespond(aFrame, reason);
return this.threadActor._pauseAndRespond(aFrame, reason, (aPacket) => {
let { url, line } = aPacket.frame.where;
return this.threadActor.sources.getOriginalLocation(url, line)
.then(function (aOrigPosition) {
aPacket.frame.where = aOrigPosition;
return aPacket;
});
});
},
/**
@ -2417,6 +2339,205 @@ update(ChromeDebuggerActor.prototype, {
});
/**
* Manages the sources for a thread. Handles source maps, locations in the
* sources, etc for ThreadActors.
*/
function ThreadSources(aThreadActor, aUseSourceMaps,
aAllowPredicate, aOnNewSource) {
this._thread = aThreadActor;
this._useSourceMaps = aUseSourceMaps;
this._allow = aAllowPredicate;
this._onNewSource = aOnNewSource;
// source map URL --> promise of SourceMapConsumer
this._sourceMaps = Object.create(null);
// generated source url --> promise of SourceMapConsumer
this._sourceMapsByGeneratedSource = Object.create(null);
// original source url --> promise of SourceMapConsumer
this._sourceMapsByOriginalSource = Object.create(null);
// source url --> SourceActor
this._sourceActors = Object.create(null);
// original url --> generated url
this._generatedUrlsByOriginalUrl = Object.create(null);
}
ThreadSources.prototype = {
/**
* Add a source to the current set of sources.
*
* Right now this takes a URL, but in the future it should
* take a Debugger.Source. See bug 637572.
*
* @param string the source URL.
* @returns a SourceActor representing the source or null.
*/
source: function TS_source(aURL) {
if (!this._allow(aURL)) {
return null;
}
if (aURL in this._sourceActors) {
return this._sourceActors[aURL];
}
let actor = new SourceActor(aURL, this._thread);
this._thread.threadLifetimePool.addActor(actor);
this._sourceActors[aURL] = actor;
try {
this._onNewSource(actor);
} catch (e) {
reportError(e);
}
return actor;
},
/**
* Add all of the sources associated with the given script.
*/
sourcesForScript: function TS_sourcesForScript(aScript) {
if (!this._useSourceMaps || !aScript.sourceMapURL) {
return resolve([this.source(aScript.url)].filter(isNotNull));
}
return this.sourceMap(aScript)
.then((aSourceMap) => {
return [
this.source(s) for (s of aSourceMap.sources)
];
}, (e) => {
reportError(e);
delete this._sourceMaps[this._normalize(aScript.sourceMapURL, aScript.url)];
delete this._sourceMapsByGeneratedSource[aScript.url];
return [this.source(aScript.url)];
})
.then(function (aSources) {
return aSources.filter(isNotNull);
});
},
/**
* Add the source map for the given script.
*/
sourceMap: function TS_sourceMap(aScript) {
if (aScript.url in this._sourceMapsByGeneratedSource) {
return this._sourceMapsByGeneratedSource[aScript.url];
}
dbg_assert(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;
this._sourceMapsByOriginalSource[s] = resolve(aSourceMap);
}
return aSourceMap;
});
this._sourceMapsByGeneratedSource[aScript.url] = map;
return map;
},
/**
* Fetch the source map located at the given url.
*/
_fetchSourceMap: function TS__fetchSourceMap(aAbsSourceMapURL) {
if (aAbsSourceMapURL in this._sourceMaps) {
return this._sourceMaps[aAbsSourceMapURL];
} else {
let promise = fetch(aAbsSourceMapURL).then((rawSourceMap) => {
let map = new SourceMapConsumer(rawSourceMap);
let base = aAbsSourceMapURL.replace(/\/[^\/]+$/, '/');
if (base.indexOf("data:") !== 0) {
map.sourceRoot = map.sourceRoot
? this._normalize(map.sourceRoot, base)
: base;
}
return map;
});
this._sourceMaps[aAbsSourceMapURL] = promise;
return promise;
}
},
/**
* Returns a promise for the location in the original source if the source is
* source mapped, otherwise a promise of the same location.
*
* TODO bug 637572: take/return a column
*/
getOriginalLocation: function TS_getOriginalLocation(aSourceUrl, aLine) {
if (aSourceUrl in this._sourceMapsByGeneratedSource) {
return this._sourceMapsByGeneratedSource[aSourceUrl]
.then(function (aSourceMap) {
let { source, line } = aSourceMap.originalPositionFor({
source: aSourceUrl,
line: aLine,
column: Infinity
});
return {
url: source,
line: line
};
});
}
// No source map
return resolve({
url: aSourceUrl,
line: aLine
});
},
/**
* Returns a promise of the location in the generated source corresponding to
* the original source and line given.
*
* TODO bug 637572: take/return a column
*/
getGeneratedLocation: function TS_getGeneratedLocation(aSourceUrl, aLine) {
if (aSourceUrl in this._sourceMapsByOriginalSource) {
return this._sourceMapsByOriginalSource[aSourceUrl]
.then((aSourceMap) => {
let { line } = aSourceMap.generatedPositionFor({
source: aSourceUrl,
line: aLine,
column: Infinity
});
return {
url: this._generatedUrlsByOriginalUrl[aSourceUrl],
line: line
};
});
}
// No source map
return resolve({
url: aSourceUrl,
line: aLine
});
},
/**
* 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];
}
}
};
// Utility functions.
/**
@ -2437,3 +2558,126 @@ function update(aTarget, aNewAttrs) {
}
}
}
/**
* Returns true if its argument is not null.
*/
function isNotNull(aThing) {
return aThing !== null;
}
/**
* Performs a request to load the desired URL and returns a promise.
*
* @param aURL String
* The URL we will request.
* @returns Promise
*
* XXX: It may be better to use nsITraceableChannel to get to the sources
* without relying on caching when we can (not for eval, etc.):
* http://www.softwareishard.com/blog/firebug/nsitraceablechannel-intercept-http-traffic/
*/
function fetch(aURL) {
let deferred = defer();
let scheme;
let url = aURL.split(" -> ").pop();
let charset;
try {
scheme = Services.io.extractScheme(url);
} catch (e) {
// In the xpcshell tests, the script url is the absolute path of the test
// file, which will make a malformed URI error be thrown. Add the file
// scheme prefix ourselves.
url = "file://" + url;
scheme = Services.io.extractScheme(url);
}
switch (scheme) {
case "file":
case "chrome":
case "resource":
try {
NetUtil.asyncFetch(url, function onFetch(aStream, aStatus) {
if (!Components.isSuccessCode(aStatus)) {
deferred.reject("Request failed: " + url);
return;
}
let source = NetUtil.readInputStreamToString(aStream, aStream.available());
deferred.resolve(source);
aStream.close();
});
} catch (ex) {
deferred.reject("Request failed: " + url);
}
break;
default:
let channel;
try {
channel = Services.io.newChannel(url, null, null);
} catch (e if e.name == "NS_ERROR_UNKNOWN_PROTOCOL") {
// On Windows xpcshell tests, c:/foo/bar can pass as a valid URL, but
// newChannel won't be able to handle it.
url = "file:///" + url;
channel = Services.io.newChannel(url, null, null);
}
let chunks = [];
let streamListener = {
onStartRequest: function(aRequest, aContext, aStatusCode) {
if (!Components.isSuccessCode(aStatusCode)) {
deferred.reject("Request failed: " + url);
}
},
onDataAvailable: function(aRequest, aContext, aStream, aOffset, aCount) {
chunks.push(NetUtil.readInputStreamToString(aStream, aCount));
},
onStopRequest: function(aRequest, aContext, aStatusCode) {
if (!Components.isSuccessCode(aStatusCode)) {
deferred.reject("Request failed: " + url);
return;
}
charset = channel.contentCharset;
deferred.resolve(chunks.join(""));
}
};
channel.loadFlags = channel.LOAD_FROM_CACHE;
channel.asyncOpen(streamListener, null);
break;
}
return deferred.promise.then(function (source) {
return convertToUnicode(source, charset);
});
}
/**
* Convert a given string, encoded in a given character set, to unicode.
*
* @param string aString
* A string.
* @param string aCharset
* A character set.
*/
function convertToUnicode(aString, aCharset=null) {
// Decoding primitives.
let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
.createInstance(Ci.nsIScriptableUnicodeConverter);
try {
converter.charset = aCharset || "UTF-8";
return converter.ConvertToUnicode(aString);
} catch(e) {
return aString;
}
}
/**
* Report the given error in the error console and to stdout.
*/
function reportError(aError) {
Cu.reportError(aError);
dumpn(aError.message + ":\n" + aError.stack);
}

View File

@ -26,6 +26,12 @@ addDebuggerToGlobal(this);
Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js");
const { defer, resolve, reject } = Promise;
let promisedArray = Promise.promised(Array);
function resolveAll(aPromises) {
return promisedArray.apply(null, aPromises);
};
Cu.import("resource://gre/modules/devtools/SourceMap.jsm");
function dumpn(str) {
if (wantLogging) {
@ -600,6 +606,17 @@ DebuggerServerConnection.prototype = {
return null;
},
_unknownError: function DSC__unknownError(aPrefix, aError) {
let errorString = safeErrorString(aError);
errorString += "\n" + aError.stack;
Cu.reportError(errorString);
dumpn(errorString);
return {
error: "unknownError",
message: (aPrefix + "': " + errorString)
};
},
// Transport hooks.
/**
@ -622,12 +639,9 @@ DebuggerServerConnection.prototype = {
try {
instance = new actor();
} catch (e) {
Cu.reportError(e);
this.transport.send({
error: "unknownError",
message: ("error occurred while creating actor '" + actor.name +
"': " + safeErrorString(e))
});
this.transport.send(this._unknownError(
"Error occurred while creating actor '" + actor.name,
e));
}
instance.parentID = actor.parentID;
// We want the newly-constructed actor to completely replace the factory
@ -639,16 +653,14 @@ DebuggerServerConnection.prototype = {
}
var ret = null;
// Dispatch the request to the actor.
if (actor.requestTypes && actor.requestTypes[aPacket.type]) {
try {
ret = actor.requestTypes[aPacket.type].bind(actor)(aPacket);
} catch(e) {
Cu.reportError(e);
ret = { error: "unknownError",
message: ("error occurred while processing '" + aPacket.type +
"' request: " + safeErrorString(e)) };
this.transport.send(this._unknownError(
"error occurred while processing '" + aPacket.type,
e));
}
} else {
ret = { error: "unrecognizedPacketType",
@ -663,12 +675,19 @@ DebuggerServerConnection.prototype = {
return;
}
resolve(ret).then(function(returnPacket) {
if (!returnPacket.from) {
returnPacket.from = aPacket.to;
}
this.transport.send(returnPacket);
}.bind(this));
resolve(ret)
.then(null, (e) => {
return this._unknownError(
"error occurred while processing '" + aPacket.type,
e);
})
.then(function (aResponse) {
if (!aResponse.from) {
aResponse.from = aPacket.to;
}
return aResponse;
})
.then(this.transport.send.bind(this.transport));
},
/**

View File

@ -111,7 +111,7 @@ function getTestGlobalContext(aClient, aName, aCallback) {
function attachTestGlobalClient(aClient, aName, aCallback) {
getTestGlobalContext(aClient, aName, function(aContext) {
aClient.attachThread(aContext.actor, aCallback);
aClient.attachThread(aContext.actor, aCallback, { useSourceMaps: true });
});
}
@ -147,7 +147,7 @@ function attachTestTabAndResume(aClient, aName, aCallback) {
aThreadClient.resume(function (aResponse) {
aCallback(aResponse, aTabClient, aThreadClient);
});
});
}, { useSourceMaps: true });
});
}
@ -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

@ -61,8 +61,8 @@ function test_child_breakpoint()
gDebuggee.eval("var line0 = Error().lineNumber;\n" +
"function foo() {\n" + // line0 + 1
" this.a = 1;\n" + // line0 + 2
" this.b = 2;\n" + // line0 + 3
" this.a = 1;\n" + // line0 + 2
" this.b = 2;\n" + // line0 + 3
"}\n" + // line0 + 4
"debugger;\n" + // line0 + 5
"foo();\n"); // line0 + 6

View File

@ -38,6 +38,7 @@ function run_test()
function test_attach(aContext)
{
gClient.request({ to: aContext.actor, type: "attach" }, function(aResponse) {
do_check_true(!aResponse.error);
do_check_eq(aResponse.type, "paused");
// Resume the thread and test the debugger statement.

View File

@ -47,6 +47,6 @@ function test_listing_zero_sources()
"Should only send one sources request at most, even though we"
+ " might have had to send one to determine feature support.");
finishClient(gClient);
finishClient(gClient);
});
}

View File

@ -0,0 +1,52 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Check getSources functionality when there are lots of sources.
*/
var gDebuggee;
var gClient;
var gThreadClient;
function run_test()
{
initTestDebuggerServer();
gDebuggee = addTestGlobal("test-sources");
gClient = new DebuggerClient(DebuggerServer.connectPipe());
gClient.connect(function () {
attachTestGlobalClientAndResume(gClient, "test-sources", function (aResponse, aThreadClient) {
gThreadClient = aThreadClient;
test_simple_listsources();
});
});
do_test_pending();
}
function test_simple_listsources()
{
gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
gThreadClient.getSources(function (aResponse) {
do_check_true(
!aResponse.error,
"There shouldn't be an error fetching large amounts of sources.");
do_check_true(aResponse.sources.some(function (s) {
return s.url.match(/foo-999.js$/);
}));
gThreadClient.resume(function () {
finishClient(gClient);
});
});
});
for (let i = 0; i < 1000; i++) {
Cu.evalInSandbox("function foo###() {return ###;}".replace(/###/g, i),
gDebuggee,
"1.8",
"http://example.com/foo-" + i + ".js",
1);
}
gDebuggee.eval("debugger;");
}

View File

@ -114,21 +114,15 @@ function test_profile(aClient, aProfiler)
do_check_eq(typeof aResponse.profile.threads[0].samples, "object");
do_check_neq(aResponse.profile.threads[0].samples.length, 0);
function some(array, cb) {
for (var i = array.length; i; i--) {
if (cb(array[i - 1]))
return true;
}
return false;
}
let location = stack.name + " (" + stack.filename + ":" + funcLine + ")";
// At least one sample is expected to have been in the busy wait above.
do_check_true(some(aResponse.profile.threads[0].samples, function(sample) {
do_check_true(aResponse.profile.threads[0].samples.some(function(sample) {
return sample.name == "(root)" &&
typeof sample.frames == "object" &&
sample.frames.length != 0 &&
sample.frames.some(function(f) {
return (f.line == stack.lineNumber) &&
(f.location == stack.name + " (" + stack.filename + ":" + funcLine + ")");
(f.location == location);
});
}));
@ -161,9 +155,13 @@ function test_profiler_status()
var profiler = aResponse.profilerActor;
do_check_false(Profiler.IsActive());
client.request({ to: profiler, type: "startProfiler", features: [] }, (aResponse) => {
client.request({
to: profiler,
type: "startProfiler",
features: []
}, function (aResponse) {
do_check_true(Profiler.IsActive());
client.close(function () {});
client.close();
});
});
});

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,64 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Check basic source map integration with the "newSource" packet in the RDP.
*/
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_simple_source_map();
});
});
do_test_pending();
}
function test_simple_source_map()
{
// Because we are source mapping, we should be notified of a.js, b.js, and
// c.js as sources, and shouldn't receive abc.js or test_sourcemaps-01.js.
let expectedSources = new Set(["http://example.com/www/js/a.js",
"http://example.com/www/js/b.js",
"http://example.com/www/js/c.js"]);
gClient.addListener("newSource", function _onNewSource(aEvent, aPacket) {
do_check_eq(aEvent, "newSource");
do_check_eq(aPacket.type, "newSource");
do_check_true(!!aPacket.source);
do_check_true(expectedSources.has(aPacket.source.url),
"The source url should be one of our original sources.");
expectedSources.delete(aPacket.source.url);
if (expectedSources.size === 0) {
gClient.removeListener("newSource", _onNewSource);
finishClient(gClient);
}
});
let { code, map } = (new SourceNode(null, null, null, [
new SourceNode(1, 0, "a.js", "function a() { return 'a'; }\n"),
new SourceNode(1, 0, "b.js", "function b() { return 'b'; }\n"),
new SourceNode(1, 0, "c.js", "function c() { return 'c'; }\n"),
])).toStringWithSourceMap({
file: "abc.js",
sourceRoot: "http://example.com/www/js/"
});
code += "//@ sourceMappingURL=data:text/json;base64," + btoa(map.toString());
Components.utils.evalInSandbox(code, gDebuggee, "1.8",
"http://example.com/www/js/abc.js", 1);
}

View File

@ -0,0 +1,73 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Check basic source map integration with the "sources" packet in the RDP.
*/
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_simple_source_map();
});
});
do_test_pending();
}
function test_simple_source_map()
{
// Because we are source mapping, we should be notified of a.js, b.js, and
// c.js as sources, and shouldn"t receive abc.js or test_sourcemaps-01.js.
let expectedSources = new Set(["http://example.com/www/js/a.js",
"http://example.com/www/js/b.js",
"http://example.com/www/js/c.js"]);
let numNewSources = 0;
gClient.addListener("newSource", function _onNewSource(aEvent, aPacket) {
if (++numNewSources !== 3) {
return;
}
gClient.removeListener("newSource", _onNewSource);
gThreadClient.getSources(function (aResponse) {
do_check_true(!aResponse.error, "Should not get an error");
for (let s of aResponse.sources) {
do_check_true(expectedSources.has(s.url),
"The source's url should be one of our original sources");
expectedSources.delete(s.url);
}
do_check_eq(expectedSources.size, 0,
"Shouldn't be expecting any more sources");
finishClient(gClient);
});
});
let { code, map } = (new SourceNode(null, null, null, [
new SourceNode(1, 0, "a.js", "function a() { return 'a'; }\n"),
new SourceNode(1, 0, "b.js", "function b() { return 'b'; }\n"),
new SourceNode(1, 0, "c.js", "function c() { return 'c'; }\n"),
])).toStringWithSourceMap({
file: "abc.js",
sourceRoot: "http://example.com/www/js/"
});
code += "//@ sourceMappingURL=data:text/json;base64," + btoa(map.toString());
Components.utils.evalInSandbox(code, gDebuggee, "1.8",
"http://example.com/www/js/abc.js", 1);
}

View File

@ -0,0 +1,149 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Check setting breakpoints in source mapped sources.
*/
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_simple_source_map();
});
});
do_test_pending();
}
function testBreakpointMapping(aName, aCallback)
{
// Pause so we can set a breakpoint.
gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
do_check_true(!aPacket.error);
do_check_eq(aPacket.why.type, "debuggerStatement");
gThreadClient.setBreakpoint({
url: "http://example.com/www/js/" + aName + ".js",
// Setting the breakpoint on an empty line so that it is pushed down one
// line and we can check the source mapped actualLocation later.
line: 3,
column: 0
}, function (aResponse) {
do_check_true(!aResponse.error);
// Actual location should come back source mapped still so that
// breakpoints are displayed in the UI correctly, etc.
do_check_eq(aResponse.actualLocation.line, 4);
do_check_eq(aResponse.actualLocation.url,
"http://example.com/www/js/" + aName + ".js");
// The eval will cause us to resume, then we get an unsolicited pause
// because of our breakpoint, we resume again to finish the eval, and
// finally receive our last pause which has the result of the client
// evaluation.
gThreadClient.eval(null, aName + "()", function (aResponse) {
do_check_true(!aResponse.error, "Shouldn't be an error resuming to eval");
do_check_eq(aResponse.type, "resumed");
gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
do_check_eq(aPacket.why.type, "breakpoint");
// Assert that we paused because of the breakpoint at the correct
// location in the code by testing that the value of `ret` is still
// undefined.
do_check_eq(aPacket.frame.environment.bindings.variables.ret.value.type,
"undefined");
gThreadClient.resume(function (aResponse) {
do_check_true(!aResponse.error);
gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
do_check_eq(aPacket.why.type, "clientEvaluated");
do_check_eq(aPacket.why.frameFinished.return, aName);
gThreadClient.resume(function (aResponse) {
do_check_true(!aResponse.error);
aCallback();
});
});
});
});
});
});
});
gDebuggee.eval("(" + function () {
debugger;
} + "());");
}
function test_simple_source_map()
{
let expectedSources = new Set([
"http://example.com/www/js/a.js",
"http://example.com/www/js/b.js",
"http://example.com/www/js/c.js"
]);
gClient.addListener("newSource", function _onNewSource(aEvent, aPacket) {
expectedSources.delete(aPacket.source.url);
if (expectedSources.size > 0) {
return;
}
gClient.removeListener("newSource", _onNewSource);
testBreakpointMapping("a", function () {
testBreakpointMapping("b", function () {
testBreakpointMapping("c", function () {
finishClient(gClient);
});
});
});
});
let a = new SourceNode(null, null, null, [
new SourceNode(1, 0, "a.js", "function a() {\n"),
new SourceNode(2, 0, "a.js", " var ret;\n"),
new SourceNode(3, 0, "a.js", " // Empty line\n"),
new SourceNode(4, 0, "a.js", " ret = 'a';\n"),
new SourceNode(5, 0, "a.js", " return ret;\n"),
new SourceNode(6, 0, "a.js", "}\n")
]);
let b = new SourceNode(null, null, null, [
new SourceNode(1, 0, "b.js", "function b() {\n"),
new SourceNode(2, 0, "b.js", " var ret;\n"),
new SourceNode(3, 0, "b.js", " // Empty line\n"),
new SourceNode(4, 0, "b.js", " ret = 'b';\n"),
new SourceNode(5, 0, "b.js", " return ret;\n"),
new SourceNode(6, 0, "b.js", "}\n")
]);
let c = new SourceNode(null, null, null, [
new SourceNode(1, 0, "c.js", "function c() {\n"),
new SourceNode(2, 0, "c.js", " var ret;\n"),
new SourceNode(3, 0, "c.js", " // Empty line\n"),
new SourceNode(4, 0, "c.js", " ret = 'c';\n"),
new SourceNode(5, 0, "c.js", " return ret;\n"),
new SourceNode(6, 0, "c.js", "}\n")
]);
let { code, map } = (new SourceNode(null, null, null, [
a, b, c
])).toStringWithSourceMap({
file: "http://example.com/www/js/abc.js",
sourceRoot: "http://example.com/www/js/"
});
code += "//@ sourceMappingURL=data:text/json;base64," + btoa(map.toString());
Components.utils.evalInSandbox(code, gDebuggee, "1.8",
"http://example.com/www/js/abc.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 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

@ -30,26 +30,26 @@ function createRootActor()
};
actor.thread.requestTypes["scripts"] = function (aRequest) {
this._discoverScriptsAndSources();
let scripts = [];
for (let s of this.dbg.findScripts()) {
if (!s.url) {
continue;
return this._discoverScriptsAndSources().then(function () {
let scripts = [];
for (let s of this.dbg.findScripts()) {
if (!s.url) {
continue;
}
let script = {
url: s.url,
startLine: s.startLine,
lineCount: s.lineCount,
source: this.sources.source(s.url).form()
};
scripts.push(script);
}
let script = {
url: s.url,
startLine: s.startLine,
lineCount: s.lineCount,
source: this._getSource(s.url).form()
};
scripts.push(script);
}
return {
from: this.actorID,
scripts: scripts
};
return {
from: this.actorID,
scripts: scripts
};
}.bind(this));
};
// Pretend that we do not know about the "sources" packet to force the
@ -70,7 +70,7 @@ function createRootActor()
url: aScript.url,
startLine: aScript.startLine,
lineCount: aScript.lineCount,
source: actor.thread._getSource(aScript.url).form()
source: actor.thread.sources.source(aScript.url).form()
});
};
}(actor.thread.onNewScript));

View File

@ -74,9 +74,19 @@ skip-if = toolkit == "gonk"
reason = bug 820380
[test_listsources-01.js]
[test_listsources-02.js]
[test_listsources-03.js]
[test_new_source-01.js]
[test_sources_backwards_compat-01.js]
[test_sources_backwards_compat-02.js]
[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

@ -44,6 +44,7 @@ define('source-map/source-map-consumer', ['require', 'exports', 'module' , 'sou
* - 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.
*
@ -70,6 +71,7 @@ define('source-map/source-map-consumer', ['require', 'exports', 'module' , 'sou
var sources = util.getArg(sourceMap, 'sources');
var names = util.getArg(sourceMap, 'names');
var sourceRoot = util.getArg(sourceMap, 'sourceRoot', null);
var sourcesContent = util.getArg(sourceMap, 'sourcesContent', null);
var mappings = util.getArg(sourceMap, 'mappings');
var file = util.getArg(sourceMap, 'file');
@ -79,7 +81,8 @@ define('source-map/source-map-consumer', ['require', 'exports', 'module' , 'sou
this._names = ArraySet.fromArray(names);
this._sources = ArraySet.fromArray(sources);
this._sourceRoot = sourceRoot;
this.sourceRoot = sourceRoot;
this.sourcesContent = sourcesContent;
this.file = file;
// `this._generatedMappings` and `this._originalMappings` hold the parsed
@ -121,7 +124,7 @@ define('source-map/source-map-consumer', ['require', 'exports', 'module' , 'sou
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 ? util.join(this.sourceRoot, s) : s;
}, this);
}
});
@ -165,12 +168,7 @@ define('source-map/source-map-consumer', ['require', 'exports', 'module' , 'sou
if (str.length > 0 && !mappingSeparator.test(str.charAt(0))) {
// Original source.
temp = base64VLQ.decode(str);
if (aSourceRoot) {
mapping.source = util.join(aSourceRoot, this._sources.at(previousSource + temp.value));
}
else {
mapping.source = this._sources.at(previousSource + temp.value);
}
mapping.source = this._sources.at(previousSource + temp.value);
previousSource += temp.value;
str = temp.rest;
if (str.length === 0 || mappingSeparator.test(str.charAt(0))) {
@ -204,7 +202,9 @@ define('source-map/source-map-consumer', ['require', 'exports', 'module' , 'sou
}
this._generatedMappings.push(mapping);
this._originalMappings.push(mapping);
if (typeof mapping.originalLine === 'number') {
this._originalMappings.push(mapping);
}
}
}
@ -291,11 +291,15 @@ define('source-map/source-map-consumer', ['require', 'exports', 'module' , 'sou
this._generatedMappings,
"generatedLine",
"generatedColumn",
this._compareGeneratedPositions)
this._compareGeneratedPositions);
if (mapping) {
var source = util.getArg(mapping, 'source', null);
if (source && this.sourceRoot) {
source = util.join(this.sourceRoot, source);
}
return {
source: util.getArg(mapping, 'source', null),
source: source,
line: util.getArg(mapping, 'originalLine', null),
column: util.getArg(mapping, 'originalColumn', null),
name: util.getArg(mapping, 'name', null)
@ -310,6 +314,32 @@ define('source-map/source-map-consumer', ['require', 'exports', 'module' , 'sou
};
};
/**
* Returns the original source content. The only argument is
* the url of the original source file. Returns null if no
* original source content is availible.
*/
SourceMapConsumer.prototype.sourceContentFor =
function SourceMapConsumer_sourceContentFor(aSource) {
if (!this.sourcesContent) {
return null;
}
if (this.sourceRoot) {
// Try to remove the sourceRoot
var relativeUrl = util.relative(this.sourceRoot, aSource);
if (this._sources.has(relativeUrl)) {
return this.sourcesContent[this._sources.indexOf(relativeUrl)];
}
}
if (this._sources.has(aSource)) {
return this.sourcesContent[this._sources.indexOf(aSource)];
}
throw new Error('"' + aSource + '" is not in the SourceMap.');
};
/**
* Returns the generated line and column information for the original source,
* line, and column positions provided. The only argument is an object with
@ -332,11 +362,15 @@ define('source-map/source-map-consumer', ['require', 'exports', 'module' , 'sou
originalColumn: util.getArg(aArgs, 'column')
};
if (this.sourceRoot) {
needle.source = util.relative(this.sourceRoot, needle.source);
}
var mapping = this._findMapping(needle,
this._originalMappings,
"originalLine",
"originalColumn",
this._compareOriginalPositions)
this._compareOriginalPositions);
if (mapping) {
return {
@ -359,8 +393,7 @@ define('source-map/source-map-consumer', ['require', 'exports', 'module' , 'sou
* generated line/column in this source map.
*
* @param Function aCallback
* The function that is called with each mapping. This function should
* not mutate the mapping.
* The function that is called with each mapping.
* @param Object aContext
* Optional. If specified, this object will be the value of `this` every
* time that `aCallback` is called.
@ -388,7 +421,21 @@ define('source-map/source-map-consumer', ['require', 'exports', 'module' , 'sou
throw new Error("Unknown order of iteration.");
}
mappings.forEach(aCallback, context);
var sourceRoot = this.sourceRoot;
mappings.map(function (mapping) {
var source = mapping.source;
if (source && sourceRoot) {
source = util.join(sourceRoot, source);
}
return {
source: source,
generatedLine: mapping.generatedLine,
generatedColumn: mapping.generatedColumn,
originalLine: mapping.originalLine,
originalColumn: mapping.originalColumn,
name: mapping.name
};
}).forEach(aCallback, context);
};
exports.SourceMapConsumer = SourceMapConsumer;
@ -423,13 +470,64 @@ 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;
/**
* 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
*/
function toSetString(aStr) {
return '$' + aStr;
}
exports.toSetString = toSetString;
function fromSetString(aStr) {
return aStr.substr(1);
}
exports.fromSetString = fromSetString;
function relative(aRoot, aPath) {
aRoot = aRoot.replace(/\/$/, '');
return aPath.indexOf(aRoot + '/') === 0
? aPath.substr(aRoot.length + 1)
: aPath;
}
exports.relative = relative;
});
/* -*- Mode: js; js-indent-level: 2; -*- */
/*
@ -515,7 +613,9 @@ define('source-map/binary-search', ['require', 'exports', 'module' , ], function
* Licensed under the New BSD license. See LICENSE or:
* http://opensource.org/licenses/BSD-3-Clause
*/
define('source-map/array-set', ['require', 'exports', 'module' , ], function(require, exports, module) {
define('source-map/array-set', ['require', 'exports', 'module' , 'source-map/util'], function(require, exports, module) {
var util = require('source-map/util');
/**
* A data structure which is a combination of an array and a set. Adding a new
@ -539,19 +639,6 @@ define('source-map/array-set', ['require', 'exports', 'module' , ], function(req
return set;
};
/**
* Because behavior goes wacky when you set `__proto__` on `this._set`, 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
*/
ArraySet.prototype._toSetString = function ArraySet__toSetString (aStr) {
return "$" + aStr;
};
/**
* Add the given string to this set.
*
@ -564,7 +651,7 @@ define('source-map/array-set', ['require', 'exports', 'module' , ], function(req
}
var idx = this._array.length;
this._array.push(aStr);
this._set[this._toSetString(aStr)] = idx;
this._set[util.toSetString(aStr)] = idx;
};
/**
@ -574,7 +661,7 @@ define('source-map/array-set', ['require', 'exports', 'module' , ], function(req
*/
ArraySet.prototype.has = function ArraySet_has(aStr) {
return Object.prototype.hasOwnProperty.call(this._set,
this._toSetString(aStr));
util.toSetString(aStr));
};
/**
@ -584,7 +671,7 @@ define('source-map/array-set', ['require', 'exports', 'module' , ], function(req
*/
ArraySet.prototype.indexOf = function ArraySet_indexOf(aStr) {
if (this.has(aStr)) {
return this._set[this._toSetString(aStr)];
return this._set[util.toSetString(aStr)];
}
throw new Error('"' + aStr + '" is not in the set.');
};
@ -819,10 +906,58 @@ define('source-map/source-map-generator', ['require', 'exports', 'module' , 'so
this._sources = new ArraySet();
this._names = new ArraySet();
this._mappings = [];
this._sourcesContents = null;
}
SourceMapGenerator.prototype._version = 3;
/**
* Creates a new SourceMapGenerator based on a SourceMapConsumer
*
* @param aSourceMapConsumer The SourceMap.
*/
SourceMapGenerator.fromSourceMap =
function SourceMapGenerator_fromSourceMap(aSourceMapConsumer) {
var sourceRoot = aSourceMapConsumer.sourceRoot;
var generator = new SourceMapGenerator({
file: aSourceMapConsumer.file,
sourceRoot: sourceRoot
});
aSourceMapConsumer.eachMapping(function (mapping) {
var newMapping = {
generated: {
line: mapping.generatedLine,
column: mapping.generatedColumn
}
};
if (mapping.source) {
newMapping.source = mapping.source;
if (sourceRoot) {
newMapping.source = util.relative(sourceRoot, newMapping.source);
}
newMapping.original = {
line: mapping.originalLine,
column: mapping.originalColumn
};
if (mapping.name) {
newMapping.name = mapping.name;
}
}
generator.addMapping(newMapping);
});
aSourceMapConsumer.sources.forEach(function (sourceFile) {
var content = aSourceMapConsumer.sourceContentFor(sourceFile);
if (content) {
generator.setSourceContent(sourceFile, content);
}
});
return generator;
};
/**
* Add a single mapping from original source line and column to the generated
* source's line and column for this source map being created. The mapping
@ -858,6 +993,110 @@ define('source-map/source-map-generator', ['require', 'exports', 'module' , 'so
});
};
/**
* Set the source content for a source file.
*/
SourceMapGenerator.prototype.setSourceContent =
function SourceMapGenerator_setSourceContent(aSourceFile, aSourceContent) {
var source = aSourceFile;
if (this._sourceRoot) {
source = util.relative(this._sourceRoot, source);
}
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 {
// 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;
}
}
};
/**
* 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.
*/
SourceMapGenerator.prototype.applySourceMap =
function SourceMapGenerator_applySourceMap(aSourceMapConsumer, aSourceFile) {
// If aSourceFile is omitted, we will use the file property of the SourceMap
if (!aSourceFile) {
aSourceFile = aSourceMapConsumer.file;
}
var sourceRoot = this._sourceRoot;
// Make "aSourceFile" relative if an absolute Url is passed.
if (sourceRoot) {
aSourceFile = util.relative(sourceRoot, aSourceFile);
}
// 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"
this._mappings.forEach(function (mapping) {
if (mapping.source === aSourceFile && mapping.original) {
// Check if it can be mapped by the source map, then update the mapping.
var original = aSourceMapConsumer.originalPositionFor({
line: mapping.original.line,
column: mapping.original.column
});
if (original.source !== null) {
// Copy mapping
if (sourceRoot) {
mapping.source = util.relative(sourceRoot, original.source);
} else {
mapping.source = original.source;
}
mapping.original.line = original.line;
mapping.original.column = original.column;
if (original.name !== null && mapping.name !== null) {
// Only use the identifier name if it's an identifier
// in both SourceMaps
mapping.name = original.name;
}
}
}
var source = mapping.source;
if (source && !newSources.has(source)) {
newSources.add(source);
}
var name = mapping.name;
if (name && !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) {
sourceFile = util.relative(sourceRoot, sourceFile);
}
this.setSourceContent(sourceFile, content);
}
}, this);
};
/**
* A mapping can have one of the three levels of data:
*
@ -978,6 +1217,17 @@ define('source-map/source-map-generator', ['require', 'exports', 'module' , 'so
if (this._sourceRoot) {
map.sourceRoot = this._sourceRoot;
}
if (this._sourcesContents) {
map.sourcesContent = map.sources.map(function (source) {
if (map.sourceRoot) {
source = util.relative(map.sourceRoot, source);
}
return Object.prototype.hasOwnProperty.call(
this._sourcesContents, util.toSetString(source))
? this._sourcesContents[util.toSetString(source)]
: null;
}, this);
}
return map;
};
@ -998,9 +1248,10 @@ define('source-map/source-map-generator', ['require', 'exports', 'module' , 'so
* 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'], function(require, exports, module) {
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');
/**
* SourceNodes provide a way to abstract over interpolating/concatenating
@ -1012,15 +1263,121 @@ define('source-map/source-node', ['require', 'exports', 'module' , 'source-map/
* @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) {
function SourceNode(aLine, aColumn, aSource, aChunks, aName) {
this.children = [];
this.line = aLine;
this.column = aColumn;
this.source = aSource;
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;
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
*/
SourceNode.fromStringWithSourceMap =
function SourceNode_fromStringWithSourceMap(aGeneratedCode, aSourceMapConsumer) {
// 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');
// 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 {
// 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);
} 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);
}
}
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"));
// Copy sourcesContent into SourceNode
aSourceMapConsumer.sources.forEach(function (sourceFile) {
var content = aSourceMapConsumer.sourceContentFor(sourceFile);
if (content) {
node.setSourceContent(sourceFile, content);
}
});
return node;
function addMappingWithCode(mapping, code) {
if (mapping.source === undefined) {
node.add(code);
} else {
node.add(new SourceNode(mapping.originalLine,
mapping.originalColumn,
mapping.source,
code,
mapping.name));
}
}
};
/**
* Add a chunk of generated JS to this source node.
*
@ -1083,7 +1440,10 @@ define('source-map/source-node', ['require', 'exports', 'module' , 'source-map/
}
else {
if (chunk !== '') {
aFn(chunk, { source: this.source, line: this.line, column: this.column });
aFn(chunk, { source: this.source,
line: this.line,
column: this.column,
name: this.name });
}
}
}, this);
@ -1098,7 +1458,7 @@ define('source-map/source-node', ['require', 'exports', 'module' , 'source-map/
SourceNode.prototype.join = function SourceNode_join(aSep) {
var newChildren;
var i;
var len = this.children.length
var len = this.children.length;
if (len > 0) {
newChildren = [];
for (i = 0; i < len-1; i++) {
@ -1132,6 +1492,36 @@ define('source-map/source-node', ['require', 'exports', 'module' , 'source-map/
return this;
};
/**
* Set the source content for a source file. This will be added to the SourceMapGenerator
* in the sourcesContent field.
*
* @param aSourceFile The filename of the source file
* @param aSourceContent The content of the source file
*/
SourceNode.prototype.setSourceContent =
function SourceNode_setSourceContent(aSourceFile, aSourceContent) {
this.sourceContents[util.toSetString(aSourceFile)] = aSourceContent;
};
/**
* Walk over the tree of SourceNodes. The walking function is called for each
* source file content and is passed the filename and source content.
*
* @param aFn The traversal function.
*/
SourceNode.prototype.walkSourceContents =
function SourceNode_walkSourceContents(aFn) {
this.children.forEach(function (chunk) {
if (chunk instanceof SourceNode) {
chunk.walkSourceContents(aFn);
}
}, this);
Object.keys(this.sourceContents).forEach(function (sourceFileKey) {
aFn(util.fromSetString(sourceFileKey), this.sourceContents[sourceFileKey]);
}, this);
};
/**
* Return the string representation of this source node. Walks over the tree
* and concatenates all the various snippets together to one string.
@ -1155,25 +1545,36 @@ define('source-map/source-node', ['require', 'exports', 'module' , 'source-map/
column: 0
};
var map = new SourceMapGenerator(aArgs);
var sourceMappingActive = false;
this.walk(function (chunk, original) {
generated.code += chunk;
if (original.source != null
&& original.line != null
&& original.column != null) {
if (original.source !== null
&& original.line !== null
&& original.column !== null) {
map.addMapping({
source: original.source,
original: {
line: original.line,
column: original.column
},
generated: {
line: generated.line,
column: generated.column
},
name: original.name
});
sourceMappingActive = true;
} else if (sourceMappingActive) {
map.addMapping({
generated: {
line: generated.line,
column: generated.column
}
});
sourceMappingActive = false;
}
chunk.split('').forEach(function (char) {
if (char === '\n') {
chunk.split('').forEach(function (ch) {
if (ch === '\n') {
generated.line++;
generated.column = 0;
} else {
@ -1181,6 +1582,9 @@ define('source-map/source-node', ['require', 'exports', 'module' , 'source-map/
}
});
});
this.walkSourceContents(function (sourceFile, sourceContent) {
map.setSourceContent(sourceFile, sourceContent);
});
return { code: generated.code, map: map };
};
@ -1191,6 +1595,6 @@ define('source-map/source-node', ['require', 'exports', 'module' , 'source-map/
/* -*- Mode: js; js-indent-level: 2; -*- */
///////////////////////////////////////////////////////////////////////////////
let SourceMapConsumer = require('source-map/source-map-consumer').SourceMapConsumer;
let SourceMapGenerator = require('source-map/source-map-generator').SourceMapGenerator;
let SourceNode = require('source-map/source-node').SourceNode;
this.SourceMapConsumer = require('source-map/source-map-consumer').SourceMapConsumer;
this.SourceMapGenerator = require('source-map/source-map-generator').SourceMapGenerator;
this.SourceNode = require('source-map/source-node').SourceNode;

View File

@ -15,7 +15,7 @@
Components.utils.import('resource://gre/modules/devtools/Require.jsm');
Components.utils.import('resource://gre/modules/devtools/SourceMap.jsm');
let EXPORTED_SYMBOLS = [ "define", "runSourceMapTests" ];
this.EXPORTED_SYMBOLS = [ "define", "runSourceMapTests" ];
/* -*- Mode: js; js-indent-level: 2; -*- */
/*
* Copyright 2011 Mozilla Foundation and contributors
@ -78,7 +78,9 @@ define('test/source-map/assert', ['exports'], function (exports) {
* Licensed under the New BSD license. See LICENSE or:
* http://opensource.org/licenses/BSD-3-Clause
*/
define('test/source-map/util', ['require', 'exports', 'module' , ], function(require, exports, module) {
define('test/source-map/util', ['require', 'exports', 'module' , 'lib/source-map/util'], function(require, exports, module) {
var util = require('source-map/util');
// This is a test mapping which maps functions from two different files
// (one.js and two.js) to a minified generated source.
@ -99,6 +101,8 @@ define('test/source-map/util', ['require', 'exports', 'module' , ], function(req
//
// ONE.foo=function(a){return baz(a);};
// TWO.inc=function(a){return a+1;};
exports.testGeneratedCode = " ONE.foo=function(a){return baz(a);};\n"+
" TWO.inc=function(a){return a+1;};";
exports.testMap = {
version: 3,
file: 'min.js',
@ -107,6 +111,22 @@ define('test/source-map/util', ['require', 'exports', 'module' , ], function(req
sourceRoot: '/the/root',
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' +
' };',
' TWO.inc = function (n) {\n' +
' return n + 1;\n' +
' };'
],
sourceRoot: '/the/root',
mappings: 'CAAC,IAAI,IAAM,SAAUA,GAClB,OAAOC,IAAID;CCDb,IAAI,IAAM,SAAUE,GAClB,OAAOA'
};
function assertMapping(generatedLine, generatedColumn, originalSource,
originalLine, originalColumn, name, map, assert,
@ -125,8 +145,21 @@ define('test/source-map/util', ['require', 'exports', 'module' , ], function(req
assert.equal(origMapping.column, originalColumn,
'Incorrect column, expected ' + JSON.stringify(originalColumn)
+ ', got ' + JSON.stringify(origMapping.column));
assert.equal(origMapping.source, originalSource,
'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));
}
@ -146,6 +179,135 @@ define('test/source-map/util', ['require', 'exports', 'module' , ], function(req
}
exports.assertMapping = assertMapping;
function assertEqualMaps(assert, actualMap, expectedMap) {
assert.equal(actualMap.version, expectedMap.version, "version mismatch");
assert.equal(actualMap.file, expectedMap.file, "file mismatch");
assert.equal(actualMap.names.length,
expectedMap.names.length,
"names length mismatch: " +
actualMap.names.join(", ") + " != " + expectedMap.names.join(", "));
for (var i = 0; i < actualMap.names.length; i++) {
assert.equal(actualMap.names[i],
expectedMap.names[i],
"names[" + i + "] mismatch: " +
actualMap.names.join(", ") + " != " + expectedMap.names.join(", "));
}
assert.equal(actualMap.sources.length,
expectedMap.sources.length,
"sources length mismatch: " +
actualMap.sources.join(", ") + " != " + expectedMap.sources.join(", "));
for (var i = 0; i < actualMap.sources.length; i++) {
assert.equal(actualMap.sources[i],
expectedMap.sources[i],
"sources[" + i + "] length mismatch: " +
actualMap.sources.join(", ") + " != " + expectedMap.sources.join(", "));
}
assert.equal(actualMap.sourceRoot,
expectedMap.sourceRoot,
"sourceRoot mismatch: " +
actualMap.sourceRoot + " != " + expectedMap.sourceRoot);
assert.equal(actualMap.mappings, expectedMap.mappings, "mappings mismatch");
if (actualMap.sourcesContent) {
assert.equal(actualMap.sourcesContent.length,
expectedMap.sourcesContent.length,
"sourcesContent length mismatch");
for (var i = 0; i < actualMap.sourcesContent.length; i++) {
assert.equal(actualMap.sourcesContent[i],
expectedMap.sourcesContent[i],
"sourcesContent[" + i + "] mismatch");
}
}
}
exports.assertEqualMaps = assertEqualMaps;
});
/* -*- 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
*/
define('lib/source-map/util', ['require', 'exports', 'module' , ], function(require, exports, module) {
/**
* This is a helper function for getting values from parameter/options
* objects.
*
* @param args The object we are extracting values from
* @param name The name of the property we are getting.
* @param defaultValue An optional value to return if the property is missing
* from the object. If this is not specified and the property is missing, an
* error will be thrown.
*/
function getArg(aArgs, aName, aDefaultValue) {
if (aName in aArgs) {
return aArgs[aName];
} 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+)?/;
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) {
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;
/**
* 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
*/
function toSetString(aStr) {
return '$' + aStr;
}
exports.toSetString = toSetString;
function fromSetString(aStr) {
return aStr.substr(1);
}
exports.fromSetString = fromSetString;
function relative(aRoot, aPath) {
aRoot = aRoot.replace(/\/$/, '');
return aPath.indexOf(aRoot + '/') === 0
? aPath.substr(aRoot.length + 1)
: aPath;
}
exports.relative = relative;
});
/* -*- Mode: js; js-indent-level: 2; -*- */
/*
@ -167,3 +329,4 @@ function runSourceMapTests(modName, do_throw) {
}
}
this.runSourceMapTests = runSourceMapTests;

View File

@ -15,6 +15,7 @@ Components.utils.import('resource://test/Utils.jsm');
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) {
assert.doesNotThrow(function () {
@ -97,6 +98,10 @@ define("test/source-map/test-source-map-consumer", ["require", "exports", "modul
map.eachMapping(function (mapping) {
assert.ok(mapping.generatedLine >= previousLine);
if (mapping.source) {
assert.equal(mapping.source.indexOf(util.testMap.sourceRoot), 0);
}
if (mapping.generatedLine === previousLine) {
assert.ok(mapping.generatedColumn >= previousColumn);
previousColumn = mapping.generatedColumn;
@ -144,6 +149,150 @@ define("test/source-map/test-source-map-consumer", ["require", "exports", "modul
}, context);
};
exports['test that the `sourcesContent` field has the original sources'] = function (assert, util) {
var map = new SourceMapConsumer(util.testMapWithSourcesContent);
var sourcesContent = map.sourcesContent;
assert.equal(sourcesContent[0], ' ONE.foo = function (bar) {\n return baz(bar);\n };');
assert.equal(sourcesContent[1], ' TWO.inc = function (n) {\n return n + 1;\n };');
assert.equal(sourcesContent.length, 2);
};
exports['test that we can get the original sources for the sources'] = function (assert, util) {
var map = new SourceMapConsumer(util.testMapWithSourcesContent);
var sources = map.sources;
assert.equal(map.sourceContentFor(sources[0]), ' ONE.foo = function (bar) {\n return baz(bar);\n };');
assert.equal(map.sourceContentFor(sources[1]), ' TWO.inc = function (n) {\n return n + 1;\n };');
assert.equal(map.sourceContentFor("one.js"), ' ONE.foo = function (bar) {\n return baz(bar);\n };');
assert.equal(map.sourceContentFor("two.js"), ' TWO.inc = function (n) {\n return n + 1;\n };');
assert.throws(function () {
map.sourceContentFor("");
}, Error);
assert.throws(function () {
map.sourceContentFor("/the/root/three.js");
}, Error);
assert.throws(function () {
map.sourceContentFor("three.js");
}, Error);
};
exports['test sourceRoot + generatedPositionFor'] = function (assert, util) {
var map = new SourceMapGenerator({
sourceRoot: 'foo/bar',
file: 'baz.js'
});
map.addMapping({
original: { line: 1, column: 1 },
generated: { line: 2, column: 2 },
source: 'bang.coffee'
});
map.addMapping({
original: { line: 5, column: 5 },
generated: { line: 6, column: 6 },
source: 'bang.coffee'
});
map = new SourceMapConsumer(map.toString());
// Should handle without sourceRoot.
var pos = map.generatedPositionFor({
line: 1,
column: 1,
source: 'bang.coffee'
});
assert.equal(pos.line, 2);
assert.equal(pos.column, 2);
// Should handle with sourceRoot.
var pos = map.generatedPositionFor({
line: 1,
column: 1,
source: 'foo/bar/bang.coffee'
});
assert.equal(pos.line, 2);
assert.equal(pos.column, 2);
};
exports['test sourceRoot + originalPositionFor'] = function (assert, util) {
var map = new SourceMapGenerator({
sourceRoot: 'foo/bar',
file: 'baz.js'
});
map.addMapping({
original: { line: 1, column: 1 },
generated: { line: 2, column: 2 },
source: 'bang.coffee'
});
map = new SourceMapConsumer(map.toString());
var pos = map.originalPositionFor({
line: 2,
column: 2,
});
// Should always have the prepended source root
assert.equal(pos.source, 'foo/bar/bang.coffee');
assert.equal(pos.line, 1);
assert.equal(pos.column, 1);
};
exports['test github issue #56'] = function (assert, util) {
var map = new SourceMapGenerator({
sourceRoot: 'http://',
file: 'www.example.com/foo.js'
});
map.addMapping({
original: { line: 1, column: 1 },
generated: { line: 2, column: 2 },
source: 'www.example.com/original.js'
});
map = new SourceMapConsumer(map.toString());
var sources = map.sources;
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.');
};
});
function run_test() {
runSourceMapTests('test/source-map/test-source-map-consumer', do_throw);

View File

@ -15,6 +15,9 @@ Components.utils.import('resource://test/Utils.jsm');
define("test/source-map/test-source-map-generator", ["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;
var util = require('source-map/util');
exports['test some simple stuff'] = function (assert, util) {
var map = new SourceMapGenerator({
@ -176,19 +179,100 @@ define("test/source-map/test-source-map-generator", ["require", "exports", "modu
map = JSON.parse(map.toString());
assert.equal(map.version, 3);
assert.equal(map.file, 'min.js');
assert.equal(map.names.length, 3);
assert.equal(map.names[0], 'bar');
assert.equal(map.names[1], 'baz');
assert.equal(map.names[2], 'n');
assert.equal(map.sources.length, 2);
assert.equal(map.sources[0], 'one.js');
assert.equal(map.sources[1], 'two.js');
assert.equal(map.sourceRoot, '/the/root');
assert.equal(map.mappings, 'CAAC,IAAI,IAAM,SAAUA,GAClB,OAAOC,IAAID;CCDb,IAAI,IAAM,SAAUE,GAClB,OAAOA');
util.assertEqualMaps(assert, map, util.testMap);
};
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 },
source: 'one.js'
});
map.addMapping({
generated: { line: 2, column: 1 },
original: { line: 1, column: 1 },
source: 'two.js'
});
map.setSourceContent('one.js', 'one file content');
map = JSON.parse(map.toString());
assert.equal(map.sources[0], 'one.js');
assert.equal(map.sources[1], 'two.js');
assert.equal(map.sourcesContent[0], 'one file content');
assert.equal(map.sourcesContent[1], null);
};
exports['test .fromSourceMap'] = function (assert, util) {
var map = SourceMapGenerator.fromSourceMap(new SourceMapConsumer(util.testMap));
util.assertEqualMaps(assert, map.toJSON(), util.testMap);
};
exports['test .fromSourceMap with sourcesContent'] = function (assert, util) {
var map = SourceMapGenerator.fromSourceMap(
new SourceMapConsumer(util.testMapWithSourcesContent));
util.assertEqualMaps(assert, map.toJSON(), util.testMapWithSourcesContent);
};
exports['test applySourceMap'] = function (assert, util) {
var node = new SourceNode(null, null, null, [
new SourceNode(2, 0, 'fileX', 'lineX2\n'),
'genA1\n',
new SourceNode(2, 0, 'fileY', 'lineY2\n'),
'genA2\n',
new SourceNode(1, 0, 'fileX', 'lineX1\n'),
'genA3\n',
new SourceNode(1, 0, 'fileY', 'lineY1\n')
]);
var mapStep1 = node.toStringWithSourceMap({
file: 'fileA'
}).map;
mapStep1.setSourceContent('fileX', 'lineX1\nlineX2\n');
mapStep1 = mapStep1.toJSON();
node = new SourceNode(null, null, null, [
'gen1\n',
new SourceNode(1, 0, 'fileA', 'lineA1\n'),
new SourceNode(2, 0, 'fileA', 'lineA2\n'),
new SourceNode(3, 0, 'fileA', 'lineA3\n'),
new SourceNode(4, 0, 'fileA', 'lineA4\n'),
new SourceNode(1, 0, 'fileB', 'lineB1\n'),
new SourceNode(2, 0, 'fileB', 'lineB2\n'),
'gen2\n'
]);
var mapStep2 = node.toStringWithSourceMap({
file: 'fileGen'
}).map;
mapStep2.setSourceContent('fileB', 'lineB1\nlineB2\n');
mapStep2 = mapStep2.toJSON();
node = new SourceNode(null, null, null, [
'gen1\n',
new SourceNode(2, 0, 'fileX', 'lineA1\n'),
new SourceNode(2, 0, 'fileA', 'lineA2\n'),
new SourceNode(2, 0, 'fileY', 'lineA3\n'),
new SourceNode(4, 0, 'fileA', 'lineA4\n'),
new SourceNode(1, 0, 'fileB', 'lineB1\n'),
new SourceNode(2, 0, 'fileB', 'lineB2\n'),
'gen2\n'
]);
var expectedMap = node.toStringWithSourceMap({
file: 'fileGen'
}).map;
expectedMap.setSourceContent('fileX', 'lineX1\nlineX2\n');
expectedMap.setSourceContent('fileB', 'lineB1\nlineB2\n');
expectedMap = expectedMap.toJSON();
// 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);
};
});
function run_test() {
runSourceMapTests('test/source-map/test-source-map-generator', do_throw);

View File

@ -136,7 +136,10 @@ define("test/source-map/test-source-node", ["require", "exports", "module"], fun
exports['test .toStringWithSourceMap()'] = function (assert, util) {
var node = new SourceNode(null, null, null,
['(function () {\n',
' ', new SourceNode(1, 0, 'a.js', ['someCall()']), ';\n',
' ',
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',
'}());']);
var map = node.toStringWithSourceMap({
@ -148,6 +151,14 @@ define("test/source-map/test-source-node", ["require", "exports", "module"], fun
var actual;
actual = map.originalPositionFor({
line: 1,
column: 4
});
assert.equal(actual.source, null);
assert.equal(actual.line, null);
assert.equal(actual.column, null);
actual = map.originalPositionFor({
line: 2,
column: 2
@ -155,6 +166,7 @@ define("test/source-map/test-source-node", ["require", "exports", "module"], fun
assert.equal(actual.source, 'a.js');
assert.equal(actual.line, 1);
assert.equal(actual.column, 0);
assert.equal(actual.name, 'originalCall');
actual = map.originalPositionFor({
line: 3,
@ -163,8 +175,115 @@ define("test/source-map/test-source-node", ["require", "exports", "module"], fun
assert.equal(actual.source, 'b.js');
assert.equal(actual.line, 2);
assert.equal(actual.column, 0);
actual = map.originalPositionFor({
line: 3,
column: 16
});
assert.equal(actual.source, null);
assert.equal(actual.line, null);
assert.equal(actual.column, null);
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) {
var node = SourceNode.fromStringWithSourceMap(
util.testGeneratedCode,
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.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() complex version'] = function (assert, util) {
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",
"/* Generated Source */"]);
input = input.toStringWithSourceMap({
file: 'foo.js'
});
var node = SourceNode.fromStringWithSourceMap(
input.code,
new SourceMapConsumer(input.map.toString()));
var result = node.toStringWithSourceMap({
file: 'foo.js'
});
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 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'),
'}());']);
node.setSourceContent('b.js', 'otherContent');
var map = node.toStringWithSourceMap({
file: 'foo.js'
}).map;
assert.ok(map instanceof SourceMapGenerator, 'map instanceof SourceMapGenerator');
map = new SourceMapConsumer(map.toString());
assert.equal(map.sources.length, 2);
assert.equal(map.sources[0], 'a.js');
assert.equal(map.sources[1], 'b.js');
assert.equal(map.sourcesContent.length, 2);
assert.equal(map.sourcesContent[0], 'someContent');
assert.equal(map.sourcesContent[1], 'otherContent');
};
exports['test walkSourceContents'] = 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'),
'}());']);
node.setSourceContent('b.js', 'otherContent');
var results = [];
node.walkSourceContents(function (sourceFile, sourceContent) {
results.push([sourceFile, sourceContent]);
});
assert.equal(results.length, 2);
assert.equal(results[0][0], 'a.js');
assert.equal(results[0][1], 'someContent');
assert.equal(results[1][0], 'b.js');
assert.equal(results[1][1], 'otherContent');
};
});
function run_test() {
runSourceMapTests('test/source-map/test-source-node', do_throw);