Bug 1298225 - Format clipboard text of console stack traces into multiple lines r?bgrins
MozReview-Commit-ID: HkCFEwjhCwx
--- a/devtools/client/shared/components/frame.js
+++ b/devtools/client/shared/components/frame.js
@@ -171,17 +171,18 @@ module.exports = createClass({
let functionDisplayName = frame.functionDisplayName;
if (!functionDisplayName && showAnonymousFunctionName) {
functionDisplayName = webl10n.getStr("stacktrace.anonymousFunction");
}
if (functionDisplayName) {
elements.push(
dom.span({ className: "frame-link-function-display-name" },
- functionDisplayName)
+ functionDisplayName),
+ " "
);
}
}
let displaySource = showFullSourceUrl ? long : short;
if (isSourceMapped) {
displaySource = getSourceMappedFile(displaySource);
} else if (showEmptyPathAsHost && (displaySource === "" || displaySource === "/")) {
@@ -231,14 +232,14 @@ module.exports = createClass({
} else {
sourceEl = dom.span({
className: "frame-link-source",
}, sourceInnerEl);
}
elements.push(sourceEl);
if (showHost && host) {
- elements.push(dom.span({ className: "frame-link-host" }, host));
+ elements.push(" ", dom.span({ className: "frame-link-host" }, host));
}
return dom.span(attributes, ...elements);
}
});
--- a/devtools/client/shared/components/stack-trace.js
+++ b/devtools/client/shared/components/stack-trace.js
@@ -37,32 +37,32 @@ const StackTrace = createClass({
},
render() {
let { stacktrace, onViewSourceInDebugger } = this.props;
let frames = [];
stacktrace.forEach(s => {
if (s.asyncCause) {
- frames.push(AsyncFrame({
+ frames.push("\t", AsyncFrame({
asyncCause: s.asyncCause
- }));
+ }), "\n");
}
- frames.push(Frame({
+ frames.push("\t", Frame({
frame: {
functionDisplayName: s.functionName,
source: s.filename.split(" -> ").pop(),
line: s.lineNumber,
column: s.columnNumber,
},
showFunctionName: true,
showAnonymousFunctionName: true,
showFullSourceUrl: true,
onClick: onViewSourceInDebugger
- }));
+ }), "\n");
});
return dom.div({ className: "stack-trace" }, frames);
}
});
module.exports = StackTrace;
--- a/devtools/client/shared/components/test/mochitest/test_stack-trace.html
+++ b/devtools/client/shared/components/test/mochitest/test_stack-trace.html
@@ -40,17 +40,19 @@ window.onload = function() {
};
let trace = ReactDOM.render(StackTrace(props), window.document.body);
yield forceRender(trace);
let traceEl = trace.getDOMNode();
ok(traceEl, "Rendered StackTrace has an element");
- let frameEls = traceEl.childNodes;
+ // Get the child nodes and filter out the text-only whitespace ones
+ let frameEls = Array.from(traceEl.childNodes)
+ .filter(n => n.className.includes("frame"));
ok(frameEls, "Rendered StackTrace has frames");
is(frameEls.length, 3, "StackTrace has 3 frames");
// Check the top frame, function name should be anonymous
checkFrameString({
el: frameEls[0],
functionName: "<anonymous>",
source: "http://myfile.com/mahscripts.js",
@@ -71,13 +73,21 @@ window.onload = function() {
functionName: "loadFunc",
source: "http://myfile.com/loadee.js",
file: "http://myfile.com/loadee.js",
line: 10,
column: null,
shouldLink: true,
tooltip: "View source in Debugger → http://myfile.com/loadee.js:10",
});
+
+ // Check the tabs and newlines in the stack trace textContent
+ let traceText = traceEl.textContent;
+ let traceLines = traceText.split("\n");
+ ok(traceLines.length > 0, "There are newlines in the stack trace text");
+ is(traceLines.pop(), "", "There is a newline at the end of the stack trace text");
+ is(traceLines.length, 3, "The stack trace text has 3 lines");
+ ok(traceLines.every(l => l[0] == "\t"), "Every stack trace line starts with tab");
});
}
</script>
</body>
</html>
--- a/devtools/client/webconsole/console-output.js
+++ b/devtools/client/webconsole/console-output.js
@@ -930,18 +930,16 @@ Messages.Simple.prototype = extend(Messa
twisty.addEventListener("click", this._onClickCollapsible);
this.element.appendChild(twisty);
this.collapsible = true;
this.element.setAttribute("collapsible", true);
}
this.element.appendChild(body);
- this.element.appendChild(this.document.createTextNode("\n"));
-
this.element.clipboardText = this.element.textContent;
if (this.private) {
this.element.setAttribute("private", true);
}
// TODO: handle object releasing in a more elegant way once all console
// messages use the new API - bug 778766.
@@ -988,22 +986,26 @@ Messages.Simple.prototype = extend(Messa
// do this before repeatNode is rendered - it has no effect afterwards
this._repeatID.textContent += "|" + container.textContent;
let repeatNode = this._renderRepeatNode();
let location = this._renderLocation();
if (repeatNode) {
+ bodyFlex.appendChild(this.document.createTextNode(" "));
bodyFlex.appendChild(repeatNode);
}
if (location) {
+ bodyFlex.appendChild(this.document.createTextNode(" "));
bodyFlex.appendChild(location);
}
+ bodyFlex.appendChild(this.document.createTextNode("\n"));
+
if (this.stack) {
this._attachment = new Widgets.Stacktrace(this, this.stack).render().element;
}
if (this._attachment) {
bodyWrapper.appendChild(this._attachment);
}
--- a/devtools/client/webconsole/test/browser_console_copy_entire_message_context_menu.js
+++ b/devtools/client/webconsole/test/browser_console_copy_entire_message_context_menu.js
@@ -1,69 +1,97 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
+/* globals goDoCommand */
+
"use strict";
// Test copying of the entire console message when right-clicked
// with no other text selected. See Bug 1100562.
-function test() {
+add_task(function* () {
let hud;
let outputNode;
let contextMenu;
- const TEST_URI = "http://example.com/browser/devtools/client/webconsole/" +
- "test/test-console.html";
-
- Task.spawn(runner).then(finishTest);
+ const TEST_URI = "http://example.com/browser/devtools/client/webconsole/test/test-console.html";
- function* runner() {
- const {tab} = yield loadTab(TEST_URI);
- hud = yield openConsole(tab);
- outputNode = hud.outputNode;
- contextMenu = hud.iframeWindow.document.getElementById("output-contextmenu");
+ const { tab, browser } = yield loadTab(TEST_URI);
+ hud = yield openConsole(tab);
+ outputNode = hud.outputNode;
+ contextMenu = hud.iframeWindow.document.getElementById("output-contextmenu");
+
+ registerCleanupFunction(() => {
+ hud = outputNode = contextMenu = null;
+ });
- registerCleanupFunction(() => {
- hud = outputNode = contextMenu = null;
- });
+ hud.jsterm.clearOutput();
- hud.jsterm.clearOutput();
- content.console.log("bug 1100562");
+ yield ContentTask.spawn(browser, {}, function* () {
+ let button = content.document.getElementById("testTrace");
+ button.click();
+ });
- let [results] = yield waitForMessages({
- webconsole: hud,
- messages: [{
+ let results = yield waitForMessages({
+ webconsole: hud,
+ messages: [
+ {
text: "bug 1100562",
category: CATEGORY_WEBDEV,
severity: SEVERITY_LOG,
- }]
- });
+ lines: 1,
+ },
+ {
+ name: "console.trace output",
+ consoleTrace: true,
+ lines: 3,
+ },
+ ]
+ });
- outputNode.focus();
- let message = [...results.matched][0];
+ outputNode.focus();
- yield waitForContextMenu(contextMenu, message, copyFromPopup,
- testContextMenuCopy);
+ for (let result of results) {
+ let message = [...result.matched][0];
- function copyFromPopup() {
+ yield waitForContextMenu(contextMenu, message, () => {
let copyItem = contextMenu.querySelector("#cMenu_copy");
copyItem.doCommand();
let controller = top.document.commandDispatcher
.getControllerForCommand("cmd_copy");
is(controller.isCommandEnabled("cmd_copy"), true, "cmd_copy is enabled");
- }
+ });
+
+ let clipboardText;
+
+ yield waitForClipboardPromise(
+ () => goDoCommand("cmd_copy"),
+ (str) => {
+ clipboardText = str;
+ return message.textContent == clipboardText;
+ }
+ );
+
+ ok(clipboardText, "Clipboard text was found and saved");
- function testContextMenuCopy() {
- waitForClipboard((str) => {
- return message.textContent.trim() == str.trim();
- }, () => {
- goDoCommand("cmd_copy");
- }, () => {}, () => {}
- );
+ let lines = clipboardText.split("\n");
+ ok(lines.length > 0, "There is at least one newline in the message");
+ is(lines.pop(), "", "There is a newline at the end");
+ is(lines.length, result.lines, `There are ${result.lines} lines in the message`);
+
+ // Test the first line for "timestamp message repeat file:line"
+ let firstLine = lines.shift();
+ ok(/^[\d:.]+ .+ \d+ .+:\d+$/.test(firstLine),
+ "The message's first line has the right format");
+
+ // Test the remaining lines (stack trace) for "TABfunctionName sourceURL:line:col"
+ for (let line of lines) {
+ ok(/^\t.+ .+:\d+:\d+$/.test(line), "The stack trace line has the right format");
}
+ }
- yield closeConsole(tab);
- }
-}
+ yield closeConsole(tab);
+ yield finishTest();
+});
--- a/devtools/client/webconsole/test/test-console.html
+++ b/devtools/client/webconsole/test/test-console.html
@@ -8,20 +8,27 @@
};
function test() {
var str = "Dolske Digs Bacon, Now and Forevermore."
for (var i=0; i < 5; i++) {
console.log(str);
}
}
+
+ function testTrace() {
+ console.log("bug 1100562");
+ console.trace();
+ }
+
console.info("INLINE SCRIPT:");
test();
console.warn("I'm warning you, he will eat up all yr bacon.");
console.error("Error Message");
</script>
</head>
<body>
<h1 id="header">Heads Up Display Demo</h1>
<button onclick="test();">Log stuff about Dolske</button>
+ <button id="testTrace" onclick="testTrace();">Log stuff with stacktrace</button>
<div id="myDiv"></div>
</body>
</html>