1 /* 2 * ***** BEGIN LICENSE BLOCK ***** 3 * Zimbra Collaboration Suite Web Client 4 * Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016 Synacor, Inc. 5 * 6 * The contents of this file are subject to the Common Public Attribution License Version 1.0 (the "License"); 7 * you may not use this file except in compliance with the License. 8 * You may obtain a copy of the License at: https://www.zimbra.com/license 9 * The License is based on the Mozilla Public License Version 1.1 but Sections 14 and 15 10 * have been added to cover use of software over a computer network and provide for limited attribution 11 * for the Original Developer. In addition, Exhibit A has been modified to be consistent with Exhibit B. 12 * 13 * Software distributed under the License is distributed on an "AS IS" basis, 14 * WITHOUT WARRANTY OF ANY KIND, either express or implied. 15 * See the License for the specific language governing rights and limitations under the License. 16 * The Original Code is Zimbra Open Source Web Client. 17 * The Initial Developer of the Original Code is Zimbra, Inc. All rights to the Original Code were 18 * transferred by Zimbra, Inc. to Synacor, Inc. on September 14, 2015. 19 * 20 * All portions of the code are Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016 Synacor, Inc. All Rights Reserved. 21 * ***** END LICENSE BLOCK ***** 22 */ 23 24 25 /** 26 * Creates a new debug window. The document inside is not kept open. All the 27 * output goes into a single <div> element. 28 * @constructor 29 * @class 30 * This class pops up a debug window and provides functions to send output there 31 * in various ways. The output is continuously appended to the bottom of the 32 * window. The document is left unopened so that the browser doesn't think it's 33 * continuously loading and keep its little icon flailing forever. Also, the DOM 34 * tree can't be manipulated on an open document. All the output is added to the 35 * window by appending it the DOM tree. Another method of appending output is to 36 * open the document and use document.write(), but then the document is left open. 37 * <p> 38 * Any client that uses this class can turn off debugging by changing the first 39 * argument to the constructor to {@link AjxDebug.NONE}. 40 * 41 * @author Conrad Damon 42 * @author Ross Dargahi 43 * 44 * @param {constant} level debug level for the current debugger (no window will be displayed for a level of NONE) 45 * @param {string} name the name of the window (deprecated) 46 * @param {boolean} showTime if <code>true</code>, display timestamps before debug messages 47 * @param {constant} target output target (AjxDebug.TGT_WINDOW | AjxDebug.TGT_CONSOLE) 48 * 49 * @private 50 */ 51 AjxDebug = function(params) { 52 53 if (arguments.length == 0) { 54 params = {}; 55 } 56 else if (typeof arguments[0] == "number") { 57 params = {level:arguments[0], name:arguments[1], showTime:arguments[2]}; 58 } 59 60 this._showTime = params.showTime; 61 this._target = params.target || AjxDebug.TGT_WINDOW; 62 this._showTiming = false; 63 this._startTimePt = this._lastTimePt = 0; 64 this._dbgWindowInited = false; 65 66 this._msgQueue = []; 67 this._isPrevWinOpen = false; 68 this.setDebugLevel(params.level); 69 }; 70 71 AjxDebug.prototype.toString = function() { return "AjxDebug"; }; 72 AjxDebug.prototype.isAjxDebug = true; 73 74 /** 75 * Defines "no debugging" level. 76 */ 77 AjxDebug.NONE = 0; // no debugging (window will not come up) 78 /** 79 * Defines "minimal" debugging level. 80 */ 81 AjxDebug.DBG1 = 1; // minimal debugging 82 /** 83 * Defines "moderate" debugging level. 84 */ 85 AjxDebug.DBG2 = 2; // moderate debugging 86 /** 87 * Defines "all" debugging level. 88 */ 89 AjxDebug.DBG3 = 3; // anything goes 90 91 // log output targets 92 AjxDebug.TGT_WINDOW = "window"; 93 AjxDebug.TGT_CONSOLE = "console"; 94 95 // holds log output in memory so we can show it to user if requested; hash of arrays by type 96 AjxDebug.BUFFER = {}; 97 AjxDebug.BUFFER_MAX = {}; 98 99 // Special log types. These can be used to make high-priority log info available in prod mode. 100 // To turn off logging for a type, set its BUFFER_MAX to 0. 101 AjxDebug.DEFAULT_TYPE = "debug"; // regular DBG messages 102 AjxDebug.RPC = "rpc"; // for troubleshooting "Out of RPC cache" errors 103 AjxDebug.NOTIFY = "notify"; // for troubleshooting missing new mail 104 AjxDebug.EXCEPTION = "exception"; // JS errors 105 AjxDebug.CALENDAR = "calendar"; // for troubleshooting calendar errors 106 AjxDebug.REPLY = "reply"; // bug 56308 107 AjxDebug.SCROLL = "scroll"; // bug 55775 108 AjxDebug.BAD_JSON = "bad_json"; // bug 57066 109 AjxDebug.PREFS = "prefs"; // bug 60942 110 AjxDebug.PROGRESS = "progress"; // progress dialog 111 AjxDebug.REMINDER = "reminder"; // bug 60692 112 AjxDebug.OFFLINE = "offline"; 113 AjxDebug.TAG_ICON = "tagIcon"; // bug 62155 114 AjxDebug.DATA_URI = "dataUri"; // bug 64693 115 AjxDebug.MSG_DISPLAY = "msgDisplay"; // bugs 68599, 69616 116 AjxDebug.ZIMLET = "zimlet"; // bugs 83009 117 AjxDebug.KEYBOARD = "kbnav"; // keyboard manager debugging 118 AjxDebug.FOCUS = "focus"; // focus 119 AjxDebug.FOCUS1 = "focus1"; // focus - minimal logging 120 AjxDebug.ACCESSIBILITY = "a11y"; // accessibility logging 121 AjxDebug.DRAFT = "draft"; // draft auto-save 122 123 AjxDebug.BUFFER_MAX[AjxDebug.DEFAULT_TYPE] = 0; // this one can get big due to object dumps 124 AjxDebug.BUFFER_MAX[AjxDebug.RPC] = 200; 125 AjxDebug.BUFFER_MAX[AjxDebug.NOTIFY] = 400; 126 AjxDebug.BUFFER_MAX[AjxDebug.EXCEPTION] = 100; 127 AjxDebug.BUFFER_MAX[AjxDebug.CALENDAR] = 400; 128 AjxDebug.BUFFER_MAX[AjxDebug.REPLY] = 400; 129 AjxDebug.BUFFER_MAX[AjxDebug.SCROLL] = 100; 130 AjxDebug.BUFFER_MAX[AjxDebug.BAD_JSON] = 200; 131 AjxDebug.BUFFER_MAX[AjxDebug.PREFS] = 200; 132 AjxDebug.BUFFER_MAX[AjxDebug.REMINDER] = 200; 133 AjxDebug.BUFFER_MAX[AjxDebug.OFFLINE] = 400; 134 AjxDebug.BUFFER_MAX[AjxDebug.TAG_ICON] = 200; 135 AjxDebug.BUFFER_MAX[AjxDebug.PROGRESS] = 200; 136 AjxDebug.BUFFER_MAX[AjxDebug.DATA_URI] = 200; 137 AjxDebug.BUFFER_MAX[AjxDebug.MSG_DISPLAY] = 200; 138 AjxDebug.BUFFER_MAX[AjxDebug.ZIMLET] = 200; 139 AjxDebug.BUFFER_MAX[AjxDebug.KEYBOARD] = null; 140 AjxDebug.BUFFER_MAX[AjxDebug.FOCUS] = null; 141 AjxDebug.BUFFER_MAX[AjxDebug.ACCESSIBILITY] = null; 142 AjxDebug.BUFFER_MAX[AjxDebug.DRAFT] = 200; 143 144 AjxDebug.MAX_OUT = 25000; // max length capable of outputting an XML msg 145 146 AjxDebug._CONTENT_FRAME_ID = "AjxDebug_CF"; 147 AjxDebug._LINK_FRAME_ID = "AjxDebug_LF"; 148 AjxDebug._BOTTOM_FRAME_ID = "AjxDebug_BFI"; 149 AjxDebug._BOTTOM_FRAME_NAME = "AjxDebug_BFN"; 150 151 AjxDebug.prototype.setTitle = 152 function(title) { 153 if (this._document && !AjxEnv.isIE) { 154 this._document.title = title; 155 } 156 }; 157 158 /** 159 * Set debug level. May open or close the debug window if moving to or from level {@link AjxDebug.NONE}. 160 * 161 * @param {constant} level debug level for the current debugger 162 */ 163 AjxDebug.prototype.setDebugLevel = 164 function(level) { 165 166 this._level = parseInt(level) || level; 167 this._enable(this._level != AjxDebug.NONE); 168 }; 169 170 /** 171 * Gets the current debug level. 172 * 173 * @return {constant} the debug level 174 */ 175 AjxDebug.prototype.getDebugLevel = 176 function() { 177 return this._level; 178 }; 179 180 /** 181 * Prints a debug message. Any HTML will be rendered, and a line break is added. 182 * 183 * @param {constant} level debug level for the current debugger 184 * @param {string} msg the text to display 185 */ 186 AjxDebug.prototype.println = 187 function(level, msg, linkName) { 188 189 if (!this._isWriteable()) { return; } 190 191 try { 192 var result = this._handleArgs(arguments); 193 if (!result) { return; } 194 195 msg = result.args.join(""); 196 var eol = (this._target != AjxDebug.TGT_CONSOLE) ? "<br>" : ""; 197 this._add({msg:this._timestamp() + msg + eol, linkName:result.linkName, level:level}); 198 } catch (ex) { 199 // do nothing 200 } 201 }; 202 203 /** 204 * Checks if debugging is disabled. 205 * 206 * @return {boolean} <code>true</code> if disabled 207 */ 208 AjxDebug.prototype.isDisabled = 209 function () { 210 return !this._enabled; 211 }; 212 213 /** 214 * 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 215 * class and the CSS class (if any). The properties are sorted (numerically if they're all numbers). Creating and appending table 216 * elements worked in Mozilla but not IE. Using the insert* methods works for both. Properties that are function 217 * definitions are skipped. 218 * 219 * @param {constant} level debug level for the current debugger 220 * @param {object} obj the object to be printed 221 * @param {boolean} showFuncs if <code>true</code>, show props that are functions 222 */ 223 AjxDebug.prototype.dumpObj = 224 function(level, obj, showFuncs, linkName) { 225 if (!this._isWriteable()) { return; } 226 227 var result = this._handleArgs(arguments); 228 if (!result) { return; } 229 230 obj = result.args[0]; 231 if (!obj) { return; } 232 233 showFuncs = result.args[1]; 234 this._add({obj:obj, linkName:result.linkName, showFuncs:showFuncs, level:level}); 235 }; 236 237 /** 238 * Dumps a bunch of text into a <textarea>, so that it is wrapped and scrollable. HTML will not be rendered. 239 * 240 * @param {constant} level debug level for the current debugger 241 * @param {string} text the text to output as is 242 */ 243 AjxDebug.prototype.printRaw = 244 function(level, text, linkName) { 245 if (!this._isWriteable()) { return; } 246 247 var result = this._handleArgs(arguments); 248 if (!result) { return; } 249 250 this._add({obj:result.args[0], isRaw:true, linkName:result.linkName, level:level}); 251 }; 252 253 /** 254 * Pretty-prints a chunk of XML, doing color highlighting for different types of nodes. 255 * 256 * @param {constant} level debug level for the current debugger 257 * @param {string} text some XML 258 * 259 * TODO: fix for printing to console 260 */ 261 AjxDebug.prototype.printXML = 262 function(level, text, linkName) { 263 if (!this._isWriteable()) { return; } 264 265 var result = this._handleArgs(arguments); 266 if (!result) { return; } 267 268 text = result.args[0]; 269 if (!text) { return; } 270 271 // skip generating pretty xml if theres too much data 272 if (text.length > AjxDebug.MAX_OUT) { 273 this.printRaw(text); 274 return; 275 } 276 this._add({obj:text, isXml:true, linkName:result.linkName, level:level}); 277 }; 278 279 /** 280 * Reveals white space in text by replacing it with tags. 281 * 282 * @param {constant} level debug level for the current debugger 283 * @param {string} text the text to be displayed 284 */ 285 AjxDebug.prototype.display = 286 function(level, text, linkName) { 287 if (!this._isWriteable()) { return; } 288 289 var result = this._handleArgs(arguments); 290 if (!result) { return; } 291 292 text = result.args[0]; 293 text = text.replace(/\r?\n/g, '[crlf]'); 294 text = text.replace(/ /g, '[space]'); 295 text = text.replace(/\t/g, '[tab]'); 296 this.printRaw(level, text, linkName); 297 }; 298 299 /** 300 * Turn the display of timing statements on/off. 301 * 302 * @param {boolean} on if <code>true</code>, display timing statements 303 * @param {string} msg the message to show when timing is turned on 304 */ 305 AjxDebug.prototype.showTiming = 306 function(on, msg) { 307 this._showTiming = on; 308 if (on) { 309 this._enable(true); 310 } 311 var state = on ? "on" : "off"; 312 var text = "Turning timing info " + state; 313 if (msg) { 314 text = text + ": " + msg; 315 } 316 317 var debugMsg = new DebugMessage({msg:text}); 318 this._addMessage(debugMsg); 319 this._startTimePt = this._lastTimePt = new Date().getTime(); 320 }; 321 322 /** 323 * Displays time elapsed since last time point. 324 * 325 * @param {string} msg the text to display with timing info 326 * @param {boolean} restart if <code>true</code>, set timer back to zero 327 */ 328 AjxDebug.prototype.timePt = 329 function(msg, restart) { 330 if (!this._showTiming || !this._isWriteable()) { return; } 331 332 if (restart) { 333 this._startTimePt = this._lastTimePt = new Date().getTime(); 334 } 335 var now = new Date().getTime(); 336 var elapsed = now - this._startTimePt; 337 var interval = now - this._lastTimePt; 338 this._lastTimePt = now; 339 340 var spacer = restart ? "<br/>" : ""; 341 msg = msg ? " " + msg : ""; 342 var text = [spacer, "[", elapsed, " / ", interval, "]", msg].join(""); 343 var html = "<div>" + text + "</div>"; 344 345 // Add the message to our stack 346 this._addMessage(new DebugMessage({msg:html})); 347 return interval; 348 }; 349 350 AjxDebug.prototype.getContentFrame = 351 function() { 352 if (this._contentFrame) { 353 return this._contentFrame; 354 } 355 if (this._debugWindow && this._debugWindow.document) { 356 return this._debugWindow.document.getElementById(AjxDebug._CONTENT_FRAME_ID); 357 } 358 return null; 359 }; 360 361 AjxDebug.prototype.getLinkFrame = 362 function(noOpen) { 363 if (this._linkFrame) { 364 return this._linkFrame; 365 } 366 if (this._debugWindow && this._debugWindow.document) { 367 return this._debugWindow.document.getElementById(AjxDebug._LINK_FRAME_ID); 368 } 369 if (!noOpen) { 370 this._openDebugWindow(); 371 return this.getLinkFrame(true); 372 } 373 return null; 374 }; 375 376 // Private methods 377 378 AjxDebug.prototype._enable = 379 function(enabled) { 380 381 this._enabled = enabled; 382 if (this._target == AjxDebug.TGT_WINDOW) { 383 if (enabled) { 384 if (!this._dbgName) { 385 this._dbgName = "AjxDebugWin_" + location.hostname.replace(/\./g,'_'); 386 } 387 if (this._debugWindow == null || this._debugWindow.closed) { 388 this._openDebugWindow(); 389 } 390 } else { 391 if (this._debugWindow) { 392 this._debugWindow.close(); 393 this._debugWindow = null; 394 } 395 } 396 } 397 }; 398 399 AjxDebug.prototype._isWriteable = 400 function() { 401 if (this.isDisabled()) { 402 return false; 403 } 404 if (this._target == AjxDebug.TGT_WINDOW) { 405 try { 406 return (!this._isPaused && this._debugWindow && !this._debugWindow.closed); 407 } catch (ex) { 408 // OMG accessing the debugWindow in IE12 is sometimes throwing a COM exception 'An outgoing call 409 // cannot be made since the application is dispatching an input-synchronous call'. IOleWindow.GetWindow 410 // is marked with input-sync and the exception implies an attempt to access another COM apartment while 411 // executing it. Sounds like an IE12 Bug with this preview version. 412 413 // Just suppress the logging for this call, since it appears _debugWindow is inaccessible 414 return false; 415 416 } 417 } 418 return true; 419 }; 420 421 AjxDebug.prototype._getHtmlForObject = 422 function(obj, params) { 423 424 params = params || {}; 425 var html = []; 426 var idx = 0; 427 428 if (obj === undefined) { 429 html[idx++] = "<span>Undefined</span>"; 430 } else if (obj === null) { 431 html[idx++] = "<span>NULL</span>"; 432 } else if (AjxUtil.isBoolean(obj)) { 433 html[idx++] = "<span>" + obj + "</span>"; 434 } else if (AjxUtil.isNumber(obj)) { 435 html[idx++] = "<span>" + obj +"</span>"; 436 } else { 437 if (params.isRaw) { 438 html[idx++] = this._timestamp(); 439 html[idx++] = "<textarea rows='25' style='width:100%' readonly='true'>"; 440 html[idx++] = obj; 441 html[idx++] = "</textarea><p></p>"; 442 } else if (params.isXml) { 443 var xmldoc = new AjxDebugXmlDocument; 444 var doc = xmldoc.create(); 445 // IE bizarrely throws error if we use doc.loadXML here (bug 40451) 446 if (doc && ("loadXML" in doc)) { 447 doc.loadXML(obj); 448 html[idx++] = "<div style='border-width:2px; border-style:inset; width:100%; height:300px; overflow:auto'>"; 449 html[idx++] = this._createXmlTree(doc, 0, {"authToken":true}); 450 html[idx++] = "</div>"; 451 } else { 452 html[idx++] = "<span>Unable to create XmlDocument to show XML</span>"; 453 } 454 } else { 455 html[idx++] = "<div style='border-width:2px; border-style:inset; width:100%; height:300px; overflow:auto'><pre>"; 456 html[idx++] = this._dump(obj, true, params.showFuncs, {"ZmAppCtxt":true, "authToken":true}); 457 html[idx++] = "</div></pre>"; 458 } 459 } 460 return html.join(""); 461 }; 462 463 // Pretty-prints a Javascript object 464 AjxDebug.prototype._dump = 465 function(obj, recurse, showFuncs, omit) { 466 467 return AjxStringUtil.prettyPrint(obj, recurse, showFuncs, omit); 468 }; 469 470 /** 471 * Marshals args to public debug functions. In general, the debug level is an optional 472 * first arg. If the first arg is a debug level, check it and then strip it from the args. 473 * The last argument is an optional name for the link from the left panel. 474 * 475 * Returns an object with the link name and a list of the arguments (other than level and 476 * link name). 477 * 478 * @param {array} args an arguments list 479 * 480 * @private 481 */ 482 AjxDebug.prototype._handleArgs = 483 function(args) { 484 485 // don't output anything if debugging is off, or timing is on 486 if (this._level == AjxDebug.NONE || this._showTiming || args.length == 0) { return; } 487 488 // convert args to a true Array so they're easier to deal with 489 var argsArray = new Array(args.length); 490 for (var i = 0; i < args.length; i++) { 491 argsArray[i] = args[i]; 492 } 493 494 var result = {args:null, linkName:null}; 495 496 // remove link name from arg list if present - check if last arg is *Request or *Response 497 var origLen = argsArray.length; 498 if (argsArray.length > 1) { 499 var lastArg = argsArray[argsArray.length - 1]; 500 if (lastArg && lastArg.indexOf && (lastArg.indexOf("DebugWarn") != -1 || ((lastArg.indexOf(" ") == -1) && (/Request|Response$/.test(lastArg))))) { 501 result.linkName = lastArg; 502 argsArray.pop(); 503 } 504 } 505 506 // check level if provided, strip it from args; level is either a number, or 1-8 lowercase letters/numbers 507 var userLevel = null; 508 var firstArg = argsArray[0]; 509 var gotUserLevel = (typeof firstArg == "number" || ((origLen > 1) && firstArg.length <= 8 && /^[a-z0-9]+$/.test(firstArg))); 510 if (gotUserLevel) { 511 userLevel = firstArg; 512 argsArray.shift(); 513 } 514 if (userLevel && (AjxDebug.BUFFER_MAX[userLevel] == null)) { 515 if (typeof this._level == "number") { 516 if (typeof userLevel != "number" || (userLevel > this._level)) { return; } 517 } else { 518 if (userLevel != this._level) { return; } 519 } 520 } 521 result.args = argsArray; 522 523 return result; 524 }; 525 526 AjxDebug.prototype._openDebugWindow = 527 function(force) { 528 var name = AjxEnv.isIE ? "_blank" : this._dbgName; 529 this._debugWindow = window.open("", name, "width=600,height=400,resizable=yes,scrollbars=yes"); 530 531 if (this._debugWindow == null) { 532 this._enabled = false; 533 return; 534 } 535 536 this._enabled = true; 537 this._isPrevWinOpen = this._debugWindow.debug; 538 this._debugWindow.debug = true; 539 540 try { 541 this._document = this._debugWindow.document; 542 this.setTitle("Debug"); 543 544 if (!this._isPrevWinOpen) { 545 this._document.write( 546 "<html>", 547 "<head>", 548 "<script>", 549 "function blank() {return [", 550 "'<html><head><style type=\"text/css\">',", 551 "'P, TD, DIV, SPAN, SELECT, INPUT, TEXTAREA, BUTTON {',", 552 "'font-family: Tahoma, Arial, Helvetica, sans-serif;',", 553 "'font-size:11px;}',", 554 "'.Content {display:block;margin:0.25em 0em;}',", 555 "'.Link {cursor: pointer;color:blue;text-decoration:underline;white-space:nowrap;width:100%;}',", 556 "'.DebugWarn {color:red;font-weight:bold;}',", 557 "'.Run {color:black; background-color:red;width:100%;font-size:18px;font-weight:bold;}',", 558 "'.RunLink {display:block;color:black;background-color:red;font-weight:bold;white-space:nowrap;width:100%;}',", 559 "'</style></head><body></body></html>'].join(\"\");}", 560 "</script>", 561 "</head>", 562 "<frameset cols='125, *'>", 563 "<frameset rows='*,40'>", 564 "<frame name='", AjxDebug._LINK_FRAME_ID, "' id='", AjxDebug._LINK_FRAME_ID, "' src='javascript:parent.parent.blank();'>", 565 "<frame name='", AjxDebug._BOTTOM_FRAME_NAME, "' id='", AjxDebug._BOTTOM_FRAME_ID, "' src='javascript:parent.parent.blank();' scrolling=no frameborder=0>", 566 "</frameset>", 567 "<frame name='", AjxDebug._CONTENT_FRAME_ID, "' id='", AjxDebug._CONTENT_FRAME_ID, "' src='javascript:parent.blank();'>", 568 "</frameset>", 569 "</html>" 570 ); 571 this._document.close(); 572 573 var ta = new AjxTimedAction(this, AjxDebug.prototype._finishInitWindow); 574 AjxTimedAction.scheduleAction(ta, 2500); 575 } else { 576 this._finishInitWindow(); 577 578 this._contentFrame = this._document.getElementById(AjxDebug._CONTENT_FRAME_ID); 579 this._linkFrame = this._document.getElementById(AjxDebug._LINK_FRAME_ID); 580 this._createLinkNContent("RunLink", "NEW RUN", "Run", "NEW RUN"); 581 582 this._attachHandlers(); 583 584 this._dbgWindowInited = true; 585 // show any messages that have been queued up, while the window loaded. 586 this._showMessages(); 587 } 588 } catch (ex) { 589 if (this._debugWindow) { 590 this._debugWindow.close(); 591 } 592 this._openDebugWindow(true); 593 } 594 }; 595 596 AjxDebug.prototype._finishInitWindow = 597 function() { 598 try { 599 this._contentFrame = this._debugWindow.document.getElementById(AjxDebug._CONTENT_FRAME_ID); 600 this._linkFrame = this._debugWindow.document.getElementById(AjxDebug._LINK_FRAME_ID); 601 602 var frame = this._debugWindow.document.getElementById(AjxDebug._BOTTOM_FRAME_ID); 603 var doc = frame.contentWindow.document; 604 var html = []; 605 var i = 0; 606 html[i++] = "<table><tr><td><button id='"; 607 html[i++] = AjxDebug._BOTTOM_FRAME_ID; 608 html[i++] = "_clear'>Clear</button></td><td><button id='"; 609 html[i++] = AjxDebug._BOTTOM_FRAME_ID; 610 html[i++] = "_pause'>Pause</button></td></tr></table>"; 611 if (doc.body) { 612 doc.body.innerHTML = html.join(""); 613 } 614 } 615 catch (ex) { 616 // IE chokes on the popup window on cold start-up (when IE is started 617 // for the first time after system reboot). This should not prevent the 618 // app from running and should not bother the user 619 } 620 621 if (doc) { 622 this._clearBtn = doc.getElementById(AjxDebug._BOTTOM_FRAME_ID + "_clear"); 623 this._pauseBtn = doc.getElementById(AjxDebug._BOTTOM_FRAME_ID + "_pause"); 624 } 625 626 this._attachHandlers(); 627 this._dbgWindowInited = true; 628 this._showMessages(); 629 }; 630 631 AjxDebug.prototype._attachHandlers = 632 function() { 633 // Firefox allows us to attach an event listener, and runs it even though 634 // the window with the code is gone ... odd, but nice. IE, though will not 635 // run the handler, so we make sure, even if we're coming back to the 636 // window, to attach the onunload handler. In general reattach all handlers 637 // for IE 638 var unloadHandler = AjxCallback.simpleClosure(this._unloadHandler, this); 639 if (this._debugWindow.attachEvent) { 640 this._unloadHandler = unloadHandler; 641 this._debugWindow.attachEvent('onunload', unloadHandler); 642 } 643 else { 644 this._debugWindow.onunload = unloadHandler; 645 } 646 647 if (this._clearBtn) { 648 this._clearBtn.onclick = AjxCallback.simpleClosure(this._clear, this); 649 } 650 if (this._pauseBtn) { 651 this._pauseBtn.onclick = AjxCallback.simpleClosure(this._pause, this); 652 } 653 }; 654 655 /** 656 * Scrolls to the bottom of the window. How it does that depends on the browser. 657 * 658 * @private 659 */ 660 AjxDebug.prototype._scrollToBottom = 661 function() { 662 var contentFrame = this.getContentFrame(); 663 var contentBody = contentFrame ? contentFrame.contentWindow.document.body : null; 664 var linkFrame = this.getLinkFrame(); 665 var linkBody = linkFrame ? linkFrame.contentWindow.document.body : null; 666 667 if (contentBody && linkBody) { 668 contentBody.scrollTop = contentBody.scrollHeight; 669 linkBody.scrollTop = linkBody.scrollHeight; 670 } 671 }; 672 673 /** 674 * Returns a timestamp string, if we are showing them. 675 * @private 676 */ 677 AjxDebug.prototype._timestamp = 678 function() { 679 return this._showTime ? this._getTimeStamp() + ": " : ""; 680 }; 681 682 AjxDebug.prototype.setShowTimestamps = 683 function(show) { 684 this._showTime = show; 685 }; 686 687 /** 688 * This function takes an XML node and returns an HTML string that displays that node 689 * the indent argument is used to describe what depth the node is at so that 690 * the HTML code can create a nice indentation. 691 * 692 * @private 693 */ 694 AjxDebug.prototype._createXmlTree = 695 function (node, indent, omit) { 696 if (node == null) { return ""; } 697 698 var str = ""; 699 var len; 700 switch (node.nodeType) { 701 case 1: // Element 702 str += "<div style='color: blue; padding-left: 16px;'><<span style='color: DarkRed;'>" + node.nodeName + "</span>"; 703 704 if (omit && omit[node.nodeName]) { 705 return str + "/></div>"; 706 } 707 708 var attrs = node.attributes; 709 len = attrs.length; 710 for (var i = 0; i < len; i++) { 711 str += this._createXmlAttribute(attrs[i]); 712 } 713 714 if (!node.hasChildNodes()) { 715 return str + "/></div>"; 716 } 717 str += "><br />"; 718 719 var cs = node.childNodes; 720 len = cs.length; 721 for (var i = 0; i < len; i++) { 722 str += this._createXmlTree(cs[i], indent + 3, omit); 723 } 724 str += "</<span style='color: DarkRed;'>" + node.nodeName + "</span>></div>"; 725 break; 726 727 case 9: // Document 728 var cs = node.childNodes; 729 len = cs.length; 730 for (var i = 0; i < len; i++) { 731 str += this._createXmlTree(cs[i], indent, omit); 732 } 733 break; 734 735 case 3: // Text 736 if (!/^\s*$/.test(node.nodeValue)) { 737 var val = node.nodeValue.replace(/</g, "<").replace(/>/g, ">"); 738 str += "<span style='color: WindowText; padding-left: 16px;'>" + val + "</span><br />"; 739 } 740 break; 741 742 case 7: // ProcessInstruction 743 str += "<?" + node.nodeName; 744 745 var attrs = node.attributes; 746 len = attrs.length; 747 for (var i = 0; i < len; i++) { 748 str += this._createXmlAttribute(attrs[i]); 749 } 750 str+= "?><br />" 751 break; 752 753 case 4: // CDATA 754 str = "<div style=''><![CDATA[<span style='color: WindowText; font-family: \"Courier New\"; white-space: pre; display: block; border-left: 1px solid Gray; padding-left: 16px;'>" + 755 node.nodeValue + 756 "</span>]" + "]></div>"; 757 break; 758 759 case 8: // Comment 760 str = "<div style='color: blue; padding-left: 16px;'><!--<span style='white-space: pre; font-family: \"Courier New\"; color: Gray; display: block;'>" + 761 node.nodeValue + 762 "</span>--></div>"; 763 break; 764 765 case 10: 766 str = "<div style='color: blue; padding-left: 16px'><!DOCTYPE " + node.name; 767 if (node.publicId) { 768 str += " PUBLIC \"" + node.publicId + "\""; 769 if (node.systemId) 770 str += " \"" + node.systemId + "\""; 771 } 772 else if (node.systemId) { 773 str += " SYSTEM \"" + node.systemId + "\""; 774 } 775 str += "></div>"; 776 777 // TODO: Handle custom DOCTYPE declarations (ELEMENT, ATTRIBUTE, ENTITY) 778 break; 779 780 default: 781 this._inspect(node); 782 } 783 784 return str; 785 }; 786 787 AjxDebug.prototype._createXmlAttribute = 788 function(a) { 789 return [" <span style='color: red'>", a.nodeName, "</span><span style='color: blue'>=\"", a.nodeValue, "\"</span>"].join(""); 790 }; 791 792 AjxDebug.prototype._inspect = 793 function(obj) { 794 var str = ""; 795 for (var k in obj) { 796 str += "obj." + k + " = " + obj[k] + "\n"; 797 } 798 window.alert(str); 799 }; 800 801 AjxDebug.prototype._add = 802 function(params) { 803 804 params.extraHtml = params.obj && this._getHtmlForObject(params.obj, params); 805 806 // Add the message to our stack 807 this._addMessage(new DebugMessage(params)); 808 }; 809 810 AjxDebug.prototype._addMessage = 811 function(msg) { 812 this._msgQueue.push(msg); 813 this._showMessages(); 814 }; 815 816 AjxDebug.prototype._showMessages = 817 function() { 818 819 switch (this._target) { 820 case AjxDebug.TGT_WINDOW: 821 this._showMessagesInWindow(); 822 break; 823 case AjxDebug.TGT_CONSOLE: 824 this._showMessagesInConsole(); 825 } 826 this._addMessagesToBuffer(); 827 this._msgQueue = []; 828 }; 829 830 AjxDebug.prototype._showMessagesInWindow = 831 function() { 832 833 if (!this._dbgWindowInited) { 834 // For now, don't show the messages-- assuming that this case only 835 // happens at startup, and many messages will be written 836 return; 837 } 838 try { 839 if (this._msgQueue.length > 0) { 840 var contentFrame = this.getContentFrame(); 841 var linkFrame = this.getLinkFrame(); 842 if (!contentFrame || !linkFrame) { return; } 843 844 var contentFrameDoc = contentFrame.contentWindow.document; 845 var linkFrameDoc = linkFrame.contentWindow.document; 846 var now = new Date(); 847 for (var i = 0, len = this._msgQueue.length; i < len; ++i ) { 848 var msg = this._msgQueue[i]; 849 var linkLabel = msg.linkName; 850 var contentLabel = [msg.message, msg.extraHtml].join(""); 851 this._createLinkNContent("Link", linkLabel, "Content", contentLabel, now); 852 } 853 } 854 855 this._scrollToBottom(); 856 } catch (ex) {} 857 }; 858 859 AjxDebug.prototype._addMessagesToBuffer = 860 function() { 861 862 var eol = (this._target == AjxDebug.TGT_CONSOLE) ? "<br>" : ""; 863 for (var i = 0, len = this._msgQueue.length; i < len; ++i ) { 864 var msg = this._msgQueue[i]; 865 AjxDebug._addMessageToBuffer(msg.type, msg.message + msg.extraHtml + eol); 866 } 867 }; 868 869 AjxDebug._addMessageToBuffer = 870 function(type, msg) { 871 872 type = type || AjxDebug.DEFAULT_TYPE; 873 var max = AjxDebug.BUFFER_MAX[type]; 874 if (max > 0) { 875 var buffer = AjxDebug.BUFFER[type] = AjxDebug.BUFFER[type] || []; 876 while (buffer.length >= max) { 877 buffer.shift(); 878 } 879 buffer.push(msg); 880 } 881 }; 882 883 AjxDebug.prototype._showMessagesInConsole = 884 function() { 885 886 if (!window.console) { return; } 887 888 var now = new Date(); 889 for (var i = 0, len = this._msgQueue.length; i < len; ++i ) { 890 var msg = this._msgQueue[i]; 891 if (window.console && window.console.log) { 892 window.console.log(AjxStringUtil.stripTags(msg.message + msg.extraHtml)); 893 } 894 } 895 }; 896 897 AjxDebug.prototype._getTimeStamp = 898 function(date) { 899 if (!AjxDebug._timestampFormatter) { 900 AjxDebug._timestampFormatter = new AjxDateFormat("HH:mm:ss.SSS"); 901 } 902 date = date || new Date(); 903 return AjxStringUtil.htmlEncode(AjxDebug._timestampFormatter.format(date), true); 904 }; 905 906 AjxDebug.prototype._createLinkNContent = 907 function(linkClass, linkLabel, contentClass, contentLabel, now) { 908 909 var linkFrame = this.getLinkFrame(); 910 if (!linkFrame) { return; } 911 912 now = now || new Date(); 913 var timeStamp = ["[", this._getTimeStamp(now), "]"].join(""); 914 var id = "Lnk_" + now.getTime(); 915 916 // create link 917 if (linkLabel) { 918 var linkFrameDoc = linkFrame.contentWindow.document; 919 var linkEl = linkFrameDoc.createElement("DIV"); 920 linkEl.className = linkClass; 921 linkEl.innerHTML = [linkLabel, timeStamp].join(" - "); 922 linkEl._targetId = id; 923 linkEl._dbg = this; 924 linkEl.onclick = AjxDebug._linkClicked; 925 926 var linkBody = linkFrameDoc.body; 927 linkBody.appendChild(linkEl); 928 } 929 930 // create content 931 var contentFrameDoc = this.getContentFrame().contentWindow.document; 932 var contentEl = contentFrameDoc.createElement("DIV"); 933 contentEl.className = contentClass; 934 contentEl.id = id; 935 contentEl.innerHTML = contentLabel; 936 937 contentFrameDoc.body.appendChild(contentEl); 938 939 // always show latest 940 this._scrollToBottom(); 941 }; 942 943 AjxDebug._linkClicked = 944 function() { 945 var contentFrame = this._dbg.getContentFrame(); 946 var el = contentFrame.contentWindow.document.getElementById(this._targetId); 947 var y = 0; 948 while (el) { 949 y += el.offsetTop; 950 el = el.offsetParent; 951 } 952 953 contentFrame.contentWindow.scrollTo(0, y); 954 }; 955 956 AjxDebug.prototype._clear = 957 function() { 958 this.getContentFrame().contentWindow.document.body.innerHTML = ""; 959 this.getLinkFrame().contentWindow.document.body.innerHTML = ""; 960 }; 961 962 AjxDebug.prototype._pause = 963 function() { 964 this._isPaused = !this._isPaused; 965 this._pauseBtn.innerHTML = this._isPaused ? "Resume" : "Pause"; 966 }; 967 968 AjxDebug.prototype._unloadHandler = 969 function() { 970 if (!this._debugWindow) { return; } // is there anything to do? 971 972 // detach event handlers 973 if (this._debugWindow.detachEvent) { 974 this._debugWindow.detachEvent('onunload', this._unloadHandler); 975 } else { 976 this._debugWindow.onunload = null; 977 } 978 }; 979 980 AjxDebug.println = 981 function(type, msg) { 982 AjxDebug._addMessageToBuffer(type, msg + "<br>"); 983 }; 984 985 AjxDebug.dumpObj = 986 function(type, obj) { 987 AjxDebug._addMessageToBuffer(type, "<pre>" + AjxStringUtil.prettyPrint(obj, true) + "</pre>"); 988 }; 989 990 /** 991 * 992 * @param {hash} params hash of params: 993 * @param {string} methodNameStr SOAP method, eg SearchRequest or SearchResponse 994 * @param {boolean} asyncMode true if request made asynchronously 995 */ 996 AjxDebug.logSoapMessage = 997 function(params) { 998 999 if (params.methodNameStr == "NoOpRequest" || params.methodNameStr == "NoOpResponse") { return; } 1000 1001 var ts = AjxDebug._getTimeStamp(); 1002 var msg = ["<b>", params.methodNameStr, params.asyncMode ? "" : " (SYNCHRONOUS)" , " - ", ts, "</b>"].join(""); 1003 for (var type in AjxDebug.BUFFER) { 1004 if (type == AjxDebug.DEFAULT_TYPE) { continue; } 1005 AjxDebug.println(type, msg); 1006 } 1007 if (window.DBG) { 1008 // Link is written here: 1009 var linkName = params.methodNameStr; 1010 if (!params.asyncMode) { 1011 linkName = "<span class='DebugWarn'>SYNCHRONOUS </span>" + linkName; 1012 } 1013 window.DBG.println(window.DBG._level, msg, linkName); 1014 } 1015 }; 1016 1017 AjxDebug._getTimeStamp = 1018 function(date) { 1019 return AjxDebug.prototype._getTimeStamp.apply(null, arguments); 1020 }; 1021 1022 AjxDebug.getDebugLog = 1023 function(type) { 1024 1025 type = type || AjxDebug.DEFAULT_TYPE; 1026 var buffer = AjxDebug.BUFFER[type]; 1027 return buffer ? buffer.join("") : ""; 1028 }; 1029 1030 /** 1031 * Simple wrapper for log messages. 1032 * @private 1033 */ 1034 DebugMessage = function(params) { 1035 1036 params = params || {}; 1037 this.message = params.msg || ""; 1038 this.type = params.type || null; 1039 this.category = params.category || ""; 1040 this.time = params.time || (new Date().getTime()); 1041 this.extraHtml = params.extraHtml || ""; 1042 this.linkName = params.linkName; 1043 this.type = (params.level && typeof(params.level) == "string") ? params.level : AjxDebug.DEFAULT_TYPE; 1044 }; 1045