1 /* See license.txt for terms of usage */
  2 
  3 /**
  4  * The 'context' in this file is always 'Firebug.currentContext'
  5  */
  6 define([
  7     "firebug/lib/object",
  8     "firebug/chrome/firefox",
  9     "firebug/lib/dom",
 10     "firebug/lib/css",
 11     "firebug/lib/system",
 12     "firebug/chrome/menu",
 13     "firebug/chrome/toolbar",
 14     "firebug/lib/url",
 15     "firebug/lib/locale",
 16     "firebug/lib/string",
 17     "firebug/lib/events",
 18     "firebug/js/fbs",
 19     "firebug/chrome/window",
 20     "firebug/lib/options"
 21 ],
 22 function chromeFactory(Obj, Firefox, Dom, Css, System, Menu, Toolbar, Url, Locale, String,
 23     Events, FBS, Win, Options) {
 24 
 25 // ********************************************************************************************* //
 26 // Constants
 27 
 28 const Cc = Components.classes;
 29 const Ci = Components.interfaces;
 30 const nsIWebNavigation = Ci.nsIWebNavigation;
 31 
 32 const wm = Cc["@mozilla.org/appshell/window-mediator;1"].getService(Ci.nsIWindowMediator);
 33 
 34 const LOAD_FLAGS_BYPASS_PROXY = nsIWebNavigation.LOAD_FLAGS_BYPASS_PROXY;
 35 const LOAD_FLAGS_BYPASS_CACHE = nsIWebNavigation.LOAD_FLAGS_BYPASS_CACHE;
 36 const LOAD_FLAGS_NONE = nsIWebNavigation.LOAD_FLAGS_NONE;
 37 
 38 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 39 
 40 const panelURL = "chrome://firebug/content/panel.html";
 41 
 42 // URLs used in the Firebug Menu and several other places
 43 const firebugURLs =
 44 {
 45     main: "https://getfirebug.com",
 46     help: "https://getfirebug.com/help",
 47     FAQ: "https://getfirebug.com/wiki/index.php/FAQ",
 48     docs: "https://getfirebug.com/docs.html",
 49     keyboard: "https://getfirebug.com/wiki/index.php/Keyboard_and_Mouse_Shortcuts",
 50     discuss: "https://groups.google.com/forum/#!forum/firebug",
 51     issues: "http://code.google.com/p/fbug/issues/list?can=1",
 52     donate: "https://getfirebug.com/getinvolved",
 53     extensions: "https://getfirebug.com/wiki/index.php/Firebug_Extensions",
 54     issue5110: "http://code.google.com/p/fbug/issues/detail?id=5110"
 55 };
 56 
 57 const statusCropSize = 20;
 58 
 59 // ********************************************************************************************* //
 60 
 61 // factory is global in module loading window
 62 var ChromeFactory =
 63 {
 64 
 65 // chrome is created in caller window.
 66 createFirebugChrome: function(win)
 67 {
 68     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 69     // Private
 70 
 71     var panelSplitter, sidePanelDeck, panelBar1, panelBar2;
 72 
 73     var disabledHead = null;
 74     var disabledCaption = null;
 75     var enableSiteLink = null;
 76     var enableSystemPagesLink = null;
 77     var enableAlwaysLink = null;
 78 
 79 var FirebugChrome =
 80 {
 81     // TODO: remove this property, add getters for location, title, focusedElement, setter popup
 82     dispatchName: "FirebugChrome",
 83 
 84     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 85     // Initialization
 86 
 87     /**
 88      * Called by panelBarWaiter when XUL panelBar(s) (main and side) are constructed
 89      * (i.e. the constructor of panelBar binding is executed twice) and when all Firebug
 90      * modules + extension modules (if any) are loaded.
 91      */
 92     initialize: function()
 93     {
 94 
 95         if (FBTrace.DBG_INITIALIZE)
 96             FBTrace.sysout("chrome.initialize;");
 97 
 98         this.window = win;
 99 
100         panelSplitter = this.getElementById("fbPanelSplitter");
101         sidePanelDeck = this.getElementById("fbSidePanelDeck");
102         panelBar1 = this.getElementById("fbPanelBar1");
103         panelBar2 = this.getElementById("fbPanelBar2");
104 
105         // Firebug has not been initialized yet
106         if (!Firebug.isInitialized)
107             Firebug.initialize(this);
108 
109         // FBL should be available at this moment.
110         if (FBTrace.sysout && (!FBL || !FBL.initialize))
111         {
112             FBTrace.sysout("Firebug is broken, FBL incomplete, if the last function is QI, " +
113                 "check lib.js:", FBL);
114         }
115 
116         var browser1Complete = false;
117         var browser2Complete = false;
118 
119         if (panelBar1)
120         {
121             var browser1 = panelBar1.browser;
122             browser1Complete = browser1.complete;
123 
124             if (!browser1Complete)
125                 Events.addEventListener(browser1, "load", browser1Loaded, true);
126 
127             browser1.droppedLinkHandler = function()
128             {
129                 return false;
130             };
131 
132             if (FBTrace.DBG_INITIALIZE)
133                 FBTrace.sysout("chrome.browser1.complete; " + browser1Complete);
134         }
135 
136         if (panelBar2)
137         {
138             var browser2 = panelBar2.browser;
139             browser2Complete = browser2.complete;
140 
141             if (!browser2Complete)
142                 Events.addEventListener(browser2, "load", browser2Loaded, true);
143 
144             browser2.droppedLinkHandler = function()
145             {
146                 return false;
147             };
148 
149             if (FBTrace.DBG_INITIALIZE)
150                 FBTrace.sysout("chrome.browser2.complete; " + browser2Complete);
151         }
152 
153         Events.addEventListener(win, "blur", onBlur, true);
154 
155         if (FBTrace.DBG_INITIALIZE)
156             FBTrace.sysout("chrome.initialized in " + win.location + " with " +
157                 (panelBar1 ? panelBar1.browser.ownerDocument.documentURI : "no panel bar"), win);
158 
159         // At this point both panelBars can be loaded already, since the src is specified
160         // in firebugOverlay.xul (asynchronously loaded). If yes, start up the initialization
161         // sequence now.
162         if (browser1Complete && browser2Complete)
163         {
164             setTimeout(function()
165             {
166                 // chrome bound into this scope
167                 FirebugChrome.initializeUI();
168             })
169         }
170     },
171 
172     /**
173      * Called when the UI is ready to be initialized, once the panel browsers are loaded.
174      */
175     initializeUI: function()
176     {
177         if (FBTrace.DBG_INITIALIZE)
178             FBTrace.sysout("chrome.initializeUI;");
179 
180         // listen for panel updates
181         Firebug.registerUIListener(this);
182 
183         try
184         {
185             var cmdPopupBrowser = this.getElementById("fbCommandPopupBrowser");
186 
187             this.applyTextSize(Firebug.textSize);
188 
189             var doc1 = panelBar1.browser.contentDocument;
190             Events.addEventListener(doc1, "mouseover", onPanelMouseOver, false);
191             Events.addEventListener(doc1, "mouseout", onPanelMouseOut, false);
192             Events.addEventListener(doc1, "mousedown", onPanelMouseDown, false);
193             Events.addEventListener(doc1, "mouseup", onPanelMouseUp, false);
194             Events.addEventListener(doc1, "click", onPanelClick, false);
195             Events.addEventListener(panelBar1, "selectingPanel", onSelectingPanel, false);
196             Events.addEventListener(panelBar1, "DOMMouseScroll", onMouseScroll, false);
197 
198             var doc2 = panelBar2.browser.contentDocument;
199             Events.addEventListener(doc2, "mouseover", onPanelMouseOver, false);
200             Events.addEventListener(doc2, "mouseout", onPanelMouseOut, false);
201             Events.addEventListener(doc2, "click", onPanelClick, false);
202             Events.addEventListener(doc2, "mousedown", onPanelMouseDown, false);
203             Events.addEventListener(doc2, "mouseup", onPanelMouseUp, false);
204             Events.addEventListener(panelBar2, "selectPanel", onSelectedSidePanel, false);
205 
206             var doc3 = cmdPopupBrowser.contentDocument;
207             Events.addEventListener(doc3, "mouseover", onPanelMouseOver, false);
208             Events.addEventListener(doc3,"mouseout", onPanelMouseOut, false);
209             Events.addEventListener(doc3, "mousedown", onPanelMouseDown, false);
210             Events.addEventListener(doc3, "click", onPanelClick, false);
211 
212             var mainTabBox = panelBar1.ownerDocument.getElementById("fbPanelBar1-tabBox");
213             Events.addEventListener(mainTabBox, "mousedown", onMainTabBoxMouseDown, false);
214 
215             // The side panel bar doesn't care about this event. It must, however,
216             // prevent it from bubbling now that we allow the side panel bar to be
217             // *inside* the main panel bar.
218             Events.addEventListener(panelBar2, "selectingPanel", stopBubble, false);
219 
220             var locationList = this.getElementById("fbLocationList");
221             Events.addEventListener(locationList, "selectObject", onSelectLocation, false);
222 
223             this.updatePanelBar1(Firebug.panelTypes);
224 
225             // Internationalize Firebug UI before firing initializeUI
226             // (so putting version into Firebug About menu operates with correct label)
227             Firebug.internationalizeUI(win.document);
228             Firebug.internationalizeUI(top.document);
229 
230             // xxxHonza: Is there any reason why we don't distribute "initializeUI"?
231             // event to modules?
232             Firebug.initializeUI();
233 
234             // Append all registered stylesheets into Firebug UI
235             for (var i=0; i<Firebug.stylesheets.length; i++)
236             {
237                 var uri = Firebug.stylesheets[i];
238                 this.appendStylesheet(uri);
239             }
240 
241             if (FBTrace.DBG_INITIALIZE)
242                 FBTrace.sysout("chrome.initializeUI; Custom stylesheet appended " +
243                     Firebug.stylesheets.length, Firebug.stylesheets);
244 
245             // Fire event for window event listeners
246             Firebug.sendLoadEvent();
247         }
248         catch (exc)
249         {
250             fatalError("chrome.initializeUI ERROR "+exc, exc);
251         }
252     },
253 
254     shutdown: function()
255     {
256         var doc1 = panelBar1.browser.contentDocument;
257         Events.removeEventListener(doc1, "mouseover", onPanelMouseOver, false);
258         Events.removeEventListener(doc1, "mouseout", onPanelMouseOut, false);
259         Events.removeEventListener(doc1, "mousedown", onPanelMouseDown, false);
260         Events.removeEventListener(doc1, "mouseup", onPanelMouseUp, false);
261         Events.removeEventListener(doc1, "click", onPanelClick, false);
262         Events.removeEventListener(panelBar1, "selectingPanel", onSelectingPanel, false);
263         Events.removeEventListener(panelBar1, "DOMMouseScroll", onMouseScroll, false);
264 
265         var doc2 = panelBar2.browser.contentDocument;
266         Events.removeEventListener(doc2, "mouseover", onPanelMouseOver, false);
267         Events.removeEventListener(doc2, "mouseout", onPanelMouseOut, false);
268         Events.removeEventListener(doc2, "mousedown", onPanelMouseDown, false);
269         Events.removeEventListener(doc2, "mouseup", onPanelMouseUp, false);
270         Events.removeEventListener(doc2, "click", onPanelClick, false);
271         Events.removeEventListener(panelBar2, "selectPanel", onSelectedSidePanel, false);
272         Events.removeEventListener(panelBar2, "selectingPanel", stopBubble, false);
273 
274         var cmdPopupBrowser = this.getElementById("fbCommandPopupBrowser");
275         var doc3 = cmdPopupBrowser.contentDocument;
276         Events.removeEventListener(doc3, "mouseover", onPanelMouseOver, false);
277         Events.removeEventListener(doc3, "mouseout", onPanelMouseOut, false);
278         Events.removeEventListener(doc3, "mousedown", onPanelMouseDown, false);
279         Events.removeEventListener(doc3, "click", onPanelClick, false);
280 
281         var mainTabBox = panelBar1.ownerDocument.getElementById("fbPanelBar1-tabBox");
282         Events.removeEventListener(mainTabBox, "mousedown", onMainTabBoxMouseDown, false);
283 
284         var locationList = this.getElementById("fbLocationList");
285         Events.removeEventListener(locationList, "selectObject", onSelectLocation, false);
286 
287         Events.removeEventListener(win, "blur", onBlur, true);
288 
289         Firebug.unregisterUIListener(this);
290 
291         Firebug.shutdown();
292 
293         if (FBTrace.DBG_EVENTLISTENERS)
294         {
295             var info = [];
296             var listeners = Firebug.Events.getRegisteredListeners();
297             for (var i=0; i<listeners.length; i++)
298             {
299                 var listener = listeners[i];
300                 info.push({
301                     parentId: listener.parentId,
302                     evendId: listener.eventId,
303                     capturing: listener.capturing,
304                     stack: listener.stack,
305                 });
306             }
307 
308             FBTrace.sysout("firebug.shutdownFirebug; listeners: " + info.length, info);
309         }
310 
311         if (FBTrace.DBG_INITIALIZE)
312             FBTrace.sysout("chrome.shutdown; Done for " + win.location);
313     },
314 
315     /**
316      * Checks if the Firebug window has the focus (is the most recent window)
317      */
318     hasFocus: function()
319     {
320         try
321         {
322             return (wm.getMostRecentWindow(null).location.href.indexOf("firebug.xul") > 0);
323         }
324         catch(ex)
325         {
326             return false;
327         }
328     },
329 
330     appendStylesheet: function(uri)
331     {
332         var cmdPopupBrowser = this.getElementById("fbCommandPopupBrowser");
333 
334         var doc1 = panelBar1.browser.contentDocument;
335         var doc2 = panelBar2.browser.contentDocument;
336         var doc3 = cmdPopupBrowser.contentDocument;
337 
338         Css.appendStylesheet(doc1, uri);
339         Css.appendStylesheet(doc2, uri);
340         Css.appendStylesheet(doc3, uri);
341 
342         if (FBTrace.DBG_INITIALIZE)
343             FBTrace.sysout("chrome.appendStylesheet; " + uri);
344     },
345 
346     updateOption: function(name, value)
347     {
348         if (panelBar1 && panelBar1.selectedPanel)
349             panelBar1.selectedPanel.updateOption(name, value);
350 
351         if (panelBar2 && panelBar2.selectedPanel)
352             panelBar2.selectedPanel.updateOption(name, value);
353 
354         if (name == "textSize")
355             this.applyTextSize(value);
356 
357         if (name == "omitObjectPathStack")
358             this.obeyOmitObjectPathStack(value);
359 
360         if (name == "viewPanelOrient")
361             this.updateOrient(value);
362     },
363 
364     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
365 
366     disableOff: function(collapse)
367     {
368         // disable/enable this button in the Firebug.chrome window
369         Dom.collapse(FirebugChrome.$("fbCloseButton"), collapse);
370     },
371 
372     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
373 
374     getPanelDocument: function(panelType)
375     {
376         var cmdPopup = this.getElementById("fbCommandPopup");
377         var cmdPopupBrowser = this.getElementById("fbCommandPopupBrowser");
378 
379         // Command Line Popup can be displayed for all the other panels
380         // (except for the Console panel)
381         // XXXjjb, xxxHonza, xxxsz: this should be somehow better, more generic and extensible,
382         // e.g. by asking each panel if it supports the Command Line Popup 
383         var consolePanelType = Firebug.getPanelType("console");
384         if (consolePanelType == panelType)
385         {
386             if (!Dom.isCollapsed(cmdPopup))
387                 return cmdPopupBrowser.contentDocument;
388         }
389 
390         // Standard panel and side panel documents.
391         if (!panelType.prototype.parentPanel)
392             return panelBar1.browser.contentDocument;
393         else
394             return panelBar2.browser.contentDocument;
395     },
396 
397     getPanelBrowser: function(panel)
398     {
399         if (!panel.parentPanel)
400             return panelBar1.browser;
401         else
402             return panelBar2.browser;
403     },
404 
405     savePanels: function()
406     {
407         var path = this.writePanels(panelBar1.browser.contentDocument);
408         if (FBTrace.DBG_PANELS)
409             FBTrace.sysout("Wrote panels to "+path+"\n");
410     },
411 
412     writePanels: function(doc)
413     {
414         var serializer = new XMLSerializer();
415         var foStream = Components.classes["@mozilla.org/network/file-output-stream;1"]
416                .createInstance(Components.interfaces.nsIFileOutputStream);
417         var file = Components.classes["@mozilla.org/file/directory_service;1"]
418            .getService(Components.interfaces.nsIProperties)
419            .get("TmpD", Components.interfaces.nsIFile);
420 
421         // extensions sub-directory
422         file.append("firebug");
423         file.append("panelSave.html");
424         file.createUnique(Components.interfaces.nsIFile.NORMAL_FILE_TYPE, 0666);
425         // write, create, truncate
426         foStream.init(file, 0x02 | 0x08 | 0x20, 0664, 0);
427         // remember, doc is the DOM tree
428         serializer.serializeToStream(doc, foStream, "");
429         foStream.close();
430         return file.path;
431     },
432 
433     // part of initializeUI
434     updatePanelBar1: function(panelTypes)
435     {
436         var mainPanelTypes = [];
437         for (var i = 0; i < panelTypes.length; ++i)
438         {
439             var panelType = panelTypes[i];
440             if (!panelType.prototype.parentPanel && !panelType.hidden)
441                 mainPanelTypes.push(panelType);
442         }
443         panelBar1.updatePanels(mainPanelTypes);
444     },
445 
446     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
447 
448     getName: function()
449     {
450         return win ? win.location.href : null;
451     },
452 
453     close: function()
454     {
455         if (FBTrace.DBG_INITIALIZE)
456             FBTrace.sysout("chrome.close closing window "+win.location);
457         win.close();
458     },
459 
460     focus: function()
461     {
462         win.focus();
463         panelBar1.browser.contentWindow.focus();
464     },
465 
466     isFocused: function()
467     {
468         return wm.getMostRecentWindow(null) == win;
469     },
470 
471     focusWatch: function(context)
472     {
473         if (Firebug.isDetached())
474             Firebug.chrome.focus();
475         else
476             Firebug.toggleBar(true);
477 
478         Firebug.chrome.selectPanel("script");
479 
480         var watchPanel = context.getPanel("watches", true);
481         if (watchPanel)
482         {
483             Firebug.CommandLine.isReadyElsePreparing(context);
484             watchPanel.editNewWatch();
485         }
486     },
487 
488     isOpen: function()
489     {
490         return !(FirebugChrome.$("fbContentBox").collapsed);
491     },
492 
493     toggleOpen: function(shouldShow)
494     {
495         var contentBox = Firebug.chrome.$("fbContentBox");
496         contentBox.setAttribute("collapsed", !shouldShow);
497 
498         if (!this.inDetachedScope)
499         {
500             Dom.collapse(Firefox.getElementById('fbMainFrame'), !shouldShow);
501 
502             var contentSplitter = Firefox.getElementById('fbContentSplitter');
503             if (contentSplitter)
504                 contentSplitter.setAttribute("collapsed", !shouldShow);
505         }
506 
507         if (shouldShow && !this.positionInitialzed)
508         {
509             this.positionInitialzed = true;
510             if (Firebug.framePosition != "detached" && Firebug.framePosition != "bottom")
511             {
512                 // null only updates frame position without side effects
513                 this.setPosition();
514             }
515         }
516     },
517 
518     onDetach: function()
519     {
520         if(!Firebug.currentContext)
521             Firebug.toggleBar(true);
522         else
523             Firebug.showBar(true);
524     },
525 
526     onUndetach: function()
527     {
528         Dom.collapse(Firebug.chrome.$('fbResumeBox'), true);
529         Dom.collapse(Firebug.chrome.$("fbContentBox"), false);
530     },
531 
532     // only called when detached
533     syncResumeBox: function(context)
534     {
535         var resumeBox = Firebug.chrome.$('fbResumeBox');
536 
537         // xxxHonza: Don't focus the Firebug window now. It would bring the detached Firebug window
538         // to the top every time the attached Firefox page is refreshed, which is annoying.
539         //this.focus();  // bring to users attention
540 
541         if (context)
542         {
543             Firebug.chrome.toggleOpen(true);
544             Firebug.chrome.syncPanel();
545             Dom.collapse(resumeBox, true);
546         }
547         else
548         {
549             Firebug.chrome.toggleOpen(false);
550             Dom.collapse(resumeBox, false);
551 
552             Firebug.chrome.window.parent.document.title =
553                 Locale.$STR("Firebug - inactive for current website");
554         }
555     },
556 
557     reload: function(skipCache)
558     {
559         var reloadFlags = skipCache
560             ? LOAD_FLAGS_BYPASS_PROXY | LOAD_FLAGS_BYPASS_CACHE
561             : LOAD_FLAGS_NONE;
562 
563         // Make sure the selected tab in the attached browser window is refreshed.
564         var browser = Firefox.getCurrentBrowser();
565         browser.firebugReload = true;
566         browser.webNavigation.reload(reloadFlags);
567 
568         if (FBTrace.DBG_WINDOWS)
569             FBTrace.sysout("chrome.reload; " + skipCache + ", " + browser.currentURI.spec);
570     },
571 
572     gotoPreviousTab: function()
573     {
574         if (Firebug.currentContext.previousPanelName)
575             this.selectPanel(Firebug.currentContext.previousPanelName);
576     },
577 
578     gotoSiblingTab : function(goRight)
579     {
580         if (FirebugChrome.$("fbContentBox").collapsed)
581             return;
582         var i, currentIndex = newIndex = -1, currentPanel = this.getSelectedPanel(), newPanel;
583         var panelTypes = Firebug.getMainPanelTypes(Firebug.currentContext);
584 
585         // get the current panel's index (is there a simpler way for this?)
586         for (i = 0; i < panelTypes.length; i++)
587         {
588             if (panelTypes[i].prototype.name === currentPanel.name)
589             {
590                 currentIndex = i;
591                 break;
592             }
593         }
594 
595         if (currentIndex != -1)
596         {
597             newIndex = goRight ? (currentIndex == panelTypes.length - 1 ?
598                 0 : ++currentIndex) : (currentIndex == 0 ? panelTypes.length - 1 : --currentIndex);
599 
600             newPanel = panelTypes[newIndex].prototype;
601             if (newPanel && newPanel.name)
602             {
603                 this.selectPanel(newPanel.name);
604             }
605         }
606     },
607 
608     getNextObject: function(reverse)
609     {
610         var panel = Firebug.currentContext.getPanel(Firebug.currentContext.panelName);
611         if (panel)
612         {
613             var panelStatus = this.getElementById("fbPanelStatus");
614             var item = panelStatus.getItemByObject(panel.selection);
615             if (item)
616             {
617                 if (reverse)
618                     item = item.previousSibling ? item.previousSibling.previousSibling : null;
619                 else
620                     item = item.nextSibling ? item.nextSibling.nextSibling : null;
621 
622                 if (item)
623                     return item.repObject;
624             }
625         }
626     },
627 
628     gotoNextObject: function(reverse)
629     {
630         var nextObject = this.getNextObject(reverse);
631         if (nextObject)
632             this.select(nextObject);
633         else
634             System.beep();
635     },
636 
637     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
638     // Panels
639 
640     /**
641      * Set this.location on the current panel or one given by name.
642      * The location object should be known to the caller to be of the correct type for the panel,
643      * e.g. SourceFile for Script panel
644      * @param object location object, null selects default location
645      * @param panelName name of the panel to select, null means current panel
646      * @param sidePanelName name of the side panel to select
647      */
648     navigate: function(object, panelName, sidePanelName)
649     {
650         var panel;
651         if (panelName || sidePanelName)
652             panel = this.selectPanel(panelName, sidePanelName);
653         else
654             panel = this.getSelectedPanel();
655 
656         if (panel)
657             panel.navigate(object);
658     },
659 
660     /**
661      *  Set this.selection by object type analysis, passing the object to all panels to
662      *      find the best match
663      *  @param object new this.selection object
664      *  @param panelName matching panel.name will be used, if its supportsObject returns true
665      *  @param sidePanelName default side panel name used, if its supportsObject returns true
666      *  @param forceUpdate if true, then (object === this.selection) is ignored and
667      *      updateSelection is called
668      */
669     select: function(object, panelName, sidePanelName, forceUpdate)
670     {
671         if (FBTrace.DBG_PANELS)
672             FBTrace.sysout("chrome.select object:"+object+" panelName:"+panelName+
673                 " sidePanelName:"+sidePanelName+" forceUpdate:"+forceUpdate+"\n");
674 
675         var bestPanelName = getBestPanelName(object, Firebug.currentContext, panelName);
676 
677         // allow refresh if needed (last argument)
678         var panel = this.selectPanel(bestPanelName, sidePanelName/*, true*/);
679         if (panel)
680             panel.select(object, forceUpdate);
681 
682         // issue 4778
683         this.syncLocationList();
684     },
685 
686     selectPanel: function(panelName, sidePanelName, noRefresh)
687     {
688         if (panelName && sidePanelName)
689             Firebug.currentContext.sidePanelNames[panelName] = sidePanelName;
690 
691         // cause panel visibility changes and events
692         return panelBar1.selectPanel(panelName, false, noRefresh);
693     },
694 
695     selectSidePanel: function(panelName)
696     {
697         return panelBar2.selectPanel(panelName);
698     },
699 
700     selectSupportingPanel: function(object, context, forceUpdate)
701     {
702         var bestPanelName = getBestPanelSupportingObject(object, context);
703         var panel = this.selectPanel(bestPanelName, false, true);
704         if (panel)
705             panel.select(object, forceUpdate);
706     },
707 
708     clearPanels: function()
709     {
710         panelBar1.hideSelectedPanel();
711         panelBar1.selectedPanel = null;
712         panelBar2.selectedPanel = null;
713     },
714 
715     getSelectedPanel: function()
716     {
717         return panelBar1 ? panelBar1.selectedPanel : null;
718     },
719 
720     getSelectedSidePanel: function()
721     {
722         return panelBar2 ? panelBar2.selectedPanel : null;
723     },
724 
725     switchToPanel: function(context, switchToPanelName)
726     {
727         // Remember the previous panel and bar state so we can revert if the user cancels.
728         this.previousPanelName = context.panelName;
729         this.previousSidePanelName = context.sidePanelName;
730         this.previouslyCollapsed = FirebugChrome.$("fbContentBox").collapsed;
731 
732         // TODO previouslyMinimized
733         this.previouslyFocused = Firebug.isDetached() && this.isFocused();
734 
735         var switchPanel = this.selectPanel(switchToPanelName);
736         if (switchPanel)
737             this.previousObject = switchPanel.selection;
738 
739         return switchPanel;
740     },
741 
742     unswitchToPanel: function(context, switchToPanelName, canceled)
743     {
744         var switchToPanel = context.getPanel(switchToPanelName);
745 
746         if (this.previouslyFocused)
747             this.focus();
748 
749         if (canceled && this.previousPanelName)
750         {
751             // revert
752             if (this.previouslyCollapsed)
753                 Firebug.showBar(false);
754 
755             if (this.previousPanelName == switchToPanelName)
756                 switchToPanel.select(this.previousObject);
757             else
758                 this.selectPanel(this.previousPanelName, this.previousSidePanelName);
759         }
760         else
761         {
762             // else stay on the switchToPanel
763             this.selectPanel(switchToPanelName);
764             if (switchToPanel.selection)
765                 this.select(switchToPanel.selection);
766             this.getSelectedPanel().panelNode.focus();
767         }
768 
769         delete this.previousObject;
770         delete this.previousPanelName;
771         delete this.previousSidePanelName;
772         delete this.inspectingChrome;
773 
774         return switchToPanel;
775     },
776 
777     getSelectedPanelURL: function()
778     {
779         var location;
780         if (Firebug.currentContext)
781         {
782             var panel = Firebug.chrome.getSelectedPanel();
783             if (panel)
784             {
785                 location = panel.location;
786                 if (!location && panel.name == "html")
787                     location = Firebug.currentContext.window.document.location;
788 
789                 if (location && (location instanceof Firebug.SourceFile ||
790                     location instanceof CSSStyleSheet))
791                     location = location.href;
792             }
793         }
794 
795         if (!location)
796         {
797             var currentURI = Firefox.getCurrentURI();
798             if (currentURI)
799                 location = currentURI.asciiSpec;
800         }
801 
802         if (!location)
803             return;
804 
805         location = location.href || location.url || location.toString();
806         if (Firebug.filterSystemURLs && Url.isSystemURL(location))
807             return;
808 
809         return location;
810     },
811 
812     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
813     // Location interface provider for binding.xml panelFileList
814 
815     getLocationProvider: function()
816     {
817         // a function that returns an object with .getObjectDescription() and .getLocationList()
818         return function getSelectedPanelFromCurrentContext()
819         {
820             // panels provide location, use the selected panel
821             return Firebug.chrome.getSelectedPanel();
822         }
823     },
824 
825     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
826     // UI Synchronization
827 
828     setFirebugContext: function(context)
829     {
830          // This sets the global value of Firebug.currentContext in the window, that this
831          // chrome is compiled into. Note, that for firebug.xul the Firebug object is shared
832          // across windows, but not FirebugChrome and Firebug.currentContext.
833          Firebug.currentContext = context;
834 
835          if (FBTrace.DBG_WINDOWS || FBTrace.DBG_DISPATCH || FBTrace.DBG_ACTIVATION)
836              FBTrace.sysout("setFirebugContext "+(Firebug.currentContext?
837                 Firebug.currentContext.getName():" **> NULL <** ") + " in "+win.location);
838     },
839 
840     hidePanel: function()
841     {
842         if (panelBar1.selectedPanel)
843             panelBar1.hideSelectedPanel()
844 
845         if (panelBar2.selectedPanel)
846             panelBar2.hideSelectedPanel()
847     },
848 
849     syncPanel: function(panelName)
850     {
851         var context = Firebug.currentContext;
852 
853         if (FBTrace.DBG_PANELS)
854             FBTrace.sysout("chrome.syncPanel Firebug.currentContext=" +
855                 (context ? context.getName() : "undefined"));
856 
857         var panelStatus = this.getElementById("fbPanelStatus");
858         panelStatus.clear();
859 
860         if (context)
861         {
862             if (!panelName)
863                 panelName = context.panelName? context.panelName : Firebug.defaultPanelName;
864 
865             // Make the HTML panel the default panel, which is displayed
866             // to the user the very first time.
867             if (!panelName || !Firebug.getPanelType(panelName))
868                 panelName = "html";
869 
870             this.syncMainPanels();
871             panelBar1.selectPanel(panelName, true);
872         }
873         else
874         {
875             panelBar1.selectPanel(null, true);
876         }
877 
878         if (Firebug.isDetached())
879             this.syncTitle();
880     },
881 
882     syncMainPanels: function()
883     {
884         if (Firebug.currentContext)
885         {
886             var panelTypes = Firebug.getMainPanelTypes(Firebug.currentContext);
887             panelBar1.updatePanels(panelTypes);
888 
889             // Update also BON tab flag (orange background if BON is active)
890             // every time the user changes the current tab in Firefox.
891             Firebug.Breakpoint.updatePanelTabs(Firebug.currentContext);
892         }
893     },
894 
895     syncSidePanels: function()
896     {
897         if (FBTrace.DBG_PANELS)
898         {
899             FBTrace.sysout("chrome.syncSidePanels; main panel: " +
900                 (panelBar1.selectedPanel ? panelBar1.selectedPanel.name : "no panel") +
901                 ", side panel: " +
902                 (panelBar2.selectedPanel ? panelBar2.selectedPanel.name : "no panel"));
903         }
904 
905         if (!panelBar1.selectedPanel)
906             return;
907 
908         var panelTypes;
909         if (Firebug.currentContext)
910         {
911             panelTypes = Firebug.getSidePanelTypes(Firebug.currentContext,
912                 panelBar1.selectedPanel);
913             panelBar2.updatePanels(panelTypes);
914         }
915 
916         if (Firebug.currentContext && Firebug.currentContext.sidePanelNames)
917         {
918             if (!panelBar2.selectedPanel ||
919                 (panelBar2.selectedPanel.parentPanel !== panelBar1.selectedPanel.name))
920             {
921                 var sidePanelName = Firebug.currentContext.sidePanelNames[
922                     Firebug.currentContext.panelName];
923                 sidePanelName = getBestSidePanelName(sidePanelName, panelTypes);
924                 panelBar2.selectPanel(sidePanelName, true);
925             }
926             else
927             {
928                 // If the context changes, we need to refresh the panel.
929                 panelBar2.selectPanel(panelBar2.selectedPanel.name, true);
930             }
931         }
932         else
933         {
934             panelBar2.selectPanel(null);
935         }
936 
937         if (FBTrace.DBG_PANELS)
938             FBTrace.sysout("chrome.syncSidePanels; selected side panel " + panelBar1.selectedPanel);
939 
940         sidePanelDeck.selectedPanel = panelBar2;
941 
942         Dom.collapse(sidePanelDeck, !panelBar2.selectedPanel);
943         Dom.collapse(panelSplitter, !panelBar2.selectedPanel);
944     },
945 
946     syncTitle: function()
947     {
948         if (Firebug.currentContext)
949         {
950             var title = Firebug.currentContext.getTitle();
951             win.parent.document.title = Locale.$STRF("WindowTitle", [title]);
952         }
953         else
954         {
955             win.parent.document.title = Locale.$STR("Firebug");
956         }
957     },
958 
959     focusLocationList: function()
960     {
961         var locationList = this.getElementById("fbLocationList");
962         locationList.popup.showPopup(locationList, -1, -1, "popup", "bottomleft", "topleft");
963     },
964 
965     syncLocationList: function()
966     {
967         var locationButtons = this.getElementById("fbLocationButtons");
968 
969         var panel = panelBar1.selectedPanel;
970         if (panel && panel.location)
971         {
972             var locationList = this.getElementById("fbLocationList");
973             locationList.location = panel.location;
974 
975             Dom.collapse(locationButtons, false);
976         }
977         else
978         {
979             Dom.collapse(locationButtons, true);
980         }
981     },
982 
983     clearStatusPath: function()
984     {
985         var panelStatus = this.getElementById("fbPanelStatus");
986         panelStatus.clear();
987     },
988 
989     syncStatusPath: function()
990     {
991         var panelStatus = this.getElementById("fbPanelStatus");
992         var panelStatusSeparator = this.getElementById("fbStatusSeparator");
993         var panel = panelBar1.selectedPanel;
994 
995         if (!panel || (panel && !panel.selection))
996         {
997             panelStatus.clear();
998         }
999         else
1000         {
1001             var path = panel.getObjectPath(panel.selection);
1002             if (!path || !path.length)
1003             {
1004                 Dom.hide(panelStatusSeparator, true);
1005                 panelStatus.clear();
1006             }
1007             else
1008             {
1009                 // Update the visibility of the separator. The separator
1010                 // is displayed only if there are some other buttons on the left side.
1011                 // Before showing the status separator let's see whether there are any other
1012                 // buttons on the left.
1013                 var hide = true;
1014                 var sibling = panelStatusSeparator.parentNode.previousSibling;
1015                 while (sibling)
1016                 {
1017                     if (!Dom.isCollapsed(sibling))
1018                     {
1019                         hide = false;
1020                         break;
1021                     }
1022                     sibling = sibling.previousSibling;
1023                 }
1024                 Dom.hide(panelStatusSeparator, hide);
1025 
1026                 if (panel.name != panelStatus.lastPanelName)
1027                     panelStatus.clear();
1028 
1029                 panelStatus.lastPanelName = panel.name;
1030 
1031                 // If the object already exists in the list, just select it and keep the path.
1032                 var selection = panel.selection;
1033                 var existingItem = panelStatus.getItemByObject(panel.selection);
1034                 if (existingItem)
1035                 {
1036                     // Update the labels of the status path elements, because it can be,
1037                     // that the elements changed even when the selected element exists
1038                     // inside the path (issue 4826)
1039                     var statusItems = panelStatus.getItems();
1040                     for (var i = 0; i < statusItems.length; ++i)
1041                     {
1042                         var object = Firebug.getRepObject(statusItems[i]);
1043                         var rep = Firebug.getRep(object, Firebug.currentContext);
1044                         var objectTitle = rep.getTitle(object, Firebug.currentContext);
1045                         var title = String.cropMultipleLines(objectTitle, statusCropSize);
1046 
1047                         statusItems[i].label = title;
1048                     }
1049                     panelStatus.selectItem(existingItem);
1050                 }
1051                 else
1052                 {
1053                     panelStatus.clear();
1054 
1055                     for (var i = 0; i < path.length; ++i)
1056                     {
1057                         var object = path[i];
1058 
1059                         var rep = Firebug.getRep(object, Firebug.currentContext);
1060                         var objectTitle = rep.getTitle(object, Firebug.currentContext);
1061 
1062                         var title = String.cropMultipleLines(objectTitle, statusCropSize);
1063                         panelStatus.addItem(title, object, rep, panel.statusSeparator);
1064                     }
1065 
1066                     panelStatus.selectObject(panel.selection);
1067                     if (FBTrace.DBG_PANELS)
1068                         FBTrace.sysout("syncStatusPath "+path.length+" items ", path);
1069                 }
1070             }
1071         }
1072     },
1073 
1074     toggleOrient: function(preferredValue)
1075     {
1076         var value = Options.get("viewPanelOrient");
1077         if (value == preferredValue)
1078             return;
1079 
1080         Options.togglePref("viewPanelOrient");
1081     },
1082 
1083     updateOrient: function(value)
1084     {
1085         var panelPane = FirebugChrome.$("fbPanelPane");
1086         if (!panelPane)
1087             return;
1088 
1089         var newOrient = value ? "vertical" : "horizontal";
1090         if (panelPane.orient == newOrient)
1091             return;
1092 
1093         panelSplitter.orient = panelPane.orient = newOrient;
1094     },
1095 
1096     setPosition: function(pos)
1097     {
1098         if (Firebug.framePosition == pos)
1099             return;
1100 
1101         if (pos)
1102         {
1103             if (Firebug.getSuspended())
1104                 Firebug.toggleBar();
1105         }
1106         else
1107         {
1108             pos = Firebug.framePosition;
1109         }
1110 
1111         if (pos == "detached")
1112         {
1113             Firebug.toggleDetachBar(true, true);
1114             return;
1115         }
1116 
1117         if (Firebug.isDetached())
1118             Firebug.toggleDetachBar(false, true);
1119 
1120         pos && this.syncPositionPref(pos);
1121 
1122         var vertical = pos == "top" || pos == "bottom";
1123         var after = pos == "bottom" || pos == "right";
1124 
1125         var document = window.parent.document;
1126         var container = document.getElementById(vertical ? "appcontent" : "browser");
1127 
1128         var splitter = Firefox.getElementById("fbContentSplitter");
1129         splitter.setAttribute("orient", vertical ? "vertical" : "horizontal");
1130         splitter.setAttribute("dir", after ? "" : "reverse");
1131         container.insertBefore(splitter, after ? null: container.firstChild);
1132 
1133         var frame = document.getElementById("fbMainFrame");
1134 
1135         var newFrame = frame.cloneNode(true);
1136         var newBrowser = newFrame.querySelector("#fbMainContainer");
1137         var oldBrowser = frame.querySelector("#fbMainContainer");
1138 
1139         newBrowser.removeAttribute("src");
1140         container.insertBefore(newFrame, after ? null: container.firstChild);
1141 
1142         this.swapBrowsers(oldBrowser, newBrowser);
1143         this.browser = newBrowser;
1144 
1145         frame.parentNode.removeChild(frame);
1146         this.framePosition = pos;
1147     },
1148 
1149     syncPositionPref: function(pos)
1150     {
1151         if (!pos)
1152         {
1153             if (Firebug.isDetached())
1154                 pos = "detached";
1155             else
1156                 pos = this.framePosition || 'bottom';
1157         }
1158 
1159         Firebug.Options.set("framePosition", pos);
1160         return Firebug.framePosition = pos;
1161     },
1162 
1163     swapBrowsers: function(oldBrowser, newBrowser)
1164     {
1165         var oldDoc = oldBrowser.contentDocument
1166         // Panels remember the top window, for which they were first opened.
1167         // So we need to destroy their views.
1168         var styleSheet = oldDoc.styleSheets[0];
1169         var rulePos = styleSheet.cssRules.length;
1170         styleSheet.insertRule(
1171             "panel{display:-moz-box!important; visibility:collapse!important;}", rulePos);
1172 
1173         // We need to deal with inner frames first since swapFrameLoaders
1174         // doesn't work for type="chrome" browser containing type="content" browsers
1175         var frames = oldDoc.querySelectorAll("browser[type*=content], iframe[type*=content]");
1176         var tmpFrames = [], placeholders = [];
1177 
1178         var topDoc = oldBrowser.ownerDocument;
1179         var temp = topDoc.createElement("box");
1180         topDoc.documentElement.appendChild(temp);
1181 
1182         var swapDocShells = function(a, b)
1183         {
1184             // important! must touch browser.contentDocument to initialize it
1185             a.contentDocument == b.contentDocument;
1186             if (a.nodeName == "iframe")
1187                 a.QueryInterface(Ci.nsIFrameLoaderOwner).swapFrameLoaders(b);
1188             else
1189                 a.swapDocShells(b);
1190         }
1191 
1192         for (var i = frames.length - 1; i >= 0; i--)
1193         {
1194             placeholders[i] = document.createElement("placeholder");
1195             tmpFrames[i] = frames[i].cloneNode(true);
1196             tmpFrames[i].removeAttribute("src");
1197             frames[i].removeAttribute("src");
1198             temp.appendChild(tmpFrames[i]);
1199         }
1200 
1201         for (var i = tmpFrames.length - 1; i >= 0; i--)
1202         {
1203             swapDocShells(tmpFrames[i], frames[i]);
1204             frames[i].parentNode.replaceChild(placeholders[i], frames[i]);
1205         }
1206 
1207         swapDocShells(oldBrowser, newBrowser);
1208 
1209         for (var i = placeholders.length - 1; i >= 0; i--)
1210             placeholders[i].parentNode.replaceChild(frames[i], placeholders[i]);
1211 
1212         for (var i = frames.length - 1; i >= 0; i--)
1213             swapDocShells(tmpFrames[i], frames[i]);
1214 
1215         temp.parentNode.removeChild(temp);
1216 
1217         styleSheet.deleteRule(rulePos);
1218     },
1219 
1220     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
1221     // Global Attributes
1222 
1223     getGlobalAttribute: function(id, name)
1224     {
1225         var elt = FirebugChrome.$(id);
1226         return elt.getAttribute(name);
1227     },
1228 
1229     setGlobalAttribute: function(id, name, value)
1230     {
1231         var elt = FirebugChrome.$(id);
1232         if (elt)
1233         {
1234             if (value == null)
1235                 elt.removeAttribute(name);
1236             else
1237                 elt.setAttribute(name, value);
1238         }
1239 
1240         if (Firebug.externalChrome)
1241             Firebug.externalChrome.setGlobalAttribute(id, name, value);
1242     },
1243 
1244     setChromeDocumentAttribute: function(id, name, value)
1245     {
1246         // call as Firebug.chrome.setChromeDocumentAttribute() to set attributes
1247         // in another window
1248         var elt = FirebugChrome.$(id);
1249         if (elt)
1250             elt.setAttribute(name, value);
1251     },
1252 
1253     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
1254 
1255     keyCodeListen: function(key, filter, listener, capture)
1256     {
1257         if (!filter)
1258             filter = Events.noKeyModifiers;
1259 
1260         var keyCode = KeyEvent["DOM_VK_"+key];
1261 
1262         function fn(event)
1263         {
1264             if (event.keyCode == keyCode && (!filter || filter(event)))
1265             {
1266                 listener();
1267                 Events.cancelEvent(event);
1268             }
1269         }
1270 
1271         Events.addEventListener(win, "keypress", fn, capture);
1272 
1273         return [fn, capture];
1274     },
1275 
1276     keyListen: function(ch, filter, listener, capture)
1277     {
1278         if (!filter)
1279             filter = Events.noKeyModifiers;
1280 
1281         var charCode = ch.charCodeAt(0);
1282 
1283         function fn(event)
1284         {
1285             if (event.charCode == charCode && (!filter || filter(event)))
1286             {
1287                 listener();
1288                 Events.cancelEvent(event);
1289             }
1290         }
1291 
1292         Events.addEventListener(win, "keypress", fn, capture);
1293 
1294         return [fn, capture];
1295     },
1296 
1297     keyIgnore: function(listener)
1298     {
1299         Events.removeEventListener(win, "keypress", listener[0], listener[1]);
1300     },
1301 
1302     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
1303 
1304     $: function(id)
1305     {
1306         return this.getElementById(id);
1307     },
1308 
1309     getElementById: function(id)
1310     {
1311         // The document we close over, not the global.
1312         return win.document.getElementById(id);
1313     },
1314 
1315     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
1316 
1317     applyTextSize: function(value)
1318     {
1319         if (!panelBar1)
1320             return;
1321 
1322         var zoom = Firebug.Options.getZoomByTextSize(value);
1323         var zoomString = (zoom * 100) + "%";
1324 
1325         // scale the aspect relative to 11pt Lucida Grande
1326         // xxxsz: The magic number 0.547 should be replaced some logic retrieving this value.
1327         var fontSizeAdjust = zoom * 0.547;
1328         var contentBox = Firebug.chrome.$("fbContentBox");
1329         contentBox.style.fontSizeAdjust = fontSizeAdjust;
1330 
1331         //panelBar1.browser.contentDocument.documentElement.style.fontSizeAdjust = fontSizeAdjust;
1332         //panelBar2.browser.contentDocument.documentElement.style.fontSizeAdjust = fontSizeAdjust;
1333 
1334         panelBar1.browser.markupDocumentViewer.textZoom = zoom;
1335         panelBar2.browser.markupDocumentViewer.textZoom = zoom;
1336 
1337         var cmdPopupBrowser = this.getElementById("fbCommandPopupBrowser");
1338         cmdPopupBrowser.markupDocumentViewer.textZoom = zoom;
1339 
1340         var box = Firebug.chrome.$("fbCommandBox");
1341         box.style.fontSizeAdjust = fontSizeAdjust;
1342         if (Firebug.CommandLine)
1343         {
1344             Firebug.CommandLine.getSingleRowCommandLine().style.fontSizeAdjust = fontSizeAdjust;
1345             Firebug.chrome.$("fbCommandLineCompletion").style.fontSizeAdjust = fontSizeAdjust;
1346             Firebug.chrome.$("fbCommandLineCompletionList").style.fontSizeAdjust = fontSizeAdjust;
1347 
1348             Firebug.CommandEditor.fontSizeAdjust(fontSizeAdjust);
1349         }
1350 
1351         Firebug.dispatchToPanels("onTextSizeChange", [zoom, fontSizeAdjust]);
1352     },
1353 
1354     obeyOmitObjectPathStack: function(value)
1355     {
1356         var panelStatus = this.getElementById("fbPanelStatus");
1357         Dom.hide(panelStatus, (value?true:false));
1358     },
1359 
1360     getPanelStatusElements: function()
1361     {
1362         return this.getElementById("fbPanelStatus");
1363     },
1364 
1365     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
1366     // UI Event Listeners uilisteners  or "panelListeners"
1367 
1368     onPanelNavigate: function(object, panel)
1369     {
1370         this.syncLocationList();
1371     },
1372 
1373     onObjectSelected: function(object, panel)
1374     {
1375         if (panel == panelBar1.selectedPanel)
1376         {
1377             this.syncStatusPath();
1378 
1379             var sidePanel = panelBar2.selectedPanel;
1380             if (sidePanel)
1381                 sidePanel.select(object);
1382         }
1383     },
1384 
1385     onObjectChanged: function(object, panel)
1386     {
1387         if (panel == panelBar1.selectedPanel)
1388         {
1389             this.syncStatusPath();
1390 
1391             var sidePanel = panelBar2.selectedPanel;
1392             if (sidePanel)
1393                 sidePanel.refresh();
1394         }
1395     },
1396 
1397     // called on setTimeout() after sourceBox viewport has been repainted
1398     onApplyDecorator: function(sourceBox)
1399     {
1400     },
1401 
1402     // called on scrollTo() passing in the selected line
1403     onViewportChange: function(sourceLink)
1404     {
1405     },
1406 
1407     // called when the Firebug UI comes up in browser
1408     showUI: function(browser, context)
1409     {
1410     },
1411 
1412     // called when the Firebug UI comes down; context may be null
1413     hideUI: function(browser, context)
1414     {
1415     },
1416 
1417     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
1418 
1419     onMenuShowing: function(popup)
1420     {
1421         var detachFirebug = Dom.getElementsByAttribute(popup, "id", "menu_firebug_detachFirebug")[0];
1422         if (detachFirebug)
1423         {
1424             detachFirebug.setAttribute("label", (Firebug.isDetached() ?
1425                 Locale.$STR("firebug.AttachFirebug") : Locale.$STR("firebug.DetachFirebug")));
1426         }
1427 
1428         var toggleFirebug = Dom.getElementsByAttribute(popup, "id", "menu_firebug_toggleFirebug")[0];
1429         if (toggleFirebug)
1430         {
1431             var fbContentBox = FirebugChrome.$("fbContentBox");
1432             var collapsed = fbContentBox.getAttribute("collapsed");
1433             if (collapsed == "true")
1434             {
1435                 toggleFirebug.setAttribute("label", Locale.$STR("inBrowser"));
1436                 toggleFirebug.setAttribute("tooltiptext", Locale.$STR("inBrowser"));
1437             }
1438             else
1439             {
1440               toggleFirebug.setAttribute("label", Locale.$STR("firebug.menu.Minimize_Firebug"));
1441               toggleFirebug.setAttribute("tooltiptext", Locale.$STR("firebug.menu.tip.Minimize_Firebug"));
1442             }
1443 
1444             // If Firebug is detached, hide the menu. ('Open Firebug' shortcut doesn't hide
1445             // but just focuses the external window)
1446             if (Firebug.isDetached())
1447                 toggleFirebug.setAttribute("collapsed", (collapsed == "true" ? "false" : "true"));
1448         }
1449     },
1450 
1451     onOptionsShowing: function(popup)
1452     {
1453         for (var child = popup.firstChild; child; child = child.nextSibling)
1454         {
1455             if (child.localName == "menuitem")
1456             {
1457                 var option = child.getAttribute("option");
1458                 if (option)
1459                 {
1460                     var checked = false;
1461                     if (option == "profiling")
1462                         checked = FBS.profiling;
1463                     else
1464                         checked = Firebug.Options.get(option);
1465 
1466                     child.setAttribute("checked", checked);
1467                 }
1468             }
1469         }
1470     },
1471 
1472     onToggleOption: function(menuitem)
1473     {
1474         var option = menuitem.getAttribute("option");
1475         var checked = menuitem.getAttribute("checked") == "true";
1476 
1477         Firebug.Options.set(option, checked);
1478     },
1479 
1480     onContextShowing: function(event)
1481     {
1482         // xxxHonza: This context menu support can be used even in a separate window, which
1483         // doesn't contain the Firebug UI (panels).
1484         //if (!panelBar1.selectedPanel)
1485         //    return false;
1486 
1487         var popup = event.target;
1488         if (popup.id != "fbContextMenu")
1489             return;
1490 
1491         var target = win.document.popupNode;
1492         var panel = target ? Firebug.getElementPanel(target) : null;
1493 
1494         // The event must be on our chrome not inside the panel.
1495         if (!panel)
1496             panel = panelBar1 ? panelBar1.selectedPanel : null;
1497 
1498         Dom.eraseNode(popup);
1499 
1500         // Make sure the Copy action is only available if there is actually something
1501         // selected in the panel.
1502         var sel = target.ownerDocument.defaultView.getSelection();
1503         if (!this.contextMenuObject &&
1504             !FirebugChrome.$("cmd_copy").getAttribute("disabled") &&
1505             !sel.isCollapsed)
1506         {
1507             var menuitem = Menu.createMenuItem(popup, {label: "Copy"});
1508             menuitem.setAttribute("command", "cmd_copy");
1509         }
1510 
1511         var object;
1512         if (this.contextMenuObject)
1513             object = this.contextMenuObject;
1514         else if (target && target.ownerDocument == document)
1515             object = Firebug.getRepObject(target);
1516         else if (target && panel)
1517             object = panel.getPopupObject(target);
1518         else if (target)
1519             // xxxHonza: What about a node from a different document? Is that OK?
1520             object = Firebug.getRepObject(target);
1521 
1522         this.contextMenuObject = null;
1523 
1524         var rep = Firebug.getRep(object, Firebug.currentContext);
1525         var realObject = rep ? rep.getRealObject(object, Firebug.currentContext) : null;
1526         var realRep = realObject ? Firebug.getRep(realObject, Firebug.currentContext) : null;
1527 
1528         if (FBTrace.DBG_MENU)
1529         {
1530             FBTrace.sysout("chrome.onContextShowing object:"+object+", rep: "+rep+
1531                 ", realObject: "+realObject+", realRep:"+realRep);
1532         }
1533 
1534         // 1. Add the custom menu items from the realRep
1535         if (realObject && realRep)
1536         {
1537             var items = realRep.getContextMenuItems(realObject, target, Firebug.currentContext);
1538             if (items)
1539                 Menu.createMenuItems(popup, items);
1540         }
1541 
1542         // 2. Add the custom menu items from the original rep
1543         if (object && rep && rep != realRep)
1544         {
1545             var items = rep.getContextMenuItems(object, target, Firebug.currentContext);
1546             if (items)
1547                 Menu.createMenuItems(popup, items);
1548         }
1549 
1550         // 3. Add the custom menu items from the panel
1551         if (panel)
1552         {
1553             var items = panel.getContextMenuItems(realObject, target);
1554             if (items)
1555                 Menu.createMenuItems(popup, items);
1556         }
1557 
1558         // 4. Add the inspect menu items
1559         if (realObject && rep && rep.inspectable)
1560         {
1561             var items = this.getInspectMenuItems(realObject);
1562 
1563             // Separate existing menu items from 'inspect' menu items.
1564             if (popup.firstChild && items.length > 0)
1565                 Menu.createMenuSeparator(popup);
1566 
1567             Menu.createMenuItems(popup, items);
1568         }
1569 
1570         // 5. Add menu items from uiListeners
1571         var items = [];
1572         Events.dispatch(Firebug.uiListeners, "onContextMenu", [items, object, target,
1573             Firebug.currentContext, panel, popup]);
1574 
1575         if (items)
1576             Menu.createMenuItems(popup, items);
1577 
1578         if (!popup.firstChild)
1579             return false;
1580     },
1581 
1582     getInspectMenuItems: function(object)
1583     {
1584         var items = [];
1585 
1586         // Domplate (+ support for context menus) can be used even in separate
1587         // windows when Firebug.currentContext doesn't have to be defined.
1588         if (!Firebug.currentContext)
1589             return items;
1590 
1591         for (var i = 0; i < Firebug.panelTypes.length; ++i)
1592         {
1593             var panelType = Firebug.panelTypes[i];
1594             if (!panelType.prototype.parentPanel
1595                 && panelType.prototype.name != Firebug.currentContext.panelName
1596                 && panelSupportsObject(panelType, object, Firebug.currentContext))
1597             {
1598                 var panelName = panelType.prototype.name;
1599 
1600                 var title = Firebug.getPanelTitle(panelType);
1601                 var label = Locale.$STRF("panel.Inspect_In_Panel", [title]);
1602                 var tooltiptext = Locale.$STRF("panel.tip.Inspect_In_Panel", [title]);
1603                 var id = "InspectIn" + panelName + "Panel";
1604 
1605                 var command = Obj.bindFixed(this.select, this, object, panelName);
1606                 items.push({label: label, tooltiptext: tooltiptext, command: command, nol10n: true,
1607                     id: id});
1608             }
1609         }
1610 
1611         return items;
1612     },
1613 
1614     onTooltipShowing: function(event)
1615     {
1616         // xxxHonza: This tooltip support can be used even in a separate window, which
1617         // doesn't contain the Firebug UI (panels).
1618         //if (!panelBar1.selectedPanel)
1619         //    return false;
1620 
1621         var tooltip = FirebugChrome.$("fbTooltip");
1622         var target = win.document.tooltipNode;
1623 
1624         var panel = target ? Firebug.getElementPanel(target) : null;
1625 
1626         var object;
1627 
1628         /* XXXjjb: This causes the Script panel to show the function body over and over.
1629          * We need to clear it at least, but actually we need to understand why the tooltip
1630          * should show the context menu object at all. One thing the contextMenuObject supports
1631          * is peeking at function bodies when stopped at a breakpoint.
1632          * That case could be supported with clearing the contextMenuObject, but we don't
1633          * know if that breaks something else. So maybe a popupMenuObject should be set
1634          * on the context if that is what we want to support.
1635          * The other complication is that there seems to be another tooltip.
1636         if (this.contextMenuObject)
1637         {
1638             object = this.contextMenuObject;
1639             FBTrace.sysout("tooltip by contextMenuObject");
1640         }
1641         else*/
1642 
1643         if (target && target.ownerDocument == document)
1644             object = Firebug.getRepObject(target);
1645         else if (panel)
1646             object = panel.getTooltipObject(target);
1647 
1648         var rep = object ? Firebug.getRep(object, Firebug.currentContext) : null;
1649         object = rep ? rep.getRealObject(object, Firebug.currentContext) : null;
1650         rep = object ? Firebug.getRep(object) : null;
1651 
1652         if (object && rep)
1653         {
1654             var label = rep.getTooltip(object, Firebug.currentContext);
1655             if (label)
1656             {
1657                 tooltip.setAttribute("label", label);
1658                 return true;
1659             }
1660         }
1661 
1662         if (Css.hasClass(target, 'noteInToolTip'))
1663             Css.setClass(tooltip, 'noteInToolTip');
1664         else
1665             Css.removeClass(tooltip, 'noteInToolTip');
1666 
1667         if (target && target.hasAttribute("title"))
1668         {
1669             tooltip.setAttribute("label", target.getAttribute("title"));
1670             return true;
1671         }
1672 
1673         return false;
1674     },
1675 
1676     openAboutDialog: function()
1677     {
1678         if (FBTrace.DBG_WINDOWS)
1679             FBTrace.sysout("Firebug.openAboutDialog");
1680 
1681         try
1682         {
1683             // Firefox 4.0+ implements a new AddonManager. In case of Firefox 3.6 the module
1684             // is not available and there is an exception.
1685             Components.utils["import"]("resource://gre/modules/AddonManager.jsm");
1686         }
1687         catch (err)
1688         {
1689         }
1690 
1691         if (typeof(AddonManager) != "undefined")
1692         {
1693             AddonManager.getAddonByID("firebug@software.joehewitt.com", function(addon)
1694             {
1695                 openDialog("chrome://mozapps/content/extensions/about.xul", "",
1696                 "chrome,centerscreen,modal", addon);
1697             });
1698         }
1699         else
1700         {
1701             var extensionManager = Cc["@mozilla.org/extensions/manager;1"].getService(
1702                 Ci.nsIExtensionManager);
1703 
1704             openDialog("chrome://mozapps/content/extensions/about.xul", "",
1705                 "chrome,centerscreen,modal", "urn:mozilla:item:firebug@software.joehewitt.com",
1706                 extensionManager.datasource);
1707         }
1708     },
1709 
1710     breakOnNext: function(context, event)
1711     {
1712         // avoid bubbling from associated options
1713         if (event.target.id != "cmd_firebug_toggleBreakOn")
1714             return;
1715 
1716         if (!context)
1717         {
1718             if (FBTrace.DBG_BP)
1719                 FBTrace.sysout("Firebug chrome: breakOnNext with no context??");
1720             return;
1721         }
1722 
1723         var panel = panelBar1.selectedPanel;
1724 
1725         if (FBTrace.DBG_BP)
1726             FBTrace.sysout("Firebug chrome: breakOnNext for panel " +
1727                 (panel ? panel.name : "NO panel"), panel);
1728 
1729         if (panel && panel.breakable)
1730             Firebug.Breakpoint.toggleBreakOnNext(panel);
1731     },
1732 
1733     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
1734 
1735     visitWebsite: function(which, arg)
1736     {
1737         var url = firebugURLs[which];
1738         if (url)
1739         {
1740             if (arg)
1741                 url += arg;
1742 
1743             Win.openNewTab(url);
1744         }
1745     },
1746 
1747     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
1748     // Main Toolbar
1749 
1750     appendToolbarButton: function(button, before)
1751     {
1752         var toolbar = FirebugChrome.$("fbPanelBar1-buttons");
1753         var element = Toolbar.createToolbarButton(toolbar, button, before);
1754         element.repObject = button;
1755     },
1756 
1757     removeToolbarButton: function(button)
1758     {
1759         var toolbar = FirebugChrome.$("fbPanelBar1-buttons");
1760         for (var child = toolbar.firstChild; child; child = child.nextSibling)
1761         {
1762             if (child.repObject == button)
1763             {
1764                 toolbar.removeChild(child);
1765                 break;
1766             }
1767         }
1768     }
1769 };
1770 
1771 // ********************************************************************************************* //
1772 // Local Helpers
1773 
1774 function panelSupportsObject(panelType, object, context)
1775 {
1776     if (panelType)
1777     {
1778         try {
1779             // This tends to throw exceptions often because some objects are weird
1780             return panelType.prototype.supportsObject(object, typeof object, context)
1781         } catch (exc) {}
1782     }
1783 
1784     return 0;
1785 }
1786 
1787 function getBestPanelName(object, context, panelName)
1788 {
1789     if (!panelName && context)
1790         panelName = context.panelName;
1791 
1792     // Check if the panel type of the suggested panel supports the object, and if so, go with it.
1793     if (panelName)
1794     {
1795         var panelType = Firebug.getPanelType(panelName);
1796         if (panelSupportsObject(panelType, object, context))
1797             return panelType.prototype.name;
1798     }
1799 
1800     // The suggested name didn't pan out, so search for the panel type with the
1801     // most specific level of support.
1802     return getBestPanelSupportingObject(object, context);
1803 }
1804 
1805 function getBestPanelSupportingObject(object, context)
1806 {
1807     var bestLevel = 0;
1808     var bestPanel = null;
1809 
1810     for (var i = 0; i < Firebug.panelTypes.length; ++i)
1811     {
1812         var panelType = Firebug.panelTypes[i];
1813         if (!panelType.prototype.parentPanel)
1814         {
1815             var level = panelSupportsObject(panelType, object, context);
1816             if (!bestLevel || (level && (level > bestLevel) ))
1817             {
1818                 bestLevel = level;
1819                 bestPanel = panelType;
1820             }
1821 
1822             if (FBTrace.DBG_PANELS)
1823                 FBTrace.sysout("chrome.getBestPanelName panelType: " + panelType.prototype.name +
1824                     " level: " + level + " bestPanel: " +
1825                     (bestPanel ? bestPanel.prototype.name : "null") +
1826                     " bestLevel: " + bestLevel);
1827         }
1828     }
1829 
1830     return bestPanel ? bestPanel.prototype.name : null;
1831 }
1832 
1833 function getBestSidePanelName(sidePanelName, panelTypes)
1834 {
1835     if (sidePanelName)
1836     {
1837         // Verify, that the suggested panel name is in the acceptable list.
1838         for (var i = 0; i < panelTypes.length; ++i)
1839         {
1840             if (panelTypes[i].prototype.name == sidePanelName)
1841                 return sidePanelName;
1842         }
1843     }
1844 
1845     // Default to the first panel type in the list.
1846     return panelTypes.length ? panelTypes[0].prototype.name : null;
1847 }
1848 
1849 // ********************************************************************************************* //
1850 // Event listeners
1851 
1852 function browser1Loaded()
1853 {
1854     if (FBTrace.DBG_INITIALIZE)
1855         FBTrace.sysout("browse1Loaded\n");
1856 
1857     var browser1 = panelBar1.browser;
1858     var browser2 = panelBar2.browser;
1859     Events.removeEventListener(browser1, "load", browser1Loaded, true);
1860 
1861     browser1.contentDocument.title = "Firebug Main Panel";
1862     browser1.complete = true;
1863 
1864     if (browser1.complete && browser2.complete)
1865     {
1866         // initializeUI() is executed asynchronously (solves issue 3442)
1867         // The problem has been introduced (for an unknown reason) by revision R12210
1868         setTimeout(function() {
1869             // chrome bound into this scope
1870             FirebugChrome.initializeUI();
1871         });
1872     }
1873 
1874     if (FBTrace.DBG_INITIALIZE)
1875         FBTrace.sysout("browse1Loaded complete\n");
1876 }
1877 
1878 function browser2Loaded()
1879 {
1880     if (FBTrace.DBG_INITIALIZE)
1881         FBTrace.sysout("browse2Loaded\n");
1882 
1883     var browser1 = panelBar1.browser;
1884     var browser2 = panelBar2.browser;
1885     Events.removeEventListener(browser2, "load", browser2Loaded, true);
1886 
1887     browser2.contentDocument.title = "Firebug Side Panel";
1888     browser2.complete = true;
1889 
1890     if (browser1.complete && browser2.complete)
1891     {
1892         // See browser1Loaded for more info.
1893         setTimeout(function() {
1894             // chrome bound into this scope
1895             FirebugChrome.initializeUI();
1896         });
1897     }
1898 
1899     if (FBTrace.DBG_INITIALIZE)
1900         FBTrace.sysout("browse2Loaded complete\n");
1901 }
1902 
1903 function onBlur(event)
1904 {
1905     // XXXjjb: this seems like a waste: called continuously to clear possible highlight I guess.
1906     // XXXhh: Is this really necessary? I disabled it for now as this was preventing me
1907     // to show highlights on focus
1908     //Firebug.Inspector.highlightObject(null, Firebug.currentContext);
1909 }
1910 
1911 function onSelectLocation(event)
1912 {
1913     var locationList = FirebugChrome.getElementById("fbLocationList");
1914     var location = locationList.repObject;
1915 
1916     FirebugChrome.navigate(location);
1917 }
1918 
1919 function onSelectingPanel(event)
1920 {
1921     var panel = panelBar1.selectedPanel;
1922     var panelName = panel ? panel.name : null;
1923 
1924     if (FBTrace.DBG_PANELS)
1925         FBTrace.sysout("chrome.onSelectingPanel=" + panelName + " Firebug.currentContext=" +
1926             (Firebug.currentContext ? Firebug.currentContext.getName() : "undefined"));
1927 
1928     if (Firebug.currentContext)
1929     {
1930         Firebug.currentContext.previousPanelName = Firebug.currentContext.panelName;
1931         Firebug.currentContext.panelName = panelName;
1932 
1933         Firebug.currentContext.sidePanelName =
1934             Firebug.currentContext.sidePanelNames &&
1935             panelName in Firebug.currentContext.sidePanelNames
1936             ? Firebug.currentContext.sidePanelNames[panelName]
1937             : null;
1938     }
1939 
1940     if (panel)
1941         panel.navigate(panel.location);
1942 
1943     // Hide all toolbars now. It's a responsibility of the new selected panel to show
1944     // those toolbars, that are necessary. This avoids the situation when a naughty panel
1945     // doesn't clean up its toolbars. This must be done before 'showPanel' is dispatched,
1946     // where the visibility of the BON buttons is managed.
1947     var toolbar = FirebugChrome.$("fbToolbarInner");
1948     var child = toolbar.firstChild;
1949     while (child)
1950     {
1951         Dom.collapse(child, true);
1952         child = child.nextSibling;
1953     }
1954 
1955     // Those extensions that don't use XUL overlays (i.e. bootstrapped extensions)
1956     // can provide toolbar buttons throug Firebug APIs.
1957     var panelToolbar = FirebugChrome.$("fbPanelToolbar");
1958     Dom.eraseNode(panelToolbar);
1959 
1960     if (panel)
1961     {
1962         // get buttons from current panel
1963         var buttons;
1964         if (panel.getPanelToolbarButtons)
1965             buttons = panel.getPanelToolbarButtons();
1966 
1967         if (!buttons)
1968             buttons = [];
1969 
1970         Events.dispatch(Firebug.uiListeners, "onGetPanelToolbarButtons", [panel, buttons]);
1971 
1972         for (var i=0; i<buttons.length; ++i)
1973             Toolbar.createToolbarButton(panelToolbar, buttons[i]);
1974 
1975         Dom.collapse(panelToolbar, buttons.length == 0);
1976     }
1977 
1978     // Calling Firebug.showPanel causes dispatching 'showPanel' to all modules.
1979     var browser = panel ? panel.context.browser : Firefox.getCurrentBrowser();
1980     Firebug.showPanel(browser, panel);
1981 
1982     // Synchronize UI around panels. Execute the sync after 'showPanel' so the logic
1983     // can decide whether to display separators or not.
1984     // xxxHonza: The command line should be synced here as well.
1985     Firebug.chrome.syncLocationList();
1986     Firebug.chrome.syncStatusPath();
1987 
1988     //xxxjjb: unfortunately the Stack side panel depends on the status path (sync after.)
1989     Firebug.chrome.syncSidePanels();
1990 }
1991 
1992 function onMouseScroll(event)
1993 {
1994     if (Events.isControlAlt(event))
1995     {
1996         Events.cancelEvent(event);
1997         Firebug.Options.changeTextSize(-event.detail);
1998     }
1999 }
2000 
2001 function onSelectedSidePanel(event)
2002 {
2003     var sidePanel = panelBar2.selectedPanel;
2004     if (Firebug.currentContext)
2005     {
2006         var panelName = Firebug.currentContext.panelName;
2007         if (panelName)
2008         {
2009             var sidePanelName = sidePanel ? sidePanel.name : null;
2010             Firebug.currentContext.sidePanelNames[panelName] = sidePanelName;
2011         }
2012     }
2013 
2014     if (FBTrace.DBG_PANELS)
2015         FBTrace.sysout("chrome.onSelectedSidePanel name=" +
2016             (sidePanel ? sidePanel.name : "undefined"));
2017 
2018     var panel = panelBar1.selectedPanel;
2019     if (panel && sidePanel)
2020         sidePanel.select(panel.selection);
2021 
2022     var browser = sidePanel ? sidePanel.context.browser : Firefox.getCurrentBrowser();
2023     // dispatch to modules
2024     Firebug.showSidePanel(browser, sidePanel);
2025 }
2026 
2027 function onPanelMouseOver(event)
2028 {
2029     var object = Firebug.getRepObject(event.target);
2030     if (!object)
2031         return;
2032 
2033     var rep = Firebug.getRep(object, Firebug.currentContext);
2034     if (rep)
2035         rep.highlightObject(object, Firebug.currentContext, event.target);
2036 }
2037 
2038 function onPanelMouseOut(event)
2039 {
2040     var object = Firebug.getRepObject(event.target);
2041     if (!object)
2042         return;
2043 
2044     var rep = Firebug.getRep(object, Firebug.currentContext);
2045     if (rep)
2046         rep.unhighlightObject(object, Firebug.currentContext, event.target);
2047 }
2048 
2049 function onPanelClick(event)
2050 {
2051     var repNode = Firebug.getRepNode(event.target);
2052     if (repNode)
2053     {
2054         var object = repNode.repObject;
2055         var rep = Firebug.getRep(object, Firebug.currentContext);
2056         var realObject = rep ? rep.getRealObject(object, Firebug.currentContext) : null;
2057         var realRep = realObject ? Firebug.getRep(realObject, Firebug.currentContext) : rep;
2058         if (!realObject)
2059             realObject = object;
2060 
2061         if (Events.isLeftClick(event))
2062         {
2063             if (Css.hasClass(repNode, "objectLink"))
2064             {
2065                 if (realRep)
2066                 {
2067                     realRep.inspectObject(realObject, Firebug.currentContext);
2068                     Events.cancelEvent(event);
2069                 }
2070             }
2071         }
2072     }
2073 }
2074 
2075 function onPanelMouseDown(event)
2076 {
2077     if (Events.isLeftClick(event))
2078     {
2079         this.lastMouseDownPosition = {x: event.screenX, y: event.screenY};
2080     }
2081     else if (Events.isMiddleClick(event, true) && Events.isControlAlt(event))
2082     {
2083         Events.cancelEvent(event);
2084         Firebug.Options.setTextSize(0);
2085     }
2086     else if (Events.isMiddleClick(event) && Firebug.getRepNode(event.target))
2087     {
2088         // Prevent auto-scroll when middle-clicking a rep object
2089         Events.cancelEvent(event);
2090     }
2091 }
2092 
2093 function onPanelMouseUp(event)
2094 {
2095     if (Events.isLeftClick(event))
2096     {
2097         var selection = event.target.ownerDocument.defaultView.getSelection();
2098         var target = selection.focusNode || event.target;
2099         if (selection.focusNode === selection.anchorNode)
2100         {
2101             var editable = Dom.getAncestorByClass(target, "editable");
2102             if (editable || Css.hasClass(event.target, "inlineExpander"))
2103             {
2104                 var selectionData;
2105                 var selFO = selection.focusOffset,selAO = selection.anchorOffset;
2106 
2107                 // selection is collapsed
2108                 if (selFO == selAO)
2109                 {
2110                     var distance = Math.abs(event.screenX - this.lastMouseDownPosition.x) +
2111                         Math.abs(event.screenY - this.lastMouseDownPosition.y);
2112 
2113                     // If mouse has moved far enough, set selection at that point
2114                     if (distance > 3)
2115                         selectionData = {start: selFO, end: selFO};
2116                     // otherwise leave selectionData undefined to select all text
2117                 }
2118                 else if (selFO < selAO)
2119                 {
2120                     selectionData = {start: selFO, end: selAO};
2121                 }
2122                 else
2123                 {
2124                     selectionData = {start: selAO, end: selFO};
2125                 }
2126 
2127                 if (editable)
2128                 {
2129                     Firebug.Editor.startEditing(editable, null, null, selectionData);
2130                 }
2131                 else
2132                 {
2133                     Firebug.Editor.setSelection(selectionData || {start: selFO, end: selFO});
2134                     selection.removeAllRanges();
2135                 }
2136 
2137                 Events.cancelEvent(event);
2138             }
2139         }
2140     }
2141     else if (Events.isControlClick(event) || Events.isMiddleClick(event))
2142     {
2143         var repNode = Firebug.getRepNode(event.target);
2144         if (!repNode)
2145             return;
2146 
2147         var object = repNode.repObject;
2148         var rep = Firebug.getRep(object, Firebug.currentContext);
2149         var realObject = rep ? rep.getRealObject(object, Firebug.currentContext) : null;
2150         var realRep = realObject ? Firebug.getRep(realObject, Firebug.currentContext) : rep;
2151         if (!realObject)
2152             realObject = object;
2153 
2154         if (!realRep || !realRep.browseObject(realObject, Firebug.currentContext))
2155         {
2156             if (rep && !(rep != realRep && rep.browseObject(object, Firebug.currentContext)))
2157             {
2158                 var panel = Firebug.getElementPanel(event.target);
2159                 if (!panel || !panel.browseObject(realObject))
2160                     return;
2161             }
2162         }
2163         Events.cancelEvent(event);
2164     }
2165 }
2166 
2167 function onMainTabBoxMouseDown(event)
2168 {
2169     if (Firebug.isInBrowser())
2170     {
2171         var contentSplitter = FirebugChrome.$("fbContentSplitter");
2172         // TODO: grab the splitter here.
2173     }
2174 }
2175 
2176 function stopBubble(event)
2177 {
2178     event.stopPropagation();
2179 }
2180 
2181 function getRealObject(object)
2182 {
2183     var rep = Firebug.getRep(object, Firebug.currentContext);
2184     var realObject = rep ? rep.getRealObject(object, Firebug.currentContext) : null;
2185     return realObject ? realObject : object;
2186 }
2187 
2188 function fatalError(summary, exc)
2189 {
2190     if (typeof(FBTrace) !== undefined)
2191         FBTrace.sysout.apply(FBTrace, arguments);
2192 
2193     Components.utils.reportError(summary);
2194 
2195     throw exc;
2196 }
2197 
2198 return FirebugChrome;
2199  
2200 }  // end of createFirebugChrome(win)
2201 }; // end of var ChromeFactory object
2202 
2203 // ********************************************************************************************* //
2204 
2205 return ChromeFactory;
2206 
2207 // ********************************************************************************************* //
2208 });
2209