Bug 859170 - Fix for hang when trying to expand a very long message in the web console output; r=past

This commit is contained in:
Mihai Sucan 2013-04-11 15:27:49 +03:00
parent 14529a750b
commit 6d467d6423
6 changed files with 188 additions and 20 deletions

View File

@ -120,6 +120,7 @@ MOCHITEST_BROWSER_FILES = \
browser_console_variables_view.js \
browser_console_variables_view_while_debugging.js \
browser_console.js \
browser_longstring_hang.js \
head.js \
$(NULL)
@ -221,6 +222,7 @@ MOCHITEST_BROWSER_FILES += \
test-bug-821877-csperrors.html \
test-bug-821877-csperrors.html^headers^ \
test-eval-in-stackframe.html \
test-bug-859170-longstring-hang.html \
$(NULL)
include $(topsrcdir)/config/rules.mk

View File

@ -0,0 +1,88 @@
/* vim:set ts=2 sw=2 sts=2 et: */
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
// Test that very long strings do not hang the browser.
function test()
{
waitForExplicitFinish();
let DebuggerServer = Cu.import("resource://gre/modules/devtools/dbg-server.jsm",
{}).DebuggerServer;
addTab("http://example.com/browser/browser/devtools/webconsole/test/test-bug-859170-longstring-hang.html");
let hud = null;
gBrowser.selectedBrowser.addEventListener("load", function onLoad() {
gBrowser.selectedBrowser.removeEventListener("load", onLoad, true);
openConsole(null, performTest);
}, true);
function performTest(aHud)
{
hud = aHud;
info("wait for the initial long string");
waitForMessages({
webconsole: hud,
messages: [
{
name: "find 'foobar', no 'foobaz', in long string output",
text: "foobar",
noText: "foobaz",
category: CATEGORY_WEBDEV,
longString: true,
},
],
}).then(onInitialString);
}
function onInitialString(aResults)
{
let msg = [...aResults[0].matched][0];
ok(msg, "console.log result message element");
let clickable = msg.querySelector(".longStringEllipsis");
ok(clickable, "long string ellipsis is shown");
scrollToVisible(clickable);
executeSoon(() => {
EventUtils.synthesizeMouse(clickable, 2, 2, {}, hud.iframeWindow);
info("wait for long string expansion");
waitForMessages({
webconsole: hud,
messages: [
{
name: "find 'foobaz' after expand, but no 'boom!' at the end",
text: "foobaz",
noText: "boom!",
category: CATEGORY_WEBDEV,
longString: false,
},
{
text: "too long to be displayed",
longString: false,
},
],
}).then(finishTest);
});
}
function scrollToVisible(aNode)
{
let richListBoxNode = aNode.parentNode;
while (richListBoxNode.tagName != "richlistbox") {
richListBoxNode = richListBoxNode.parentNode;
}
let boxObject = richListBoxNode.scrollBoxObject;
let nsIScrollBoxObject = boxObject.QueryInterface(Ci.nsIScrollBoxObject);
nsIScrollBoxObject.ensureElementIsVisible(aNode);
}
}

View File

@ -875,20 +875,28 @@ function waitForMessages(aOptions)
let listenerAdded = false;
let deferred = Promise.defer();
function checkText(aRule, aText)
{
let result;
if (typeof aRule == "string") {
result = aText.indexOf(aRule) > -1;
}
else if (aRule instanceof RegExp) {
result = aRule.test(aText);
}
return result;
}
function checkMessage(aRule, aElement)
{
if (aRule.text) {
let elemText = getMessageElementText(aElement);
let matched = false;
if (typeof aRule.text == "string") {
matched = elemText.indexOf(aRule.text) > -1;
}
else if (aRule.text instanceof RegExp) {
matched = aRule.text.test(elemText);
}
if (!matched) {
return false;
}
let elemText = getMessageElementText(aElement);
if (aRule.text && !checkText(aRule.text, elemText)) {
return false;
}
if (aRule.noText && checkText(aRule.noText, elemText)) {
return false;
}
if (aRule.category) {
@ -910,6 +918,11 @@ function waitForMessages(aOptions)
}
}
let longString = !!aElement.querySelector(".longStringEllipsis");
if ("longString" in aRule && aRule.longString != longString) {
return false;
}
let count = aRule.count || 1;
if (!aRule.matched) {
aRule.matched = new Set();
@ -945,6 +958,7 @@ function waitForMessages(aOptions)
if (rulesMatched == rules.length) {
if (listenerAdded) {
webconsole.ui.off("messages-added", onMessagesAdded);
webconsole.ui.off("messages-updated", onMessagesAdded);
}
gPendingOutputTest--;
deferred.resolve(rules);
@ -964,7 +978,7 @@ function waitForMessages(aOptions)
for (let rule of rules) {
if (!rule._ruleMatched) {
console.log("failed to match rule: " + displayRule(rule));
ok(false, "failed to match rule: " + displayRule(rule));
}
}
}
@ -980,6 +994,7 @@ function waitForMessages(aOptions)
listenerAdded = true;
registerCleanupFunction(testCleanup);
webconsole.ui.on("messages-added", onMessagesAdded);
webconsole.ui.on("messages-updated", onMessagesAdded);
}
});

