1 /* See license.txt for terms of usage */
  2 
  3 var Firebug = null;
  4 var FirebugContext = null;
  5 
  6 if(!fbXPCOMUtils)
  7     throw "Failed to load FBL";
  8 
  9 (function() { with (fbXPCOMUtils) {
 10 
 11 // ************************************************************************************************
 12 // Constants
 13 
 14 const Cc = Components.classes;
 15 const Ci = Components.interfaces;
 16 const nsIWebNavigation = Ci.nsIWebNavigation;
 17 
 18 const LOAD_FLAGS_BYPASS_PROXY = nsIWebNavigation.LOAD_FLAGS_BYPASS_PROXY;
 19 const LOAD_FLAGS_BYPASS_CACHE = nsIWebNavigation.LOAD_FLAGS_BYPASS_CACHE;
 20 const LOAD_FLAGS_NONE = nsIWebNavigation.LOAD_FLAGS_NONE;
 21 
 22 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 23 
 24 const panelURL = "chrome://firebug/content/panel.html";
 25 
 26 const statusCropSize = 20;
 27 
 28 const positiveZoomFactors = [1, 1.1, 1.2, 1.3, 1.5, 2, 3];
 29 const negativeZoomFactors = [1, 0.95, 0.8, 0.7, 0.5, 0.2, 0.1];
 30 
 31 // ************************************************************************************************
 32 // Globals
 33 
 34 var panelBox, panelSplitter, sidePanelDeck, panelBar1, panelBar2, locationList, locationSeparator,
 35     panelStatus, panelStatusSeparator;
 36 
 37 var waitingPanelBarCount = 2;
 38 
 39 var inDetachedScope = (window.location == "chrome://firebug/content/firebug.xul");
 40 
 41 var disabledHead = null;
 42 var disabledCaption = null;
 43 var enableSiteLink = null;
 44 var enableSystemPagesLink = null;
 45 var enableAlwaysLink = null;
 46 // ************************************************************************************************
 47 
 48 top.FirebugChrome =
 49 {
 50     window: window,
 51 
 52     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 53     // Initialization
 54 
 55     panelBarReady: function(panelBar)
 56     {
 57         try
 58         {
 59             // Wait until all panelBar bindings are ready before initializing
 60             if (--waitingPanelBarCount == 0)
 61                 this.initialize();
 62             else
 63                 return false;
 64         }
 65         catch (exc)
 66         {
 67             if (FBTrace.sysout)
 68                 FBTrace.sysout("chrome.panelBarReady FAILS: "+exc, exc);
 69             return false;
 70         }
 71         return true; // the panel bar is ready
 72     },
 73 
 74     initialize: function()
 75     {
 76         if (window.arguments)
 77             var detachArgs = window.arguments[0];
 78 
 79         if (!detachArgs)
 80             detachArgs = {};
 81 
 82         if (FBTrace.DBG_INITIALIZE) FBTrace.sysout("chrome.initialize w/detachArgs=", detachArgs);
 83 
 84         if (detachArgs.FBL)
 85             top.FBL = detachArgs.FBL;
 86         else
 87         {
 88             if (FBTrace.sysout && (!FBL || !FBL.initialize) )
 89                 FBTrace.sysout("Firebug is broken, FBL incomplete, if the last function is QI, check lib.js:", FBL);
 90 
 91             FBL.initialize();
 92         }
 93 
 94         if (detachArgs.Firebug)
 95         {
 96             Firebug = detachArgs.Firebug;
 97             FirebugContext = detachArgs.FirebugContext;
 98         }
 99         else
100             Firebug.initialize();
101 
102         Firebug.internationalizeUI(window.document);
103 
104         panelBox = $("fbPanelBox");
105         panelSplitter = $("fbPanelSplitter");
106         sidePanelDeck = $("fbSidePanelDeck");
107         panelBar1 = $("fbPanelBar1");
108         panelBar2 = $("fbPanelBar2");
109         locationList = $("fbLocationList");
110         locationSeparator = $("fbLocationSeparator");
111         panelStatus = $("fbPanelStatus");
112         panelStatusSeparator = $("fbStatusSeparator");
113 
114         var browser1 = panelBar1.browser;
115         browser1.addEventListener("load", browser1Loaded, true);
116 
117         var browser2 = panelBar2.browser;
118         browser2.addEventListener("load", browser2Loaded, true);
119 
120         window.addEventListener("blur", onBlur, true);
121 
122         // Initialize Firebug Tools & Firebug Icon menus.
123         var firebugMenuPopup = $("fbFirebugMenuPopup");
124         this.initializeMenu($("menu_firebug"), firebugMenuPopup);
125         this.initializeMenu($("fbFirebugMenu"), firebugMenuPopup);
126 
127         if (FBTrace.DBG_INITIALIZE)
128             FBTrace.sysout("chrome.initialized ", window);
129     },
130 
131     initializeMenu: function(parentMenu, popupMenu)
132     {
133         if (!parentMenu)
134             return;
135 
136         if (parentMenu.getAttribute("initialized"))
137             return;
138 
139         parentMenu.appendChild(popupMenu.cloneNode(true));
140         parentMenu.setAttribute("initialized", "true");
141     },
142 
143     /**
144      * Called when the UI is ready to be initialized, once the panel browsers are loaded.
145      */
146     initializeUI: function()
147     {
148         // we listen for panel update
149         Firebug.registerUIListener(this);
150 
151         try {
152             if (window.arguments)
153                 var detachArgs = window.arguments[0];
154 
155             this.applyTextSize(Firebug.textSize);
156 
157             var doc1 = panelBar1.browser.contentDocument;
158             doc1.addEventListener("mouseover", onPanelMouseOver, false);
159             doc1.addEventListener("mouseout", onPanelMouseOut, false);
160             doc1.addEventListener("mousedown", onPanelMouseDown, false);
161             doc1.addEventListener("click", onPanelClick, false);
162             panelBar1.addEventListener("selectingPanel", onSelectingPanel, false);
163 
164             var doc2 = panelBar2.browser.contentDocument;
165             doc2.addEventListener("mouseover", onPanelMouseOver, false);
166             doc2.addEventListener("mouseout", onPanelMouseOut, false);
167             doc2.addEventListener("click", onPanelClick, false);
168             doc2.addEventListener("mousedown", onPanelMouseDown, false);
169             panelBar2.addEventListener("selectPanel", onSelectedSidePanel, false);
170 
171             var mainTabBox = panelBar1.ownerDocument.getElementById("fbPanelBar1-tabBox");
172             mainTabBox.addEventListener("mousedown", onMainTabBoxMouseDown, false);
173 
174             // The side panel bar doesn't care about this event.  It must, however,
175             // prevent it from bubbling now that we allow the side panel bar to be
176             // *inside* the main panel bar.
177             function stopBubble(event) { event.stopPropagation(); }
178             panelBar2.addEventListener("selectingPanel", stopBubble, false);
179 
180             locationList.addEventListener("selectObject", onSelectLocation, false);
181 
182             this.updatePanelBar1(Firebug.panelTypes);
183 
184             if (inDetachedScope)
185                 this.attachBrowser(FirebugContext);
186             else
187                 Firebug.initializeUI(detachArgs);
188 
189         } catch (exc) {
190             FBTrace.sysout("chrome.initializeUI fails "+exc, exc);
191         }
192     },
193 
194     shutdown: function()
195     {
196         if (FBTrace.DBG_INITIALIZE || !panelBar1)
197             FBTrace.sysout("chrome.shutdown entered for "+window.location+"\n");
198 
199         var doc1 = panelBar1.browser.contentDocument;
200         doc1.removeEventListener("mouseover", onPanelMouseOver, false);
201         doc1.removeEventListener("mouseout", onPanelMouseOut, false);
202         doc1.removeEventListener("mousedown", onPanelMouseDown, false);
203         doc1.removeEventListener("click", onPanelClick, false);
204 
205         var doc2 = panelBar2.browser.contentDocument;
206         doc2.removeEventListener("mouseover", onPanelMouseOver, false);
207         doc2.removeEventListener("mouseout", onPanelMouseOut, false);
208         doc2.removeEventListener("mousedown", onPanelMouseDown, false);
209         doc2.removeEventListener("click", onPanelClick, false);
210 
211         var mainTabBox = panelBar1.ownerDocument.getElementById("fbPanelBar1-tabBox");
212         mainTabBox.removeEventListener("mousedown", onMainTabBoxMouseDown, false);
213 
214         locationList.removeEventListener("selectObject", onSelectLocation, false);
215 
216         window.removeEventListener("blur", onBlur, true);
217 
218         Firebug.unregisterUIListener(this);
219 
220         if (inDetachedScope)
221             this.undetach();
222         else
223             Firebug.shutdown();
224     },
225 
226     updateOption: function(name, value)
227     {
228         if (panelBar1.selectedPanel)
229             panelBar1.selectedPanel.updateOption(name, value);
230         if (panelBar2.selectedPanel)
231             panelBar2.selectedPanel.updateOption(name, value);
232 
233         if (name == "textSize")
234             this.applyTextSize(value);
235         if (name =="omitObjectPathStack")
236             this.obeyOmitObjectPathStack(value);
237     },
238 
239     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
240 
241     attachBrowser: function(context)  // XXXjjb context == (FirebugContext || null)  and inDetachedScope == true
242     {
243         if (FBTrace.DBG_ACTIVATION)
244             FBTrace.sysout("chrome.attachBrowser with inDetachedScope="+inDetachedScope+" context="+context
245                                +" context==FirebugContext: "+(context==FirebugContext)+" in window: "+window.location);
246 
247         if (inDetachedScope)  // then we are initializing in external window
248         {
249             Firebug.setChrome(this, "detached"); // 1.4
250 
251             var browser = context ? context.browser : this.getCurrentBrowser();
252             Firebug.showContext(browser, context);
253 
254             if (FBTrace.DBG_WINDOWS)
255                 FBTrace.sysout("attachBrowser inDetachedScope in Firebug.chrome.window: "+Firebug.chrome.window.location);
256         }
257 
258     },
259 
260     undetach: function()
261     {
262         var detachedChrome = Firebug.chrome;
263         Firebug.setChrome(Firebug.originalChrome, "minimized");
264 
265         Firebug.showBar(false);
266         Firebug.resetTooltip();
267 
268         // when we are done here the window.closed will be true so we don't want to hang on to the ref.
269         detachedChrome.window = "This is detached chrome!";
270     },
271 
272     disableOff: function(collapse)
273     {
274         FBL.collapse($("fbCloseButton"), collapse);  // disable/enable this button in the Firebug.chrome window.
275     },
276 
277     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
278 
279     getBrowsers: function()
280     {
281           return Firebug.tabBrowser.browsers;
282     },
283 
284     getCurrentBrowser: function()
285     {
286         return Firebug.tabBrowser.selectedBrowser;
287     },
288 
289     getCurrentURI: function()
290     {
291         try
292         {
293             return Firebug.tabBrowser.currentURI;
294         }
295         catch (exc)
296         {
297             return null;
298         }
299     },
300 
301     getPanelDocument: function(panelType)
302     {
303         if (!panelType.prototype.parentPanel)
304             return panelBar1.browser.contentDocument;
305         else
306             return panelBar2.browser.contentDocument;
307     },
308 
309     getPanelBrowser: function(panel)
310     {
311         if (!panel.parentPanel)
312             return panelBar1.browser;
313         else
314             return panelBar2.browser;
315     },
316 
317     savePanels: function()
318     {
319         var path = this.writePanels(panelBar1.browser.contentDocument);
320         $("fbStatusText").setAttribute("value", path);
321         if (FBTrace.DBG_PANELS)
322             FBTrace.sysout("Wrote panels to "+path+"\n");
323     },
324 
325     writePanels: function(doc)
326     {
327         var serializer = new XMLSerializer();
328         var foStream = Components.classes["@mozilla.org/network/file-output-stream;1"]
329                .createInstance(Components.interfaces.nsIFileOutputStream);
330         var file = Components.classes["@mozilla.org/file/directory_service;1"]
331            .getService(Components.interfaces.nsIProperties)
332            .get("TmpD", Components.interfaces.nsIFile);
333         file.append("firebug");   // extensions sub-directory
334         file.append("panelSave.html");
335         file.createUnique(Components.interfaces.nsIFile.NORMAL_FILE_TYPE, 0666);
336         foStream.init(file, 0x02 | 0x08 | 0x20, 0664, 0);   // write, create, truncate
337         serializer.serializeToStream(doc, foStream, "");   // rememeber, doc is the DOM tree
338         foStream.close();
339         return file.path;
340     },
341 
342     updatePanelBar1: function(panelTypes)  // part of initializeUI
343     {
344         var mainPanelTypes = [];
345         for (var i = 0; i < panelTypes.length; ++i)
346         {
347             var panelType = panelTypes[i];
348             if (!panelType.prototype.parentPanel && !panelType.hidden)
349                 mainPanelTypes.push(panelType);
350         }
351         panelBar1.updatePanels(mainPanelTypes);
352     },
353 
354     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
355 
356     getName: function()
357     {
358         return window ? window.location.href : null;
359     },
360 
361     close: function()
362     {
363         if (FBTrace.DBG_INITIALIZE)
364             FBTrace.sysout("chrome.close closing window "+window.location);
365         window.close();
366     },
367 
368     focus: function()
369     {
370         window.focus();
371         panelBar1.browser.contentWindow.focus();
372     },
373 
374     isFocused: function()
375     {
376         var winMediator = CCSV("@mozilla.org/appshell/window-mediator;1", "nsIWindowMediator");
377 
378         return winMediator.getMostRecentWindow(null) == window;
379     },
380 
381     isOpen: function()
382     {
383         return !($("fbContentBox").collapsed);
384     },
385 
386     reload: function(skipCache)
387     {
388         var reloadFlags = skipCache
389             ? LOAD_FLAGS_BYPASS_PROXY | LOAD_FLAGS_BYPASS_CACHE
390             : LOAD_FLAGS_NONE;
391 
392         // Make sure the selected tab in the attached browser window is refreshed.
393         var browser = Firebug.chrome.getCurrentBrowser();
394         browser.firebugReload = true;
395         browser.webNavigation.reload(reloadFlags);
396 
397         if (FBTrace.DBG_WINDOWS)
398             FBTrace.sysout("chrome.reload; " + skipCache + ", " + browser.currentURI.spec);
399     },
400 
401     gotoPreviousTab: function()
402     {
403         if (FirebugContext.previousPanelName)
404             this.selectPanel(FirebugContext.previousPanelName);
405     },
406 
407     gotoSiblingTab : function(goRight)
408     {
409         if ($('fbContentBox').collapsed)
410             return;
411         var i, currentIndex = newIndex = -1, currentPanel = this.getSelectedPanel(), newPanel;
412         var panelTypes = Firebug.getMainPanelTypes(FirebugContext);
413         /*get current panel's index (is there a simpler way for this?*/
414         for (i = 0; i < panelTypes.length; i++)
415         {
416             if (panelTypes[i].prototype.name === currentPanel.name)
417             {
418                 currentIndex = i;
419                 break;
420             }
421         }
422         if (currentIndex != -1)
423         {
424             newIndex = goRight ? (currentIndex == panelTypes.length - 1 ? 0 : ++currentIndex) : (currentIndex == 0 ? panelTypes.length - 1 : --currentIndex);
425             newPanel = panelTypes[newIndex].prototype;
426             if (newPanel && newPanel.name)
427             {
428                 this.selectPanel(newPanel.name);
429             }
430         }
431     },
432 
433     getNextObject: function(reverse)
434     {
435         var panel = FirebugContext.getPanel(FirebugContext.panelName);
436         if (panel)
437         {
438             var item = panelStatus.getItemByObject(panel.selection);
439             if (item)
440             {
441                 if (reverse)
442                     item = item.previousSibling ? item.previousSibling.previousSibling : null;
443                 else
444                     item = item.nextSibling ? item.nextSibling.nextSibling : null;
445 
446                 if (item)
447                     return item.repObject;
448             }
449         }
450     },
451 
452     gotoNextObject: function(reverse)
453     {
454         var nextObject = this.getNextObject(reverse);
455         if (nextObject)
456             this.select(nextObject);
457         else
458             beep();
459     },
460 
461     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
462     // Panels
463 
464     navigate: function(object, panelName, sidePanelName)
465     {
466         var panel;
467         if (panelName || sidePanelName)
468             panel = this.selectPanel(panelName, sidePanelName);
469         else
470             panel = this.getSelectedPanel();
471 
472         if (panel)
473             panel.navigate(object);
474     },
475 
476     select: function(object, panelName, sidePanelName, forceUpdate)
477     {
478         if (FBTrace.DBG_PANELS)
479             FBTrace.sysout("chrome.select object:"+object+" panelName:"+panelName+" sidePanelName:"+sidePanelName+" forceUpdate:"+forceUpdate+"\n");
480         var bestPanelName = getBestPanelName(object, FirebugContext, panelName);
481         var panel = this.selectPanel(bestPanelName, sidePanelName, true);
482         if (panel)
483             panel.select(object, forceUpdate);
484     },
485 
486     selectPanel: function(panelName, sidePanelName, noRefresh)
487     {
488         if (panelName && sidePanelName)
489             FirebugContext.sidePanelNames[panelName] = sidePanelName;
490 
491         return panelBar1.selectPanel(panelName, false, noRefresh);  // cause panel visibility changes and events
492     },
493 
494     selectSidePanel: function(panelName)
495     {
496         return panelBar2.selectPanel(panelName);
497     },
498 
499     clearPanels: function()
500     {
501         panelBar1.hideSelectedPanel();
502         panelBar1.selectedPanel = null;
503         panelBar2.selectedPanel = null;
504     },
505 
506     getSelectedPanel: function()
507     {
508         return panelBar1.selectedPanel;
509     },
510 
511     getSelectedSidePanel: function()
512     {
513         return panelBar2.selectedPanel;
514     },
515 
516     switchToPanel: function(context, switchToPanelName)
517     {
518         // Remember the previous panel and bar state so we can revert if the user cancels
519         this.previousPanelName = context.panelName;
520         this.previousSidePanelName = context.sidePanelName;
521         this.previouslyCollapsed = $("fbContentBox").collapsed;
522         this.previouslyFocused = Firebug.isDetached() && this.isFocused();  // TODO previouslyMinimized
523 
524         var switchPanel = this.selectPanel(switchToPanelName);
525         if (switchPanel)
526             this.previousObject = switchPanel.selection;
527 
528         return switchPanel;
529     },
530 
531     unswitchToPanel: function(context, switchToPanelName, cancelled)
532     {
533         var switchToPanel = context.getPanel(switchToPanelName);
534 
535         if (this.previouslyFocused)
536             this.focus();
537 
538         if (cancelled && this.previousPanelName)  // revert
539         {
540             if (this.previouslyCollapsed)
541                 Firebug.showBar(false);
542 
543             if (this.previousPanelName == switchToPanelName)
544                 this.select(this.previousObject);
545             else
546                 this.selectPanel(this.previousPanelName, this.previousSidePanelName);
547         }
548         else // else stay on the switchToPanel
549         {
550             this.selectPanel(switchToPanelName);
551             if (switchToPanel.selection)
552                 this.select(switchToPanel.selection);
553             this.getSelectedPanel().panelNode.focus();
554         }
555 
556         delete this.previousObject;
557         delete this.previousPanelName;
558         delete this.previousSidePanelName;
559         delete this.inspectingChrome;
560 
561         return switchToPanel;
562     },
563 
564     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
565     // Location interface provider for binding.xml panelFileList
566 
567     getLocationProvider: function()
568     {
569         // a function that returns an object with .getObjectDescription() and .getLocationList()
570         return function getSelectedPanelFromCurrentContext()
571         {
572             return Firebug.chrome.getSelectedPanel();  // panels provide location, use the selected panel
573         }
574     },
575 
576     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
577     // UI Synchronization
578 
579     setFirebugContext: function(context)
580     {
581          // This sets the global value of FirebugContext in the window that this chrome is compiled into.
582          // Note that for firebug.xul, the Firebug object is shared across windows, but not FirebugChrome and FirebugContext
583          FirebugContext = context;
584 
585          if (FBTrace.DBG_WINDOWS || FBTrace.DBG_DISPATCH)
586              FBTrace.sysout("setFirebugContext "+(FirebugContext?FirebugContext.getName():" **> NULL <** ") + " in "+window.location+" has wrapped: "+(FirebugContext?FirebugContext.wrappedJSObject:"no"));
587     },
588 
589     hidePanel: function()
590     {
591         if (panelBar1.selectedPanel)
592             panelBar1.hideSelectedPanel()
593 
594         if (panelBar2.selectedPanel)
595             panelBar2.hideSelectedPanel()
596     },
597 
598     syncPanel: function()
599     {
600         if (FBTrace.DBG_PANELS) FBTrace.sysout("chrome.syncPanel FirebugContext="+
601             (FirebugContext ? FirebugContext.getName() : "undefined")+"\n");
602 
603         panelStatus.clear();
604 
605         if (FirebugContext)
606         {
607             var panelName = FirebugContext.panelName
608                 ? FirebugContext.panelName
609                 : Firebug.defaultPanelName;
610 
611             // Make HTML panel the default panel, which is displayed
612             // to the user the very first time.
613             if (!panelName || !Firebug.getPanelType(panelName))
614                 panelName = "html";
615 
616             this.syncMainPanels();
617             panelBar1.selectPanel(panelName, true);
618         }
619         else
620         {
621             panelBar1.selectPanel(null, true);
622         }
623 
624         if (Firebug.isDetached())
625             this.syncTitle();
626     },
627 
628     syncMainPanels: function()
629     {
630         var panelTypes = Firebug.getMainPanelTypes(FirebugContext);
631         panelBar1.updatePanels(panelTypes);
632     },
633 
634     syncSidePanels: function()
635     {
636         var panelTypes = Firebug.getSidePanelTypes(FirebugContext, panelBar1.selectedPanel);
637         panelBar2.updatePanels(panelTypes);
638 
639         if (FirebugContext && FirebugContext.sidePanelNames)
640         {
641             var sidePanelName = FirebugContext.sidePanelNames[FirebugContext.panelName];
642             sidePanelName = getBestSidePanelName(sidePanelName, panelTypes);
643             panelBar2.selectPanel(sidePanelName, true);
644         }
645         else
646             panelBar2.selectPanel(null);
647 
648         sidePanelDeck.selectedPanel = panelBar2;
649         FBL.collapse(sidePanelDeck, !panelBar2.selectedPanel);
650         FBL.collapse(panelSplitter, !panelBar2.selectedPanel);
651     },
652 
653     syncTitle: function()
654     {
655         if (FirebugContext)
656         {
657             var title = FirebugContext.getTitle();
658             window.document.title = FBL.$STRF("WindowTitle", [title]);
659         }
660         else
661             window.document.title = FBL.$STR("Firebug");
662     },
663 
664     focusLocationList: function()
665     {
666         locationList.popup.showPopup(locationList, -1, -1, "popup", "bottomleft", "topleft");
667     },
668 
669     syncLocationList: function()
670     {
671         var panel = panelBar1.selectedPanel;
672         if (panel && panel.location)
673         {
674             locationList.location = panel.location;
675             FBL.collapse(locationSeparator, false);
676             FBL.collapse(locationList, false);
677         }
678         else
679         {
680             FBL.collapse(locationSeparator, true);
681             FBL.collapse(locationList, true);
682         }
683     },
684 
685     clearStatusPath: function()
686     {
687         panelStatus.clear();
688     },
689 
690     syncStatusPath: function()
691     {
692         var panel = panelBar1.selectedPanel;
693         if (!panel || (panel && !panel.selection))
694         {
695             panelStatus.clear();
696         }
697         else
698         {
699             var path = panel.getObjectPath(panel.selection);
700             if (!path || !path.length)
701             {
702                 FBL.hide(panelStatusSeparator, true);
703                 panelStatus.clear();
704             }
705             else
706             {
707                 FBL.hide(panelStatusSeparator, false);
708 
709                 if (panel.name != panelStatus.lastPanelName)
710                     panelStatus.clear();
711 
712                 panelStatus.lastPanelName = panel.name;
713 
714                 // If the object already exists in the list, just select it and keep the path
715                 var selection = panel.selection;
716                 var existingItem = panelStatus.getItemByObject(panel.selection);
717                 if (existingItem)
718                     panelStatus.selectItem(existingItem);
719                 else
720                 {
721                     panelStatus.clear();
722 
723                     for (var i = 0; i < path.length; ++i)
724                     {
725                         var object = path[i];
726 
727                         var rep = Firebug.getRep(object);
728                         var objectTitle = rep.getTitle(object, FirebugContext);
729 
730                         var title = FBL.cropMultipleLines(objectTitle, statusCropSize);
731                         panelStatus.addItem(title, object, rep, panel.statusSeparator);
732                     }
733 
734                     panelStatus.selectObject(panel.selection);
735                 }
736             }
737         }
738     },
739 
740     toggleOrient: function()
741     {
742         var panelPane = $("fbPanelPane");
743         panelSplitter.orient = panelPane.orient
744             = panelPane.orient == "vertical" ? "horizontal" : "vertical";
745         var option = $('menu_toggleOrient').getAttribute("option");
746         Firebug.setPref(Firebug.prefDomain, option, panelPane.orient != "vertical");
747     },
748 
749     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
750 
751     addTab: function(context, url, title, parentPanel)
752     {
753         context.addPanelType(url, title, parentPanel);
754         if (context == FirebugContext)
755         {
756             if (parentPanel)
757             {
758                 var currentPanel = this.getSelectedPanel();
759                 if (currentPanel && parentPanel == currentPanel.name)
760                     this.syncSidePanels();
761             }
762             else
763             {
764                 this.syncMainPanels();
765             }
766         }
767     },
768 
769     removeTab: function(context, url)
770     {
771         context.removePanelType(url);
772     },
773 
774     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
775 
776     getGlobalAttribute: function(id, name)
777     {
778         var elt = $(id);
779         return elt.getAttribute(name);
780     },
781 
782     setGlobalAttribute: function(id, name, value)
783     {
784         var elt = $(id);
785         if (elt)
786         {
787             if (value == null)
788                 elt.removeAttribute(name);
789             else
790                 elt.setAttribute(name, value);
791         }
792 
793         if (Firebug.externalChrome)
794             Firebug.externalChrome.setGlobalAttribute(id, name, value);
795     },
796 
797 
798     setChromeDocumentAttribute: function(id, name, value)
799     {
800         // Call as  Firebug.chrome.setChromeDocumentAttribute() to set attributes in another window.
801         var elt = $(id);
802         if (elt)
803             elt.setAttribute(name, value);
804     },
805 
806     keyCodeListen: function(key, filter, listener, capture)
807     {
808         if (!filter)
809             filter = FBL.noKeyModifiers;
810 
811         var keyCode = KeyEvent["DOM_VK_"+key];
812 
813         function fn(event)
814         {
815             if (event.keyCode == keyCode && (!filter || filter(event)))
816             {
817                 listener();
818                 FBL.cancelEvent(event);
819             }
820         }
821 
822         window.addEventListener("keypress", fn, capture);
823 
824         return [fn, capture];
825     },
826 
827     keyListen: function(ch, filter, listener, capture)
828     {
829         if (!filter)
830             filter = FBL.noKeyModifiers;
831 
832         var charCode = ch.charCodeAt(0);
833 
834         function fn(event)
835         {
836             if (event.charCode == charCode && (!filter || filter(event)))
837             {
838                 listener();
839                 FBL.cancelEvent(event);
840             }
841         }
842 
843         window.addEventListener("keypress", fn, capture);
844 
845         return [fn, capture];
846     },
847 
848     keyIgnore: function(listener)
849     {
850         window.removeEventListener("keypress", listener[0], listener[1]);
851     },
852 
853     $: function(id)
854     {
855         return $(id);
856     },
857 
858     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
859 
860     applyTextSize: function(value)
861     {
862         var zoom = value >= 0 ? positiveZoomFactors[value] : negativeZoomFactors[Math.abs(value)];
863 
864         panelBar1.browser.markupDocumentViewer.textZoom = zoom;
865         panelBar2.browser.markupDocumentViewer.textZoom = zoom;
866     },
867 
868     obeyOmitObjectPathStack: function(value)
869     {
870         FBL.hide(panelStatus, (value?true:false));
871     },
872 
873     getPanelStatusElements: function()
874     {
875         return panelStatus;
876     },
877 
878     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
879     // UI Event Listeners uilisteners  or "panelListeners"
880 
881     onPanelNavigate: function(object, panel)
882     {
883         this.syncLocationList();
884     },
885 
886     onObjectSelected: function(object, panel)
887     {
888         if (panel == panelBar1.selectedPanel)
889         {
890             this.syncStatusPath();
891 
892             var sidePanel = panelBar2.selectedPanel;
893             if (sidePanel)
894                 sidePanel.select(object);
895         }
896     },
897 
898     onApplyDecorator: function(sourceBox) // called on setTimeout after sourceBox viewport has been repainted
899     {
900     },
901 
902     onViewportChange: function(sourceLink) // called on scrollTo, passing in the selected line
903     {
904     },
905 
906     showUI: function(browser, context) // called when the Firebug UI comes up in browser or detached
907     {
908     },
909 
910     hideUI: function(browser, context)  // called when the Firebug UI comes down; context may be null
911     {
912     },
913 
914     //* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
915 
916     onOptionsShowing: function(popup)
917     {
918         for (var child = popup.firstChild; child; child = child.nextSibling)
919         {
920             if (child.localName == "menuitem")
921             {
922                 var option = child.getAttribute("option");
923                 if (option)
924                 {
925                     var checked = false;
926                     if (option == "profiling")
927                         checked = fbs.profiling;
928                     else
929                         checked = Firebug.getPref(Firebug.prefDomain, option);
930 
931                     child.setAttribute("checked", checked);
932                 }
933             }
934         }
935     },
936 
937     onToggleOption: function(menuitem)
938     {
939         var option = menuitem.getAttribute("option");
940         var checked = menuitem.getAttribute("checked") == "true";
941 
942         Firebug.setPref(Firebug.prefDomain, option, checked);
943     },
944 
945     onContextShowing: function(event)
946     {
947         // xxxHonza: This context-menu support can be used even in separate window, which
948         // doesn't contain the FBUI (panels).
949         //if (!panelBar1.selectedPanel)
950         //    return false;
951 
952         var popup = $("fbContextMenu");
953         var target = document.popupNode;
954         var panel = target ? Firebug.getElementPanel(target) : null;
955 
956         if (!panel)
957             panel = panelBar1 ? panelBar1.selectedPanel : null; // the event must be on our chrome not inside the panel
958 
959         FBL.eraseNode(popup);
960 
961         // Make sure the Copy action is only available if there is actually someting
962         // selected in the panel.
963         var sel = target.ownerDocument.defaultView.getSelection();
964         if (!this.contextMenuObject && !$("cmd_copy").getAttribute("disabled") && !sel.isCollapsed)
965         {
966             var menuitem = FBL.createMenuItem(popup, {label: "Copy"});
967             menuitem.setAttribute("command", "cmd_copy");
968         }
969 
970         var object;
971         if (this.contextMenuObject)
972             object = this.contextMenuObject;
973         else if (target && target.ownerDocument == document)
974             object = Firebug.getRepObject(target);
975         else if (target && panel)
976             object = panel.getPopupObject(target);
977         else if (target)
978             object = Firebug.getRepObject(target); // xxxHonza: What about a node from different document? Is that OK?
979 
980         this.contextMenuObject = null;
981 
982         var rep = Firebug.getRep(object);
983         var realObject = rep ? rep.getRealObject(object, FirebugContext) : null;
984         var realRep = realObject ? Firebug.getRep(realObject) : null;
985 
986         if (FBTrace.DBG_OPTIONS)
987             FBTrace.sysout("chrome.onContextShowing object:"+object+" rep: "+rep+" realObject: "+realObject+" realRep:"+realRep+"\n");
988 
989         if (realObject && realRep)
990         {
991             // 1. Add the custom menu items from the realRep
992             var menu = realRep.getContextMenuItems(realObject, target, FirebugContext);
993             if (menu)
994             {
995                 for (var i = 0; i < menu.length; ++i)
996                     FBL.createMenuItem(popup, menu[i]);
997             }
998         }
999 
1000         if (object && rep && rep != realRep)
1001         {
1002             // 1. Add the custom menu items from the original rep
1003             var items = rep.getContextMenuItems(object, target, FirebugContext);
1004             if (items)
1005             {
1006                 for (var i = 0; i < items.length; ++i)
1007                     FBL.createMenuItem(popup, items[i]);
1008             }
1009         }
1010 
1011         // 1. Add the custom menu items from the panel
1012         if (panel)
1013         {
1014             var items = panel.getContextMenuItems(realObject, target);
1015             if (items)
1016             {
1017                 for (var i = 0; i < items.length; ++i)
1018                     FBL.createMenuItem(popup, items[i]);
1019             }
1020         }
1021 
1022         // 2. Add the inspect menu items
1023         if (realObject && rep && rep.inspectable)
1024         {
1025             var separator = null;
1026 
1027             var items = this.getInspectMenuItems(realObject);
1028             for (var i = 0; i < items.length; ++i)
1029             {
1030                 if (popup.firstChild && !separator)
1031                     separator = FBL.createMenuSeparator(popup);
1032 
1033                 FBL.createMenuItem(popup, items[i]);
1034             }
1035         }
1036 
1037         if (!popup.firstChild)
1038             return false;
1039     },
1040 
1041     onEditorsShowing: function(popup)  // TODO move to Firebug.Editors module in editors.js
1042     {
1043         var editors = Firebug.registeredEditors;
1044         if ( editors.length > 0 )
1045         {
1046             var lastChild = popup.lastChild;
1047             FBL.eraseNode(popup);
1048             var disabled = (!FirebugContext);
1049             for( var i = 0; i < editors.length; ++i )
1050             {
1051                 if (editors[i] == "-")
1052                 {
1053                     FBL.createMenuItem(popup, "-");
1054                     continue;
1055                 }
1056                 var item = {label: editors[i].label, image: editors[i].image,
1057                                 nol10n: true, disabled: disabled };
1058                 var menuitem = FBL.createMenuItem(popup, item);
1059                 menuitem.setAttribute("command", "cmd_openInEditor");
1060                 menuitem.value = editors[i].id;
1061             }
1062             FBL.createMenuItem(popup, "-");
1063             popup.appendChild(lastChild);
1064         }
1065     },
1066 
1067     getInspectMenuItems: function(object)
1068     {
1069         var items = [];
1070 
1071         // Domplate (+ support for context menus) can be used even in separate
1072         // windows when FirebugContext doesn't have to be defined.
1073         if (!FirebugContext)
1074             return items;
1075 
1076         for (var i = 0; i < Firebug.panelTypes.length; ++i)
1077         {
1078             var panelType = Firebug.panelTypes[i];
1079             if (!panelType.prototype.parentPanel
1080                 && panelType.prototype.name != FirebugContext.panelName
1081                 && panelSupportsObject(panelType, object))
1082             {
1083                 var panelName = panelType.prototype.name;
1084 
1085                 var title = Firebug.getPanelTitle(panelType);
1086                 var label = FBL.$STRF("InspectInTab", [title]);
1087 
1088                 var command = bindFixed(this.select, this, object, panelName);
1089                 items.push({label: label, command: command, nol10n: true});
1090             }
1091         }
1092 
1093         return items;
1094     },
1095 
1096     onTooltipShowing: function(event)
1097     {
1098         // xxxHonza: This tooltip support can be used even in separate window, which
1099         // doesn't contain the FBUI (panels).
1100         //if (!panelBar1.selectedPanel)
1101         //    return false;
1102 
1103         var tooltip = $("fbTooltip");
1104         var target = document.tooltipNode;
1105 
1106         var panel = target ? Firebug.getElementPanel(target) : null;
1107 
1108         var object;
1109         /* XXXjjb This causes the Script panel to show the function body over and over. We need to clear it at least,
1110          * but really we need to understand why the tooltip should show the context menu object at all.
1111          * One thing the contextMenuObject supports is peeking at function bodies when stopped a breakpoint.
1112          * That case could be supported with clearing the contextMenuObject, but we don't know if that breaks
1113          * something else. So maybe a popupMenuObject should be set on the context if that is what we want to support
1114          * The other complication is that there seems to be another tooltip.
1115         if (this.contextMenuObject)
1116         {
1117             object = this.contextMenuObject;
1118             FBTrace.sysout("tooltip by contextMenuObject");
1119         }
1120         else*/
1121         if (target && target.ownerDocument == document)
1122             object = Firebug.getRepObject(target);
1123         else if (panel)
1124             object = panel.getTooltipObject(target);
1125 
1126         var rep = object ? Firebug.getRep(object) : null;
1127         object = rep ? rep.getRealObject(object, FirebugContext) : null;
1128         rep = object ? Firebug.getRep(object) : null;
1129 
1130         if (object && rep)
1131         {
1132             var label = rep.getTooltip(object, FirebugContext);
1133             if (label)
1134             {
1135                 tooltip.setAttribute("label", label);
1136                 return true;
1137             }
1138         }
1139 
1140         if (FBL.hasClass(target, 'noteInToolTip'))
1141             FBL.setClass(tooltip, 'noteInToolTip');
1142         else
1143             FBL.removeClass(tooltip, 'noteInToolTip');
1144 
1145         if (target.hasAttribute("title"))
1146         {
1147             tooltip.setAttribute("label", target.getAttribute("title"));
1148             return true;
1149         }
1150 
1151         return false;
1152     },
1153 
1154     openAboutDialog: function()
1155     {
1156         var extensionManager = CCSV("@mozilla.org/extensions/manager;1", "nsIExtensionManager");
1157         openDialog("chrome://mozapps/content/extensions/about.xul", "",
1158             "chrome,centerscreen,modal", "urn:mozilla:item:firebug@software.joehewitt.com", extensionManager.datasource);
1159     },
1160 
1161     breakOnNext: function(context, event)
1162     {
1163         // Avoid bubbling from associated options.
1164         if (event.target.id != "cmd_breakOnNext")
1165             return;
1166 
1167         if (!context)
1168         {
1169             if (FBTrace.DBG_BP)
1170                 FBTrace.sysout("Firebug chrome: breakOnNext with no context??");
1171             return;
1172         }
1173 
1174         var panel = panelBar1.selectedPanel;
1175 
1176         if (FBTrace.DBG_BP)
1177             FBTrace.sysout("Firebug chrome: breakOnNext for panel " +
1178                 (panel ? panel.name : "NO panel"), panel);
1179 
1180         if (panel && panel.breakable)
1181             Firebug.Breakpoint.toggleBreakOnNext(panel);
1182     },
1183 };
1184 
1185 // ************************************************************************************************
1186 // Local Helpers
1187 
1188 function panelSupportsObject(panelType, object)
1189 {
1190     if (panelType)
1191     {
1192         try {
1193             // This tends to throw exceptions often because some objects are weird
1194             return panelType.prototype.supportsObject(object)
1195         } catch (exc) {}
1196     }
1197 
1198     return 0;
1199 }
1200 
1201 function getBestPanelName(object, context, panelName)
1202 {
1203     if (!panelName)
1204         panelName = context.panelName;
1205 
1206     // Check if the suggested panel name supports the object, and if so, go with it
1207     if (panelName)
1208     {
1209         panelType = Firebug.getPanelType(panelName);
1210         if (panelSupportsObject(panelType, object))
1211             return panelType.prototype.name;
1212     }
1213 
1214     // The suggested name didn't pan out, so search for the panel type with the
1215     // most specific level of support
1216 
1217     var bestLevel = 0;
1218     var bestPanel = null;
1219 
1220     for (var i = 0; i < Firebug.panelTypes.length; ++i)
1221     {
1222         var panelType = Firebug.panelTypes[i];
1223         if (!panelType.prototype.parentPanel)
1224         {
1225             var level = panelSupportsObject(panelType, object);
1226             if (!bestLevel || (level && (level > bestLevel) ))
1227             {
1228                 bestLevel = level;
1229                 bestPanel = panelType;
1230             }
1231             if (FBTrace.DBG_PANELS)
1232                 FBTrace.sysout("chrome.getBestPanelName panelType: "+panelType.prototype.name+" level: "+level+" bestPanel: "+ (bestPanel ? bestPanel.prototype.name : "null")+" bestLevel: "+bestLevel+"\n");
1233         }
1234     }
1235 
1236     return bestPanel ? bestPanel.prototype.name : null;
1237 }
1238 
1239 function getBestSidePanelName(sidePanelName, panelTypes)
1240 {
1241     if (sidePanelName)
1242     {
1243         // Verify that the suggested panel name is in the acceptable list
1244         for (var i = 0; i < panelTypes.length; ++i)
1245         {
1246             if (panelTypes[i].prototype.name == sidePanelName)
1247                 return sidePanelName;
1248         }
1249     }
1250 
1251     // Default to the first panel type in the list
1252     return panelTypes.length ? panelTypes[0].prototype.name : null;
1253 }
1254 
1255 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
1256 // Event listeners
1257 
1258 function browser1Loaded()
1259 {
1260     if (FBTrace.DBG_INITIALIZE)
1261         FBTrace.sysout("browse1Loaded\n");
1262     var browser1 = panelBar1.browser;
1263     browser1.removeEventListener("load", browser1Loaded, true);
1264 
1265     browser1.contentDocument.title = "Firebug Main Panel";
1266     browser1Loaded.complete = true;
1267 
1268     if (browser1Loaded.complete && browser2Loaded.complete)
1269         FirebugChrome.initializeUI();
1270 }
1271 
1272 function browser2Loaded()
1273 {
1274     if (FBTrace.DBG_INITIALIZE)
1275         FBTrace.sysout("browse2Loaded\n");
1276     var browser2 = panelBar2.browser;
1277     browser2.removeEventListener("load", browser2Loaded, true);
1278 
1279     browser2.contentDocument.title = "Firebug Side Panel";
1280     browser2Loaded.complete = true;
1281 
1282     if (browser1Loaded.complete && browser2Loaded.complete)
1283         FirebugChrome.initializeUI();  // the chrome bound into this scope
1284 
1285     if (FBTrace.DBG_INITIALIZE)
1286         FBTrace.sysout("browse2Loaded complete\n");
1287 }
1288 
1289 function onBlur(event)
1290 {
1291     // XXXjjb this seems like a waste: called continuously to clear possible highlight I guess.
1292     // XXXhh Is this really necessary? I disabled it for now as this was preventing me to show highlights on focus
1293     //Firebug.Inspector.highlightObject(null, FirebugContext);
1294 }
1295 
1296 function onSelectLocation(event)
1297 {
1298     var location = locationList.repObject;
1299     FirebugChrome.navigate(location);
1300 }
1301 
1302 function onSelectingPanel(event)
1303 {
1304     var panel = panelBar1.selectedPanel;
1305     var panelName = panel ? panel.name : null;
1306     if (FBTrace.DBG_PANELS)
1307         FBTrace.sysout("chrome.onSelectingPanel="+panelName+" FirebugContext="+(FirebugContext?FirebugContext.getName():"undefined")+"\n");
1308 
1309     if (FirebugContext)
1310     {
1311         FirebugContext.previousPanelName = FirebugContext.panelName;
1312         FirebugContext.panelName = panelName;
1313 
1314         FirebugContext.sidePanelName =
1315             FirebugContext.sidePanelNames && panelName in FirebugContext.sidePanelNames
1316             ? FirebugContext.sidePanelNames[panelName]
1317             : null;
1318     }
1319 
1320     if (panel)
1321     {
1322         panel.navigate(panel.location);
1323         Firebug.chrome.syncLocationList();
1324         Firebug.chrome.syncSidePanels();
1325         Firebug.chrome.syncStatusPath();
1326 
1327         Firebug.showPanel(panel.context.browser, panel);
1328     }
1329     else
1330     {
1331         var browser = FirebugChrome.getCurrentBrowser();
1332         Firebug.showPanel(browser, null);
1333     }
1334 }
1335 
1336 function onSelectedSidePanel(event)
1337 {
1338     var sidePanel = panelBar2.selectedPanel;
1339     if (FirebugContext)
1340     {
1341         var panelName = FirebugContext.panelName;
1342         if (panelName)
1343         {
1344             var sidePanelName = sidePanel ? sidePanel.name : null;
1345             FirebugContext.sidePanelNames[panelName] = sidePanelName;
1346         }
1347         else
1348         {
1349             if (FBTrace.DBG_ERRORS)
1350                 FBTrace.sysout("onSelectedSidePanel FirebugContext has no panelName: ",FirebugContext);
1351         }
1352     }
1353     if (FBTrace.DBG_PANELS) FBTrace.sysout("chrome.onSelectedSidePanel name="+(sidePanel?sidePanel.name:"undefined")+"\n");
1354 
1355     var panel = panelBar1.selectedPanel;
1356     if (panel && sidePanel)
1357         sidePanel.select(panel.selection);
1358 
1359     var browser = sidePanel ? sidePanel.context.browser : FirebugChrome.getCurrentBrowser();
1360     Firebug.showSidePanel(browser, sidePanel);  // dispatch to modules
1361 }
1362 
1363 function onPanelMouseOver(event)
1364 {
1365     var object = Firebug.getRepObject(event.target);
1366     if (object)
1367     {
1368         var realObject = getRealObject(object);
1369         if (realObject)
1370             Firebug.Inspector.highlightObject(realObject, FirebugContext);
1371     }
1372 }
1373 
1374 function onPanelMouseOut(event)
1375 {
1376     Firebug.Inspector.highlightObject(null);
1377 }
1378 
1379 function onPanelClick(event)
1380 {
1381     var repNode = Firebug.getRepNode(event.target);
1382     if (repNode)
1383     {
1384         var object = repNode.repObject;
1385         var rep = Firebug.getRep(object);
1386         var realObject = rep ? rep.getRealObject(object, FirebugContext) : null;
1387         var realRep = realObject ? Firebug.getRep(realObject) : rep;
1388         if (!realObject)
1389             realObject = object;
1390 
1391         if (FBL.isLeftClick(event))
1392         {
1393             if (FBL.hasClass(repNode, "objectLink"))
1394             {
1395                 if (realRep)
1396                 {
1397                     realRep.inspectObject(realObject, FirebugContext);
1398                     FBL.cancelEvent(event);
1399                 }
1400             }
1401         }
1402         else if (FBL.isControlClick(event) || FBL.isMiddleClick(event))
1403         {
1404             if (!realRep || !realRep.browseObject(realObject, FirebugContext))
1405             {
1406                 if (rep && !(rep != realRep && rep.browseObject(object, FirebugContext)))
1407                 {
1408                     var panel = Firebug.getElementPanel(event.target);
1409                     if (!panel || !panel.browseObject(realObject))
1410                         return;
1411                 }
1412             }
1413             FBL.cancelEvent(event);
1414         }
1415     }
1416 }
1417 
1418 function onPanelMouseDown(event)
1419 {
1420     if (FBL.isLeftClick(event))
1421     {
1422         var editable = FBL.getAncestorByClass(event.target, "editable");
1423         if (editable)
1424         {
1425             Firebug.Editor.startEditing(editable);
1426             FBL.cancelEvent(event);
1427         }
1428     }
1429     else if (FBL.isMiddleClick(event) && Firebug.getRepNode(event.target))
1430     {
1431         // Prevent auto-scroll when middle-clicking a rep object
1432         FBL.cancelEvent(event);
1433     }
1434 }
1435 
1436 function onMainTabBoxMouseDown(event)
1437 {
1438     if (Firebug.isInBrowser())
1439     {
1440         var contentSplitter = Firebug.chrome.$("fbContentSplitter");
1441         if (FBTrace.DBG_ERRORS)
1442             FBTrace.sysout("onMainTabBoxMouseDown ", event);
1443         // TODO: grab the splitter here.
1444     }
1445 }
1446 
1447 function getRealObject(object)
1448 {
1449     var rep = Firebug.getRep(object);
1450     var realObject = rep ? rep.getRealObject(object, FirebugContext) : null;
1451     return realObject ? realObject : object;
1452 }
1453 
1454 
1455 // ************************************************************************************************
1456 // Utils (duplicated from lib.js)
1457 
1458 function $(id, doc)
1459 {
1460     if (doc)
1461         return doc.getElementById(id);
1462     else
1463         return document.getElementById(id);
1464 }
1465 
1466 function cloneArray(array, fn)
1467 {
1468    var newArray = [];
1469 
1470    for (var i = 0; i < array.length; ++i)
1471        newArray.push(array[i]);
1472 
1473    return newArray;
1474 }
1475 
1476 function bindFixed()
1477 {
1478     var args = cloneArray(arguments), fn = args.shift(), object = args.shift();
1479     return function() { return fn.apply(object, args); }
1480 }
1481 
1482 }})();
1483 
1484 // ************************************************************************************************
1485 
1486 // XXXjoe This horrible hack works around a focus bug in Firefox which is caused when
1487 // the HTML Validator extension and Firebug are installed.  It causes the keyboard to
1488 // behave erratically when typing, and the only solution I've found is to delay
1489 // the initialization of HTML Validator by overriding this function with a timeout.
1490 // XXXrobc Do we still need this? Does this extension even exist anymore?
1491 if (top.hasOwnProperty('TidyBrowser'))
1492 {
1493     var prev = TidyBrowser.prototype.updateStatusBar;
1494     TidyBrowser.prototype.updateStatusBar = function()
1495     {
1496         var self = this, args = arguments;
1497         setTimeout(function()
1498         {
1499             prev.apply(self, args);
1500         });
1501     }
1502 }
1503 
1504 // ************************************************************************************************
1505 
1506 function dddx()
1507 {
1508     Firebug.Console.logFormatted(arguments);
1509 }
1510 
1511 
1512 
1513