1 /* See license.txt for terms of usage */
  2 
  3 define([
  4     "firebug/lib/trace",
  5     "firebug/lib/options",
  6     "firebug/lib/locale",
  7     "firebug/lib/array",
  8     "firebug/firefox/browserOverlayLib",
  9     "firebug/firefox/browserCommands",
 10     "firebug/firefox/browserMenu",
 11     "firebug/firefox/browserToolbar",
 12 ],
 13 function(FBTrace, Options, Locale, Arr, BrowserOverlayLib, BrowserCommands, BrowserMenu,
 14     BrowserToolbar) {
 15 
 16 with (BrowserOverlayLib) {
 17 
 18 // ********************************************************************************************* //
 19 // Constants
 20 
 21 var Cc = Components.classes;
 22 var Ci = Components.interfaces;
 23 var Cu = Components.utils;
 24 
 25 Locale.registerStringBundle("chrome://firebug/locale/firebug.properties");
 26 Locale.registerStringBundle("chrome://firebug/locale/cookies.properties");
 27 
 28 Cu.import("resource://firebug/loader.js");
 29 Cu.import("resource://firebug/fbtrace.js");
 30 
 31 const firstRunPage = "https://getfirebug.com/firstrun#Firebug ";
 32 
 33 // ********************************************************************************************* //
 34 // BrowserOverlay Implementation
 35 
 36 function BrowserOverlay(win)
 37 {
 38     this.win = win;
 39     this.doc = win.document;
 40 }
 41 
 42 BrowserOverlay.prototype =
 43 {
 44     // When Firebug is disabled or unistalled this elements must be removed from
 45     // chrome UI (XUL).
 46     nodesToRemove: [],
 47 
 48     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 49     // Initialization
 50 
 51     initialize: function(reason)
 52     {
 53         // Expose BrowserOverlayLib object to extensions.
 54         this.win.Firebug.BrowserOverlayLib = BrowserOverlayLib;
 55 
 56         // This element (a broadcaster) is storing Firebug state information. Other elements
 57         // (like for example the Firebug start button) can watch it and display the info to
 58         // the user.
 59         $el(this.doc, "broadcaster", {id: "firebugStatus", suspended: true},
 60             $(this.doc, "mainBroadcasterSet"));
 61 
 62         var node = $stylesheet(this.doc, "chrome://firebug/content/firefox/browserOverlay.css");
 63         this.nodesToRemove.push(node);
 64 
 65         this.loadContextMenuOverlay();
 66         this.loadFirstRunPage(reason);
 67 
 68         var version = this.getVersion();
 69 
 70         BrowserCommands.overlay(this.doc);
 71         BrowserMenu.overlay(this.doc);
 72         BrowserToolbar.overlay(this.doc, version);
 73 
 74         this.internationalize();
 75         this.allPagesActivation();
 76     },
 77 
 78     internationalize: function()
 79     {
 80         // Internationalize all elements with 'fbInternational' class. Clone
 81         // before internationalizing.
 82         var elements = Arr.cloneArray(this.doc.getElementsByClassName("fbInternational"));
 83         Locale.internationalizeElements(this.doc, elements, ["label", "tooltiptext", "aria-label"]);
 84     },
 85 
 86     allPagesActivation: function()
 87     {
 88         // Load Firebug by default if activation is on for all pages (see issue 5522)
 89         if (Options.get("allPagesActivation") == "on" || !Options.get("delayLoad"))
 90         {
 91             var self = this;
 92             this.startFirebug(function(Firebug)
 93             {
 94                 var browser = Firebug.Firefox.getBrowserForWindow(self.win);
 95                 var uri = Firebug.Firefox.getCurrentURI();
 96 
 97                 // Open Firebug UI (e.g. if the annotations say so, issue 5623)
 98                 if (uri && Firebug.TabWatcher.shouldCreateContext(browser, uri.spec, null))
 99                     Firebug.toggleBar(true);
100 
101                 FBTrace.sysout("Firebug loaded by default since 'allPagesActivation' is on " +
102                     "or 'delayLoad' is false");
103             });
104         }
105     },
106 
107     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
108     // Load Rest of Firebug
109 
110     /**
111      * This method is called by the Fremework to load entire Firebug. It's executed when
112      * the user requires Firebug for the first time.
113      *
114      * @param {Object} callback Executed when Firebug is fully loaded
115      */
116     startFirebug: function(callback)
117     {
118         if (this.win.Firebug.waitingForFirstLoad)
119             return;
120 
121         if (this.win.Firebug.isInitialized)
122             return callback && callback(this.win.Firebug);
123 
124         if (FBTrace.DBG_INITIALIZE)
125             FBTrace.sysout("overlay; Load Firebug...", (callback ? callback.toString() : ""));
126 
127         this.win.Firebug.waitingForFirstLoad = true;
128 
129         var container = $(this.doc, "appcontent");
130 
131         // List of Firebug scripts that must be loaded into the global scope (browser.xul)
132         // FBTrace is no longer loaded into the global space.
133         var scriptSources = [
134             "chrome://firebug/content/legacy.js",
135             "chrome://firebug/content/moduleConfig.js"
136         ]
137 
138         // Create script elements.
139         var self = this;
140         scriptSources.forEach(function(url)
141         {
142             $script(self.doc, url);
143         });
144 
145         // Create Firebug splitter element.
146         $el(this.doc, "splitter", {id: "fbContentSplitter", collapsed: "true"}, container);
147 
148         // Create Firebug main frame and container.
149         $el(this.doc, "vbox", {id: "fbMainFrame", collapsed: "true", persist: "height,width"}, [
150             $el(this.doc, "browser", {
151                 id: "fbMainContainer",
152                 flex: "2",
153                 src: "chrome://firebug/content/firefox/firebugFrame.xul",
154                 disablehistory: "true"
155             })
156         ], container);
157 
158         // When Firebug is fully loaded and initialized it fires a "FirebugLoaded"
159         // event to the browser document (browser.xul scope). Wait for that to happen.
160         this.doc.addEventListener("FirebugLoaded", function onLoad()
161         {
162             self.doc.removeEventListener("FirebugLoaded", onLoad, false);
163             self.win.Firebug.waitingForFirstLoad = false;
164 
165             // xxxHonza: TODO find a better place for notifying extensions
166             FirebugLoader.dispatchToScopes("firebugFrameLoad", [self.win.Firebug]);
167             callback && callback(self.win.Firebug);
168         }, false);
169     },
170 
171     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
172     // Firebug Menu Handlers
173 
174     onOptionsShowing: function(popup)
175     {
176         for (var child = popup.firstChild; child; child = child.nextSibling)
177         {
178             if (child.localName == "menuitem")
179             {
180                 var option = child.getAttribute("option");
181                 if (option)
182                 {
183                     var checked = Options.get(option);
184 
185                     // xxxHonza: I belive that allPagesActivation could be simple boolean option.
186                     if (option == "allPagesActivation")
187                         checked = (checked == "on") ? true : false;
188 
189                     child.setAttribute("checked", checked);
190                 }
191             }
192         }
193     },
194 
195     onToggleOption: function(menuItem)
196     {
197         var option = menuItem.getAttribute("option");
198         var checked = menuItem.getAttribute("checked") == "true";
199 
200         Options.set(option, checked);
201     },
202 
203     onMenuShowing: function(popup, event)
204     {
205         // If the event comes from a sub menu, just ignore it.
206         if (popup != event.target)
207             return;
208 
209         while (popup.lastChild)
210             popup.removeChild(popup.lastChild);
211 
212         // Generate dynamic content.
213         for (var i=0; i<BrowserMenu.firebugMenuContent.length; i++)
214             popup.appendChild(BrowserMenu.firebugMenuContent[i].cloneNode(true));
215 
216         var collapsed = "true";
217         if (this.win.Firebug.chrome)
218         {
219             var fbContentBox = this.win.Firebug.chrome.$("fbContentBox");
220             collapsed = fbContentBox.getAttribute("collapsed");
221         }
222 
223         var currPos = Options.get("framePosition");
224         var placement = this.win.Firebug.getPlacement ? this.win.Firebug.getPlacement() : "";
225 
226         // Switch between "Open Firebug" and "Hide Firebug" label in the popup menu.
227         var toggleFirebug = popup.querySelector("#menu_firebug_toggleFirebug");
228         if (toggleFirebug)
229         {
230             var hiddenUI = (collapsed == "true" || placement == "minimized");
231             toggleFirebug.setAttribute("label", (hiddenUI ?
232                 Locale.$STR("firebug.ShowFirebug") : Locale.$STR("firebug.HideFirebug")));
233 
234             toggleFirebug.setAttribute("tooltiptext", (hiddenUI ?
235                 Locale.$STR("firebug.menu.tip.Open_Firebug") :
236                 Locale.$STR("firebug.menu.tip.Minimize_Firebug")));
237 
238             var currentLocation = toggleFirebug.ownerDocument.defaultView.top.location.href;
239             var inDetachedWindow = currentLocation.indexOf("firebug.xul") > 0;
240 
241             // If Firebug is detached, use "Focus Firebug Window" label
242             // instead of "Hide Firebug" when the menu isn't opened from
243             // within the detached Firebug window. the 'placement' is used
244             // to ensure Firebug isn't closed with close button of detached window
245             // and 'inDetachedWindow' variable is also used to ensure the menu is
246             // opened from within the detached window.
247             if (currPos == "detached" && this.win.Firebug.currentContext &&
248                 placement != "minimized" && !inDetachedWindow)
249             {
250                 toggleFirebug.setAttribute("label", Locale.$STR("firebug.FocusFirebug"));
251                 toggleFirebug.setAttribute("tooltiptext",
252                     Locale.$STR("firebug.menu.tip.Focus_Firebug"));
253             }
254         }
255 
256         // Hide "Deactivate Firebug" menu if Firebug is not active.
257         var closeFirebug = popup.querySelector("#menu_firebug_closeFirebug");
258         if (closeFirebug)
259         {
260             closeFirebug.setAttribute("collapsed",
261                 (this.win.Firebug.currentContext ? "false" : "true"));
262         }
263 
264         // Update About Menu
265         var version = this.getVersion();
266         if (version)
267         {
268             var node = popup.getElementsByClassName("firebugAbout")[0];
269             var aboutLabel = node.getAttribute("label");
270             node.setAttribute("label", aboutLabel + " " + version);
271             node.classList.remove("firebugAbout");
272         }
273 
274         // Allow Firebug menu customization (see FBTest and FBTrace as an example).
275         var event = new this.win.CustomEvent("firebugMenuShowing", {detail: popup});
276         this.doc.dispatchEvent(event);
277     },
278 
279     onMenuHiding: function(popup, event)
280     {
281         if (popup != event.target)
282             return;
283 
284         // xxxHonza: I don't know why the timeout must be here, but if it isn't
285         // the icon menu is broken (see issue 5427)
286         this.win.setTimeout(function()
287         {
288             while (popup.lastChild)
289                 popup.removeChild(popup.lastChild);
290         });
291     },
292 
293     onPositionPopupShowing: function(popup)
294     {
295         while (popup.lastChild)
296             popup.removeChild(popup.lastChild);
297 
298         // Load Firebug before the position is changed.
299         var oncommand = "Firebug.browserOverlay.startFirebug(function(){" +
300             "Firebug.chrome.setPosition('%pos%')" + "})";
301 
302         var items = [];
303         var currPos = Options.get("framePosition");
304         for each (var pos in ["detached", "top", "bottom", "left", "right"])
305         {
306             var label = pos.charAt(0).toUpperCase() + pos.slice(1);
307             var item = $menuitem(this.doc, {
308                 label: Locale.$STR("firebug.menu." + label),
309                 tooltiptext: Locale.$STR("firebug.menu.tip." + label),
310                 type: "radio",
311                 oncommand: oncommand.replace("%pos%", pos),
312                 checked: (currPos == pos)
313             });
314 
315             if (pos == "detached")
316                 items.key = "key_firebug_detachFirebug";
317 
318             popup.appendChild(item);
319         }
320 
321         return true;
322     },
323 
324     openAboutDialog: function()
325     {
326         var self = this;
327 
328         // Firefox 4.0+
329         Cu["import"]("resource://gre/modules/AddonManager.jsm");
330         this.win.AddonManager.getAddonByID("firebug@software.joehewitt.com", function(addon)
331         {
332             self.win.openDialog("chrome://mozapps/content/extensions/about.xul", "",
333                 "chrome,centerscreen,modal", addon);
334         });
335     },
336 
337     setPosition: function(newPosition)
338     {
339         // todo
340     },
341 
342     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
343     // Firebug Version
344 
345     getVersion: function()
346     {
347         var versionURL = "chrome://firebug/content/branch.properties";
348         var ioService = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
349 
350         var channel = ioService.newChannel(versionURL, null, null);
351         var input = channel.open();
352         var sis = Cc["@mozilla.org/scriptableinputstream;1"].
353             createInstance(Ci.nsIScriptableInputStream);
354         sis.init(input);
355 
356         var content = sis.readBytes(input.available());
357         sis.close();
358 
359         var m = /RELEASE=(.*)/.exec(content);
360         if (m)
361             var release = m[1];
362         else
363             return "no RELEASE in " + versionURL;
364 
365         m = /VERSION=(.*)/.exec(content);
366         if (m)
367             var version = m[1];
368         else
369             return "no VERSION in " + versionURL;
370 
371         return version+""+release;
372     },
373 
374     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
375     // External Editors
376 
377     onEditorsShowing: function(popup)
378     {
379         var self = this;
380         this.startFirebug(function()
381         {
382             self.win.Firebug.ExternalEditors.onEditorsShowing(popup);
383         });
384 
385         return true;
386     },
387 
388     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
389     // Page Context Menu Overlay
390 
391     loadContextMenuOverlay: function()
392     {
393         var contextMenu = this.win.nsContextMenu;
394         if (typeof(contextMenu) == "undefined")
395             return;
396 
397         // isTargetAFormControl is removed, see:
398         // https://bugzilla.mozilla.org/show_bug.cgi?id=433168
399         if (typeof(contextMenu.prototype.isTargetAFormControl) != "undefined")
400         {
401             // https://bugzilla.mozilla.org/show_bug.cgi?id=433168
402             var setTargetOriginal = this.setTargetOriginal = contextMenu.prototype.setTarget;
403             contextMenu.prototype.setTarget = function(aNode, aRangeParent, aRangeOffset)
404             {
405                 setTargetOriginal.apply(this, arguments);
406 
407                 if (this.isTargetAFormControl(aNode))
408                     this.shouldDisplay = true;
409             };
410         }
411 
412         // Hide built-in inspector if the pref says so.
413         var initItemsOriginal = this.initItemsOriginal = contextMenu.prototype.initItems;
414         contextMenu.prototype.initItems = function()
415         {
416             initItemsOriginal.apply(this, arguments);
417 
418             // Hide built-in inspector menu item if the pref "extensions.firebug.hideDefaultInspector"
419             // says so. Note that there is also built-in preference "devtools.inspector.enable" that
420             // can be used for the same purpose.
421             var hideInspect = Options.get("hideDefaultInspector");
422             if (hideInspect)
423             {
424                 this.showItem("inspect-separator", false);
425                 this.showItem("context-inspect", false);
426             }
427         }
428     },
429 
430     unloadContextMenuOverlay: function()
431     {
432         var contextMenu = this.win.nsContextMenu;
433         if (typeof(contextMenu) == "undefined")
434             return;
435 
436         contextMenu.prototype.setTarget = this.setTargetOriginal;
437         contextMenu.prototype.initItems = this.initItemsOriginal;
438     },
439 
440     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
441     // First Run Page
442 
443     loadFirstRunPage: function(reason)
444     {
445         if (this.checkFirebugVersion(Options.get("currentVersion")) <= 0)
446             return;
447 
448         // Do not show the first run page when Firebug is being updated. It'll be displayed
449         // the next time the browser is restarted
450         // # ADDON_UPGRADE == 7
451         if (reason == 7)
452             return;
453 
454         // Open the page in the top most window, so the user can see it immediately.
455         var wm = Cc["@mozilla.org/appshell/window-mediator;1"].getService(Ci.nsIWindowMediator);
456         if (wm.getMostRecentWindow("navigator:browser") == this.win.top)
457         {
458             // Update the preference to make sure the page is not displayed again.
459             // To avoid being annoying when Firefox crashes, forcibly save it, too.
460             var version = this.getVersion();
461             Options.set("currentVersion", version);
462 
463             if (Options.get("showFirstRunPage"))
464             {
465                 var self = this;
466                 var timeout = this.win.setTimeout(function()
467                 {
468                     if (self.win.closed)
469                         return;
470 
471                     self.openFirstRunPage(self.win);
472                 }, 1000);
473 
474                 this.win.addEventListener("unload", function()
475                 {
476                     clearTimeout(timeout);
477                 }, false);
478             }
479         }
480     },
481 
482     openFirstRunPage: function(win)
483     {
484         var version = this.getVersion();
485         var url = firstRunPage + version;
486 
487         var browser = win.gBrowser;
488         if (!browser)
489         {
490             FBTrace.sysout("browserOverlay.openFirstRunPage; ERROR there is no gBrowser!");
491             return;
492         }
493 
494         // Open the firstRunPage in background
495         /*gBrowser.selectedTab = */browser.addTab(url, null, null, null);
496 
497         // Make sure prefs are stored, otherwise the firstRunPage would be displayed
498         // again if Firefox crashes.
499         this.win.setTimeout(function()
500         {
501             Options.forceSave();
502         }, 400);
503     },
504 
505     checkFirebugVersion: function(currentVersion)
506     {
507         if (!currentVersion)
508             return 1;
509 
510         var version = this.getVersion();
511 
512         // Use Firefox comparator service.
513         var versionChecker = Cc["@mozilla.org/xpcom/version-comparator;1"].
514             getService(Ci.nsIVersionComparator);
515 
516         return versionChecker.compare(version, currentVersion);
517     }
518 }
519 
520 // ********************************************************************************************* //
521 // Registration
522 
523 return BrowserOverlay;
524 
525 // ********************************************************************************************* //
526 }});
527