View File

@ -0,0 +1,23 @@
<!DOCTYPE html>
<html lang="en">
<head><meta charset="utf-8">
<title>Web Console test for bug 859170 - very long strings hang the browser</title>
<!-- Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ -->
<script type="application/javascript">
(function() {
var longString = "abbababazomglolztest";
for (var i = 0; i < 10; i++) {
longString += longString + longString;
}
longString = "foobar" + (new Array(20000)).join("a") + "foobaz" +
longString + "boom!";
console.log(longString);
})();
</script>
</head>
<body>
<p>Web Console test for bug 859170 - very long strings hang the browser.</p>
</body>
</html>

View File

@ -171,6 +171,9 @@ const FILTER_PREFS_PREFIX = "devtools.webconsole.filter.";
// The minimum font size.
const MIN_FONT_SIZE = 10;
// The maximum length of strings to be displayed by the Web Console.
const MAX_LONG_STRING_LENGTH = 200000;
const PREF_CONNECTION_TIMEOUT = "devtools.debugger.remote-timeout";
/**
@ -1319,7 +1322,7 @@ WebConsoleFrame.prototype = {
},
/**
* Inform user that the Web Console API has been replaced by a script
* Inform user that the window.console API has been replaced by a script
* in a content page.
*/
logWarningAboutReplacedAPI: function WCF_logWarningAboutReplacedAPI()
@ -1329,6 +1332,16 @@ WebConsoleFrame.prototype = {
this.outputMessage(CATEGORY_JS, node);
},
/**
* Inform user that the string he tries to view is too long.
*/
logWarningAboutStringTooLong: function WCF_logWarningAboutStringTooLong()
{
let node = this.createMessageNode(CATEGORY_JS, SEVERITY_WARNING,
l10n.getStr("longStringTooLong"));
this.outputMessage(CATEGORY_JS, node);
},
/**
* Handle the network events coming from the remote Web Console.
*
@ -1695,11 +1708,17 @@ WebConsoleFrame.prototype = {
let hudIdSupportsString = WebConsoleUtils.supportsString(this.hudId);
// Output the current batch of messages.
let newOrUpdatedNodes = new Set();
let newMessages = new Set();
let updatedMessages = new Set();
for (let item of batch) {
let result = this._outputMessageFromQueue(hudIdSupportsString, item);
if (result) {
newOrUpdatedNodes.add(result.isRepeated || result.node);
if (result.isRepeated) {
updatedMessages.add(result.isRepeated);
}
else {
newMessages.add(result.node);
}
if (result.visible && result.node == this.outputNode.lastChild) {
lastVisibleNode = result.node;
}
@ -1743,7 +1762,12 @@ WebConsoleFrame.prototype = {
scrollBox.scrollTop -= oldScrollHeight - scrollBox.scrollHeight;
}
this.emit("messages-added", newOrUpdatedNodes);
if (newMessages.size) {
this.emit("messages-added", newMessages);
}
if (updatedMessages.size) {
this.emit("messages-updated", updatedMessages);
}
// If the queue is not empty, schedule another flush.
if (this._outputQueue.length > 0) {
@ -2255,7 +2279,8 @@ WebConsoleFrame.prototype = {
}
let longString = this.webConsoleClient.longString(aActor);
longString.substring(longString.initial.length, longString.length,
let toIndex = Math.min(longString.length, MAX_LONG_STRING_LENGTH);
longString.substring(longString.initial.length, toIndex,
function WCF__onSubstring(aResponse) {
if (aResponse.error) {
Cu.reportError("WCF__longStringClick substring failure: " +
@ -2271,7 +2296,13 @@ WebConsoleFrame.prototype = {
aMessage.category == CATEGORY_OUTPUT) {
aMessage.clipboardText = aMessage.textContent;
}
});
this.emit("messages-updated", new Set([aMessage]));
if (toIndex != longString.length) {
this.logWarningAboutStringTooLong();
}
}.bind(this));
},
/**
@ -3468,7 +3499,8 @@ JSTerm.prototype = {
}
let client = this.webConsoleClient.longString(grip);
client.substring(grip.initial.length, grip.length, (aResponse) => {
let toIndex = Math.min(grip.length, MAX_LONG_STRING_LENGTH);
client.substring(grip.initial.length, toIndex, (aResponse) => {
if (aResponse.error) {
Cu.reportError("JST__fetchVarLongString substring failure: " +
aResponse.error + ": " + aResponse.message);
@ -3479,6 +3511,10 @@ JSTerm.prototype = {
aVar.setGrip(grip.initial + aResponse.substring);
aVar.hideArrow();
aVar._retrieved = true;
if (toIndex != grip.length) {
this.hud.logWarningAboutStringTooLong();
}
});
},

View File

@ -180,6 +180,10 @@ ToolboxWebconsole.tooltip=Web Console
# from the server.
longStringEllipsis=[…]
# LOCALIZATION NOTE (longStringTooLong): The string displayed after the user
# tries to expand a long string.
longStringTooLong=The string you are trying to view is too long to be displayed by the Web Console.
# LOCALIZATION NOTE (executeEmptyInput): This is displayed when the user tries
# to execute code, but the input is empty.
executeEmptyInput=No value to execute.