/* * ***** BEGIN LICENSE BLOCK ***** * Version: ZAPL 1.1 * * The contents of this file are subject to the Zimbra AJAX Public * License Version 1.1 ("License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * http://www.zimbra.com/license * * Software distributed under the License is distributed on an "AS IS" * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See * the License for the specific language governing rights and limitations * under the License. * * The Original Code is: Zimbra AJAX Toolkit. * * The Initial Developer of the Original Code is Zimbra, Inc. * Portions created by Zimbra are Copyright (C) 2005 Zimbra, Inc. * All Rights Reserved. * * Contributor(s): * * ***** END LICENSE BLOCK ***** */ /** * Creates a new debug window. The document inside is not kept open. All the output goes into a single <div> element. * @constructor * @class * This class pops up a debug window and provides functions to send output there in various ways. The output is continuously * appended to the bottom of the window. The document is left unopened so that the browser doesn't think it's continuously loading * and keep its little icon flailing forever. Also, the DOM tree can't be manipulated on an open document. All the output is added * to the window by appending it the DOM tree. Another method of appending output is to open the document and use document.write(), * but then the document is left open. *

* Any client that uses this class can turn off debugging by changing the first argument to the constructor to AjxDebug.NONE.

* * @author Conrad Damon * @author Enrique Del Campo * @param level debug level for the current debugger (no window will be displayed for a level of NONE) * @param name the name of the window. Defaults to "debug_" prepended to the calling window's URL. * @param showTime a boolean that toggles the display of timestamps before debug messages */ function AjxDebug(level, name, showTime) { this._dbgName = "AjxDebugWin_" + location.hostname.replace(/\./g,'_'); this._level = level; this._showTime = showTime; this._enabled = (this._level != AjxDebug.NONE); this._showTiming = false; this._startTimePt = 0; this._lastTimePt = 0; this._msgQueue = new Array(); this._debugBoxId = AjxDebug.DEBUG_BOX_ID; this._isPrevWinOpen = false; this._useDiv = false; if (!this._enabled) return; this._openContainer(); } AjxDebug.NONE = "DBG0"; // no debugging (window will not come up) AjxDebug.DBG1 = "DBG1"; // minimal debugging AjxDebug.DBG2 = "DBG2"; // moderate debugging AjxDebug.DBG3 = "DBG3"; // anything goes // map from number to debug level AjxDebug.DBG = new Object(); AjxDebug.DBG[0] = AjxDebug.NONE; AjxDebug.DBG[1] = AjxDebug.DBG1; AjxDebug.DBG[2] = AjxDebug.DBG2; AjxDebug.DBG[3] = AjxDebug.DBG3; AjxDebug.MAX_OUT = 25000; // max length capable of outputting AjxDebug.DEBUG_BOX_ID = "AjxDebugBox"; AjxDebug.prototype.toString = function() { return "AjxDebug"; } /** * Set debug level. May open or close the debug window if moving to or from level NONE. * * @param level debug level for the current debugger */ AjxDebug.prototype.setDebugLevel = function(level) { if (level == this._level) return; this._level = level; if (level == AjxDebug.NONE) { this._enabled = false; this._debugWindow.close(); this._debugWindow = null; } else { this._enabled = true; if (this._debugWindow == null || this._debugWindow.closed) { this._openContainer(); } } } AjxDebug.prototype.setUseDiv = function (useDiv){ this._useDiv = useDiv; AjxDebug.deleteWindowCookie(); if (useDiv && this._debugWindow && !this._debugWindow.closed) { this._debugWindow.close(); } this._enabled = true; this._openContainer(); }; AjxDebug.prototype._getWindowName = function () { return this._dbgName; }; /** * Turn the display of timing statements on/off. Timing starts over any time it's turned on. * * @param on whether to display timing statements */ AjxDebug.prototype.showTiming = function(on, msg) { if (on) this._startTimePt = this._lastTimePt = 0; this._showTiming = on; if (on && msg) this.println(" ----- " + msg + " ----- "); this._startTimePt = this._lastTimePt = new Date().getTime(); } /** * Prints a debug message. Any HTML will be rendered, and a line break is added. * * @param level debug level for the current debugger * @param msg the text to display */ AjxDebug.prototype.println = function(level, msg) { if (this.isDisabled()) return; var args = this._handleArgs(arguments); if (!args) return; //msg = args[0]; msg = args.join(""); this._add(this._timestamp() + msg + "
"); }; AjxDebug.prototype.isDisabled = function () { if (!this._useDiv){ if (!this._enabled){ return true; }; } else { if (this._divContainer && this._divContainer.style.display == 'none'){ return true; } } }; AjxDebug.prototype._getHtmlForObject = function (anObj, isXml, isRaw) { var html = new Array(); var idx = 0; if (AjxUtil.isUndefined(anObj)) { html[idx++] = "Undefined"; } else if (AjxUtil.isNull(anObj)) { html[idx++] = "NULL"; } else if (AjxUtil.isBoolean(anObj)) { html[idx++] = "" + anObj + ""; } else if (AjxUtil.isNumber(anObj)) { html[idx++] = "" + anObj +""; } else { if (isRaw) { html[idx++] = this._timestamp(); html[idx++] = ""; html[idx++] = "

