1 /* See license.txt for terms of usage */ 2 3 define([ 4 "firebug/lib/object", 5 "firebug/firebug", 6 "firebug/chrome/reps", 7 "firebug/lib/locale", 8 "firebug/lib/events", 9 "firebug/lib/css", 10 "firebug/lib/dom", 11 "firebug/lib/search", 12 "firebug/chrome/menu", 13 "firebug/lib/options", 14 "firebug/lib/wrapper", 15 "firebug/lib/xpcom", 16 "firebug/console/profiler", 17 "firebug/chrome/searchBox" 18 ], 19 function(Obj, Firebug, FirebugReps, Locale, Events, Css, Dom, Search, Menu, Options, 20 Wrapper, Xpcom) { 21 22 // ********************************************************************************************* // 23 // Constants 24 25 var versionChecker = Xpcom.CCSV("@mozilla.org/xpcom/version-comparator;1", "nsIVersionComparator"); 26 var appInfo = Xpcom.CCSV("@mozilla.org/xre/app-info;1", "nsIXULAppInfo"); 27 var firefox15AndHigher = versionChecker.compare(appInfo.version, "15") >= 0; 28 29 const Cc = Components.classes; 30 const Ci = Components.interfaces; 31 32 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // 33 34 const logTypes = 35 { 36 "error": 1, 37 "warning": 1, 38 "info": 1, 39 "debug": 1, 40 "profile": 1, 41 "table": 1, 42 "group": 1, 43 "command": 1, 44 "stackTrace": 1, 45 "log": 1, 46 "dir": 1, 47 "assert": 1, 48 "spy": 1 49 }; 50 51 // ********************************************************************************************* // 52 53 Firebug.ConsolePanel = function () {}; 54 55 Firebug.ConsolePanel.prototype = Obj.extend(Firebug.ActivablePanel, 56 { 57 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // 58 // Members 59 60 wasScrolledToBottom: false, 61 messageCount: 0, 62 lastLogTime: 0, 63 groups: null, 64 limit: null, 65 order: 10, 66 67 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // 68 // extends Panel 69 70 name: "console", 71 searchable: true, 72 breakable: true, 73 editable: false, 74 enableA11y: true, 75 76 initialize: function() 77 { 78 Firebug.ActivablePanel.initialize.apply(this, arguments); // loads persisted content 79 80 if (!this.persistedContent && Firebug.Console.isAlwaysEnabled()) 81 { 82 this.insertLogLimit(this.context); 83 84 if (this.context.consoleReloadWarning) // we have not yet injected the console 85 this.insertReloadWarning(); 86 } 87 }, 88 89 destroy: function(state) 90 { 91 if (FBTrace.DBG_CONSOLE) 92 FBTrace.sysout("console.destroy; wasScrolledToBottom: " + 93 this.wasScrolledToBottom + " " + this.context.getName()); 94 95 if (state) 96 state.wasScrolledToBottom = this.wasScrolledToBottom; 97 98 // If we are profiling and reloading, save the profileRow for the new context 99 if (this.context.profileRow && this.context.profileRow.ownerDocument) 100 { 101 this.context.profileRow.parentNode.removeChild(this.context.profileRow); 102 state.profileRow = this.context.profileRow; 103 } 104 105 if (FBTrace.DBG_CONSOLE) 106 FBTrace.sysout("console.destroy; wasScrolledToBottom: " + 107 this.wasScrolledToBottom + ", " + this.context.getName()); 108 109 Firebug.ActivablePanel.destroy.apply(this, arguments); // must be called last 110 }, 111 112 initializeNode: function() 113 { 114 Firebug.ActivablePanel.initializeNode.apply(this, arguments); 115 116 this.onScroller = Obj.bind(this.onScroll, this); 117 Events.addEventListener(this.panelNode, "scroll", this.onScroller, true); 118 119 this.onResizer = Obj.bind(this.onResize, this); 120 this.resizeEventTarget = Firebug.chrome.$('fbContentBox'); 121 Events.addEventListener(this.resizeEventTarget, "resize", this.onResizer, true); 122 }, 123 124 destroyNode: function() 125 { 126 Firebug.ActivablePanel.destroyNode.apply(this, arguments); 127 128 if (this.onScroller) 129 Events.removeEventListener(this.panelNode, "scroll", this.onScroller, true); 130 131 Events.removeEventListener(this.resizeEventTarget, "resize", this.onResizer, true); 132 }, 133 134 show: function(state) 135 { 136 if (FBTrace.DBG_CONSOLE) 137 FBTrace.sysout("Console.panel show; wasScrolledToBottom: " + 138 (state ? state.wasScrolledToBottom : "no prev state") + 139 " " + this.context.getName(), state); 140 141 this.showCommandLine(true); 142 this.showToolbarButtons("fbConsoleButtons", true); 143 144 this.setFilter(Firebug.consoleFilterTypes); 145 146 Firebug.chrome.setGlobalAttribute("cmd_firebug_togglePersistConsole", "checked", 147 this.persistContent); 148 149 this.showPanel(state); 150 }, 151 152 showPanel: function(state) 153 { 154 var wasScrolledToBottom; 155 if (state) 156 wasScrolledToBottom = state.wasScrolledToBottom; 157 158 if (typeof wasScrolledToBottom == "boolean") 159 { 160 this.wasScrolledToBottom = wasScrolledToBottom; 161 delete state.wasScrolledToBottom; 162 } 163 else if (typeof this.wasScrolledToBottom != "boolean") 164 { 165 // If the previous state doesn't says where to scroll, 166 // scroll to the bottom by default. 167 this.wasScrolledToBottom = true; 168 } 169 170 if (this.wasScrolledToBottom) 171 Dom.scrollToBottom(this.panelNode); 172 173 if (FBTrace.DBG_CONSOLE) 174 FBTrace.sysout("console.show; wasScrolledToBottom: " + 175 this.wasScrolledToBottom + ", " + this.context.getName()); 176 177 if (state && state.profileRow) // then we reloaded while profiling 178 { 179 if (FBTrace.DBG_CONSOLE) 180 FBTrace.sysout("console.show; state.profileRow:", state.profileRow); 181 182 this.context.profileRow = state.profileRow; 183 this.panelNode.appendChild(state.profileRow); 184 delete state.profileRow; 185 } 186 }, 187 188 hide: function(state) 189 { 190 if (FBTrace.DBG_CONSOLE) 191 FBTrace.sysout("console.hide; wasScrolledToBottom: " + 192 this.wasScrolledToBottom + " " + this.context.getName()); 193 194 if (state) 195 state.wasScrolledToBottom = this.wasScrolledToBottom; 196 197 this.showCommandLine(false); 198 199 if (FBTrace.DBG_CONSOLE) 200 FBTrace.sysout("console.hide; wasScrolledToBottom: " + 201 this.wasScrolledToBottom + ", " + this.context.getName()); 202 }, 203 204 updateOption: function(name, value) 205 { 206 if (name == "consoleFilterTypes") 207 { 208 Firebug.Console.syncFilterButtons(Firebug.chrome); 209 Firebug.connection.eachContext(function syncFilters(context) 210 { 211 Firebug.Console.onToggleFilter(context, value); 212 }); 213 } 214 }, 215 216 shouldBreakOnNext: function() 217 { 218 // xxxHonza: shouldn't the breakOnErrors be context related? 219 // xxxJJB, yes, but we can't support it because we can't yet tell 220 // which window the error is on. 221 return Options.get("breakOnErrors"); 222 }, 223 224 getBreakOnNextTooltip: function(enabled) 225 { 226 return (enabled ? Locale.$STR("console.Disable Break On All Errors") : 227 Locale.$STR("console.Break On All Errors")); 228 }, 229 230 /** 231 * Support for panel activation. 232 */ 233 onActivationChanged: function(enable) 234 { 235 if (FBTrace.DBG_CONSOLE || FBTrace.DBG_ACTIVATION) 236 FBTrace.sysout("console.ConsolePanel.onActivationChanged; " + enable); 237 238 if (enable) 239 Firebug.Console.addObserver(this); 240 else 241 Firebug.Console.removeObserver(this); 242 }, 243 244 getOptionsMenuItems: function() 245 { 246 return [ 247 Menu.optionMenu("ShowJavaScriptErrors", "showJSErrors", 248 "console.option.tip.Show_JavaScript_Errors"), 249 Menu.optionMenu("ShowJavaScriptWarnings", "showJSWarnings", 250 "console.option.tip.Show_JavaScript_Warnings"), 251 Menu.optionMenu("ShowCSSErrors", "showCSSErrors", 252 "console.option.tip.Show_CSS_Errors"), 253 Menu.optionMenu("ShowXMLHTMLErrors", "showXMLErrors", 254 "console.option.tip.Show_XML_HTML_Errors"), 255 Menu.optionMenu("ShowXMLHttpRequests", "showXMLHttpRequests", 256 "console.option.tip.Show_XMLHttpRequests"), 257 Menu.optionMenu("ShowChromeErrors", "showChromeErrors", 258 "console.option.tip.Show_System_Errors"), 259 Menu.optionMenu("ShowChromeMessages", "showChromeMessages", 260 "console.option.tip.Show_System_Messages"), 261 Menu.optionMenu("ShowNetworkErrors", "showNetworkErrors", 262 "console.option.tip.Show_Network_Errors"), 263 this.getShowStackTraceMenuItem(), 264 this.getStrictOptionMenuItem(), 265 "-", 266 Menu.optionMenu("console.option.Show_Command_Editor", "commandEditor", 267 "console.option.tip.Show_Command_Editor"), 268 Menu.optionMenu("commandLineShowCompleterPopup", "commandLineShowCompleterPopup", 269 "console.option.tip.Show_Completion_List_Popup") 270 ]; 271 }, 272 273 getShowStackTraceMenuItem: function() 274 { 275 var menuItem = Menu.optionMenu("ShowStackTrace", "showStackTrace", 276 "console.option.tip.Show_Stack_Trace"); 277 278 if (Firebug.currentContext && !Firebug.Debugger.isAlwaysEnabled()) 279 menuItem.disabled = true; 280 281 return menuItem; 282 }, 283 284 getStrictOptionMenuItem: function() 285 { 286 var strictDomain = "javascript.options"; 287 var strictName = "strict"; 288 var strictValue = Options.getPref(strictDomain, strictName); 289 290 return { 291 label: "JavascriptOptionsStrict", 292 type: "checkbox", 293 checked: strictValue, 294 tooltiptext: "console.option.tip.Show_Strict_Warnings", 295 command: function() 296 { 297 var checked = this.hasAttribute("checked"); 298 Options.setPref(strictDomain, strictName, checked); 299 } 300 }; 301 }, 302 303 getBreakOnMenuItems: function() 304 { 305 return []; 306 }, 307 308 setFilter: function(filterTypes) 309 { 310 var panelNode = this.panelNode; 311 312 Events.dispatch(this.fbListeners, "onFilterSet", [logTypes]); 313 314 for (var type in logTypes) 315 { 316 // Different types of errors and warnings are combined for filtering 317 if (filterTypes == "all" || filterTypes == "" || filterTypes.indexOf(type) != -1 || 318 (filterTypes.indexOf("error") != -1 && (type == "error" || type == "errorMessage")) || 319 (filterTypes.indexOf("warning") != -1 && (type == "warn" || type == "warningMessage"))) 320 { 321 Css.removeClass(panelNode, "hideType-" + type); 322 } 323 else 324 { 325 Css.setClass(panelNode, "hideType-" + type); 326 } 327 } 328 }, 329 330 search: function(text) 331 { 332 // Make previously visible nodes invisible again 333 if (this.matchSet) 334 { 335 for (var i in this.matchSet) 336 Css.removeClass(this.matchSet[i], "matched"); 337 } 338 339 if (!text) 340 return; 341 342 this.matchSet = []; 343 344 function findRow(node) { return Dom.getAncestorByClass(node, "logRow"); } 345 var search = new Search.TextSearch(this.panelNode, findRow); 346 347 var logRow = search.find(text); 348 if (!logRow) 349 { 350 Events.dispatch(this.fbListeners, "onConsoleSearchMatchFound", [this, text, []]); 351 return false; 352 } 353 354 for (; logRow; logRow = search.findNext()) 355 { 356 Css.setClass(logRow, "matched"); 357 this.matchSet.push(logRow); 358 } 359 360 Events.dispatch(this.fbListeners, "onConsoleSearchMatchFound", 361 [this, text, this.matchSet]); 362 363 return true; 364 }, 365 366 breakOnNext: function(breaking) 367 { 368 Options.set("breakOnErrors", breaking); 369 }, 370 371 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // 372 373 append: function(appender, objects, className, rep, sourceLink, noRow) 374 { 375 var container = this.getTopContainer(); 376 377 if (noRow) 378 { 379 appender.apply(this, [objects]); 380 } 381 else 382 { 383 var row = this.createRow("logRow", className); 384 385 appender.apply(this, [objects, row, rep]); 386 387 if (sourceLink) 388 FirebugReps.SourceLink.tag.append({object: sourceLink}, row); 389 390 container.appendChild(row); 391 392 this.filterLogRow(row, this.wasScrolledToBottom); 393 394 if (FBTrace.DBG_CONSOLE) 395 FBTrace.sysout("console.append; wasScrolledToBottom " + this.wasScrolledToBottom + 396 " " + row.textContent); 397 398 if (this.wasScrolledToBottom) 399 Dom.scrollToBottom(this.panelNode); 400 401 return row; 402 } 403 }, 404 405 clear: function() 406 { 407 if (this.panelNode) 408 { 409 if (FBTrace.DBG_CONSOLE) 410 FBTrace.sysout("ConsolePanel.clear"); 411 Dom.clearNode(this.panelNode); 412 this.insertLogLimit(this.context); 413 414 Dom.scrollToBottom(this.panelNode); 415 this.wasScrolledToBottom = true; 416 417 // Don't forget to clear opened groups, if any. 418 this.groups = null; 419 } 420 }, 421 422 insertLogLimit: function() 423 { 424 // Create limit row. This row is the first in the list of entries 425 // and initially hidden. It's displayed as soon as the number of 426 // entries reaches the limit. 427 var row = this.createRow("limitRow"); 428 429 var limitInfo = { 430 totalCount: 0, 431 limitPrefsTitle: Locale.$STRF("LimitPrefsTitle", 432 [Options.prefDomain+".console.logLimit"]) 433 }; 434 435 var netLimitRep = Firebug.NetMonitor.NetLimit; 436 var nodes = netLimitRep.createTable(row, limitInfo); 437 438 this.limit = nodes[1]; 439 440 var container = this.panelNode; 441 container.insertBefore(nodes[0], container.firstChild); 442 }, 443 444 insertReloadWarning: function() 445 { 446 // put the message in, we will clear if the window console is injected. 447 this.warningRow = this.append(this.appendObject, Locale.$STR( 448 "message.Reload to activate window console"), "info"); 449 }, 450 451 clearReloadWarning: function() 452 { 453 if (this.warningRow && this.warningRow.parentNode) 454 { 455 this.warningRow.parentNode.removeChild(this.warningRow); 456 delete this.warningRow; 457 } 458 }, 459 460 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // 461 462 appendObject: function(object, row, rep) 463 { 464 // Issue 5712: Firefox crashes when trying to log XMLHTTPRequest to console 465 // xxxHonza: should be removed as soon as Firefox 16 is the minimum version. 466 if (!firefox15AndHigher) 467 { 468 if (typeof(object) == "object") 469 { 470 try 471 { 472 // xxxHonza: could we log directly the unwrapped object? 473 var unwrapped = Wrapper.unwrapObject(object); 474 if (unwrapped.constructor.name == "XMLHttpRequest") 475 object = object + ""; 476 } 477 catch (e) 478 { 479 if (FBTrace.DBG_ERRORS) 480 FBTrace.sysout("consolePanel.appendObject; EXCEPTION " + e, e); 481 } 482 } 483 } 484 485 if (!rep) 486 rep = Firebug.getRep(object, this.context); 487 488 // Don't forget to pass the template itself as the 'self' parameter so that it's used 489 // by domplate as the 'subject' for the generation. Note that the primary purpose 490 // of the subject is to provide a context object ('with (subject) {...}') for data that 491 // are dynamically consumed during the rendering process. 492 // This allows to derive new templates from an existing ones, without breaking 493 // the default subject set within domplate() function. 494 return rep.tag.append({object: object}, row, rep); 495 }, 496 497 appendFormatted: function(objects, row, rep) 498 { 499 function logText(text, row) 500 { 501 var nodeSpan = row.ownerDocument.createElement("span"); 502 Css.setClass(nodeSpan, "logRowHint"); 503 var node = row.ownerDocument.createTextNode(text); 504 row.appendChild(nodeSpan); 505 nodeSpan.appendChild(node); 506 } 507 508 function logTextNode(text, row) 509 { 510 var nodeSpan = row.ownerDocument.createElement("span"); 511 if (text === "" || text === null || typeof(text) == "undefined") 512 Css.setClass(nodeSpan, "logRowHint"); 513 514 if (text === "") 515 text = Locale.$STR("console.msg.an_empty_string"); 516 517 var node = row.ownerDocument.createTextNode(text); 518 row.appendChild(nodeSpan); 519 nodeSpan.appendChild(node); 520 } 521 522 if (!objects || !objects.length) 523 { 524 // Make sure the log-row has proper height (even if empty). 525 logText(Locale.$STR("console.msg.nothing_to_output"), row); 526 return; 527 } 528 529 var format = objects[0]; 530 var objIndex = 1; 531 532 if (typeof(format) != "string") 533 { 534 format = ""; 535 objIndex = 0; 536 } 537 else 538 { 539 // So, we have only a string... 540 if (objects.length === 1) 541 { 542 // ...and it has no characters. 543 if (format.length < 1) 544 { 545 logText(Locale.$STR("console.msg.an_empty_string"), row); 546 return; 547 } 548 } 549 } 550 551 var parts = parseFormat(format); 552 var trialIndex = objIndex; 553 for (var i = 0; i < parts.length; i++) 554 { 555 var part = parts[i]; 556 if (part && typeof(part) == "object") 557 { 558 if (trialIndex++ >= objects.length) 559 { 560 // Too few parameters for format, assume unformatted. 561 format = ""; 562 objIndex = 0; 563 parts.length = 0; 564 break; 565 } 566 } 567 } 568 569 // Last CSS style defined using "%c" that should be applied on 570 // created log-row parts (elements). See issue 6064. 571 // Example: console.log('%cred-text %cgreen-text', 'color:red', 'color:green'); 572 var lastStyle; 573 574 for (var i = 0; i < parts.length; ++i) 575 { 576 var node; 577 var part = parts[i]; 578 if (part && typeof(part) == "object") 579 { 580 var object = objects[objIndex]; 581 if (part.type == "%c") 582 lastStyle = object.toString(); 583 else if (objIndex < objects.length) 584 node = this.appendObject(object, row, part.rep); 585 else 586 node = this.appendObject(part.type, row, FirebugReps.Text); 587 objIndex++; 588 } 589 else 590 { 591 node = FirebugReps.Text.tag.append({object: part}, row); 592 } 593 594 // Apply custom style if available. 595 if (lastStyle && node) 596 node.setAttribute("style", lastStyle); 597 598 node = null; 599 } 600 601 for (var i = objIndex; i < objects.length; ++i) 602 { 603 logTextNode(" ", row); 604 605 var object = objects[i]; 606 if (typeof(object) == "string") 607 logTextNode(object, row); 608 else 609 this.appendObject(object, row); 610 } 611 }, 612 613 appendCollapsedGroup: function(objects, row, rep) 614 { 615 this.appendOpenGroup(objects, row, rep); 616 Css.removeClass(row, "opened"); 617 }, 618 619 appendOpenGroup: function(objects, row, rep) 620 { 621 if (!this.groups) 622 this.groups = []; 623 624 Css.setClass(row, "logGroup"); 625 Css.setClass(row, "opened"); 626 627 var innerRow = this.createRow("logRow"); 628 Css.setClass(innerRow, "logGroupLabel"); 629 630 // Custom rep is used in place of group label. 631 if (rep) 632 rep.tag.replace({"object": objects}, innerRow); 633 else 634 this.appendFormatted(objects, innerRow, rep); 635 636 row.appendChild(innerRow); 637 Events.dispatch(this.fbListeners, 'onLogRowCreated', [this, innerRow]); 638 639 // Create group body, which is displayed when the group is expanded. 640 var groupBody = this.createRow("logGroupBody"); 641 row.appendChild(groupBody); 642 groupBody.setAttribute('role', 'group'); 643 this.groups.push(groupBody); 644 645 // Expand/collapse logic. 646 Events.addEventListener(innerRow, "mousedown", function(event) 647 { 648 if (Events.isLeftClick(event)) 649 { 650 var groupRow = event.currentTarget.parentNode; 651 if (Css.hasClass(groupRow, "opened")) 652 { 653 Css.removeClass(groupRow, "opened"); 654 event.target.setAttribute('aria-expanded', 'false'); 655 } 656 else 657 { 658 Css.setClass(groupRow, "opened"); 659 event.target.setAttribute('aria-expanded', 'true'); 660 } 661 } 662 }, false); 663 }, 664 665 appendCloseGroup: function(object, row, rep) 666 { 667 if (this.groups) 668 this.groups.pop(); 669 }, 670 671 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // 672 // private 673 674 createRow: function(rowName, className) 675 { 676 var elt = this.document.createElement("div"); 677 elt.className = rowName + (className ? " " + rowName + "-" + className : ""); 678 return elt; 679 }, 680 681 getTopContainer: function() 682 { 683 if (this.groups && this.groups.length) 684 return this.groups[this.groups.length-1]; 685 else 686 return this.panelNode; 687 }, 688 689 filterLogRow: function(logRow, scrolledToBottom) 690 { 691 if (this.searchText) 692 { 693 Css.setClass(logRow, "matching"); 694 Css.setClass(logRow, "matched"); 695 696 // Search after a delay because we must wait for a frame to be created for 697 // the new logRow so that the finder will be able to locate it 698 setTimeout(Obj.bindFixed(function() 699 { 700 if (this.searchFilter(this.searchText, logRow)) 701 this.matchSet.push(logRow); 702 else 703 Css.removeClass(logRow, "matched"); 704 705 Css.removeClass(logRow, "matching"); 706 707 if (scrolledToBottom) 708 Dom.scrollToBottom(this.panelNode); 709 }, this), 100); 710 } 711 }, 712 713 searchFilter: function(text, logRow) 714 { 715 var count = this.panelNode.childNodes.length; 716 var searchRange = this.document.createRange(); 717 searchRange.setStart(this.panelNode, 0); 718 searchRange.setEnd(this.panelNode, count); 719 720 var startPt = this.document.createRange(); 721 startPt.setStartBefore(logRow); 722 723 var endPt = this.document.createRange(); 724 endPt.setStartAfter(logRow); 725 726 return Search.finder.Find(text, searchRange, startPt, endPt) != null; 727 }, 728 729 showCommandLine: function(shouldShow) 730 { 731 if (shouldShow) 732 { 733 Dom.collapse(Firebug.chrome.$("fbCommandBox"), false); 734 Firebug.CommandLine.setMultiLine(Firebug.commandEditor, Firebug.chrome); 735 } 736 else 737 { 738 // Make sure that entire content of the Console panel is hidden when 739 // the panel is disabled. 740 Firebug.CommandLine.setMultiLine(false, Firebug.chrome, Firebug.commandEditor); 741 Dom.collapse(Firebug.chrome.$("fbCommandBox"), true); 742 } 743 }, 744 745 onScroll: function(event) 746 { 747 // Update the scroll position flag if the position changes. 748 this.wasScrolledToBottom = Dom.isScrolledToBottom(this.panelNode); 749 750 if (FBTrace.DBG_CONSOLE) 751 FBTrace.sysout("console.onScroll; wasScrolledToBottom: " + 752 this.wasScrolledToBottom + ", wasScrolledToBottom: " + 753 this.context.getName(), event); 754 }, 755 756 onResize: function(event) 757 { 758 if (FBTrace.DBG_CONSOLE) 759 FBTrace.sysout("console.onResize; wasScrolledToBottom: " + 760 this.wasScrolledToBottom + ", offsetHeight: " + this.panelNode.offsetHeight + 761 ", scrollTop: " + this.panelNode.scrollTop + ", scrollHeight: " + 762 this.panelNode.scrollHeight + ", " + this.context.getName(), event); 763 764 if (this.wasScrolledToBottom) 765 Dom.scrollToBottom(this.panelNode); 766 }, 767 768 showInfoTip: function(infoTip, target, x, y) 769 { 770 var object = Firebug.getRepObject(target); 771 var rep = Firebug.getRep(object, this.context); 772 if (!rep) 773 return false; 774 775 return rep.showInfoTip(infoTip, target, x, y); 776 } 777 }); 778 779 // ********************************************************************************************* // 780 781 function parseFormat(format) 782 { 783 var parts = []; 784 if (format.length <= 0) 785 return parts; 786 787 var reg = /((^%|(?=.)%)(\d+)?(\.)([a-zA-Z]))|((^%|(?=.)%)([a-zA-Z]))/; 788 for (var m = reg.exec(format); m; m = reg.exec(format)) 789 { 790 if (m[0].substr(0, 2) == "%%") 791 { 792 parts.push(format.substr(0, m.index)); 793 parts.push(m[0].substr(1)); 794 } 795 else 796 { 797 var type = m[8] ? m[8] : m[5]; 798 var precision = m[3] ? parseInt(m[3]) : (m[4] == "." ? -1 : 0); 799 800 var rep = null; 801 switch (type) 802 { 803 case "s": 804 rep = FirebugReps.Text; 805 break; 806 807 case "f": 808 case "i": 809 case "d": 810 rep = FirebugReps.Number; 811 break; 812 813 case "o": 814 case "c": 815 rep = null; 816 break; 817 } 818 819 parts.push(format.substr(0, m[0][0] == "%" ? m.index : m.index+1)); 820 parts.push({rep: rep, precision: precision, type: ("%" + type)}); 821 } 822 823 format = format.substr(m.index+m[0].length); 824 } 825 826 parts.push(format); 827 return parts; 828 } 829 830 // ********************************************************************************************* // 831 // Registration 832 833 Firebug.registerPanel(Firebug.ConsolePanel); 834 835 return Firebug.ConsolePanel; 836 837 // ********************************************************************************************* // 838 }); 839