timeless%mozdev.org 180af87a17 Bug 348627 O(N^2) or worse algorithm in error console
r=bz sr=bz

Perhaps an explanation. String concatenation is a chance for garbage collection.
If you have unbounded input, this can be pretty bad. When you start running low
on memory, the garbage collector gets desperate and won't take shortcuts. Worst of
all, the original code generates one string of garbage every iteration starting with
the second or third.

This code uses a=[] to construct an array and a[n] to set the upper array boundary,
0 being the lower, and there are n slots between a[0] and a[n].
join() will build a string with n copies of the join argument, where n is the number                                                        of slots between between cells. So for:
a=[]; a[1]=''; there's 1 slot between [0] and [1]. The array's length is actually 2,
but the joined length will be 1.

While it is possible to use new Array(x), the behavior is different because the
array has length x and there are x-1 spaces between cells. This leads to code which
would be both much longer and harder to read. And definitely not particularly
intuitive.

The code used here otoh, while magical will hopefully require you to pause, think,
and quickly understand what's going on. If not, you paused long enough for this
bubble to appear and explain it to you :).


git-svn-id: svn://10.0.0.236/trunk@207376 18797224-902f-48f8-a5cc-f745e15eee43
2006-08-14 22:03:34 +00:00

391 lines
14 KiB
XML