"; } else if (isXml) { var xmldoc = new AjxDebugXmlDocument; var doc = xmldoc.create(); doc.loadXML(anObj); html[idx++] = "
"; html[idx++] = this._createXmlTree(doc, 0); html[idx++] = "
"; } else { html[idx++] = "
"; html[idx++] = "
";
			html[idx++] = this._dump(anObj, true);
			html[idx++] = "
"; html[idx++] = ""; } html[idx++] = "

"; html[idx++] = "Clear Debug Window"; html[idx++] = "

"; } return html.join(""); }; // Pretty-prints a Javascript object AjxDebug.prototype._dump = function(obj, recurse) { var indentLevel = 0; var showBraces = false; var stopRecursion = false; if (arguments.length > 2) { indentLevel = arguments[2]; showBraces = arguments[3]; stopRecursion = arguments[4]; } if (AjxUtil.isObject(obj)) { if (obj.toString() == "LmAppCtxt"){ return "[LmAppCtxt]"; } if (AjxDebug._visited.contains(obj)) return "[visited object]"; else AjxDebug._visited.add(obj); } var indent = AjxStringUtil.repeat(" ", indentLevel); var text = ""; if (AjxUtil.isUndefined(obj)) { text += "[undefined]"; } else if (AjxUtil.isNull(obj)) { text += "[null]"; } else if (AjxUtil.isBoolean(obj)) { text += obj ? "true" : "false"; } else if (AjxUtil.isString(obj)) { obj = obj.replace(/\r/g, "\\r"); obj = obj.replace(/\n/g, "\\n"); obj = obj.replace(/\t/g, "\\t"); text += '"' + AjxDebug._escapeForHTML(obj) + '"'; } else if (AjxUtil.isNumber(obj)) { text += obj; } else if (AjxUtil.isObject(obj)) { var isArray = AjxUtil.isArray(obj); if (stopRecursion) { text += isArray ? "[Array]" : obj.toString(); } else { stopRecursion = !recurse; var keys = new Array(); for (var i in obj) keys.push(i); isArray ? keys.sort(function(a,b) {return a - b;}) : keys.sort(); if (showBraces) text += isArray ? "[" : "{"; for (var i = 0; i < keys.length; i++) { var key = keys[i]; var nextObj = obj[key]; var value = null; // 5/31/05 EMC: // For dumping events, and dom elements, though I may not want to // traverse the node, I do want to know what the attribute is. if (nextObj == window || nextObj == document || (!AjxEnv.isIE && nextObj instanceof Node)){ value = nextObj.toString(); } if ((typeof(nextObj) == "function")){ if (this._showFuncs) { value = "[function]"; } else { continue; } } if (i > 0) text += ","; text += "\n" + indent; if (value != null) { text += key + ": " + value; } else { text += key + ": " + this._dump(nextObj, recurse, indentLevel + 2, true, stopRecursion); } } if (i > 0) text += "\n" + AjxStringUtil.repeat(" ", indentLevel - 1); if (showBraces) text += isArray ? "]" : "}"; } } return text; } /** * Prints an object into a table, with a column for properties and a column for values. Above the table is a header with the object * class and the CSS class (if any). The properties are sorted (numerically if they're all numbers). Creating and appending table * elements worked in Mozilla but not IE. Using the insert* methods works for both. Properties that are function * definitions are skipped. * * @param level debug level for the current debugger * @param obj the object to be printed * @param showFuncs whether to show props that are functions */ AjxDebug.prototype.dumpObj = function(level, obj, showFuncs) { if (this.isDisabled())return; var args = this._handleArgs(arguments); if (!args) return; obj = args[0]; if (!obj) return; this._showFuncs = args[1]; AjxDebug._visited = new AjxVector(); this._add(null, obj); this._showFuncs = null; } /** * Dumps a bunch of text into a <textarea>, so that it is wrapped and scrollable. HTML will not be rendered. * * @param level debug level for the current debugger * @param text the text to output as is */ AjxDebug.prototype.printRaw = function(level, text) { if (this.isDisabled()) return; var args = this._handleArgs(arguments); if (!args) return; text = args[0]; this._add(null,text, false, true); } /** * Pretty-prints a chunk of XML, doing color highlighting for different types of nodes. * @param level debug level for the current debugger * @param text some XML */ AjxDebug.prototype.printXML = function(level, text) { if (this.isDisabled()) return; var args = this._handleArgs(arguments); if (!args) return; text = args[0]; if (!text) return; // skip generating pretty xml if theres too much data if (AjxEnv.isSafari || text.length > AjxDebug.MAX_OUT) { this.printRaw(text); return; } this._add(null, text, true, false); } /** * Reveals white space in text by replacing it with tags. * * @param level debug level for the current debugger * @param text the text to be displayed */ AjxDebug.prototype.display = function(level, text) { if (this.isDisabled()) return; var args = this._handleArgs(arguments); if (!args) return; text = args[0]; text = text.replace(/\r?\n/g, '[crlf]'); text = text.replace(/ /g, '[space]'); text = text.replace(/\t/g, '[tab]'); this.printRaw(level, text); } AjxDebug.prototype.timePt = function(msg) { if (!this._showTiming || !this._enabled || this._debugWindow.closed) return; var now = new Date().getTime(); var elapsed = now - this._startTimePt; var interval = now - this._lastTimePt; this._lastTimePt = now; var text = "[" + elapsed + " / " + interval + "]"; if (msg) text += " " + msg; html = "
" + text + "
"; extraType = typeof(text); var myMsg = new DebugMessage(html); // Add the message to our stack this._addMessage(myMsg); return interval; } // If the first arg is a debug level, check it and then strip it. AjxDebug.prototype._handleArgs = function(args) { if (this._level == AjxDebug.NONE) return; var num1 = 0; var first = args[0]; if (typeof first == "string" && first.indexOf("DBG") == 0) { num1 = Number(first.charAt(first.length - 1)); var num2 = Number(this._level.charAt(this._level.length - 1)); if (num1 > num2) return null; } var a = new Array(args.length); for (var i = 0; i < args.length; i++) a[i] = args[i]; if (num1) a.shift(); return a; } AjxDebug.prototype._getCookieVal = function (cookieName) { var myRE = cookieName + "=([^;]+)"; var myVals = document.cookie.match(new RegExp(myRE)); var val = null; // Return the last value defined (if found) if (myVals && (myVals.length > 0)) { var valStr = myVals[myVals.length-1]; if (valStr == "true") { val = true; } else if (valStr == "false") { val = false; } else { val = valStr; } } return val; }; AjxDebug.prototype._openContainer = function () { this._enabled = true; if (!this._useDiv){ this._openDebugWindow(); } else { this._openDebugDiv(); } }; AjxDebug.prototype._openDebugDiv = function() { this._initDiv(); }; AjxDebug.prototype._openDebugWindow = function() { // check if there is a debug window already open if (!this._useDiv){ this._isPrevWinOpen = this._getCookieVal("AjxDebugWinOpen"); var winName = this._getWindowName(); if (!this._isPrevWinOpen) { this._debugWindow = AjxWindowOpener.openBlank( winName, "width=400,height=400,resizable=yes,scrollbars=yes", this._initWindow, this); } else { this._debugWindow = window.open("" , winName, "width=400,height=400,resizable=yes,scrollbars=yes"); this._initWindow(); } } }; AjxDebug.prototype._initDiv = function() { this._document = document; var container = this._divContainer = this._document.createElement("div"); container.id = "AjxDebugDivContainer"; container.style.height = "300px"; container.style.width = "300px"; container.style.display = "block"; container.style.position = "absolute"; container.style.top = "0px"; container.style.left = "0px"; container.style.zIndex = 10000; container.style.backgroundColor = "white"; var div = this._document.createElement("div"); div.style.height="20px"; div.style.width="100%"; div.innerHTML = "Close"; container.appendChild(div); this._document.body.appendChild(container); this._debugBox = this._document.createElement("div"); this._debugBox.style.overflow = "auto"; this._debugBox.style.height = "98%"; this._debugBox.style.width = "100%"; this._debugBox.id = this._debugBoxId; container.appendChild(this._debugBox); AjxDebug._divBuffer = document.createElement('div'); this._showMessages(); }; AjxDebug.closeDiv = function (anchor) { var container = anchor.parentNode.parentNode; container.style.display = 'none'; }; AjxDebug._openErrors = 0; AjxDebug.prototype._initWindow = function() { if (this._debugWindow == null) { this._enabled = false; return; } try { this._document = this._debugWindow.document; this._document.title = "Debug"; if (!this._isPrevWinOpen) { this._document.body.innerHTML = ""; this._debugBox = this._document.createElement("div"); this._debugBox.id = this._debugBoxId; this._document.body.appendChild(this._debugBox); AjxDebug._divBuffer = this._debugWindow.document.createElement('div'); // If we're not using a div // Set a cookie telling ourselves that a debug window is already open document.cookie = "AjxDebugWinOpen=true"; // setup an onunload method if (!AjxEnv.isIE) { this._debugWindow.onunload = AjxDebug.unloadHandler; window.addEventListener('unload', AjxDebug.myWindowUnloadHandler, true); } else { this._debugWindow.attachEvent('onunload', AjxDebug.unloadHandler); window.attachEvent = AjxDebug.myWindowUnloadHandler; } } else { this._debugBox = this._document.getElementById(this._debugBoxId); AjxDebug._divBuffer = this._debugWindow.document.createElement('div'); var sepDiv = this._parseHtmlFragment("
Debugging new window
"); this._debugBox.appendChild(sepDiv); // Firefox allows us to attach an event listener, and runs it even // though the window with the code is gone ... odd, but nice. IE, // though will not run the handler, so we make sure, event if we // are coming back to the window, to attach the onunload handler. if (AjxEnv.isIE) { this._debugWindow.attachEvent('onunload', AjxDebug.unloadHandler); } } // show any messages that have been queued up, while the window // loaded. this._showMessages(); } catch (ex) { AjxDebug.deleteWindowCookie(); this._debugWindow.close(); // If we've exceeded a certain number of errors, // let's just close the window, and bail. if (AjxDebug._openErrors < 5) { AjxDebug._openErrors++; this._openContainer(); } return; } } AjxDebug.myWindowUnloadHandler = function () { if (AjxEnv.isNav) { DBG._debugWindow.onunload = null; } else { DBG._debugWindow.detachEvent('onunload', AjxDebug.unloadHandler); } }; AjxDebug.unloadHandler = function () { try { window.AjxDebug.deleteWindowCookie(); } catch (ex) { // do nothing. This might be caused by the unload handler // firing while the window is changing domains. } }; AjxDebug.deleteWindowCookie = function () { AjxDebug.deleteCookie("AjxDebugWinOpen", false); }; AjxDebug.deleteCookie = function (cookieName, val) { var expiredDate = new Date('Fri, 31 Dec 1999 23:59:59 GMT'); document.cookie = cookieName +"=" + val+ ";expires=" + expiredDate.toGMTString(); }; AjxDebug._nextId = 0; AjxDebug.getNextId = function () { if (window.Dwt){ return Dwt.getNextId(); } else { return AjxDebug._nextId++; } }; /** * Scrolls to the bottom of the window. How it does that depends on the browser. * * @private */ AjxDebug.prototype._scrollToBottom = function() { AjxEnv.isIE ? this._debugBox.scrollIntoView(false) : this._debugWindow.scrollTo(0, this._document.body.offsetHeight); } /** * Returns a timestamp string, if we are showing them. * @private */ AjxDebug.prototype._timestamp = function() { return this._showTime ? new Date().toLocaleTimeString() + ": " : ""; } // this function takes an xml node and returns an html string that displays that node // the indent argument is used to describe what depth the node is at so that // the html code can create a nice indention AjxDebug.prototype._createXmlTree = function (node, indent) { if (node == null) return ""; var str = ""; switch (node.nodeType) { case 1: // Element str += "
<" + node.nodeName + ""; var attrs = node.attributes; for (var i = 0; i < attrs.length; i++) str += this._createXmlAttribute(attrs[i]); if (!node.hasChildNodes()) return str + "/>
"; str += ">
"; var cs = node.childNodes; for (var i = 0; i < cs.length; i++) str += this._createXmlTree(cs[i], indent + 3); str += "</" + node.nodeName + ">"; break; case 9: // Document var cs = node.childNodes; for (var i = 0; i < cs.length; i++) str += this._createXmlTree(cs[i], indent); break; case 3: // Text if (!/^\s*$/.test(node.nodeValue)) { var val = node.nodeValue.replace(//g, ">"); str += "" + val + "
"; } break; case 7: // ProcessInstruction str += "<?" + node.nodeName; var attrs = node.attributes; for (var i = 0; i < attrs.length; i++) str += this._createXmlAttribute(attrs[i]); str+= "?>
" break; case 4: // CDATA str = "
<![CDATA[" + node.nodeValue + "]" + "]>
"; break; case 8: // Comment str = "
<!--" + node.nodeValue + "-->
"; break; case 10: str = "
<!DOCTYPE " + node.name; if (node.publicId) { str += " PUBLIC \"" + node.publicId + "\""; if (node.systemId) str += " \"" + node.systemId + "\""; } else if (node.systemId) { str += " SYSTEM \"" + node.systemId + "\""; } str += ">
"; // TODO: Handle custom DOCTYPE declarations (ELEMENT, ATTRIBUTE, ENTITY) break; default: //alert(node.nodeType + "\n" + node.nodeValue); this._inspect(node); } return str; } AjxDebug.prototype._createXmlAttribute = function(a) { return " " + a.nodeName + "=\"" + a.nodeValue + "\""; } AjxDebug.prototype._inspect = function(obj) { var str = ""; for (var k in obj) str += "obj." + k + " = " + obj[k] + "\n"; window.alert(str); } AjxDebug.prototype._add = function (aMsg, extraInfo, isXml, isRaw){ var extraType = typeof(extraInfo); if (AjxUtil.isSpecified(extraInfo)) { extraInfo = this._getHtmlForObject(extraInfo, isXml, isRaw); } var myMsg = new DebugMessage(aMsg, null, null, null, extraInfo); // Add the message to our stack this._addMessage(myMsg); }; AjxDebug.prototype._addMessage = function (aMsg) { this._msgQueue[this._msgQueue.length] = aMsg; this._showMessages(); }; AjxDebug.buf = new Array(); AjxDebug.prototype._showMessages = function (retryNum) { if (!this._document) { // For now, don't show the messages-- assuming that // this case only happens at startup, and many // messages will be written return; } var i = 0; var buf = AjxDebug.buf; buf.length = 0; var idx = 0; var msg; for (i ; i < this._msgQueue.length ; ++i ) { msg = this._msgQueue[i]; buf[idx++] = "
"; buf[idx++] = msg.message; buf[idx++] = msg.eHtml; buf[idx++] = "
"; } this._msgQueue.length = 0; if (buf.length > 0){ var div = this._parseHtmlFragment(buf.join("")); if (!this.debugBox) this._debugBox = this._document.getElementById(this._debugBoxId); if (this._debugBox) this._debugBox.appendChild(div); } this._scrollToBottom(); }; AjxDebug._escapeForHTML = function(str){ if (typeof(str) != 'string') return str; var s = str; s = s.replace(/\&/g, '&'); s = s.replace(/\/g, '>'); s = s.replace(/\"/g, '"'); s = s.replace(/\xA0/g, ' '); return s; }; AjxDebug.prototype._parseHtmlFragment = function (htmlStr, tagName) { var html = htmlStr; if (tagName && tagName == "TR"){ html = "" + htmlStr + "
"; } var div = AjxDebug._divBuffer; div.innerHTML = html; if (tagName && tagName == "TR"){ return div.firstChild.rows[0]; } else { return div.firstChild; } }; /** * Simple wrapper for log messages */ DebugMessage = function(aMsg, aType, aCategory, aTime, extraHtml) { this.message = (AjxUtil.isSpecified(aMsg)) ? aMsg : ''; this.type = aType ? aType : null; this.category = aCategory ? aCategory : ''; this.time = aTime ? aTime : (new Date().getTime()); this.eHtml = extraHtml; };