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 
 11 const nsIPrefBranch = Ci.nsIPrefBranch;
 12 const nsIPrefBranch2 = Ci.nsIPrefBranch2;
 13 const nsISupports = Ci.nsISupports;
 14 const nsIFile = Ci.nsIFile;
 15 const nsILocalFile = Ci.nsILocalFile;
 16 const nsISafeOutputStream = Ci.nsISafeOutputStream;
 17 const nsIURI = Ci.nsIURI;
 18 
 19 const PrefService = Cc["@mozilla.org/preferences-service;1"];
 20 const DirService =  CCSV("@mozilla.org/file/directory_service;1", "nsIDirectoryServiceProvider");
 21 
 22 const nsIPrefService = Ci.nsIPrefService;
 23 const prefService = PrefService.getService(nsIPrefService);
 24 
 25 const observerService = CCSV("@mozilla.org/observer-service;1", "nsIObserverService");
 26 const categoryManager = CCSV("@mozilla.org/categorymanager;1", "nsICategoryManager");
 27 const stringBundleService = CCSV("@mozilla.org/intl/stringbundle;1", "nsIStringBundleService");
 28 const promptService = CCSV("@mozilla.org/embedcomp/prompt-service;1", "nsIPromptService");
 29 
 30 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 31 // There is one Firebug object per browser.xul
 32 
 33 const contentBox = $("fbContentBox");
 34 const contentSplitter = $("fbContentSplitter");
 35 const toggleCommand = $("cmd_toggleFirebug");
 36 const detachCommand = $("cmd_toggleDetachFirebug");
 37 const tabBrowser = $("content");
 38 const versionURL = "chrome://firebug/content/branch.properties";
 39 const statusBarContextMenu = $("fbStatusContextMenu");
 40 
 41 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 42 
 43 const prefs = PrefService.getService(nsIPrefBranch2);
 44 const NS_OS_TEMP_DIR = "TmpD"
 45 
 46 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 47 
 48 const firebugURLs =
 49 {
 50     main: "http://www.getfirebug.com",
 51     docs: "http://www.getfirebug.com/docs.html",
 52     keyboard: "http://www.getfirebug.com/keyboard.html",
 53     discuss: "http://groups.google.com/group/firebug",
 54     issues: "http://code.google.com/p/fbug/issues/list",
 55     donate: "http://www.getfirebug.com/contribute.html?product"
 56 };
 57 
 58 const prefNames =  // XXXjjb TODO distribute to modules
 59 [
 60     // Global
 61     "defaultPanelName", "throttleMessages", "textSize", "showInfoTips",
 62     "largeCommandLine", "textWrapWidth", "openInWindow", "showErrorCount",
 63     "activateSameOrigin", "allPagesActivation", "hiddenPanels",
 64     "panelTabMinWidth", "sourceLinkLabelWidth",
 65 
 66     // Search
 67     "searchCaseSensitive", "searchGlobal", "netSearchHeaders", "netSearchParameters",
 68     "netSearchResponseBody",
 69 
 70     // Console
 71     "showJSErrors", "showJSWarnings", "showCSSErrors", "showXMLErrors",
 72     "showChromeErrors", "showChromeMessages", "showExternalErrors",
 73     "showXMLHttpRequests", "showNetworkErrors",
 74 
 75     "persistBreakOnError",
 76 
 77     // HTML
 78     "showFullTextNodes", "showCommentNodes",
 79     "showTextNodesWithWhitespace", "showTextNodesWithEntities",
 80     "highlightMutations", "expandMutations", "scrollToMutations", "shadeBoxModel",
 81     "showQuickInfoBox",
 82 
 83     // CSS
 84     "showUserAgentCSS",
 85     "expandShorthandProps",
 86 
 87     // Script
 88     "decompileEvals", "replaceTabs",
 89 
 90     // DOM
 91     "showUserProps", "showUserFuncs", "showDOMProps", "showDOMFuncs", "showDOMConstants",
 92 
 93     // Layout
 94     "showRulers",
 95 
 96     // Net
 97     "netFilterCategory", "collectHttpHeaders", "netDisplayedResponseLimit",
 98     "netDisplayedPostBodyLimit", "netPhaseInterval", "sizePrecision",
 99     "netParamNameLimit",
100 
101     // Stack
102     "omitObjectPathStack",
103 
104     // Debugging
105     "clearDomplate"
106 ];
107 
108 const servicePrefNames = [
109     "showStackTrace", // Console
110     "filterSystemURLs", // Stack
111     "showAllSourceFiles", "breakOnErrors",  "trackThrowCatch" // Script
112 ];
113 
114 const scriptBlockSize = 20;
115 
116 const PLACEMENT_NONE = 0;
117 const PLACEMENT_INBROWSER = 1;
118 const PLACEMENT_DETACHED = 2;
119 const PLACEMENT_MINIMIZED = 3;
120 
121 // ************************************************************************************************
122 // Globals
123 
124 var modules = [];
125 var activeContexts = [];
126 var activableModules = [];
127 var extensions = [];
128 var panelTypes = [];
129 var reps = [];
130 var defaultRep = null;
131 var defaultFuncRep = null;
132 var editors = [];
133 var externalEditors = [];
134 
135 var panelTypeMap = {};
136 var optionUpdateMap = {};
137 
138 var deadWindows = [];
139 var deadWindowTimeout = 0;
140 var clearContextTimeout = 0;
141 var temporaryFiles = [];
142 var temporaryDirectory = null;
143 
144 // Register default Firebug string bundle (yet before domplate templates).
145 categoryManager.addCategoryEntry("strings_firebug",
146     "chrome://firebug/locale/firebug.properties", "", true, true);
147 
148 // ************************************************************************************************
149 
150 /**
151  * @class Represents the main Firebug application object. An instance of this object is
152  * created for each browser window (browser.xul).
153  */
154 top.Firebug =
155 {
156     version: "1.5",
157 
158     dispatchName: "Firebug",
159     module: modules,
160     panelTypes: panelTypes,
161     uiListeners: [],
162     reps: reps,
163     prefDomain: "extensions.firebug",
164     servicePrefDomain: "extensions.firebug.service",
165 
166     stringCropLength: 50,
167 
168     tabBrowser: tabBrowser,
169     originalChrome: FirebugChrome,
170     chrome: FirebugChrome,
171 
172     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
173     // Initialization
174 
175     initialize: function()
176     {
177         var version = this.getVersion();
178         if (version)
179         {
180             this.version = version;
181             $('fbStatusBar').setAttribute("tooltiptext", "Firebug " + version);
182 
183             var about = $('Firebug_About');
184             if (about)
185             {
186                 var aboutLabel = about.getAttribute("label");
187                 $('Firebug_About').setAttribute("label",  aboutLabel + " " + version);
188             }
189         }
190 
191         for (var i = 0; i < prefNames.length; ++i)
192             this[prefNames[i]] = this.getPref(this.prefDomain, prefNames[i]);
193         for (var i = 0; i < servicePrefNames.length; ++i)
194             this[servicePrefNames[i]] = this.getPref(this.servicePrefDomain, servicePrefNames[i]);
195 
196         this.loadExternalEditors();
197 
198         prefs.addObserver(this.prefDomain, this, false);
199         prefs.addObserver(this.servicePrefDomain, this, false);
200 
201         var basePrefNames = prefNames.length;
202 
203         this.clientID = Firebug.Debugger.registerClient(this);
204 
205         dispatch(modules, "initialize", [this.prefDomain, prefNames]);
206 
207         for (var i = basePrefNames; i < prefNames.length; ++i)
208             this[prefNames[i]] = this.getPref(this.prefDomain, prefNames[i]);
209 
210         if (FBTrace.DBG_OPTIONS)
211         {
212              for (var i = 0; i < prefNames.length; ++i)
213                 FBTrace.sysout("firebug.initialize option "+this.prefDomain+"."+prefNames[i]+"="+this[prefNames[i]]+"\n");
214         }
215         if (FBTrace.DBG_INITIALIZE)
216             FBTrace.sysout("firebug.initialize client: "+this.clientID+" with prefDomain "+this.prefDomain);
217 
218         // In the case that the user opens firebug in a new window but then closes Firefox window, we don't get the
219         // quitApplicationGranted event (platform is still running) and we call shutdown (Firebug isDetached).
220         window.addEventListener('unload', shutdownFirebug, false);
221     },
222 
223     getVersion: function()
224     {
225         if (!this.fullVersion)
226             this.fullVersion = this.loadVersion(versionURL);
227 
228         return this.fullVersion;
229     },
230 
231     loadVersion: function(versionURL)
232     {
233         var content = getResource(versionURL);
234         if (!content)
235             return "no content at "+versionURL;
236 
237         var m = /RELEASE=(.*)/.exec(content);
238         if (m)
239             var release = m[1];
240         else
241             return "no RELEASE in "+versionURL;
242 
243         m = /VERSION=(.*)/.exec(content);
244         if (m)
245             var version = m[1];
246         else
247             return "no VERSION in "+versionURL;
248 
249         return version+""+release;
250     },
251 
252     internationalizeUI: function(doc)  // Substitute strings in the UI with fall back to en-US
253     {
254         if (!doc)
255             return;
256 
257         if (FBTrace.DBG_INITIALIZE)
258             FBTrace.sysout("Firebug.internationalizeUI");
259 
260         var elements = ["menu_clearConsole", "menu_resetAllOptions",
261             "menu_enablePanels", "menu_disablePanels",
262             "fbCommandLine", "fbFirebugMenu", "fbLargeCommandLine", "menu_customizeShortcuts",
263             "menu_enableA11y", "menu_activateSameOrigin", "menu_onByDefault", "fbContinueButton",
264             "fbBreakOnNextButton", "fbConsolePersist",
265             "fbMinimizeButton", "FirebugMenu_Sites", "fbResumeBoxButton",
266             "menu_AllOn", "menu_clearActivationList", "showQuickInfoBox"];
267 
268         for (var i=0; i<elements.length; i++)
269         {
270             var element = doc.getElementById(elements[i]);
271             if (!element)
272             {
273                 if (FBTrace.DBG_LOCALE)
274                     FBTrace.sysout("firebug.internationalizeUI; Element Not Found: " + elements[i]);
275                 continue;
276             }
277 
278             if (element.hasAttribute("label"))
279                 FBL.internationalize(element, "label");
280 
281             if (element.hasAttribute("tooltiptext"))
282                 FBL.internationalize(element, "tooltiptext");
283         }
284 
285         // Allow other modules to internationalize UI labels (called also for
286         // detached Firebug window).
287         dispatch(modules, "internationalizeUI", [doc]);
288     },
289 
290     broadcast: function(message, args)
291     {
292         // dispatch message to all XUL windows registered to firebug service.
293         // Implemented in Firebug.Debugger.
294     },
295 
296     /**
297      * Called when the UI is ready to be initialized, once the panel browsers are loaded,
298      * but before any contexts are created.
299      */
300     initializeUI: function(detachArgs)
301     {
302         if (FBTrace.DBG_INITIALIZE)
303             FBTrace.sysout("firebug.initializeUI this.disabledAlways="+this.disabledAlways+
304                     " detachArgs:", detachArgs);
305 
306         TabWatcher.initialize(this);
307         TabWatcher.addListener(this);
308 
309         // Initialize all modules.
310         dispatch(modules, "initializeUI", [detachArgs]);
311     },
312 
313 
314     shutdown: function()  // called in browser when Firefox closes and in externalMode when fbs gets quitApplicationGranted.
315     {
316         window.removeEventListener('unload', shutdownFirebug, false);
317 
318         Firebug.Debugger.unregisterClient(this);
319 
320         TabWatcher.destroy();
321 
322         // Remove the listener after the TabWatcher.destroy() method is called so,
323         // destroyContext event is properly dispatched to the Firebug object and
324         // consequently to all registered modules.
325         TabWatcher.removeListener(this);
326 
327         dispatch(modules, "disable", [FirebugChrome]);
328 
329         prefService.savePrefFile(null);
330         prefs.removeObserver(this.prefDomain, this, false);
331         prefs.removeObserver(this.servicePrefDomain, this, false);
332 
333         dispatch(modules, "shutdown");
334 
335         this.closeDeadWindows();
336         this.deleteTemporaryFiles();
337 
338         if (FBTrace.DBG_INITIALIZE)
339             FBTrace.sysout("firebug.shutdown exited client "+this.clientID);
340     },
341 
342     // ----------------------------------------------------------------------------------------------------------------
343 
344     getSuspended: function()
345     {
346         var suspendMarker = $("fbStatusIcon");
347         if (suspendMarker.hasAttribute("suspended"))
348             return suspendMarker.getAttribute("suspended");
349         return null;
350     },
351 
352     setSuspended: function(value)
353     {
354         var suspendMarker = $("fbStatusIcon");
355         if (FBTrace.DBG_ACTIVATION)
356             FBTrace.sysout("Firebug.setSuspended to "+value+". Browser: " +
357                 Firebug.chrome.window.document.title);
358 
359         if (value)
360             suspendMarker.setAttribute("suspended", value);
361         else
362             suspendMarker.removeAttribute("suspended");
363 
364         Firebug.resetTooltip();
365     },
366 
367     toggleSuspend: function()
368     {
369         // getSuspended returns non-null value if Firebug is suspended.
370         if (this.getSuspended())
371         {
372             // Firebug is suspended now. Two possible actions have been executed:
373             // 1) Firebug UI is closed and the user clicked on the status bar icon in order to
374             //    show the UI and resume Firebug.
375             // 2) Firebug is detached, but suspended for the current page. The user clicked
376             //    either on the status bar icon or on an activation button that is displayed
377             //    within detached Firebug window.
378             this.toggleBar(true);
379         }
380         else
381         {
382             // The users wants to suspend Firebug, let's do it and pull down the visible UI.
383             // xxxHonza: the Firebug isn't suspended if detached and the user clicks on the
384             // status bar icon (the detached window should becoma blank displaying only
385             // the activation button).
386             this.suspend();
387 
388             // Close detached Firebug or
389             // show/hide Firebug UI according to the browser.showFirebug flag.
390             if (Firebug.isDetached())
391                 this.toggleDetachBar(false);
392             else
393                 this.syncBar();
394         }
395     },
396 
397     disablePanels: function(context)
398     {
399         Firebug.ModuleManager.disableModules();
400     },
401 
402     suspend: function()  // dispatch suspendFirebug to all windows
403     {
404         this.broadcast('suspendFirebug', []);
405     },
406 
407     suspendFirebug: function() // dispatch onSuspendFirebug to all modules
408     {
409         this.setSuspended("suspending");
410 
411         var cancelSuspend = dispatch2(activableModules, 'onSuspendFirebug', [FirebugContext]);  // TODO no context arg
412 
413         if (cancelSuspend)
414             Firebug.resume();
415         else
416             this.setSuspended("suspended");
417     },
418 
419     resume: function()
420     {
421         this.broadcast('resumeFirebug', []);
422     },
423 
424     resumeFirebug: function()  // dispatch onResumeFirebug to all modules
425     {
426         this.setSuspended("resuming");
427         dispatch(activableModules, 'onResumeFirebug', [FirebugContext]);// TODO no context arg
428         this.setSuspended(null);
429     },
430 
431     getEnablementStatus: function()
432     {
433         var strOn = $STR("enablement.on");
434         var strOff = $STR("enablement.off");
435 
436         var status = "";
437         var fbStatusIcon = $('fbStatusIcon');
438         if (fbStatusIcon.getAttribute("console") == "on")
439             status +="Console: "+strOn+",";
440         else
441             status +="Console: "+strOff+",";
442 
443         if (fbStatusIcon.getAttribute("net") == "on")
444             status +=" Net: "+strOn+",";
445         else
446             status +=" Net: "+strOff+",";
447 
448         if (fbStatusIcon.getAttribute("script") == "on")
449             status +=" Script: "+strOn;
450         else
451             status +=" Script: "+strOff+"";
452 
453         return status;
454     },
455 
456     resetTooltip: function()
457     {
458         if (FBTrace.DBG_TOOLTIP)
459           FBTrace.sysout("resetTooltip called");
460 
461         var tooltip = "Firebug " + Firebug.getVersion();
462 
463         tooltip += "\n" + Firebug.getEnablementStatus();
464 
465         if (Firebug.getSuspended())
466             tooltip += "\n" + Firebug.getSuspended();
467         else
468             tooltip += "\n" + $STRP("plural.Total_Firebugs", [TabWatcher.contexts.length]);
469 
470         if (Firebug.allPagesActivation == "on")
471         {
472             var label = $STR("enablement.on");
473             tooltip += "\n"+label+" "+$STR("enablement.for all pages");
474         }
475         // else allPagesActivation == "none" we don't show it.
476 
477         tooltip += "\n" + $STR(Firebug.getPlacement());
478 
479         $('fbStatusBar').setAttribute("tooltiptext", tooltip);
480     },
481 
482     getURLsForAllActiveContexts: function()
483     {
484         var contextURLSet = [];  // create a list of all unique activeContexts
485         TabWatcher.iterateContexts( function createActiveContextList(context)
486         {
487             if (FBTrace.DBG_WINDOWS)
488                 FBTrace.sysout("context "+context.getName());
489 
490             try
491             {
492                 var cw = context.window;
493                 if (cw)
494                 {
495                     if (cw.closed)
496                         url = "about:closed";
497                     else
498                         if ('location' in cw)
499                             var url = cw.location.toString();
500                         else
501                             var url = context.getName();
502                     if (url)
503                     {
504                         if (contextURLSet.indexOf(url) == -1)
505                             contextURLSet.push(url);
506                     }
507                 }
508             }
509             catch(e)
510             {
511                 if (FBTrace.DBG_ERRORS)
512                     FBTrace.sysout("firebug.getURLsForAllActiveContexts could not get window.location for a context", e);
513             }
514         });
515 
516         if (FBTrace.DBG_ACTIVATION)
517             FBTrace.sysout("active contexts urls "+contextURLSet.length);
518 
519         return contextURLSet;
520     },
521 
522     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
523     // Dead Windows  XXXjjb this code is not used by 1.4, external placement.
524 
525     killWindow: function(browser, chrome)
526     {
527         deadWindows.push({browser: browser, chrome: chrome});
528         deadWindowTimeout = setTimeout(function() { Firebug.closeDeadWindows(); }, 3000);
529     },
530 
531     rescueWindow: function(browser)
532     {
533         for (var i = 0; i < deadWindows.length; ++i)
534         {
535             if (deadWindows[i].browser == browser)
536             {
537                 deadWindows.splice(i, 1);
538                 if (FBTrace.DBG_WINDOWS)
539                     FBTrace.sysout("rescued "+browser.currentURI.spec);
540                 break;
541             }
542         }
543     },
544 
545     closeDeadWindows: function()
546     {
547         for (var i = 0; i < deadWindows.length; ++i)
548             deadWindows[i].chrome.close();
549 
550         deadWindows = [];
551         deadWindowTimeout = 0;
552     },
553 
554     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
555     // Registration
556 
557     registerModule: function()
558     {
559         modules.push.apply(modules, arguments);
560 
561         if (FBTrace.DBG_INITIALIZE)
562         {
563             for (var i = 0; i < arguments.length; ++i)
564                 FBTrace.sysout("registerModule "+arguments[i].dispatchName);
565         }
566     },
567 
568     unregisterModule: function()
569     {
570         for (var i = 0; i < arguments.length; ++i)
571             remove(modules, arguments[i]);
572     },
573 
574     registerActivableModule: function()
575     {
576         activableModules.push.apply(activableModules, arguments);
577         this.registerModule.apply(this, arguments);
578     },
579 
580     registerExtension: function()  // TODO remove
581     {
582         extensions.push.apply(extensions, arguments);
583 
584         for (var i = 0; i < arguments.length; ++i)
585             TabWatcher.addListener(arguments[i]);
586 
587         for (var j = 0; j < arguments.length; j++)
588             Firebug.uiListeners.push(arguments[j]);
589     },
590 
591     unregisterExtension: function()  // TODO remove
592     {
593         for (var i = 0; i < arguments.length; ++i)
594         {
595             TabWatcher.removeListener(arguments[i]);
596             remove(Firebug.uiListeners, arguments[i]);
597             remove(extensions, arguments[i])
598         }
599     },
600 
601     registerUIListener: function()
602     {
603         for (var j = 0; j < arguments.length; j++)
604             Firebug.uiListeners.push(arguments[j]);
605     },
606 
607     unregisterUIListener: function()
608     {
609         for (var i = 0; i < arguments.length; ++i)
610             remove(Firebug.uiListeners, arguments[i]);
611     },
612 
613     registerPanel: function()
614     {
615         panelTypes.push.apply(panelTypes, arguments);
616 
617         for (var i = 0; i < arguments.length; ++i)
618             panelTypeMap[arguments[i].prototype.name] = arguments[i];
619 
620         if (FBTrace.DBG_INITIALIZE)
621             for (var i = 0; i < arguments.length; ++i)
622                 FBTrace.sysout("registerPanel "+arguments[i].prototype.name+"\n");
623     },
624 
625     registerRep: function()
626     {
627         reps.push.apply(reps, arguments);
628     },
629 
630     unregisterRep: function()
631     {
632         for (var i = 0; i < arguments.length; ++i)
633             remove(reps, arguments[i]);
634     },
635 
636     setDefaultReps: function(funcRep, rep)
637     {
638         defaultRep = rep;
639         defaultFuncRep = funcRep;
640     },
641 
642     registerEditor: function()
643     {
644         editors.push.apply(editors, arguments);
645     },
646 
647     registerStringBundle: function(bundleURI)
648     {
649         categoryManager.addCategoryEntry("strings_firebug", bundleURI, "", true, true);
650         this.stringBundle = null;
651     },
652 
653     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
654     // Localization
655 
656     getStringBundle: function()
657     {
658         if (!this.stringBundle)
659             this.stringBundle = stringBundleService.createExtensibleBundle("strings_firebug");
660         return this.stringBundle;
661     },
662 
663     getPluralRule: function()
664     {
665         try {
666             return this.getStringBundle().GetStringFromName("pluralRule");
667         } catch (err) { }
668     },
669 
670     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
671     // Options
672 
673     togglePref: function(name)
674     {
675         this.setPref(Firebug.prefDomain, name, !this[name]);
676     },
677 
678     getPref: function(prefDomain, name)
679     {
680         var prefName = prefDomain + "." + name;
681 
682         var type = prefs.getPrefType(prefName);
683         if (type == nsIPrefBranch.PREF_STRING)
684             return prefs.getCharPref(prefName);
685         else if (type == nsIPrefBranch.PREF_INT)
686             return prefs.getIntPref(prefName);
687         else if (type == nsIPrefBranch.PREF_BOOL)
688             return prefs.getBoolPref(prefName);
689     },
690 
691     setPref: function(prefDomain, name, value)
692     {
693         var prefName = prefDomain + "." + name;
694 
695         var type = prefs.getPrefType(prefName);
696         if (type == nsIPrefBranch.PREF_STRING)
697             prefs.setCharPref(prefName, value);
698         else if (type == nsIPrefBranch.PREF_INT)
699             prefs.setIntPref(prefName, value);
700         else if (type == nsIPrefBranch.PREF_BOOL)
701             prefs.setBoolPref(prefName, value);
702         else if (type == nsIPrefBranch.PREF_INVALID)
703         {
704             FBTrace.sysout("firebug.setPref FAILS: Invalid preference "+prefName+" check that it is listed in defaults/prefs.js");
705         }
706 
707         if (FBTrace.DBG_OPTIONS)
708             FBTrace.sysout("firebug.setPref type="+type+" name="+prefName+" value="+value+"\n");
709     },
710 
711     clearPref: function(prefDomain, name)
712     {
713         var prefName = prefDomain + "." + name;
714         if (prefs.prefHasUserValue(prefName))
715             prefs.clearUserPref(prefName);
716     },
717 
718     increaseTextSize: function(amt)
719     {
720         this.setTextSize(this.textSize+amt);
721     },
722 
723     setTextSize: function(value)
724     {
725         this.setPref(Firebug.prefDomain, "textSize", value);
726     },
727 
728     updatePref: function(name, value)
729     {
730         // Prevent infinite recursion due to pref observer
731         if ( optionUpdateMap.hasOwnProperty(name) )
732             return;
733 
734         optionUpdateMap[name] = 1;
735         this[name] = value;
736 
737         dispatch(modules, "updateOption", [name, value]);
738 
739         // Update the current chrome...
740         Firebug.chrome.updateOption(name, value);
741 
742         // ... as well as the original in-browser chrome (if Firebug is currently detached).
743         // xxxHonza, xxxJJB: John, the Firebug.externalChrome is not longer set, is it correct?
744         // it's still used in FirebugChrome.setGlobalAttribute.
745         if (Firebug.chrome != Firebug.originalChrome)
746             Firebug.originalChrome.updateOption(name, value);
747 
748         if (name.substr(0, 15) == "externalEditors")
749             this.loadExternalEditors();
750 
751         delete optionUpdateMap[name];
752 
753         if (FBTrace.DBG_OPTIONS)
754             FBTrace.sysout("firebug.updatePref EXIT: "+name+"="+value+"\n");
755     },
756 
757     // ********************************************************************************************
758     // External editors
759     // TODO move to editors.js as Firebug.Editors module
760 
761     loadExternalEditors: function()
762     {
763         const prefName = "externalEditors";
764         const editorPrefNames = ["label", "executable", "cmdline", "image"];
765 
766         externalEditors = [];
767         var list = this.getPref(this.prefDomain, prefName).split(",");
768         for (var i = 0; i < list.length; ++i)
769         {
770             var editorId = list[i];
771             if ( !editorId || editorId == "")
772                 continue;
773             var item = { id: editorId };
774             for( var j = 0; j < editorPrefNames.length; ++j )
775             {
776                 try {
777                     item[editorPrefNames[j]] = this.getPref(this.prefDomain, prefName+"."+editorId+"."+editorPrefNames[j]);
778                 }
779                 catch(exc)
780                 {
781                 }
782             }
783             if ( item.label && item.executable )
784             {
785                 if (!item.image)
786                     item.image = getIconURLForFile(item.executable);
787                 externalEditors.push(item);
788             }
789         }
790         return externalEditors;
791     },
792 
793     get registeredEditors()
794     {
795         var newArray = [];
796         if ( editors.length > 0 )
797         {
798             newArray.push.apply(newArray, editors);
799             if ( externalEditors.length > 0 )
800                 newArray.push("-");
801         }
802         if ( externalEditors.length > 0 )
803             newArray.push.apply(newArray, externalEditors);
804 
805         return newArray;
806     },
807 
808     openEditors: function()
809     {
810         var args = {
811             FBL: FBL,
812             prefName: this.prefDomain + ".externalEditors"
813         };
814         openWindow("Firebug:ExternalEditors", "chrome://firebug/content/editors.xul", "", args);
815     },
816 
817     openInEditor: function(context, editorId)
818     {
819         try
820         {
821             if (!editorId)
822                 return;
823 
824             var location;
825             if (context)
826             {
827                 var panel = Firebug.chrome.getSelectedPanel();
828                 if (panel)
829                 {
830                     location = panel.location;
831                     if (!location && panel.name == "html")
832                         location = context.window.document.location;
833                     if (location && (location instanceof Firebug.SourceFile || location instanceof CSSStyleSheet ))
834                         location = location.href;
835                 }
836             }
837             if (!location)
838             {
839                 if (tabBrowser.currentURI)
840                     location = tabBrowser.currentURI.asciiSpec;
841             }
842             if (!location)
843                 return;
844             location = location.href || location.toString();
845             if (Firebug.filterSystemURLs && isSystemURL(location))
846                 return;
847 
848             var list = extendArray(editors, externalEditors);
849             var editor = null;
850             for( var i = 0; i < list.length; ++i )
851             {
852                 if (editorId == list[i].id)
853                 {
854                     editor = list[i];
855                     break;
856                 }
857             }
858             if (editor)
859             {
860                 if (editor.handler)
861                 {
862                     editor.handler(location);
863                     return;
864                 }
865                 var args = [];
866                 var localFile = null;
867                 var targetAdded = false;
868                 if (editor.cmdline)
869                 {
870                     args = editor.cmdline.split(" ");
871                     for( var i = 0; i < args.length; ++i )
872                     {
873                         if ( args[i] == "%url" )
874                         {
875                             args[i] = location;
876                             targetAdded = true;
877                         }
878                         else if ( args[i] == "%file" )
879                         {
880                             if (!localFile)
881                                 localFile = this.getLocalSourceFile(context, location);
882                             args[i] = localFile;
883                             targetAdded = true;
884                         }
885                     }
886                 }
887                 if (!targetAdded)
888                 {
889                     localFile = this.getLocalSourceFile(context, location);
890                     if (!localFile)
891                         return;
892                     args.push(localFile);
893                 }
894                 FBL.launchProgram(editor.executable, args);
895             }
896         } catch(exc) { ERROR(exc); }
897     },
898 
899     getLocalSourceFile: function(context, href)
900     {
901         if ( isLocalURL(href) )
902             return getLocalPath(href);
903 
904         var data;
905         if (context)
906         {
907             data = context.sourceCache.loadText(href);
908         }
909         else
910         {
911             // xxxHonza: if the fake context is used the source code is always get using
912             // (a) the browser cache or (b) request to the server.
913             var selectedBrowser = Firebug.chrome.getCurrentBrowser();
914             var ctx = {
915                 browser: selectedBrowser,
916                 window: selectedBrowser.contentWindow
917             };
918             data = new Firebug.SourceCache(ctx).loadText(href);
919         }
920 
921         if (!data)
922             return;
923 
924         if (!temporaryDirectory)
925         {
926             var tmpDir = DirService.getFile(NS_OS_TEMP_DIR, {});
927             tmpDir.append("fbtmp");
928             tmpDir.createUnique(nsIFile.DIRECTORY_TYPE, 0775);
929             temporaryDirectory = tmpDir;
930         }
931 
932         var lpath = href.replace(/^[^:]+:\/*/g, "").replace(/\?.*$/g, "").replace(/[^0-9a-zA-Z\/.]/g, "_");
933         /* dummy comment to workaround eclipse bug */
934         if ( !/\.[\w]{1,5}$/.test(lpath) )
935         {
936             if ( lpath.charAt(lpath.length-1) == '/' )
937                 lpath += "index";
938             lpath += ".html";
939         }
940 
941         if ( getPlatformName() == "WINNT" )
942             lpath = lpath.replace(/\//g, "\\");
943 
944         var file = QI(temporaryDirectory.clone(), nsILocalFile);
945         file.appendRelativePath(lpath);
946         if (!file.exists())
947             file.create(nsIFile.NORMAL_FILE_TYPE, 0664);
948         temporaryFiles.push(file.path);
949 
950         var converter = CCIN("@mozilla.org/intl/scriptableunicodeconverter", "nsIScriptableUnicodeConverter");
951         converter.charset = 'UTF-8'; // TODO detect charset from current tab
952         data = converter.ConvertFromUnicode(data);
953 
954         var stream = CCIN("@mozilla.org/network/safe-file-output-stream;1", "nsIFileOutputStream");
955         stream.init(file, 0x04 | 0x08 | 0x20, 0664, 0); // write, create, truncate
956         stream.write(data, data.length);
957         if (stream instanceof nsISafeOutputStream)
958             stream.finish();
959         else
960             stream.close();
961 
962         return file.path;
963     },
964 
965     deleteTemporaryFiles: function()  // TODO call on "shutdown" event to modules
966     {
967         try {
968             var file = CCIN("@mozilla.org/file/local;1", "nsILocalFile");
969             for( var i = 0; i < temporaryFiles.length; ++i)
970             {
971                 file.initWithPath(temporaryFiles[i]);
972                 if (file.exists())
973                     file.remove(false);
974             }
975         }
976         catch(exc)
977         {
978         }
979         try {
980             if (temporaryDirectory && temporaryDirectory.exists())
981                 temporaryDirectory.remove(true);
982         } catch(exc)
983         {
984         }
985     },
986 
987     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
988     // Browser Bottom Bar
989 
990     showBar: function(show)
991     {
992         var browser = Firebug.chrome.getCurrentBrowser();
993         if (FBTrace.DBG_WINDOWS || FBTrace.DBG_ACTIVATION)
994             FBTrace.sysout("showBar("+show+") for browser "+browser.currentURI.spec+" FirebugContext "+FirebugContext);
995 
996         var contentBox = Firebug.chrome.$("fbContentBox");
997         var contentSplitter = Firebug.chrome.$("fbContentSplitter");
998 
999         var shouldShow = show/* && !Firebug.isDetached()*/;
1000         contentBox.setAttribute("collapsed", !shouldShow);
1001 
1002         if(!show)
1003             Firebug.Inspector.inspectNode(null);
1004 
1005         if (contentSplitter)
1006             contentSplitter.setAttribute("collapsed", !shouldShow);
1007 
1008         if (toggleCommand)
1009             toggleCommand.setAttribute("checked", !!shouldShow);
1010 
1011         if (detachCommand)
1012             detachCommand.setAttribute("checked", Firebug.isDetached());
1013 
1014         this.showKeys(shouldShow);
1015 
1016         dispatch(Firebug.uiListeners, show ? "showUI" : "hideUI", [browser, FirebugContext]);
1017 
1018         // Sync panel state after the showUI event is dispatched. syncPanel method calls
1019         // Panel.show method, which expects the active context to be already registered.
1020         if (show)
1021             Firebug.chrome.syncPanel();
1022     },
1023 
1024     showKeys: function(shouldShow)
1025     {
1026         if (!this.fbOnlyKeys)
1027         {
1028             var keyset = document.getElementById("mainKeyset");
1029             this.fbOnlyKeys = keyset.getElementsByClassName("fbOnlyKey").item(0);
1030         }
1031         var keys = this.fbOnlyKeys;
1032         for (var i = 0; i < keys.length; i++)
1033             keys[i].setAttribute("disabled", !!shouldShow);
1034     },
1035 
1036     closeFirebug: function(userCommand)  // this is really deactivate
1037     {
1038         var browser = FirebugChrome.getCurrentBrowser();
1039 
1040         TabWatcher.unwatchBrowser(browser, userCommand);
1041         Firebug.resetTooltip();
1042     },
1043 
1044     /*
1045      * Primary function to activate or minimize firebug. Used by
1046      * <ol>
1047      * <li>the status bar icon click action</li>
1048      * <li>the activation button (within Firebug.xul) click action</li>
1049      * </ol>
1050      * @param forceOpen: don't minimize, stay open if open.
1051      * @param panelName: eg 'script', to select a specific panel.
1052      */
1053     toggleBar: function(forceOpen, panelName)
1054     {
1055         var browser = FirebugChrome.getCurrentBrowser();
1056 
1057         if (panelName)
1058             Firebug.chrome.selectPanel(panelName);
1059 
1060         if (FirebugContext && browser.showFirebug)  // then we are already debugging the selected tab
1061         {
1062             if (Firebug.isDetached()) // if we are out of the browser focus the window
1063                 Firebug.chrome.focus();
1064             else if (Firebug.openInWindow)
1065                 this.detachBar(context);
1066             else if (Firebug.isMinimized()) // toggle minimize
1067                 Firebug.unMinimize();
1068             else if (!forceOpen)  // else isInBrowser
1069                 Firebug.minimizeBar();
1070         }
1071         else  // closed or no context or no showFirebug
1072         {
1073             if (FBTrace.DBG_ERRORS)
1074             {
1075                 var context = TabWatcher.getContextByWindow(browser.contentWindow);
1076                 if (context) // ASSERT: we should not have showFirebug false on a page with a context
1077                     FBTrace.sysout("Firebug.toggleBar: placement "+this.getPlacement()+ " context: "+context.getName()+" FirebugContext: "+(FirebugContext?FirebugContext.getName():"null")+" browser.showFirebug:"+browser.showFirebug);
1078             }
1079 
1080             var created = TabWatcher.watchBrowser(browser);  // create a context for this page
1081             if (!created)
1082             {
1083                 if (FBTrace.DBG_ERRORS)
1084                     FBTrace.sysout("Rejected page should explain to user!");
1085                 return false;
1086             }
1087 
1088             if (Firebug.isMinimized()) // then toggle minimize
1089                 Firebug.unMinimize();
1090         }
1091         return true;
1092      },
1093 
1094     minimizeBar: function()  // just pull down the UI, but don't deactivate the context
1095     {
1096         if (Firebug.isDetached())  // TODO disable minimize on externalMode
1097         {
1098             // TODO reattach
1099             Firebug.toggleDetachBar(false, false);
1100             Firebug.chrome.focus() ;
1101         }
1102         else // inBrowser -> minimized
1103         {
1104             Firebug.setPlacement("minimized");
1105             this.showBar(false);
1106         }
1107     },
1108 
1109     unMinimize: function()
1110     {
1111         this.updateActiveContexts(FirebugContext);
1112         Firebug.setPlacement("inBrowser");
1113         Firebug.showBar(true);
1114     },
1115 
1116     toggleDetachBar: function(forceOpen, reopenInBrowser)  // detached -> closed; inBrowser -> detached TODO reattach
1117     {
1118         if (!forceOpen && Firebug.isDetached())  // detached -> minimized
1119         {
1120             Firebug.chrome.close();
1121             detachCommand.setAttribute("checked", false);
1122             if (reopenInBrowser)
1123             {
1124                 setTimeout(function delayMinimize()
1125                 {
1126                     Firebug.unMinimize()
1127                 });
1128             }
1129         }
1130         else
1131             this.detachBar(FirebugContext);
1132     },
1133 
1134     closeDetachedWindow: function(userCommands)
1135     {
1136         Firebug.showBar(false);
1137 
1138         if (FirebugContext)
1139             TabWatcher.unwatchBrowser(FirebugContext.browser, userCommands);
1140         // else the user closed Firebug external window while not looking at a debugged web page.
1141 
1142         Firebug.resetTooltip();
1143     },
1144 
1145     setChrome: function(newChrome, newPlacement)
1146     {
1147         var oldChrome = Firebug.chrome;
1148         Firebug.chrome = newChrome;
1149         Firebug.setPlacement(newPlacement);
1150 
1151         // reattach all contexts to the new chrome
1152         TabWatcher.iterateContexts(function reattach(context)
1153         {
1154             context.reattach(oldChrome, newChrome);
1155 
1156             Firebug.reattachContext(context.browser, context);
1157         });
1158     },
1159 
1160     detachBar: function(context)
1161     {
1162         if (!context)
1163         {
1164             var browser = Firebug.chrome.getCurrentBrowser();
1165             var created = TabWatcher.watchBrowser(browser);  // create a context for this page
1166             if (!created)
1167             {
1168                 if (FBTrace.DBG_ERRORS)
1169                     FBTrace.sysout("Firebug.detachBar, no context in "+window.location);
1170                 return null;
1171             }
1172             context = TabWatcher.getContextByWindow(browser.contentWindow);
1173         }
1174 
1175         if (Firebug.isDetached())  // can be set true attachBrowser
1176         {
1177             Firebug.chrome.focus();
1178             return null;
1179         }
1180 
1181         this.showBar(false);  // don't show in browser.xul now
1182 
1183         Firebug.chrome.setFirebugContext(context);  // make sure the FirebugContext agrees with context
1184         FirebugContext = context;
1185 
1186         this.setPlacement("detached");  // we'll reset it in the new window, but we seem to race with code in this window.
1187 
1188         if (FBTrace.DBG_ACTIVATION)
1189             FBTrace.sysout("Firebug.detachBar opening firebug.xul for context "+FirebugContext.getName() );
1190 
1191         var args = {
1192             FBL: FBL,
1193             Firebug: this,
1194             browser: context.browser,
1195             FirebugContext: window.FirebugContext
1196         };
1197         var win = openWindow("Firebug", "chrome://firebug/content/firebug.xul", "", args);
1198 
1199         return win;
1200     },
1201 
1202     syncBar: function()  // show firebug if we should
1203     {
1204         var browser = FirebugChrome.getCurrentBrowser();
1205         this.showBar(browser && browser.showFirebug);  // implicitly this is operating in the chrome of browser.xul
1206     },
1207 
1208     onClickStatusIcon: function(context, event)
1209     {
1210         if (event.button != 0)
1211             return;
1212         else if (isControl(event))
1213             this.toggleDetachBar(true);
1214         else if (context && context.errorCount)
1215             Firebug.toggleBar(undefined, 'console');
1216         else
1217             this.toggleBar();
1218     },
1219 
1220     onClickStatusText: function(context, event)
1221     {
1222         if (event.button != 0)
1223             return;
1224 
1225         if (!context || !context.errorCount)
1226             return;
1227 
1228         var panel = Firebug.chrome.getSelectedPanel();
1229         if (panel && panel.name != "console")
1230         {
1231             Firebug.chrome.selectPanel("console");
1232             cancelEvent(event);
1233         }
1234     },
1235 
1236     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
1237 
1238     resetAllOptions: function(confirm)  // to default state
1239     {
1240         if (confirm)
1241         {
1242             if (!promptService.confirm(null, $STR("Firebug"), $STR("confirmation.Reset_All_Firebug_Options")))
1243                 return;
1244         }
1245 
1246         var preferences = prefs.getChildList("extensions.firebug", {});
1247         for (var i = 0; i < preferences.length; i++)
1248         {
1249             if (preferences[i].indexOf("DBG_") == -1 && preferences[i].indexOf("filterSystemURLs") == -1)
1250             {
1251                 if (FBTrace.DBG_OPTIONS)
1252                     FBTrace.sysout("Clearing option: "+i+") "+preferences[i]);
1253                 if (prefs.prefHasUserValue(preferences[i]))  // avoid exception
1254                     prefs.clearUserPref(preferences[i]);
1255             }
1256             else
1257             {
1258                 if (FBTrace.DBG_OPTIONS)
1259                     FBTrace.sysout("Skipped clearing option: "+i+") "+preferences[i]);
1260             }
1261         }
1262 
1263         TabWatcher.iterateContexts( function clearBPs(context)
1264         {
1265             Firebug.Debugger.clearAllBreakpoints(context);
1266         });
1267     },
1268 
1269     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
1270     // Panels
1271 
1272     getPanelType: function(panelName)
1273     {
1274         if (panelTypeMap.hasOwnProperty(panelName))
1275             return panelTypeMap[panelName];
1276         else
1277             return null;
1278     },
1279 
1280     getPanelTitle: function(panelType)
1281     {
1282         return panelType.prototype.title ? panelType.prototype.title
1283             : FBL.$STR("Panel-"+panelType.prototype.name);
1284     },
1285 
1286     getMainPanelTypes: function(context)
1287     {
1288         var resultTypes = [];
1289 
1290         for (var i = 0; i < panelTypes.length; ++i)
1291         {
1292             var panelType = panelTypes[i];
1293             if (!panelType.prototype.parentPanel)
1294                 resultTypes.push(panelType);
1295         }
1296 
1297         if (context.panelTypes)
1298         {
1299             for (var i = 0; i < context.panelTypes.length; ++i)
1300             {
1301                 var panelType = context.panelTypes[i];
1302                 if (!panelType.prototype.parentPanel)
1303                     resultTypes.push(panelType);
1304             }
1305         }
1306 
1307         return resultTypes;
1308     },
1309 
1310     getSidePanelTypes: function(context, mainPanel)
1311     {
1312         if (!mainPanel)
1313             return [];
1314 
1315         var resultTypes = [];
1316 
1317         for (var i = 0; i < panelTypes.length; ++i)
1318         {
1319             var panelType = panelTypes[i];
1320             if (panelType.prototype.parentPanel && (panelType.prototype.parentPanel == mainPanel.name) )
1321                 resultTypes.push(panelType);
1322         }
1323 
1324         if (context.panelTypes)
1325         {
1326             for (var i = 0; i < context.panelTypes.length; ++i)
1327             {
1328                 var panelType = context.panelTypes[i];
1329                 if (panelType.prototype.parentPanel == mainPanel.name)
1330                     resultTypes.push(panelType);
1331             }
1332         }
1333 
1334         resultTypes.sort(function(a, b)
1335         {
1336             return a.prototype.order < b.prototype.order ? -1 : 1;
1337         });
1338 
1339         return resultTypes;
1340     },
1341 
1342     /**
1343      * Gets an object containing the state of the panel from the last time
1344      * it was displayed before one or more page reloads.
1345      * The 'null' return here is a too-subtle signal to the panel code in bindings.xml.
1346      * Note that panel.context may not have a persistedState, but in addition the persisted state for panel.name may be null.
1347      */
1348     getPanelState: function(panel)
1349     {
1350         var persistedState = panel.context.persistedState;
1351         if (!persistedState || !persistedState.panelState)
1352             return null;
1353 
1354         return persistedState.panelState[panel.name];
1355     },
1356 
1357     showPanel: function(browser, panel)
1358     {
1359         dispatch(modules, "showPanel", [browser, panel]);
1360     },
1361 
1362     showSidePanel: function(browser, sidePanel)
1363     {
1364         dispatch(modules, "showSidePanel", [browser, sidePanel]);
1365     },
1366 
1367     reattachContext: function(browser, context)
1368     {
1369         dispatch(modules, "reattachContext", [browser, context]);
1370     },
1371 
1372     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
1373     // URL mapping
1374 
1375     getObjectByURL: function(context, url)
1376     {
1377         for (var i = 0; i < modules.length; ++i)
1378         {
1379             var object = modules[i].getObjectByURL(context, url);
1380             if (object)
1381                 return object;
1382         }
1383     },
1384 
1385     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
1386     // Reps
1387 
1388     getRep: function(object)
1389     {
1390         var type = typeof(object);
1391         if (type == 'object' && object instanceof String)
1392             type = 'string';
1393 
1394         for (var i = 0; i < reps.length; ++i)
1395         {
1396             var rep = reps[i];
1397             try
1398             {
1399                 if (rep.supportsObject(object, type))
1400                 {
1401                     if (FBTrace.DBG_DOM)
1402                         FBTrace.sysout("getRep type: "+type+" object: "+object, rep);
1403                     return rep;
1404                 }
1405             }
1406             catch (exc)
1407             {
1408                 if (FBTrace.DBG_ERRORS)
1409                 {
1410                     FBTrace.sysout("firebug.getRep FAILS: "+ exc, exc);
1411                     FBTrace.sysout("firebug.getRep reps["+i+"/"+reps.length+"]: "+(typeof(reps[i])), reps[i]);
1412                 }
1413             }
1414         }
1415 
1416         if (FBTrace.DBG_DOM)
1417             FBTrace.sysout("getRep default type: "+type+" object: "+object, rep);
1418 
1419         return (type == 'function')?defaultFuncRep:defaultRep;
1420     },
1421 
1422     getRepObject: function(node)
1423     {
1424         var target = null;
1425         for (var child = node; child; child = child.parentNode)
1426         {
1427             if (hasClass(child, "repTarget"))
1428                 target = child;
1429 
1430             if (child.repObject)
1431             {
1432                 if (!target && hasClass(child, "repIgnore"))
1433                     break;
1434                 else
1435                     return child.repObject;
1436             }
1437         }
1438     },
1439 
1440     getRepNode: function(node)
1441     {
1442         for (var child = node; child; child = child.parentNode)
1443         {
1444             if (child.repObject)
1445                 return child;
1446         }
1447     },
1448 
1449     getElementByRepObject: function(element, object)
1450     {
1451         for (var child = element.firstChild; child; child = child.nextSibling)
1452         {
1453             if (child.repObject == object)
1454                 return child;
1455         }
1456     },
1457 
1458     /**
1459      * Takes an element from a panel document and finds the owning panel.
1460      */
1461     getElementPanel: function(element)
1462     {
1463         for (; element; element = element.parentNode)
1464         {
1465             if (element.ownerPanel)
1466                 return element.ownerPanel;
1467         }
1468     },
1469 
1470     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
1471 
1472     visitWebsite: function(which)
1473     {
1474         openNewTab(firebugURLs[which]);
1475     },
1476 
1477     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
1478     // nsISupports
1479 
1480     QueryInterface : function(iid)
1481     {
1482         if (iid.equals(nsISupports))
1483         {
1484             return this;
1485         }
1486 
1487         throw Components.results.NS_NOINTERFACE;
1488     },
1489 
1490     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
1491     // nsIPrefObserver
1492 
1493     observe: function(subject, topic, data)
1494     {
1495         if (data.indexOf("extensions.") == -1)
1496             return;
1497 
1498         if (data.substring(0, Firebug.prefDomain.length) == Firebug.prefDomain)
1499             var domain = Firebug.prefDomain;
1500         if (data.substring(0, Firebug.servicePrefDomain.length) == Firebug.servicePrefDomain)
1501             var domain = Firebug.servicePrefDomain;
1502 
1503         if (domain)
1504         {
1505             var name = data.substr(domain.length+1);
1506             var value = this.getPref(domain, name);
1507             if (FBTrace.DBG_OPTIONS) FBTrace.sysout("firebug.observe name = value: "+name+"= "+value+"\n");
1508             this.updatePref(name, value);
1509         }
1510 
1511         if (topic == "nsPref:changed")
1512         {
1513             if (data.indexOf(".enableSites") != -1)
1514             {
1515                 if (FBTrace.DBG_PANELS)
1516                     FBTrace.sysout("Firebug.observe subject: "+subject+" topic "+topic+" data: "+data+"\n");
1517                 dispatch(modules, "onEnablePrefChange", [data]);
1518             }
1519         }
1520     },
1521 
1522     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
1523     // nsIFireBugClient  These are per Firefox XUL window callbacks
1524 
1525     enableXULWindow: function()  // Called when the first context is created.
1526     {
1527         if (window.closed)
1528             throw new Error("enableXULWindow window is closed!!");
1529 
1530         if (FBTrace.DBG_ACTIVATION)
1531             FBTrace.sysout("enable XUL Window +++++++++++++++++++++++++++++++++++++++", Firebug.detachArgs);
1532 
1533         dispatch(modules, "enable", [FirebugChrome]);  // allows errors to flow thru fbs and callbacks to supportWindow to begin
1534     },
1535 
1536     disableXULWindow: function()
1537     {
1538         dispatch(modules, "disable", [FirebugChrome]);
1539         if (FBTrace.DBG_ACTIVATION)
1540             FBTrace.sysout("disable XUL Window --------------------------------------");
1541     },
1542 
1543     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
1544     // These are XUL window level call backs and should be moved into Firebug where is says nsIFirebugClient
1545     // xxxHonza: so I did
1546 
1547     onPauseJSDRequested: function(rejection)
1548     {
1549         if (top.FirebugContext)  // then we are active in this browser.xul
1550             rejection.push(true); // so reject the request
1551 
1552         dispatch2(Firebug.Debugger.fbListeners, "onPauseJSDRequested", [rejection]);
1553     },
1554 
1555     onJSDActivate: function(active, why)  // just before hooks are set
1556     {
1557         this.setIsJSDActive(active);
1558 
1559         if (FBTrace.DBG_ACTIVATION)
1560             FBTrace.sysout("debugger.onJSDActivate "+why+" active:"+active+"\n");
1561 
1562         dispatch2(Firebug.Debugger.fbListeners, "onJSDActivate", [active, why]);
1563     },
1564 
1565     onJSDDeactivate: function(active, why)
1566     {
1567         this.setIsJSDActive(active);
1568 
1569         if (FBTrace.DBG_ACTIVATION)
1570             FBTrace.sysout("debugger.onJSDDeactivate "+why+" active:"+active+"\n");
1571 
1572         dispatch2(Firebug.Debugger.fbListeners, "onJSDDeactivate", [active, why]);
1573     },
1574 
1575     setIsJSDActive: function(active)  // should only be call on the jsd activation events, so it correctly reflects jsd state
1576     {
1577         if (active)
1578             $('fbStatusIcon').setAttribute("script", "on");
1579         else
1580             $('fbStatusIcon').setAttribute("script", "off");
1581 
1582         if (FBTrace.DBG_ACTIVATION)
1583             FBTrace.sysout("debugger.setIsJSDActive "+active+"\n");
1584 
1585     },
1586 
1587     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
1588     // Placement
1589 
1590     isDetached: function()
1591     {
1592         return Firebug.placement == PLACEMENT_DETACHED;
1593     },
1594 
1595     isMinimized: function()
1596     {
1597         return Firebug.placement == PLACEMENT_MINIMIZED;
1598     },
1599 
1600     isInBrowser: function()
1601     {
1602         return Firebug.placement == PLACEMENT_INBROWSER;
1603     },
1604 
1605     placements: ["none", "inBrowser", "detached", "minimized"],
1606 
1607     placement: 1,
1608 
1609     setPlacement: function(toPlacement)
1610     {
1611         // TODO : This should probably be an event so others can link into this
1612         Firebug.chrome.$("fbSearchBox").hideOptions();
1613 
1614         if (FBTrace.DBG_ACTIVATION)
1615             FBTrace.sysout("Firebug.setPlacement from "+Firebug.getPlacement()+" to "+toPlacement+" with chrome "+Firebug.chrome.window.location);
1616 
1617         for (var i = 0; i < Firebug.placements.length; i++)
1618         {
1619             if (toPlacement == Firebug.placements[i])
1620             {
1621                 if (Firebug.placement != i) // then we are changing the value
1622                 {
1623                     Firebug.placement = i;
1624                     delete Firebug.previousPlacement;
1625                     Firebug.setPref(Firebug.prefDomain, "previousPlacement", Firebug.placement);
1626                     Firebug.resetTooltip();
1627                 }
1628                 return Firebug.placement;
1629             }
1630         }
1631         throw new Error("Firebug.setPlacement cannot match "+toPlacement+" as a placement");
1632     },
1633 
1634     getPlacement: function()
1635     {
1636         return Firebug.placements[Firebug.placement];
1637     },
1638 
1639     openMinimized: function()
1640     {
1641         if (!Firebug.previousPlacement)
1642             Firebug.previousPlacement = Firebug.getPref(Firebug.prefDomain, "previousPlacement");
1643 
1644         return (Firebug.previousPlacement && (Firebug.previousPlacement == PLACEMENT_MINIMIZED) )
1645     },
1646 
1647     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
1648     // TabWatcher Listener
1649 
1650     getContextType: function()
1651     {
1652         return Firebug.TabContext;
1653     },
1654 
1655     shouldShowContext: function(context)
1656     {
1657         return dispatch2(modules, "shouldShowContext", [context]);
1658     },
1659 
1660     shouldCreateContext: function(browser, url, userCommands)
1661     {
1662         return dispatch2(modules, "shouldCreateContext", [browser, url, userCommands]);
1663     },
1664 
1665     shouldNotCreateContext: function(browser, url, userCommands)
1666     {
1667         return dispatch2(modules, "shouldNotCreateContext", [browser, url, userCommands]);
1668     },
1669 
1670     initContext: function(context, persistedState)  // called after a context is created.
1671     {
1672         context.panelName = context.browser.panelName;
1673         if (context.browser.sidePanelNames)
1674             context.sidePanelNames = context.browser.sidePanelNames;
1675 
1676 
1677         if (FBTrace.DBG_ERRORS && !context.sidePanelNames)
1678             FBTrace.sysout("firebug.initContext sidePanelNames:",context.sidePanelNames);
1679 
1680         dispatch(modules, "initContext", [context, persistedState]);
1681 
1682         this.updateActiveContexts(context); // a newly created context is active
1683 
1684         Firebug.chrome.setFirebugContext(context); // a newly created context becomes the default for the view
1685         FirebugContext = context;
1686 
1687         if (deadWindowTimeout)
1688             this.rescueWindow(context.browser); // if there is already a window, clear showDetached.
1689     },
1690 
1691     updateActiveContexts: function(context) // this should be the only method to call suspend and resume.
1692     {
1693         if (context)  // either a new context or revisiting an old one
1694         {
1695             if(!this.hadFirstContext)  // then we need to enable the panels iff the prefs say so
1696             {
1697                 this.hadFirstContext = true;
1698                 Firebug.ModuleManager.obeyPrefs(context);
1699             }
1700             if (Firebug.getSuspended())
1701                 Firebug.resume();  // This will cause onResumeFirebug for every context including this one.
1702         }
1703         else // this browser has no context
1704         {
1705             Firebug.suspend();
1706         }
1707 
1708         Firebug.resetTooltip();
1709     },
1710 
1711     showContext: function(browser, context)  // TabWatcher showContext. null context means we don't debug that browser
1712     {
1713         if (clearContextTimeout)
1714         {
1715             clearTimeout(clearContextTimeout);
1716             clearContextTimeout = 0;
1717         }
1718 
1719         FirebugContext = context;
1720         Firebug.chrome.setFirebugContext(context); // the context becomes the default for its view
1721         this.updateActiveContexts(context);  // resume, after setting FirebugContext
1722 
1723         dispatch(modules, "showContext", [browser, context]);  // tell modules we may show UI
1724 
1725         // user wants detached but we are not yet
1726         if (Firebug.openInWindow && !Firebug.isDetached())
1727         {
1728             if (context && !Firebug.isMinimized()) // don't detach if it's minimized 2067
1729                 this.detachBar(context);  //   the placement will be set once the external window opens
1730             else  // just make sure we are not showing
1731                 this.showBar(false);
1732 
1733             return;
1734         }
1735 
1736         // previous browser.xul had placement minimized
1737         if (Firebug.openMinimized() && !Firebug.isMinimized())
1738         {
1739             this.minimizeBar();
1740             return;
1741         }
1742 
1743         if (Firebug.isMinimized())
1744             this.showBar(false);  // don't show, we are minimized
1745         else if (Firebug.isDetached())
1746             this.syncResumeBox(context);
1747         else  // inBrowser
1748             this.showBar(context?true:false);
1749 
1750     },
1751 
1752     syncResumeBox: function(context)
1753     {
1754         var contentBox = Firebug.chrome.$('fbContentBox');
1755         var resumeBox = Firebug.chrome.$('fbResumeBox');
1756 
1757         if (!resumeBox) // the showContext is being called before the reattachContext, we'll get a second showContext
1758             return;
1759 
1760         if (context)
1761         {
1762             collapse(contentBox, false);
1763             Firebug.chrome.syncPanel();
1764             collapse(resumeBox, true);
1765         }
1766         else
1767         {
1768             collapse(contentBox, true);
1769             collapse(resumeBox, false);
1770             Firebug.chrome.window.document.title = $STR("Firebug - inactive for selected Firefox tab");
1771         }
1772     },
1773 
1774     unwatchBrowser: function(browser)  // the context for this browser has been destroyed and removed
1775     {
1776         Firebug.updateActiveContexts(null);
1777     },
1778 
1779     // Either a top level or a frame, (interior window) for an exist context is seen by the tabWatcher.
1780     watchWindow: function(context, win)
1781     {
1782         for (var panelName in context.panelMap)
1783         {
1784             var panel = context.panelMap[panelName];
1785             panel.watchWindow(win);
1786         }
1787 
1788         dispatch(modules, "watchWindow", [context, win]);
1789     },
1790 
1791     unwatchWindow: function(context, win)
1792     {
1793         for (var panelName in context.panelMap)
1794         {
1795             var panel = context.panelMap[panelName];
1796             panel.unwatchWindow(win);
1797         }
1798         dispatch(modules, "unwatchWindow", [context, win]);
1799     },
1800 
1801     loadedContext: function(context)
1802     {
1803         if (!context.browser.currentURI)
1804             FBTrace.sysout("firebug.loadedContext problem browser ", context.browser);
1805 
1806         dispatch(modules, "loadedContext", [context]);
1807     },
1808 
1809     destroyContext: function(context, persistedState, browser)
1810     {
1811         if (!context)  // then we are called just to clean up
1812             return;
1813 
1814         dispatch(modules, "destroyContext", [context, persistedState]);
1815 
1816         if (FirebugContext == context)
1817             Firebug.chrome.setFirebugContext(null);  // FirebugContext is about to be destroyed
1818 
1819         var browser = context.browser;
1820         // Persist remnants of the context for restoration if the user reloads
1821         browser.panelName = context.panelName;
1822         browser.sidePanelNames = context.sidePanelNames;
1823 
1824         // next the context is deleted and removed from the TabWatcher, we clean up in unWatchBrowser
1825     },
1826 
1827     onSourceFileCreated: function(context, sourceFile)
1828     {
1829         dispatch(modules, "onSourceFileCreated", [context, sourceFile]);
1830     },
1831 
1832     //*********************************************************************************************
1833 
1834     getTabForWindow: function(aWindow)
1835     {
1836         aWindow = getRootWindow(aWindow);
1837 
1838         if (!aWindow || !this.tabBrowser.getBrowserIndexForDocument)
1839             return null;
1840 
1841         try {
1842             var targetDoc = aWindow.document;
1843 
1844             var tab = null;
1845             var targetBrowserIndex = this.tabBrowser.getBrowserIndexForDocument(targetDoc);
1846 
1847             if (targetBrowserIndex != -1)
1848             {
1849                 tab = this.tabBrowser.tabContainer.childNodes[targetBrowserIndex];
1850                 return tab;
1851             }
1852         } catch (ex) {}
1853 
1854         return null;
1855     },
1856 
1857     getTabIdForWindow: function(win)
1858     {
1859         var tab = this.getTabForWindow(win);
1860         return tab ? tab.linkedPanel : null;
1861     },
1862 };
1863 
1864 // ************************************************************************************************
1865 
1866 /**
1867  * Support for listeners registration. This object also extended by Firebug.Module so,
1868  * all modules supports listening automatically. Notice that array of listeners
1869  * is created for each intance of a module within initialize method. Thus all derived
1870  * module classes must ensure that Firebug.Module.initialize method is called for the
1871  * super class.
1872  */
1873 Firebug.Listener = function()
1874 {
1875     // The array is created when the first listeners is added.
1876     // It can't be created here since derived objects would share
1877     // the same array.
1878     this.fbListeners = null;
1879 }
1880 Firebug.Listener.prototype =
1881 {
1882     addListener: function(listener)
1883     {
1884         if (!this.fbListeners)
1885             this.fbListeners = []; // delay the creation until the objects are created so 'this' causes new array for each module
1886 
1887         this.fbListeners.push(listener);
1888     },
1889 
1890     removeListener: function(listener)
1891     {
1892         remove(this.fbListeners, listener);  // if this.fbListeners is null, remove is being called with no add
1893     }
1894 };
1895 
1896 // ************************************************************************************************
1897 
1898 /**
1899  * @module Base class for all modules. Every derived module object must be registered using
1900  * <code>Firebug.registerModule</code> method. There is always one instance of a module object
1901  * per browser window.
1902  */
1903 Firebug.Module = extend(new Firebug.Listener(),
1904 /** @lends Firebug.Module */
1905 {
1906     /**
1907      * Called by Firebug when Firefox window is opened.
1908      */
1909     initialize: function()
1910     {
1911 
1912     },
1913 
1914     /**
1915      * Called when the UI is ready for context creation.
1916      * Used by chromebug; normally FrameProgressListener events trigger UI synchronization,
1917      * this event allows sync without progress events.
1918      */
1919     initializeUI: function(detachArgs)
1920     {
1921     },
1922 
1923     /**
1924      * Called by Firebug when Firefox window is closed.
1925      */
1926     shutdown: function()
1927     {
1928 
1929     },
1930 
1931     /**
1932      * Called when a new context is created but before the page is loaded.
1933      */
1934     initContext: function(context, persistedState)
1935     {
1936     },
1937 
1938     /**
1939      * Called after a context is detached to a separate window;
1940      */
1941     reattachContext: function(browser, context)
1942     {
1943     },
1944 
1945     /**
1946      * Called when a context is destroyed. Module may store info on persistedState for reloaded pages.
1947      */
1948     destroyContext: function(context, persistedState)
1949     {
1950     },
1951 
1952     /**
1953      * Called when attaching to a window (top-level or frame).
1954      */
1955     watchWindow: function(context, win)
1956     {
1957     },
1958 
1959     /**
1960      * Called when unwatching a window (top-level or frame).
1961      */
1962     unwatchWindow: function(context, win)
1963     {
1964     },
1965 
1966     // Called when a FF tab is create or activated (user changes FF tab)
1967     // Called after context is created or with context == null (to abort?)
1968     showContext: function(browser, context)
1969     {
1970     },
1971 
1972     /**
1973      * Called after a context's page gets DOMContentLoaded
1974      */
1975     loadedContext: function(context)
1976     {
1977     },
1978 
1979     /*
1980      * After "onSelectingPanel", a panel has been selected but is not yet visible
1981      */
1982     showPanel: function(browser, panel)
1983     {
1984     },
1985 
1986     showSidePanel: function(browser, sidePanel)
1987     {
1988     },
1989 
1990     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
1991 
1992     updateOption: function(name, value)
1993     {
1994     },
1995 
1996     getObjectByURL: function(context, url)
1997     {
1998     },
1999     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
2000     // intermodule dependency
2001 
2002     // caller needs module. win maybe context.window or iframe in context.window.
2003     // true means module is ready now, else getting ready
2004     isReadyElsePreparing: function(context, win)
2005     {
2006     },
2007 });
2008 
2009 //************************************************************************************************
2010 
2011 Firebug.Extension =
2012 {
2013     acceptContext: function(win,uri)
2014     {
2015         return false;
2016     },
2017 
2018     declineContext: function(win,uri)
2019     {
2020         return false;
2021     }
2022 };
2023 
2024 // ************************************************************************************************
2025 
2026 /**
2027  * @panel Base class for all panels. Every derived panel must define a constructor and
2028  * register with <code>Firebug.registerPanel</code> method. An instance of the panel
2029  * object is created by the framework for each browser tab where Firebug is activated.
2030  */
2031 Firebug.Panel =
2032 /** @lends Firebug.Panel */
2033 {
2034     searchable: false,
2035     editable: true,
2036     breakable: false,
2037     order: 2147483647,
2038     statusSeparator: "<",
2039 
2040     initialize: function(context, doc)
2041     {
2042         if (!context.browser)
2043         {
2044             if (FBTrace.DBG_ERRORS)
2045                 FBTrace.sysout("attempt to create panel with dud context!");
2046             return false;
2047         }
2048 
2049         this.context = context;
2050         this.document = doc;
2051 
2052         this.panelNode = doc.createElement("div");
2053         this.panelNode.ownerPanel = this;
2054 
2055         setClass(this.panelNode, "panelNode panelNode-"+this.name+" contextUID="+context.uid);
2056 
2057         // Load persistent content if any.
2058         var persistedState = Firebug.getPanelState(this);
2059         if (persistedState)
2060         {
2061             this.persistContent = persistedState.persistContent;
2062             if (this.persistContent && persistedState.panelNode)
2063                 this.loadPersistedContent(persistedState);
2064         }
2065 
2066         doc.body.appendChild(this.panelNode);
2067 
2068         // Update panel's tab in case the break-on-next (BON) is active.
2069         var shouldBreak = this.shouldBreakOnNext();
2070         Firebug.Breakpoint.updatePanelTab(this, shouldBreak);
2071 
2072         if (FBTrace.DBG_INITIALIZE)
2073             FBTrace.sysout("firebug.initialize panelNode for "+this.name+"\n");
2074 
2075         this.initializeNode(this.panelNode);
2076     },
2077 
2078     destroy: function(state) // Panel may store info on state
2079     {
2080         if (FBTrace.DBG_INITIALIZE)
2081             FBTrace.sysout("firebug.destroy panelNode for "+this.name+"\n");
2082 
2083         if (this.panelNode)
2084         {
2085             if (this.persistContent)
2086                 this.savePersistedContent(state);
2087             else
2088                 delete state.persistContent;
2089 
2090             delete this.panelNode.ownerPanel;
2091         }
2092 
2093         this.destroyNode();
2094 
2095         clearDomplate(this.panelNode);
2096     },
2097 
2098     savePersistedContent: function(state)
2099     {
2100         state.panelNode = this.panelNode;
2101         state.persistContent = this.persistContent;
2102     },
2103 
2104     loadPersistedContent: function(persistedState)
2105     {
2106         // move the nodes from the persistedState to the panel
2107         while (persistedState.panelNode.firstChild)
2108             this.panelNode.appendChild(persistedState.panelNode.firstChild);
2109 
2110         scrollToBottom(this.panelNode);
2111     },
2112 
2113     // called when a panel in one XUL window is about to appear in another one.
2114     detach: function(oldChrome, newChrome)
2115     {
2116     },
2117 
2118     reattach: function(doc)  // this is how a panel in one window reappears in another window; lazy called
2119     {
2120         this.document = doc;
2121 
2122         if (this.panelNode)
2123         {
2124             this.panelNode = doc.adoptNode(this.panelNode, true);
2125             this.panelNode.ownerPanel = this;
2126             doc.body.appendChild(this.panelNode);
2127         }
2128     },
2129 
2130     // Called at the end of module.initialize; addEventListener-s here
2131     initializeNode: function(myPanelNode)
2132     {
2133     },
2134 
2135     // removeEventListener-s here.
2136     destroyNode: function()
2137     {
2138     },
2139 
2140     show: function(state)  // persistedPanelState plus non-persisted hide() values
2141     {
2142     },
2143 
2144     hide: function(state)  // store info on state for next show.
2145     {
2146     },
2147 
2148     watchWindow: function(win)
2149     {
2150     },
2151 
2152     unwatchWindow: function(win)
2153     {
2154     },
2155 
2156     updateOption: function(name, value)
2157     {
2158     },
2159 
2160     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
2161 
2162     /**
2163      * Toolbar helpers
2164      */
2165     showToolbarButtons: function(buttonsId, show)
2166     {
2167         try
2168         {
2169             var buttons = Firebug.chrome.$(buttonsId);
2170             collapse(buttons, !show);
2171         }
2172         catch (exc)
2173         {
2174             if (FBTrace.DBG_ERRORS)
2175                 FBTrace.sysout("firebug.Panel showToolbarButtons FAILS "+exc, exc);
2176         }
2177     },
2178 
2179     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
2180 
2181     /**
2182      * Returns a number indicating the view's ability to inspect the object.
2183      *
2184      * Zero means not supported, and higher numbers indicate specificity.
2185      */
2186     supportsObject: function(object)
2187     {
2188         return 0;
2189     },
2190 
2191     hasObject: function(object)  // beyond type testing, is this object selectable?
2192     {
2193         return false;
2194     },
2195 
2196     navigate: function(object)
2197     {
2198         if (FBTrace.DBG_PANELS)
2199             FBTrace.sysout("navigate "+this.name+" to "+object+" when this.location="+this.location+"\n");
2200         if (!object)
2201             object = this.getDefaultLocation(this.context);
2202         if (!object)
2203             object = null;  // not undefined.
2204 
2205         if ( !this.location || (object != this.location) )  // if this.location undefined, may set to null
2206         {
2207             if (FBTrace.DBG_PANELS)
2208                 FBTrace.sysout("navigate "+this.name+" to location "+object+"\n");
2209 
2210             this.location = object;
2211             this.updateLocation(object);
2212 
2213             dispatch(Firebug.uiListeners, "onPanelNavigate", [object, this]);
2214         }
2215     },
2216 
2217     updateLocation: function(object)  // if the module can return null from getDefaultLocation, then it must handle it here.
2218     {
2219     },
2220 
2221     /**
2222      * Navigates to the next document whose match parameter returns true.
2223      */
2224     navigateToNextDocument: function(match, reverse)
2225     {
2226         // This is an approximation of the UI that is displayed by the location
2227         // selector. This should be close enough, although it may be better
2228         // to simply generate the sorted list within the module, rather than
2229         // sorting within the UI.
2230         var self = this;
2231         function compare(a, b) {
2232             var locA = self.getObjectDescription(a);
2233             var locB = self.getObjectDescription(b);
2234             if(locA.path > locB.path)
2235                 return 1;
2236             if(locA.path < locB.path)
2237                 return -1;
2238             if(locA.name > locB.name)
2239                 return 1;
2240             if(locA.name < locB.name)
2241                 return -1;
2242             return 0;
2243         }
2244         var allLocs = this.getLocationList().sort(compare);
2245         for (var curPos = 0; curPos < allLocs.length && allLocs[curPos] != this.location; curPos++);
2246 
2247         function transformIndex(index) {
2248             if (reverse) {
2249                 // For the reverse case we need to implement wrap around.
2250                 var intermediate = curPos - index - 1;
2251                 return (intermediate < 0 ? allLocs.length : 0) + intermediate;
2252             } else {
2253                 return (curPos + index + 1) % allLocs.length;
2254             }
2255         };
2256 
2257         for (var next = 0; next < allLocs.length - 1; next++)
2258         {
2259             var object = allLocs[transformIndex(next)];
2260 
2261             if (match(object))
2262             {
2263                 this.navigate(object);
2264                 return object;
2265             }
2266         }
2267     },
2268 
2269     select: function(object, forceUpdate)
2270     {
2271         if (!object)
2272             object = this.getDefaultSelection(this.context);
2273 
2274         if(FBTrace.DBG_PANELS)
2275             FBTrace.sysout("firebug.select "+this.name+" forceUpdate: "+forceUpdate+" "+object+((object==this.selection)?"==":"!=")+this.selection);
2276 
2277         if (forceUpdate || object != this.selection)
2278         {
2279             this.selection = object;
2280             this.updateSelection(object);
2281 
2282             dispatch(Firebug.uiListeners, "onObjectSelected", [object, this]);
2283         }
2284     },
2285 
2286 
2287     updateSelection: function(object)
2288     {
2289     },
2290 
2291     refresh: function()
2292     {
2293 
2294     },
2295 
2296     markChange: function(skipSelf)
2297     {
2298         if (this.dependents)
2299         {
2300             if (skipSelf)
2301             {
2302                 for (var i = 0; i < this.dependents.length; ++i)
2303                 {
2304                     var panelName = this.dependents[i];
2305                     if (panelName != this.name)
2306                         this.context.invalidatePanels(panelName);
2307                 }
2308             }
2309             else
2310                 this.context.invalidatePanels.apply(this.context, this.dependents);
2311         }
2312     },
2313 
2314     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
2315 
2316     startInspecting: function()
2317     {
2318     },
2319 
2320     stopInspecting: function(object, cancelled)
2321     {
2322     },
2323 
2324     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
2325 
2326     search: function(text, reverse)
2327     {
2328     },
2329 
2330     /**
2331      * Retrieves the search options that this modules supports.
2332      * This is used by the search UI to present the proper options.
2333      */
2334     getSearchOptionsMenuItems: function()
2335     {
2336         return [
2337             Firebug.Search.searchOptionMenu("search.Case_Sensitive", "searchCaseSensitive")
2338         ];
2339     },
2340 
2341     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
2342 
2343     // Called when "Options" clicked. Return array of
2344     // {label: 'name', nol10n: true,  type: "checkbox", checked: <value>, command:function to set <value>}
2345     getOptionsMenuItems: function()
2346     {
2347         return null;
2348     },
2349 
2350     /*
2351      * Called by chrome.onContextMenu to build the context menu when this panel has focus.
2352      * See also FirebugRep for a similar function also called by onContextMenu
2353      * Extensions may monkey patch and chain off this call
2354      * @param object: the 'realObject', a model value, eg a DOM property
2355      * @param target: the HTML element clicked on.
2356      * @return an array of menu items.
2357      */
2358     getContextMenuItems: function(object, target)
2359     {
2360         return [];
2361     },
2362 
2363     getBreakOnMenuItems: function()
2364     {
2365         return [];
2366     },
2367 
2368     getEditor: function(target, value)
2369     {