1 /* See license.txt for terms of usage */
  2 
  3 FBL.ns(function() { with (FBL) {
  4 
  5 // ************************************************************************************************
  6 // Constants
  7 
  8 const Cc = Components.classes;
  9 const Ci = Components.interfaces;
 10 const nsIPrefBranch2 = Ci.nsIPrefBranch2;
 11 const PrefService = Cc["@mozilla.org/preferences-service;1"];
 12 const prefs = PrefService.getService(nsIPrefBranch2);
 13 
 14 // ************************************************************************************************
 15 
 16 var maxQueueRequests = 500;
 17 
 18 // ************************************************************************************************
 19 
 20 Firebug.ConsoleBase =
 21 {
 22     log: function(object, context, className, rep, noThrottle, sourceLink)
 23     {
 24         dispatch(this.fbListeners,"log",[context, object, className, sourceLink]);
 25         return this.logRow(appendObject, object, context, className, rep, sourceLink, noThrottle);
 26     },
 27 
 28     logFormatted: function(objects, context, className, noThrottle, sourceLink)
 29     {
 30         dispatch(this.fbListeners,"logFormatted",[context, objects, className, sourceLink]);
 31         return this.logRow(appendFormatted, objects, context, className, null, sourceLink, noThrottle);
 32     },
 33 
 34     openGroup: function(objects, context, className, rep, noThrottle, sourceLink, noPush)
 35     {
 36         return this.logRow(appendOpenGroup, objects, context, className, rep, sourceLink, noThrottle);
 37     },
 38 
 39     closeGroup: function(context, noThrottle)
 40     {
 41         return this.logRow(appendCloseGroup, null, context, null, null, null, noThrottle, true);
 42     },
 43 
 44     logRow: function(appender, objects, context, className, rep, sourceLink, noThrottle, noRow)
 45     {
 46         if (!context)
 47             context = FirebugContext;
 48 
 49         if (FBTrace.DBG_ERRORS && !context)
 50             FBTrace.sysout("Console.logRow has no context, skipping objects", objects);
 51 
 52         if (!context)
 53             return;
 54 
 55         if (noThrottle || !context)
 56         {
 57             var panel = this.getPanel(context);
 58             if (panel)
 59             {
 60                 var row = panel.append(appender, objects, className, rep, sourceLink, noRow);
 61                 var container = panel.panelNode;
 62                 var template = Firebug.NetMonitor.NetLimit;
 63 
 64                 while (container.childNodes.length > maxQueueRequests + 1)
 65                 {
 66                     clearDomplate(container.firstChild.nextSibling);
 67                     container.removeChild(container.firstChild.nextSibling);
 68                     panel.limit.limitInfo.totalCount++;
 69                     template.updateCounter(panel.limit);
 70                 }
 71                 dispatch([Firebug.A11yModel], "onLogRowCreated", [panel , row]);
 72                 return row;
 73             }
 74         }
 75         else
 76         {
 77             if (!context.throttle)
 78             {
 79                 FBTrace.sysout("console.logRow has not context.throttle! ");
 80                 return;
 81             }
 82             var args = [appender, objects, context, className, rep, sourceLink, true, noRow];
 83             context.throttle(this.logRow, this, args);
 84         }
 85     },
 86 
 87     appendFormatted: function(args, row, context)
 88     {
 89         if (!context)
 90             context = FirebugContext;
 91 
 92         var panel = this.getPanel(context);
 93         panel.appendFormatted(args, row);
 94     },
 95 
 96     clear: function(context)
 97     {
 98         if (!context)
 99             context = FirebugContext;
100 
101         if (context)
102             Firebug.Errors.clear(context);
103 
104         var panel = this.getPanel(context, true);
105         if (panel)
106             panel.clear();
107     },
108 
109     // Override to direct output to your panel
110     getPanel: function(context, noCreate)
111     {
112         if (context)
113             return context.getPanel("console", noCreate);
114     },
115 
116 };
117 
118 // ************************************************************************************************
119 
120 var ActivableConsole = extend(Firebug.ActivableModule, Firebug.ConsoleBase);
121 
122 Firebug.Console = extend(ActivableConsole,
123 {
124     dispatchName: "console",
125 
126     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
127     // extends Module
128 
129     showPanel: function(browser, panel)
130     {
131     },
132 
133     getFirebugConsoleElement: function(context, win)
134     {
135         var element = win.document.getElementById("_firebugConsole");
136         if (!element)
137         {
138             if (FBTrace.DBG_CONSOLE)
139                 FBTrace.sysout("getFirebugConsoleElement forcing element");
140             var elementForcer = "(function(){var r=null; try { r = window._getFirebugConsoleElement();}catch(exc){r=exc;} return r;})();";  // we could just add the elements here
141 
142             if (context.stopped)
143                 Firebug.Console.injector.evaluateConsoleScript(context);  // todo evaluate consoleForcer on stack
144             else
145                 var r = Firebug.CommandLine.evaluateInWebPage(elementForcer, context, win);
146 
147             if (FBTrace.DBG_CONSOLE)
148                 FBTrace.sysout("getFirebugConsoleElement forcing element result "+r, r);
149 
150             var element = win.document.getElementById("_firebugConsole");
151             if (!element) // elementForce fails
152             {
153                 if (FBTrace.DBG_ERRORS) FBTrace.sysout("console.getFirebugConsoleElement: no _firebugConsole in win:", win);
154                 Firebug.Console.logFormatted(["Firebug cannot find _firebugConsole element", r, win], context, "error", true);
155             }
156         }
157 
158         return element;
159     },
160 
161     isReadyElsePreparing: function(context, win) // this is the only code that should call injector.attachIfNeeded
162     {
163         if (FBTrace.DBG_CONSOLE)
164             FBTrace.sysout("console.isReadyElsePreparing, win is " +
165                 (win?"an argument: ":"null, context.window: ") +
166                 (win?win.location:context.window.location), (win?win:context.window));
167 
168         if (win)
169             return this.injector.attachIfNeeded(context, win);
170         else
171         {
172             var attached = true;
173             for (var i = 0; i < context.windows.length; i++)
174                 attached = attached && this.injector.attachIfNeeded(context, context.windows[i]);
175             // already in the list above attached = attached && this.injector.attachIfNeeded(context, context.window);
176             if (context.windows.indexOf(context.window) == -1)
177                 FBTrace.sysout("isReadyElsePreparing ***************** context.window not in context.windows");
178             if (FBTrace.DBG_CONSOLE)
179                 FBTrace.sysout("console.isReadyElsePreparing attached to "+context.windows.length+" and returns "+attached);
180             return attached;
181         }
182     },
183 
184     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
185     // extends ActivableModule
186 
187     initialize: function()
188     {
189         this.panelName = "console";
190 
191         Firebug.ActivableModule.initialize.apply(this, arguments);
192         Firebug.Debugger.addListener(this);
193 
194     },
195 
196     enable: function()
197     {
198         if (Firebug.Console.isAlwaysEnabled())
199             this.watchForErrors();
200     },
201 
202     disable: function()
203     {
204         if (Firebug.Console.isAlwaysEnabled())
205             this.unwatchForErrors();
206     },
207 
208     initContext: function(context, persistedState)
209     {
210         Firebug.ActivableModule.initContext.apply(this, arguments);
211         context.consoleReloadWarning = true;  // mark as need to warn.
212     },
213 
214     loadedContext: function(context)
215     {
216         for (var url in context.sourceFileMap)
217             return;  // if there are any sourceFiles, then do nothing
218 
219         // else we saw no JS, so the reload warning it not needed.
220         this.clearReloadWarning(context);
221     },
222 
223     clearReloadWarning: function(context) // remove the warning about reloading.
224     {
225          if (context.consoleReloadWarning)
226          {
227              var panel = context.getPanel(this.panelName);
228              panel.clearReloadWarning();
229              delete context.consoleReloadWarning;
230          }
231     },
232 
233     togglePersist: function(context)
234     {
235         var panel = context.getPanel(this.panelName);
236         panel.persistContent = panel.persistContent ? false : true;
237         Firebug.chrome.setGlobalAttribute("cmd_togglePersistConsole", "checked", panel.persistContent);
238     },
239 
240     showContext: function(browser, context)
241     {
242         Firebug.chrome.setGlobalAttribute("cmd_clearConsole", "disabled", !context);
243 
244         Firebug.ActivableModule.showContext.apply(this, arguments);
245     },
246 
247     destroyContext: function(context, persistedState)
248     {
249         Firebug.Console.injector.detachConsole(context, context.window);  // TODO iterate windows?
250     },
251 
252     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
253 
254     onPanelEnable: function(panelName)
255     {
256         if (panelName != this.panelName)  // we don't care about other panels
257             return;
258 
259         if (FBTrace.DBG_CONSOLE)
260             FBTrace.sysout("console.onPanelEnable**************");
261 
262         this.watchForErrors();
263         Firebug.Debugger.addDependentModule(this); // we inject the console during JS compiles so we need jsd
264     },
265 
266     onPanelDisable: function(panelName)
267     {
268         if (panelName != this.panelName)  // we don't care about other panels
269             return;
270 
271         if (FBTrace.DBG_CONSOLE)
272             FBTrace.sysout("console.onPanelDisable**************");
273 
274         Firebug.Debugger.removeDependentModule(this); // we inject the console during JS compiles so we need jsd
275         this.unwatchForErrors();
276 
277         // Make sure possible errors coming from the page and displayed in the Firefox
278         // status bar are removed.
279         this.clear();
280     },
281 
282     onSuspendFirebug: function()
283     {
284         if (FBTrace.DBG_CONSOLE)
285             FBTrace.sysout("console.onSuspendFirebug\n");
286         if (Firebug.Console.isAlwaysEnabled())
287             this.unwatchForErrors();
288     },
289 
290     onResumeFirebug: function()
291     {
292         if (FBTrace.DBG_CONSOLE)
293             FBTrace.sysout("console.onResumeFirebug\n");
294         if (Firebug.Console.isAlwaysEnabled())
295             this.watchForErrors();
296     },
297 
298     watchForErrors: function()
299     {
300         Firebug.Errors.checkEnabled();
301         $('fbStatusIcon').setAttribute("console", "on");
302     },
303 
304     unwatchForErrors: function()
305     {
306         Firebug.Errors.checkEnabled();
307         $('fbStatusIcon').removeAttribute("console");
308     },
309 
310     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
311     // Firebug.Debugger listener
312 
313     onMonitorScript: function(context, frame)
314     {
315         Firebug.Console.log(frame, context);
316     },
317 
318     onFunctionCall: function(context, frame, depth, calling)
319     {
320         if (calling)
321             Firebug.Console.openGroup([frame, "depth:"+depth], context);
322         else
323             Firebug.Console.closeGroup(context);
324     },
325 
326     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
327 
328     logRow: function(appender, objects, context, className, rep, sourceLink, noThrottle, noRow)
329     {
330         if (!context)
331             context = FirebugContext;
332 
333         if (FBTrace.DBG_WINDOWS && !context) FBTrace.sysout("Console.logRow: no context \n");
334 
335         if (this.isAlwaysEnabled())
336             return Firebug.ConsoleBase.logRow.apply(this, arguments);
337     },
338 });
339 
340 Firebug.ConsoleListener =
341 {
342     log: function(context, object, className, sourceLink)
343     {
344     },
345 
346     logFormatted: function(context, objects, className, sourceLink)
347     {
348     }
349 };
350 
351 // ************************************************************************************************
352 
353 Firebug.ConsolePanel = function () {} // XXjjb attach Firebug so this panel can be extended.
354 
355 Firebug.ConsolePanel.prototype = extend(Firebug.ActivablePanel,
356 {
357     wasScrolledToBottom: false,
358     messageCount: 0,
359     lastLogTime: 0,
360     groups: null,
361     limit: null,
362 
363     append: function(appender, objects, className, rep, sourceLink, noRow)
364     {
365         var container = this.getTopContainer();
366 
367         if (noRow)
368         {
369             appender.apply(this, [objects]);
370         }
371         else
372         {
373             // Don't update the this.wasScrolledToBottom flag now. At the beginning (when the
374             // first log is created) the isScrolledToBottom always returns true.
375             // But make sure the panel scrolls if it should.
376             var wasScrolledToBottom = false;
377             if (this.panelNode.offsetHeight)
378                 wasScrolledToBottom = isScrolledToBottom(this.panelNode);
379 
380             var row = this.createRow("logRow", className);
381             appender.apply(this, [objects, row, rep]);
382 
383             if (sourceLink)
384                 FirebugReps.SourceLink.tag.append({object: sourceLink}, row);
385 
386             container.appendChild(row);
387 
388             this.filterLogRow(row, this.wasScrolledToBottom);
389 
390             if (wasScrolledToBottom)
391                 scrollToBottom(this.panelNode);
392 
393             return row;
394         }
395     },
396 
397     clear: function()
398     {
399         if (this.panelNode)
400         {
401             if (FBTrace.DBG_CONSOLE)
402                 FBTrace.sysout("ConsolePanel.clear");
403             clearNode(this.panelNode);
404             this.insertLogLimit(this.context);
405         }
406     },
407 
408     insertLogLimit: function()
409     {
410         // Create limit row. This row is the first in the list of entries
411         // and initially hidden. It's displayed as soon as the number of
412         // entries reaches the limit.
413         var row = this.createRow("limitRow");
414 
415         var limitInfo = {
416             totalCount: 0,
417             limitPrefsTitle: $STRF("LimitPrefsTitle", [Firebug.prefDomain+".console.logLimit"])
418         };
419 
420         var netLimitRep = Firebug.NetMonitor.NetLimit;
421         var nodes = netLimitRep.createTable(row, limitInfo);
422 
423         this.limit = nodes[1];
424 
425         var container = this.panelNode;
426         container.insertBefore(nodes[0], container.firstChild);
427     },
428 
429     insertReloadWarning: function()
430     {
431         // put the message in, we will clear if the window console is injected.
432         this.warningRow = this.append(appendObject, $STR("message.Reload to activate window console"), "info");
433     },
434 
435     clearReloadWarning: function()
436     {
437         if (this.warningRow)
438         {
439             this.warningRow.parentNode.removeChild(this.warningRow);
440             delete this.warningRow;
441         }
442     },
443 
444     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
445 
446     appendObject: function(object, row, rep)
447     {
448         if (!rep)
449             rep = Firebug.getRep(object);
450         return rep.tag.append({object: object}, row);
451     },
452 
453     appendFormatted: function(objects, row, rep)
454     {
455         if (!objects || !objects.length)
456             return;
457 
458         function logText(text, row)
459         {
460             var node = row.ownerDocument.createTextNode(text);
461             row.appendChild(node);
462         }
463 
464         var format = objects[0];
465         var objIndex = 0;
466 
467         if (typeof(format) != "string")
468         {
469             format = "";
470             objIndex = -1;
471         }
472         else  // a string
473         {
474             if (objects.length === 1) // then we have only a string...
475             {
476                 if (format.length < 1) { // ...and it has no characters.
477                     logText("(an empty string)", row);
478                     return;
479                 }
480             }
481         }
482 
483         var parts = parseFormat(format);
484         var trialIndex = objIndex;
485         for (var i= 0; i < parts.length; i++)
486         {
487             var part = parts[i];
488             if (part && typeof(part) == "object")
489             {
490                 if (++trialIndex > objects.length)  // then too few parameters for format, assume unformatted.
491                 {
492                     format = "";
493                     objIndex = -1;
494                     parts.length = 0;
495                     break;
496                 }
497             }
498 
499         }
500         for (var i = 0; i < parts.length; ++i)
501         {
502             var part = parts[i];
503             if (part && typeof(part) == "object")
504             {
505                 var object = objects[++objIndex];
506                 if (part.type == "%c")
507                     row.setAttribute("style", object.toString());
508                 else if (typeof(object) != "undefined")
509                     this.appendObject(object, row, part.rep);
510                 else
511                     this.appendObject(part.type, row, FirebugReps.Text);
512             }
513             else
514                 FirebugReps.Text.tag.append({object: part}, row);
515         }
516 
517         for (var i = objIndex+1; i < objects.length; ++i)
518         {
519             logText(" ", row);
520             var object = objects[i];
521             if (typeof(object) == "string")
522                 FirebugReps.Text.tag.append({object: object}, row);
523             else
524                 this.appendObject(object, row);
525         }
526     },
527 
528     appendOpenGroup: function(objects, row, rep)
529     {
530         if (!this.groups)
531             this.groups = [];
532 
533         setClass(row, "logGroup");
534         setClass(row, "opened");
535 
536         var innerRow = this.createRow("logRow");
537         setClass(innerRow, "logGroupLabel");
538         if (rep)
539             rep.tag.replace({"objects": objects}, innerRow);
540         else
541             this.appendFormatted(objects, innerRow, rep);
542         row.appendChild(innerRow);
543         dispatch([Firebug.A11yModel], 'onLogRowCreated', [this, innerRow]);
544         var groupBody = this.createRow("logGroupBody");
545         row.appendChild(groupBody);
546         groupBody.setAttribute('role', 'group');
547         this.groups.push(groupBody);
548 
549         innerRow.addEventListener("mousedown", function(event)
550         {
551             if (isLeftClick(event))
552             {
553                 var groupRow = event.currentTarget.parentNode;
554                 if (hasClass(groupRow, "opened"))
555                 {
556                     removeClass(groupRow, "opened");
557                     event.target.setAttribute('aria-expanded', 'false');
558                 }
559                 else
560                 {
561                     setClass(groupRow, "opened");
562                     event.target.setAttribute('aria-expanded', 'true');
563                 }
564             }
565         }, false);
566     },
567 
568     appendCloseGroup: function(object, row, rep)
569     {
570         if (this.groups)
571             this.groups.pop();
572     },
573 
574     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
575     // extends Panel
576 
577     name: "console",
578     searchable: true,
579     breakable: true,
580     editable: false,
581 
582     initialize: function()
583     {
584         Firebug.ActivablePanel.initialize.apply(this, arguments);  // loads persisted content
585 
586         if (!this.persistedContent && Firebug.Console.isAlwaysEnabled())
587         {
588             this.insertLogLimit(this.context);
589 
590             // Initialize log limit and listen for changes.
591             this.updateMaxLimit();
592 
593             if (this.context.consoleReloadWarning)  // we have not yet injected the console
594                 this.insertReloadWarning();
595         }
596 
597         prefs.addObserver(Firebug.prefDomain, this, false);
598     },
599 
600     initializeNode : function()
601     {
602         dispatch([Firebug.A11yModel], 'onInitializeNode', [this]);
603         if (FBTrace.DBG_CONSOLE)
604         {
605             this.onScroller = bind(this.onScroll, this);
606             this.panelNode.addEventListener("scroll", this.onScroller, true);
607         }
608 
609         this.onResizer = bind(this.onResize, this);
610         this.resizeEventTarget = Firebug.chrome.$('fbContentBox');
611         this.resizeEventTarget.addEventListener("resize", this.onResizer, true);
612     },
613 
614     destroyNode : function()
615     {
616         dispatch([Firebug.A11yModel], 'onDestroyNode', [this]);
617         if (this.onScroller)
618             this.panelNode.removeEventListener("scroll", this.onScroller, true);
619 
620         this.resizeEventTarget.removeEventListener("resize", this.onResizer, true);
621     },
622 
623     shutdown: function()
624     {
625         prefs.removeObserver(Firebug.prefDomain, this, false);
626     },
627 
628     show: function(state)
629     {
630         if (FBTrace.DBG_CONSOLE)
631             FBTrace.sysout("Console.panel show; " + this.context.getName(), state);
632 
633         var enabled = Firebug.Console.isAlwaysEnabled();
634         if (enabled)
635         {
636              Firebug.Console.disabledPanelPage.hide(this);
637              this.showCommandLine(true);
638              this.showToolbarButtons("fbConsoleButtons", true);
639              Firebug.chrome.setGlobalAttribute("cmd_togglePersistConsole", "checked", this.persistContent);
640 
641              if (state && state.wasScrolledToBottom)
642              {
643                  this.wasScrolledToBottom = state.wasScrolledToBottom;
644                  delete state.wasScrolledToBottom;
645              }
646 
647              if (this.wasScrolledToBottom)
648                  scrollToBottom(this.panelNode);
649 
650              if (FBTrace.DBG_CONSOLE)
651                  FBTrace.sysout("console.show ------------------ wasScrolledToBottom: " +
652                     this.wasScrolledToBottom + ", " + this.context.getName());
653 
654              if (state && state.profileRow) // then we reloaded while profiling
655              {
656                  FBTrace.sysout("console.initialize state.profileRow:", state.profileRow);
657                  this.context.profileRow = state.profileRow;
658                  this.panelNode.appendChild(state.profileRow);
659                  delete state.profileRow;
660              }
661         }
662         else
663         {
664             this.hide(state);
665             Firebug.Console.disabledPanelPage.show(this);
666         }
667     },
668 
669     hide: function(state)
670     {
671         if (FBTrace.DBG_CONSOLE)
672             FBTrace.sysout("Console.panel hide; " + this.context.getName(), state);
673 
674         this.showToolbarButtons("fbConsoleButtons", false);
675         this.showCommandLine(false);
676 
677         if (FBTrace.DBG_CONSOLE)
678             FBTrace.sysout("console.hide ------------------ wasScrolledToBottom: " +
679                 this.wasScrolledToBottom + ", " + this.context.getName());
680     },
681 
682     destroy: function(state)
683     {
684         Firebug.ActivablePanel.destroy.apply(this, arguments);
685 
686         if (this.panelNode.offsetHeight)
687             this.wasScrolledToBottom = isScrolledToBottom(this.panelNode);
688 
689         if (state)
690             state.wasScrolledToBottom = this.wasScrolledToBottom;
691 
692         // If we are profiling and reloading, save the profileRow for the new context
693         if (this.context.profileRow && this.context.profileRow.ownerDocument)
694         {
695             this.context.profileRow.parentNode.removeChild(this.context.profileRow);
696             state.profileRow = this.context.profileRow;
697         }
698 
699         if (FBTrace.DBG_CONSOLE)
700             FBTrace.sysout("console.destroy ------------------ wasScrolledToBottom: " +
701                 this.wasScrolledToBottom + ", " + this.context.getName());
702     },
703 
704     shouldBreakOnNext: function()
705     {
706         // xxxHonza: shouldn't the breakOnErrors be context related?
707         // xxxJJB, yes, but we can't support it because we can't yet tell
708         // which window the error is on.
709         return Firebug.getPref(Firebug.servicePrefDomain, "breakOnErrors");
710     },
711 
712     getBreakOnNextTooltip: function(enabled)
713     {
714         return (enabled ? $STR("console.Disable Break On All Errors") :
715             $STR("console.Break On All Errors"));
716     },
717 
718     enablePanel: function(module)
719     {
720         if (FBTrace.DBG_CONSOLE)
721             FBTrace.sysout("console.ConsolePanel.enablePanel; " + this.context.getName());
722 
723         Firebug.ActivablePanel.enablePanel.apply(this, arguments);
724 
725         this.showCommandLine(true);
726 
727         if (this.wasScrolledToBottom)
728             scrollToBottom(this.panelNode);
729     },
730 
731     disablePanel: function(module)
732     {
733         if (FBTrace.DBG_CONSOLE)
734             FBTrace.sysout("console.ConsolePanel.disablePanel; " + this.context.getName());
735 
736         Firebug.ActivablePanel.disablePanel.apply(this, arguments);
737 
738         this.showCommandLine(false);
739     },
740 
741     getOptionsMenuItems: function()
742     {
743         return [
744             optionMenu("ShowJavaScriptErrors", "showJSErrors"),
745             optionMenu("ShowJavaScriptWarnings", "showJSWarnings"),
746             optionMenu("ShowCSSErrors", "showCSSErrors"),
747             optionMenu("ShowXMLErrors", "showXMLErrors"),
748             optionMenu("ShowXMLHttpRequests", "showXMLHttpRequests"),
749             optionMenu("ShowChromeErrors", "showChromeErrors"),
750             optionMenu("ShowChromeMessages", "showChromeMessages"),
751             optionMenu("ShowExternalErrors", "showExternalErrors"),
752             optionMenu("ShowNetworkErrors", "showNetworkErrors"),
753             this.getShowStackTraceMenuItem(),
754             this.getStrictOptionMenuItem(),
755             "-",
756             optionMenu("LargeCommandLine", "largeCommandLine")
757         ];
758     },
759 
760     getShowStackTraceMenuItem: function()
761     {
762         var menuItem = serviceOptionMenu("ShowStackTrace", "showStackTrace");
763         if (FirebugContext && !Firebug.Debugger.isAlwaysEnabled())
764             menuItem.disabled = true;
765         return menuItem;
766     },
767 
768     getStrictOptionMenuItem: function()
769     {
770         var strictDomain = "javascript.options";
771         var strictName = "strict";
772         var strictValue = prefs.getBoolPref(strictDomain+"."+strictName);
773         return {label: "JavascriptOptionsStrict", type: "checkbox", checked: strictValue,
774             command: bindFixed(Firebug.setPref, Firebug, strictDomain, strictName, !strictValue) };
775     },
776 
777     getBreakOnMenuItems: function()
778     {
779         //xxxHonza: no BON options for now.
780         /*return [
781             optionMenu("console.option.Persist Break On Error", "persistBreakOnError")
782         ];*/
783        return [];
784     },
785 
786     search: function(text)
787     {
788         if (!text)
789             return;
790 
791         // Make previously visible nodes invisible again
792         if (this.matchSet)
793         {
794             for (var i in this.matchSet)
795                 removeClass(this.matchSet[i], "matched");
796         }
797 
798         this.matchSet = [];
799 
800         function findRow(node) { return getAncestorByClass(node, "logRow"); }
801         var search = new TextSearch(this.panelNode, findRow);
802 
803         var logRow = search.find(text);
804         if (!logRow)
805         {
806             dispatch([Firebug.A11yModel], 'onConsoleSearchMatchFound', [this, text, []]);
807             return false;
808         }
809         for (; logRow; logRow = search.findNext())
810         {
811             setClass(logRow, "matched");
812             this.matchSet.push(logRow);
813         }
814         dispatch([Firebug.A11yModel], 'onConsoleSearchMatchFound', [this, text, this.matchSet]);
815         return true;
816     },
817 
818     breakOnNext: function(breaking)
819     {
820         Firebug.setPref(Firebug.servicePrefDomain, "breakOnErrors", breaking);
821     },
822 
823     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
824     // private
825 
826     createRow: function(rowName, className)
827     {
828         var elt = this.document.createElement("div");
829         elt.className = rowName + (className ? " " + rowName + "-" + className : "");
830         return elt;
831     },
832 
833     getTopContainer: function()
834     {
835         if (this.groups && this.groups.length)
836             return this.groups[this.groups.length-1];
837         else
838             return this.panelNode;
839     },
840 
841     filterLogRow: function(logRow, scrolledToBottom)
842     {
843         if (this.searchText)
844         {
845             setClass(logRow, "matching");
846             setClass(logRow, "matched");
847 
848             // Search after a delay because we must wait for a frame to be created for
849             // the new logRow so that the finder will be able to locate it
850             setTimeout(bindFixed(function()
851             {
852                 if (this.searchFilter(this.searchText, logRow))
853                     this.matchSet.push(logRow);
854                 else
855                     removeClass(logRow, "matched");
856 
857                 removeClass(logRow, "matching");
858 
859                 if (scrolledToBottom)
860                     scrollToBottom(this.panelNode);
861             }, this), 100);
862         }
863     },
864 
865     searchFilter: function(text, logRow)
866     {
867         var count = this.panelNode.childNodes.length;
868         var searchRange = this.document.createRange();
869         searchRange.setStart(this.panelNode, 0);
870         searchRange.setEnd(this.panelNode, count);
871 
872         var startPt = this.document.createRange();
873         startPt.setStartBefore(logRow);
874 
875         var endPt = this.document.createRange();
876         endPt.setStartAfter(logRow);
877 
878         return finder.Find(text, searchRange, startPt, endPt) != null;
879     },
880 
881     // nsIPrefObserver
882     observe: function(subject, topic, data)
883     {
884         // We're observing preferences only.
885         if (topic != "nsPref:changed")
886           return;
887 
888         // xxxHonza check this out.
889         var prefDomain = "Firebug.extension.";
890         var prefName = data.substr(prefDomain.length);
891         if (prefName == "console.logLimit")
892             this.updateMaxLimit();
893     },
894 
895     updateMaxLimit: function()
896     {
897         var value = Firebug.getPref(Firebug.prefDomain, "console.logLimit");
898         maxQueueRequests =  value ? value : maxQueueRequests;
899     },
900 
901     showCommandLine: function(shouldShow)
902     {
903         if (shouldShow)
904         {
905             collapse(Firebug.chrome.$("fbCommandBox"), false);
906             Firebug.CommandLine.setMultiLine(Firebug.largeCommandLine, Firebug.chrome);
907         }
908         else
909         {
910             // Make sure that entire content of the Console panel is hidden when
911             // the panel is disabled.
912             Firebug.CommandLine.setMultiLine(false, Firebug.chrome, Firebug.largeCommandLine);
913             collapse(Firebug.chrome.$("fbCommandBox"), true);
914         }
915     },
916 
917     onScroll: function(event)
918     {
919         // Update the scroll position flag if the position changes.
920         this.wasScrolledToBottom = FBL.isScrolledToBottom(this.panelNode);
921 
922         if (FBTrace.DBG_CONSOLE)
923             FBTrace.sysout("console.onScroll ------------------ wasScrolledToBottom: " +
924                 this.wasScrolledToBottom + ", wasScrolledToBottom: " +
925                 this.context.getName(), event);
926     },
927 
928     onResize: function(event)
929     {
930         if (FBTrace.DBG_CONSOLE)
931             FBTrace.sysout("console.onResize ------------------ wasScrolledToBottom: " +
932                 this.wasScrolledToBottom + ", offsetHeight: " + this.panelNode.offsetHeight +
933                 ", scrollTop: " + this.panelNode.scrollTop + ", scrollHeight: " +
934                 this.panelNode.scrollHeight + ", " + this.context.getName(), event);
935 
936         if (this.wasScrolledToBottom)
937             scrollToBottom(this.panelNode);
938     },
939 });
940 
941 // ************************************************************************************************
942 
943 function parseFormat(format)
944 {
945     var parts = [];
946     if (format.length <= 0)
947         return parts;
948 
949     var reg = /((^%|.%)(\d+)?(\.)([a-zA-Z]))|((^%|.%)([a-zA-Z]))/;
950     for (var m = reg.exec(format); m; m = reg.exec(format))
951     {
952         if (m[0].substr(0, 2) == "%%")
953         {
954             parts.push(format.substr(0, m.index));
955             parts.push(m[0].substr(1));
956         }
957         else
958         {
959             var type = m[8] ? m[8] : m[5];
960             var precision = m[3] ? parseInt(m[3]) : (m[4] == "." ? -1 : 0);
961 
962             var rep = null;
963             switch (type)
964             {
965                 case "s":
966                     rep = FirebugReps.Text;
967                     break;
968                 case "f":
969                 case "i":
970                 case "d":
971                     rep = FirebugReps.Number;
972                     break;
973                 case "o":
974                 case "c":
975                     rep = null;
976                     break;
977             }
978 
979             parts.push(format.substr(0, m[0][0] == "%" ? m.index : m.index+1));
980             parts.push({rep: rep, precision: precision, type: ("%" + type)});
981         }
982 
983         format = format.substr(m.index+m[0].length);
984     }
985 
986     parts.push(format);
987     return parts;
988 }
989 
990 // ************************************************************************************************
991 
992 var appendObject = Firebug.ConsolePanel.prototype.appendObject;
993 var appendFormatted = Firebug.ConsolePanel.prototype.appendFormatted;
994 var appendOpenGroup = Firebug.ConsolePanel.prototype.appendOpenGroup;
995 var appendCloseGroup = Firebug.ConsolePanel.prototype.appendCloseGroup;
996 
997 // ************************************************************************************************
998 
999 Firebug.registerActivableModule(Firebug.Console);
1000 Firebug.registerPanel(Firebug.ConsolePanel);
1001 
1002 // ************************************************************************************************
1003 }});
1004