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