diff options
Diffstat (limited to 'mailnews/base/util/errUtils.js')
-rw-r--r-- | mailnews/base/util/errUtils.js | 309 |
1 files changed, 309 insertions, 0 deletions
diff --git a/mailnews/base/util/errUtils.js b/mailnews/base/util/errUtils.js new file mode 100644 index 0000000000..21b205b82b --- /dev/null +++ b/mailnews/base/util/errUtils.js @@ -0,0 +1,309 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * This file contains helper methods for debugging -- things like logging + * exception objects, dumping DOM nodes, Events, and generic object dumps. + */ + +this.EXPORTED_SYMBOLS = ["logObject", "logException", "logElement", "logEvent", + "errorWithDebug"]; + +/** + * Report on an object to stdout. + * @param aObj the object to be dumped + * @param aName the name of the object, for informational purposes + */ +function logObject(aObj, aName) { + dump("Dumping Object: " + aName + "\n"); + stringifier.dumpObj(aObj, aName); +} + +/** + * Log an exception to stdout. This function should not be called in + * expected circumstances. + * @param aException the exception to log + * @param [aRethrow] set to true to rethrow the exception after logging + * @param [aMsg] optional message to log + */ +function logException(aException, aRethrow, aMsg) { + stringifier.dumpException(aException, aMsg); + + if (aMsg) + Components.utils.reportError(aMsg); + Components.utils.reportError(aException); + + if (aRethrow) + throw aException; +} + +/** + * Log an DOM element to stdout. + * @param aElement the DOM element to dump + */ +function logElement(aElement) { + stringifier.dumpDOM(aElement); +} + +/** + * Log an DOM event to stdout. + * @param aEvent the DOM event object to dump + */ +function logEvent(aEvent) { + stringifier.dumpEvent(aEvent); +} + +/** + * Dump the current stack and return an Error suitable for throwing. We return + * the new Error so that your code can use a "throw" statement which makes it + * obvious to syntactic analysis that there is an exit occuring at that point. + * + * Example: + * throw errorWithDebug("I did not expect this!"); + * + * @param aString The message payload for the exception. + */ +function errorWithDebug(aString) { + dump("PROBLEM: " + aString + "\n"); + dump("CURRENT STACK (and throwing):\n"); + // skip this frame. + dump(stringifier.getStack(1)); + return new Error(aString); +} + +function Stringifier() {}; + +Stringifier.prototype = { + dumpObj: function (o, name) { + this._reset(); + this._append(this.objectTreeAsString(o, true, true, 0)); + dump(this._asString()); + }, + + dumpDOM: function(node, level, recursive) { + this._reset(); + let s = this.DOMNodeAsString(node, level, recursive); + dump(s); + }, + + dumpEvent: function(event) { + dump(this.eventAsString(event)); + }, + + dumpException: function(exc, message) { + dump(exc + "\n"); + this._reset(); + if (message) + this._append("Exception (" + message + ")\n"); + + this._append("-- Exception object --\n"); + this._append(this.objectTreeAsString(exc)); + if (exc.stack) { + this._append("-- Stack Trace --\n"); + this._append(exc.stack); // skip dumpException and logException + } + dump(this._asString()); + }, + + _reset: function() { + this._buffer = []; + }, + + _append: function(string) { + this._buffer.push(string); + }, + + _asString: function() { + let str = this._buffer.join(''); + this._reset(); + return str; + }, + + getStack: function(skipCount) { + if (!((typeof Components == "object") && + (typeof Components.classes == "object"))) + return "No stack trace available."; + if (typeof(skipCount) === undefined) + skipCount = 0; + + let frame = Components.stack.caller; + let str = "<top>"; + + while (frame) { + if (skipCount > 0) { + // Skip this frame. + skipCount -= 1; + } + else { + // Include the data from this frame. + let name = frame.name ? frame.name : "[anonymous]"; + str += "\n" + name + "@" + frame.filename + ':' + frame.lineNumber; + } + frame = frame.caller; + } + return str + "\n"; + }, + + objectTreeAsString: function(o, recurse, compress, level) { + let s = ""; + if (recurse === undefined) + recurse = 0; + if (level === undefined) + level = 0; + if (compress === undefined) + compress = true; + let pfx = ""; + + for (var junk = 0; junk < level; junk++) + pfx += (compress) ? "| " : "| "; + + let tee = (compress) ? "+ " : "+- "; + + if (typeof(o) != "object") { + s += pfx + tee + " (" + typeof(o) + ") " + o + "\n"; + } + else { + for (let i in o) { + try { + let t = typeof o[i]; + switch (t) { + case "function": + let sfunc = String(o[i]).split("\n"); + if (sfunc[2] == " [native code]") + sfunc = "[native code]"; + else + sfunc = sfunc.length + " lines"; + s += pfx + tee + i + " (function) " + sfunc + "\n"; + break; + case "object": + s += pfx + tee + i + " (object) " + o[i] + "\n"; + if (!compress) + s += pfx + "|\n"; + if ((i != "parent") && (recurse)) + s += this.objectTreeAsString(o[i], recurse - 1, + compress, level + 1); + break; + case "string": + if (o[i].length > 200) + s += pfx + tee + i + " (" + t + ") " + o[i].length + " chars\n"; + else + s += pfx + tee + i + " (" + t + ") '" + o[i] + "'\n"; + break; + default: + s += pfx + tee + i + " (" + t + ") " + o[i] + "\n"; + } + } catch (ex) { + s += pfx + tee + " (exception) " + ex + "\n"; + } + if (!compress) + s += pfx + "|\n"; + } + } + s += pfx + "*\n"; + return s; + }, + + _repeatStr: function (str, aCount) { + let res = ""; + while (--aCount >= 0) + res += str; + return res; + }, + + DOMNodeAsString: function(node, level, recursive) { + if (level === undefined) + level = 0 + if (recursive === undefined) + recursive = true; + this._append(this._repeatStr(" ", 2*level) + "<" + node.nodeName + "\n"); + + if (node.nodeType == 3) { + this._append(this._repeatStr(" ", (2*level) + 4) + node.nodeValue + "'\n"); + } + else { + if (node.attributes) { + for (let i = 0; i < node.attributes.length; i++) { + this._append(this._repeatStr( + " ", (2*level) + 4) + node.attributes[i].nodeName + + "='" + node.attributes[i].nodeValue + "'\n"); + } + } + if (node.childNodes.length == 0) { + this._append(this._repeatStr(" ", (2*level)) + "/>\n"); + } + else if (recursive) { + this._append(this._repeatStr(" ", (2*level)) + ">\n"); + for (let i = 0; i < node.childNodes.length; i++) { + this._append(this.DOMNodeAsString(node.childNodes[i], level + 1)); + } + this._append(this._repeatStr(" ", 2*level) + "</" + node.nodeName + ">\n"); + } + } + return this._asString(); + }, + + eventAsString: function (event) { + this._reset(); + this._append("-EVENT --------------------------\n"); + this._append("type: " + event.type + "\n"); + this._append("eventPhase: " + event.eventPhase + "\n"); + if ("charCode" in event) { + this._append("charCode: " + event.charCode + "\n"); + if ("name" in event) + this._append("str(charCode): '" + String.fromCharCode(event.charCode) + "'\n"); + } + if (("target" in event) && event.target) { + this._append("target: " + event.target + "\n"); + if ("nodeName" in event.target) + this._append("target.nodeName: " + event.target.nodeName + "\n"); + if ("getAttribute" in event.target) + this._append("target.id: " + event.target.getAttribute("id") + "\n"); + } + if (("currentTarget" in event) && event.currentTarget) { + this._append("currentTarget: " + event.currentTarget + "\n"); + if ("nodeName" in event.currentTarget) + this._append("currentTarget.nodeName: "+ event.currentTarget.nodeName + "\n"); + if ("getAttribute" in event.currentTarget) + this._append("currentTarget.id: "+ event.currentTarget.getAttribute("id") + "\n"); + } + if (("originalTarget" in event) && event.originalTarget) { + this._append("originalTarget: " + event.originalTarget + "\n"); + if ("nodeName" in event.originalTarget) + this._append("originalTarget.nodeName: "+ event.originalTarget.nodeName + "\n"); + if ("getAttribute" in event.originalTarget) + this._append("originalTarget.id: "+ event.originalTarget.getAttribute("id") + "\n"); + } + let names = [ + "bubbles", + "cancelable", + "detail", + "button", + "keyCode", + "isChar", + "shiftKey", + "altKey", + "ctrlKey", + "metaKey", + "clientX", + "clientY", + "screenX", + "screenY", + "layerX", + "layerY", + "isTrusted", + "timeStamp", + "currentTargetXPath", + "targetXPath", + "originalTargetXPath" + ]; + for (let i in names) { + if (names[i] in event) + this._append(names[i] + ": " + event[names[i]] + "\n"); + } + this._append("-------------------------------------\n"); + return this._asString(); + } +}; + +var stringifier = new Stringifier(); |