Bug 760876 - Part 1: switch from XUL to XHTML for the Web Console output; r=robcee,paul

This commit is contained in:
Mihai Sucan 2013-09-13 15:06:46 +03:00
parent d9c3eb7320
commit bb5f28f898
8 changed files with 443 additions and 321 deletions

View File

@ -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;

View File

@ -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 <xul:richlistbox>.
*
* @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;
},
};

View File

@ -33,7 +33,7 @@ function goUpdateConsoleCommands() {
<commandset id="consoleCommands"
commandupdater="true"
events="richlistbox-select"
events="focus,select"
oncommandupdate="goUpdateConsoleCommands();">
<command id="consoleCmd_openURL"
oncommand="goDoCommand('consoleCmd_openURL');"/>
@ -63,7 +63,7 @@ function goUpdateConsoleCommands() {
<keyset id="editMenuKeys"/>
<popupset id="mainPopupSet">
<menupopup id="output-contextmenu">
<menupopup id="output-contextmenu" onpopupshowing="goUpdateGlobalEditMenuItems()">
<menuitem id="saveBodiesContextMenu" type="checkbox" label="&saveBodies.label;"
accesskey="&saveBodies.accesskey;"/>
<menuitem id="menu_openURL" label="&openURL.label;"
@ -77,6 +77,8 @@ function goUpdateConsoleCommands() {
</menupopup>
</popupset>
<tooltip id="aHTMLTooltip" page="true"/>
<box class="hud-outer-wrapper devtools-responsive-container" flex="1">
<vbox class="hud-console-wrapper" flex="1">
<toolbar class="hud-console-filter-toolbar devtools-toolbar" mode="full">
@ -163,9 +165,11 @@ function goUpdateConsoleCommands() {
placeholder="&filterOutput.placeholder;" tabindex="2"/>
</toolbar>
<richlistbox class="hud-output-node" orient="vertical" flex="1"
seltype="multiple" context="output-contextmenu"
style="direction:ltr;" tabindex="1"/>
<hbox id="output-wrapper" flex="1" context="output-contextmenu" tooltip="aHTMLTooltip">
<div xmlns="http://www.w3.org/1999/xhtml" id="output-wrapper2">
<div class="hud-output-node" tabindex="1"/>
</div>
</hbox>
<hbox class="jsterm-input-container" style="direction:ltr">
<stack class="jsterm-stack-node" flex="1">

View File

@ -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

View File

@ -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;
}

View File

@ -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.
*/

View File

@ -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;
}

View File

@ -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);