author | Mihai Sucan <mihai.sucan@gmail.com> |
Thu, 23 Jan 2014 23:37:32 +0200 | |
changeset 181107 | 2552d554372d96baf3e26ddaf66468886b51cbb9 |
parent 181106 | f9a4e354878bc5503def366a4c6275f4967798fd |
child 181108 | 3977d57df6568053fa6d19e092738ca5d183078b |
push id | 3343 |
push user | ffxbld |
push date | Mon, 17 Mar 2014 21:55:32 +0000 |
treeherder | mozilla-beta@2f7d3415f79f [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | robcee |
bugs | 939783 |
milestone | 29.0a1 |
first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
--- a/browser/devtools/scratchpad/test/browser_scratchpad_wrong_window_focus.js +++ b/browser/devtools/scratchpad/test/browser_scratchpad_wrong_window_focus.js @@ -43,17 +43,17 @@ function test() } function testFocus(sw, hud) { let sp = sw.Scratchpad; function onMessage(event, messages) { let msg = [...messages][0]; - var loc = msg.querySelector(".location"); + var loc = msg.querySelector(".message-location"); ok(loc, "location element exists"); is(loc.textContent.trim(), sw.Scratchpad.uniqueName + ":1", "location value is correct"); sw.addEventListener("focus", function onFocus() { sw.removeEventListener("focus", onFocus, true); let win = Services.wm.getMostRecentWindow("devtools:scratchpad");
--- a/browser/devtools/webconsole/console-output.js +++ b/browser/devtools/webconsole/console-output.js @@ -141,16 +141,28 @@ ConsoleOutput.prototype = { * Getter for the debugger WebConsoleClient. * @type object */ get webConsoleClient() { return this.owner.webConsoleClient; }, /** + * Release an actor. + * + * @private + * @param string actorId + * The actor ID you want to release. + */ + _releaseObject: function(actorId) + { + this.owner._releaseObject(actorId); + }, + + /** * Add a message to output. * * @param object ...args * Any number of Message objects. * @return this */ addMessage: function(...args) { @@ -842,17 +854,17 @@ Messages.Simple.prototype = Heritage.ext _renderRepeatNode: function() { if (!this._filterDuplicates) { return null; } let repeatNode = this.document.createElementNS(XHTML_NS, "span"); repeatNode.setAttribute("value", "1"); - repeatNode.className = "repeats"; + repeatNode.className = "message-repeats"; repeatNode.textContent = 1; repeatNode._uid = this.getRepeatID(); return repeatNode; }, /** * Render the message source location DOM element. * @private @@ -1072,16 +1084,144 @@ Messages.ConsoleGeneric = function(packe Messages.ConsoleGeneric.prototype = Heritage.extend(Messages.Extended.prototype, { _renderBodyPieceSeparator: function() { return this.document.createTextNode(" "); }, }); // Messages.ConsoleGeneric.prototype +/** + * The ConsoleTrace message is used for console.trace() calls. + * + * @constructor + * @extends Messages.Simple + * @param object packet + * The Console API call packet received from the server. + */ +Messages.ConsoleTrace = function(packet) +{ + let options = { + className: "consoleTrace cm-s-mozilla", + timestamp: packet.timeStamp, + category: "webdev", + severity: CONSOLE_API_LEVELS_TO_SEVERITIES[packet.level], + private: packet.private, + filterDuplicates: true, + location: { + url: packet.filename, + line: packet.lineNumber, + }, + }; + + this._renderStack = this._renderStack.bind(this); + Messages.Simple.call(this, this._renderStack, options); + + this._repeatID.consoleApiLevel = packet.level; + this._stacktrace = this._repeatID.stacktrace = packet.stacktrace; + this._arguments = packet.arguments; +}; + +Messages.ConsoleTrace.prototype = Heritage.extend(Messages.Simple.prototype, +{ + /** + * Holds the stackframes received from the server. + * + * @private + * @type array + */ + _stacktrace: null, + + /** + * Holds the arguments the content script passed to the console.trace() + * method. This array is cleared when the message is initialized, and + * associated actors are released. + * + * @private + * @type array + */ + _arguments: null, + + init: function() + { + let result = Messages.Simple.prototype.init.apply(this, arguments); + + // We ignore console.trace() arguments. Release object actors. + if (Array.isArray(this._arguments)) { + for (let arg of this._arguments) { + if (WebConsoleUtils.isActorGrip(arg)) { + this.output._releaseObject(arg.actor); + } + } + } + this._arguments = null; + + return result; + }, + + /** + * Render the stack frames. + * + * @private + * @return DOMElement + */ + _renderStack: function() + { + let cmvar = this.document.createElementNS(XHTML_NS, "span"); + cmvar.className = "cm-variable"; + cmvar.textContent = "console"; + + let cmprop = this.document.createElementNS(XHTML_NS, "span"); + cmprop.className = "cm-property"; + cmprop.textContent = "trace"; + + let title = this.document.createElementNS(XHTML_NS, "span"); + title.className = "title devtools-monospace"; + title.appendChild(cmvar); + title.appendChild(this.document.createTextNode(".")); + title.appendChild(cmprop); + title.appendChild(this.document.createTextNode("():")); + + let repeatNode = Messages.Simple.prototype._renderRepeatNode.call(this); + let location = Messages.Simple.prototype._renderLocation.call(this); + if (location) { + location.target = "jsdebugger"; + } + + let widget = new Widgets.Stacktrace(this, this._stacktrace).render(); + + let body = this.document.createElementNS(XHTML_NS, "div"); + body.appendChild(title); + if (repeatNode) { + body.appendChild(repeatNode); + } + if (location) { + body.appendChild(location); + } + body.appendChild(this.document.createTextNode("\n")); + + let frag = this.document.createDocumentFragment(); + frag.appendChild(body); + frag.appendChild(widget.element); + + return frag; + }, + + _renderBody: function() + { + let body = Messages.Simple.prototype._renderBody.apply(this, arguments); + body.classList.remove("devtools-monospace"); + return body; + }, + + // no-op for the message location and .repeats elements. + // |this._renderStack| handles customized message output. + _renderLocation: function() { }, + _renderRepeatNode: function() { }, +}); // Messages.ConsoleTrace.prototype let Widgets = {}; /** * The base widget class. * * @constructor * @param object message @@ -1349,16 +1489,101 @@ Widgets.LongString.prototype = Heritage. category: "output", severity: "warning", }); this.output.addMessage(msg); }, }); // Widgets.LongString.prototype +/** + * The stacktrace widget. + * + * @constructor + * @extends Widgets.BaseWidget + * @param object message + * The owning message. + * @param array stacktrace + * The stacktrace to display, array of frames as supplied by the server, + * over the remote protocol. + */ +Widgets.Stacktrace = function(message, stacktrace) +{ + Widgets.BaseWidget.call(this, message); + this.stacktrace = stacktrace; +}; + +Widgets.Stacktrace.prototype = Heritage.extend(Widgets.BaseWidget.prototype, +{ + /** + * The stackframes received from the server. + * @type array + */ + stacktrace: null, + + render: function() + { + if (this.element) { + return this; + } + + let result = this.element = this.document.createElementNS(XHTML_NS, "ul"); + result.className = "stacktrace devtools-monospace"; + + for (let frame of this.stacktrace) { + result.appendChild(this._renderFrame(frame)); + } + + return this; + }, + + /** + * Render a frame object received from the server. + * + * @param object frame + * The stack frame to display. This object should have the following + * properties: functionName, filename and lineNumber. + * @return DOMElement + * The DOM element to display for the given frame. + */ + _renderFrame: function(frame) + { + let fn = this.document.createElementNS(XHTML_NS, "span"); + fn.className = "function"; + if (frame.functionName) { + let span = this.document.createElementNS(XHTML_NS, "span"); + span.className = "cm-variable"; + span.textContent = frame.functionName; + fn.appendChild(span); + fn.appendChild(this.document.createTextNode("()")); + } else { + fn.classList.add("cm-comment"); + fn.textContent = l10n.getStr("stacktrace.anonymousFunction"); + } + + let location = this.output.owner.createLocationNode(frame.filename, + frame.lineNumber, + "jsdebugger"); + + // .devtools-monospace sets font-size to 80%, however .body already has + // .devtools-monospace. If we keep it here, the location would be rendered + // smaller. + location.classList.remove("devtools-monospace"); + + let elem = this.document.createElementNS(XHTML_NS, "li"); + elem.appendChild(fn); + elem.appendChild(location); + elem.appendChild(this.document.createTextNode("\n")); + + return elem; + }, + +}); // Widgets.Stacktrace.prototype + + function gSequenceId() { return gSequenceId.n++; } gSequenceId.n = 0; exports.ConsoleOutput = ConsoleOutput; exports.Messages = Messages;
--- a/browser/devtools/webconsole/test/browser.ini +++ b/browser/devtools/webconsole/test/browser.ini @@ -98,16 +98,17 @@ support-files = test_bug_770099_bad_policy_uri.html^headers^ test_bug_770099_violation.html test_bug_770099_violation.html^headers^ test-autocomplete-in-stackframe.html testscript.js test-bug_923281_console_log_filter.html test-bug_923281_test1.js test-bug_923281_test2.js + test-bug_939783_console_trace_duplicates.html [browser_bug664688_sandbox_update_after_navigation.js] [browser_bug_638949_copy_link_location.js] [browser_bug_862916_console_dir_and_filter_off.js] [browser_bug_865288_repeat_different_objects.js] [browser_bug_865871_variables_view_close_on_esc_key.js] [browser_bug_869003_inspect_cross_domain_object.js] [browser_bug_871156_ctrlw_close_tab.js] @@ -252,8 +253,9 @@ run-if = os == "mac" [browser_webconsole_expandable_timestamps.js] [browser_webconsole_autocomplete_in_debugger_stackframe.js] [browser_webconsole_autocomplete_popup_close_on_tab_switch.js] [browser_webconsole_output_01.js] [browser_webconsole_output_02.js] [browser_webconsole_output_03.js] [browser_webconsole_output_04.js] [browser_webconsole_output_events.js] +[browser_webconsole_console_trace_duplicates.js]
--- a/browser/devtools/webconsole/test/browser_console_addonsdk_loader_exception.js +++ b/browser/devtools/webconsole/test/browser_console_addonsdk_loader_exception.js @@ -65,17 +65,17 @@ function test() onMessageFound(results); }); } function onMessageFound(results) { let msg = [...results[0].matched][0]; ok(msg, "message element found"); - let locationNode = msg.querySelector(".location"); + let locationNode = msg.querySelector(".message-location"); ok(locationNode, "message location element found"); let title = locationNode.getAttribute("title"); info("location node title: " + title); isnot(title.indexOf(" -> "), -1, "error comes from a subscript"); let viewSource = browserconsole.viewSource; let URL = null;
--- a/browser/devtools/webconsole/test/browser_console_error_source_click.js +++ b/browser/devtools/webconsole/test/browser_console_error_source_click.js @@ -56,17 +56,17 @@ function test() let viewSourceCalled = false; hud.viewSource = () => viewSourceCalled = true; for (let result of results) { viewSourceCalled = false; let msg = [...results[0].matched][0]; ok(msg, "message element found for: " + result.text); - let locationNode = msg.querySelector(".location"); + let locationNode = msg.querySelector(".message-location"); ok(locationNode, "message location element found"); EventUtils.synthesizeMouse(locationNode, 2, 2, {}, hud.iframeWindow); ok(viewSourceCalled, "view source opened"); } hud.viewSource = viewSource;
--- a/browser/devtools/webconsole/test/browser_webconsole_bug_585956_console_trace.js +++ b/browser/devtools/webconsole/test/browser_webconsole_bug_585956_console_trace.js @@ -1,49 +1,46 @@ /* vim:set ts=2 sw=2 sts=2 et: */ /* This Source Code Form is subject to the terms of the Mozilla Public * 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/. */ const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-bug-585956-console-trace.html"; function test() { - addTab("data:text/html;charset=utf8,<p>hello"); - browser.addEventListener("load", tabLoaded, true); + Task.spawn(runner).then(finishTest); - function tabLoaded() { - browser.removeEventListener("load", tabLoaded, true); + function* runner() { + let {tab} = yield loadTab("data:text/html;charset=utf8,<p>hello"); + let hud = yield openConsole(tab); - openConsole(null, function(hud) { - content.location = TEST_URI; + content.location = TEST_URI; - waitForMessages({ - webconsole: hud, - messages: [{ - name: "console.trace output", - consoleTrace: { - file: "test-bug-585956-console-trace.html", - fn: "window.foobar585956c", - }, - }], - }).then(performChecks); + let [result] = yield waitForMessages({ + webconsole: hud, + messages: [{ + name: "console.trace output", + consoleTrace: { + file: "test-bug-585956-console-trace.html", + fn: "window.foobar585956c", + }, + }], }); - } - function performChecks(results) { - let node = [...results[0].matched][0]; + let node = [...result.matched][0]; + ok(node, "found trace log node"); + + let obj = node._messageObject; + ok(obj, "console.trace message object"); // The expected stack trace object. let stacktrace = [ { filename: TEST_URI, lineNumber: 9, functionName: "window.foobar585956c", language: 2 }, { filename: TEST_URI, lineNumber: 14, functionName: "foobar585956b", language: 2 }, { filename: TEST_URI, lineNumber: 18, functionName: "foobar585956a", language: 2 }, { filename: TEST_URI, lineNumber: 21, functionName: null, language: 2 } ]; - ok(node, "found trace log node"); - ok(node._stacktrace, "found stacktrace object"); - is(node._stacktrace.toSource(), stacktrace.toSource(), "stacktrace is correct"); + ok(obj._stacktrace, "found stacktrace object"); + is(obj._stacktrace.toSource(), stacktrace.toSource(), "stacktrace is correct"); isnot(node.textContent.indexOf("bug-585956"), -1, "found file name"); - - finishTest(); } }
--- a/browser/devtools/webconsole/test/browser_webconsole_bug_642108_pruneTest.js +++ b/browser/devtools/webconsole/test/browser_webconsole_bug_642108_pruneTest.js @@ -69,17 +69,17 @@ function testCSSPruning(hudRef) { }], }).then(([result]) => { is(countMessageNodes(), LOG_LIMIT, "number of messages"); is(Object.keys(hudRef.ui._repeatNodes).length, LOG_LIMIT, "repeated nodes pruned from repeatNodes"); let msg = [...result.matched][0]; - let repeats = msg.querySelector(".repeats"); + let repeats = msg.querySelector(".message-repeats"); is(repeats.getAttribute("value"), 1, "repeated nodes pruned from repeatNodes (confirmed)"); finishTest(); }); }); }
--- a/browser/devtools/webconsole/test/browser_webconsole_bug_766001_JS_Console_in_Debugger.js +++ b/browser/devtools/webconsole/test/browser_webconsole_bug_766001_JS_Console_in_Debugger.js @@ -29,18 +29,18 @@ function test() { text: "Blah Blah", category: CATEGORY_WEBDEV, severity: SEVERITY_LOG, }], }); let exceptionMsg = [...exceptionRule.matched][0]; let consoleMsg = [...consoleRule.matched][0]; - let nodes = [exceptionMsg.querySelector(".location"), - consoleMsg.querySelector(".location")]; + let nodes = [exceptionMsg.querySelector(".message-location"), + consoleMsg.querySelector(".message-location")]; ok(nodes[0], ".location node for the exception message"); ok(nodes[1], ".location node for the console message"); for (let i = 0; i < nodes.length; i++) { yield checkClickOnNode(i, nodes[i]); yield gDevTools.showToolbox(hud.target, "webconsole"); }
--- a/browser/devtools/webconsole/test/browser_webconsole_bug_782653_CSS_links_in_Style_Editor.js +++ b/browser/devtools/webconsole/test/browser_webconsole_bug_782653_CSS_links_in_Style_Editor.js @@ -36,20 +36,20 @@ function testViewSource(aHud) { text: "'color'", category: CATEGORY_CSS, severity: SEVERITY_WARNING, }], }).then(([error1Rule, error2Rule]) => { let error1Msg = [...error1Rule.matched][0]; let error2Msg = [...error2Rule.matched][0]; - nodes = [error1Msg.querySelector(".location"), - error2Msg.querySelector(".location")]; - ok(nodes[0], ".location node for the first error"); - ok(nodes[1], ".location node for the second error"); + nodes = [error1Msg.querySelector(".message-location"), + error2Msg.querySelector(".message-location")]; + ok(nodes[0], ".message-location node for the first error"); + ok(nodes[1], ".message-location node for the second error"); let target = TargetFactory.forTab(gBrowser.selectedTab); let toolbox = gDevTools.getToolbox(target); toolbox.once("styleeditor-selected", (event, panel) => { StyleEditorUI = panel.UI; let count = 0; StyleEditorUI.on("editor-added", function() {
new file mode 100644 --- /dev/null +++ b/browser/devtools/webconsole/test/browser_webconsole_console_trace_duplicates.js @@ -0,0 +1,36 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * 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/. */ + +function test() { + const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-bug_939783_console_trace_duplicates.html"; + + Task.spawn(runner).then(finishTest); + + function* runner() { + const {tab} = yield loadTab("data:text/html;charset=utf8,<p>hello"); + const hud = yield openConsole(tab); + + content.location = TEST_URI; + + yield waitForMessages({ + webconsole: hud, + messages: [{ + name: "console.trace output for foo1()", + text: "foo1()", + repeats: 2, + consoleTrace: { + file: "test-bug_939783_console_trace_duplicates.html", + fn: "foo3()", + }, + }, { + name: "console.trace output for foo1b()", + text: "foo1b()", + consoleTrace: { + file: "test-bug_939783_console_trace_duplicates.html", + fn: "foo3()", + }, + }], + }); + } +}
--- a/browser/devtools/webconsole/test/browser_webconsole_scratchpad_panel_link.js +++ b/browser/devtools/webconsole/test/browser_webconsole_scratchpad_panel_link.js @@ -48,17 +48,17 @@ function runTests(aToolbox) webconsole: hud, messages: [{ text: "foobar-from-scratchpad" }] }); info("Clicking link to switch to and focus Scratchpad"); let [matched] = [...messages[0].matched]; ok(matched, "Found logged message from Scratchpad"); - let anchor = matched.querySelector("a.location"); + let anchor = matched.querySelector("a.message-location"); aToolbox.on("scratchpad-selected", function selected() { aToolbox.off("scratchpad-selected", selected); is(aToolbox.getCurrentPanel(), scratchpadPanel, "Clicking link switches to Scratchpad panel"); is(Services.ww.activeWindow, aToolbox.frame.ownerGlobal,
--- a/browser/devtools/webconsole/test/browser_webconsole_view_source.js +++ b/browser/devtools/webconsole/test/browser_webconsole_view_source.js @@ -42,17 +42,17 @@ function testViewSource(hud) { }], }).then(onMessage); }); }); function onMessage([result]) { let msg = [...result.matched][0]; ok(msg, "error message"); - let locationNode = msg.querySelector(".location"); + let locationNode = msg.querySelector(".message-location"); ok(locationNode, "location node"); Services.ww.registerNotification(observer); containsValue = Sources.containsValue; Sources.containsValue = () => { containsValueInvoked = true; return false;
--- a/browser/devtools/webconsole/test/head.js +++ b/browser/devtools/webconsole/test/head.js @@ -1,26 +1,22 @@ /* vim:set ts=2 sw=2 sts=2 et: */ /* This Source Code Form is subject to the terms of the Mozilla Public * 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/. */ -let WebConsoleUtils, TargetFactory, require; 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/commonjs/sdk/core/promise.js", {}); let {Task} = Cu.import("resource://gre/modules/Task.jsm", {}); +let {devtools} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}); +let {require, TargetFactory} = devtools; +let {Utils: WebConsoleUtils} = require("devtools/toolkit/webconsole/utils"); +let {Messages} = require("devtools/webconsole/console-output"); -(() => { - let {devtools} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}); - let utils = devtools.require("devtools/toolkit/webconsole/utils"); - TargetFactory = devtools.TargetFactory; - WebConsoleUtils = utils.Utils; - require = devtools.require; -})(); // promise._reportErrors = true; // please never leave me. let gPendingOutputTest = 0; // The various categories of messages. const CATEGORY_NETWORK = 0; const CATEGORY_CSS = 1; const CATEGORY_JS = 2; @@ -278,17 +274,17 @@ function dumpConsoles() * 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 = aMessage.textContent; - let repeats = aMessage.querySelector(".repeats"); + let repeats = aMessage.querySelector(".message-repeats"); if (repeats) { repeats = repeats.getAttribute("value"); } console.debug("id", aMessage.getAttribute("id"), "date", aMessage.timestamp, "class", aMessage.className, "category", aMessage.category, "severity", aMessage.severity, @@ -927,54 +923,54 @@ function waitForMessages(aOptions) return result; } function checkConsoleTrace(aRule, aElement) { let elemText = aElement.textContent; let trace = aRule.consoleTrace; - if (!checkText("Stack trace from ", elemText)) { + if (!checkText("console.trace():", elemText)) { return false; } - let clickable = aElement.querySelector(".body a"); - 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; + 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 && - !checkText(", function " + trace.fn + ", ", elemText)) { - ok(false, "console.trace() message is missing the function name: " + - trace.fn); - 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 && - !checkText(", line " + trace.line + ".", elemText)) { - ok(false, "console.trace() message is missing the line number: " + - trace.line); - 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; return true; } function checkConsoleTime(aRule, aElement) { let elemText = aElement.textContent; let time = aRule.consoleTime; @@ -1033,17 +1029,17 @@ function waitForMessages(aOptions) aRule.category = CATEGORY_WEBDEV; aRule.severity = SEVERITY_LOG; return true; } function checkSource(aRule, aElement) { - let location = aElement.querySelector(".location"); + let location = aElement.querySelector(".message-location"); if (!location) { return false; } if (!checkText(aRule.source.url, location.getAttribute("title"))) { return false; } @@ -1085,26 +1081,33 @@ function waitForMessages(aOptions) if (aRule.consoleGroup && !checkConsoleGroup(aRule, aElement)) { return false; } if (aRule.source && !checkSource(aRule, aElement)) { return false; } + let partialMatch = !!(aRule.consoleTrace || aRule.consoleTime || + aRule.consoleTimeEnd); + // The rule tries to match the newer types of messages, based on their // object constructor. - if (aRule.type && (!aElement._messageObject || - !(aElement._messageObject instanceof aRule.type))) { - return false; + if (aRule.type) { + if (!aElement._messageObject || + !(aElement._messageObject instanceof aRule.type)) { + if (partialMatch) { + ok(false, "message type for rule: " + displayRule(aRule)); + displayErrorContext(aRule, aElement); + } + return false; + } + partialMatch = true; } - let partialMatch = !!(aRule.consoleTrace || aRule.consoleTime || - aRule.consoleTimeEnd || aRule.type); - if ("category" in aRule && aElement.category != aRule.category) { if (partialMatch) { is(aElement.category, aRule.category, "message category for rule: " + displayRule(aRule)); displayErrorContext(aRule, aElement); } return false; } @@ -1119,17 +1122,17 @@ function waitForMessages(aOptions) } if (aRule.category == CATEGORY_NETWORK && "url" in aRule && !checkText(aRule.url, aElement.url)) { return false; } if ("repeats" in aRule) { - let repeats = aElement.querySelector(".repeats"); + let repeats = aElement.querySelector(".message-repeats"); if (!repeats || repeats.getAttribute("value") != aRule.repeats) { return false; } } if ("groupDepth" in aRule) { let timestamp = aElement.querySelector(".timestamp"); let indent = (GROUP_INDENT * aRule.groupDepth) + "px"; @@ -1175,17 +1178,17 @@ function waitForMessages(aOptions) aRule.matched.add(aElement); return aRule.matched.size == count; } function onMessagesAdded(aEvent, aNewElements) { for (let elem of aNewElements) { - let location = elem.querySelector(".location"); + let location = elem.querySelector(".message-location"); if (location) { let url = location.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; } }
new file mode 100644 --- /dev/null +++ b/browser/devtools/webconsole/test/test-bug_939783_console_trace_duplicates.html @@ -0,0 +1,35 @@ +<!DOCTYPE html> +<html lang="en"> + <head> + <meta charset="utf-8"> + <title>Web Console test for bug 939783 - different console.trace() calls + wrongly filtered as duplicates</title> + <!-- Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ --> +<script type="application/javascript"> +function foo1() { + foo2(); +} + +function foo1b() { + foo2(); +} + +function foo2() { + foo3(); +} + +function foo3() { + console.trace(); +} + +foo1(); foo1(); +foo1b(); + +</script> + </head> + <body> + <p>Web Console test for bug 939783 - different console.trace() calls + wrongly filtered as duplicates</p> + </body> +</html>
--- a/browser/devtools/webconsole/webconsole.js +++ b/browser/devtools/webconsole/webconsole.js @@ -1051,17 +1051,17 @@ WebConsoleFrame.prototype = { * @param nsIDOMNode aOriginal * The Original Node. The one being merged into. * @param nsIDOMNode aFiltered * The node being filtered out because it is repeated. */ mergeFilteredMessageNode: function WCF_mergeFilteredMessageNode(aOriginal, aFiltered) { - let repeatNode = aOriginal.getElementsByClassName("repeats")[0]; + let repeatNode = aOriginal.getElementsByClassName("message-repeats")[0]; if (!repeatNode) { return; // no repeat node, return early. } let occurrences = parseInt(repeatNode.getAttribute("value")) + 1; repeatNode.setAttribute("value", occurrences); repeatNode.textContent = occurrences; let str = l10n.getStr("messageRepeats.tooltip2"); @@ -1076,17 +1076,17 @@ WebConsoleFrame.prototype = { * @param nsIDOMNode aNode * The message node to be filtered or not. * @returns nsIDOMNode|null * Returns the duplicate node if the message was filtered, null * otherwise. */ _filterRepeatedMessage: function WCF__filterRepeatedMessage(aNode) { - let repeatNode = aNode.getElementsByClassName("repeats")[0]; + let repeatNode = aNode.getElementsByClassName("message-repeats")[0]; if (!repeatNode) { return null; } let uid = repeatNode._uid; let dupeNode = null; if (aNode.category == CATEGORY_CSS || @@ -1100,17 +1100,17 @@ WebConsoleFrame.prototype = { aNode.category == CATEGORY_JS) && aNode.category != CATEGORY_NETWORK && !aNode.classList.contains("inlined-variables-view")) { let lastMessage = this.outputNode.lastChild; if (!lastMessage) { return null; } - let lastRepeatNode = lastMessage.getElementsByClassName("repeats")[0]; + let lastRepeatNode = lastMessage.getElementsByClassName("message-repeats")[0]; if (lastRepeatNode && lastRepeatNode._uid == uid) { dupeNode = lastMessage; } } if (dupeNode) { this.mergeFilteredMessageNode(dupeNode, aNode); return dupeNode; @@ -1186,58 +1186,31 @@ WebConsoleFrame.prototype = { case "error": case "exception": case "assert": case "debug": { let msg = new Messages.ConsoleGeneric(aMessage); node = msg.init(this.output).render().element; break; } + case "trace": { + let msg = new Messages.ConsoleTrace(aMessage); + node = msg.init(this.output).render().element; + break; + } case "dir": { body = { arguments: args }; let clipboardArray = []; args.forEach((aValue) => { clipboardArray.push(VariablesView.getString(aValue)); }); clipboardText = clipboardArray.join(" "); break; } - case "trace": { - let filename = WebConsoleUtils.abbreviateSourceURL(aMessage.filename); - let functionName = aMessage.functionName || - l10n.getStr("stacktrace.anonymousFunction"); - - body = this.document.createElementNS(XHTML_NS, "a"); - body.setAttribute("aria-haspopup", true); - body.href = "#"; - body.draggable = false; - body.textContent = l10n.getFormatStr("stacktrace.outputMessage", - [filename, functionName, - sourceLine]); - - this._addMessageLinkCallback(body, () => { - this.jsterm.openVariablesView({ - rawObject: aMessage.stacktrace, - autofocus: true, - }); - }); - - clipboardText = body.textContent + "\n"; - - aMessage.stacktrace.forEach(function(aFrame) { - clipboardText += aFrame.filename + " :: " + - aFrame.functionName + " :: " + - aFrame.lineNumber + "\n"; - }); - - clipboardText = clipboardText.trimRight(); - break; - } - case "group": case "groupCollapsed": clipboardText = body = aMessage.groupName; this.groupDepth++; break; case "groupEnd": if (this.groupDepth > 0) { @@ -1276,17 +1249,16 @@ WebConsoleFrame.prototype = { } // Release object actors for arguments coming from console API methods that // we ignore their arguments. switch (level) { case "group": case "groupCollapsed": case "groupEnd": - case "trace": case "time": case "timeEnd": for (let actor of objectActors) { this._releaseObject(actor); } objectActors.clear(); } @@ -1302,25 +1274,21 @@ WebConsoleFrame.prototype = { node.setAttribute("private", true); } } if (objectActors.size > 0) { node._objectActors = objectActors; if (!node._messageObject) { - let repeatNode = node.getElementsByClassName("repeats")[0]; + let repeatNode = node.getElementsByClassName("message-repeats")[0]; repeatNode._uid += [...objectActors].join("-"); } } - if (level == "trace") { - node._stacktrace = aMessage.stacktrace; - } - return node; }, /** * Handle ConsoleAPICall objects received from the server. This method outputs * the window.console API call. * * @param object aMessage @@ -2383,17 +2351,17 @@ WebConsoleFrame.prototype = { for (let actor of aNode._objectActors) { this._releaseObject(actor); } aNode._objectActors.clear(); } if (aNode.category == CATEGORY_CSS || aNode.category == CATEGORY_SECURITY) { - let repeatNode = aNode.getElementsByClassName("repeats")[0]; + let repeatNode = aNode.getElementsByClassName("message-repeats")[0]; if (repeatNode && repeatNode._uid) { delete this._repeatNodes[repeatNode._uid]; } } else if (aNode._connectionId && aNode.category == CATEGORY_NETWORK) { delete this._networkRequests[aNode._connectionId]; this._releaseObject(aNode._connectionId); @@ -2496,17 +2464,17 @@ WebConsoleFrame.prototype = { // Add the message repeats node only when needed. let repeatNode = null; if (aCategory != CATEGORY_INPUT && aCategory != CATEGORY_OUTPUT && aCategory != CATEGORY_NETWORK && !(aCategory == CATEGORY_CSS && aSeverity == SEVERITY_LOG)) { repeatNode = this.document.createElementNS(XHTML_NS, "span"); repeatNode.setAttribute("value", "1"); - repeatNode.className = "repeats"; + repeatNode.className = "message-repeats"; repeatNode.textContent = 1; repeatNode._uid = [bodyNode.textContent, aCategory, aSeverity, aLevel, aSourceURL, aSourceLine].join(":"); } // Create the timestamp. let timestampNode = this.document.createElementNS(XHTML_NS, "span"); timestampNode.className = "timestamp devtools-monospace"; @@ -2562,21 +2530,28 @@ WebConsoleFrame.prototype = { * Creates the anchor that displays the textual location of an incoming * message. * * @param string aSourceURL * The URL of the source file responsible for the error. * @param number aSourceLine [optional] * The line number on which the error occurred. If zero or omitted, * there is no line number associated with this message. + * @param string aTarget [optional] + * Tells which tool to open the link with, on click. Supported tools: + * jsdebugger, styleeditor, scratchpad. * @return nsIDOMNode * The new anchor element, ready to be added to the message node. */ - createLocationNode: function WCF_createLocationNode(aSourceURL, aSourceLine) + createLocationNode: + function WCF_createLocationNode(aSourceURL, aSourceLine, aTarget) { + if (!aSourceURL) { + aSourceURL = ""; + } let locationNode = this.document.createElementNS(XHTML_NS, "a"); let filenameNode = this.document.createElementNS(XHTML_NS, "span"); // Create the text, which consists of an abbreviated version of the URL // Scratchpad URLs should not be abbreviated. let filename; let fullURL; let isScratchpad = false; @@ -2587,40 +2562,49 @@ WebConsoleFrame.prototype = { isScratchpad = true; } else { fullURL = aSourceURL.split(" -> ").pop(); filename = WebConsoleUtils.abbreviateSourceURL(fullURL); } filenameNode.className = "filename"; - filenameNode.textContent = " " + filename; + filenameNode.textContent = " " + (filename || l10n.getStr("unknownLocation")); locationNode.appendChild(filenameNode); - locationNode.href = isScratchpad ? "#" : fullURL; + locationNode.href = isScratchpad || !fullURL ? "#" : fullURL; locationNode.draggable = false; + locationNode.target = aTarget; locationNode.setAttribute("title", aSourceURL); - locationNode.className = "location theme-link devtools-monospace"; + locationNode.className = "message-location theme-link devtools-monospace"; // Make the location clickable. - this._addMessageLinkCallback(locationNode, () => { - if (isScratchpad) { + let onClick = () => { + let target = locationNode.target; + if (target == "scratchpad" || isScratchpad) { this.owner.viewSourceInScratchpad(aSourceURL); + return; } - else if (locationNode.parentNode.category == CATEGORY_CSS) { + + let category = locationNode.parentNode.category; + if (target == "styleeditor" || category == CATEGORY_CSS) { this.owner.viewSourceInStyleEditor(fullURL, aSourceLine); } - else if (locationNode.parentNode.category == CATEGORY_JS || - locationNode.parentNode.category == CATEGORY_WEBDEV) { + else if (target == "jsdebugger" || + category == CATEGORY_JS || category == CATEGORY_WEBDEV) { this.owner.viewSourceInDebugger(fullURL, aSourceLine); } else { this.owner.viewSource(fullURL, aSourceLine); } - }); + }; + + if (fullURL) { + this._addMessageLinkCallback(locationNode, onClick); + } if (aSourceLine) { let lineNumberNode = this.document.createElementNS(XHTML_NS, "span"); lineNumberNode.className = "line-number"; lineNumberNode.textContent = ":" + aSourceLine; locationNode.appendChild(lineNumberNode); locationNode.sourceLine = aSourceLine; }
--- a/browser/locales/en-US/chrome/browser/devtools/webconsole.properties +++ b/browser/locales/en-US/chrome/browser/devtools/webconsole.properties @@ -97,25 +97,23 @@ gcliterm.instanceLabel=Instance of %S # The 2nd line, from "function" to the end of the line, is a link to the # JavaScript debugger. reflow.messageWithNoLink=reflow: %Sms reflow.messageWithLink=reflow: %Sms\u0020 reflow.messageLinkText=function %1$S, %2$S line %3$S # LOCALIZATION NOTE (stacktrace.anonymousFunction): this string is used to # display JavaScript functions that have no given name - they are said to be -# anonymous. See also stacktrace.outputMessage. +# anonymous. Test console.trace() in the webconsole. stacktrace.anonymousFunction=<anonymous> -# LOCALIZATION NOTE (stacktrace.outputMessage): this string is used in the Web -# Console output to identify a web developer call to console.trace(). The -# stack trace of JavaScript function calls is displayed. In this minimal -# message we only show the last call. Parameters: %1$S is the file name, %2$S -# is the function name, %3$S is the line number. -stacktrace.outputMessage=Stack trace from %1$S, function %2$S, line %3$S. +# LOCALIZATION NOTE (unknownLocation): this string is used to +# display messages with sources that have an unknown location, eg. from +# console.trace() calls. +unknownLocation=<unknown> # LOCALIZATION NOTE (timerStarted): this string is used to display the result # of the console.time() call. Parameters: %S is the name of the timer. timerStarted=%S: timer started # LOCALIZATION NOTE (timeEnd): this string is used to display the result of # the console.timeEnd() call. Parameters: %1$S is the name of the timer, %2$S # is the number of milliseconds.
--- a/browser/themes/shared/devtools/webconsole.inc.css +++ b/browser/themes/shared/devtools/webconsole.inc.css @@ -41,59 +41,59 @@ a { .message > .body { flex: 1 1 100%; white-space: pre-wrap; word-wrap: break-word; margin-top: 4px; } /* The red bubble that shows the number of times a message is repeated */ -.message > .repeats { +.message-repeats { -moz-user-select: none; flex: 0 0 auto; margin: 2px 6px; padding: 0 6px; height: 1.25em; color: white; background-color: red; border-radius: 40px; font: message-box; font-size: 0.9em; font-weight: 600; } -.message > .repeats[value="1"] { +.message-repeats[value="1"] { display: none; } -.message > .location { +.message-location { -moz-margin-start: 6px; display: flex; flex: 0 0 auto; align-self: flex-start; justify-content: flex-end; width: 10em; margin-top: 4px; color: -moz-nativehyperlinktext; text-decoration: none; + white-space: nowrap; } -.message > .location:hover, -.message > .location:focus { +.message-location:hover, +.message-location:focus { text-decoration: underline; } -.message > .location > .filename { +.message-location > .filename { text-overflow: ellipsis; text-align: end; overflow: hidden; - white-space: nowrap; } -.message > .location > .line-number { +.message-location > .line-number { flex: 0 0 auto; } .jsterm-input-container { border-top-width: 1px; border-top-style: solid; } @@ -331,16 +331,47 @@ a { font-size: 0.9em; } .navigation-marker .url { -moz-padding-end: 9px; text-decoration: none; } +.consoleTrace .body > div { + display: flex; + margin-bottom: 5px; +} + +.consoleTrace .title { + display: block; + flex: 1 1 auto; +} + +.stacktrace { + list-style: none; + padding: 0 1em 0 1.5em; + margin: 0; + max-height: 10em; + overflow-y: auto; + + border: 1px solid rgba(128, 128, 128, .5); + border-radius: 3px; +} + +.stacktrace li { + display: flex; + margin: 0; +} + +.stacktrace .function { + display: block; + flex: 1 1 auto; +} + /* Replace these values with CSS variables as available */ .theme-dark .jsterm-input-container { background-color: #252c33; /* tabToolbarBackgroundColor */ border-color: #131c26; /* mainBackgroundColor */ } .theme-dark .jsterm-input-node { color: #8fa1b2; /* textColor */ @@ -353,16 +384,20 @@ a { .theme-dark .navigation-marker .url { background: #131c26; /* mainBackgroundColor */ } .theme-dark .inlined-variables-view iframe { border-color: #333; } +.theme-dark .stacktrace { + border-color: #333; +} + .theme-light .jsterm-input-container { background-color: #fff; /* mainBackgroundColor */ border-color: ThreeDShadow; } .theme-light .jsterm-input-node { color: black; /* textColor */ } @@ -374,16 +409,20 @@ a { .theme-light .navigation-marker .url { background: #fff; /* mainBackgroundColor */ } .theme-light .inlined-variables-view iframe { border-color: #ccc; } +.theme-light .stacktrace { + border-color: #ccc; +} + @media (max-width: 500px) { .message > .timestamp { display: none; } .toolbarbutton-text { display: none; } .hud-console-filter-toolbar .webconsole-filter-button {