From bb5f28f898c1f06a0f92a5a63d780abb9a98e3e3 Mon Sep 17 00:00:00 2001 From: Mihai Sucan Date: Fri, 13 Sep 2013 15:06:46 +0300 Subject: [PATCH] Bug 760876 - Part 1: switch from XUL to XHTML for the Web Console output; r=robcee,paul --- browser/devtools/webconsole/console-output.js | 128 +++++- browser/devtools/webconsole/webconsole.js | 392 ++++++++---------- browser/devtools/webconsole/webconsole.xul | 14 +- .../browser/devtools/webconsole.properties | 5 + .../themes/shared/devtools/webconsole.inc.css | 204 +++++---- toolkit/devtools/webconsole/client.js | 4 +- toolkit/devtools/webconsole/network-helper.js | 4 +- toolkit/devtools/webconsole/utils.js | 13 + 8 files changed, 443 insertions(+), 321 deletions(-) diff --git a/browser/devtools/webconsole/console-output.js b/browser/devtools/webconsole/console-output.js index 89e39e920eb..d970549eade 100644 --- a/browser/devtools/webconsole/console-output.js +++ b/browser/devtools/webconsole/console-output.js @@ -54,17 +54,29 @@ function ConsoleOutput(owner) } ConsoleOutput.prototype = { + /** + * The output container. + * @type DOMElement + */ + get element() { + return this.owner.outputNode; + }, + /** * The document that holds the output. * @type DOMDocument */ - get document() this.owner.document, + get document() { + return this.owner.document; + }, /** * The DOM window that holds the output. * @type Window */ - get window() this.owner.window, + get window() { + return this.owner.window; + }, /** * Add a message to output. @@ -102,6 +114,100 @@ ConsoleOutput.prototype = { return message.render().element; }, + /** + * Get an array of selected messages. This list is based on the text selection + * start and end points. + * + * @param number [limit] + * Optional limit of selected messages you want. If no value is given, + * all of the selected messages are returned. + * @return array + * Array of DOM elements for each message that is currently selected. + */ + getSelectedMessages: function(limit) + { + let selection = this.window.getSelection(); + if (selection.isCollapsed) { + return []; + } + + if (selection.containsNode(this.element, true)) { + return Array.slice(this.element.children); + } + + let anchor = this.getMessageForElement(selection.anchorNode); + let focus = this.getMessageForElement(selection.focusNode); + if (!anchor || !focus) { + return []; + } + + let start, end; + if (anchor.timestamp > focus.timestamp) { + start = focus; + end = anchor; + } else { + start = anchor; + end = focus; + } + + let result = []; + let current = start; + while (current) { + result.push(current); + if (current == end || (limit && result.length == limit)) { + break; + } + current = current.nextSibling; + } + return result; + }, + + /** + * Find the DOM element of a message for any given descendant. + * + * @param DOMElement elem + * The element to start the search from. + * @return DOMElement|null + * The DOM element of the message, if any. + */ + getMessageForElement: function(elem) + { + while (elem && elem.parentNode) { + if (elem.classList && elem.classList.contains("hud-msg-node")) { + return elem; + } + elem = elem.parentNode; + } + return null; + }, + + /** + * Select all messages. + */ + selectAllMessages: function() + { + let selection = this.window.getSelection(); + selection.removeAllRanges(); + let range = this.document.createRange(); + range.selectNodeContents(this.element); + selection.addRange(range); + }, + + /** + * Add a message to the selection. + * + * @param DOMElement elem + * The message element to select. + */ + selectMessage: function(elem) + { + let selection = this.window.getSelection(); + selection.removeAllRanges(); + let range = this.document.createRange(); + range.selectNodeContents(elem); + selection.addRange(range); + }, + /** * Destroy this ConsoleOutput instance. */ @@ -216,21 +322,15 @@ Messages.BaseMessage.prototype = { _renderCompat: function() { let doc = this.output.document; - let container = doc.createElementNS(XUL_NS, "richlistitem"); - container.setAttribute("id", "console-msg-" + gSequenceId()); - container.setAttribute("class", "hud-msg-node " + this._elementClassCompat); + let container = doc.createElementNS(XHTML_NS, "div"); + container.id = "console-msg-" + gSequenceId(); + container.className = "hud-msg-node " + this._elementClassCompat; container.category = this._categoryCompat; container.severity = this._severityCompat; container.clipboardText = this.textContent; container.timestamp = this.timestamp; container._messageObject = this; - let body = doc.createElementNS(XUL_NS, "description"); - body.flex = 1; - body.classList.add("webconsole-msg-body"); - body.classList.add("devtools-monospace"); - container.appendChild(body); - return container; }, }; // Messages.BaseMessage.prototype @@ -287,13 +387,13 @@ Messages.NavigationMarker.prototype = Heritage.extend(Messages.BaseMessage.proto } let doc = this.output.document; - let urlnode = doc.createElementNS(XHTML_NS, "span"); + let urlnode = doc.createElementNS(XHTML_NS, "a"); urlnode.className = "url"; urlnode.textContent = url; + urlnode.title = this._url; - // Add the text in the xul:description.webconsole-msg-body element. let render = Messages.BaseMessage.prototype.render.bind(this); - render().element.firstChild.appendChild(urlnode); + render().element.appendChild(urlnode); this.element.classList.add("navigation-marker"); this.element.url = this._url; diff --git a/browser/devtools/webconsole/webconsole.js b/browser/devtools/webconsole/webconsole.js index bc0424520ea..48205f12b3c 100644 --- a/browser/devtools/webconsole/webconsole.js +++ b/browser/devtools/webconsole/webconsole.js @@ -33,11 +33,9 @@ loader.lazyImporter(this, "VariablesViewController", "resource:///modules/devtoo const STRINGS_URI = "chrome://browser/locale/devtools/webconsole.properties"; let l10n = new WebConsoleUtils.l10n(STRINGS_URI); +const XHTML_NS = "http://www.w3.org/1999/xhtml"; -// The XUL namespace. -const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"; - -const MIXED_CONTENT_LEARN_MORE = "https://developer.mozilla.org/Security/MixedContent"; +const MIXED_CONTENT_LEARN_MORE = "https://developer.mozilla.org/docs/Security/MixedContent"; const INSECURE_PASSWORDS_LEARN_MORE = "https://developer.mozilla.org/docs/Security/InsecurePasswords"; @@ -940,15 +938,15 @@ WebConsoleFrame.prototype = { mergeFilteredMessageNode: function WCF_mergeFilteredMessageNode(aOriginal, aFiltered) { - // childNodes[3].firstChild is the node containing the number of repetitions - // of a node. - let repeatNode = aOriginal.childNodes[3].firstChild; + let repeatNode = aOriginal.getElementsByClassName("webconsole-msg-repeat")[0]; if (!repeatNode) { return; // no repeat node, return early. } let occurrences = parseInt(repeatNode.getAttribute("value")) + 1; repeatNode.setAttribute("value", occurrences); + repeatNode.textContent = occurrences; + repeatNode.title = l10n.getFormatStr("messageRepeats.tooltip", [occurrences]); }, /** @@ -1090,7 +1088,7 @@ WebConsoleFrame.prototype = { body = l10n.getFormatStr("stacktrace.outputMessage", [filename, functionName, sourceLine]); - clipboardText = ""; + clipboardText = body + "\n"; aMessage.stacktrace.forEach(function(aFrame) { clipboardText += aFrame.filename + " :: " + @@ -1173,7 +1171,7 @@ WebConsoleFrame.prototype = { if (objectActors.size > 0) { node._objectActors = objectActors; - let repeatNode = node.querySelector(".webconsole-msg-repeat"); + let repeatNode = node.getElementsByClassName("webconsole-msg-repeat")[0]; repeatNode._uid += [...objectActors].join("-"); } @@ -1344,60 +1342,52 @@ WebConsoleFrame.prototype = { } let request = networkInfo.request; - - let msgNode = this.document.createElementNS(XUL_NS, "hbox"); - - let methodNode = this.document.createElementNS(XUL_NS, "label"); - methodNode.setAttribute("value", request.method); - methodNode.classList.add("webconsole-msg-body-piece"); - msgNode.appendChild(methodNode); - - let linkNode = this.document.createElementNS(XUL_NS, "hbox"); - linkNode.flex = 1; - linkNode.classList.add("webconsole-msg-body-piece"); - linkNode.classList.add("webconsole-msg-link"); - msgNode.appendChild(linkNode); - - let urlNode = this.document.createElementNS(XUL_NS, "label"); - urlNode.flex = 1; - urlNode.setAttribute("crop", "center"); - urlNode.setAttribute("title", request.url); - urlNode.setAttribute("tooltiptext", request.url); - urlNode.setAttribute("value", request.url); - urlNode.classList.add("hud-clickable"); - urlNode.classList.add("webconsole-msg-body-piece"); - urlNode.classList.add("webconsole-msg-url"); - linkNode.appendChild(urlNode); - + let clipboardText = request.method + " " + request.url; let severity = SEVERITY_LOG; let mixedRequest = WebConsoleUtils.isMixedHTTPSRequest(request.url, this.contentLocation); if (mixedRequest) { - urlNode.classList.add("webconsole-mixed-content"); - this.makeMixedContentNode(linkNode); - // If we define a SEVERITY_SECURITY in the future, switch this to - // SEVERITY_SECURITY. severity = SEVERITY_WARNING; } - let statusNode = this.document.createElementNS(XUL_NS, "label"); - statusNode.setAttribute("value", ""); - statusNode.classList.add("hud-clickable"); - statusNode.classList.add("webconsole-msg-body-piece"); - statusNode.classList.add("webconsole-msg-status"); - linkNode.appendChild(statusNode); - - let clipboardText = request.method + " " + request.url; + let methodNode = this.document.createElementNS(XHTML_NS, "span"); + methodNode.className = "webconsole-msg-method"; + methodNode.textContent = request.method + " "; let messageNode = this.createMessageNode(CATEGORY_NETWORK, severity, - msgNode, null, null, clipboardText); + methodNode, null, null, + clipboardText); if (networkInfo.private) { messageNode.setAttribute("private", true); } - messageNode._connectionId = aActorId; messageNode.url = request.url; + let body = methodNode.parentNode; + body.setAttribute("aria-haspopup", true); + + let displayUrl = request.url; + let pos = displayUrl.indexOf("?"); + if (pos > -1) { + displayUrl = displayUrl.substr(0, pos); + } + + let urlNode = this.document.createElementNS(XHTML_NS, "a"); + urlNode.classList.add("webconsole-msg-url"); + urlNode.setAttribute("title", request.url); + urlNode.textContent = displayUrl; + body.appendChild(urlNode); + body.appendChild(this.document.createTextNode(" ")); + + if (mixedRequest) { + urlNode.classList.add("webconsole-mixed-content"); + this.makeMixedContentNode(body); + } + + let statusNode = this.document.createElementNS(XHTML_NS, "a"); + statusNode.classList.add("webconsole-msg-status"); + body.appendChild(statusNode); + this.makeOutputMessageLink(messageNode, function WCF_net_message_link() { if (!messageNode._panelOpen) { this.openNetworkPanel(messageNode, networkInfo); @@ -1422,11 +1412,10 @@ WebConsoleFrame.prototype = { let mixedContentWarning = "[" + l10n.getStr("webConsoleMixedContentWarning") + "]"; // Mixed content warning message links to a Learn More page - let mixedContentWarningNode = this.document.createElement("label"); - mixedContentWarningNode.setAttribute("value", mixedContentWarning); - mixedContentWarningNode.setAttribute("title", mixedContentWarning); - mixedContentWarningNode.classList.add("hud-clickable"); + let mixedContentWarningNode = this.document.createElementNS(XHTML_NS, "a"); + mixedContentWarningNode.title = MIXED_CONTENT_LEARN_MORE; mixedContentWarningNode.classList.add("webconsole-mixed-content-link"); + mixedContentWarningNode.textContent = mixedContentWarning; aLinkNode.appendChild(mixedContentWarningNode); @@ -1483,21 +1472,11 @@ WebConsoleFrame.prototype = { addLearnMoreWarningNode: function WCF_addLearnMoreWarningNode(aNode, aURL) { - let moreInfoLabel = - "[" + l10n.getStr("webConsoleMoreInfoLabel") + "]"; + let moreInfoLabel = "[" + l10n.getStr("webConsoleMoreInfoLabel") + "]"; - // The node that holds the clickable warning node. - let linkNode = this.document.createElementNS(XUL_NS, "hbox"); - linkNode.flex = 1; - linkNode.classList.add("webconsole-msg-body-piece"); - linkNode.classList.add("webconsole-msg-link"); - aNode.appendChild(linkNode); - - // Create the actual warning node and make it clickable - let warningNode = this.document.createElement("label"); - warningNode.setAttribute("value", moreInfoLabel); - warningNode.setAttribute("title", moreInfoLabel); - warningNode.classList.add("hud-clickable"); + let warningNode = this.document.createElementNS(XHTML_NS, "a"); + warningNode.title = aURL; + warningNode.textContent = moreInfoLabel; warningNode.classList.add("webconsole-learn-more-link"); warningNode.addEventListener("click", function(aEvent) { @@ -1506,7 +1485,7 @@ WebConsoleFrame.prototype = { aEvent.stopPropagation(); }.bind(this)); - linkNode.appendChild(warningNode); + aNode.appendChild(warningNode); }, /** @@ -1519,14 +1498,10 @@ WebConsoleFrame.prototype = { */ logFileActivity: function WCF_logFileActivity(aFileURI) { - let urlNode = this.document.createElementNS(XUL_NS, "label"); - urlNode.flex = 1; - urlNode.setAttribute("crop", "center"); + let urlNode = this.document.createElementNS(XHTML_NS, "a"); urlNode.setAttribute("title", aFileURI); - urlNode.setAttribute("tooltiptext", aFileURI); - urlNode.setAttribute("value", aFileURI); - urlNode.classList.add("hud-clickable"); urlNode.classList.add("webconsole-msg-url"); + urlNode.textContent = aFileURI; let outputNode = this.createMessageNode(CATEGORY_NETWORK, SEVERITY_LOG, urlNode, null, null, aFileURI); @@ -1645,8 +1620,8 @@ WebConsoleFrame.prototype = { break; } - if (networkInfo.node) { - this._updateNetMessage(aActorId); + if (networkInfo.node && this._updateNetMessage(aActorId)) { + this.emit("messages-updated", new Set([networkInfo.node])); } // For unit tests we pass the HTTP activity object to the test callback, @@ -1665,6 +1640,8 @@ WebConsoleFrame.prototype = { * @private * @param string aActorId * The network event actor ID for which you want to update the message. + * @return boolean + * |true| if the message node was updated, or |false| otherwise. */ _updateNetMessage: function WCF__updateNetMessage(aActorId) { @@ -1679,6 +1656,7 @@ WebConsoleFrame.prototype = { let hasResponseStart = updates.indexOf("responseStart") > -1; let request = networkInfo.request; let response = networkInfo.response; + let updated = false; if (hasEventTimings || hasResponseStart) { let status = []; @@ -1691,9 +1669,8 @@ WebConsoleFrame.prototype = { } let statusText = "[" + status.join(" ") + "]"; - let linkNode = messageNode.querySelector(".webconsole-msg-link"); - let statusNode = linkNode.querySelector(".webconsole-msg-status"); - statusNode.setAttribute("value", statusText); + let statusNode = messageNode.getElementsByClassName("webconsole-msg-status")[0]; + statusNode.textContent = statusText; messageNode.clipboardText = [request.method, request.url, statusText] .join(" "); @@ -1702,11 +1679,15 @@ WebConsoleFrame.prototype = { response.status <= MAX_HTTP_ERROR_CODE) { this.setMessageType(messageNode, CATEGORY_NETWORK, SEVERITY_ERROR); } + + updated = true; } if (messageNode._netPanel) { messageNode._netPanel.update(); } + + return updated; }, /** @@ -1960,9 +1941,8 @@ WebConsoleFrame.prototype = { let outputNode = this.outputNode; let lastVisibleNode = null; + let scrollNode = outputNode.parentNode; let scrolledToBottom = Utils.isOutputScrolledToBottom(outputNode); - let scrollBox = outputNode.scrollBoxObject.element; - let hudIdSupportsString = WebConsoleUtils.supportsString(this.hudId); // Output the current batch of messages. @@ -1989,7 +1969,7 @@ WebConsoleFrame.prototype = { // improve performance. let removedNodes = 0; if (shouldPrune || !this._outputQueue.length) { - oldScrollHeight = scrollBox.scrollHeight; + oldScrollHeight = scrollNode.scrollHeight; let categories = Object.keys(this._pruneCategoriesQueue); categories.forEach(function _pruneOutput(aCategory) { @@ -2009,10 +1989,10 @@ WebConsoleFrame.prototype = { Utils.scrollToVisible(lastVisibleNode); } else if (!scrolledToBottom && removedNodes > 0 && - oldScrollHeight != scrollBox.scrollHeight) { + oldScrollHeight != scrollNode.scrollHeight) { // If there were pruned messages and if scroll is not at the bottom, then // we need to adjust the scroll location. - scrollBox.scrollTop -= oldScrollHeight - scrollBox.scrollHeight; + scrollNode.scrollTop -= oldScrollHeight - scrollNode.scrollHeight; } if (newMessages.size) { @@ -2028,7 +2008,14 @@ WebConsoleFrame.prototype = { } else { this._outputTimerInitialized = false; - this._flushCallback && this._flushCallback(); + if (this._flushCallback) { + try { + this._flushCallback(); + } + catch (ex) { + console.error(ex); + } + } } this._lastOutputFlush = Date.now(); @@ -2291,8 +2278,8 @@ WebConsoleFrame.prototype = { * The timestamp to use for this message node. If omitted, the current * date and time is used. * @return nsIDOMNode - * The message node: a XUL richlistitem ready to be inserted into - * the Web Console output node. + * The message node: a DIV ready to be inserted into the Web Console + * output node. */ createMessageNode: function WCF_createMessageNode(aCategory, aSeverity, aBody, aSourceURL, @@ -2305,25 +2292,11 @@ WebConsoleFrame.prototype = { // Make the icon container, which is a vertical box. Its purpose is to // ensure that the icon stays anchored at the top of the message even for // long multi-line messages. - let iconContainer = this.document.createElementNS(XUL_NS, "vbox"); - iconContainer.classList.add("webconsole-msg-icon-container"); - // Apply the curent group by indenting appropriately. - iconContainer.style.marginLeft = this.groupDepth * GROUP_INDENT + "px"; - - // Make the icon node. It's sprited and the actual region of the image is - // determined by CSS rules. - let iconNode = this.document.createElementNS(XUL_NS, "image"); - iconNode.classList.add("webconsole-msg-icon"); - iconContainer.appendChild(iconNode); - - // Make the spacer that positions the icon. - let spacer = this.document.createElementNS(XUL_NS, "spacer"); - spacer.flex = 1; - iconContainer.appendChild(spacer); + let iconContainer = this.document.createElementNS(XHTML_NS, "span"); + iconContainer.className = "webconsole-msg-icon"; // Create the message body, which contains the actual text of the message. - let bodyNode = this.document.createElementNS(XUL_NS, "description"); - bodyNode.flex = 1; + let bodyNode = this.document.createElementNS(XHTML_NS, "span"); bodyNode.classList.add("webconsole-msg-body"); bodyNode.classList.add("devtools-monospace"); @@ -2335,8 +2308,15 @@ WebConsoleFrame.prototype = { (aBody + (aSourceURL ? " @ " + aSourceURL : "") + (aSourceLine ? ":" + aSourceLine : "")); + let timestamp = aTimeStamp || Date.now(); + // Create the containing node and append all its elements to it. - let node = this.document.createElementNS(XUL_NS, "richlistitem"); + let node = this.document.createElementNS(XHTML_NS, "div"); + node.id = "console-msg-" + gSequenceId(); + node.className = "hud-msg-node"; + node.clipboardText = aClipboardText; + node.timestamp = timestamp; + this.setMessageType(node, aCategory, aSeverity); if (aBody instanceof Ci.nsIDOMNode) { bodyNode.appendChild(aBody); @@ -2360,23 +2340,27 @@ WebConsoleFrame.prototype = { } } - let repeatContainer = this.document.createElementNS(XUL_NS, "hbox"); - repeatContainer.setAttribute("align", "start"); - let repeatNode = this.document.createElementNS(XUL_NS, "label"); - repeatNode.setAttribute("value", "1"); - repeatNode.classList.add("webconsole-msg-repeat"); - repeatNode._uid = [bodyNode.textContent, aCategory, aSeverity, aLevel, - aSourceURL, aSourceLine].join(":"); - repeatContainer.appendChild(repeatNode); + // Add the message repeats node only when needed. + let repeatNode = null; + if (aCategory != CATEGORY_INPUT && aCategory != CATEGORY_OUTPUT && + aCategory != CATEGORY_NETWORK) { + repeatNode = this.document.createElementNS(XHTML_NS, "span"); + repeatNode.setAttribute("value", "1"); + repeatNode.classList.add("webconsole-msg-repeat"); + repeatNode.textContent = 1; + repeatNode._uid = [bodyNode.textContent, aCategory, aSeverity, aLevel, + aSourceURL, aSourceLine].join(":"); + } // Create the timestamp. - let timestampNode = this.document.createElementNS(XUL_NS, "label"); + let timestampNode = this.document.createElementNS(XHTML_NS, "span"); timestampNode.classList.add("webconsole-timestamp"); timestampNode.classList.add("devtools-monospace"); + // Apply the current group by indenting appropriately. + timestampNode.style.marginRight = this.groupDepth * GROUP_INDENT + "px"; - let timestamp = aTimeStamp || Date.now(); let timestampString = l10n.timestampString(timestamp); - timestampNode.setAttribute("value", timestampString); + timestampNode.textContent = timestampString + " "; // Create the source location (e.g. www.example.com:6) that sits on the // right side of the message, if applicable. @@ -2385,25 +2369,17 @@ WebConsoleFrame.prototype = { locationNode = this.createLocationNode(aSourceURL, aSourceLine); } - node.clipboardText = aClipboardText; - node.classList.add("hud-msg-node"); - - node.timestamp = timestamp; - this.setMessageType(node, aCategory, aSeverity); - node.appendChild(timestampNode); node.appendChild(iconContainer); // Display the variables view after the message node. if (aLevel == "dir") { - let viewContainer = this.document.createElement("hbox"); - viewContainer.flex = 1; - viewContainer.height = this.outputNode.clientHeight * - CONSOLE_DIR_VIEW_HEIGHT; + bodyNode.style.height = (this.window.innerHeight * + CONSOLE_DIR_VIEW_HEIGHT) + "px"; let options = { objectActor: body.arguments[0], - targetElement: viewContainer, + targetElement: bodyNode, hideFilterInput: true, }; this.jsterm.openVariablesView(options).then((aView) => { @@ -2413,23 +2389,17 @@ WebConsoleFrame.prototype = { } }); - let bodyContainer = this.document.createElement("vbox"); - bodyContainer.flex = 1; - bodyContainer.appendChild(bodyNode); - bodyContainer.appendChild(viewContainer); - node.appendChild(bodyContainer); node.classList.add("webconsole-msg-inspector"); } - else { - node.appendChild(bodyNode); + + node.appendChild(bodyNode); + if (repeatNode) { + node.appendChild(repeatNode); } - node.appendChild(repeatContainer); if (locationNode) { node.appendChild(locationNode); } - node.setAttribute("id", "console-msg-" + gSequenceId()); - return node; }, @@ -2452,7 +2422,7 @@ WebConsoleFrame.prototype = { { Object.defineProperty(aMessage, "_panelOpen", { get: function() { - let nodes = aContainer.querySelectorAll(".hud-clickable"); + let nodes = aContainer.getElementsByTagName("a"); return Array.prototype.some.call(nodes, function(aNode) { return aNode._panelOpen; }); @@ -2473,8 +2443,7 @@ WebConsoleFrame.prototype = { aContainer.appendChild(this.document.createTextNode(text)); if (aItem.type && aItem.type == "longString") { - let ellipsis = this.document.createElement("description"); - ellipsis.classList.add("hud-clickable"); + let ellipsis = this.document.createElementNS(XHTML_NS, "a"); ellipsis.classList.add("longStringEllipsis"); ellipsis.textContent = l10n.getStr("longStringEllipsis"); @@ -2489,10 +2458,9 @@ WebConsoleFrame.prototype = { } // For inspectable objects. - let elem = this.document.createElement("description"); - elem.classList.add("hud-clickable"); + let elem = this.document.createElementNS(XHTML_NS, "a"); elem.setAttribute("aria-haspopup", "true"); - elem.appendChild(this.document.createTextNode(text)); + elem.textContent = text; this._addMessageLinkCallback(elem, this._consoleLogClick.bind(this, elem, aItem)); @@ -2556,7 +2524,7 @@ WebConsoleFrame.prototype = { }, /** - * Creates the XUL label that displays the textual location of an incoming + * Creates the anchor that displays the textual location of an incoming * message. * * @param string aSourceURL @@ -2565,11 +2533,11 @@ WebConsoleFrame.prototype = { * The line number on which the error occurred. If zero or omitted, * there is no line number associated with this message. * @return nsIDOMNode - * The new XUL label node, ready to be added to the message node. + * The new anchor element, ready to be added to the message node. */ createLocationNode: function WCF_createLocationNode(aSourceURL, aSourceLine) { - let locationNode = this.document.createElementNS(XUL_NS, "label"); + let locationNode = this.document.createElementNS(XHTML_NS, "a"); // Create the text, which consists of an abbreviated version of the URL // plus an optional line number. Scratchpad URLs should not be abbreviated. @@ -2590,14 +2558,9 @@ WebConsoleFrame.prototype = { locationNode.sourceLine = aSourceLine; } - locationNode.setAttribute("value", displayLocation); - - // Style appropriately. - locationNode.setAttribute("crop", "center"); + locationNode.textContent = " " + displayLocation; locationNode.setAttribute("title", aSourceURL); - locationNode.setAttribute("tooltiptext", aSourceURL); locationNode.classList.add("webconsole-location"); - locationNode.classList.add("text-link"); locationNode.classList.add("devtools-monospace"); // Make the location clickable. @@ -2729,19 +2692,17 @@ WebConsoleFrame.prototype = { */ copySelectedItems: function WCF_copySelectedItems(aOptions) { - aOptions = aOptions || { linkOnly: false }; + aOptions = aOptions || { linkOnly: false, contextmenu: false }; // Gather up the selected items and concatenate their clipboard text. let strings = []; - let children = this.outputNode.children; - - for (let i = 0; i < children.length; i++) { - let item = children[i]; - if (!item.selected) { - continue; - } + let children = this.output.getSelectedMessages(); + if (!children.length && aOptions.contextmenu) { + children = [this._contextMenuHandler.lastClickedMessage]; + } + for (let item of children) { // Ensure the selected item hasn't been filtered by type or string. if (!item.classList.contains("hud-filtered-by-type") && !item.classList.contains("hud-filtered-by-string")) { @@ -2800,7 +2761,8 @@ WebConsoleFrame.prototype = { */ openSelectedItemInTab: function WCF_openSelectedItemInTab() { - let item = this.outputNode.selectedItem; + let item = this.output.getSelectedMessages(1)[0] || + this._contextMenuHandler.lastClickedMessage; if (!item || !item.url) { return; @@ -3136,15 +3098,6 @@ JSTerm.prototype = { return; } - if (aCallback) { - let oldFlushCallback = this.hud._flushCallback; - this.hud._flushCallback = function() { - aCallback(); - oldFlushCallback && oldFlushCallback(); - this.hud._flushCallback = oldFlushCallback; - }.bind(this); - } - let node; if (errorMessage) { @@ -3161,6 +3114,20 @@ JSTerm.prototype = { aAfterNode, aResponse.timestamp); } + if (aCallback) { + let oldFlushCallback = this.hud._flushCallback; + this.hud._flushCallback = () => { + aCallback(node); + if (oldFlushCallback) { + oldFlushCallback(); + this.hud._flushCallback = oldFlushCallback; + } + else { + this.hud._flushCallback = null; + } + }; + } + node._objectActors = new Set(); let error = aResponse.exception; @@ -3176,8 +3143,7 @@ JSTerm.prototype = { // inspectable. let body = node.querySelector(".webconsole-msg-body"); - let ellipsis = this.hud.document.createElement("description"); - ellipsis.classList.add("hud-clickable"); + let ellipsis = this.hud.document.createElementNS(XHTML_NS, "a"); ellipsis.classList.add("longStringEllipsis"); ellipsis.textContent = l10n.getStr("longStringEllipsis"); @@ -3352,7 +3318,7 @@ JSTerm.prototype = { let deferred = promise.defer(); openPromise = deferred.promise; let document = aOptions.targetElement.ownerDocument; - let iframe = document.createElement("iframe"); + let iframe = document.createElementNS(XHTML_NS, "iframe"); iframe.addEventListener("load", function onIframeLoad(aEvent) { iframe.removeEventListener("load", onIframeLoad, true); @@ -3777,7 +3743,7 @@ JSTerm.prototype = { */ clearPrivateMessages: function JST_clearPrivateMessages() { - let nodes = this.hud.outputNode.querySelectorAll("richlistitem[private]"); + let nodes = this.hud.outputNode.querySelectorAll(".hud-msg-node[private]"); for (let node of nodes) { this.hud.removeOutputMessage(node); } @@ -4507,13 +4473,7 @@ JSTerm.prototype = { */ var Utils = { /** - * Flag to turn on and off scrolling. - */ - scroll: true, - - /** - * Scrolls a node so that it's visible in its containing XUL "scrollbox" - * element. + * Scrolls a node so that it's visible in its containing element. * * @param nsIDOMNode aNode * The node to make visible. @@ -4521,20 +4481,7 @@ var Utils = { */ scrollToVisible: function Utils_scrollToVisible(aNode) { - if (!this.scroll) { - return; - } - - // Find the enclosing richlistbox node. - let richListBoxNode = aNode.parentNode; - while (richListBoxNode.tagName != "richlistbox") { - richListBoxNode = richListBoxNode.parentNode; - } - - // Use the scroll box object interface to ensure the element is visible. - let boxObject = richListBoxNode.scrollBoxObject; - let nsIScrollBoxObject = boxObject.QueryInterface(Ci.nsIScrollBoxObject); - nsIScrollBoxObject.ensureElementIsVisible(aNode); + aNode.scrollIntoView(false); }, /** @@ -4549,10 +4496,9 @@ var Utils = { { let lastNodeHeight = aOutputNode.lastChild ? aOutputNode.lastChild.clientHeight : 0; - let scrollBox = aOutputNode.scrollBoxObject.element; - - return scrollBox.scrollTop + scrollBox.clientHeight >= - scrollBox.scrollHeight - lastNodeHeight / 2; + let scrollNode = aOutputNode.parentNode; + return scrollNode.scrollTop + scrollNode.clientHeight >= + scrollNode.scrollHeight - lastNodeHeight / 2; }, /** @@ -4621,21 +4567,12 @@ function CommandController(aWebConsole) } CommandController.prototype = { - /** - * Copies the currently-selected entries in the Web Console output to the - * clipboard. - */ - copy: function CommandController_copy() - { - this.owner.copySelectedItems(); - }, - /** * Selects all the text in the HUD output. */ selectAll: function CommandController_selectAll() { - this.owner.outputNode.selectAll(); + this.owner.output.selectAllMessages(); }, /** @@ -4648,24 +4585,25 @@ CommandController.prototype = { copyURL: function CommandController_copyURL() { - this.owner.copySelectedItems({ linkOnly: true }); + this.owner.copySelectedItems({ linkOnly: true, contextmenu: true }); }, supportsCommand: function CommandController_supportsCommand(aCommand) { + if (!this.owner || !this.owner.output) { + return false; + } return this.isCommandEnabled(aCommand); }, isCommandEnabled: function CommandController_isCommandEnabled(aCommand) { switch (aCommand) { - case "cmd_copy": - // Only enable "copy" if nodes are selected. - return this.owner.outputNode.selectedCount > 0; case "consoleCmd_openURL": case "consoleCmd_copyURL": { // Only enable URL-related actions if node is Net Activity. - let selectedItem = this.owner.outputNode.selectedItem; + let selectedItem = this.owner.output.getSelectedMessages(1)[0] || + this.owner._contextMenuHandler.lastClickedMessage; return selectedItem && "url" in selectedItem; } case "consoleCmd_clearOutput": @@ -4684,9 +4622,6 @@ CommandController.prototype = { doCommand: function CommandController_doCommand(aCommand) { switch (aCommand) { - case "cmd_copy": - this.copy(); - break; case "consoleCmd_openURL": this.openURL(); break; @@ -5168,14 +5103,14 @@ function ConsoleContextMenu(aOwner) } ConsoleContextMenu.prototype = { + lastClickedMessage: null, + /* * Handle to show/hide context menu item. */ build: function CCM_build(aEvent) { - let view = this.owner.outputNode; - let metadata = this.getSelectionMetadata(view); - + let metadata = this.getSelectionMetadata(aEvent.rangeParent); for (let element of this.popup.children) { element.hidden = this.shouldHideMenuItem(element, metadata); } @@ -5184,21 +5119,27 @@ ConsoleContextMenu.prototype = { /* * Get selection information from the view. * - * @param nsIDOMElement aView - * This should be . - * + * @param nsIDOMElement aClickElement + * The DOM element the user clicked on. * @return object * Selection metadata. */ - getSelectionMetadata: function CCM_getSelectionMetadata(aView) + getSelectionMetadata: function CCM_getSelectionMetadata(aClickElement) { let metadata = { selectionType: "", selection: new Set(), }; - let selectedItems = aView.selectedItems; + let selectedItems = this.owner.output.getSelectedMessages(); + if (!selectedItems.length) { + let clickedItem = this.owner.output.getMessageForElement(aClickElement); + if (clickedItem) { + this.lastClickedMessage = clickedItem; + selectedItems = [clickedItem]; + } + } - metadata.selectionType = (selectedItems > 1) ? "multiple" : "single"; + metadata.selectionType = selectedItems.length > 1 ? "multiple" : "single"; let selection = metadata.selection; for (let item of selectedItems) { @@ -5262,6 +5203,7 @@ ConsoleContextMenu.prototype = { this.popup.removeEventListener("popupshowing", this.build); this.popup = null; this.owner = null; + this.lastClickedMessage = null; }, }; diff --git a/browser/devtools/webconsole/webconsole.xul b/browser/devtools/webconsole/webconsole.xul index a1582fb30a0..071aac09db5 100644 --- a/browser/devtools/webconsole/webconsole.xul +++ b/browser/devtools/webconsole/webconsole.xul @@ -33,7 +33,7 @@ function goUpdateConsoleCommands() { @@ -63,7 +63,7 @@ function goUpdateConsoleCommands() { - + + + @@ -163,9 +165,11 @@ function goUpdateConsoleCommands() { placeholder="&filterOutput.placeholder;" tabindex="2"/> - + +
+
+
+ diff --git a/browser/locales/en-US/chrome/browser/devtools/webconsole.properties b/browser/locales/en-US/chrome/browser/devtools/webconsole.properties index 1aaa054fec0..bfdef0d51f5 100644 --- a/browser/locales/en-US/chrome/browser/devtools/webconsole.properties +++ b/browser/locales/en-US/chrome/browser/devtools/webconsole.properties @@ -187,3 +187,8 @@ propertiesFilterPlaceholder=Filter properties # LOCALIZATION NOTE (emptyPropertiesList): the text that is displayed in the # properties pane when there are no properties to display. emptyPropertiesList=No properties to display + +# LOCALIZATION NOTE (messageRepeats.tooltip): the tooltip text that is displayed +# when you hover the red bubble that shows how many times a message is repeated +# in the web console output. +messageRepeats.tooltip=%S repeats diff --git a/browser/themes/shared/devtools/webconsole.inc.css b/browser/themes/shared/devtools/webconsole.inc.css index f1b942d7d79..4bc5adc7fc3 100644 --- a/browser/themes/shared/devtools/webconsole.inc.css +++ b/browser/themes/shared/devtools/webconsole.inc.css @@ -4,21 +4,42 @@ /* General output styles */ +a { + -moz-user-focus: normal; + -moz-user-input: enabled; + cursor: pointer; + text-decoration: underline; +} + +a:focus { + outline: 1px dashed gray; +} + +/* Workaround for Bug 575675 - FindChildWithRules aRelevantLinkVisited + * assertion when loading HTML page with links in XUL iframe */ +*:visited { } + .webconsole-timestamp { + flex: 0 0 auto; color: GrayText; - margin-top: 0; - margin-bottom: 0; + margin: 4px 0; + font-size: 0.9em; } .hud-msg-node { - list-style-image: url(chrome://browser/skin/devtools/webconsole.png); - -moz-image-region: rect(0, 1px, 0, 0); + display: flex; + -moz-margin-start: 6px; + -moz-margin-end: 8px; + width: calc(100% - 6px - 8px); } .webconsole-msg-icon { - margin: 3px 4px; + background: -moz-image-rect(url(chrome://browser/skin/devtools/webconsole.png), 0, 1, 0, 0) no-repeat; + background-position: center 0.3em; + flex: 0 0 auto; + margin: 0 6px; + padding: 0 4px; width: 8px; - height: 8px; } .hud-clickable { @@ -27,26 +48,26 @@ } .webconsole-msg-body { - margin-top: 0; - margin-bottom: 3px; - -moz-margin-start: 3px; - -moz-margin-end: 6px; + flex: 1 1 100%; white-space: pre-wrap; -} - -.webconsole-msg-body-piece { - margin: 0; + word-wrap: break-word; } .webconsole-msg-url { - margin: 0 6px; + flex: 1 1 auto; + min-width: 5em; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; } /* Repeated messages */ .webconsole-msg-repeat { - margin: 2px 0; - padding-left: 4px; - padding-right: 4px; + -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; @@ -60,41 +81,58 @@ } .webconsole-location { - margin-top: 0; - margin-bottom: 0; - -moz-margin-start: 0; - -moz-margin-end: 6px; + -moz-margin-start: 6px; + flex: 0 0 auto; + align-self: flex-start; width: 10em; text-align: end; + color: -moz-nativehyperlinktext; + text-overflow: ellipsis; + text-decoration: none; + overflow: hidden; + white-space: nowrap; +} + +.webconsole-location:hover, +.webconsole-location:focus { + text-decoration: underline; } .webconsole-mixed-content { color: #FF0000; } -.webconsole-mixed-content-link { - color: #0000EE; - margin: 0; -} - +.webconsole-mixed-content-link, .webconsole-learn-more-link { color: #0000EE; - margin: 0 0 0 4px; -} - -.hud-msg-node[selected="true"] > .webconsole-timestamp, -.hud-msg-node[selected="true"] > .webconsole-location { - color: inherit; + margin: 0 6px; } .jsterm-input-container { background: white; } -.hud-output-node { - -moz-appearance: none; +#output-wrapper { + background: #fff; + color: #000; + direction: ltr; border-bottom: 1px solid ThreeDShadow; - margin: 0; +} + +#output-wrapper2 { + -moz-user-select: text; + -moz-box-flex: 1; + overflow: auto; + /* Set dimensions to show scroll bars when needed. Actual size is changed by + * the flexbox. */ + width: 1px; + height: 1px; +} + +.hud-output-node { + display: table; + table-layout: fixed; + width: 100%; } .hud-filtered-by-type, @@ -136,12 +174,25 @@ border-color: #777; } -.webconsole-msg-network > .webconsole-msg-icon-container { +.webconsole-msg-network > .webconsole-msg-icon { -moz-border-start: solid #000 6px; } -.webconsole-msg-network.webconsole-msg-error { - -moz-image-region: rect(0, 16px, 8px, 8px); +.webconsole-msg-network.webconsole-msg-error > .webconsole-msg-icon { + background-image: -moz-image-rect(url(chrome://browser/skin/devtools/webconsole.png), 0, 16, 8, 8); +} + +.webconsole-msg-network > .webconsole-msg-body { + display: flex; +} + +.webconsole-msg-method { + flex: 0 0 auto; +} + +.webconsole-msg-status { + flex: 0 0 auto; + -moz-margin-start: 6px; } /* CSS styles */ @@ -150,16 +201,16 @@ border-color: #1BA2CC; } -.webconsole-msg-cssparser > .webconsole-msg-icon-container { +.webconsole-msg-cssparser > .webconsole-msg-icon { -moz-border-start: solid #00b6f0 6px; } -.webconsole-msg-cssparser.webconsole-msg-error { - -moz-image-region: rect(8px, 16px, 16px, 8px); +.webconsole-msg-cssparser.webconsole-msg-error > .webconsole-msg-icon { + background-image: -moz-image-rect(url(chrome://browser/skin/devtools/webconsole.png), 8, 16, 16, 8); } -.webconsole-msg-cssparser.webconsole-msg-warn { - -moz-image-region: rect(8px, 24px, 16px, 16px); +.webconsole-msg-cssparser.webconsole-msg-warn > .webconsole-msg-icon { + background-image: -moz-image-rect(url(chrome://browser/skin/devtools/webconsole.png), 8, 24, 16, 16); } /* JS styles */ @@ -168,16 +219,16 @@ border-color: #E98A00; } -.webconsole-msg-exception > .webconsole-msg-icon-container { +.webconsole-msg-exception > .webconsole-msg-icon { -moz-border-start: solid #fb9500 6px; } -.webconsole-msg-exception.webconsole-msg-error { - -moz-image-region: rect(16px, 16px, 24px, 8px); +.webconsole-msg-exception.webconsole-msg-error > .webconsole-msg-icon { + background-image: -moz-image-rect(url(chrome://browser/skin/devtools/webconsole.png), 16, 16, 24, 8); } -.webconsole-msg-exception.webconsole-msg-warn { - -moz-image-region: rect(16px, 24px, 24px, 16px); +.webconsole-msg-exception.webconsole-msg-warn > .webconsole-msg-icon { + background-image: -moz-image-rect(url(chrome://browser/skin/devtools/webconsole.png), 16, 24, 24, 16); } /* Web Developer styles */ @@ -186,35 +237,35 @@ border-color: #929292; } -.webconsole-msg-console > .webconsole-msg-icon-container { +.webconsole-msg-console > .webconsole-msg-icon { -moz-border-start: solid #cbcbcb 6px; } -.webconsole-msg-console.webconsole-msg-error, -.webconsole-msg-output.webconsole-msg-error { - -moz-image-region: rect(24px, 16px, 32px, 8px); +.webconsole-msg-console.webconsole-msg-error > .webconsole-msg-icon, +.webconsole-msg-output.webconsole-msg-error > .webconsole-msg-icon { + background-image: -moz-image-rect(url(chrome://browser/skin/devtools/webconsole.png), 24, 16, 32, 8); } -.webconsole-msg-console.webconsole-msg-warn { - -moz-image-region: rect(24px, 24px, 32px, 16px); +.webconsole-msg-console.webconsole-msg-warn > .webconsole-msg-icon { + background-image: -moz-image-rect(url(chrome://browser/skin/devtools/webconsole.png), 24, 24, 32, 16); } -.webconsole-msg-console.webconsole-msg-info { - -moz-image-region: rect(24px, 32px, 32px, 24px); +.webconsole-msg-console.webconsole-msg-info > .webconsole-msg-icon { + background-image: -moz-image-rect(url(chrome://browser/skin/devtools/webconsole.png), 24, 32, 32, 24); } /* Input and output styles */ -.webconsole-msg-input > .webconsole-msg-icon-container, -.webconsole-msg-output > .webconsole-msg-icon-container { - border-left: solid #808080 6px; +.webconsole-msg-input > .webconsole-msg-icon, +.webconsole-msg-output > .webconsole-msg-icon { + -moz-border-start: solid #808080 6px; } -.webconsole-msg-input { - -moz-image-region: rect(24px, 40px, 32px, 32px); +.webconsole-msg-input > .webconsole-msg-icon { + background-image: -moz-image-rect(url(chrome://browser/skin/devtools/webconsole.png), 24, 40, 32, 32); } -.webconsole-msg-output { - -moz-image-region: rect(24px, 48px, 32px, 40px); +.webconsole-msg-output > .webconsole-msg-icon { + background-image: -moz-image-rect(url(chrome://browser/skin/devtools/webconsole.png), 24, 48, 32, 40); } /* JSTerm Styles */ @@ -239,10 +290,16 @@ color: GrayText; } +.webconsole-msg-inspector .webconsole-msg-body { + display: flex; + flex-direction: column; +} .webconsole-msg-inspector iframe { - height: 7em; + display: block; + flex: 1; margin-bottom: 15px; -moz-margin-end: 15px; + border: 1px solid #ccc; border-radius: 4px; box-shadow: 0 0 12px #dfdfdf; } @@ -254,7 +311,7 @@ /* Security styles */ -.webconsole-msg-security > .webconsole-msg-icon-container { +.webconsole-msg-security > .webconsole-msg-icon { -moz-border-start: solid red 6px; } @@ -263,24 +320,25 @@ border-color: #D12C2C; } -.webconsole-msg-security.webconsole-msg-error { - -moz-image-region: rect(32px, 16px, 40px, 8px); +.webconsole-msg-security.webconsole-msg-error > .webconsole-msg-icon { + background-image: -moz-image-rect(url(chrome://browser/skin/devtools/webconsole.png), 32, 16, 40, 8); } -.webconsole-msg-security.webconsole-msg-warn { - -moz-image-region: rect(32px, 24px, 40px, 16px); +.webconsole-msg-security.webconsole-msg-warn > .webconsole-msg-icon { + background-image: -moz-image-rect(url(chrome://browser/skin/devtools/webconsole.png), 32, 24, 40, 16); } .navigation-marker { color: #aaa; background: linear-gradient(#fff, #bbb, #fff) no-repeat left 50%; background-size: 100% 2px; - -moz-margin-start: 3px; - -moz-margin-end: 6px; + margin-top: 6px; + margin-bottom: 6px; font-size: 0.9em; } .navigation-marker .url { background: #fff; - -moz-padding-end: 6px; + -moz-padding-end: 9px; + text-decoration: none; } diff --git a/toolkit/devtools/webconsole/client.js b/toolkit/devtools/webconsole/client.js index 19ae746e1c6..7ffa5cc8962 100644 --- a/toolkit/devtools/webconsole/client.js +++ b/toolkit/devtools/webconsole/client.js @@ -148,8 +148,8 @@ WebConsoleClient.prototype = { /** * Get Web Console-related preferences on the server. * - * @param object aPreferences - * An object with the preferences you want to retrieve. + * @param array aPreferences + * An array with the preferences you want to retrieve. * @param function [aOnResponse] * Optional function to invoke when the response is received. */ diff --git a/toolkit/devtools/webconsole/network-helper.js b/toolkit/devtools/webconsole/network-helper.js index ed13f3e1a05..83154037c03 100644 --- a/toolkit/devtools/webconsole/network-helper.js +++ b/toolkit/devtools/webconsole/network-helper.js @@ -52,7 +52,7 @@ * Mihai Sucan (Mozilla Corp.) */ -const {Cc, Ci, Cu} = require("chrome"); +const {components, Cc, Ci, Cu} = require("chrome"); loader.lazyImporter(this, "NetUtil", "resource://gre/modules/NetUtil.jsm"); /** @@ -244,7 +244,7 @@ let NetworkHelper = { Ci.nsICachingChannel.LOAD_BYPASS_LOCAL_CACHE_IF_BUSY; NetUtil.asyncFetch(channel, function (aInputStream, aStatusCode, aRequest) { - if (!Components.isSuccessCode(aStatusCode)) { + if (!components.isSuccessCode(aStatusCode)) { aCallback(null); return; } diff --git a/toolkit/devtools/webconsole/utils.js b/toolkit/devtools/webconsole/utils.js index a7cc8f3f7a0..6cce395905f 100644 --- a/toolkit/devtools/webconsole/utils.js +++ b/toolkit/devtools/webconsole/utils.js @@ -174,12 +174,25 @@ let WebConsoleUtils = { */ abbreviateSourceURL: function WCU_abbreviateSourceURL(aSourceURL) { + if (aSourceURL.substr(0, 5) == "data:") { + let commaIndex = aSourceURL.indexOf(","); + if (commaIndex > -1) { + aSourceURL = "data:" + aSourceURL.substring(commaIndex + 1); + } + } + // Remove any query parameters. let hookIndex = aSourceURL.indexOf("?"); if (hookIndex > -1) { aSourceURL = aSourceURL.substring(0, hookIndex); } + // Remove any hash fragments. + let hashIndex = aSourceURL.indexOf("#"); + if (hashIndex > -1) { + aSourceURL = aSourceURL.substring(0, hashIndex); + } + // Remove a trailing "/". if (aSourceURL[aSourceURL.length - 1] == "/") { aSourceURL = aSourceURL.substring(0, aSourceURL.length - 1);