1 /* See license.txt for terms of usage */
  2 
  3 /**
  4  * UI control of debug Logging for Firebug internals
  5  */
  6 FBL.ns(function() { with (FBL) {
  7 
  8 // ***********************************************************************************
  9 // Shorcuts and Services
 10 
 11 const Cc = Components.classes;
 12 const Ci = Components.interfaces;
 13 
 14 const clipboard = CCSV("@mozilla.org/widget/clipboard;1", "nsIClipboard");
 15 
 16 const PrefService = Cc["@mozilla.org/preferences-service;1"];
 17 const prefs = PrefService.getService(Ci.nsIPrefBranch2);
 18 const prefService = PrefService.getService(Ci.nsIPrefService);
 19 
 20 const reDBG = /extensions\.([^\.]*)\.(DBG_.*)/;
 21 const reDBG_FBS = /DBG_FBS_(.*)/;
 22 
 23 var EOF = "<br/>";
 24 
 25 // Register locale file with strings for the Tracing Console window.
 26 Firebug.registerStringBundle("chrome://firebug/locale/firebug-tracing.properties");
 27 
 28 //************************************************************************************************
 29 //  The controller for the prefDomain Model.
 30 //  getOptionsMenuItems to create View, onPrefChangeHandler for View update
 31 //  base for trace viewers like tracePanel and traceConsole
 32 //  binds  to the branch 'prefDomain' of prefs
 33 
 34 Firebug.TraceOptionsController = function(prefDomain, onPrefChangeHandler)
 35 {
 36     this.prefDomain = prefDomain;
 37 
 38     this.traceService = Cc["@joehewitt.com/firebug-trace-service;1"]
 39         .getService(Ci.nsISupports).wrappedJSObject;
 40 
 41     this.addObserver = function()
 42     {
 43         prefs.setBoolPref("browser.dom.window.dump.enabled", true);
 44         this.observer = { observe: bind(this.observe, this) };
 45         prefs.addObserver(prefDomain, this.observer, false);
 46     };
 47 
 48     this.removeObserver = function()
 49     {
 50         prefs.removeObserver( prefDomain, this.observer, false);
 51     };
 52 
 53     // nsIObserver
 54     this.observe = function(subject, topic, data)
 55     {
 56         if (topic == "nsPref:changed")
 57         {
 58             var m = reDBG.exec(data);
 59             if (m)
 60             {
 61                 var changedPrefDomain = "extensions." + m[1];
 62                 if (changedPrefDomain == prefDomain)
 63                 {
 64                     var optionName = data.substr(prefDomain.length+1); // skip dot
 65                     var optionValue = Firebug.getPref(prefDomain, m[2]);
 66                     if (this.prefEventToUserEvent)
 67                         this.prefEventToUserEvent(optionName, optionValue);
 68                 }
 69             }
 70             else
 71             {
 72                 if (FBTrace.DBG_OPTIONS)
 73                     FBTrace.sysout("traceModule.observe : "+data+"\n");
 74             }
 75         }
 76     };
 77 
 78 //*************** UI ***************************************************************
 79 
 80     this.getOptionsMenuItems = function()  // Firebug menu items from option map
 81     {
 82         var optionMap = this.traceService.getTracer(prefDomain);
 83         var items = [];
 84         for (var p in optionMap)
 85         {
 86             var m = p.indexOf("DBG_");
 87             if (m != 0)
 88                 continue;
 89 
 90             try
 91             {
 92                 var prefValue = Firebug.getPref(this.prefDomain, p);
 93                 var label = p.substr(4);
 94                 items.push({
 95                  label: label,
 96                  nol10n: true,
 97                  type: "checkbox",
 98                  checked: prefValue,
 99                  pref: p,
100                  command: bind(this.userEventToPrefEvent, this)
101                 });
102             }
103             catch (err)
104             {
105                 if (FBTrace.DBG_ERRORS)
106                     FBTrace.sysout("traceModule.getOptionsMenuItems could not create item for "+p+" in prefDomain "+this.prefDomain);
107                 // if the option doesn't exist in this prefDomain, just continue...
108             }
109         }
110 
111         items.sort(function(a, b) {
112             return a.label > b.label;
113         });
114 
115         return items;
116     };
117 
118     this.userEventToPrefEvent = function(event)  // use as an event listener on UI control
119     {
120         var menuitem = event.target.wrappedJSObject;
121         if (!menuitem)
122             menuitem = event.target;
123 
124         var label = menuitem.getAttribute("label");
125         var category = 'DBG_'+label;
126         var value = Firebug.getPref(this.prefDomain, category);
127         var newValue = !value;
128 
129         Firebug.setPref(this.prefDomain, category, newValue);
130         prefService.savePrefFile(null);
131 
132         if (FBTrace.DBG_OPTIONS)
133             FBTrace.sysout("traceConsole.setOption: new value "+ this.prefDomain+"."+category+ " = " + newValue, menuitem);
134     };
135 
136     if (onPrefChangeHandler)
137         this.prefEventToUserEvent = onPrefChangeHandler;
138     else
139     {
140         this.prefEventToUserEvent = function(optionName, optionValue)
141         {
142             FBTrace.sysout("TraceOptionsController owner needs to implement prefEventToUser Event", {name: optionName, value: optionValue});
143         };
144     }
145 
146     this.clearOptions = function()
147     {
148         var optionMap = this.traceService.getTracer(prefDomain);
149         var items = [];
150         for (var p in optionMap)
151         {
152             var m = p.indexOf("DBG_");
153             if (m != 0)
154                 continue;
155 
156             Firebug.setPref(this.prefDomain, p, false);
157         }
158         prefService.savePrefFile(null);
159     };
160 
161 };
162 
163 // ***********************************************************************************
164 // Trace Module
165 
166 Firebug.TraceModule = extend(Firebug.Module,
167 {
168     dispatchName: "traceModule",
169 
170     initialize: function(prefDomain, prefNames)  // prefDomain is the calling app, firebug or chromebug
171     {
172         Firebug.Module.initialize.apply(this, arguments);
173 
174         FBTrace.DBG_OPTIONS = Firebug.getPref(prefDomain, "DBG_OPTIONS");
175 
176         this.prefDomain = prefDomain;
177 
178         // Open console automatically if the pref says so.
179         if (Firebug.getPref(this.prefDomain, "alwaysOpenTraceConsole"))
180             this.openConsole();
181 
182         if (FBTrace.DBG_OPTIONS)
183             FBTrace.sysout("traceModule.initialize: " + prefDomain+" alwayOpen:"+Firebug.getPref(this.prefDomain, "alwaysOpenTraceConsole"));
184     },
185 
186     internationalizeUI: function(doc)
187     {
188         var elements = ["FirebugMenu_Options_alwaysOpenTraceConsole", "menu_openTraceConsole"];
189         for (var i=0; i<elements.length; i++)
190         {
191             var element = doc.getElementById(elements[i]);
192             if (element)
193                 FBL.internationalize(element, "label");
194         }
195     },
196 
197     shutdown: function()
198     {
199         if (this.consoleWindow && this.consoleWindow.TraceConsole)
200             this.consoleWindow.TraceConsole.unregisterModule(this);
201     },
202 
203     reattachContext: function(browser, context)
204     {
205         if (FBTrace.DBG_OPTIONS)
206             FBTrace.sysout("traceModule.reattachContext for: " +
207                 context ? context.getName() : "null context",
208                 [browser, context]);
209     },
210 
211     getTraceConsoleURL: function()
212     {
213         return "chrome://firebug/content/traceConsole.xul";
214     },
215 
216     openConsole: function(prefDomain, windowURL)
217     {
218         if (!prefDomain)
219             prefDomain = this.prefDomain;
220 
221         var self = this;
222         iterateBrowserWindows("FBTraceConsole", function(win) {
223             if (win.TraceConsole.prefDomain == prefDomain) {
224                 self.consoleWindow = win;
225                 return true;
226             }
227         });
228 
229         // Try to connect an existing trace-console window first.
230         if (this.consoleWindow && this.consoleWindow.TraceConsole) {
231             this.consoleWindow.TraceConsole.registerModule(this);
232             this.consoleWindow.focus();
233             return;
234         }
235 
236         if (!windowURL)
237             windowURL = this.getTraceConsoleURL();
238 
239         if (FBTrace.DBG_OPTIONS)
240             FBTrace.sysout("traceModule.openConsole, prefDomain: " + prefDomain);
241 
242         var self = this;
243         var args = {
244             FBL: FBL,
245             Firebug: Firebug,
246             traceModule: self,
247             prefDomain: prefDomain,
248         };
249 
250         if (FBTrace.DBG_OPTIONS) {
251             for (var p in args)
252                 FBTrace.sysout("tracePanel.openConsole prefDomain:" +
253                     prefDomain +" args["+p+"]= "+ args[p]+"\n");
254         }
255 
256         this.consoleWindow = window.openDialog(
257             windowURL,
258             "FBTraceConsole." + prefDomain,
259             "chrome,resizable,scrollbars=auto,minimizable,dialog=no",
260             args);
261     },
262 
263     // Trace console listeners
264     onLoadConsole: function(win, rootNode)
265     {
266         dispatch(this.fbListeners, "onLoadConsole", [win, rootNode]);
267     },
268 
269     onUnloadConsole: function(win)
270     {
271         dispatch(this.fbListeners, "onUnloadConsole", [win]);
272     },
273 
274     onDump: function(message)
275     {
276         dispatch(this.fbListeners, "onDump", [message]);
277     },
278 
279     dump: function(message, outputNodes)
280     {
281         // xxxHonza: find better solution for checking an ERROR messages
282         // (setup some rules).
283         var text = message.text;
284         if (text && (text.indexOf("ERROR") != -1 ||
285             text.indexOf("EXCEPTION") != -1 ||
286             text.indexOf("FAILS") != -1))
287         {
288             message.type = "DBG_ERRORS";
289         }
290 
291         Firebug.TraceModule.MessageTemplate.dump(message, outputNodes);
292     },
293 });
294 
295 Firebug.TraceModule.CommonBaseUI = {
296 
297     destroy: function()
298     {
299         this.optionsController.removeObserver();
300     },
301 
302     initializeContent: function(parentNode, outputNodes, prefDomain, callback)
303     {
304         var doc = parentNode.ownerDocument;
305 
306         // Create basic layout for trace console content.
307         var rep = Firebug.TraceModule.PanelTemplate;
308         rep.tag.replace({}, parentNode, rep);
309 
310         // This IFRAME is the container for all logs.
311         var logTabIframe = parentNode.getElementsByClassName("traceInfoLogsFrame").item(0);
312         var self = this;
313         logTabIframe.addEventListener("load", function(event)
314         {
315             var frameDoc = logTabIframe.contentWindow.document;
316 
317             addStyleSheet(frameDoc, createStyleSheet(frameDoc, "chrome://firebug/skin/panelbase.css"));
318             addStyleSheet(frameDoc, createStyleSheet(frameDoc, "chrome://firebug/skin/traceConsole.css"));
319 
320             var rootNode = frameDoc.getElementById("traceLogContent");
321             outputNodes.setScrollingNode(rootNode);
322 
323             var logNode = Firebug.TraceModule.MessageTemplate.createTable(rootNode);
324 
325             function recalcLayout() {
326                logTabIframe.style.height = (doc.defaultView.innerHeight - 25) + "px";
327             }
328 
329             doc.defaultView.addEventListener("resize", function(event) {
330                recalcLayout();
331             }, true);
332 
333             recalcLayout();
334 
335             callback(logNode);
336         }, true);
337 
338         // Initialize content for Options tab (a button for each DBG_ option).
339         var optionsBody = parentNode.getElementsByClassName("traceInfoOptionsText").item(0);
340         this.optionsController = new Firebug.TraceOptionsController(prefDomain, function updateButton(optionName, optionValue)
341         {
342             var button = parentNode.ownerDocument.getElementById(optionName);
343             if (button)
344                 button.setAttribute("checked", optionValue?"true":"false");
345             else
346                 FBTrace.sysout("traceModule onPrefChange no button with name "+optionName+ " in parentNode", parentNode);
347         });
348 
349         var menuitems = this.optionsController.getOptionsMenuItems();
350         for (var i=0; i<menuitems.length; i++)
351         {
352             var menuitem = menuitems[i];
353             var button = doc.createElement("button");
354             FBL.setClass(button, "traceOption");
355             FBL.setItemIntoElement(button, menuitem);
356             button.innerHTML = menuitem.label;
357             button.setAttribute("id", menuitem.pref);
358             button.removeAttribute("type");
359             button.addEventListener("click", menuitem.command, false);
360             optionsBody.appendChild(button);
361         }
362 
363         // Select default tab.
364         rep.selectTabByName(parentNode, "Logs");
365 
366         this.optionsController.addObserver();
367     },
368 
369 };
370 
371 
372 //************************************************************************************************
373 //Trace Console Rep
374 
375 Firebug.TraceModule.PanelTemplate = domplate({
376 
377     tag:
378         TABLE({"class": "traceTable", cellpadding: 0, cellspacing: 0},
379             TBODY(
380                 TR({"class": "traceInfoRow"},
381                     TD({"class": "traceInfoCol"},
382                         DIV({"class": "traceInfoBody"},
383                             DIV({"class": "traceInfoTabs"},
384                                 A({"class": "traceInfoLogsTab traceInfoTab", onclick: "$onClickTab",
385                                     view: "Logs"},
386                                     $STR("Logs")
387                                 ),
388                                 A({"class": "traceInfoOptionsTab traceInfoTab", onclick: "$onClickTab",
389                                     view: "Options"},
390                                     $STR("Options")
391                                 )
392                             ),
393                             DIV({"class": "traceInfoLogsText traceInfoText"},
394                                 IFRAME({"class": "traceInfoLogsFrame",
395                                     src: "chrome://firebug/content/traceLogFrame.html"}
396                                 )
397                             ),
398                             DIV({"class": "traceInfoOptionsText traceInfoText"})
399                         )
400                     )
401                 )
402             )
403         ),
404 
405     onClickTab: function(event)
406     {
407         this.selectTab(event.currentTarget);
408     },
409 
410     selectTabByName: function(parentNode, tabName)
411     {
412         var tab = parentNode.getElementsByClassName("traceInfo" + tabName + "Tab").item(0);
413         if (tab)
414             this.selectTab(tab);
415     },
416 
417     selectTab: function(tab)
418     {
419         var messageInfoBody = tab.parentNode.parentNode;
420 
421         var view = tab.getAttribute("view");
422         if (messageInfoBody.selectedTab)
423         {
424             messageInfoBody.selectedTab.removeAttribute("selected");
425             messageInfoBody.selectedText.removeAttribute("selected");
426         }
427 
428         var textBodyName = "traceInfo" + view + "Text";
429 
430         messageInfoBody.selectedTab = tab;
431         messageInfoBody.selectedText = getChildByClass(messageInfoBody, textBodyName);
432 
433         messageInfoBody.selectedTab.setAttribute("selected", "true");
434         messageInfoBody.selectedText.setAttribute("selected", "true");
435     }
436 });
437 
438 // ************************************************************************************************
439 // Trace message
440 
441 Firebug.TraceModule.MessageTemplate = domplate(Firebug.Rep,
442 {
443     inspectable: false,
444 
445     tableTag:
446         TABLE({"class": "messageTable", cellpadding: 0, cellspacing: 0},
447             TBODY()
448         ),
449 
450     rowTag:
451         TR({"class": "messageRow $message|getMessageType",
452             _repObject: "$message",
453             $exception: "$message|isException",
454             onclick: "$onClickRow"},
455             TD({"class": "messageNameCol messageCol"},
456                 DIV({"class": "messageNameLabel messageLabel"},
457                     "$message|getMessageIndex")
458             ),
459             TD({"class": "messageTimeCol messageCol"},
460                 DIV({"class": "messageTimeLabel messageLabel"},
461                     "$message|getMessageTime")
462             ),
463             TD({"class": "messageBodyCol messageCol"},
464                 DIV({"class": "messageLabel", title: "$message|getMessageTitle"},
465                     "$message|getMessageLabel")
466             )
467         ),
468 
469     separatorTag:
470         TR({"class": "messageRow separatorRow", _repObject: "$message"},
471             TD({"class": "messageCol", colspan: "3"},
472                 DIV("$message|getMessageIndex")
473             )
474         ),
475 
476     importHeaderTag:
477         TR({"class": "messageRow importHeaderRow", _repObject: "$message"},
478             TD({"class": "messageCol", colspan: "3"},
479                 DIV(B("Firebug: $message.firebug")),
480                 DIV("$message.app.name, $message.app.version, " +
481                     "$message.app.platformVersion, $message.app.buildID, " +
482                     "$message.app.locale"),
483                 DIV("$message.os.name $message.os.version"),
484                 DIV("$message.date"),
485                 DIV("$message.filePath")
486             )
487         ),
488 
489     importFooterTag:
490         TR({"class": "messageRow importFooterRow", _repObject: "$message"},
491             TD({"class": "messageCol", colspan: "3"})
492         ),
493 
494     bodyRow:
495         TR({"class": "messageInfoRow"},
496             TD({"class": "messageInfoCol", colspan: 8})
497         ),
498 
499     bodyTag:
500         DIV({"class": "messageInfoBody", _repObject: "$message"},
501             DIV({"class": "messageInfoTabs"},
502                 A({"class": "messageInfoStackTab messageInfoTab", onclick: "$onClickTab",
503                     view: "Stack"},
504                     $STR("tracing.tab.Stack")
505                 ),
506                 A({"class": "messageInfoExcTab messageInfoTab", onclick: "$onClickTab",
507                     view: "Exc",
508                     $collapsed: "$message|hideException"},
509                     $STR("tracing.tab.Exception")
510                 ),
511                 A({"class": "messageInfoPropsTab messageInfoTab", onclick: "$onClickTab",
512                     view: "Props",
513                     $collapsed: "$message|hideProperties"},
514                     $STR("tracing.tab.Properties")
515                 ),
516                 A({"class": "messageInfoScopeTab messageInfoTab", onclick: "$onClickTab",
517                     view: "Scope",
518                     $collapsed: "$message|hideScope"},
519                     $STR("tracing.tab.Scope")
520                 ),
521                 A({"class": "messageInfoResponseTab messageInfoTab", onclick: "$onClickTab",
522                     view: "Response",
523                     $collapsed: "$message|hideResponse"},
524                     $STR("tracing.tab.Response")
525                 ),
526                 A({"class": "messageInfoSourceTab messageInfoTab", onclick: "$onClickTab",
527                     view: "Source",
528                     $collapsed: "$message|hideSource"},
529                     $STR("tracing.tab.Source")
530                 ),
531                 A({"class": "messageInfoIfacesTab messageInfoTab", onclick: "$onClickTab",
532                     view: "Ifaces",
533                     $collapsed: "$message|hideInterfaces"},
534                     $STR("tracing.tab.Interfaces")
535                 ),
536                 // xxxHonza: this doesn't seem to be much useful.
537                 /*A({"class": "messageInfoTypesTab messageInfoTab", onclick: "$onClickTab",
538                     view: "Types",
539                     $collapsed: "$message|hideTypes"},
540                     "Types"
541                 ),*/
542                 A({"class": "messageInfoObjectTab messageInfoTab", onclick: "$onClickTab",
543                     view: "Types",
544                     $collapsed: "$message|hideObject"},
545                     $STR("tracing.tab.Object")
546                 ),
547                 A({"class": "messageInfoEventTab messageInfoTab", onclick: "$onClickTab",
548                     view: "Event",
549                     $collapsed: "$message|hideEvent"},
550                     $STR("tracing.tab.Event")
551                 )
552             ),
553             DIV({"class": "messageInfoStackText messageInfoText"},
554                 TABLE({"class": "messageInfoStackTable", cellpadding: 0, cellspacing: 0},
555                     TBODY(
556                         FOR("stack", "$message|stackIterator",
557                             TR(
558                                 TD({"class": "stackFrame"},
559                                     A({"class": "stackFrameLink", onclick: "$onClickStackFrame",
560                                         lineNumber: "$stack.lineNumber"},
561                                         "$stack.fileName"),
562                                     SPAN(" "),
563                                     SPAN("(", "$stack.lineNumber", ")"),
564                                     SPAN(" "),
565                                     SPAN({"class": "stackFuncName"},
566                                         "$stack.funcName"),
567                                     A({"class": "openDebugger", onclick: "$onOpenDebugger",
568                                         lineNumber: "$stack.lineNumber",
569                                         fileName: "$stack.fileName"},
570                                         "[...]")
571                                 )
572                             )
573                         )
574                     )
575                 )
576             ),
577             DIV({"class": "messageInfoExcText messageInfoText"}),
578             DIV({"class": "messageInfoPropsText messageInfoText"}),
579             DIV({"class": "messageInfoResponseText messageInfoText"},
580                 IFRAME({"class": "messageInfoResponseFrame"})
581             ),
582             DIV({"class": "messageInfoSourceText messageInfoText"}),
583             DIV({"class": "messageInfoIfacesText messageInfoText"}),
584             DIV({"class": "messageInfoScopeText messageInfoText"}),
585             DIV({"class": "messageInfoTypesText messageInfoText"}),
586             DIV({"class": "messageInfoObjectText messageInfoText"}),
587             DIV({"class": "messageInfoEventText messageInfoText"})
588         ),
589 
590     // Data providers
591     getMessageType: function(message)
592     {
593         return message.getType();
594     },
595 
596     getMessageIndex: function(message)
597     {
598         return message.index + 1;
599     },
600 
601     getMessageTime: function(message)
602     {
603         var date = new Date(message.time);
604         var m = date.getMinutes() + "";
605         var s = date.getSeconds() + "";
606         var ms = date.getMilliseconds() + "";
607         return "[" + ((m.length > 1) ? m : "0" + m) + ":" +
608             ((s.length > 1) ? s : "0" + s) + ":" +
609             ((ms.length > 2) ? ms : ((ms.length > 1) ? "0" + ms : "00" + ms)) + "]";
610     },
611 
612     getMessageLabel: function(message)
613     {
614         var maxLength = Firebug.getPref(Firebug.TraceModule.prefDomain,
615             "trace.maxMessageLength");
616         return message.getLabel(maxLength);
617     },
618 
619     getMessageTitle: function(message)
620     {
621         return message.getLabel(-1);
622     },
623 
624     isException: function(message)
625     {
626         return message.getException();
627     },
628 
629     hideProperties: function(message)
630     {
631         var props = message.getProperties();
632         for (var name in props)
633             return false;
634 
635         return true;
636     },
637 
638     hideScope: function(message)
639     {
640         return !message.getScope();
641     },
642 
643     hideInterfaces: function(message)
644     {
645         var ifaces = message.getInterfaces();
646         for (var name in ifaces)
647             return false;
648 
649         return true;
650     },
651 
652     hideTypes: function(message)
653     {
654         return !message.getTypes();
655     },
656 
657     hideObject: function(message)
658     {
659         return !message.getObject();
660     },
661 
662     hideEvent: function(message)
663     {
664         return !message.getEvent();
665     },
666 
667     hideException: function(message)
668     {
669         return !message.getException();
670     },
671 
672     hideResponse: function(message)
673     {
674         return !(message.obj instanceof Ci.nsIHttpChannel);
675     },
676 
677     hideSource: function(message)
678     {
679         return !(message.obj instanceof Ci.nsIHttpChannel);
680     },
681 
682     // Stack frame support
683     stackIterator: function(message)
684     {
685         return message.getStackArray();
686     },
687 
688     onClickStackFrame: function(event)
689     {
690         var winType = "FBTraceConsole-SourceView";
691         var lineNumber = event.target.getAttribute("lineNumber");
692 
693         openDialog("chrome://global/content/viewSource.xul",
694             winType, "all,dialog=no",
695             event.target.innerHTML, null, null, lineNumber, false);
696     },
697 
698     onOpenDebugger: function(event)
699     {
700         var target = event.target;
701         var lineNumber = target.getAttribute("lineNumber");
702         var fileName = target.getAttribute("fileName");
703 
704         if (typeof(ChromeBugOpener) == "undefined")
705             return;
706 
707         // Open Chromebug window.
708         var cbWindow = ChromeBugOpener.openNow();
709         FBTrace.sysout("Chromebug window has been opened", cbWindow);
710 
711         // xxxHonza: Open Chromebug with the source code file, scrolled automatically
712         // to the specified line number. Currently chrome bug doesn't return the window
713         // from ChromeBugOpener.openNow method. If it would be following code opens
714         // the source code file and scrolls to the given line.
715 
716         // Register onLoad listener and open the source file at the specified line.
717         if (cbWindow) {
718             cbWindow.addEventListener("load", function() {
719                 var context = cbWindow.FirebugContext;
720                 var link = new cbWindow.FBL.SourceLink(fileName, lineNumber, "js");
721                 Firebug.chrome.select(link, "script");
722             }, true);
723         }
724     },
725 
726     // Firebug rep support
727     supportsObject: function(object, type)
728     {
729         return object instanceof Firebug.TraceModule.TraceMessage ||
730             object instanceof Firebug.TraceModule.ImportedMessage;
731     },
732 
733     browseObject: function(message, context)
734     {
735         return false;
736     },
737 
738     getRealObject: function(message, context)
739     {
740         return message;
741     },
742 
743     // Context menu
744     getContextMenuItems: function(message, target, context)
745     {
746         var items = [];
747 
748         if (getAncestorByClass(target, "messageRow"))
749         {
750             items.push({
751               label: $STR("Cut"),
752               nol10n: true,
753               command: bindFixed(this.onCutMessage, this, message)
754             });
755 
756             items.push({
757               label: $STR("Copy"),
758               nol10n: true,
759               command: bindFixed(this.onCopyMessage, this, message)
760             });
761 
762             items.push("-");
763 
764             items.push({
765               label: $STR("Remove"),
766               nol10n: true,
767               command: bindFixed(this.onRemoveMessage, this, message)
768             });
769         }
770 
771         if (getAncestorByClass(target, "messageInfoStackText"))
772         {
773             items.push({
774               label: $STR("Copy Stack"),
775               nol10n: true,
776               command: bindFixed(this.onCopyStack, this, message)
777             });
778         }
779 
780         if (getAncestorByClass(target, "messageInfoExcText"))
781         {
782             items.push({
783               label: $STR("Copy Exception"),
784               nol10n: true,
785               command: bindFixed(this.onCopyException, this, message)
786             });
787         }
788 
789         if (items.length > 0)
790             items.push("-");
791 
792         items.push(this.optionMenu($STR("tracing.Show Time"), "trace.showTime"));
793         items.push(this.optionMenu($STR("tracing.Show Scope Variables"), "trace.enableScope"));
794         items.push("-");
795 
796         items.push({
797           label: $STR("tracing.cmd.Expand All"),
798           nol10n: true,
799           command: bindFixed(this.onExpandAll, this, message)
800         });
801 
802         items.push({
803           label: $STR("tracing.cmd.Collapse All"),
804           nol10n: true,
805           command: bindFixed(this.onCollapseAll, this, message)
806         });
807 
808         return items;
809     },
810 
811     optionMenu: function(label, option)
812     {
813         var checked = Firebug.getPref(Firebug.TraceModule.prefDomain, option);
814         return {label: label, type: "checkbox", checked: checked, nol10n: true,
815             command: bindFixed(Firebug.setPref, Firebug, Firebug.TraceModule.prefDomain,
816                 option, !checked) };
817     },
818 
819     getTooltip: function(message)
820     {
821         return message.text;
822     },
823 
824     // Context menu commands
825     onCutMessage: function(message)
826     {
827         this.onCopyMessage(message);
828         this.onRemoveMessage(message);
829     },
830 
831     onCopyMessage: function(message)
832     {
833         copyToClipboard(message.text);
834     },
835 
836     onRemoveMessage: function(message)
837     {
838         var row = message.row;
839         var parentNode = row.parentNode;
840         this.toggleRow(row, false);
841         parentNode.removeChild(row);
842     },
843 
844     onCopyStack: function(message)
845     {
846         copyToClipboard(message.getStack());
847     },
848 
849     onCopyException: function(message)
850     {
851         copyToClipboard(message.getException());
852     },
853 
854     onExpandAll: function(message)
855     {
856         var table = getAncestorByClass(message.row, "messageTable");
857         var rows = cloneArray(table.firstChild.childNodes);
858         for (var i=0; i<rows.length; i++)
859             this.expandRow(rows[i]);
860     },
861 
862     onCollapseAll: function(message)
863     {
864         var table = getAncestorByClass(message.row, "messageTable");
865         var rows = cloneArray(table.firstChild.childNodes);
866         for (var i=0; i<rows.length; i++)
867             this.collapseRow(rows[i]);
868     },
869 
870     // Clipboard helpers
871     copyToClipboard: function(text)
872     {
873         if (!text)
874             return;
875 
876         // Initialize transfer data.
877         var trans = CCIN("@mozilla.org/widget/transferable;1", "nsITransferable");
878         var wrapper = CCIN("@mozilla.org/supports-string;1", "nsISupportsString");
879         wrapper.data = text;
880         trans.addDataFlavor("text/unicode");
881         trans.setTransferData("text/unicode", wrapper, text.length * 2);
882 
883         // Set the data into the global clipboard
884         clipboard.setData(trans, null, Ci.nsIClipboard.kGlobalClipboard);
885     },
886 
887     // Implementation
888     createTable: function(parentNode)
889     {
890         return HelperDomplate.replace(this.tableTag, {}, parentNode, this);
891     },
892 
893     dump: function(message, outputNodes, index)
894     {
895         var scrollingNode = outputNodes.getScrollingNode();
896         var scrolledToBottom = isScrolledToBottom(scrollingNode);
897 
898         var targetNode = outputNodes.getTargetNode();
899         // Set message index
900         if (index)
901             message.index = index;
902         else
903             message.index = targetNode.childNodes.length;
904 
905         // Insert log into the console.
906         var row = HelperDomplate.insertRows(this.rowTag, {message: message},
907             targetNode, this)[0];
908 
909         message.row = row;
910 
911         // Only if the manifest uses useNativeWrappers=no.
912         // The row in embedded frame, which uses type="content-primary", from some
913         // reason, this conten type changes wrapper around the row, so let's set
914         // directly thte wrappedJSObject here, so row-expand works.
915         if (row.wrappedJSObject)
916             row.wrappedJSObject.repObject = message;
917 
918         if (scrolledToBottom)
919             scrollToBottom(scrollingNode);
920     },
921 
922     dumpSeparator: function(outputNodes, tag, object)
923     {
924         var panelNode = outputNodes.getScrollingNode();
925         var scrolledToBottom = isScrolledToBottom(panelNode);
926 
927         var targetNode = outputNodes.getTargetNode();
928 
929         if (!tag)
930             tag = this.separatorTag;
931 
932         if (!object)
933             object = {type: "separator"};
934 
935         object.index = targetNode.childNodes.length;
936 
937         var row = HelperDomplate.insertRows(tag, {message: object}, targetNode, this)[0];
938 
939         if (scrolledToBottom)
940             scrollToBottom(panelNode);
941 
942         panelNode.scrollTop = panelNode.scrollHeight - panelNode.offsetHeight + 50;
943     },
944 
945     // Body of the message.
946     onClickRow: function(event)
947     {
948         if (isLeftClick(event))
949         {
950             var row = getAncestorByClass(event.target, "messageRow");
951             if (row)
952             {
953                 this.toggleRow(row);
954                 cancelEvent(event);
955             }
956         }
957     },
958 
959     collapseRow: function(row)
960     {
961         if (hasClass(row, "messageRow") && hasClass(row, "opened"))
962             this.toggleRow(row);
963     },
964 
965     expandRow: function(row)
966     {
967         if (hasClass(row, "messageRow"))
968             this.toggleRow(row, true);
969     },
970 
971     toggleRow: function(row, state)
972     {
973         var opened = hasClass(row, "opened");
974         if ((state != null) && (opened == state))
975              return;
976 
977         toggleClass(row, "opened");
978 
979         if (hasClass(row, "opened"))
980         {
981             var message = row.repObject;
982             if (!message && row.wrappedJSObject)
983                 message = row.wrappedJSObject.repObject;
984 
985             var bodyRow = HelperDomplate.insertRows(this.bodyRow, {}, row)[0];
986             var messageInfo = HelperDomplate.replace(this.bodyTag,
987                 {message: message}, bodyRow.firstChild);
988             message.bodyRow = bodyRow;
989 
990             this.selectTabByName(messageInfo, "Stack");
991         }
992         else
993         {
994             row.parentNode.removeChild(row.nextSibling);
995         }
996     },
997 
998     selectTabByName: function(messageInfoBody, tabName)
999     {
1000         var tab = getChildByClass(messageInfoBody, "messageInfoTabs",
1001             "messageInfo" + tabName + "Tab");
1002         if (tab)
1003             this.selectTab(tab);
1004     },
1005 
1006     onClickTab: function(event)
1007     {
1008         this.selectTab(event.currentTarget);
1009     },
1010 
1011     selectTab: function(tab)
1012     {
1013         var messageInfoBody = tab.parentNode.parentNode;
1014 
1015         var view = tab.getAttribute("view");
1016         if (messageInfoBody.selectedTab)
1017         {
1018             messageInfoBody.selectedTab.removeAttribute("selected");
1019             messageInfoBody.selectedText.removeAttribute("selected");
1020         }
1021 
1022         var textBodyName = "messageInfo" + view + "Text";
1023 
1024         messageInfoBody.selectedTab = tab;
1025         messageInfoBody.selectedText = getChildByClass(messageInfoBody, textBodyName);
1026 
1027         messageInfoBody.selectedTab.setAttribute("selected", "true");
1028         messageInfoBody.selectedText.setAttribute("selected", "true");
1029 
1030         var message = Firebug.getRepObject(messageInfoBody);
1031 
1032         // Make sure the original Domplate is *not* tracing for now.
1033         var dumpDOM = FBTrace.DBG_DOMPLATE;
1034         FBTrace.DBG_DOMPLATE = false;
1035         this.updateInfo(messageInfoBody, view, message);
1036         FBTrace.DBG_DOMPLATE = dumpDOM;
1037     },
1038 
1039     updateInfo: function(messageInfoBody, view, message)
1040     {
1041         var tab = messageInfoBody.selectedTab;
1042         if (hasClass(tab, "messageInfoStackTab"))
1043         {
1044             // The content is generated by domplate template.
1045         }
1046         else if (hasClass(tab, "messageInfoPropsTab"))
1047         {
1048             this.updateInfoImpl(messageInfoBody, view, message, message.getProperties,
1049                 function (message, valueBox, text) {
1050                     Firebug.TraceModule.Tree.tag.replace({object: message.props}, valueBox,
1051                         Firebug.TraceModule.Tree);
1052                 });
1053         }
1054         else if (hasClass(tab, "messageInfoScopeTab"))
1055         {
1056             this.updateInfoImpl(messageInfoBody, view, message, message.getScope,
1057                 function (message, valueBox, text) {
1058                     Firebug.TraceModule.PropertyTree.tag.replace({object: message.scope}, valueBox,
1059                         Firebug.TraceModule.PropertyTree);
1060                 });
1061         }
1062         else if (hasClass(tab, "messageInfoIfacesTab"))
1063         {
1064             this.updateInfoImpl(messageInfoBody, view, message, message.getInterfaces,
1065                 function (message, valueBox, text) {
1066                     Firebug.TraceModule.Tree.tag.replace({object: message.ifaces}, valueBox,
1067                         Firebug.TraceModule.Tree);
1068                 });
1069         }
1070         else if (hasClass(tab, "messageInfoTypesTab"))
1071         {
1072             this.updateInfoImpl(messageInfoBody, view, message, message.getTypes);
1073         }
1074         else if (hasClass(tab, "messageInfoEventTab"))
1075         {
1076             this.updateInfoImpl(messageInfoBody, view, message, message.getEvent);
1077         }
1078         else if (hasClass(tab, "messageInfoObjectTab"))
1079         {
1080             this.updateInfoImpl(messageInfoBody, view, message, message.getProperties,
1081                 function (message, valueBox, text) {
1082                     if (message.obj instanceof Element)
1083                         Firebug.HTMLPanel.CompleteElement.tag.replace({object: message.obj}, valueBox,
1084                             Firebug.HTMLPanel.CompleteElement);
1085                     else
1086                         Firebug.TraceModule.PropertyTree.tag.replace({object: message.obj}, valueBox,
1087                             Firebug.TraceModule.PropertyTree);
1088                 });
1089         }
1090         else if (hasClass(tab, "messageInfoExcTab"))
1091         {
1092             this.updateInfoImpl(messageInfoBody, view, message, message.getException);
1093         }
1094         else if (hasClass(tab, "messageInfoResponseTab"))
1095         {
1096             this.updateInfoImpl(messageInfoBody, view, message, message.getResponse,
1097                 function (message, valueBox, text) {
1098                     var iframe = getChildByClass(valueBox, "messageInfoResponseFrame");
1099                     iframe.contentWindow.document.body.innerHTML = text;
1100                 });
1101         }
1102         else if (hasClass(tab, "messageInfoSourceTab"))
1103         {
1104             this.updateInfoImpl(messageInfoBody, view, message, message.getResponse,
1105                 function (message, valueBox, text) {
1106                     if (text)
1107                         insertWrappedText(text, valueBox);
1108                 });
1109         }
1110     },
1111 
1112     updateInfoImpl: function(messageInfoBody, view, message, getter, setter)
1113     {
1114         var valueBox = getChildByClass(messageInfoBody, "messageInfo" + view + "Text");
1115         if (!valueBox.valuePresented)
1116         {
1117             var text = getter.apply(message);
1118             if (typeof(text) != "undefined")
1119             {
1120                 valueBox.valuePresented = true;
1121 
1122                 if (setter)
1123                     setter(message, valueBox, text);
1124                 else
1125                     valueBox.innerHTML = text;
1126             }
1127         }
1128     }
1129 });
1130 
1131 // ************************************************************************************************
1132 // Helper Domplate object that doesn't trace.
1133 
1134 var HelperDomplate = (function()
1135 {
1136     // Private helper function.
1137     function execute()
1138     {
1139         var args = cloneArray(arguments), fn = args.shift(), object = args.shift();
1140 
1141         // Make sure the original Domplate is *not* tracing for now.
1142         if (typeof FBTrace != "undefined") {
1143             var dumpDOM = FBTrace.DBG_DOMPLATE;
1144             FBTrace.DBG_DOMPLATE = false;
1145         }
1146 
1147         var retValue = fn.apply(object, args);
1148 
1149         if (typeof FBTrace != "undefined")
1150             FBTrace.DBG_DOMPLATE = dumpDOM;
1151 
1152         return retValue;
1153     }
1154 
1155     return {
1156         insertRows: function(tag, args, parentNode, self)
1157         {
1158             return execute(tag.insertRows, tag, args, parentNode, self);
1159         },
1160 
1161         replace: function(tag, args, parentNode, self)
1162         {
1163             return execute(tag.replace, tag, args, parentNode, self);
1164         }
1165    }
1166 }());
1167 
1168 // ************************************************************************************************
1169 // Trace Message Object
1170 
1171 Firebug.TraceModule.TraceMessage = function(type, text, obj, scope, time)
1172 {
1173     this.type = type;
1174     this.text = text;
1175     this.obj = obj;
1176     this.stack = [];
1177     this.scope = scope;
1178     this.time = time;
1179 
1180     if (this.obj instanceof Ci.nsIScriptError)
1181     {
1182         var trace = Firebug.errorStackTrace;
1183         if (trace)
1184         {
1185             for (var i=0; i<trace.frames.length; i++)
1186             {
1187                 var frame = trace.frames[i];
1188                 if (frame.href && frame.line)
1189                     this.stack.push({fileName:frame.href, lineNumber:frame.line, funcName:""});
1190             }
1191         }
1192         else
1193         {
1194             // Put info about the script error location into the stack.
1195             this.stack.push({fileName:this.obj.sourceName, lineNumber:this.obj.lineNumber, funcName:""});
1196         }
1197     }
1198     //xxxHonza: the object doesn't have to always be an instance of Error.
1199     else if (this.obj && this.obj.stack && /*(this.obj instanceof Error) &&*/
1200         (typeof this.obj.stack.split == "function"))
1201     {
1202         // If the passed object is an error with stack trace attached, use it.
1203         // This stack trace points directly to the place where the error occurred.
1204         var stack = this.obj.stack.split("\n");
1205         for (var i=0; i<stack.length; i++)
1206         {
1207             var frame = stack[i].split("@");
1208             if (frame.length != 2)
1209                 continue;
1210 
1211             var index = frame[1].lastIndexOf(":");
1212             this.stack.push({
1213                 fileName: frame[1].substr(0, index),
1214                 lineNumber: frame[1].substr(index+1),
1215                 funcName: frame[0]
1216             });
1217         }
1218     }
1219     else
1220     {
1221         // Initialize stack trace info. This must be done now, when the stack
1222         // is available.
1223         for (var frame = Components.stack, i=0; frame; frame = frame.caller, i++)
1224         {
1225             // Skip frames related to the tracing code.
1226             var fileName = unescape(frame.filename ? frame.filename : "");
1227             var traceServiceFile = "firebug@software.joehewitt.com/components/firebug-trace-service.js";
1228             if (i < 6 || fileName.indexOf(traceServiceFile) != -1)
1229                 continue;
1230 
1231             var sourceLine = frame.sourceLine ? frame.sourceLine : "";
1232             var lineNumber = frame.lineNumber ? frame.lineNumber : "";
1233             this.stack.push({fileName:fileName, lineNumber:lineNumber, funcName:""});
1234         }
1235     }
1236 
1237     if (this.obj instanceof Ci.nsICachingChannel)
1238     {
1239         try
1240         {
1241             var cacheToken = this.obj.cacheToken;
1242             if (cacheToken instanceof Ci.nsICacheEntryDescriptor)
1243             {
1244                 this.cacheClient = cacheToken.clientID;
1245                 this.cacheKey = cacheToken.key;
1246             }
1247         }
1248         catch (e)
1249         {
1250         }
1251     }
1252 
1253     if (this.obj instanceof Error ||
1254         this.obj instanceof Ci.nsIException ||
1255         this.obj instanceof Ci.nsIScriptError)
1256     {
1257         // Put the error message into the title so, it's immediately visible.
1258         this.text += " " + this.obj.message;
1259     }
1260 
1261     // Get snapshot of all properties now, as they can be changed.
1262     this.getProperties();
1263 
1264     // Get current scope
1265     this.getScope();
1266 }
1267 
1268 // ************************************************************************************************
1269 
1270 Firebug.TraceModule.TraceMessage.prototype =
1271 {
1272     getType: function()
1273     {
1274         return this.type;
1275     },
1276 
1277     getLabel: function(maxLength)
1278     {
1279         if (!this.text)
1280             return "";
1281 
1282         if (maxLength <= 10 || this.text.length <= maxLength)
1283             return this.text.replace(/[\n]/g,"");
1284 
1285         return this.text.substr(0, maxLength - 3) + "...";
1286     },
1287 
1288     getStackArray: function()
1289     {
1290         return this.stack;
1291     },
1292 
1293     getStack: function()
1294     {
1295         var result = "";
1296         for (var i=0; i<this.stack.length; i++) {
1297             var frame = this.stack[i];
1298             result += frame.fileName + " (" + frame.lineNumber + ")\n";
1299         }
1300 
1301         return result;
1302     },
1303 
1304     getProperties: function()
1305     {
1306         if (this.props)
1307             return this.props;
1308 
1309         this.props = [];
1310 
1311         if (this.obj instanceof Array)
1312         {
1313             if (this.obj.length)
1314             {
1315                 for (var p=0; p<this.obj.length; p++)
1316                 {
1317                     try
1318                     {
1319                         var getter = this.obj.__lookupGetter__(p);
1320                         if (getter)
1321                             this.props[p] = "" + getter;
1322                         else
1323                             this.props[p] = "" + this.obj[p];
1324                     }
1325                     catch (e)
1326                     {
1327                         onPanic("instanceof Array with length, item "+p, e);
1328                     }
1329                 }
1330             }
1331             else
1332             {
1333                 for (var p in this.obj)
1334                 {
1335                     try
1336                     {
1337                         var subProps = this.props[p] = [];
1338                         var subobj = this.obj.__lookupGetter__(p);
1339                         if (!subobj)
1340                             subobj = this.obj[p];
1341                         for (var p1 in subobj)
1342                         {
1343                             var getter = subobj.lookupGetter__(p1);
1344                             if (getter)
1345                                 subProps[p1] = "" + getter;
1346                             else
1347                                 subProps[p1] = "" + subobj[p1];
1348                         }
1349                     }
1350                     catch (e)
1351                     {
1352                         onPanic("instanceof Array, item "+p, e);
1353                     }
1354                 }
1355             }
1356         }
1357         else if (typeof(this.obj) == "string")
1358         {
1359             this.props = this.obj;
1360         }
1361         else if (this.obj instanceof Ci.jsdIValue)
1362         {
1363             var listValue = {value: null}, lengthValue = {value: 0};
1364             this.obj.getProperties(listValue, lengthValue);
1365             for (var i = 0; i < lengthValue.value; ++i)
1366             {
1367                 var prop = listValue.value[i];
1368                 try {
1369                     var name = unwrapIValue(prop.name);
1370                     this.props[name] = "" + unwrapIValue(prop.value);
1371                 } catch (e) {
1372                     onPanic("instanceof jsdIValue, i="+i, e);
1373                 }
1374             }
1375         }
1376         else if (this.obj instanceof Ci.nsISupportsCString)
1377         {
1378             this.props = this.obj.data;
1379         }
1380         else
1381         {
1382             try
1383             {
1384                 this.props = {};
1385                 var propsTotal = 0;
1386                 for (var p in this.obj)
1387                 {
1388                     propsTotal++;
1389 
1390                     try {
1391                         if (this.obj.__lookupGetter__)
1392                             var getter = this.obj.__lookupGetter__(p);
1393                         if (getter)
1394                             var value = "" + getter;
1395                         else
1396                             var value = safeToString(this.obj[p]);
1397                         this.props[p] = value;
1398                     }
1399                     catch (err) {
1400                         window.dump(">>>>>>>>>>>>>>>> traceModule.getProperties FAILS with "+err+"\n");
1401                         window.dump(">>>>>>>>>>>>>>>> traceModule.getProperties FAILS on object "+safeToString(this.obj)+"\n");
1402                         this.props[p] = "{Error}";
1403                     }
1404                 }
1405             }
1406             catch (exc)
1407             {
1408                 window.dump(">>>>>>>>>>>>>>>> traceModule.getProperties enumeration FAILS after "+propsTotal+ " with "+exc+"\n");
1409                 window.dump(">>>>>>>>>>>>>>>> traceModule.getProperties enumeration FAILS on object "+safeToString(this.obj)+"\n");
1410                 if (this.obj instanceof Window)
1411                     window.dump(">>>>>>>>>>>>>>>> traceModule.getProperties enumeration FAILS window closed:"+this.obj.closed+"\n");
1412             }
1413         }
1414 
1415         return this.props;
1416     },
1417 
1418     getInterfaces: function()
1419     {
1420         if (this.ifaces)
1421             return this.ifaces;
1422 
1423         this.ifaces = [];
1424         for (var iface in Ci) {
1425             if (this.obj instanceof Ci[iface]) {
1426                 var ifaceProps = this.ifaces[iface] = [];
1427                 for (p in Ci[iface])
1428                     ifaceProps[p] = this.obj[p];
1429             }
1430         }
1431         return this.ifaces;
1432     },
1433 
1434    getScope: function()
1435    {
1436        if (!Firebug.getPref(Firebug.prefDomain, "trace.enableScope"))
1437            return null;
1438 
1439        if (this.scope)
1440            return this.scope;
1441 
1442        var scope = {};
1443        Firebug.Debugger.halt(function(frame)
1444        {
1445            for (var i=0; i<4 && frame; i++)
1446                frame = frame.callingFrame;
1447 
1448            if (frame)
1449            {
1450                var listValue = {value: null}, lengthValue = {value: 0};
1451                frame.scope.getProperties(listValue, lengthValue);
1452 
1453                for (var i=lengthValue.value-1; i>=0; i--)
1454                {
1455                    var prop = listValue.value[i];
1456                    var name = unwrapIValue(prop.name);
1457                    var value = unwrapIValue(prop.value);
1458 
1459                    if ((typeof(value) != "function") && name && value)
1460                        scope[name.toString()] = value.toString();
1461                }
1462            }
1463        });
1464 
1465        return this.scope = scope;
1466    },
1467 
1468     getResponse: function()
1469     {
1470         var result = null;
1471         try
1472         {
1473             var self = this;
1474             TabWatcher.iterateContexts(function(context) {
1475                 var url = self.obj.originalURI.spec;
1476                 return context.sourceCache.loadText(url);
1477             });
1478         }
1479         catch (err)
1480         {
1481         }
1482 
1483         return result;
1484     },
1485 
1486     getException: function()
1487     {
1488         if (this.err)
1489             return this.err;
1490 
1491         this.err = "";
1492 
1493         if (this.obj && this.obj.message)
1494             return this.obj.message;
1495 
1496         // xxxJJB: this isn't needed, instanceof does QI. try {this.obj = this.obj.QueryInterface(Ci.nsIException);} catch (err){}
1497         if (!this.obj)
1498             return null;
1499 
1500         if (this.obj instanceof Error || this.obj instanceof Ci.nsIException)
1501         {
1502             try
1503             {
1504                 this.err += "<span class='ExceptionMessage'>" + this.obj.message + "</span>" + EOF;
1505                 this.err += this.obj.name + EOF;
1506                 this.err += this.obj.fileName + "(" + this.obj.lineNumber+ ")" + EOF;
1507             }
1508             catch (err)
1509             {
1510                 onPanic("instanceof Error or nsIExcpetion", e);
1511             }
1512         }
1513 
1514         return this.err;
1515     },
1516 
1517     getTypes: function()
1518     {
1519         if (this.types)
1520             return this.types;
1521 
1522         this.types = "";
1523 
1524         try {
1525             var obj = this.obj;
1526             while (obj)
1527             {
1528                 this.types += "typeof = " + typeof(obj) + EOF;
1529                 if (obj)
1530                     this.types += "    constructor = " + obj.constructor + EOF;
1531 
1532                 obj = obj.prototype;
1533             }
1534         }
1535         catch (e)
1536         {
1537             onPanic("getTypes "+this.types, e);
1538         }
1539 
1540         return this.types;
1541     },
1542 
1543     getEvent: function()
1544     {
1545         if (!(this.obj instanceof Event))
1546             return;
1547 
1548         if (this.eventInfo)
1549             return this.eventInfo;
1550 
1551         this.eventInfo = "";
1552 
1553         try
1554         {
1555             if (this.obj.eventPhase == this.obj.AT_TARGET)
1556                 this.eventInfo += " at target ";
1557             else if (this.obj.eventPhase == this.obj.BUBBLING_PHASE)
1558                 this.eventInfo += " bubbling phase ";
1559             else
1560                 this.eventInfo += " capturing phase ";
1561 
1562             if (this.obj.relatedTarget)
1563                 this.eventInfo += this.obj.relatedTarget.tagName + "->";
1564 
1565             if (this.obj.currentTarget)
1566             {
1567                 if (this.obj.currentTarget.tagName)
1568                     this.eventInfo += this.obj.currentTarget.tagName + "->";
1569                 else
1570                     this.eventInfo += this.obj.currentTarget.nodeName + "->";
1571             }
1572 
1573             this.eventInfo += this.obj.target.tagName;
1574         }
1575         catch (err)
1576         {
1577             onPanic("event", err);
1578         }
1579 
1580         return this.eventInfo;
1581     },
1582 
1583     getObject: function()
1584     {
1585         return this.obj;
1586     }
1587 }
1588 
1589 // ************************************************************************************************
1590 // Imported message
1591 
1592 Firebug.TraceModule.ImportedMessage = function(logMsg)
1593 {
1594     this.type = logMsg.type;
1595     this.text = logMsg.text;
1596     this.obj = null;
1597     this.stack = logMsg.stack;
1598     this.scope = null;
1599     this.time = logMsg.time;
1600 }
1601 
1602 Firebug.TraceModule.ImportedMessage.prototype = extend(Firebug.TraceModule.TraceMessage.prototype,
1603 {
1604     getStackArray: function()
1605     {
1606         return cloneArray(this.stack);
1607     },
1608 })
1609 
1610 // ************************************************************************************************
1611 
1612 var lastPanic = null;
1613 function onPanic(contextMessage, errorMessage)
1614 {
1615     var appShellService = Components.classes["@mozilla.org/appshell/appShellService;1"].getService(Components.interfaces.nsIAppShellService);
1616     var win = appShellService.hiddenDOMWindow;
1617     // XXXjjb I cannot get these tests to work.
1618     //if (win.lastPanic && (win.lastPanic == errorMessage))
1619         win.dump("traceModule: "+contextMessage +" panic attack "+errorMessage+"\n");
1620     //else
1621     //alert("Firebug traceModule panics: "+errorMessage);
1622 
1623     win.lastPanic = errorMessage;
1624 }
1625 
1626 // ************************************************************************************************
1627 // Domplate helpers - Tree (domplate widget)
1628 
1629 /**
1630  * This object is intended as a domplate widget for displaying hierarchical
1631  * structure (tree). Specific tree should be derived from this object and
1632  * getMembers method should be implemented.
1633  */
1634 Firebug.TraceModule.Tree = domplate(Firebug.Rep,
1635 {
1636     tag:
1637         TABLE({"class": "domTable", cellpadding: 0, cellspacing: 0, onclick: "$onClick"},
1638             TBODY(
1639                 FOR("member", "$object|memberIterator",
1640                     TAG("$member|getRowTag", {member: "$member"}))
1641             )
1642         ),
1643 
1644     rowTag:
1645         TR({"class": "memberRow $member.open $member.type\\Row", $hasChildren: "$member.hasChildren",
1646             _repObject: "$member", level: "$member.level"},
1647             TD({"class": "memberLabelCell",
1648                 style: "padding-left: $member.indent\\px; width:1%; white-space: nowrap"},
1649                 DIV({"class": "memberLabel $member.type\\Label"}, "$member.name")
1650             ),
1651             TD({"class": "memberValueCell", style: "width: 100%;"},
1652                 TAG("$member.tag", {object: "$member.value"})
1653             )
1654         ),
1655 
1656     loop:
1657         FOR("member", "$members",
1658             TAG("$member|getRowTag", {member: "$member"})),
1659 
1660     memberIterator: function(object)
1661     {
1662         return this.getMembers(object);
1663     },
1664 
1665     getRowTag: function(member)
1666     {
1667         return this.rowTag;
1668     },
1669 
1670     onClick: function(event)
1671     {
1672         if (!isLeftClick(event))
1673             return;
1674 
1675         var row = getAncestorByClass(event.target, "memberRow");
1676         var label = getAncestorByClass(event.target, "memberLabel");
1677         if (label && hasClass(row, "hasChildren"))
1678             this.toggleRow(row);
1679     },
1680 
1681     toggleRow: function(row)
1682     {
1683         var level = parseInt(row.getAttribute("level"));
1684 
1685         if (hasClass(row, "opened"))
1686         {
1687             removeClass(row, "opened");
1688 
1689             var tbody = row.parentNode;
1690             for (var firstRow = row.nextSibling; firstRow; firstRow = row.nextSibling) {
1691                 if (parseInt(firstRow.getAttribute("level")) <= level)
1692                     break;
1693 
1694                 tbody.removeChild(firstRow);
1695             }
1696         }
1697         else
1698         {
1699             setClass(row, "opened");
1700 
1701             var repObject = row.repObject;
1702             if (repObject) {
1703                 var members = this.getMembers(repObject.value, level+1);
1704                 if (members)
1705                     this.loop.insertRows({members: members}, row);
1706             }
1707         }
1708     },
1709 
1710     getMembers: function(object, level)
1711     {
1712         if (!level)
1713             level = 0;
1714 
1715         if (typeof(object) == "string")
1716             return [this.createMember("", "", object, level)];
1717 
1718         var members = [];
1719         for (var p in object) {
1720             var member = this.createMember("", p, object[p], level);
1721             if (object[p] instanceof Array)
1722                 member.tag = FirebugReps.Nada.tag;
1723             members.push(member);
1724         }
1725         return members;
1726     },
1727 
1728     createMember: function(type, name, value, level)
1729     {
1730         var rep = Firebug.getRep(value);
1731         var tag = rep.shortTag ? rep.shortTag : rep.tag;
1732         var valueType = typeof(value);
1733 
1734         var hasChildren = hasProperties(value) && !(value instanceof ErrorCopy) &&
1735             (valueType == "function" || (valueType == "object" && value != null)
1736             || (valueType == "string" && value.length > Firebug.stringCropLength));
1737 
1738         return {
1739             name: name,
1740             value: value,
1741             type: type,
1742             rowClass: "memberRow-" + type,
1743             open: "",
1744             level: level,
1745             indent: level*16,
1746             hasChildren: hasChildren,
1747             tag: tag
1748         };
1749     }
1750 });
1751 
1752 // ************************************************************************************************
1753 
1754 Firebug.TraceModule.PropertyTree = domplate(Firebug.TraceModule.Tree,
1755 {
1756     getMembers: function(object, level)
1757     {
1758         if (!level)
1759             level = 0;
1760 
1761         try
1762         {
1763             var members = [];
1764             for (var p in object)
1765             {
1766                 try
1767                 {
1768                     members.push(this.createMember("dom", p, object[p], level));
1769                 }
1770                 catch (e)
1771                 {
1772                 }
1773             }
1774         }
1775         catch (err)
1776         {
1777             FBTrace.sysout("Exception", err);
1778         }
1779 
1780         return members;
1781     }
1782 });
1783 
1784 // ************************************************************************************************
1785 // Registration
1786 
1787 Firebug.registerModule(Firebug.TraceModule);
1788 Firebug.registerRep(Firebug.TraceModule.MessageTemplate);
1789 
1790 // ************************************************************************************************
1791 
1792 }});
1793