<?xml version="1.0"?>
<!DOCTYPE window SYSTEM "chrome://global/locale/console.dtd">
<bindings id="consoleBindings"
xmlns="http://www.mozilla.org/xbl"
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
xmlns:xbl="http://www.mozilla.org/xbl">
<binding id="console-box" extends="xul:box">
<content>
<xul:stringbundle src="chrome://global/locale/console.properties" role="string-bundle"/>
<xul:vbox class="console-box-internal">
<xul:vbox class="console-rows" role="console-rows" xbl:inherits="dir=sortOrder"/>
</xul:vbox>
</content>
<implementation implements="nsIConsoleListener">
<field name="limit" readonly="true">
250
</field>
<field name="_showChromeErrors">-1</field>
<property name="showChromeErrors">
<getter><![CDATA[
if (this._showChromeErrors != -1)
return this._showChromeErrors;
var pref = Components.classes['@mozilla.org/preferences-service;1'].getService();
pref = pref.QueryInterface(Components.interfaces.nsIPrefBranch);
try {
return this._showChromeErrors = pref.getBoolPref("javascript.options.showInConsole");
}
catch(ex) {
return this._showChromeErrors = false;
}
]]></getter>
</property>
<property name="count" readonly="true">
<getter>return this.mCount</getter>
</property>
<property name="mode">
<getter>return this.mMode;</getter>
<setter><![CDATA[
this.mMode = val || "All";
this.setAttribute("mode", this.mMode);
return val;
]]></setter>
</property>
<property name="sortOrder">
<getter>return this.getAttribute("sortOrder");</getter>
<setter>this.setAttribute("sortOrder", val); return val;</setter>
</property>
<field name="mSelectedItem">null</field>
<property name="selectedItem">
<getter>return this.mSelectedItem</getter>
<setter><![CDATA[
if (this.mSelectedItem)
this.mSelectedItem.removeAttribute("selected");
this.mSelectedItem = val;
val.setAttribute("selected", "true");
]]></setter>
</property>
<method name="init">
<body><![CDATA[
this.mCount = 0;
this.mConsoleRowBox = document.getAnonymousElementByAttribute(this, "role", "console-rows");
this.mStrBundle = document.getAnonymousElementByAttribute(this, "role", "string-bundle");
try {
this.mCService = Components.classes['@mozilla.org/consoleservice;1']
.getService(Components.interfaces.nsIConsoleService);
this.mCService.registerListener(this);
} catch (ex) {
this.appendMessage(
"Unable to display errors - couldn't get Console Service component. " +
"(Missing @mozilla.org/consoleservice;1)");
return;
}
this.mMode = this.getAttribute("mode") || "All";
this.appendInitialItems();
]]></body>
</method>
<method name="destroy">
<body><![CDATA[
this.mCService.unregisterListener(this);
]]></body>
</method>
<method name="appendInitialItems">
<body><![CDATA[
var out = {}; // Throwaway references to support 'out' parameters.
this.mCService.getMessageArray(out, {});
var messages = out.value;
// In case getMessageArray returns 0-length array as null
if (!messages)
messages = [];
var limit = messages.length - this.limit;
if (limit < 0) limit = 0;
// Checks if console ever been cleared
for (var i = messages.length - 1; i >= limit; --i)
if (!messages[i].message)
break;
// Populate with messages after latest "clear"
while (++i < messages.length)
this.observe(messages[i]);
]]></body>
</method>
<!-- nsIConsoleListener -->
<method name="observe">
<parameter name="aConsoleMessage"/>
<body><![CDATA[
/* This method may be called in cases where the code
* called by this method has thrown an exception because
* some resource ran OUT OF MEMORY (OOM).
*
* This method must not throw an exception, because
* an exception thrown by it could be asynchrounously
* dispatched back to this method resulting in an
* asynchronous loop - bug 288544.
*
* If you need any try/catch handling for code in this
* function, please stick it in its own try block inside
* the main try block. Do *NOT* use the main try block.
*/
try {
if (aConsoleMessage instanceof
Components.interfaces.nsIScriptError) {
// filter chrome urls
if (!this.showChromeErrors &&
/^chrome:/.test(aConsoleMessage.sourceName))
return;
this.appendError(aConsoleMessage);
} else if (aConsoleMessage.message) {
this.appendMessage(aConsoleMessage.message);
}
} catch (e) {
/* This catch block is for bug 288544,
* if you want to handle some edge case, please
* make your own inner try block inside preceding
* try block. Do *NOT* stick any code in here.
*/
}
]]></body>
</method>
<method name="appendError">
<parameter name="aObject"/>
<body><![CDATA[
var row = this.createConsoleRow();
var nsIScriptError = Components.interfaces.nsIScriptError;
// Is this error actually just a non-fatal warning?
var warning = aObject.flags & nsIScriptError.warningFlag != 0;
var typetext = warning ? "typeWarning" : "typeError";
row.setAttribute("typetext", this.mStrBundle.getString(typetext));
row.setAttribute("type", warning ? "warning" : "error");
row.setAttribute("msg", aObject.errorMessage);
row.setAttribute("category", aObject.category);
if (aObject.lineNumber || aObject.sourceName) {
row.setAttribute("url", aObject.sourceName);
row.setAttribute("line", aObject.lineNumber);
} else {
row.setAttribute("hideSource", "true");
}
if (aObject.sourceLine) {
row.setAttribute("code", aObject.sourceLine.replace("\n", "", "g"));
if (aObject.columnNumber) {
row.setAttribute("col", aObject.columnNumber);
row.setAttribute("errorDots", this.repeatChar(" ", aObject.columnNumber));
row.setAttribute("errorCaret", " ");
} else {
row.setAttribute("hideCaret", "true");
}
} else {
row.setAttribute("hideCode", "true");
}
this.appendConsoleRow(row);
]]></body>
</method>
<method name="appendMessage">
<parameter name="aMessage"/>
<parameter name="aType"/>
<body><![CDATA[
var row = this.createConsoleRow();
row.setAttribute("type", aType || "message");
row.setAttribute("msg", aMessage);
this.appendConsoleRow(row);
]]></body>
</method>
<method name="clear">
<body><![CDATA[
this.mCService.logStringMessage(null);
this.mCount = 0;
var newRows = this.mConsoleRowBox.cloneNode(false);
this.mConsoleRowBox.parentNode.replaceChild(newRows, this.mConsoleRowBox);
this.mConsoleRowBox = newRows;
]]></body>
</method>
<method name="copySelectedItem">
<body><![CDATA[
if (this.mSelectedItem) try {
const clipURI = "@mozilla.org/widget/clipboardhelper;1";
const clipI = Components.interfaces.nsIClipboardHelper;
var clipboard = Components.classes[clipURI].getService(clipI);
clipboard.copyString(this.mSelectedItem.toString());
} catch (ex) {
// Unable to copy anything, die quietly
}
]]></body>
</method>
<method name="createConsoleRow">
<body><![CDATA[
var row = document.createElement("box");
row.setAttribute("class", "console-row");
row._IsConsoleRow = true;
row._ConsoleBox = this;
return row;
]]></body>
</method>
<method name="appendConsoleRow">
<parameter name="aRow"/>
<body><![CDATA[
this.mConsoleRowBox.appendChild(aRow);
if (++this.mCount > this.limit) this.deleteFirst();
]]></body>
</method>
<method name="deleteFirst">
<body><![CDATA[
var node = this.mConsoleRowBox.firstChild;
this.mConsoleRowBox.removeChild(node);
--this.mCount;
]]></body>
</method>
<method name="repeatChar">
<parameter name="aChar"/>
<parameter name="aCol"/>
<body><![CDATA[
var ary = [];
ary[aCol] = '';
return ary.join(aChar);
]]></body>
</method>
<constructor> this.init(); </constructor>
<destructor> this.destroy(); </destructor>
</implementation>
<handlers>
<handler event="mousedown"><![CDATA[
if (event.button == 0 || event.button == 2) {
var target = event.originalTarget;
while (target && !("_IsConsoleRow" in target))
target = target.parentNode;
if (target)
this.selectedItem = target;
}
]]></handler>
</handlers>
</binding>
<binding id="error" extends="xul:box">
<content>
<xul:box class="console-row-internal-box" flex="1">
<xul:box class="console-row-icon" align="center" xbl:inherits="selected">
<xul:image class="console-icon" xbl:inherits="src,type"/>
</xul:box>
<xul:vbox class="console-row-content" xbl:inherits="selected" flex="1">
<xul:box class="console-row-msg" align="start">
<xul:label class="label" xbl:inherits="value=typetext"/>
<xul:description class="console-error-msg" xbl:inherits="xbl:text=msg" flex="1"/>
</xul:box>
<xul:box class="console-row-file" xbl:inherits="hidden=hideSource">
<xul:label class="label" value="&errFile.label;"/>
<xul:box class="console-error-source" xbl:inherits="url,line"/>
<spacer flex="1"/>
<xul:hbox class="lineNumberRow" xbl:inherits="line">
<xul:label class="label" value="&errLine.label;"/>
<xul:label class="label" xbl:inherits="value=line"/>
</xul:hbox>
</xul:box>
<xul:vbox class="console-row-code" xbl:inherits="selected,hidden=hideCode">
<xul:label class="monospace console-code" xbl:inherits="value=code" crop="end"/>
<xul:box xbl:inherits="hidden=hideCaret">
<xul:label class="monospace console-dots" xbl:inherits="value=errorDots"/>
<xul:label class="monospace console-caret" xbl:inherits="value=errorCaret"/>
<xul:spacer flex="1"/>
</xul:box>
</xul:vbox>
</xul:vbox>
</xul:box>
</content>
<implementation>
<method name="toString">
<body><![CDATA[
var msg = this.getAttribute("typetext") + " " + this.getAttribute("msg");
var strBundle = this._ConsoleBox.mStrBundle;
if (this.hasAttribute("line") && this.hasAttribute("url")) {
msg += "\n" + strBundle.getFormattedString("errFile",
[this.getAttribute("url")]) + "\n";
if (this.hasAttribute("col")) {
msg += strBundle.getFormattedString("errLineCol",
[this.getAttribute("line"), this.getAttribute("col")]);
} else
msg += strBundle.getFormattedString("errLine", [this.getAttribute("line")]);
}
if (this.hasAttribute("code"))
msg += "\n" + strBundle.getString("errCode") + "\n" + this.getAttribute("code");
return msg;
]]></body>
</method>
</implementation>
</binding>
<binding id="message" extends="xul:box">
<content>
<xul:box class="console-internal-box" flex="1">
<xul:box class="console-row-icon" align="center">
<xul:image class="console-icon" xbl:inherits="src,type"/>
</xul:box>
<xul:vbox class="console-row-content" xbl:inherits="selected" flex="1">
<xul:vbox class="console-row-msg" flex="1">
<xul:description class="console-msg-text" xbl:inherits="xbl:text=msg"/>
</xul:vbox>
</xul:vbox>
</xul:box>
</content>
<implementation>
<method name="toString">
<body><![CDATA[
return this.getAttribute("msg");
]]></body>
</method>
</implementation>
</binding>
<binding id="console-error-source" extends="xul:box">
<content>
<xul:label class="text-link" xbl:inherits="value=url" crop="right"/>
</content>
<handlers>
<handler event="click" button="0"><![CDATA[
var url = this.getAttribute("url");
var line = getAttribute("line");
window.openDialog(
"chrome://navigator/content/viewSource.xul", "_blank",
"all,dialog=no", url, null, null, line);
]]></handler>
</handlers>
</binding>
</bindings>