Bug 722685 - Console logging is slow; r=rcampbell

This commit is contained in:
Mihai Sucan 2012-06-02 13:45:32 +03:00
parent 395219db95
commit 79c59262b6
15 changed files with 827 additions and 333 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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"),

View File

@ -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() {

View File

@ -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() {

View File

@ -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() {

View File

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

View File

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

View File

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

View File

@ -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() {

View File

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