mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-10 11:55:49 +00:00
Bug 722685 - Console logging is slow; r=rcampbell
This commit is contained in:
parent
069ccd90ef
commit
da9958af0a
@ -499,9 +499,8 @@ let Manager = {
|
||||
Manager = ConsoleAPIObserver = JSTerm = ConsoleListener = NetworkMonitor =
|
||||
NetworkResponseListener = ConsoleProgressListener = null;
|
||||
|
||||
Cc = Ci = Cu = XPCOMUtils = Services = gConsoleStorage =
|
||||
WebConsoleUtils = l10n = JSPropertyProvider = NetworkHelper =
|
||||
NetUtil = activityDistributor = null;
|
||||
XPCOMUtils = gConsoleStorage = WebConsoleUtils = l10n = JSPropertyProvider =
|
||||
NetworkHelper = NetUtil = activityDistributor = null;
|
||||
},
|
||||
};
|
||||
|
||||
@ -1503,7 +1502,7 @@ NetworkResponseListener.prototype = {
|
||||
*/
|
||||
_findOpenResponse: function NRL__findOpenResponse()
|
||||
{
|
||||
if (this._foundOpenResponse) {
|
||||
if (!_alive || this._foundOpenResponse) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -1611,7 +1610,9 @@ NetworkResponseListener.prototype = {
|
||||
|
||||
this.receivedData = "";
|
||||
|
||||
NetworkMonitor.sendActivity(this.httpActivity);
|
||||
if (_alive) {
|
||||
NetworkMonitor.sendActivity(this.httpActivity);
|
||||
}
|
||||
|
||||
this.httpActivity.channel = null;
|
||||
this.httpActivity = null;
|
||||
@ -1745,7 +1746,7 @@ let NetworkMonitor = {
|
||||
// NetworkResponseListener is responsible with updating the httpActivity
|
||||
// object with the data from the new object in openResponses.
|
||||
|
||||
if (aTopic != "http-on-examine-response" ||
|
||||
if (!_alive || aTopic != "http-on-examine-response" ||
|
||||
!(aSubject instanceof Ci.nsIHttpChannel)) {
|
||||
return;
|
||||
}
|
||||
|
@ -173,6 +173,14 @@ const GROUP_INDENT = 12;
|
||||
// The pref prefix for webconsole filters
|
||||
const PREFS_PREFIX = "devtools.webconsole.filter.";
|
||||
|
||||
// The number of messages to display in a single display update. If we display
|
||||
// too many messages at once we slow the Firefox UI too much.
|
||||
const MESSAGES_IN_INTERVAL = 30;
|
||||
|
||||
// The delay between display updates - tells how often we should push new
|
||||
// messages to screen.
|
||||
const OUTPUT_INTERVAL = 90; // milliseconds
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
//// Helper for creating the network panel.
|
||||
|
||||
@ -212,58 +220,21 @@ function createElement(aDocument, aTag, aAttributes)
|
||||
* @param integer aCategory
|
||||
* The category of message nodes to limit.
|
||||
* @return number
|
||||
* The current user-selected log limit.
|
||||
* The number of removed nodes.
|
||||
*/
|
||||
function pruneConsoleOutputIfNecessary(aHUDId, aCategory)
|
||||
{
|
||||
// Get the log limit, either from the pref or from the constant.
|
||||
let logLimit;
|
||||
try {
|
||||
let prefName = CATEGORY_CLASS_FRAGMENTS[aCategory];
|
||||
logLimit = Services.prefs.getIntPref("devtools.hud.loglimit." + prefName);
|
||||
} catch (e) {
|
||||
logLimit = DEFAULT_LOG_LIMIT;
|
||||
}
|
||||
|
||||
let hudRef = HUDService.getHudReferenceById(aHUDId);
|
||||
let outputNode = hudRef.outputNode;
|
||||
let logLimit = hudRef.logLimitForCategory(aCategory);
|
||||
|
||||
let scrollBox = outputNode.scrollBoxObject.element;
|
||||
let oldScrollHeight = scrollBox.scrollHeight;
|
||||
let scrolledToBottom = ConsoleUtils.isOutputScrolledToBottom(outputNode);
|
||||
|
||||
// Prune the nodes.
|
||||
let messageNodes = outputNode.querySelectorAll(".webconsole-msg-" +
|
||||
let messageNodes = outputNode.getElementsByClassName("webconsole-msg-" +
|
||||
CATEGORY_CLASS_FRAGMENTS[aCategory]);
|
||||
let removeNodes = messageNodes.length - logLimit;
|
||||
for (let i = 0; i < removeNodes; i++) {
|
||||
let node = messageNodes[i];
|
||||
if (node._evalCacheId && !node._panelOpen) {
|
||||
hudRef.jsterm.clearObjectCache(node._evalCacheId);
|
||||
}
|
||||
let n = Math.max(0, messageNodes.length - logLimit);
|
||||
let toRemove = Array.prototype.slice.call(messageNodes, 0, n);
|
||||
toRemove.forEach(hudRef.removeOutputMessage, hudRef);
|
||||
|
||||
if (node.classList.contains("webconsole-msg-cssparser")) {
|
||||
let desc = messageNodes[i].childNodes[2].textContent;
|
||||
let location = "";
|
||||
if (node.childNodes[4]) {
|
||||
location = node.childNodes[4].getAttribute("title");
|
||||
}
|
||||
delete hudRef.cssNodes[desc + location];
|
||||
}
|
||||
else if (node.classList.contains("webconsole-msg-inspector")) {
|
||||
hudRef.pruneConsoleDirNode(node);
|
||||
continue;
|
||||
}
|
||||
|
||||
node.parentNode.removeChild(node);
|
||||
}
|
||||
|
||||
if (!scrolledToBottom && removeNodes > 0 &&
|
||||
oldScrollHeight != scrollBox.scrollHeight) {
|
||||
scrollBox.scrollTop -= oldScrollHeight - scrollBox.scrollHeight;
|
||||
}
|
||||
|
||||
return logLimit;
|
||||
return n;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
@ -470,11 +441,10 @@ HUD_SERVICE.prototype =
|
||||
{
|
||||
// Go through the nodes and adjust the placement of "webconsole-new-group"
|
||||
// classes.
|
||||
|
||||
let nodes = aOutputNode.querySelectorAll(".hud-msg-node" +
|
||||
":not(.hud-filtered-by-string):not(.hud-filtered-by-type)");
|
||||
let lastTimestamp;
|
||||
for (let i = 0; i < nodes.length; i++) {
|
||||
for (let i = 0, n = nodes.length; i < n; i++) {
|
||||
let thisTimestamp = nodes[i].timestamp;
|
||||
if (lastTimestamp != null &&
|
||||
thisTimestamp >= lastTimestamp + NEW_GROUP_DELAY) {
|
||||
@ -565,9 +535,9 @@ HUD_SERVICE.prototype =
|
||||
{
|
||||
let outputNode = this.getHudReferenceById(aHUDId).outputNode;
|
||||
|
||||
let nodes = outputNode.querySelectorAll(".hud-msg-node");
|
||||
let nodes = outputNode.getElementsByClassName("hud-msg-node");
|
||||
|
||||
for (let i = 0; i < nodes.length; ++i) {
|
||||
for (let i = 0, n = nodes.length; i < n; ++i) {
|
||||
let node = nodes[i];
|
||||
|
||||
// hide nodes that match the strings
|
||||
@ -1054,6 +1024,9 @@ function HeadsUpDisplay(aTab)
|
||||
// create a panel dynamically and attach to the parentNode
|
||||
this.createHUD();
|
||||
|
||||
this._outputQueue = [];
|
||||
this._pruneCategoriesQueue = {};
|
||||
|
||||
// create the JSTerm input element
|
||||
this.jsterm = new JSTerm(this);
|
||||
this.jsterm.inputNode.focus();
|
||||
@ -1065,6 +1038,40 @@ function HeadsUpDisplay(aTab)
|
||||
}
|
||||
|
||||
HeadsUpDisplay.prototype = {
|
||||
/**
|
||||
* Last time when we displayed any message in the output. Timestamp in
|
||||
* milliseconds since the Unix epoch.
|
||||
*
|
||||
* @private
|
||||
* @type number
|
||||
*/
|
||||
_lastOutputFlush: 0,
|
||||
|
||||
/**
|
||||
* The number of messages displayed in the last interval. The interval is
|
||||
* given by OUTPUT_INTERVAL.
|
||||
*
|
||||
* @private
|
||||
* @type number
|
||||
*/
|
||||
_messagesDisplayedInInterval: 0,
|
||||
|
||||
/**
|
||||
* Message nodes are stored here in a queue for later display.
|
||||
*
|
||||
* @private
|
||||
* @type array
|
||||
*/
|
||||
_outputQueue: null,
|
||||
|
||||
/**
|
||||
* Keep track of the categories we need to prune from time to time.
|
||||
*
|
||||
* @private
|
||||
* @type array
|
||||
*/
|
||||
_pruneCategoriesQueue: null,
|
||||
|
||||
/**
|
||||
* Message names that the HUD listens for. These messages come from the remote
|
||||
* Web Console content script.
|
||||
@ -1394,10 +1401,6 @@ HeadsUpDisplay.prototype = {
|
||||
return;
|
||||
}
|
||||
|
||||
// Turn off scrolling for the moment.
|
||||
ConsoleUtils.scroll = false;
|
||||
this.outputNode.hidden = true;
|
||||
|
||||
aRemoteMessages.forEach(function(aMessage) {
|
||||
switch (aMessage._type) {
|
||||
case "PageError":
|
||||
@ -1408,17 +1411,6 @@ HeadsUpDisplay.prototype = {
|
||||
break;
|
||||
}
|
||||
}, this);
|
||||
|
||||
this.outputNode.hidden = false;
|
||||
ConsoleUtils.scroll = true;
|
||||
|
||||
// Scroll to bottom.
|
||||
let numChildren = this.outputNode.childNodes.length;
|
||||
if (numChildren && this.outputNode.clientHeight) {
|
||||
// We also check the clientHeight to force a reflow, otherwise
|
||||
// ensureIndexIsVisible() does not work after outputNode.hidden = false.
|
||||
this.outputNode.ensureIndexIsVisible(numChildren - 1);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
@ -2485,12 +2477,273 @@ HeadsUpDisplay.prototype = {
|
||||
}, false);
|
||||
},
|
||||
|
||||
/**
|
||||
* Output a message node. This filters a node appropriately, then sends it to
|
||||
* the output, regrouping and pruning output as necessary.
|
||||
*
|
||||
* Note: this call is async - the given message node may not be displayed when
|
||||
* you call this method.
|
||||
*
|
||||
* @param nsIDOMNode aNode
|
||||
* The message node to send to the output.
|
||||
* @param nsIDOMNode [aNodeAfter]
|
||||
* Insert the node after the given aNodeAfter (optional).
|
||||
*/
|
||||
outputMessageNode: function HUD_outputMessageNode(aNode, aNodeAfter)
|
||||
{
|
||||
this._outputQueue.push([aNode, aNodeAfter]);
|
||||
this._flushMessageQueue();
|
||||
},
|
||||
|
||||
/**
|
||||
* Try to flush the output message queue. This takes the messages in the
|
||||
* output queue and displays them. Outputting stops at MESSAGES_IN_INTERVAL.
|
||||
* Further output is queued to happen later - see OUTPUT_INTERVAL.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_flushMessageQueue: function HUD__flushMessageQueue()
|
||||
{
|
||||
if ((Date.now() - this._lastOutputFlush) >= OUTPUT_INTERVAL) {
|
||||
this._messagesDisplayedInInterval = 0;
|
||||
}
|
||||
|
||||
// Determine how many messages we can display now.
|
||||
let toDisplay = Math.min(this._outputQueue.length,
|
||||
MESSAGES_IN_INTERVAL -
|
||||
this._messagesDisplayedInInterval);
|
||||
|
||||
if (!toDisplay) {
|
||||
if (!this._outputTimeout && this._outputQueue.length > 0) {
|
||||
this._outputTimeout =
|
||||
this.chromeWindow.setTimeout(function() {
|
||||
delete this._outputTimeout;
|
||||
this._flushMessageQueue();
|
||||
}.bind(this), OUTPUT_INTERVAL);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Try to prune the message queue.
|
||||
let shouldPrune = false;
|
||||
if (this._outputQueue.length > toDisplay && this._pruneOutputQueue()) {
|
||||
toDisplay = Math.min(this._outputQueue.length, toDisplay);
|
||||
shouldPrune = true;
|
||||
}
|
||||
|
||||
let batch = this._outputQueue.splice(0, toDisplay);
|
||||
if (!batch.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
let outputNode = this.outputNode;
|
||||
let lastVisibleNode = null;
|
||||
let scrolledToBottom = ConsoleUtils.isOutputScrolledToBottom(outputNode);
|
||||
let scrollBox = outputNode.scrollBoxObject.element;
|
||||
|
||||
let hudIdSupportsString = WebConsoleUtils.supportsString(this.hudId);
|
||||
|
||||
// Output the current batch of messages.
|
||||
for (let item of batch) {
|
||||
if (this._outputMessageFromQueue(hudIdSupportsString, item)) {
|
||||
lastVisibleNode = item[0];
|
||||
}
|
||||
}
|
||||
|
||||
// Keep track of how many messages we displayed, so we do not display too
|
||||
// many at once.
|
||||
this._messagesDisplayedInInterval += batch.length;
|
||||
|
||||
let oldScrollHeight = 0;
|
||||
|
||||
// Prune messages if needed. We do not do this for every flush call to
|
||||
// improve performance.
|
||||
let removedNodes = 0;
|
||||
if (shouldPrune || !(this._outputQueue.length % 20)) {
|
||||
oldScrollHeight = scrollBox.scrollHeight;
|
||||
|
||||
let categories = Object.keys(this._pruneCategoriesQueue);
|
||||
categories.forEach(function _pruneOutput(aCategory) {
|
||||
removedNodes += pruneConsoleOutputIfNecessary(this.hudId, aCategory);
|
||||
}, this);
|
||||
this._pruneCategoriesQueue = {};
|
||||
}
|
||||
|
||||
// Regroup messages at the end of the queue.
|
||||
if (!this._outputQueue.length) {
|
||||
HUDService.regroupOutput(outputNode);
|
||||
}
|
||||
|
||||
let isInputOutput = lastVisibleNode &&
|
||||
(lastVisibleNode.classList.contains("webconsole-msg-input") ||
|
||||
lastVisibleNode.classList.contains("webconsole-msg-output"));
|
||||
|
||||
// Scroll to the new node if it is not filtered, and if the output node is
|
||||
// scrolled at the bottom or if the new node is a jsterm input/output
|
||||
// message.
|
||||
if (lastVisibleNode && (scrolledToBottom || isInputOutput)) {
|
||||
ConsoleUtils.scrollToVisible(lastVisibleNode);
|
||||
}
|
||||
else if (!scrolledToBottom && removedNodes > 0 &&
|
||||
oldScrollHeight != scrollBox.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;
|
||||
}
|
||||
|
||||
// If the queue is not empty, schedule another flush.
|
||||
if (!this._outputTimeout && this._outputQueue.length > 0) {
|
||||
this._outputTimeout =
|
||||
this.chromeWindow.setTimeout(function() {
|
||||
delete this._outputTimeout;
|
||||
this._flushMessageQueue();
|
||||
}.bind(this), OUTPUT_INTERVAL);
|
||||
}
|
||||
|
||||
this._lastOutputFlush = Date.now();
|
||||
},
|
||||
|
||||
/**
|
||||
* Output a message from the queue.
|
||||
*
|
||||
* @private
|
||||
* @param nsISupportsString aHudIdSupportsString
|
||||
* The HUD ID as an nsISupportsString.
|
||||
* @param array aItem
|
||||
* An item from the output queue - this item represents a message.
|
||||
* @return boolean
|
||||
* True if the message is visible, false otherwise.
|
||||
*/
|
||||
_outputMessageFromQueue:
|
||||
function HUD__outputMessageFromQueue(aHudIdSupportsString, aItem)
|
||||
{
|
||||
let [node, afterNode] = aItem;
|
||||
|
||||
let isFiltered = ConsoleUtils.filterMessageNode(node, this.hudId);
|
||||
|
||||
let isRepeated = false;
|
||||
if (node.classList.contains("webconsole-msg-cssparser")) {
|
||||
isRepeated = ConsoleUtils.filterRepeatedCSS(node, this.outputNode,
|
||||
this.hudId);
|
||||
}
|
||||
|
||||
if (!isRepeated &&
|
||||
(node.classList.contains("webconsole-msg-console") ||
|
||||
node.classList.contains("webconsole-msg-exception") ||
|
||||
node.classList.contains("webconsole-msg-error"))) {
|
||||
isRepeated = ConsoleUtils.filterRepeatedConsole(node, this.outputNode);
|
||||
}
|
||||
|
||||
if (!isRepeated) {
|
||||
this.outputNode.insertBefore(node,
|
||||
afterNode ? afterNode.nextSibling : null);
|
||||
this._pruneCategoriesQueue[node.category] = true;
|
||||
}
|
||||
|
||||
let nodeID = node.getAttribute("id");
|
||||
Services.obs.notifyObservers(aHudIdSupportsString,
|
||||
"web-console-message-created", nodeID);
|
||||
|
||||
return !isRepeated && !isFiltered;
|
||||
},
|
||||
|
||||
/**
|
||||
* Prune the queue of messages to display. This avoids displaying messages
|
||||
* that will be removed at the end of the queue anyway.
|
||||
* @private
|
||||
*/
|
||||
_pruneOutputQueue: function HUD__pruneOutputQueue()
|
||||
{
|
||||
let nodes = {};
|
||||
|
||||
// Group the messages per category.
|
||||
this._outputQueue.forEach(function(aItem, aIndex) {
|
||||
let [node] = aItem;
|
||||
let category = node.category;
|
||||
if (!(category in nodes)) {
|
||||
nodes[category] = [];
|
||||
}
|
||||
nodes[category].push(aIndex);
|
||||
}, this);
|
||||
|
||||
let pruned = 0;
|
||||
|
||||
// Loop through the categories we found and prune if needed.
|
||||
for (let category in nodes) {
|
||||
let limit = this.logLimitForCategory(category);
|
||||
let indexes = nodes[category];
|
||||
if (indexes.length > limit) {
|
||||
let n = Math.max(0, indexes.length - limit);
|
||||
pruned += n;
|
||||
for (let i = n - 1; i >= 0; i--) {
|
||||
let node = this._outputQueue[indexes[i]][0];
|
||||
this._outputQueue.splice(indexes[i], 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return pruned;
|
||||
},
|
||||
|
||||
/**
|
||||
* Retrieve the limit of messages for a specific category.
|
||||
*
|
||||
* @param number aCategory
|
||||
* The category of messages you want to retrieve the limit for. See the
|
||||
* CATEGORY_* constants.
|
||||
* @return number
|
||||
* The number of messages allowed for the specific category.
|
||||
*/
|
||||
logLimitForCategory: function HUD_logLimitForCategory(aCategory)
|
||||
{
|
||||
let logLimit = DEFAULT_LOG_LIMIT;
|
||||
|
||||
try {
|
||||
let prefName = CATEGORY_CLASS_FRAGMENTS[aCategory];
|
||||
logLimit = Services.prefs.getIntPref("devtools.hud.loglimit." + prefName);
|
||||
logLimit = Math.max(logLimit, 1);
|
||||
}
|
||||
catch (e) { }
|
||||
|
||||
return logLimit;
|
||||
},
|
||||
|
||||
/**
|
||||
* Remove a given message from the output.
|
||||
*
|
||||
* @param nsIDOMNode aNode
|
||||
* The message node you want to remove.
|
||||
*/
|
||||
removeOutputMessage: function HUD_removeOutputMessage(aNode)
|
||||
{
|
||||
if (aNode._evalCacheId && !aNode._panelOpen) {
|
||||
this.jsterm.clearObjectCache(aNode._evalCacheId);
|
||||
}
|
||||
|
||||
if (aNode.classList.contains("webconsole-msg-cssparser")) {
|
||||
let desc = aNode.childNodes[2].textContent;
|
||||
let location = "";
|
||||
if (aNode.childNodes[4]) {
|
||||
location = aNode.childNodes[4].getAttribute("title");
|
||||
}
|
||||
delete this.cssNodes[desc + location];
|
||||
}
|
||||
else if (aNode.classList.contains("webconsole-msg-inspector")) {
|
||||
this.pruneConsoleDirNode(aNode);
|
||||
return;
|
||||
}
|
||||
|
||||
aNode.parentNode.removeChild(aNode);
|
||||
},
|
||||
|
||||
/**
|
||||
* Destroy the HUD object. Call this method to avoid memory leaks when the Web
|
||||
* Console is closed.
|
||||
*/
|
||||
destroy: function HUD_destroy()
|
||||
{
|
||||
this._outputQueue = [];
|
||||
|
||||
this.sendMessageToContent("WebConsole:Destroy", {});
|
||||
|
||||
this._messageListeners.forEach(function(aName) {
|
||||
@ -2533,6 +2786,8 @@ HeadsUpDisplay.prototype = {
|
||||
delete this.messageManager;
|
||||
delete this.browser;
|
||||
delete this.chromeDocument;
|
||||
delete this.chromeWindow;
|
||||
delete this.outputNode;
|
||||
|
||||
this.positionMenuitems.above.removeEventListener("command",
|
||||
this._positionConsoleAbove, false);
|
||||
@ -2592,7 +2847,7 @@ function JSTerm(aHud)
|
||||
|
||||
this.hudId = this.hud.hudId;
|
||||
|
||||
this.lastCompletion = {};
|
||||
this.lastCompletion = { value: null };
|
||||
this.history = [];
|
||||
this.historyIndex = 0;
|
||||
this.historyPlaceHolder = 0; // this.history.length;
|
||||
@ -2890,17 +3145,7 @@ JSTerm.prototype = {
|
||||
let outputNode = hud.outputNode;
|
||||
let node;
|
||||
while ((node = outputNode.firstChild)) {
|
||||
if (node._evalCacheId && !node._panelOpen) {
|
||||
this.clearObjectCache(node._evalCacheId);
|
||||
}
|
||||
|
||||
if (node.classList &&
|
||||
node.classList.contains("webconsole-msg-inspector")) {
|
||||
hud.pruneConsoleDirNode(node);
|
||||
}
|
||||
else {
|
||||
outputNode.removeChild(node);
|
||||
}
|
||||
hud.removeOutputMessage(node);
|
||||
}
|
||||
|
||||
hud.HUDBox.lastTimestamp = 0;
|
||||
@ -3244,7 +3489,11 @@ JSTerm.prototype = {
|
||||
input: this.inputNode.value,
|
||||
};
|
||||
|
||||
this.lastCompletion = {requestId: message.id, completionType: aType};
|
||||
this.lastCompletion = {
|
||||
requestId: message.id,
|
||||
completionType: aType,
|
||||
value: null,
|
||||
};
|
||||
let callback = this._receiveAutocompleteProperties.bind(this, aCallback);
|
||||
this.hud.sendMessageToContent("JSTerm:Autocomplete", message, callback);
|
||||
},
|
||||
@ -3336,7 +3585,7 @@ JSTerm.prototype = {
|
||||
clearCompletion: function JSTF_clearCompletion()
|
||||
{
|
||||
this.autocompletePopup.clearItems();
|
||||
this.lastCompletion = {};
|
||||
this.lastCompletion = { value: null };
|
||||
this.updateCompleteNode("");
|
||||
if (this.autocompletePopup.isOpen) {
|
||||
this.autocompletePopup.hidePopup();
|
||||
@ -3389,7 +3638,10 @@ JSTerm.prototype = {
|
||||
*/
|
||||
clearObjectCache: function JST_clearObjectCache(aCacheId)
|
||||
{
|
||||
this.hud.sendMessageToContent("JSTerm:ClearObjectCache", {cacheId: aCacheId});
|
||||
if (this.hud) {
|
||||
this.hud.sendMessageToContent("JSTerm:ClearObjectCache",
|
||||
{ cacheId: aCacheId });
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
@ -3887,13 +4139,18 @@ ConsoleUtils = {
|
||||
* The newly-created message node.
|
||||
* @param string aHUDId
|
||||
* The ID of the HUD which this node is to be inserted into.
|
||||
* @return boolean
|
||||
* True if the message was filtered or false otherwise.
|
||||
*/
|
||||
filterMessageNode: function ConsoleUtils_filterMessageNode(aNode, aHUDId) {
|
||||
let isFiltered = false;
|
||||
|
||||
// Filter by the message type.
|
||||
let prefKey = MESSAGE_PREFERENCE_KEYS[aNode.category][aNode.severity];
|
||||
if (prefKey && !HUDService.getFilterState(aHUDId, prefKey)) {
|
||||
// The node is filtered by type.
|
||||
aNode.classList.add("hud-filtered-by-type");
|
||||
isFiltered = true;
|
||||
}
|
||||
|
||||
// Filter on the search string.
|
||||
@ -3903,7 +4160,10 @@ ConsoleUtils = {
|
||||
// if string matches the filter text
|
||||
if (!HUDService.stringMatchesFilters(text, search)) {
|
||||
aNode.classList.add("hud-filtered-by-string");
|
||||
isFiltered = true;
|
||||
}
|
||||
|
||||
return isFiltered;
|
||||
},
|
||||
|
||||
/**
|
||||
@ -4010,50 +4270,8 @@ ConsoleUtils = {
|
||||
*/
|
||||
outputMessageNode:
|
||||
function ConsoleUtils_outputMessageNode(aNode, aHUDId, aNodeAfter) {
|
||||
ConsoleUtils.filterMessageNode(aNode, aHUDId);
|
||||
let outputNode = HUDService.hudReferences[aHUDId].outputNode;
|
||||
|
||||
let scrolledToBottom = ConsoleUtils.isOutputScrolledToBottom(outputNode);
|
||||
|
||||
let isRepeated = false;
|
||||
if (aNode.classList.contains("webconsole-msg-cssparser")) {
|
||||
isRepeated = this.filterRepeatedCSS(aNode, outputNode, aHUDId);
|
||||
}
|
||||
|
||||
if (!isRepeated &&
|
||||
(aNode.classList.contains("webconsole-msg-console") ||
|
||||
aNode.classList.contains("webconsole-msg-exception") ||
|
||||
aNode.classList.contains("webconsole-msg-error"))) {
|
||||
isRepeated = this.filterRepeatedConsole(aNode, outputNode);
|
||||
}
|
||||
|
||||
if (!isRepeated) {
|
||||
outputNode.insertBefore(aNode, aNodeAfter ? aNodeAfter.nextSibling : null);
|
||||
}
|
||||
|
||||
HUDService.regroupOutput(outputNode);
|
||||
|
||||
if (pruneConsoleOutputIfNecessary(aHUDId, aNode.category) == 0) {
|
||||
// We can't very well scroll to make the message node visible if the log
|
||||
// limit is zero and the node was destroyed in the first place.
|
||||
return;
|
||||
}
|
||||
|
||||
let isInputOutput = aNode.classList.contains("webconsole-msg-input") ||
|
||||
aNode.classList.contains("webconsole-msg-output");
|
||||
let isFiltered = aNode.classList.contains("hud-filtered-by-string") ||
|
||||
aNode.classList.contains("hud-filtered-by-type");
|
||||
|
||||
// Scroll to the new node if it is not filtered, and if the output node is
|
||||
// scrolled at the bottom or if the new node is a jsterm input/output
|
||||
// message.
|
||||
if (!isFiltered && !isRepeated && (scrolledToBottom || isInputOutput)) {
|
||||
ConsoleUtils.scrollToVisible(aNode);
|
||||
}
|
||||
|
||||
let id = WebConsoleUtils.supportsString(aHUDId);
|
||||
let nodeID = aNode.getAttribute("id");
|
||||
Services.obs.notifyObservers(id, "web-console-message-created", nodeID);
|
||||
let hud = HUDService.getHudReferenceById(aHUDId);
|
||||
hud.outputMessageNode(aNode, aNodeAfter);
|
||||
},
|
||||
|
||||
/**
|
||||
@ -4083,6 +4301,10 @@ ConsoleUtils = {
|
||||
HeadsUpDisplayUICommands = {
|
||||
refreshCommand: function UIC_refreshCommand() {
|
||||
var window = HUDService.currentContext();
|
||||
if (!window) {
|
||||
return;
|
||||
}
|
||||
|
||||
let command = window.document.getElementById("Tools:WebConsole");
|
||||
if (this.getOpenHUD() != null) {
|
||||
command.setAttribute("checked", true);
|
||||
|
@ -888,6 +888,9 @@ function JSPropertyProvider(aScope, aInputValue)
|
||||
matchProp = properties.pop().trimLeft();
|
||||
for (let i = 0; i < properties.length; i++) {
|
||||
let prop = properties[i].trim();
|
||||
if (!prop) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// If obj is undefined or null, then there is no chance to run completion
|
||||
// on it. Exit here.
|
||||
|
@ -14,25 +14,32 @@ function test() {
|
||||
}
|
||||
|
||||
function onLoad(aEvent) {
|
||||
browser.removeEventListener(aEvent.type, arguments.callee, true);
|
||||
openConsole();
|
||||
|
||||
browser.addEventListener("load", testBasicNetLogging, true);
|
||||
content.location = TEST_NETWORK_URI;
|
||||
}
|
||||
|
||||
function testBasicNetLogging(aEvent) {
|
||||
browser.removeEventListener(aEvent.type, arguments.callee, true);
|
||||
|
||||
outputNode = HUDService.getHudByWindow(content).outputNode;
|
||||
|
||||
executeSoon(function() {
|
||||
findLogEntry("test-network.html");
|
||||
findLogEntry("testscript.js");
|
||||
findLogEntry("test-image.png");
|
||||
findLogEntry("network console");
|
||||
|
||||
finishTest();
|
||||
browser.removeEventListener(aEvent.type, onLoad, true);
|
||||
openConsole(null, function() {
|
||||
browser.addEventListener("load", testBasicNetLogging, true);
|
||||
content.location = TEST_NETWORK_URI;
|
||||
});
|
||||
}
|
||||
|
||||
function testBasicNetLogging(aEvent) {
|
||||
browser.removeEventListener(aEvent.type, testBasicNetLogging, true);
|
||||
|
||||
outputNode = HUDService.getHudByWindow(content).outputNode;
|
||||
|
||||
waitForSuccess({
|
||||
name: "network console message",
|
||||
validatorFn: function()
|
||||
{
|
||||
return outputNode.textContent.indexOf("running network console") > -1;
|
||||
},
|
||||
successFn: function()
|
||||
{
|
||||
findLogEntry("test-network.html");
|
||||
findLogEntry("testscript.js");
|
||||
findLogEntry("test-image.png");
|
||||
finishTest();
|
||||
},
|
||||
failureFn: finishTest,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -18,32 +18,61 @@ function test() {
|
||||
function testGroups(HUD) {
|
||||
let jsterm = HUD.jsterm;
|
||||
let outputNode = HUD.outputNode;
|
||||
jsterm.clearOutput();
|
||||
|
||||
// We test for one group by testing for zero "new" groups. The
|
||||
// "webconsole-new-group" class creates a divider. Thus one group is
|
||||
// indicated by zero new groups, two groups are indicated by one new group,
|
||||
// and so on.
|
||||
|
||||
let waitForSecondMessage = {
|
||||
name: "second console message",
|
||||
validatorFn: function()
|
||||
{
|
||||
return outputNode.querySelectorAll(".webconsole-msg-output").length == 2;
|
||||
},
|
||||
successFn: function()
|
||||
{
|
||||
let timestamp1 = Date.now();
|
||||
if (timestamp1 - timestamp0 < 5000) {
|
||||
is(outputNode.querySelectorAll(".webconsole-new-group").length, 0,
|
||||
"no group dividers exist after the second console message");
|
||||
}
|
||||
|
||||
for (let i = 0; i < outputNode.itemCount; i++) {
|
||||
outputNode.getItemAtIndex(i).timestamp = 0; // a "far past" value
|
||||
}
|
||||
|
||||
jsterm.execute("2");
|
||||
waitForSuccess(waitForThirdMessage);
|
||||
},
|
||||
failureFn: finishTest,
|
||||
};
|
||||
|
||||
let waitForThirdMessage = {
|
||||
name: "one group divider exists after the third console message",
|
||||
validatorFn: function()
|
||||
{
|
||||
return outputNode.querySelectorAll(".webconsole-new-group").length == 1;
|
||||
},
|
||||
successFn: finishTest,
|
||||
failureFn: finishTest,
|
||||
};
|
||||
|
||||
let timestamp0 = Date.now();
|
||||
jsterm.execute("0");
|
||||
is(outputNode.querySelectorAll(".webconsole-new-group").length, 0,
|
||||
"no group dividers exist after the first console message");
|
||||
|
||||
jsterm.execute("1");
|
||||
let timestamp1 = Date.now();
|
||||
if (timestamp1 - timestamp0 < 5000) {
|
||||
is(outputNode.querySelectorAll(".webconsole-new-group").length, 0,
|
||||
"no group dividers exist after the second console message");
|
||||
}
|
||||
|
||||
for (let i = 0; i < outputNode.itemCount; i++) {
|
||||
outputNode.getItemAtIndex(i).timestamp = 0; // a "far past" value
|
||||
}
|
||||
|
||||
jsterm.execute("2");
|
||||
is(outputNode.querySelectorAll(".webconsole-new-group").length, 1,
|
||||
"one group divider exists after the third console message");
|
||||
|
||||
finishTest();
|
||||
waitForSuccess({
|
||||
name: "no group dividers exist after the first console message",
|
||||
validatorFn: function()
|
||||
{
|
||||
return outputNode.querySelectorAll(".webconsole-new-group").length == 0;
|
||||
},
|
||||
successFn: function()
|
||||
{
|
||||
jsterm.execute("1");
|
||||
waitForSuccess(waitForSecondMessage);
|
||||
},
|
||||
failureFn: finishTest,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -96,21 +96,6 @@ function testGen() {
|
||||
is(countMessageNodes(), 30, "there are 30 message nodes in the output " +
|
||||
"when the log limit is set to 30");
|
||||
|
||||
prefBranch.setIntPref("console", 0);
|
||||
console.log("baz");
|
||||
|
||||
waitForSuccess({
|
||||
name: "clear output",
|
||||
validatorFn: function()
|
||||
{
|
||||
return countMessageNodes() == 0;
|
||||
},
|
||||
successFn: testNext,
|
||||
failureFn: finishTest,
|
||||
});
|
||||
|
||||
yield;
|
||||
|
||||
prefBranch.clearUserPref("console");
|
||||
hud = testDriver = prefBranch = console = outputNode = null;
|
||||
finishTest();
|
||||
|
@ -23,18 +23,36 @@ function consoleOpened(aHud) {
|
||||
hud.filterBox.value = "test message";
|
||||
HUDService.updateFilterText(hud.filterBox);
|
||||
|
||||
browser.addEventListener("load", tabReload, true);
|
||||
let waitForNetwork = {
|
||||
name: "network message",
|
||||
validatorFn: function()
|
||||
{
|
||||
return hud.outputNode.querySelector(".webconsole-msg-network");
|
||||
},
|
||||
successFn: testScroll,
|
||||
failureFn: finishTest,
|
||||
};
|
||||
|
||||
executeSoon(function() {
|
||||
content.location.reload();
|
||||
waitForSuccess({
|
||||
name: "console messages displayed",
|
||||
validatorFn: function()
|
||||
{
|
||||
return hud.outputNode.textContent.indexOf("test message 199") > -1;
|
||||
},
|
||||
successFn: function()
|
||||
{
|
||||
browser.addEventListener("load", function onReload() {
|
||||
browser.removeEventListener("load", onReload, true);
|
||||
waitForSuccess(waitForNetwork);
|
||||
}, true);
|
||||
content.location.reload();
|
||||
},
|
||||
failureFn: finishTest,
|
||||
});
|
||||
}
|
||||
|
||||
function tabReload(aEvent) {
|
||||
browser.removeEventListener(aEvent.type, tabReload, true);
|
||||
|
||||
function testScroll() {
|
||||
let msgNode = hud.outputNode.querySelector(".webconsole-msg-network");
|
||||
ok(msgNode, "found network message");
|
||||
ok(msgNode.classList.contains("hud-filtered-by-type"),
|
||||
"network message is filtered by type");
|
||||
ok(msgNode.classList.contains("hud-filtered-by-string"),
|
||||
|
@ -22,24 +22,28 @@ let TestObserver = {
|
||||
};
|
||||
|
||||
function tabLoad(aEvent) {
|
||||
browser.removeEventListener(aEvent.type, arguments.callee, true);
|
||||
browser.removeEventListener(aEvent.type, tabLoad, true);
|
||||
|
||||
openConsole();
|
||||
|
||||
let hudId = HUDService.getHudIdByWindow(content);
|
||||
hud = HUDService.hudReferences[hudId];
|
||||
|
||||
Services.obs.addObserver(TestObserver, "console-api-log-event", false);
|
||||
content.location.reload();
|
||||
openConsole(null, function(aHud) {
|
||||
hud = aHud;
|
||||
Services.obs.addObserver(TestObserver, "console-api-log-event", false);
|
||||
content.location.reload();
|
||||
});
|
||||
}
|
||||
|
||||
function performTest() {
|
||||
isnot(hud.outputNode.textContent.indexOf("foobarBug613013"), -1,
|
||||
"console.log() message found");
|
||||
|
||||
Services.obs.removeObserver(TestObserver, "console-api-log-event");
|
||||
TestObserver = null;
|
||||
finishTest();
|
||||
|
||||
waitForSuccess({
|
||||
name: "console.log() message",
|
||||
validatorFn: function()
|
||||
{
|
||||
return hud.outputNode.textContent.indexOf("foobarBug613013") > -1;
|
||||
},
|
||||
successFn: finishTest,
|
||||
failureFn: finishTest,
|
||||
});
|
||||
}
|
||||
|
||||
function test() {
|
||||
|
@ -17,6 +17,8 @@ function consoleOpened(hud) {
|
||||
content.console.log("test message " + i);
|
||||
}
|
||||
|
||||
let oldScrollTop = -1;
|
||||
|
||||
waitForSuccess({
|
||||
name: "console.log messages displayed",
|
||||
validatorFn: function()
|
||||
@ -25,11 +27,24 @@ function consoleOpened(hud) {
|
||||
},
|
||||
successFn: function()
|
||||
{
|
||||
let oldScrollTop = boxObject.scrollTop;
|
||||
oldScrollTop = boxObject.scrollTop;
|
||||
ok(oldScrollTop > 0, "scroll location is not at the top");
|
||||
|
||||
hud.jsterm.execute("'hello world'");
|
||||
|
||||
waitForSuccess(waitForExecute);
|
||||
},
|
||||
failureFn: finishTest,
|
||||
});
|
||||
|
||||
let waitForExecute = {
|
||||
name: "jsterm output displayed",
|
||||
validatorFn: function()
|
||||
{
|
||||
return outputNode.querySelector(".webconsole-msg-output");
|
||||
},
|
||||
successFn: function()
|
||||
{
|
||||
isnot(boxObject.scrollTop, oldScrollTop, "scroll location updated");
|
||||
|
||||
oldScrollTop = boxObject.scrollTop;
|
||||
@ -40,7 +55,7 @@ function consoleOpened(hud) {
|
||||
finishTest();
|
||||
},
|
||||
failureFn: finishTest,
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
function test() {
|
||||
|
@ -56,14 +56,15 @@ function testWebDevLimits2() {
|
||||
}
|
||||
|
||||
waitForSuccess({
|
||||
name: "11 console.log messages displayed",
|
||||
name: "10 console.log messages displayed and one pruned",
|
||||
validatorFn: function()
|
||||
{
|
||||
return outputNode.textContent.indexOf("test message 10") > -1;
|
||||
let message0 = outputNode.textContent.indexOf("test message 0");
|
||||
let message10 = outputNode.textContent.indexOf("test message 10");
|
||||
return message0 == -1 && message10 > -1;
|
||||
},
|
||||
successFn: function()
|
||||
{
|
||||
testLogEntry(outputNode, "test message 0", "first message is pruned", false, true);
|
||||
findLogEntry("test message 1");
|
||||
// Check if the sentinel entry is still there.
|
||||
findLogEntry("bar is not defined");
|
||||
@ -161,14 +162,28 @@ function loadImage() {
|
||||
gCounter++;
|
||||
return;
|
||||
}
|
||||
is(gCounter, 11, "loaded 11 files");
|
||||
testLogEntry(outputNode, "test-image.png?_fubar=0", "first message is pruned", false, true);
|
||||
findLogEntry("test-image.png?_fubar=1");
|
||||
// Check if the sentinel entry is still there.
|
||||
findLogEntry("testing Net limits");
|
||||
|
||||
Services.prefs.setIntPref("devtools.hud.loglimit.network", gOldPref);
|
||||
testCssLimits();
|
||||
is(gCounter, 11, "loaded 11 files");
|
||||
|
||||
waitForSuccess({
|
||||
name: "loaded 11 files, one message pruned",
|
||||
validatorFn: function()
|
||||
{
|
||||
let message0 = outputNode.querySelector('*[value*="test-image.png?_fubar=0"]');
|
||||
let message10 = outputNode.querySelector('*[value*="test-image.png?_fubar=10"]');
|
||||
return !message0 && message10;
|
||||
},
|
||||
successFn: function()
|
||||
{
|
||||
findLogEntry("test-image.png?_fubar=1");
|
||||
// Check if the sentinel entry is still there.
|
||||
findLogEntry("testing Net limits");
|
||||
|
||||
Services.prefs.setIntPref("devtools.hud.loglimit.network", gOldPref);
|
||||
testCssLimits();
|
||||
},
|
||||
failureFn: testCssLimits,
|
||||
});
|
||||
}
|
||||
|
||||
function testCssLimits() {
|
||||
|
@ -16,29 +16,38 @@ function test() {
|
||||
}
|
||||
|
||||
function onLoad(aEvent) {
|
||||
browser.removeEventListener(aEvent.type, arguments.callee, true);
|
||||
openConsole();
|
||||
hudId = HUDService.getHudIdByWindow(content);
|
||||
|
||||
browser.addEventListener("load", testConsoleFileLocation, true);
|
||||
content.location = TEST_URI;
|
||||
}
|
||||
|
||||
function testConsoleFileLocation(aEvent) {
|
||||
browser.removeEventListener(aEvent.type, arguments.callee, true);
|
||||
|
||||
outputNode = HUDService.hudReferences[hudId].outputNode;
|
||||
|
||||
executeSoon(function() {
|
||||
findLogEntry("test-file-location.js");
|
||||
findLogEntry("message for level");
|
||||
findLogEntry("test-file-location.js:5");
|
||||
findLogEntry("test-file-location.js:6");
|
||||
findLogEntry("test-file-location.js:7");
|
||||
findLogEntry("test-file-location.js:8");
|
||||
findLogEntry("test-file-location.js:9");
|
||||
|
||||
finishTest();
|
||||
browser.removeEventListener(aEvent.type, onLoad, true);
|
||||
openConsole(null, function(aHud) {
|
||||
hud = aHud;
|
||||
browser.addEventListener("load", testConsoleFileLocation, true);
|
||||
content.location = TEST_URI;
|
||||
});
|
||||
}
|
||||
|
||||
function testConsoleFileLocation(aEvent) {
|
||||
browser.removeEventListener(aEvent.type, testConsoleFileLocation, true);
|
||||
|
||||
outputNode = hud.outputNode;
|
||||
|
||||
waitForSuccess({
|
||||
name: "console API messages",
|
||||
validatorFn: function()
|
||||
{
|
||||
return outputNode.textContent.indexOf("message for level debug") > -1;
|
||||
},
|
||||
successFn: function()
|
||||
{
|
||||
findLogEntry("test-file-location.js");
|
||||
findLogEntry("message for level");
|
||||
findLogEntry("test-file-location.js:5");
|
||||
findLogEntry("test-file-location.js:6");
|
||||
findLogEntry("test-file-location.js:7");
|
||||
findLogEntry("test-file-location.js:8");
|
||||
findLogEntry("test-file-location.js:9");
|
||||
|
||||
finishTest();
|
||||
},
|
||||
failureFn: finishTest,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -18,18 +18,25 @@ function test() {
|
||||
function consoleOpened(hud) {
|
||||
outputNode = hud.outputNode;
|
||||
|
||||
executeSoon(function() {
|
||||
findLogEntry("aTimer: timer started");
|
||||
findLogEntry("ms");
|
||||
|
||||
// The next test makes sure that timers with the same name but in separate
|
||||
// tabs, do not contain the same value.
|
||||
addTab("data:text/html;charset=utf-8,<script type='text/javascript'>" +
|
||||
"console.timeEnd('bTimer');</script>");
|
||||
browser.addEventListener("load", function onLoad() {
|
||||
browser.removeEventListener("load", onLoad, true);
|
||||
openConsole(null, testTimerIndependenceInTabs);
|
||||
}, true);
|
||||
waitForSuccess({
|
||||
name: "aTimer started",
|
||||
validatorFn: function()
|
||||
{
|
||||
return outputNode.textContent.indexOf("aTimer: timer started") > -1;
|
||||
},
|
||||
successFn: function()
|
||||
{
|
||||
findLogEntry("ms");
|
||||
// The next test makes sure that timers with the same name but in separate
|
||||
// tabs, do not contain the same value.
|
||||
addTab("data:text/html;charset=utf-8,<script type='text/javascript'>" +
|
||||
"console.timeEnd('bTimer');</script>");
|
||||
browser.addEventListener("load", function onLoad() {
|
||||
browser.removeEventListener("load", onLoad, true);
|
||||
openConsole(null, testTimerIndependenceInTabs);
|
||||
}, true);
|
||||
},
|
||||
failureFn: finishTest,
|
||||
});
|
||||
}
|
||||
|
||||
@ -56,22 +63,30 @@ function testTimerIndependenceInSameTab() {
|
||||
let hud = HUDService.hudReferences[hudId];
|
||||
outputNode = hud.outputNode;
|
||||
|
||||
executeSoon(function() {
|
||||
findLogEntry("bTimer: timer started");
|
||||
hud.jsterm.clearOutput();
|
||||
waitForSuccess({
|
||||
name: "bTimer started",
|
||||
validatorFn: function()
|
||||
{
|
||||
return outputNode.textContent.indexOf("bTimer: timer started") > -1;
|
||||
},
|
||||
successFn: function() {
|
||||
hud.jsterm.clearOutput();
|
||||
|
||||
// Now the following console.timeEnd() call shouldn't display anything,
|
||||
// if the timers in different pages are not related.
|
||||
browser.addEventListener("load", function onLoad() {
|
||||
browser.removeEventListener("load", onLoad, true);
|
||||
executeSoon(testTimerIndependenceInSameTabAgain);
|
||||
}, true);
|
||||
content.location = "data:text/html;charset=utf-8,<script type='text/javascript'>" +
|
||||
"console.timeEnd('bTimer');</script>";
|
||||
// Now the following console.timeEnd() call shouldn't display anything,
|
||||
// if the timers in different pages are not related.
|
||||
browser.addEventListener("load", function onLoad() {
|
||||
browser.removeEventListener("load", onLoad, true);
|
||||
executeSoon(testTimerIndependenceInSameTabAgain);
|
||||
}, true);
|
||||
content.location = "data:text/html;charset=utf-8," +
|
||||
"<script type='text/javascript'>" +
|
||||
"console.timeEnd('bTimer');</script>";
|
||||
},
|
||||
failureFn: finishTest,
|
||||
});
|
||||
}
|
||||
|
||||
function testTimerIndependenceInSameTabAgain(hud) {
|
||||
function testTimerIndependenceInSameTabAgain() {
|
||||
let hudId = HUDService.getHudIdByWindow(content);
|
||||
let hud = HUDService.hudReferences[hudId];
|
||||
outputNode = hud.outputNode;
|
||||
|
@ -7,13 +7,28 @@
|
||||
let Cu = Components.utils;
|
||||
let Ci = Components.interfaces;
|
||||
let Cc = Components.classes;
|
||||
|
||||
// The maximum allowed number of concurrent timers per page.
|
||||
const MAX_PAGE_TIMERS = 10000;
|
||||
|
||||
// The regular expression used to parse %s/%d and other placeholders for
|
||||
// variables in strings that need to be interpolated.
|
||||
const ARGUMENT_PATTERN = /%\d*\.?\d*([osdif])\b/g;
|
||||
|
||||
// The maximum stacktrace depth when populating the stacktrace array used for
|
||||
// console.trace().
|
||||
const DEFAULT_MAX_STACKTRACE_DEPTH = 200;
|
||||
|
||||
// The console API methods are async and their action is executed later. This
|
||||
// delay tells how much later.
|
||||
const CALL_DELAY = 30; // milliseconds
|
||||
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/ConsoleAPIStorage.jsm");
|
||||
|
||||
let nsITimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
|
||||
|
||||
function ConsoleAPI() {}
|
||||
ConsoleAPI.prototype = {
|
||||
|
||||
@ -21,11 +36,17 @@ ConsoleAPI.prototype = {
|
||||
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsIDOMGlobalPropertyInitializer]),
|
||||
|
||||
_timerInitialized: false,
|
||||
_queuedCalls: null,
|
||||
_timerCallback: null,
|
||||
_destroyedWindows: null,
|
||||
|
||||
// nsIDOMGlobalPropertyInitializer
|
||||
init: function CA_init(aWindow) {
|
||||
Services.obs.addObserver(this, "xpcom-shutdown", false);
|
||||
Services.obs.addObserver(this, "inner-window-destroyed", false);
|
||||
|
||||
|
||||
let outerID;
|
||||
let innerID;
|
||||
try {
|
||||
@ -39,45 +60,50 @@ ConsoleAPI.prototype = {
|
||||
Cu.reportError(ex);
|
||||
}
|
||||
|
||||
let meta = {
|
||||
outerID: outerID,
|
||||
innerID: innerID,
|
||||
};
|
||||
|
||||
let self = this;
|
||||
let chromeObject = {
|
||||
// window.console API
|
||||
log: function CA_log() {
|
||||
self.notifyObservers(outerID, innerID, "log", self.processArguments(arguments));
|
||||
self.queueCall("log", arguments, meta);
|
||||
},
|
||||
info: function CA_info() {
|
||||
self.notifyObservers(outerID, innerID, "info", self.processArguments(arguments));
|
||||
self.queueCall("info", arguments, meta);
|
||||
},
|
||||
warn: function CA_warn() {
|
||||
self.notifyObservers(outerID, innerID, "warn", self.processArguments(arguments));
|
||||
self.queueCall("warn", arguments, meta);
|
||||
},
|
||||
error: function CA_error() {
|
||||
self.notifyObservers(outerID, innerID, "error", self.processArguments(arguments));
|
||||
self.queueCall("error", arguments, meta);
|
||||
},
|
||||
debug: function CA_debug() {
|
||||
self.notifyObservers(outerID, innerID, "log", self.processArguments(arguments));
|
||||
self.queueCall("debug", arguments, meta);
|
||||
},
|
||||
trace: function CA_trace() {
|
||||
self.notifyObservers(outerID, innerID, "trace", self.getStackTrace());
|
||||
self.queueCall("trace", arguments, meta);
|
||||
},
|
||||
// Displays an interactive listing of all the properties of an object.
|
||||
dir: function CA_dir() {
|
||||
self.notifyObservers(outerID, innerID, "dir", arguments);
|
||||
self.queueCall("dir", arguments, meta);
|
||||
},
|
||||
group: function CA_group() {
|
||||
self.notifyObservers(outerID, innerID, "group", self.beginGroup(arguments));
|
||||
self.queueCall("group", arguments, meta);
|
||||
},
|
||||
groupCollapsed: function CA_groupCollapsed() {
|
||||
self.notifyObservers(outerID, innerID, "groupCollapsed", self.beginGroup(arguments));
|
||||
self.queueCall("groupCollapsed", arguments, meta);
|
||||
},
|
||||
groupEnd: function CA_groupEnd() {
|
||||
self.notifyObservers(outerID, innerID, "groupEnd", arguments);
|
||||
self.queueCall("groupEnd", arguments, meta);
|
||||
},
|
||||
time: function CA_time() {
|
||||
self.notifyObservers(outerID, innerID, "time", self.startTimer(innerID, arguments[0]));
|
||||
self.queueCall("time", arguments, meta);
|
||||
},
|
||||
timeEnd: function CA_timeEnd() {
|
||||
self.notifyObservers(outerID, innerID, "timeEnd", self.stopTimer(innerID, arguments[0]));
|
||||
self.queueCall("timeEnd", arguments, meta);
|
||||
},
|
||||
__exposedProps__: {
|
||||
log: "r",
|
||||
@ -123,6 +149,12 @@ ConsoleAPI.prototype = {
|
||||
Object.defineProperties(contentObj, properties);
|
||||
Cu.makeObjectPropsNormal(contentObj);
|
||||
|
||||
this._queuedCalls = [];
|
||||
this._destroyedWindows = [];
|
||||
this._timerCallback = {
|
||||
notify: this._timerCallbackNotify.bind(this),
|
||||
};
|
||||
|
||||
return contentObj;
|
||||
},
|
||||
|
||||
@ -131,51 +163,144 @@ ConsoleAPI.prototype = {
|
||||
if (aTopic == "xpcom-shutdown") {
|
||||
Services.obs.removeObserver(this, "xpcom-shutdown");
|
||||
Services.obs.removeObserver(this, "inner-window-destroyed");
|
||||
this._destroyedWindows = [];
|
||||
this._queuedCalls = [];
|
||||
}
|
||||
else if (aTopic == "inner-window-destroyed") {
|
||||
let innerWindowID = aSubject.QueryInterface(Ci.nsISupportsPRUint64).data;
|
||||
delete this.timerRegistry[innerWindowID + ""];
|
||||
this._destroyedWindows.push(innerWindowID);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Queue a call to a console method. See the CALL_DELAY constant.
|
||||
*
|
||||
* @param string aMethod
|
||||
* The console method the code has invoked.
|
||||
* @param object aArguments
|
||||
* The arguments passed to the console method.
|
||||
* @param object aMeta
|
||||
* The associated call meta information. This needs to hold the inner
|
||||
* and outer window IDs from where the console method was called.
|
||||
*/
|
||||
queueCall: function CA_queueCall(aMethod, aArguments, aMeta)
|
||||
{
|
||||
let metaForCall = {
|
||||
outerID: aMeta.outerID,
|
||||
innerID: aMeta.innerID,
|
||||
timeStamp: Date.now(),
|
||||
stack: this.getStackTrace(aMethod != "trace" ? 1 : null),
|
||||
};
|
||||
|
||||
this._queuedCalls.push([aMethod, aArguments, metaForCall]);
|
||||
|
||||
if (!this._timerInitialized) {
|
||||
nsITimer.initWithCallback(this._timerCallback, CALL_DELAY,
|
||||
Ci.nsITimer.TYPE_ONE_SHOT);
|
||||
this._timerInitialized = true;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Timer callback used to process each of the queued calls.
|
||||
* @private
|
||||
*/
|
||||
_timerCallbackNotify: function CA__timerCallbackNotify()
|
||||
{
|
||||
this._timerInitialized = false;
|
||||
this._queuedCalls.splice(0).forEach(this._processQueuedCall, this);
|
||||
this._destroyedWindows = [];
|
||||
},
|
||||
|
||||
/**
|
||||
* Process a queued call to a console method.
|
||||
*
|
||||
* @private
|
||||
* @param array aCall
|
||||
* Array that holds information about the queued call.
|
||||
*/
|
||||
_processQueuedCall: function CA__processQueuedItem(aCall)
|
||||
{
|
||||
let [method, args, meta] = aCall;
|
||||
|
||||
let notifyMeta = {
|
||||
outerID: meta.outerID,
|
||||
innerID: meta.innerID,
|
||||
timeStamp: meta.timeStamp,
|
||||
frame: meta.stack[0],
|
||||
};
|
||||
|
||||
let notifyArguments = null;
|
||||
|
||||
switch (method) {
|
||||
case "log":
|
||||
case "info":
|
||||
case "warn":
|
||||
case "error":
|
||||
case "debug":
|
||||
notifyArguments = this.processArguments(args);
|
||||
break;
|
||||
case "trace":
|
||||
notifyArguments = meta.stack;
|
||||
break;
|
||||
case "group":
|
||||
case "groupCollapsed":
|
||||
notifyArguments = this.beginGroup(args);
|
||||
break;
|
||||
case "groupEnd":
|
||||
case "dir":
|
||||
notifyArguments = args;
|
||||
break;
|
||||
case "time":
|
||||
notifyArguments = this.startTimer(meta.innerID, args[0], meta.timeStamp);
|
||||
break;
|
||||
case "timeEnd":
|
||||
notifyArguments = this.stopTimer(meta.innerID, args[0], meta.timeStamp);
|
||||
break;
|
||||
default:
|
||||
// unknown console API method!
|
||||
return;
|
||||
}
|
||||
|
||||
this.notifyObservers(method, notifyArguments, notifyMeta);
|
||||
},
|
||||
|
||||
/**
|
||||
* Notify all observers of any console API call.
|
||||
*
|
||||
* @param number aOuterWindowID
|
||||
* The outer window ID from where the message came from.
|
||||
* @param number aInnerWindowID
|
||||
* The inner window ID from where the message came from.
|
||||
* @param string aLevel
|
||||
* The message level.
|
||||
* @param mixed aArguments
|
||||
* The arguments given to the console API call.
|
||||
**/
|
||||
notifyObservers:
|
||||
function CA_notifyObservers(aOuterWindowID, aInnerWindowID, aLevel, aArguments) {
|
||||
if (!aOuterWindowID) {
|
||||
return;
|
||||
}
|
||||
|
||||
let stack = this.getStackTrace();
|
||||
// Skip the first frame since it contains an internal call.
|
||||
let frame = stack[1];
|
||||
* @param object aMeta
|
||||
* Object that holds metadata about the console API call:
|
||||
* - outerID - the outer ID of the window where the message came from.
|
||||
* - innerID - the inner ID of the window where the message came from.
|
||||
* - frame - the youngest content frame in the call stack.
|
||||
* - timeStamp - when the console API call occurred.
|
||||
*/
|
||||
notifyObservers: function CA_notifyObservers(aLevel, aArguments, aMeta) {
|
||||
let consoleEvent = {
|
||||
ID: aOuterWindowID,
|
||||
innerID: aInnerWindowID,
|
||||
ID: aMeta.outerID,
|
||||
innerID: aMeta.innerID,
|
||||
level: aLevel,
|
||||
filename: frame.filename,
|
||||
lineNumber: frame.lineNumber,
|
||||
functionName: frame.functionName,
|
||||
filename: aMeta.frame.filename,
|
||||
lineNumber: aMeta.frame.lineNumber,
|
||||
functionName: aMeta.frame.functionName,
|
||||
arguments: aArguments,
|
||||
timeStamp: Date.now(),
|
||||
timeStamp: aMeta.timeStamp,
|
||||
};
|
||||
|
||||
consoleEvent.wrappedJSObject = consoleEvent;
|
||||
|
||||
ConsoleAPIStorage.recordEvent(aInnerWindowID, consoleEvent);
|
||||
// Store messages for which the inner window was not destroyed.
|
||||
if (this._destroyedWindows.indexOf(aMeta.innerID) == -1) {
|
||||
ConsoleAPIStorage.recordEvent(aMeta.innerID, consoleEvent);
|
||||
}
|
||||
|
||||
Services.obs.notifyObservers(consoleEvent,
|
||||
"console-api-log-event", aOuterWindowID);
|
||||
Services.obs.notifyObservers(consoleEvent, "console-api-log-event",
|
||||
aMeta.outerID);
|
||||
},
|
||||
|
||||
/**
|
||||
@ -189,18 +314,14 @@ ConsoleAPI.prototype = {
|
||||
* The arguments given to the console API call.
|
||||
**/
|
||||
processArguments: function CA_processArguments(aArguments) {
|
||||
if (aArguments.length < 2) {
|
||||
if (aArguments.length < 2 || typeof aArguments[0] != "string") {
|
||||
return aArguments;
|
||||
}
|
||||
let args = Array.prototype.slice.call(aArguments);
|
||||
let format = args.shift();
|
||||
if (typeof format != "string") {
|
||||
return aArguments;
|
||||
}
|
||||
// Format specification regular expression.
|
||||
let pattern = /%(\d*).?(\d*)[a-zA-Z]/g;
|
||||
let processed = format.replace(pattern, function CA_PA_substitute(spec) {
|
||||
switch (spec[spec.length-1]) {
|
||||
let processed = format.replace(ARGUMENT_PATTERN, function CA_PA_substitute(match, submatch) {
|
||||
switch (submatch) {
|
||||
case "o":
|
||||
case "s":
|
||||
return String(args.shift());
|
||||
@ -210,7 +331,7 @@ ConsoleAPI.prototype = {
|
||||
case "f":
|
||||
return parseFloat(args.shift());
|
||||
default:
|
||||
return spec;
|
||||
return submatch;
|
||||
};
|
||||
});
|
||||
args.unshift(processed);
|
||||
@ -220,13 +341,19 @@ ConsoleAPI.prototype = {
|
||||
/**
|
||||
* Build the stacktrace array for the console.trace() call.
|
||||
*
|
||||
* @param number [aMaxDepth=DEFAULT_MAX_STACKTRACE_DEPTH]
|
||||
* Optional maximum stacktrace depth.
|
||||
* @return array
|
||||
* Each element is a stack frame that holds the following properties:
|
||||
* filename, lineNumber, functionName and language.
|
||||
**/
|
||||
getStackTrace: function CA_getStackTrace() {
|
||||
*/
|
||||
getStackTrace: function CA_getStackTrace(aMaxDepth) {
|
||||
if (!aMaxDepth) {
|
||||
aMaxDepth = DEFAULT_MAX_STACKTRACE_DEPTH;
|
||||
}
|
||||
|
||||
let stack = [];
|
||||
let frame = Components.stack.caller;
|
||||
let frame = Components.stack.caller.caller;
|
||||
while (frame = frame.caller) {
|
||||
if (frame.language == Ci.nsIProgrammingLanguage.JAVASCRIPT ||
|
||||
frame.language == Ci.nsIProgrammingLanguage.JAVASCRIPT2) {
|
||||
@ -236,6 +363,9 @@ ConsoleAPI.prototype = {
|
||||
functionName: frame.name,
|
||||
language: frame.language,
|
||||
});
|
||||
if (stack.length == aMaxDepth) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -265,13 +395,15 @@ ConsoleAPI.prototype = {
|
||||
* The inner ID of the window.
|
||||
* @param string aName
|
||||
* The name of the timer.
|
||||
* @param number [aTimestamp=Date.now()]
|
||||
* Optional timestamp that tells when the timer was originally started.
|
||||
* @return object
|
||||
* The name property holds the timer name and the started property
|
||||
* holds the time the timer was started. In case of error, it returns
|
||||
* an object with the single property "error" that contains the key
|
||||
* for retrieving the localized error message.
|
||||
**/
|
||||
startTimer: function CA_startTimer(aWindowId, aName) {
|
||||
startTimer: function CA_startTimer(aWindowId, aName, aTimestamp) {
|
||||
if (!aName) {
|
||||
return;
|
||||
}
|
||||
@ -285,7 +417,7 @@ ConsoleAPI.prototype = {
|
||||
}
|
||||
let key = aWindowId + "-" + aName.toString();
|
||||
if (!pageTimers[key]) {
|
||||
pageTimers[key] = Date.now();
|
||||
pageTimers[key] = aTimestamp || Date.now();
|
||||
}
|
||||
return { name: aName, started: pageTimers[key] };
|
||||
},
|
||||
@ -297,11 +429,13 @@ ConsoleAPI.prototype = {
|
||||
* The inner ID of the window.
|
||||
* @param string aName
|
||||
* The name of the timer.
|
||||
* @param number [aTimestamp=Date.now()]
|
||||
* Optional timestamp that tells when the timer was originally stopped.
|
||||
* @return object
|
||||
* The name property holds the timer name and the duration property
|
||||
* holds the number of milliseconds since the timer was started.
|
||||
**/
|
||||
stopTimer: function CA_stopTimer(aWindowId, aName) {
|
||||
stopTimer: function CA_stopTimer(aWindowId, aName, aTimestamp) {
|
||||
if (!aName) {
|
||||
return;
|
||||
}
|
||||
@ -314,7 +448,7 @@ ConsoleAPI.prototype = {
|
||||
if (!pageTimers[key]) {
|
||||
return;
|
||||
}
|
||||
let duration = Date.now() - pageTimers[key];
|
||||
let duration = (aTimestamp || Date.now()) - pageTimers[key];
|
||||
delete pageTimers[key];
|
||||
return { name: aName, duration: duration };
|
||||
}
|
||||
|
@ -5,7 +5,7 @@
|
||||
|
||||
const TEST_URI = "http://example.com/browser/dom/tests/browser/test-console-api.html";
|
||||
|
||||
var gWindow, gLevel, gArgs;
|
||||
var gWindow, gLevel, gArgs, gTestDriver;
|
||||
|
||||
function test() {
|
||||
waitForExplicitFinish();
|
||||
@ -15,6 +15,7 @@ function test() {
|
||||
var browser = gBrowser.selectedBrowser;
|
||||
|
||||
registerCleanupFunction(function () {
|
||||
gWindow = gLevel = gArgs = gTestDriver = null;
|
||||
gBrowser.removeTab(tab);
|
||||
});
|
||||
|
||||
@ -25,7 +26,8 @@ function test() {
|
||||
executeSoon(function test_executeSoon() {
|
||||
gWindow = browser.contentWindow;
|
||||
consoleAPISanityTest();
|
||||
observeConsoleTest();
|
||||
gTestDriver = observeConsoleTest();
|
||||
gTestDriver.next();
|
||||
});
|
||||
|
||||
}, false);
|
||||
@ -42,9 +44,6 @@ function testConsoleData(aMessageObject) {
|
||||
if (gLevel == "trace") {
|
||||
is(aMessageObject.arguments.toSource(), gArgs.toSource(),
|
||||
"stack trace is correct");
|
||||
|
||||
// Now test the location information in console.log()
|
||||
startLocationTest();
|
||||
}
|
||||
else {
|
||||
gArgs.forEach(function (a, i) {
|
||||
@ -52,10 +51,7 @@ function testConsoleData(aMessageObject) {
|
||||
});
|
||||
}
|
||||
|
||||
if (aMessageObject.level == "error") {
|
||||
// Now test console.trace()
|
||||
startTraceTest();
|
||||
}
|
||||
gTestDriver.next();
|
||||
}
|
||||
|
||||
function testLocationData(aMessageObject) {
|
||||
@ -163,9 +159,11 @@ function observeConsoleTest() {
|
||||
let win = XPCNativeWrapper.unwrap(gWindow);
|
||||
expect("log", "arg");
|
||||
win.console.log("arg");
|
||||
yield;
|
||||
|
||||
expect("info", "arg", "extra arg");
|
||||
win.console.info("arg", "extra arg");
|
||||
yield;
|
||||
|
||||
// We don't currently support width and precision qualifiers, but we don't
|
||||
// choke on them either.
|
||||
@ -174,29 +172,49 @@ function observeConsoleTest() {
|
||||
1,
|
||||
"PI",
|
||||
3.14159);
|
||||
yield;
|
||||
|
||||
expect("log", "%d, %s, %l");
|
||||
win.console.log("%d, %s, %l");
|
||||
yield;
|
||||
|
||||
expect("log", "%a %b %c");
|
||||
win.console.log("%a %b %c");
|
||||
yield;
|
||||
|
||||
expect("log", "%a %b %c", "a", "b");
|
||||
win.console.log("%a %b %c", "a", "b");
|
||||
yield;
|
||||
|
||||
expect("log", "2, a, %l", 3);
|
||||
win.console.log("%d, %s, %l", 2, "a", 3);
|
||||
yield;
|
||||
|
||||
// Bug #692550 handle null and undefined.
|
||||
expect("log", "null, undefined");
|
||||
win.console.log("%s, %s", null, undefined);
|
||||
yield;
|
||||
|
||||
// Bug #696288 handle object as first argument.
|
||||
let obj = { a: 1 };
|
||||
expect("log", obj, "a");
|
||||
win.console.log(obj, "a");
|
||||
yield;
|
||||
|
||||
expect("dir", win.toString());
|
||||
win.console.dir(win);
|
||||
yield;
|
||||
|
||||
expect("error", "arg");
|
||||
win.console.error("arg");
|
||||
|
||||
yield;
|
||||
|
||||
startTraceTest();
|
||||
yield;
|
||||
|
||||
startLocationTest();
|
||||
yield;
|
||||
}
|
||||
|
||||
function consoleAPISanityTest() {
|
||||
|
@ -14,24 +14,44 @@ function test() {
|
||||
var CSS = {};
|
||||
Cu.import("resource://gre/modules/ConsoleAPIStorage.jsm", CSS);
|
||||
|
||||
function checkStorageOccurs(shouldOccur) {
|
||||
let innerID, beforeEvents, storageShouldOccur;
|
||||
|
||||
var ConsoleObserver = {
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver]),
|
||||
|
||||
observe: function CO_observe(aSubject, aTopic, aData)
|
||||
{
|
||||
if (aTopic != "console-api-log-event") {
|
||||
return;
|
||||
}
|
||||
|
||||
let afterEvents = CSS.ConsoleAPIStorage.getEvents(innerID);
|
||||
|
||||
is(beforeEvents.length == afterEvents.length - 1,
|
||||
storageShouldOccur,
|
||||
"storage should" + (storageShouldOccur ? "" : " not") + " occur");
|
||||
|
||||
executeSoon(function() {
|
||||
Services.obs.removeObserver(ConsoleObserver, "console-api-log-event");
|
||||
pb.privateBrowsingEnabled = storageShouldOccur;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
function checkStorageOccurs() {
|
||||
Services.obs.addObserver(ConsoleObserver, "console-api-log-event", false);
|
||||
|
||||
let win = XPCNativeWrapper.unwrap(browser.contentWindow);
|
||||
let innerID = getInnerWindowId(win);
|
||||
innerID = getInnerWindowId(win);
|
||||
|
||||
let beforeEvents = CSS.ConsoleAPIStorage.getEvents(innerID);
|
||||
win.console.log("foo bar baz (private: " + !shouldOccur + ")");
|
||||
|
||||
let afterEvents = CSS.ConsoleAPIStorage.getEvents(innerID);
|
||||
|
||||
is(beforeEvents.length == afterEvents.length - 1,
|
||||
shouldOccur, "storage should" + (shouldOccur ? "" : "n't") + " occur");
|
||||
beforeEvents = CSS.ConsoleAPIStorage.getEvents(innerID);
|
||||
win.console.log("foo bar baz (private: " + !storageShouldOccur + ")");
|
||||
}
|
||||
|
||||
function pbObserver(aSubject, aTopic, aData) {
|
||||
if (aData == "enter") {
|
||||
checkStorageOccurs(false);
|
||||
|
||||
executeSoon(function () { pb.privateBrowsingEnabled = false; });
|
||||
storageShouldOccur = false;
|
||||
checkStorageOccurs();
|
||||
} else if (aData == "exit") {
|
||||
executeSoon(finish);
|
||||
}
|
||||
@ -61,9 +81,8 @@ function test() {
|
||||
|
||||
browser.removeEventListener("DOMContentLoaded", onLoad, false);
|
||||
|
||||
checkStorageOccurs(true);
|
||||
|
||||
pb.privateBrowsingEnabled = true;
|
||||
storageShouldOccur = true;
|
||||
checkStorageOccurs();
|
||||
}, false);
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user