mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Merge m-c to birch.
This commit is contained in:
commit
1f4f27488e
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
18
browser/devtools/debugger/test/binary_search.coffee
Normal file
18
browser/devtools/debugger/test/binary_search.coffee
Normal 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
|
12
browser/devtools/debugger/test/binary_search.html
Normal file
12
browser/devtools/debugger/test/binary_search.html
Normal 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>
|
29
browser/devtools/debugger/test/binary_search.js
Normal file
29
browser/devtools/debugger/test/binary_search.js
Normal 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
|
||||
*/
|
10
browser/devtools/debugger/test/binary_search.map
Normal file
10
browser/devtools/debugger/test/binary_search.map
Normal 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"
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
156
browser/devtools/debugger/test/browser_dbg_source_maps-01.js
Normal file
156
browser/devtools/debugger/test/browser_dbg_source_maps-01.js
Normal 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;
|
||||
});
|
@ -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;
|
||||
}
|
||||
};
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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));
|
||||
});
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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");
|
||||
},
|
||||
};
|
||||
|
@ -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.
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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));
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,6 @@
|
||||
foo = (n) ->
|
||||
return "foo" + i for i in [0...n]
|
||||
|
||||
[first, second, third] = foo(3)
|
||||
|
||||
debugger
|
@ -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"
|
||||
}
|
16
toolkit/devtools/debugger/tests/unit/sourcemapped.js
Normal file
16
toolkit/devtools/debugger/tests/unit/sourcemapped.js
Normal 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);
|
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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);
|
||||
});
|
||||
}
|
||||
|
52
toolkit/devtools/debugger/tests/unit/test_listsources-03.js
Normal file
52
toolkit/devtools/debugger/tests/unit/test_listsources-03.js
Normal 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;");
|
||||
}
|
@ -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();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -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);
|
||||
});
|
||||
|
64
toolkit/devtools/debugger/tests/unit/test_sourcemaps-01.js
Normal file
64
toolkit/devtools/debugger/tests/unit/test_sourcemaps-01.js
Normal 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);
|
||||
}
|
73
toolkit/devtools/debugger/tests/unit/test_sourcemaps-02.js
Normal file
73
toolkit/devtools/debugger/tests/unit/test_sourcemaps-02.js
Normal 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);
|
||||
}
|
149
toolkit/devtools/debugger/tests/unit/test_sourcemaps-03.js
Normal file
149
toolkit/devtools/debugger/tests/unit/test_sourcemaps-03.js
Normal 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);
|
||||
}
|
48
toolkit/devtools/debugger/tests/unit/test_sourcemaps-04.js
Normal file
48
toolkit/devtools/debugger/tests/unit/test_sourcemaps-04.js
Normal 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);
|
||||
}
|
48
toolkit/devtools/debugger/tests/unit/test_sourcemaps-05.js
Normal file
48
toolkit/devtools/debugger/tests/unit/test_sourcemaps-05.js
Normal 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);
|
||||
}
|
@ -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));
|
||||
|
@ -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]
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
Loading…
Reference in New Issue
Block a user