1 /* See license.txt for terms of usage */
  2 
  3 /**
  4  * Firebug module can depend only on modules that don't use the 'Firebug' namespace.
  5  * So, be careful before you create a new dependency.
  6  */
  7 define([
  8     "firebug/lib/lib",
  9     "firebug/lib/object",
 10     "firebug/chrome/firefox",
 11     "firebug/chrome/chrome",
 12     "firebug/lib/domplate",
 13     "firebug/lib/options",
 14     "firebug/lib/locale",
 15     "firebug/lib/events",
 16     "firebug/lib/wrapper",
 17     "firebug/lib/url",
 18     "firebug/lib/css",
 19     "firebug/chrome/window",
 20     "firebug/lib/string",
 21     "firebug/lib/array",
 22     "firebug/lib/dom",
 23     "firebug/lib/http",
 24     "firebug/trace/traceListener",
 25     "firebug/console/commandLineExposed",
 26 ],
 27 function(FBL, Obj, Firefox, ChromeFactory, Domplate, Options, Locale, Events,
 28     Wrapper, Url, Css, Win, Str, Arr, Dom, Http, TraceListener, CommandLineExposed) {
 29 
 30 // ********************************************************************************************* //
 31 // Constants
 32 
 33 const Cc = Components.classes;
 34 const Ci = Components.interfaces;
 35 
 36 const nsISupports = Ci.nsISupports;
 37 
 38 const observerService = Cc["@mozilla.org/observer-service;1"].getService(Ci.nsIObserverService);
 39 const categoryManager = Cc["@mozilla.org/categorymanager;1"].getService(Ci.nsICategoryManager);
 40 const promptService = Cc["@mozilla.org/embedcomp/prompt-service;1"].getService(Ci.nsIPromptService);
 41 
 42 const versionURL = "chrome://firebug/content/branch.properties";
 43 
 44 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 45 
 46 const scriptBlockSize = 20;
 47 
 48 const PLACEMENT_NONE = 0;
 49 const PLACEMENT_INBROWSER = 1;
 50 const PLACEMENT_DETACHED = 2;
 51 const PLACEMENT_MINIMIZED = 3;
 52 
 53 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 54 
 55 var modules = [];
 56 var activeContexts = [];
 57 var activableModules = [];
 58 var panelTypes = [];
 59 var earlyRegPanelTypes = []; // See Firebug.registerPanelType for more info
 60 var reps = [];
 61 var defaultRep = null;
 62 var defaultFuncRep = null;
 63 var menuItemControllers = [];
 64 var panelTypeMap = {};
 65 
 66 // ********************************************************************************************* //
 67 
 68 //xxxHonza: we should use the existing Firebug object.
 69 if (window.Firebug)
 70 {
 71     // Stow the pre-load properties, add them back at the end
 72     var PreFirebug = {};
 73     var preFirebugKeys = Object.keys(Firebug);
 74     preFirebugKeys.forEach(function copyProps(key)
 75     {
 76         PreFirebug[key] = Firebug[key];
 77     });
 78 }
 79 
 80 /**
 81  * @class Represents the main Firebug application object. An instance of this object is
 82  * created for each browser window (browser.xul).
 83  */
 84 window.Firebug =
 85 {
 86     version: "1.12",
 87 
 88     dispatchName: "Firebug",
 89     modules: modules,
 90     panelTypes: panelTypes,
 91     earlyRegPanelTypes: earlyRegPanelTypes,
 92     uiListeners: [],
 93     reps: reps,
 94 
 95     stringCropLength: 50,
 96 
 97     isInitialized: false,
 98     isLoaded: false,
 99 
100     migrations: {},
101 
102     // Custom stylesheets registered by extensions.
103     stylesheets: [],
104 
105     // xxxHonza: hack, all "Firebug.Options" occurences should be replaced by "Options"
106     Options: Options,
107 
108     viewChrome: null,
109 
110     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
111     // Initialization
112 
113     initialize: function(chrome)
114     {
115         // This says how much time was necessary to load Firebug overlay (+ all script tags).
116         FBTrace.timeEnd("SCRIPTTAG_TIME");
117 
118         // Measure the entire Firebug initialiation time.
119         FBTrace.time("INITIALIZATION_TIME");
120 
121         Firebug.chrome = chrome;
122         Firebug.originalChrome = Firebug.chrome;
123 
124         if (FBTrace.sysout && (!FBL || !FBL.initialize))
125         {
126             FBTrace.sysout("Firebug is broken, FBL incomplete, if the last function is QI, " +
127                 "check lib.js:", FBL);
128         }
129         else if (FBTrace.DBG_INITIALIZE)
130         {
131             FBTrace.sysout("firebug.initialize FBL: " + FBL);
132         }
133 
134         if (window.FBL.legacyApiPatch)
135             window.FBL.legacyApiPatch(FBL, this, Firefox);
136 
137         // Till now all registered panels have been inserted into earlyRegPanelTypes.
138         var tempPanelTypes = earlyRegPanelTypes;
139         earlyRegPanelTypes = null;
140         Firebug.completeInitialize(tempPanelTypes);
141     },
142 
143     completeInitialize: function(tempPanelTypes)
144     {
145         if (FBL)
146             FBL.initialize();  // non require.js modules
147 
148         // Append early registered panels at the end.
149         panelTypes.push.apply(panelTypes, tempPanelTypes);
150 
151         // Firebug is getting option-updates from the connection so,
152         // do not register it again here (see issue 6035)
153         //Firebug.Options.addListener(this);
154 
155         this.isInitialized = true;
156 
157         Events.dispatch(modules, "initialize", []);
158 
159         // This is the final of Firebug initialization.
160         FBTrace.timeEnd("INITIALIZATION_TIME");
161     },
162 
163     sendLoadEvent: function()
164     {
165         this.isLoaded = true;
166 
167         var event = document.createEvent("Events");
168         event.initEvent("FirebugLoaded", true, false);
169 
170         // Send to the current window/scope (firebugFrame.xul)
171         window.document.dispatchEvent(event);
172 
173         // Send to the top window/scope (browser.xul)
174         if (top != window)
175             top.document.dispatchEvent(event);
176     },
177 
178     getVersion: function()
179     {
180         if (!this.fullVersion)
181             this.fullVersion = this.loadVersion(versionURL);
182 
183         return this.fullVersion;
184     },
185 
186     loadVersion: function(versionURL)
187     {
188         var content = Http.getResource(versionURL);
189         if (!content)
190             return "no content at "+versionURL;
191 
192         var m = /RELEASE=(.*)/.exec(content);
193         if (m)
194             var release = m[1];
195         else
196             return "no RELEASE in "+versionURL;
197 
198         m = /VERSION=(.*)/.exec(content);
199         if (m)
200             var version = m[1];
201         else
202             return "no VERSION in "+versionURL;
203 
204         return version+""+release;
205     },
206 
207     /**
208      *  Substitute strings in the UI, with fall back to en-US
209      */
210     internationalizeUI: function(doc) // TODO chrome.js
211     {
212         if (!doc)
213             return;
214 
215         if (FBTrace.DBG_INITIALIZE)
216             FBTrace.sysout("Firebug.internationalizeUI");
217 
218         var elements = doc.getElementsByClassName("fbInternational");
219         elements = Arr.cloneArray(elements);
220         var attributes = ["label", "tooltiptext", "aria-label"];
221         for (var i=0; i<elements.length; i++)
222         {
223             var element = elements[i];
224             Css.removeClass(elements[i], "fbInternational");
225             for (var j=0; j<attributes.length; j++)
226             {
227                 if (element.hasAttribute(attributes[j]))
228                     Locale.internationalize(element, attributes[j]);
229             }
230         }
231 
232         // Allow other modules to internationalize UI labels (called also for
233         // detached Firebug window).
234         Events.dispatch(modules, "internationalizeUI", [doc]);
235     },
236 
237     /**
238      * Called when the UI is ready to be initialized, once the panel browsers are loaded,
239      * but before any contexts are created.
240      */
241     initializeUI: function(detachArgs)
242     {
243         if (FBTrace.DBG_INITIALIZE)
244             FBTrace.sysout("firebug.initializeUI detachArgs:", detachArgs);
245 
246         Events.dispatch(menuItemControllers, "initialize", []);  // TODO chrome.js
247 
248         // In the case that the user opens firebug in a new window but then closes Firefox
249         // window, we don't get the quitApplicationGranted event (platform is still running)
250         // and we call shutdown (Firebug isDetached).
251         window.addEventListener('unload', shutdownFirebug, false);
252 
253         // Initial activation of registered panel types. All panel -> module dependencies
254         // should be defined now (in onActivationChange). Must be called after
255         // Firebug.TabWatcher is ready.
256         if (Firebug.PanelActivation)
257             Firebug.PanelActivation.activatePanelTypes(panelTypes);
258 
259         // Tell the modules the UI is up.
260         Events.dispatch(modules, "initializeUI", [detachArgs]);
261     },
262 
263     /**
264      * called in browser when Firefox closes and in externalMode when fbs gets
265      * quitApplicationGranted.
266      */
267     shutdown: function()
268     {
269         if (this.isShutdown)
270             return;
271 
272         this.isShutdown = true;
273 
274         this.shutdownUI();
275 
276         Events.dispatch(modules, "shutdown");
277 
278         this.Options.shutdown();
279         this.Options.removeListener(this);
280 
281         this.connection.disconnect();
282 
283         this.PanelActivation.deactivatePanelTypes(panelTypes);
284 
285         // Shutdown all registered extensions.
286         this.unregisterExtensions();
287 
288         if (FBTrace.DBG_OBSERVERS)
289         {
290             // import fbObserverService
291             Components.utils.import("resource://firebug/observer-service.js");
292             fbObserverService.traceStacksForTrack();
293         }
294 
295         if (FBTrace.DBG_INITIALIZE)
296             FBTrace.sysout("firebug.shutdown exited ");
297     },
298 
299     shutdownUI: function()  // TODO chrome.js
300     {
301         window.removeEventListener("unload", shutdownFirebug, false);
302 
303         Events.dispatch(modules, "disable", [Firebug.chrome]);
304     },
305 
306     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
307     // TODO this entire section to XULWindow
308 
309     // TODO XULWindow
310     getSuspended: function()
311     {
312         return Firebug.StartButton.getSuspended();
313     },
314 
315     // TODO XULWindow
316     setSuspended: function(value)
317     {
318         Firebug.StartButton.setSuspended(value);
319     },
320 
321     // TODO XULWindow IN detached "Activate Firebug for the current website"
322     toggleSuspend: function()
323     {
324         // getSuspended returns non-null value if Firebug is suspended.
325         if (this.getSuspended() || this.isDetached())
326         {
327             // Firebug is suspended now. Two possible actions have been executed:
328             // 1) Firebug UI is closed and the user clicked on the status bar icon in order to
329             //    show the UI and resume Firebug.
330             // 2) Firebug is detached, but suspended for the current page. The user clicked
331             //    either on the status bar icon or on an activation button that is displayed
332             //    within detached Firebug window.
333             this.toggleBar(true);
334         }
335         else
336         {
337             // The users wants to suspend Firebug, let's do it and pull down the visible UI.
338             // xxxHonza: the Firebug isn't suspended if detached and the user clicks on the
339             // status bar icon (the detached window should becoma blank displaying only
340             // the activation button).
341             this.suspend();
342 
343             // Close detached Firebug or
344             // show/hide Firebug UI according to the browser.showFirebug flag.
345             if (Firebug.isDetached())
346                 this.toggleDetachBar(false);
347             else
348                 this.syncBar();
349         }
350     },
351 
352     // dispatch suspendFirebug to all windows
353     suspend: function()
354     {
355         if (Firebug.rerun)
356             return;
357 
358         Firebug.suspendFirebug();
359     },
360 
361     // dispatch onSuspendFirebug to all modules
362     suspendFirebug: function()
363     {
364         var cancelSuspend = Events.dispatch2(activableModules, "onSuspendingFirebug", []);
365         if (cancelSuspend)
366             return;
367 
368         this.setSuspended("suspending");
369 
370         // TODO no context arg
371         var cancelSuspend = Events.dispatch2(activableModules, "onSuspendFirebug",
372             [Firebug.currentContext]);
373 
374         if (cancelSuspend)
375             Firebug.resume();
376         else
377             this.setSuspended("suspended");
378     },
379 
380     resume: function()
381     {
382         Firebug.resumeFirebug();
383     },
384 
385     resumeFirebug: function()  // dispatch onResumeFirebug to all modules
386     {
387         this.setSuspended("resuming");
388 
389         // TODO no context arg
390         Events.dispatch(activableModules, 'onResumeFirebug', [Firebug.currentContext]);
391         this.setSuspended(null);
392     },
393 
394     getURLsForAllActiveContexts: function()
395     {
396         var contextURLSet = [];
397 
398         // create a list of all unique activeContexts
399         Firebug.connection.eachContext(function createActiveContextList(context)
400         {
401             if (FBTrace.DBG_WINDOWS)
402                 FBTrace.sysout("context " + context.getName());
403 
404             try
405             {
406                 var cw = context.window;
407                 if (cw)
408                 {
409                     var url;
410                     if (cw.closed)
411                     {
412                         url = "about:closed";
413                     }
414                     else
415                     {
416                         if ("location" in cw)
417                             url = cw.location.toString();
418                         else
419                             url = context.getName();
420                     }
421 
422                     if (url)
423                     {
424                         if (contextURLSet.indexOf(url) == -1)
425                             contextURLSet.push(url);
426                     }
427                 }
428             }
429             catch(e)
430             {
431                 if (FBTrace.DBG_ERRORS)
432                     FBTrace.sysout("firebug.getURLsForAllActiveContexts could not get " +
433                         "window.location for a context", e);
434             }
435         });
436 
437         if (FBTrace.DBG_ACTIVATION)
438             FBTrace.sysout("active contexts urls " + contextURLSet.length);
439 
440         return contextURLSet;
441     },
442 
443     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
444     // Registration
445 
446     /**
447      * Set a default value for a preference into the firebug preferences list.
448      *
449      * @param name preference name, possibly dot segmented, will be stored under
450      *      extensions.firebug.<name>
451      * @param value default value of preference
452      * @return true if default set, else false
453      */
454     registerPreference: function(name, value)
455     {
456         Firebug.Options.register(name, value);
457     },
458 
459     registerModule: function()
460     {
461         modules.push.apply(modules, arguments);
462 
463         // Fire the initialize event for modules that are registered later.
464         if (Firebug.isInitialized)
465             Events.dispatch(arguments, "initialize", []);
466 
467         if (FBTrace.DBG_REGISTRATION)
468         {
469             for (var i = 0; i < arguments.length; ++i)
470                 FBTrace.sysout("registerModule "+arguments[i].dispatchName);
471         }
472     },
473 
474     unregisterModule: function()
475     {
476         for (var i = 0; i < arguments.length; ++i)
477             Arr.remove(modules, arguments[i]);
478 
479         // Fire shutdown if module was unregistered dynamically (not on Firebug shutdown).
480         if (!Firebug.isShutdown)
481             Events.dispatch(arguments, "shutdown", []);
482     },
483 
484     registerActivableModule: function()
485     {
486         activableModules.push.apply(activableModules, arguments);
487         this.registerModule.apply(this, arguments);
488     },
489 
490     registerUIListener: function()
491     {
492         for (var j = 0; j < arguments.length; j++)
493             Firebug.uiListeners.push(arguments[j]);
494     },
495 
496     unregisterUIListener: function()
497     {
498         for (var i = 0; i < arguments.length; ++i)
499             Arr.remove(Firebug.uiListeners, arguments[i]);
500     },
501 
502     registerPanel: function()
503     {
504         for (var i=0; i<arguments.length; ++i)
505         {
506             var panelName = arguments[i].prototype.name;
507             var panel = panelTypeMap[panelName];
508             if (panel)
509             {
510                 if (FBTrace.DBG_ERRORS)
511                 {
512                     FBTrace.sysout("firebug.registerPanel; ERROR a panel with the same " +
513                         "ID already registered! " + panelName);
514                 }
515             }
516         }
517 
518         // In order to keep built in panels (like Console, Script...) be the first one
519         // and insert all panels coming from extension at the end, catch any early registered
520         // panel (i.e. before FBL.initialize is called, such as YSlow) in a temp array
521         // that is appended at the end as soon as FBL.initialize is called.
522         if (earlyRegPanelTypes)
523             earlyRegPanelTypes.push.apply(earlyRegPanelTypes, arguments);
524         else
525             panelTypes.push.apply(panelTypes, arguments);
526 
527         for (var i=0; i<arguments.length; ++i)
528             panelTypeMap[arguments[i].prototype.name] = arguments[i];
529 
530         if (FBTrace.DBG_REGISTRATION)
531         {
532             for (var i=0; i<arguments.length; ++i)
533                 FBTrace.sysout("registerPanel " + arguments[i].prototype.name);
534         }
535 
536         // If Firebug is not initialized yet the UI will be updated automatically soon.
537         if (!this.isInitialized)
538             return;
539 
540         Firebug.chrome.syncMainPanels();
541         Firebug.chrome.syncSidePanels();
542     },
543 
544     unregisterPanel: function(panelType)
545     {
546         var panelName = panelType ? panelType.prototype.name : null;
547 
548         if (FBTrace.DBG_REGISTRATION)
549         {
550             FBTrace.sysout("firebug.unregisterPanel: " +
551                 (panelName ? panelName : "Undefined panelType"));
552         }
553 
554         // Remove all instance of the panel.
555         Firebug.connection.eachContext(function (context)
556         {
557             // An empty state can be probably used at this moment since
558             // we are unregistering the panel anyway.
559             var state = {}; //context.browser.persistedState;
560             context.removePanel(panelType, state);
561         });
562 
563         // Now remove panel-type itself.
564         for (var i=0; i<panelTypes.length; i++)
565         {
566             if (panelTypes[i] == panelType)
567             {
568                 panelTypes.splice(i, 1);
569                 break;
570             }
571         }
572 
573         delete panelTypeMap[panelType.prototype.name];
574 
575         // We don't have to update Firebug UI if it's just closing.
576         if (this.isShutdown)
577             return;
578 
579         // Make sure another panel is selected if the current one is has been removed.
580         var panel = this.chrome.getSelectedPanel();
581         if (panel && panel.name == panelName)
582             Firebug.chrome.selectPanel("html");
583 
584         // The panel tab must be removed from the UI.
585         Firebug.chrome.syncMainPanels();
586         Firebug.chrome.syncSidePanels();
587     },
588 
589     registerRep: function()
590     {
591         reps.push.apply(reps, arguments);
592     },
593 
594     unregisterRep: function()
595     {
596         for (var i = 0; i < arguments.length; ++i)
597             Arr.remove(reps, arguments[i]);
598     },
599 
600     setDefaultReps: function(funcRep, rep)
601     {
602         defaultRep = rep;
603         defaultFuncRep = funcRep;
604     },
605 
606     registerStringBundle: function(bundleURI)
607     {
608         Locale.registerStringBundle(bundleURI);
609     },
610 
611     unregisterStringBundle: function(bundleURI)
612     {
613         // xxxHonza: TODO:
614     },
615 
616     /**
617      * Allows registering of custom stylesheet coming from extension. The stylesheet is then
618      * used automatially thorough Firebug UI.
619      * @param {Object} styleURI URI of the stylesheet.
620      */
621     registerStylesheet: function(styleURI)
622     {
623         this.stylesheets.push(styleURI);
624 
625         // Append the stylesheet into the UI if Firebug is already loaded
626         if (this.isLoaded)
627             Firebug.chrome.appendStylesheet(styleURI);
628 
629         if (FBTrace.DBG_REGISTRATION)
630             FBTrace.sysout("registerStylesheet " + styleURI);
631     },
632 
633     unregisterStylesheet: function(styleURI)
634     {
635         // xxxHonza: TODO
636     },
637 
638     registerMenuItem: function(menuItemController)
639     {
640         FBTrace.sysout("Firebug.registerMenuItem");
641         menuItemControllers.push(menuItemController);
642     },
643 
644     registerTracePrefix: function(prefix, type, removePrefix, styleURI)
645     {
646         var listener = Firebug.TraceModule.getListenerByPrefix(prefix);
647         if (listener && FBTrace.DBG_ERRORS)
648         {
649             FBTrace.sysout("firebug.registerTracePrefix; ERROR " +
650                 "there is already such prefix registered!");
651             return;
652         }
653 
654         listener = new TraceListener(prefix, type, removePrefix, styleURI);
655         Firebug.TraceModule.addListener(listener);
656     },
657 
658     unregisterTracePrefix: function(prefix)
659     {
660         var listener = Firebug.TraceModule.getListenerByPrefix(prefix);
661         if (listener)
662             Firebug.TraceModule.removeListener(listener);
663     },
664 
665     registerCommand: function(name, config)
666     {
667         return CommandLineExposed.registerCommand(name, config);
668     },
669 
670     unregistereCommand: function(name)
671     {
672         return CommandLineExposed.unregisterCommand(name);
673     },
674 
675     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
676     // Options
677 
678     getPref: function()
679     {
680         // TODO deprecated
681         return Options.getPref.apply(Firebug.Options, arguments);
682     },
683 
684     setPref: function()
685     {
686         // TODO deprecated
687         return Options.setPref.apply(Firebug.Options, arguments);
688     },
689 
690     clearPref: function()
691     {
692         // TODO deprecated
693         return Options.clearPref.apply(Options, arguments);
694     },
695 
696     prefDomain: "extensions.firebug",
697 
698     updateOption: function(name, value)
699     {
700         // fbtest changes options which change prefs which trigger updates in fbtrace
701         if (!Firebug.chrome)
702             return;
703 
704         // Distribute to the current chrome.
705         Firebug.chrome.updateOption(name, value);
706 
707         // If Firebug is detached distribute also into the in-browser chrome.
708         if (Firebug.chrome != Firebug.originalChrome)
709             Firebug.originalChrome.updateOption(name, value);
710 
711         Events.dispatch(Firebug.modules, "updateOption", [name, value]);
712     },
713 
714     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
715 
716     shouldIgnore: function(objectChromeView)
717     {
718         if (objectChromeView)
719         {
720             var contentView = Wrapper.unwrapObject(objectChromeView);
721             return (contentView && contentView.firebugIgnore);
722         }
723         // else don't ignore things we don't understand
724     },
725 
726     setIgnored: function(objectChromeView)
727     {
728         if (objectChromeView)
729         {
730             var contentView = Wrapper.unwrapObject(objectChromeView);
731             if (contentView)
732                 contentView.firebugIgnore = true;
733         }
734     },
735 
736     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
737     // Browser Bottom Bar
738 
739     // TODO XULWindow
740     showBar: function(show)
741     {
742         var browser = Firefox.getCurrentBrowser();
743 
744         if (FBTrace.DBG_WINDOWS || FBTrace.DBG_ACTIVATION)
745             FBTrace.sysout("showBar("+show+") for browser "+browser.currentURI.spec+
746                 " Firebug.currentContext "+Firebug.currentContext);
747 
748         Firebug.chrome.toggleOpen(show);
749 
750         if (!show)
751             Firebug.Inspector.inspectNode(null);
752 
753         //xxxHonza: should be removed.
754         Events.dispatch(Firebug.uiListeners, show ? "showUI" : "hideUI",
755             [browser, Firebug.currentContext]);
756 
757         // Sync panel state after the showUI event is dispatched. syncPanel method calls
758         // Panel.show method, which expects the active context to be already registered.
759         if (show)
760             Firebug.chrome.syncPanel();
761         else
762             Firebug.chrome.selectPanel(); // select null causes hide() on selected
763 
764         Firebug.StartButton.resetTooltip();
765     },
766 
767     closeFirebug: function(userCommands)  // this is really deactivate
768     {
769         if (!Firebug.currentContext)
770             return;
771 
772         // It looks like FBTest is calling Firebug.Activation.clearAnnotations()
773         // when there is no current context.
774         //throw new Error("closeFirebug ERROR: no Firebug.currentContext ");
775 
776         // Focus the browser window again
777         Firebug.currentContext.window.focus();
778 
779         Firebug.connection.closeContext(Firebug.currentContext, userCommands);
780         Firebug.StartButton.resetTooltip();
781     },
782 
783     /**
784      * Primary function to activate or minimize firebug. Used by
785      * <ol>
786      * <li>the status bar icon click action</li>
787      * <li>the activation button (within Firebug.xul) click action</li>
788      * </ol>
789      * @param forceOpen: don't minimize, stay open if open.
790      * @param panelName: eg 'script', to select a specific panel.
791      */
792     toggleBar: function(forceOpen, panelName)
793     {
794         if (panelName)
795             Firebug.chrome.selectPanel(panelName);
796         // if is deactivated.
797         if (!Firebug.currentContext)
798         {
799             var context = Firebug.getContext();
800             // Be sure the UI is open for a newly created context.
801             forceOpen = true;
802         }
803 
804         if (Firebug.isDetached())
805         {
806             //in detached mode, two possibilities exist, the firebug windows is 
807             // the active window of the user or no.
808             if ( !Firebug.chrome.hasFocus() || forceOpen)
809                 Firebug.chrome.focus();
810             else
811                 Firebug.minimizeBar();
812         }
813         // toggle minimize
814         else if (Firebug.isMinimized())
815         {
816             // be careful, unMinimize func always sets placement to
817             // inbrowser first then unminimizes. when we want to
818             // unminimize in detached mode must call detachBar func.
819             if (Firebug.framePosition == "detached")
820                 this.detachBar();
821             else
822                 Firebug.unMinimize();
823         }
824         // else isInBrowser
825         else if (!forceOpen)
826         {
827             Firebug.minimizeBar();
828         }
829 
830         return true;
831     },
832 
833     /**
834      * Get context for the current website
835      */
836     getContext: function()
837     {
838         var webApp = Firebug.connection.getCurrentSelectedWebApp();
839         var context = Firebug.connection.getContextByWebApp(webApp);
840         // we are not debugging the selected tab.
841         if (!context)
842         {
843             context = Firebug.connection.getOrCreateContextByWebApp(webApp);
844         }
845         return context;
846     },
847 
848     /**
849      * Primary function to re-show firebug due to visiting active site.
850      * Unlike toggleBar, we are trying to obey the current placement, not change it.
851      */
852     showContext: function(browser, context)
853     {
854         // user wants detached but we are not yet
855         if (Firebug.framePosition == "detached" && !Firebug.isDetached())
856         {
857             if (context && !Firebug.isMinimized()) // don't detach if it's minimized 2067
858                 this.detachBar();  //   the placement will be set once the external window opens
859             else  // just make sure we are not showing
860                 this.showBar(false);
861         }
862         else if (Firebug.openMinimized() && !Firebug.isMinimized())
863             this.minimizeBar();
864         else if (Firebug.isMinimized())
865             this.showBar(false);  // don't show, we are minimized
866         else if (Firebug.isDetached())
867             Firebug.chrome.syncResumeBox(context);
868         else  // inBrowser
869             this.showBar(context?true:false);
870     },
871 
872     minimizeBar: function()  // just pull down the UI, but don't deactivate the context
873     {
874         if (Firebug.isDetached())
875         {
876             // TODO reattach
877 
878             // window is closing in detached mode
879             var parent = this.getFirebugFrameParent();
880             if (parent)
881             {
882                 parent.exportFirebug();
883                 parent.close();
884             }
885 
886             Firebug.setPlacement("minimized");
887             this.showBar(false);
888             Firebug.chrome.focus();
889         }
890         else // inBrowser -> minimized
891         {
892             Firebug.setPlacement("minimized");
893             this.showBar(false);
894 
895             // Focus the browser window again
896             if (Firebug.currentContext)
897                 Firebug.currentContext.window.focus();
898         }
899     },
900 
901     unMinimize: function()
902     {
903         Firebug.setPlacement("inBrowser");
904         Firebug.showBar(true);
905     },
906 
907     onShowDetachTooltip: function(tooltip)
908     {
909         tooltip.label = Firebug.isDetached() ? Locale.$STR("firebug.AttachFirebug") :
910             Locale.$STR("firebug.DetachFirebug");
911         return true;
912     },
913 
914     /**
915      * function to switch between detached and inbrowser modes.
916      * @param forceOpen: should not be closed, stay open if open or open it.
917      * @param reopenInBrowser: switch from detahced to inbrowser mode.
918      */
919     toggleDetachBar: function(forceOpen, reopenInBrowser)
920     {
921         //detached -> inbrowser
922         if (!forceOpen && Firebug.isDetached())
923         {
924             var parent = this.getFirebugFrameParent();
925             parent.exportFirebug();
926             parent.close();
927 
928             if (reopenInBrowser)
929             {
930                 // Is Firebug deactivated ? if yes, should be
931                 // activated at first, then unminimize.
932                 if (!Firebug.currentContext)
933                 {
934                     var context = Firebug.getContext();
935                 }
936                 Firebug.unMinimize();
937             }
938             else
939             {
940                 Firebug.minimizeBar();
941             }
942 
943             Firebug.chrome.syncPositionPref();
944         }
945         // is minimized now but the last time that has been closed, was in detached mode,
946         // so it should be returned to in browser mode because the user has pressed CTRL+F12.
947         else if (Firebug.framePosition == "detached" && Firebug.isMinimized())
948         {
949             Firebug.unMinimize();
950             Firebug.chrome.syncPositionPref();
951         }
952         // else is in browser mode, then switch to detached mode.
953         else
954         {
955             this.detachBar();
956         }
957     },
958 
959     closeDetachedWindow: function(userCommands)
960     {
961         Firebug.showBar(false);
962 
963         if (Firebug.currentContext)
964             ToolInterface.browser.closeContext(Firebug.currentContext, userCommands);
965 
966         // else the user closed Firebug external window while not looking at
967         // a debugged web page.
968         Firebug.StartButton.resetTooltip();
969     },
970 
971     detachBar: function()
972     {
973         if (Firebug.isDetached())  // can be set true attachBrowser
974         {
975             Firebug.chrome.focus();
976             return null;
977         }
978 
979         if (Firebug.chrome.waitingForDetach)
980             return null;
981 
982         Firebug.chrome.waitingForDetach = true;
983         Firebug.chrome.toggleOpen(false);  // don't show in browser.xul now
984 
985         if (FBTrace.DBG_ACTIVATION)
986         {
987             FBTrace.sysout("Firebug.detachBar opening firebug.xul for context " +
988                 Firebug.currentContext.getName() );
989         }
990 
991         Firebug.chrome.syncPositionPref("detached");
992 
993         return Firefox.openWindow("Firebug",
994             "chrome://firebug/content/firefox/firebug.xul",
995             "", {});
996     },
997 
998     // show firebug if we should
999     syncBar: function()
1000     {
1001         var browser = Firefox.getCurrentBrowser();
1002 
1003         // implicitly this is operating in the chrome of browser.xul
1004         this.showBar(browser && browser.showFirebug);
1005     },
1006 
1007     toggleCommandLine: function(showCommandEditor)
1008     {
1009         Options.set("commandEditor", showCommandEditor);
1010     },
1011 
1012     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
1013 
1014     /**
1015      * Returns parent of the firebugFrame.xul frame. The actual parent depends on whether
1016      * Firebug is attached or detached.
1017      *
1018      * attached -> browser.xul
1019      * detached -> firebug.xul
1020      */
1021     getFirebugFrameParent: function()
1022     {
1023         // We need firebug.xul in case of detached state. So, don't use 'top' since
1024         // it references browser.xul
1025         return Firebug.chrome.window.parent;
1026     },
1027 
1028     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
1029     // deprecated
1030 
1031     resetAllOptions: function(confirm)
1032     {
1033         if (confirm)
1034         {
1035             var promptService = Cc["@mozilla.org/embedcomp/prompt-service;1"].
1036                 getService(Ci.nsIPromptService);
1037 
1038             // Do not reset options if the user changed its mind.
1039             if (!promptService.confirm(null, Locale.$STR("Firebug"),
1040                 Locale.$STR("confirmation.Reset_All_Firebug_Options")))
1041             {
1042                 return;
1043             }
1044         }
1045 
1046         // Dispatch to non-module objects.
1047         Options.resetAllOptions(confirm);
1048 
1049         // Dispatch to all modules so that additional settings can be reset.
1050         Events.dispatch(modules, "resetAllOptions", []);
1051     },
1052 
1053     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
1054     // Panels
1055 
1056     getPanelType: function(panelName)
1057     {
1058         if (panelTypeMap.hasOwnProperty(panelName))
1059             return panelTypeMap[panelName];
1060         else
1061             return null;
1062     },
1063 
1064     getPanelTitle: function(panelType)
1065     {
1066         if (!panelType)
1067             return null;
1068 
1069         return panelType.prototype.title ? panelType.prototype.title
1070             : Locale.$STR("Panel-"+panelType.prototype.name);
1071     },
1072 
1073     getPanelTooltip: function(panelType)
1074     {
1075         var tooltip = panelType.prototype.tooltip ? panelType.prototype.tooltip
1076             : Locale.$STR("panel.tip."+panelType.prototype.name);
1077         return tooltip != panelType.prototype.name ? tooltip : this.getPanelTitle(panelType);
1078     },
1079 
1080     getMainPanelTypes: function(context)
1081     {
1082         var resultTypes = [];
1083 
1084         for (var i = 0; i < panelTypes.length; ++i)
1085         {
1086             var panelType = panelTypes[i];
1087             if (!panelType.prototype.parentPanel)
1088                 resultTypes.push(panelType);
1089         }
1090 
1091         if (context.panelTypes)
1092         {
1093             for (var i = 0; i < context.panelTypes.length; ++i)
1094             {
1095                 var panelType = context.panelTypes[i];
1096                 if (!panelType.prototype.parentPanel)
1097                     resultTypes.push(panelType);
1098             }
1099         }
1100 
1101         resultTypes.sort(function(a, b)
1102         {
1103             return a.prototype.order < b.prototype.order ? -1 : 1;
1104         });
1105 
1106         return resultTypes;
1107     },
1108 
1109     getSidePanelTypes: function(context, mainPanel)
1110     {
1111         if (!mainPanel)
1112             return [];
1113 
1114         var resultTypes = [];
1115 
1116         for (var i = 0; i < panelTypes.length; ++i)
1117         {
1118             var panelType = panelTypes[i];
1119 
1120             if (panelType.prototype.parentPanel &&
1121                 (panelType.prototype.parentPanel == mainPanel.name))
1122             {
1123                 resultTypes.push(panelType);
1124             }
1125         }
1126 
1127         if (context.panelTypes)
1128         {
1129             for (var i = 0; i < context.panelTypes.length; ++i)
1130             {
1131                 var panelType = context.panelTypes[i];
1132                 if (panelType.prototype.parentPanel == mainPanel.name)
1133                     resultTypes.push(panelType);
1134             }
1135         }
1136 
1137         resultTypes.sort(function(a, b)
1138         {
1139             return a.prototype.order < b.prototype.order ? -1 : 1;
1140         });
1141 
1142         return resultTypes;
1143     },
1144 
1145     /**
1146      * Returns all panel types, whose activation can be toggled
1147      * @returns {Object} Activable panel types
1148      */
1149     getActivablePanelTypes: function()
1150     {
1151         var activablePanelTypes = [];
1152         for (var i = 0; i < panelTypes.length; ++i)
1153         {
1154             if (this.PanelActivation.isPanelActivable(panelTypes[i]))
1155                 activablePanelTypes.push(panelTypes[i]);
1156         }
1157 
1158         return activablePanelTypes;
1159     },
1160 
1161     /**
1162      * Gets an object containing the state of the panel from the last time
1163      * it was displayed before one or more page reloads.
1164      * The 'null' return here is a too-subtle signal to the panel code in bindings.xml.
1165      * Note that panel.context may not have a persistedState, but in addition the persisted
1166      * state for panel.name may be null.
1167      */
1168     getPanelState: function(panel)
1169     {
1170         var persistedState = panel.context.persistedState;
1171         if (!persistedState || !persistedState.panelState)
1172             return null;
1173 
1174         return persistedState.panelState[panel.name];
1175     },
1176 
1177     showPanel: function(browser, panel)
1178     {
1179         // The panel may be null
1180         Events.dispatch(modules, "showPanel", [browser, panel]);
1181     },
1182 
1183     showSidePanel: function(browser, sidePanel)
1184     {
1185         Events.dispatch(modules, "showSidePanel", [browser, sidePanel]);
1186     },
1187 
1188     eachPanel: function(callback)
1189     {
1190         Firebug.connection.eachContext(function iteratePanels(context)
1191         {
1192             var rc = context.eachPanelInContext(callback);
1193             if (rc)
1194                 return rc;
1195         });
1196     },
1197 
1198     dispatchToPanels: function(fName, args)
1199     {
1200         Firebug.eachPanel( function dispatchToPanel(panel)
1201         {
1202             if (panel[fName])
1203                 return panel[fName].apply(panel,args);
1204         });
1205     },
1206 
1207     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
1208 
1209     dispatch: function(listeners, eventId, args)
1210     {
1211         Events.dispatch(listeners, eventId, args);
1212     },
1213 
1214     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
1215     // URL mapping
1216 
1217     getObjectByURL: function(context, url)
1218     {
1219         for (var i = 0; i < modules.length; ++i)
1220         {
1221             var object = modules[i].getObjectByURL(context, url);
1222             if (object)
1223                 return object;
1224         }
1225     },
1226 
1227     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
1228     // Reps
1229 
1230     getRep: function(object, context)
1231     {
1232         var type = typeof(object);
1233         if (type == 'object' && object instanceof String)
1234             type = 'string';
1235 
1236         for (var i = 0; i < reps.length; ++i)
1237         {
1238             var rep = reps[i];
1239             try
1240             {
1241                 if (rep.supportsObject(object, type, (context?context:Firebug.currentContext) ))
1242                 {
1243                     //if (FBTrace.DBG_DOM)
1244                     //    FBTrace.sysout("getRep type: "+type+" object: "+object, rep);
1245                     return rep;
1246                 }
1247             }
1248             catch (exc)
1249             {
1250                 if (FBTrace.DBG_ERRORS)
1251                 {
1252                     FBTrace.sysout("firebug.getRep FAILS: "+ exc, exc);
1253                     FBTrace.sysout("firebug.getRep reps["+i+"/"+reps.length+"]: "+
1254                         (typeof(reps[i])), reps[i]);
1255                 }
1256             }
1257         }
1258 
1259         //if (FBTrace.DBG_DOM)
1260         //    FBTrace.sysout("getRep default type: "+type+" object: "+object, rep);
1261 
1262         return (type == "function") ? defaultFuncRep : defaultRep;
1263     },
1264 
1265     getRepObject: function(node)
1266     {
1267         var target = null;
1268         for (var child = node; child; child = child.parentNode)
1269         {
1270             if (Css.hasClass(child, "repTarget"))
1271                 target = child;
1272 
1273             if (child.repObject != null)
1274             {
1275                 if (!target && Css.hasClass(child, "repIgnore"))
1276                     break;
1277                 else
1278                     return child.repObject;
1279             }
1280         }
1281     },
1282 
1283     /**
1284      * The child node that has a repObject
1285      */
1286     getRepNode: function(node)
1287     {
1288         for (var child = node; child; child = child.parentNode)
1289         {
1290             if (child.repObject != null)
1291                 return child;
1292         }
1293     },
1294 
1295     getElementByRepObject: function(element, object)
1296     {
1297         for (var child = element.firstChild; child; child = child.nextSibling)
1298         {
1299             if (child.repObject === object)
1300                 return child;
1301         }
1302     },
1303 
1304     /**
1305      * Takes an element from a panel document and finds the owning panel.
1306      */
1307     getElementPanel: function(element)
1308     {
1309         for (; element; element = element.parentNode)
1310         {
1311             if (element.ownerPanel)
1312                 return element.ownerPanel;
1313         }
1314     },
1315 
1316     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
1317 
1318     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
1319     // nsISupports
1320 
1321     QueryInterface : function(iid)
1322     {
1323         if (iid.equals(nsISupports))
1324         {
1325             return this;
1326         }
1327 
1328         throw Components.results.NS_NOINTERFACE;
1329     },
1330 
1331     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
1332     // Placement
1333 
1334     isDetached: function()
1335     {
1336         return Firebug.placement == PLACEMENT_DETACHED;
1337     },
1338 
1339     isMinimized: function()
1340     {
1341         return Firebug.placement == PLACEMENT_MINIMIZED;
1342     },
1343 
1344     isInBrowser: function()
1345     {
1346         return Firebug.placement == PLACEMENT_INBROWSER;
1347     },
1348 
1349     placements: ["none", "inBrowser", "detached", "minimized"],
1350 
1351     placement: 1,
1352 
1353     setPlacement: function(toPlacement)
1354     {
1355         // TODO : This should probably be an event so others can link into this
1356         Firebug.chrome.$("fbSearchBox").hideOptions();
1357 
1358         if (FBTrace.DBG_ACTIVATION)
1359             FBTrace.sysout("Firebug.setPlacement from " + Firebug.getPlacement() + " to " +
1360                 toPlacement + " with chrome " + Firebug.chrome.window.location);
1361 
1362         for (var i=0; i<Firebug.placements.length; i++)
1363         {
1364             if (toPlacement == Firebug.placements[i])
1365             {
1366                 if (Firebug.placement != i) // then we are changing the value
1367                 {
1368                     Firebug.placement = i;
1369                     delete Firebug.previousPlacement;
1370                     Options.set("previousPlacement", Firebug.placement);
1371                     Firebug.StartButton.resetTooltip();
1372                 }
1373                 return Firebug.placement;
1374             }
1375         }
1376         throw new Error("Firebug.setPlacement cannot match "+toPlacement+" as a placement");
1377     },
1378 
1379     getPlacement: function()
1380     {
1381         return Firebug.placements[Firebug.placement];
1382     },
1383 
1384     openMinimized: function()
1385     {
1386         if (!Firebug.previousPlacement)
1387             Firebug.previousPlacement = Options.get("previousPlacement");
1388 
1389         return (Firebug.previousPlacement && (Firebug.previousPlacement == PLACEMENT_MINIMIZED) )
1390     },
1391 
1392     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
1393     // Firebug.TabWatcher Listener
1394 
1395     getContextType: function()
1396     {
1397         return Firebug.TabContext;
1398     },
1399 
1400     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
1401 
1402     /**
1403      * This method syncs the UI to a context
1404      * @param context to become the active and visible context
1405      */
1406     selectContext: function(context)
1407     {
1408         this.showContext(context.browser, context);
1409     },
1410 
1411     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
1412 
1413     focusBrowserTab: function(win)    // TODO move to FBL
1414     {
1415         Firefox.selectTabByWindow(win);
1416         this.chrome.focus();
1417     },
1418 
1419     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
1420     // FBTest
1421 
1422     // Expose our test list to the FBTest console for automated testing.
1423     onGetTestList: function(testLists)
1424     {
1425         testLists.push({
1426             extension: "Firebug",
1427             testListURL: "http://getfirebug.com/tests/head/firebug.html"
1428         });
1429     }
1430 };
1431 
1432 // ********************************************************************************************* //
1433 // API for Greasemonkey, Jetpack and other Firefox extensions
1434 
1435 /**
1436  * @param global wrapped up global: outer window or sandbox
1437  * @return a |console| object for the window
1438  */
1439 Firebug.getConsoleByGlobal = function getConsoleByGlobal(global)
1440 {
1441     try
1442     {
1443         var context = Firebug.connection.getContextByWindow(global);
1444         if (context)
1445         {
1446             var handler = Firebug.Console.injector.getConsoleHandler(context, global);
1447 
1448             if (!handler)
1449                 handler = Firebug.Console.isReadyElsePreparing(context, global);;
1450 
1451             if (handler)
1452             {
1453                 FBTrace.sysout("Firebug.getConsoleByGlobal " + handler.console + " for " +
1454                     context.getName(), handler);
1455 
1456                 return handler.console;
1457             }
1458 
1459             if (FBTrace.DBG_ERRORS)
1460                 FBTrace.sysout("Firebug.getConsoleByGlobal FAILS, no handler for global " +
1461                     global + " " + Win.safeGetWindowLocation(global), global);
1462         }
1463         else
1464         {
1465             if (FBTrace.DBG_ERRORS)
1466                 FBTrace.sysout("Firebug.getConsoleByGlobal FAILS, no context for global " +
1467                     global, global);
1468         }
1469     }
1470     catch (exc)
1471     {
1472         if (FBTrace.DBG_ERRORS)
1473             FBTrace.sysout("Firebug.getConsoleByGlobal FAILS " + exc, exc);
1474     }
1475 }
1476 
1477 // ********************************************************************************************* //
1478 
1479 /**
1480  * Support for listeners registration. This object is also extended by Firebug.Module,
1481  * so all modules supports listening automatically. Note that an array of listeners is
1482  * created for each intance of a module within the initialize method. Thus all derived
1483  * module classes must ensure that the Firebug.Module.initialize method is called for the
1484  * super class.
1485  */
1486 Firebug.Listener = function()
1487 {
1488     // The array is created when the first listeners is added.
1489     // It can't be created here since derived objects would share
1490     // the same array.
1491     this.fbListeners = null;
1492 }
1493 Firebug.Listener.prototype =
1494 {
1495     addListener: function(listener)
1496     {
1497         if (!listener)
1498         {
1499             if (FBTrace.DBG_ERRORS)
1500                 FBTrace.sysout("firebug.Listener.addListener; ERROR null listener registered.");
1501             return;
1502         }
1503 
1504         // Delay the creation until the objects are created so 'this' causes new array
1505         // for this object (e.g. module, panel, etc.)
1506         if (!this.fbListeners)
1507             this.fbListeners = [];
1508 
1509         this.fbListeners.push(listener);
1510     },
1511 
1512     removeListener: function(listener)
1513     {
1514         // if this.fbListeners is null, remove is being called with no add
1515         Arr.remove(this.fbListeners, listener);
1516     }
1517 };
1518 
1519 // ********************************************************************************************* //
1520 
1521 /**
1522  * @module Base class for all modules. Every derived module object must be registered using
1523  * <code>Firebug.registerModule</code> method. There is always one instance of a module object
1524  * per browser window.
1525  */
1526 Firebug.Module = Obj.extend(new Firebug.Listener(),
1527 /** @lends Firebug.Module */
1528 {
1529     /**
1530      * Called by Firebug when Firefox window is opened.
1531      */
1532     initialize: function()
1533     {
1534     },
1535 
1536     /**
1537      * Called when the UI is ready for context creation.
1538      * Used by chromebug; normally FrameProgressListener events trigger UI synchronization,
1539      * this event allows sync without progress events.
1540      */
1541     initializeUI: function(detachArgs)
1542     {
1543     },
1544 
1545     /**
1546      * Called by Firebug when Firefox window is closed.
1547      */
1548     shutdown: function()
1549     {
1550     },
1551 
1552     /**
1553      * Called when a new context is created but before the page is loaded.
1554      */
1555     initContext: function(context, persistedState)
1556     {
1557     },
1558 
1559     /**
1560      * Called when a context is destroyed. Module may store info on persistedState
1561      * for reloaded pages.
1562      */
1563     destroyContext: function(context, persistedState)
1564     {
1565     },
1566 
1567     /**
1568      * Called when attaching to a window (top-level or frame).
1569      */
1570     watchWindow: function(context, win)
1571     {
1572     },
1573 
1574     /**
1575      * Called when unwatching a window (top-level or frame).
1576      */
1577     unwatchWindow: function(context, win)
1578     {
1579     },
1580 
1581     // Called when a FF tab is create or activated (user changes FF tab)
1582     // Called after context is created or with context == null (to abort?)
1583     showContext: function(browser, context)
1584     {
1585     },
1586 
1587     /**
1588      * Called after a context's page gets DOMContentLoaded
1589      */
1590     loadedContext: function(context)
1591     {
1592     },
1593 
1594     /*
1595      * After "onSelectingPanel", a panel has been selected but is not yet visible
1596      * @param browser a tab's browser element
1597      * @param panel selectet panel OR null
1598      */
1599     showPanel: function(browser, panel)
1600     {
1601     },
1602 
1603     showSidePanel: function(browser, sidePanel)
1604     {
1605     },
1606 
1607     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
1608 
1609     updateOption: function(name, value)
1610     {
1611     },
1612 
1613     getObjectByURL: function(context, url)
1614     {
1615     },
1616 
1617     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
1618     // intermodule dependency
1619 
1620     // caller needs module. win maybe context.window or iframe in context.window.
1621     // true means module is ready now, else getting ready
1622     isReadyElsePreparing: function(context, win)
1623     {
1624     },
1625 });
1626 
1627 // ********************************************************************************************* //
1628 
1629 Firebug.Extension =
1630 {
1631     acceptContext: function(win,uri)
1632     {
1633         return false;
1634     },
1635 
1636     declineContext: function(win,uri)
1637     {
1638         return false;
1639     }
1640 };
1641 
1642 // ********************************************************************************************* //
1643 
1644 /**
1645  * @panel Base class for all panels. Every derived panel must define a constructor and
1646  * register with <code>Firebug.registerPanel</code> method. An instance of the panel
1647  * object is created by the framework for each browser tab where Firebug is activated.
1648  */
1649 Firebug.Panel = Obj.extend(new Firebug.Listener(),
1650 /** @lends Firebug.Panel */
1651 {
1652     searchable: false,    // supports search
1653     editable: true,       // clicking on contents in the panel will invoke the inline editor, eg the CSS Style panel or HTML panel.
1654     breakable: false,     // if true, supports break-on-next (the pause button functionality)
1655     order: 2147483647,    // relative position of the panel (or a side panel)
1656     statusSeparator: "<", // the character used to separate items on the panel status (aka breadcrumbs) in the tool bar, eg ">"  in the DOM panel
1657     enableA11y: false,    // true if the panel wants to participate in A11y accessibility support.
1658     deriveA11yFrom: null, // Name of the panel that uses the same a11y logic.
1659     inspectable: false,   // true to support inspecting elements inside this panel
1660 
1661     initialize: function(context, doc)
1662     {
1663         if (!context.browser)
1664         {
1665             if (FBTrace.DBG_ERRORS)
1666                 FBTrace.sysout("attempt to create panel with dud context!");
1667             return false;
1668         }
1669 
1670         this.context = context;
1671         this.document = doc;
1672 
1673         this.panelNode = doc.createElement("div");
1674         this.panelNode.ownerPanel = this;
1675 
1676         Css.setClass(this.panelNode, "panelNode panelNode-" + this.name + " contextUID=" +
1677             context.uid);
1678 
1679         // Load persistent content if any.
1680         var persistedState = Firebug.getPanelState(this);
1681         if (persistedState)
1682         {
1683             this.persistContent = persistedState.persistContent;
1684             if (this.persistContent && persistedState.panelNode)
1685                 this.loadPersistedContent(persistedState);
1686         }
1687 
1688         doc.body.appendChild(this.panelNode);
1689 
1690         // Update panel's tab in case the break-on-next (BON) is active.
1691         var shouldBreak = this.shouldBreakOnNext();
1692         Firebug.Breakpoint.updatePanelTab(this, shouldBreak);
1693 
1694         if (FBTrace.DBG_INITIALIZE)
1695             FBTrace.sysout("firebug.initialize panelNode for " + this.name);
1696 
1697         this.initializeNode(this.panelNode);
1698     },
1699 
1700     destroy: function(state) // Panel may store info on state
1701     {
1702         if (FBTrace.DBG_INITIALIZE)
1703             FBTrace.sysout("firebug.destroy panelNode for " + this.name);
1704 
1705         if (this.panelNode)
1706         {
1707             if (this.persistContent)
1708                 this.savePersistedContent(state);
1709             else
1710                 delete state.persistContent;
1711 
1712             delete this.panelNode.ownerPanel;
1713         }
1714 
1715         this.destroyNode();
1716 
1717         // xxxHonza: not exactly sure why, but it helps when testing memory-leask.
1718         // Note the the selection can point to a document (in case of the HTML panel).
1719         // Perhaps it breaks a cycle (page -> firebug -> page)?
1720         delete this.selection;
1721         delete this.panelBrowser;
1722     },
1723 
1724     savePersistedContent: function(state)
1725     {
1726         state.panelNode = this.panelNode;
1727         state.persistContent = this.persistContent;
1728     },
1729 
1730     loadPersistedContent: function(persistedState)
1731     {
1732         // move the nodes from the persistedState to the panel
1733         while (persistedState.panelNode.firstChild)
1734             this.panelNode.appendChild(persistedState.panelNode.firstChild);
1735 
1736         Dom.scrollToBottom(this.panelNode);
1737     },
1738 
1739     // called when a panel in one XUL window is about to disappear to later reappear
1740     // another XUL window.
1741     detach: function(oldChrome, newChrome)
1742     {
1743     },
1744 
1745     // this is how a panel in one window reappears in another window; lazy called
1746     reattach: function(doc)
1747     {
1748         this.document = doc;
1749 
1750         if (this.panelNode)
1751         {
1752             var scrollTop = this.panelNode.scrollTop;
1753             this.panelNode = doc.adoptNode(this.panelNode, true);
1754             this.panelNode.ownerPanel = this;
1755             doc.body.appendChild(this.panelNode);
1756             this.panelNode.scrollTop = scrollTop;
1757         }
1758     },
1759 
1760     // Called at the end of module.initialize; addEventListener-s here
1761     initializeNode: function(panelNode)
1762     {
1763         Events.dispatch(this.fbListeners, "onInitializeNode", [this]);
1764     },
1765 
1766     // removeEventListener-s here.
1767     destroyNode: function()
1768     {
1769         Events.dispatch(this.fbListeners, "onDestroyNode", [this]);
1770     },
1771 
1772     show: function(state)  // persistedPanelState plus non-persisted hide() values
1773     {
1774     },
1775 
1776     hide: function(state)  // store info on state for next show.
1777     {
1778     },
1779 
1780     watchWindow: function(context, win)
1781     {
1782     },
1783 
1784     unwatchWindow: function(context, win)
1785     {
1786     },
1787 
1788     updateOption: function(name, value)
1789     {
1790     },
1791 
1792     /**
1793      * Called after chrome.applyTextSize
1794      * @param zoom: ratio of current size to normal size, eg 1.5
1795      */
1796     onTextSizeChange: function(zoom)
1797     {
1798 
1799     },
1800 
1801     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
1802     // Toolbar
1803 
1804     showToolbarButtons: function(buttonsId, show)
1805     {
1806         try
1807         {
1808             var buttons = Firebug.chrome.$(buttonsId);
1809             Dom.collapse(buttons, !show);
1810         }
1811         catch (exc)
1812         {
1813             if (FBTrace.DBG_ERRORS)
1814                 FBTrace.sysout("firebug.Panel showToolbarButtons FAILS "+exc, exc);
1815         }
1816     },
1817 
1818     onGetPanelToolbarButtons: function(panel, items)
1819     {
1820         return [];
1821     },
1822 
1823     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
1824 
1825     /**
1826      * Returns a number indicating the view's ability to inspect the object.
1827      *
1828      * Zero means not supported, and higher numbers indicate specificity.
1829      */
1830     supportsObject: function(object, type)
1831     {
1832         return 0;
1833     },
1834 
1835     hasObject: function(object)  // beyond type testing, is this object selectable?
1836     {
1837         return false;
1838     },
1839 
1840     navigate: function(object)
1841     {
1842         if (!object)
1843             object = this.getDefaultLocation();
1844         if (!object)
1845             object = null;  // not undefined.
1846 
1847         // if this.location undefined, may set to null
1848         if (!this.location || (object != this.location))
1849         {
1850             if (FBTrace.DBG_PANELS)
1851                 FBTrace.sysout("navigate "+this.name+" to location "+object, object);
1852 
1853             this.location = object;
1854             this.updateLocation(object);
1855 
1856             Events.dispatch(Firebug.uiListeners, "onPanelNavigate", [object, this]);
1857         }
1858         else
1859         {
1860             if (FBTrace.DBG_PANELS)
1861             {
1862                 FBTrace.sysout("navigate skipped for panel " + this.name + " when object " +
1863                     object + " vs this.location=" + this.location,
1864                     {object: object, location: this.location});
1865             }
1866         }
1867     },
1868 
1869     /**
1870      * The location object has been changed, the panel should update it view
1871      * @param object a location, must be one of getLocationList() returns
1872      *  if  getDefaultLocation() can return null, then updateLocation must handle it here.
1873      */
1874     updateLocation: function(object)
1875     {
1876     },
1877 
1878     select: function(object, forceUpdate)
1879     {
1880         if (!object)
1881             object = this.getDefaultSelection();
1882 
1883         if (FBTrace.DBG_PANELS)
1884             FBTrace.sysout("firebug.select "+this.name+" forceUpdate: "+forceUpdate+" "+
1885                 object+((object==this.selection)?"==":"!=")+this.selection);
1886 
1887         if (forceUpdate || object != this.selection)
1888         {
1889             this.selection = object;
1890             this.updateSelection(object);
1891 
1892             Events.dispatch(Firebug.uiListeners, "onObjectSelected", [object, this]);
1893         }
1894     },
1895 
1896     /**
1897      * Firebug wants to show an object to the user and this panel has the best supportsObject()
1898      * result for the object. If the panel displays a container for objects of this type,
1899      * it should set this.selectedObject = object
1900      */
1901     updateSelection: function(object)
1902     {
1903     },
1904 
1905     /**
1906      * Redisplay the panel based on the current location and selection
1907      */
1908     refresh: function()
1909     {
1910         if (this.location)
1911             this.updateLocation(this.location);
1912         else if (this.selection)
1913             this.updateSelection(this.selection);
1914     },
1915 
1916     markChange: function(skipSelf)
1917     {
1918         if (this.dependents)
1919         {
1920             if (skipSelf)
1921             {
1922                 for (var i = 0; i < this.dependents.length; ++i)
1923                 {
1924                     var panelName = this.dependents[i];
1925                     if (panelName != this.name)
1926                         this.context.invalidatePanels(panelName);
1927                 }
1928             }
1929             else
1930                 this.context.invalidatePanels.apply(this.context, this.dependents);
1931         }
1932     },
1933 
1934     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
1935     // Inspector
1936 
1937     /**
1938      * Called by the framework when the user starts inspecting. Inspecting must be enabled
1939      * for the panel (panel.inspectable == true)
1940      */
1941     startInspecting: function()
1942     {
1943     },
1944 
1945     /**
1946      * Called by the framework when inspecting is in progress and the user moves mouse over
1947      * a new page element. Inspecting must be enabled for the panel (panel.inspectable == true).
1948      * This method is called in a timeout to avoid performance penalties when the user moves
1949      * the mouse over the page elements too fast.
1950      * @param {Element} node The page element being inspected
1951      * @returns {Boolean} Returns true if the node should be selected within the panel using
1952      *      the default panel selection mechanism (i.e. by calling panel.select(node) method).
1953      */
1954     inspectNode: function(node)
1955     {
1956         return true;
1957     },
1958 
1959     /**
1960      * Called by the framework when the user stops inspecting. Inspecting must be enabled
1961      * for the panel (panel.inspectable == true)
1962      * @param {Element} node The last page element inspected
1963      * @param {Boolean} canceled Set to true if inspecing has been canceled
1964      *          by pressing the escape key.
1965      */
1966     stopInspecting: function(node, canceled)
1967     {
1968     },
1969 
1970     /**
1971      * Called by the framework when inspecting is in progress. Allows to inspect
1972      * only nodes that are supported by the panel. Derived panels can provide effective
1973      * algorithms to provide these nodes.
1974      * @param {Element} node Currently inspected page element.
1975      */
1976     getInspectNode: function(node)
1977     {
1978         while (node)
1979         {
1980             if (this.supportsObject(node, typeof node))
1981                 return node;
1982             node = node.parentNode;
1983         }
1984         return null;
1985     },
1986 
1987     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
1988 
1989     /*
1990      * Called by search in the case something was found.
1991      * This will highlight the given node for a specific timespan. There's only one node
1992      * highlighted at a time.
1993      * @param {Node} Node to highlight
1994      */
1995     highlightNode: function(node)
1996     {
1997         if (this.highlightedNode)
1998             Css.cancelClassTimed(this.highlightedNode, "jumpHighlight", this.context);
1999 
2000         this.highlightedNode = node;
2001 
2002         if (node)
2003             Css.setClassTimed(node, "jumpHighlight", this.context);
2004     },
2005 
2006     /*
2007      * Called by the framework when panel search is used.
2008      * This is responsible for finding and highlighting search matches.
2009      * @param {String} text String to search for
2010      * @param {Boolean} reverse Indicates, if search is reversed
2011      * @return true, if search matched, otherwise false
2012      */
2013     search: function(text, reverse)
2014     {
2015     },
2016 
2017     /**
2018      * Retrieves the search options that this modules supports.
2019      * This is used by the search UI to present the proper options.
2020      */
2021     getSearchOptionsMenuItems: function()
2022     {
2023         return [
2024             Firebug.Search.searchOptionMenu("search.Case Sensitive", "searchCaseSensitive",
2025                 "search.tip.Case_Sensitive")
2026         ];
2027     },
2028 
2029     /**
2030      * Navigates to the next document whose match parameter returns true.
2031      */
2032     navigateToNextDocument: function(match, reverse)
2033     {
2034         // This is an approximation of the UI that is displayed by the location
2035         // selector. This should be close enough, although it may be better
2036         // to simply generate the sorted list within the module, rather than
2037         // sorting within the UI.
2038         var self = this;
2039         function compare(a, b)
2040         {
2041             var locA = self.getObjectDescription(a);
2042             var locB = self.getObjectDescription(b);
2043             if (locA.path > locB.path)
2044                 return 1;
2045             if (locA.path < locB.path)
2046                 return -1;
2047             if (locA.name > locB.name)
2048                 return 1;
2049             if (locA.name < locB.name)
2050                 return -1;
2051             return 0;
2052         }
2053 
2054         var allLocs = this.getLocationList().sort(compare);
2055         for (var curPos = 0; curPos < allLocs.length && allLocs[curPos] != this.location; curPos++);
2056 
2057         function transformIndex(index)
2058         {
2059             if (reverse)
2060             {
2061                 // For the reverse case we need to implement wrap around.
2062                 var intermediate = curPos - index - 1;
2063                 return (intermediate < 0 ? allLocs.length : 0) + intermediate;
2064             }
2065             else
2066             {
2067                 return (curPos + index + 1) % allLocs.length;
2068             }
2069         };
2070 
2071         for (var next = 0; next < allLocs.length - 1; next++)
2072         {
2073             var object = allLocs[transformIndex(next)];
2074 
2075             if (match(object))
2076             {
2077                 this.navigate(object);
2078                 return object;
2079             }
2080         }
2081     },
2082 
2083     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
2084 
2085     // Called when "Options" clicked. Return array of
2086     // {label: 'name', nol10n: true,  type: "checkbox", checked: <value>,
2087     //      command:function to set <value>}
2088     getOptionsMenuItems: function()
2089     {
2090         return null;
2091     },
2092 
2093     /**
2094      * Called by chrome.onContextMenu to build the context menu when this panel has focus.
2095      * See also FirebugRep for a similar function also called by onContextMenu
2096      * Extensions may monkey patch and chain off this call
2097      * @param object: the 'realObject', a model value, eg a DOM property
2098      * @param target: the HTML element clicked on.
2099      * @return an array of menu items.
2100      */
2101     getContextMenuItems: function(object, target)
2102     {
2103         return [];
2104     },
2105 
2106     getBreakOnMenuItems: function()
2107     {
2108         return [];
2109     },
2110 
2111     getEditor: function(target, value)
2112     {
2113     },
2114 
2115     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
2116 
2117     getDefaultSelection: function()
2118     {
2119         return null;
2120     },
2121 
2122     browseObject: function(object)
2123     {
2124     },
2125 
2126     getPopupObject: function(target)
2127     {
2128         return Firebug.getRepObject(target);
2129     },
2130 
2131     getTooltipObject: function(target)
2132     {
2133         return Firebug.getRepObject(target);
2134     },
2135 
2136     showInfoTip: function(infoTip, x, y)
2137     {
2138 
2139     },
2140 
2141     getObjectPath: function(object)
2142     {
2143         return null;
2144     },
2145 
2146     // An array of objects that can be passed to getObjectLocation.
2147     // The list of things a panel can show, eg sourceFiles.
2148     // Only shown if panel.location defined and supportsObject true
2149     getLocationList: function()
2150     {
2151         return null;
2152     },
2153 
2154     getDefaultLocation: function()
2155     {
2156         return null;
2157     },
2158 
2159     getObjectLocation: function(object)
2160     {
2161         return "";
2162     },
2163 
2164     // Text for the location list menu eg script panel source file list
2165     // return.path: group/category label, return.name: item label
2166     getObjectDescription: function(object)
2167     {
2168         var url = this.getObjectLocation(object);
2169         return Url.splitURLBase(url);
2170     },
2171 
2172     /**
2173      *  UI signal that a tab needs attention, eg Script panel is currently stopped on a breakpoint
2174      *  @param: show boolean, true turns on.
2175      */
2176     highlight: function(show)
2177     {
2178         var tab = this.getTab();
2179         if (!tab)
2180             return;
2181 
2182         if (show)
2183             tab.setAttribute("highlight", "true");
2184         else
2185             tab.removeAttribute("highlight");
2186     },
2187 
2188     getTab: function()
2189     {
2190         var chrome = Firebug.chrome;
2191 
2192         var tab = chrome.$("fbPanelBar2").getTab(this.name);
2193         if (!tab)
2194             tab = chrome.$("fbPanelBar1").getTab(this.name);
2195         return tab;
2196     },
2197 
2198     /**
2199      * If the panel supports source viewing, then return a SourceLink, else null
2200      * @param target an element from the panel under the mouse
2201      * @param object the realObject under the mouse
2202      */
2203     getSourceLink: function(target, object)
2204     {
2205     },
2206 
2207     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
2208     // Support for Break On Next
2209 
2210     /**
2211      * Called by the framework to see if the panel currently supports BON
2212      */
2213     supportsBreakOnNext: function()
2214     {
2215         return this.breakable;  // most panels just use this flag
2216     },
2217 
2218     /**
2219      * Called by the framework when the user clicks on the Break On Next button.
2220      * @param {Boolean} armed Set to true if the Break On Next feature is
2221      * to be armed for action and set to false if the Break On Next should be disarmed.
2222      * If 'armed' is true, then the next call to shouldBreakOnNext should be |true|.
2223      */
2224     breakOnNext: function(armed)
2225     {
2226     },
2227 
2228     /**
2229      * Called when a panel is selected/displayed. The method should return true
2230      * if the Break On Next feature is currently armed for this panel.
2231      */
2232     shouldBreakOnNext: function()
2233     {
2234         return false;
2235     },
2236 
2237     /**
2238      * Returns labels for Break On Next tooltip (one for enabled and one for disabled state).
2239      * @param {Boolean} enabled Set to true if the Break On Next feature is
2240      * currently activated for this panel.
2241      */
2242     getBreakOnNextTooltip: function(enabled)
2243     {
2244         return null;
2245     },
2246 });
2247 
2248 // ********************************************************************************************* //
2249 
2250 /**
2251  * @panel This object represents a panel with two states: enabled/disabled. Such support
2252  * is important for panel that represents performance penalties and it's useful for the
2253  * user to have the option to disable them.
2254  *
2255  * All methods in this object are used on the prototype object (they reprent class methods)
2256  * and so, |this| points to the panel's prototype and *not* to the panel instance.
2257  */
2258 Firebug.ActivablePanel = Obj.extend(Firebug.Panel,
2259 {
2260     activable: true,
2261 
2262     isActivable: function()
2263     {
2264         return this.activable;
2265     },
2266 
2267     isEnabled: function()
2268     {
2269         if (!this.isActivable())
2270             return true;
2271 
2272         if (!this.name)
2273             return false;
2274 
2275         return Options.get(this.name+".enableSites");
2276     },
2277 
2278     setEnabled: function(enable)
2279     {
2280         if (!this.name || !this.activable)
2281             return;
2282 
2283         Options.set(this.name+".enableSites", enable);
2284     },
2285 
2286     /**
2287      * Called when an instance of this panel type is enabled or disabled. Again notice that
2288      * this is a class method and so, panel instance variables (like e.g. context) are
2289      * not accessible from this method.
2290      * @param {Object} enable Set to true if this panel type is now enabled.
2291      */
2292     onActivationChanged: function(enable)
2293     {
2294         // TODO: Use Firebug.ActivableModule.addObserver to express dependencies on modules.
2295     },
2296 });
2297 
2298 // ********************************************************************************************* //
2299 
2300 /**
2301  * @module Should be used by modules (Firebug specific task controllers) that supports
2302  * activation. An example of such 'activable' module can be the debugger module
2303  * {@link Firebug.Debugger}, which can be disabled in order to avoid performance
2304  * penalties (in cases where the user doesn't need a debugger for the moment).
2305  */
2306 Firebug.ActivableModule = Obj.extend(Firebug.Module,
2307 /** @lends Firebug.ActivableModule */
2308 {
2309     /**
2310      * Every activable module is disabled by default waiting for on a panel
2311      * that wants to have it enabled (and display provided data). The rule is
2312      * if there is no panel (view) the module is disabled.
2313      */
2314     enabled: false,
2315 
2316     /**
2317      * List of observers (typically panels). If there is at least one observer registered
2318      * The module becomes active.
2319      */
2320     observers: null,
2321 
2322     /**
2323      * List of dependent modules.
2324      */
2325     dependents: null,
2326 
2327     initialize: function()
2328     {
2329         Firebug.Module.initialize.apply(this, arguments);
2330     },
2331 
2332     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
2333     // Observers (dependencies)
2334 
2335     hasObservers: function()
2336     {
2337         return this.observers ? this.observers.length > 0 : false;
2338     },
2339 
2340     addObserver: function(observer)
2341     {
2342         if (!this.observers)
2343             this.observers = [];
2344 
2345         if (this.observers.indexOf(observer) === -1)
2346         {
2347             this.observers.push(observer);
2348             this.onObserverChange(observer);  // targeted, not dispatched.
2349         }
2350         // else no-op
2351     },
2352 
2353     removeObserver: function(observer)
2354     {
2355         if (!this.observers)
2356             return;
2357 
2358         if (this.observers.indexOf(observer) !== -1)
2359         {
2360             Arr.remove(this.observers, observer);
2361             this.onObserverChange(observer);  // targeted, not dispatched
2362         }
2363         // else no-op
2364     },
2365 
2366     /**
2367      * This method is called if an observer (e.g. {@link Firebug.Panel}) is added or removed.
2368      * The module should decide about activation/deactivation upon existence of at least one
2369      * observer.
2370      */
2371     onObserverChange: function(observer)
2372     {
2373         if (FBTrace.DBG_WINDOWS)
2374             FBTrace.sysout("firebug.ActivableModule.onObserverChange;");
2375     },
2376 
2377     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
2378     // Firebug Activation
2379 
2380     onSuspendingFirebug: function()
2381     {
2382         // Called before any suspend actions. First caller to return true aborts suspend.
2383     },
2384 
2385     onSuspendFirebug: function()
2386     {
2387         // When the number of activeContexts decreases to zero. Modules should remove
2388         // listeners, disable function that takes resources
2389     },
2390 
2391     onResumeFirebug: function()
2392     {
2393         // When the number of activeContexts increases from zero. Modules should undo the
2394         // work done in onSuspendFirebug
2395     },
2396 
2397     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
2398     // Module enable/disable APIs.
2399 
2400     setDefaultState: function(enable)
2401     {
2402         //@deprecated
2403         Firebug.Console.log("Deprecated: don't use ActivableModule.setDefaultState!",
2404             Firebug.currentContext);
2405     },
2406 
2407     isEnabled: function()
2408     {
2409         return this.hasObservers();
2410     },
2411 
2412     isAlwaysEnabled: function()
2413     {
2414         return this.hasObservers();
2415     }
2416 });
2417 
2418 // ********************************************************************************************* //
2419 
2420 /**
2421  * MeasureBox
2422  * To get pixels size.width and size.height:
2423  * <ul><li>     this.startMeasuring(view); </li>
2424  *     <li>     var size = this.measureText(lineNoCharsSpacer); </li>
2425  *     <li>     this.stopMeasuring(); </li>
2426  * </ul>
2427  */
2428 Firebug.MeasureBox =
2429 {
2430     startMeasuring: function(target)
2431     {
2432         if (!this.measureBox)
2433         {
2434             this.measureBox = target.ownerDocument.createElement("span");
2435             this.measureBox.className = "measureBox";
2436         }
2437 
2438         Css.copyTextStyles(target, this.measureBox);
2439         target.ownerDocument.body.appendChild(this.measureBox);
2440     },
2441 
2442     getMeasuringElement: function()
2443     {
2444         return this.measureBox;
2445     },
2446 
2447     measureText: function(value)
2448     {
2449         this.measureBox.innerHTML = value ? Str.escapeForSourceLine(value) : "m";
2450         return {width: this.measureBox.offsetWidth, height: this.measureBox.offsetHeight-1};
2451     },
2452 
2453     measureInputText: function(value)
2454     {
2455         value = value ? Str.escapeForTextNode(value) : "m";
2456         if (!Firebug.showTextNodesWithWhitespace)
2457             value = value.replace(/\t/g,'mmmmmm').replace(/\ /g,'m');
2458 
2459         this.measureBox.innerHTML = value;
2460         return {width: this.measureBox.offsetWidth, height: this.measureBox.offsetHeight-1};
2461     },
2462 
2463     getBox: function(target)
2464     {
2465         var style = this.measureBox.ownerDocument.defaultView.getComputedStyle(this.measureBox, "");
2466         var box = Css.getBoxFromStyles(style, this.measureBox);
2467         return box;
2468     },
2469 
2470     stopMeasuring: function()
2471     {
2472         this.measureBox.parentNode.removeChild(this.measureBox);
2473     }
2474 };
2475 
2476 // ********************************************************************************************* //
2477 
2478 with (Domplate) {
2479 Firebug.Rep = domplate(
2480 {
2481     className: "",
2482     inspectable: true,
2483 
2484     supportsObject: function(object, type)
2485     {
2486         return false;
2487     },
2488 
2489     highlightObject: function(object, context)
2490     {
2491         var realObject = this.getRealObject(object, context);
2492         if (realObject)
2493             Firebug.Inspector.highlightObject(realObject, context);
2494     },
2495 
2496     unhighlightObject: function(object, context)
2497     {
2498         Firebug.Inspector.highlightObject(null);
2499     },
2500 
2501     inspectObject: function(object, context)
2502     {
2503         Firebug.chrome.select(object);
2504     },
2505 
2506     browseObject: function(object, context)
2507     {
2508     },
2509 
2510     persistObject: function(object, context)
2511     {
2512     },
2513 
2514     getRealObject: function(object, context)
2515     {
2516         return object;
2517     },
2518 
2519     getTitle: function(object)
2520     {
2521         if (object.constructor && typeof(object.constructor) == 'function')
2522         {
2523             var ctorName = object.constructor.name;
2524             if (ctorName && ctorName != "Object")
2525                 return ctorName;
2526         }
2527 
2528         var label = FBL.safeToString(object); // eg [object XPCWrappedNative [object foo]]
2529 
2530         const re =/\[object ([^\]]*)/;
2531         var m = re.exec(label);
2532         var n = null;
2533         if (m)
2534             n = re.exec(m[1]);  // eg XPCWrappedNative [object foo
2535 
2536         if (n)
2537             return n[1];  // eg foo
2538         else
2539             return m ? m[1] : label;
2540     },
2541 
2542     showInfoTip: function(infoTip, target, x, y)
2543     {
2544         return false;
2545     },
2546 
2547     getTooltip: function(object)
2548     {
2549         return null;
2550     },
2551 
2552     /**
2553      * Called by chrome.onContextMenu to build the context menu when the underlying object
2554      * has this rep. See also Panel for a similar function also called by onContextMenu
2555      * Extensions may monkey patch and chain off this call
2556      *
2557      * @param object: the 'realObject', a model value, eg a DOM property
2558      * @param target: the HTML element clicked on.
2559      * @param context: the context, probably Firebug.currentContext
2560      * @return an array of menu items.
2561      */
2562     getContextMenuItems: function(object, target, context)
2563     {
2564         return [];
2565     },
2566 
2567     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
2568     // Convenience for domplates
2569 
2570     STR: function(name)
2571     {
2572         return Locale.$STR(name);
2573     },
2574 
2575     cropString: function(text)
2576     {
2577         return Str.cropString(text);
2578     },
2579 
2580     cropMultipleLines: function(text, limit)
2581     {
2582         return Str.cropMultipleLines(text, limit);
2583     },
2584 
2585     toLowerCase: function(text)
2586     {
2587         return text ? text.toLowerCase() : text;
2588     },
2589 
2590     plural: function(n)
2591     {
2592         return n == 1 ? "" : "s";
2593     }
2594 })};
2595 
2596 // ********************************************************************************************* //
2597 
2598 Firebug.Migrator =
2599 {
2600     /*
2601      * UI Update from element oldButton to element newButton.
2602      * On first call, Arrow points from old to new, button OK required
2603      * After OK, the oldButton is removed and not shown again unless preference is erased.
2604      */
2605     migrateButton: function(oldButton, newButton)
2606     {
2607         if (Firebug.Migrator.getMigrated(oldButton))
2608         {
2609             oldButton.parentNode.removeChild(oldButton);
2610             return;
2611         }
2612 
2613         function showMigration(event)
2614         {
2615             oldButton.removeEventListener('mouseover', showMigration, false);
2616 
2617             var endPoint = newButton.getBoundingClientRect();
2618             var origin = oldButton.getBoundingClientRect();
2619 
2620             // A box surrounding both buttons
2621             var left =   Math.min(origin.left,   endPoint.left);
2622             var right =  Math.max(origin.right,  endPoint.right);
2623             var top =    Math.min(origin.top,    endPoint.top);
2624             var bottom = Math.max(origin.bottom, endPoint.bottom);
2625 
2626             var width = right - left;
2627             var height =  bottom - top;
2628 
2629             var migrationPanel = Firebug.chrome.$("fbMigrator");
2630             var panelWidth = Math.max(width, 150);
2631             var panelHeight = Math.max(height, 150);
2632             migrationPanel.sizeTo(panelWidth, panelHeight);
2633 
2634             // x, y are offsets from the upper left corner of the oldButton, that
2635             // is the reference point of the 'overlap' position of the popup
2636             // (Hint, think about all the x values then all the y values.)
2637             if (left == origin.left)
2638             {
2639                 var x = 0;
2640                 var x1 = origin.width;
2641             }
2642             else
2643             {
2644                 var x = origin.width - width;
2645                 var x1 = width - origin.width;
2646             }
2647             if (top == origin.top)
2648             {
2649                 var y = 0;
2650                 var y1 = origin.height;
2651             }
2652             else
2653             {
2654                 var y = origin.height - origin;
2655                 var y1 = height - origin.height;
2656             }
2657 
2658             if (left == endPoint.left)
2659                 var x2 = endPoint.width;
2660             else
2661                 var x2 = width - endPoint.width;
2662 
2663             if (top == endPoint.top)
2664                 var y2 = endPoint.height;
2665             else
2666                 var y2 = height - endPoint.height;
2667 
2668             migrationPanel.openPopup(oldButton, 'overlap',  x,  y, false, true);
2669 
2670             Firebug.Migrator.drawMigrationLine(x1, y1, x2, y2);
2671 
2672             Firebug.Migrator.removeButtonOnOk(oldButton, migrationPanel);
2673         }
2674         oldButton.addEventListener('mouseover', showMigration, false);
2675     },
2676 
2677     drawMigrationLine: function(x1, y1, x2, y2)
2678     {
2679         var migrationFrame = Firebug.chrome.$('fbMigrationFrame');
2680 
2681         var line  = migrationFrame.contentDocument.getElementById("migrationPath");
2682         line.setAttribute("x1", x1);
2683         line.setAttribute("x2", x1);
2684         line.setAttribute("y1", y1);
2685         line.setAttribute("y2", y1);
2686 
2687         var progress = 0;
2688         var steps = 100;
2689         var stepStep = 1;
2690         var xStep = (x2 - x1)/steps;
2691         var yStep = (y2 - y1)/steps;
2692         var xCur = x1;
2693         var yCur = y1;
2694         Firebug.Migrator.animate = setInterval(function growLine()
2695         {
2696             xCur += stepStep*xStep;
2697             yCur += stepStep*yStep;
2698             steps -= stepStep;
2699             if (steps > 50)
2700                 stepStep++;
2701             else
2702                 stepStep--;
2703             //FBTrace.sysout("animate steps "+steps+" stepStep "+stepStep+" x "+xCur+" y "+yCur);
2704             line.setAttribute("x2", xCur);
2705             line.setAttribute("y2", yCur);
2706             if (steps < 0)
2707                 clearInterval(animate);
2708         }, 50);
2709     },
2710 
2711     removeButtonOnOk: function(oldButton, migrationPanel)
2712     {
2713         var migrationOk = Firebug.chrome.$('fbMigrationOk');
2714         migrationOk.addEventListener('click', function migrationComplete(event)
2715         {
2716             // xxxHonza, XXXjjb: I have seen an exception saying that oldButton.parentNode is null.
2717             oldButton.parentNode.removeChild(oldButton);
2718             Firebug.Migrator.setMigrated(oldButton);
2719             clearInterval(Firebug.Migrator.animate);
2720             migrationPanel.hidePopup();
2721             migrationOk.removeEventListener('click', migrationComplete, true);
2722         }, true);
2723     },
2724 
2725     getMigrated: function(elt)
2726     {
2727         var id = elt.getAttribute('id');
2728         return Options.get("migrated_"+id);
2729     },
2730 
2731     setMigrated: function(elt)
2732     {
2733         var id = elt.getAttribute('id');
2734         Options.set( "migrated_"+id, true, typeof(true));
2735     },
2736 
2737 }
2738 
2739 // ********************************************************************************************* //
2740 
2741 /**
2742  * If we are detached and the main Firefox window closes, also close the matching Firebug window.
2743  */
2744 function shutdownFirebug()
2745 {
2746     try
2747     {
2748         if (Firebug.isDetached())
2749             Firebug.chrome.close();
2750     }
2751     catch (exc)
2752     {
2753         window.dump("shutdownFirebug FAILS: "+exc+"\n");
2754     }
2755 
2756     Firebug.shutdown();
2757 }
2758 
2759 if (preFirebugKeys)
2760 {
2761     // Add back the preLoad properties
2762     preFirebugKeys.forEach(function copyProps(key)
2763     {
2764         Firebug[key] = PreFirebug[key];
2765     });
2766 }
2767 
2768 // ********************************************************************************************* //
2769 // Registration
2770 
2771 Firebug.Firefox = Firefox;
2772 Firebug.Domplate = Domplate;
2773 Firebug.ChromeFactory = ChromeFactory;
2774 
2775 return Firebug;
2776 
2777 // ********************************************************************************************* //
2778 });
2779