Bug 920116 - Show full stack traces for console logged messages; r=robcee

This commit is contained in:
Mihai Sucan 2014-04-22 21:45:04 +03:00
parent 51ed54dbb0
commit c286e981b0
22 changed files with 438 additions and 87 deletions

View File

@ -870,7 +870,7 @@ Messages.Simple.prototype = Heritage.extend(Messages.BaseMessage.prototype,
_renderBody: function()
{
let body = this.document.createElementNS(XHTML_NS, "span");
body.className = "body devtools-monospace";
body.className = "message-body-wrapper message-body devtools-monospace";
let anchor, container = body;
if (this._link || this._linkCallback) {
@ -1223,6 +1223,7 @@ Messages.ConsoleGeneric = function(packet)
line: packet.lineNumber,
},
};
switch (packet.level) {
case "count": {
let counter = packet.counter, label = counter.label;
@ -1239,12 +1240,30 @@ Messages.ConsoleGeneric = function(packet)
this._repeatID.consoleApiLevel = packet.level;
this._repeatID.styles = packet.styles;
this._stacktrace = this._repeatID.stacktrace = packet.stacktrace;
this._styles = packet.styles || [];
this._onClickCollapsible = this._onClickCollapsible.bind(this);
};
Messages.ConsoleGeneric.prototype = Heritage.extend(Messages.Extended.prototype,
{
_styles: null,
_stacktrace: null,
/**
* Tells if the message can be expanded/collapsed.
* @type boolean
*/
collapsible: false,
/**
* Getter that tells if this message is collapsed - no details are shown.
* @type boolean
*/
get collapsed() {
return this.collapsible && this.element && !this.element.hasAttribute("open");
},
_renderBodyPieceSeparator: function()
{
@ -1253,13 +1272,84 @@ Messages.ConsoleGeneric.prototype = Heritage.extend(Messages.Extended.prototype,
render: function()
{
let lastStyle = null;
let msg = this.document.createElementNS(XHTML_NS, "span");
msg.className = "message-body devtools-monospace";
this._renderBodyPieces(msg);
let repeatNode = Messages.Simple.prototype._renderRepeatNode.call(this);
let location = Messages.Simple.prototype._renderLocation.call(this);
if (location) {
location.target = "jsdebugger";
}
let stack = null;
let twisty = null;
if (this._stacktrace && this._stacktrace.length > 0) {
stack = new Widgets.Stacktrace(this, this._stacktrace).render().element;
twisty = this.document.createElementNS(XHTML_NS, "a");
twisty.className = "theme-twisty";
twisty.href = "#";
twisty.title = l10n.getStr("messageToggleDetails");
twisty.addEventListener("click", this._onClickCollapsible);
}
let flex = this.document.createElementNS(XHTML_NS, "span");
flex.className = "message-flex-body";
if (twisty) {
flex.appendChild(twisty);
}
flex.appendChild(msg);
if (repeatNode) {
flex.appendChild(repeatNode);
}
if (location) {
flex.appendChild(location);
}
let result = this.document.createDocumentFragment();
result.appendChild(flex);
if (stack) {
result.appendChild(this.document.createTextNode("\n"));
result.appendChild(stack);
}
this._message = result;
this._stacktrace = null;
Messages.Simple.prototype.render.call(this);
if (stack) {
this.collapsible = true;
this.element.setAttribute("collapsible", true);
let icon = this.element.querySelector(".icon");
icon.addEventListener("click", this._onClickCollapsible);
}
return this;
},
_renderBody: function()
{
let body = Messages.Simple.prototype._renderBody.apply(this, arguments);
body.classList.remove("devtools-monospace", "message-body");
return body;
},
_renderBodyPieces: function(container)
{
let lastStyle = null;
for (let i = 0; i < this._messagePieces.length; i++) {
let separator = i > 0 ? this._renderBodyPieceSeparator() : null;
if (separator) {
result.appendChild(separator);
container.appendChild(separator);
}
let piece = this._messagePieces[i];
@ -1270,13 +1360,11 @@ Messages.ConsoleGeneric.prototype = Heritage.extend(Messages.Extended.prototype,
lastStyle = this.cleanupStyle(style);
}
result.appendChild(this._renderBodyPiece(piece, lastStyle));
container.appendChild(this._renderBodyPiece(piece, lastStyle));
}
this._message = result;
this._messagePieces = null;
this._styles = null;
return Messages.Simple.prototype.render.call(this);
},
_renderBodyPiece: function(piece, style)
@ -1298,6 +1386,41 @@ Messages.ConsoleGeneric.prototype = Heritage.extend(Messages.Extended.prototype,
return result;
},
// no-op for the message location and .repeats elements.
// |this.render()| handles customized message output.
_renderLocation: function() { },
_renderRepeatNode: function() { },
/**
* Expand/collapse message details.
*/
toggleDetails: function()
{
let twisty = this.element.querySelector(".theme-twisty");
if (this.element.hasAttribute("open")) {
this.element.removeAttribute("open");
twisty.removeAttribute("open");
} else {
this.element.setAttribute("open", true);
twisty.setAttribute("open", true);
}
},
/**
* The click event handler for the message expander arrow element. This method
* toggles the display of message details.
*
* @private
* @param nsIDOMEvent ev
* The DOM event object.
* @see this.toggleDetails()
*/
_onClickCollapsible: function(ev)
{
ev.preventDefault();
this.toggleDetails();
},
/**
* Given a style attribute value, return a cleaned up version of the string
* such that:
@ -1355,7 +1478,7 @@ Messages.ConsoleGeneric.prototype = Heritage.extend(Messages.Extended.prototype,
Messages.ConsoleTrace = function(packet)
{
let options = {
className: "consoleTrace cm-s-mozilla",
className: "cm-s-mozilla",
timestamp: packet.timeStamp,
category: "webdev",
severity: CONSOLE_API_LEVELS_TO_SEVERITIES[packet.level],
@ -1412,6 +1535,13 @@ Messages.ConsoleTrace.prototype = Heritage.extend(Messages.Simple.prototype,
return result;
},
render: function()
{
Messages.Simple.prototype.render.apply(this, arguments);
this.element.setAttribute("open", true);
return this;
},
/**
* Render the stack frames.
*
@ -1429,7 +1559,7 @@ Messages.ConsoleTrace.prototype = Heritage.extend(Messages.Simple.prototype,
cmprop.textContent = "trace";
let title = this.document.createElementNS(XHTML_NS, "span");
title.className = "title devtools-monospace";
title.className = "message-body devtools-monospace";
title.appendChild(cmvar);
title.appendChild(this.document.createTextNode("."));
title.appendChild(cmprop);
@ -1443,7 +1573,8 @@ Messages.ConsoleTrace.prototype = Heritage.extend(Messages.Simple.prototype,
let widget = new Widgets.Stacktrace(this, this._stacktrace).render();
let body = this.document.createElementNS(XHTML_NS, "div");
let body = this.document.createElementNS(XHTML_NS, "span");
body.className = "message-flex-body";
body.appendChild(title);
if (repeatNode) {
body.appendChild(repeatNode);
@ -1463,7 +1594,7 @@ Messages.ConsoleTrace.prototype = Heritage.extend(Messages.Simple.prototype,
_renderBody: function()
{
let body = Messages.Simple.prototype._renderBody.apply(this, arguments);
body.classList.remove("devtools-monospace");
body.classList.remove("devtools-monospace", "message-body");
return body;
},
@ -2722,7 +2853,6 @@ Widgets.Stacktrace.prototype = Heritage.extend(Widgets.BaseWidget.prototype,
return elem;
},
}); // Widgets.Stacktrace.prototype

View File

@ -113,6 +113,7 @@ support-files =
test-bug-609872-cd-iframe-parent.html
test-bug-609872-cd-iframe-child.html
test-bug-989025-iframe-parent.html
test-console-api-stackframe.html
[browser_bug664688_sandbox_update_after_navigation.js]
[browser_bug_638949_copy_link_location.js]
@ -283,3 +284,4 @@ run-if = os == "mac"
[browser_webconsole_cd_iframe.js]
[browser_webconsole_autocomplete_crossdomain_iframe.js]
[browser_webconsole_console_custom_styles.js]
[browser_webconsole_console_api_stackframe.js]

View File

@ -67,6 +67,7 @@ function onConsoleMessage(aResults) {
// Test that the "Copy Link Location" menu item is hidden for non-network
// messages.
message.scrollIntoView();
waitForContextMenu(menu, message, () => {
let isHidden = menu.querySelector(CONTEXT_MENU_ID).hidden;
ok(isHidden, CONTEXT_MENU_ID + " is hidden");
@ -107,6 +108,7 @@ function onNetworkMessage(aResults) {
function testMenuWithNetActivity() {
// Test that the "Copy Link Location" menu item is visible for network-related
// messages.
message.scrollIntoView();
waitForContextMenu(menu, message, () => {
let isVisible = !menu.querySelector(CONTEXT_MENU_ID).hidden;
ok(isVisible, CONTEXT_MENU_ID + " is visible");

View File

@ -53,7 +53,7 @@ function checkMessages([result])
if (msg) {
ok(msg, "message element #" + m);
let clickable = msg.querySelector(".body a");
let clickable = msg.querySelector(".message-body a");
ok(clickable, "clickable object #" + m);
msg.scrollIntoView(false);

View File

@ -23,7 +23,7 @@ function test()
ok(msg, "output message found");
let anchor = msg.querySelector("a");
let body = msg.querySelector(".body");
let body = msg.querySelector(".message-body");
ok(anchor, "object anchor");
ok(body, "message body");
ok(body.textContent.contains('testProp: "testValue"'), "message text check");
@ -59,7 +59,7 @@ function test()
msg = yield execute("window.location");
ok(msg, "output message found");
body = msg.querySelector(".body");
body = msg.querySelector(".message-body");
ok(body, "message body");
anchor = msg.querySelector("a");
ok(anchor, "object anchor");

View File

@ -47,7 +47,7 @@ function onConsoleMessage(aResults)
let msg = [...aResults[0].matched][0];
ok(msg, "message element");
let body = msg.querySelector(".body");
let body = msg.querySelector(".message-body");
ok(body, "message body");
let clickable = aResults[0].clickableElements[0];

View File

@ -25,7 +25,7 @@ function testInputFocus() {
}],
}).then(([result]) => {
let msg = [...result.matched][0];
let outputItem = msg.querySelector(".body");
let outputItem = msg.querySelector(".message-body");
ok(outputItem, "found a logged message");
let inputNode = hud.jsterm.inputNode;
ok(inputNode.getAttribute("focused"), "input node is focused, first");

View File

@ -34,7 +34,7 @@ function performTest(hud)
}).then(([result]) => {
let msg = [...result.matched][0];
ok(msg, "message element");
let body = msg.querySelector(".body");
let body = msg.querySelector(".message-body");
ok(body, "message body");
let clickable = result.clickableElements[0];
ok(clickable, "the console.log() object anchor was found");

View File

@ -36,7 +36,7 @@ function tabLoad2(aEvent) {
}],
}).then(([result]) => {
let msg = [...result.matched][0];
outputItem = msg.querySelector(".body .url");
outputItem = msg.querySelector(".message-body .url");
ok(outputItem, "found a network message");
document.addEventListener("popupshown", networkPanelShown, false);
@ -102,7 +102,7 @@ function networkPanelHidden(aEvent) {
info("jsterm execute 'document' callback");
HUD.jsterm.once("variablesview-open", onVariablesViewOpen);
let outputItem = msg.querySelector(".body a");
let outputItem = msg.querySelector(".message-body a");
ok(outputItem, "jsterm output message found");
// Send the mousedown and click events such that the property panel opens.

View File

@ -66,7 +66,7 @@ function consoleOpened(HUD) {
jsterm.execute("window._container", (msg) => {
jsterm.once("variablesview-fetched", testVariablesView.bind(null, HUD));
let anchor = msg.querySelector(".body a");
let anchor = msg.querySelector(".message-body a");
EventUtils.synthesizeMouse(anchor, 2, 2, {}, HUD.iframeWindow);
});
}

View File

@ -75,7 +75,7 @@ function testPropertyPanel()
jsterm.clearOutput();
jsterm.execute("document", (msg) => {
jsterm.once("variablesview-fetched", onVariablesViewReady);
let anchor = msg.querySelector(".body a");
let anchor = msg.querySelector(".message-body a");
EventUtils.synthesizeMouse(anchor, 2, 2, {}, gHUD.iframeWindow);
});
}

View File

@ -58,6 +58,7 @@ function onConsoleMessage(aResults) {
let isDisabled = !controller || !controller.isCommandEnabled(COMMAND_NAME);
ok(isDisabled, COMMAND_NAME + " should be disabled.");
outputNode.selectedItem.scrollIntoView();
waitForContextMenu(contextMenu, outputNode.selectedItem, () => {
let isHidden = contextMenu.querySelector(CONTEXT_MENU_ID).hidden;
ok(isHidden, CONTEXT_MENU_ID + " should be hidden.");
@ -117,6 +118,7 @@ function testOnNetActivity_contextmenu(msg) {
outputNode.focus();
HUD.ui.output.selectMessage(msg);
msg.scrollIntoView();
waitForContextMenu(contextMenu, msg, () => {
let isShown = !contextMenu.querySelector(CONTEXT_MENU_ID).hidden;
ok(isShown, CONTEXT_MENU_ID + " should be shown.");

View File

@ -1,8 +1,12 @@
/* vim:set ts=2 sw=2 sts=2 et: */
/* ***** BEGIN LICENSE BLOCK *****
* Any copyright is dedicated to the Public Domain.
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/
* ***** END LICENSE BLOCK ***** */
*/
// Test that message source links for js errors and console API calls open in
// the jsdebugger when clicked.
"use strict";
const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test" +
"/test-bug-766001-js-console-links.html";
@ -61,10 +65,8 @@ function test() {
EventUtils.sendMouseEvent({ type: "click" }, node);
});
yield hud.ui.once("source-in-debugger-opened", checkLine.bind(null, url, line));
}
yield hud.ui.once("source-in-debugger-opened");
function* checkLine(url, line) {
let toolbox = yield gDevTools.getToolbox(hud.target);
let {panelWin: { DebuggerView: view }} = toolbox.getPanel("jsdebugger");
is(view.Sources.selectedValue, url, "expected source url");

View File

@ -0,0 +1,81 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Test that the console API messages for console.error()/exception()/assert()
// include a stackframe. See bug 920116.
function test() {
let hud;
const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-console-api-stackframe.html";
const TEST_FILE = TEST_URI.substr(TEST_URI.lastIndexOf("/"));
Task.spawn(runner).then(finishTest);
function* runner() {
const {tab} = yield loadTab(TEST_URI);
hud = yield openConsole(tab);
const stack = [{
file: TEST_FILE,
fn: "thirdCall",
line: /\b2[123]\b/, // 21,22,23
}, {
file: TEST_FILE,
fn: "secondCall",
line: 16,
}, {
file: TEST_FILE,
fn: "firstCall",
line: 12,
}];
let results = yield waitForMessages({
webconsole: hud,
messages: [{
text: "foo-log",
category: CATEGORY_WEBDEV,
severity: SEVERITY_LOG,
collapsible: false,
}, {
text: "foo-error",
category: CATEGORY_WEBDEV,
severity: SEVERITY_ERROR,
collapsible: true,
stacktrace: stack,
}, {
text: "foo-exception",
category: CATEGORY_WEBDEV,
severity: SEVERITY_ERROR,
collapsible: true,
stacktrace: stack,
}, {
text: "foo-assert",
category: CATEGORY_WEBDEV,
severity: SEVERITY_ERROR,
collapsible: true,
stacktrace: stack,
}],
});
let elem = [...results[1].matched][0];
ok(elem, "message element");
let msg = elem._messageObject;
ok(msg, "message object");
ok(msg.collapsed, "message is collapsed");
msg.toggleDetails();
ok(!msg.collapsed, "message is not collapsed");
msg.toggleDetails();
ok(msg.collapsed, "message is collapsed");
yield closeConsole(tab);
}
}

View File

@ -59,7 +59,7 @@ function test() {
let msg = [...result.matched][0];
ok(msg, "message element");
let span = msg.querySelector(".body span[style]");
let span = msg.querySelector(".message-body span[style]");
ok(span, "span element");
info("span textContent is: " + span.textContent);

View File

@ -30,7 +30,7 @@ function checkResult(msg, desc) {
category: CATEGORY_OUTPUT,
}],
}).then(([result]) => {
let node = [...result.matched][0].querySelector(".body");
let node = [...result.matched][0].querySelector(".message-body");
if (typeof msg == "string") {
is(node.textContent.trim(), msg,
"correct message shown for " + desc);

View File

@ -3,6 +3,8 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
let {gDevTools} = Cu.import("resource:///modules/devtools/gDevTools.jsm", {});
let {console} = Cu.import("resource://gre/modules/devtools/Console.jsm", {});
let {Promise: promise} = Cu.import("resource://gre/modules/Promise.jsm", {});
@ -882,6 +884,8 @@ function openDebugger(aOptions = {})
* message.
* - longString: boolean, set to |true} to match long strings in the
* message.
* - collapsible: boolean, set to |true| to match messages that can
* be collapsed/expanded.
* - type: match messages that are instances of the given object. For
* example, you can point to Messages.NavigationMarker to match any
* such message.
@ -890,6 +894,8 @@ function openDebugger(aOptions = {})
* - source: object of the shape { url, line }. This is used to
* match the source URL and line number of the error message or
* console API call.
* - stacktrace: array of objects of the form { file, fn, line } that
* can match frames in the stacktrace associated with the message.
* - groupDepth: number used to check the depth of the message in
* a group.
* - url: URL to match for network requests.
@ -918,7 +924,7 @@ function waitForMessages(aOptions)
function checkText(aRule, aText)
{
let result;
let result = false;
if (Array.isArray(aRule)) {
result = aRule.every((s) => checkText(s, aText));
}
@ -928,6 +934,9 @@ function waitForMessages(aOptions)
else if (aRule instanceof RegExp) {
result = aRule.test(aText);
}
else {
result = aRule == aText;
}
return result;
}
@ -940,41 +949,18 @@ function waitForMessages(aOptions)
return false;
}
let frame = aElement.querySelector(".stacktrace li:first-child");
if (trace.file) {
let file = frame.querySelector(".message-location").title;
if (!checkText(trace.file, file)) {
ok(false, "console.trace() message is missing the file name: " +
trace.file);
displayErrorContext(aRule, aElement);
return false;
}
}
if (trace.fn) {
let fn = frame.querySelector(".function").textContent;
if (!checkText(trace.fn, fn)) {
ok(false, "console.trace() message is missing the function name: " +
trace.fn);
displayErrorContext(aRule, aElement);
return false;
}
}
if (trace.line) {
let line = frame.querySelector(".message-location").sourceLine;
if (!checkText(trace.line, line)) {
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;
aRule.type = Messages.ConsoleTrace;
if (!aRule.stacktrace && typeof trace == "object" && trace !== true) {
if (Array.isArray(trace)) {
aRule.stacktrace = trace;
} else {
aRule.stacktrace = [trace];
}
}
return true;
}
@ -1058,6 +1044,66 @@ function waitForMessages(aOptions)
return true;
}
function checkCollapsible(aRule, aElement)
{
let msg = aElement._messageObject;
if (!msg || !!msg.collapsible != aRule.collapsible) {
return false;
}
return true;
}
function checkStacktrace(aRule, aElement)
{
let stack = aRule.stacktrace;
let frames = aElement.querySelectorAll(".stacktrace > li");
if (!frames.length) {
return false;
}
for (let i = 0; i < stack.length; i++) {
let frame = frames[i];
let expected = stack[i];
if (!frame) {
ok(false, "expected frame #" + i + " but didnt find it");
return false;
}
if (expected.file) {
let file = frame.querySelector(".message-location").title;
if (!checkText(expected.file, file)) {
ok(false, "frame #" + i + " does not match file name: " +
expected.file);
displayErrorContext(aRule, aElement);
return false;
}
}
if (expected.fn) {
let fn = frame.querySelector(".function").textContent;
if (!checkText(expected.fn, fn)) {
ok(false, "frame #" + i + " does not match the function name: " +
expected.fn);
displayErrorContext(aRule, aElement);
return false;
}
}
if (expected.line) {
let line = frame.querySelector(".message-location").sourceLine;
if (!checkText(expected.line, line)) {
ok(false, "frame #" + i + " does not match the line number: " +
expected.line);
displayErrorContext(aRule, aElement);
return false;
}
}
}
return true;
}
function checkMessage(aRule, aElement)
{
let elemText = aElement.textContent;
@ -1094,6 +1140,10 @@ function waitForMessages(aOptions)
return false;
}
if ("collapsible" in aRule && !checkCollapsible(aRule, aElement)) {
return false;
}
let partialMatch = !!(aRule.consoleTrace || aRule.consoleTime ||
aRule.consoleTimeEnd);
@ -1129,6 +1179,18 @@ function waitForMessages(aOptions)
return false;
}
if (aRule.text) {
partialMatch = true;
}
if (aRule.stacktrace && !checkStacktrace(aRule, aElement)) {
if (partialMatch) {
ok(false, "failed to match stacktrace for rule: " + displayRule(aRule));
displayErrorContext(aRule, aElement);
}
return false;
}
if (aRule.category == CATEGORY_NETWORK && "url" in aRule &&
!checkText(aRule.url, aElement.url)) {
return false;
@ -1166,7 +1228,7 @@ function waitForMessages(aOptions)
}
if ("objects" in aRule) {
let clickables = aElement.querySelectorAll(".body a");
let clickables = aElement.querySelectorAll(".message-body a");
if (aRule.objects != !!clickables[0]) {
if (partialMatch) {
is(!!clickables[0], aRule.objects,
@ -1410,7 +1472,8 @@ function checkOutputForInputs(hud, inputTests)
function checkObjectClick(entry, msg)
{
let body = msg.querySelector(".body a") || msg.querySelector(".body");
let body = msg.querySelector(".message-body a") ||
msg.querySelector(".message-body");
ok(body, "the message body");
let deferred = promise.defer();

View File

@ -3,6 +3,8 @@
* http://creativecommons.org/publicdomain/zero/1.0/
*/
window.addEventListener("load", function() {
function onLoad123() {
console.log("Blah Blah");
}, false);
}
window.addEventListener("load", onLoad123, false);

View File

@ -0,0 +1,32 @@
<!DOCTYPE HTML>
<html dir="ltr" lang="en">
<head>
<meta charset="utf8">
<!--
- Any copyright is dedicated to the Public Domain.
- http://creativecommons.org/publicdomain/zero/1.0/
-->
<title>Test for bug 920116 - stacktraces for console API messages</title>
<script>
function firstCall() {
secondCall();
}
function secondCall() {
thirdCall();
}
function thirdCall() {
console.log("foo-log");
console.error("foo-error");
console.exception("foo-exception");
console.assert("red" == "blue", "foo-assert");
}
window.onload = firstCall;
</script>
</head>
<body>
<p>Hello world!</p>
</body>
</html>

View File

@ -1373,7 +1373,7 @@ WebConsoleFrame.prototype = {
aScriptError.timeStamp);
// Select the body of the message node that is displayed in the console
let msgBody = node.getElementsByClassName("body")[0];
let msgBody = node.getElementsByClassName("message-body")[0];
// Add the more info link node to messages that belong to certain categories
this.addMoreInfoLink(msgBody, aScriptError);
@ -2461,7 +2461,7 @@ WebConsoleFrame.prototype = {
// Create the message body, which contains the actual text of the message.
let bodyNode = this.document.createElementNS(XHTML_NS, "span");
bodyNode.className = "body devtools-monospace";
bodyNode.className = "message-body-wrapper message-body devtools-monospace";
// Store the body text, since it is needed later for the variables view.
let body = aBody;
@ -2608,7 +2608,9 @@ WebConsoleFrame.prototype = {
locationNode.href = isScratchpad || !fullURL ? "#" : fullURL;
locationNode.draggable = false;
locationNode.target = aTarget;
if (aTarget) {
locationNode.target = aTarget;
}
locationNode.setAttribute("title", aSourceURL);
locationNode.className = "message-location theme-link devtools-monospace";

View File

@ -222,3 +222,8 @@ openNodeInInspector=Click to select the node in the inspector
# LOCALIZATION NOTE (cdFunctionInvalidArgument): the text that is displayed when
# cd() is invoked with an invalid argument.
cdFunctionInvalidArgument=Cannot cd() to the given window. Invalid argument.
# LOCALIZATION NOTE (messageToggleDetails): the text that is displayed when
# you hover the arrow for expanding/collapsing the message details. For
# console.error() and other messages we show the stacktrace.
messageToggleDetails=Show/hide message details.

View File

@ -38,10 +38,8 @@ a {
width: 8px;
}
.message > .body {
.message > .message-body-wrapper {
flex: 1 1 100%;
white-space: pre-wrap;
word-wrap: break-word;
margin-top: 4px;
}
@ -71,7 +69,7 @@ a {
align-self: flex-start;
justify-content: flex-end;
width: 10em;
margin-top: 4px;
margin: 3px 0;
color: -moz-nativehyperlinktext;
text-decoration: none;
white-space: nowrap;
@ -92,6 +90,20 @@ a {
flex: 0 0 auto;
}
.message-flex-body {
display: flex;
}
.message-body {
white-space: pre-wrap;
word-wrap: break-word;
}
.message-flex-body > .message-body {
display: block;
flex: 1 1 auto;
}
.jsterm-input-container {
border-top-width: 1px;
border-top-style: solid;
@ -153,6 +165,14 @@ a {
border-color: #777;
}
.theme-light .message[severity=error] {
background-color: rgba(255, 150, 150, 0.3);
}
.theme-dark .message[severity=error] {
background-color: rgba(255, 100, 100, 0.3);
}
.message[category=network] > .icon {
-moz-border-start: solid #000 6px;
}
@ -161,7 +181,7 @@ a {
background-image: -moz-image-rect(url(chrome://browser/skin/devtools/webconsole.png), 0, 16, 8, 8);
}
.message[category=network] > .body {
.message[category=network] > .message-body {
display: flex;
}
@ -284,7 +304,7 @@ a {
overflow-x: hidden;
}
.inlined-variables-view .body {
.inlined-variables-view .message-body {
display: flex;
flex-direction: column;
}
@ -340,27 +360,35 @@ a {
text-decoration: none;
}
.consoleTrace .body > div {
display: flex;
margin-bottom: 5px;
}
.consoleTrace .title {
display: block;
flex: 1 1 auto;
}
.stacktrace {
display: none;
list-style: none;
padding: 0 1em 0 1.5em;
margin: 0;
margin: 5px 0 0 0;
max-height: 10em;
overflow-y: auto;
border: 1px solid rgba(128, 128, 128, .5);
border: 1px solid rgb(200,200,200);
border-radius: 3px;
}
.theme-light .message[severity=error] .stacktrace {
background-color: rgba(255, 255, 255, 0.5);
}
.theme-dark .message[severity=error] .stacktrace {
background-color: rgba(0, 0, 0, 0.5);
}
.message[open] .stacktrace {
display: block;
}
.message .theme-twisty {
display: inline-block;
vertical-align: middle;
margin: 2px 3px 0 0;
}
.stacktrace li {
display: flex;
margin: 0;