Bug 575870 - Implement custom drawn titlebar for classic, aero basic, and xp. Also...
[mozilla-central.git] / browser / base / content / browser.js
blobd38fda3c80a22167ed09c350d10176cd333cff6a
1 # -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2 # ***** BEGIN LICENSE BLOCK *****
3 # Version: MPL 1.1/GPL 2.0/LGPL 2.1
5 # The contents of this file are subject to the Mozilla Public License Version
6 # 1.1 (the "License"); you may not use this file except in compliance with
7 # the License. You may obtain a copy of the License at
8 # http://www.mozilla.org/MPL/
10 # Software distributed under the License is distributed on an "AS IS" basis,
11 # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 # for the specific language governing rights and limitations under the
13 # License.
15 # The Original Code is mozilla.org code.
17 # The Initial Developer of the Original Code is
18 # Netscape Communications Corporation.
19 # Portions created by the Initial Developer are Copyright (C) 1998
20 # the Initial Developer. All Rights Reserved.
22 # Contributor(s):
23 #   Blake Ross <blake@cs.stanford.edu>
24 #   David Hyatt <hyatt@mozilla.org>
25 #   Peter Annema <disttsc@bart.nl>
26 #   Dean Tessman <dean_tessman@hotmail.com>
27 #   Kevin Puetz <puetzk@iastate.edu>
28 #   Ben Goodger <ben@netscape.com>
29 #   Pierre Chanial <chanial@noos.fr>
30 #   Jason Eager <jce2@po.cwru.edu>
31 #   Joe Hewitt <hewitt@netscape.com>
32 #   Alec Flett <alecf@netscape.com>
33 #   Asaf Romano <mozilla.mano@sent.com>
34 #   Jason Barnabe <jason_barnabe@fastmail.fm>
35 #   Peter Parente <parente@cs.unc.edu>
36 #   Giorgio Maone <g.maone@informaction.com>
37 #   Tom Germeau <tom.germeau@epigoon.com>
38 #   Jesse Ruderman <jruderman@gmail.com>
39 #   Joe Hughes <joe@retrovirus.com>
40 #   Pamela Greene <pamg.bugs@gmail.com>
41 #   Michael Ventnor <m.ventnor@gmail.com>
42 #   Simon Bünzli <zeniko@gmail.com>
43 #   Johnathan Nightingale <johnath@mozilla.com>
44 #   Ehsan Akhgari <ehsan.akhgari@gmail.com>
45 #   Dão Gottwald <dao@mozilla.com>
46 #   Thomas K. Dyas <tdyas@zecador.org>
47 #   Edward Lee <edward.lee@engineering.uiuc.edu>
48 #   Paul O’Shannessy <paul@oshannessy.com>
49 #   Nils Maier <maierman@web.de>
50 #   Rob Arnold <robarnold@cmu.edu>
51 #   Dietrich Ayala <dietrich@mozilla.com>
52 #   Gavin Sharp <gavin@gavinsharp.com>
53 #   Justin Dolske <dolske@mozilla.com>
54 #   Rob Campbell <rcampbell@mozilla.com>
56 # Alternatively, the contents of this file may be used under the terms of
57 # either the GNU General Public License Version 2 or later (the "GPL"), or
58 # the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
59 # in which case the provisions of the GPL or the LGPL are applicable instead
60 # of those above. If you wish to allow use of your version of this file only
61 # under the terms of either the GPL or the LGPL, and not to allow others to
62 # use your version of this file under the terms of the MPL, indicate your
63 # decision by deleting the provisions above and replace them with the notice
64 # and other provisions required by the GPL or the LGPL. If you do not delete
65 # the provisions above, a recipient may use your version of this file under
66 # the terms of any one of the MPL, the GPL or the LGPL.
68 # ***** END LICENSE BLOCK *****
70 let Ci = Components.interfaces;
71 let Cu = Components.utils;
73 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
75 const nsIWebNavigation = Ci.nsIWebNavigation;
77 var gCharsetMenu = null;
78 var gLastBrowserCharset = null;
79 var gPrevCharset = null;
80 var gProxyFavIcon = null;
81 var gLastValidURLStr = "";
82 var gInPrintPreviewMode = false;
83 var gDownloadMgr = null;
84 var gContextMenu = null; // nsContextMenu instance
86 #ifndef XP_MACOSX
87 var gEditUIVisible = true;
88 #endif
91   ["gBrowser",            "content"],
92   ["gNavToolbox",         "navigator-toolbox"],
93   ["gURLBar",             "urlbar"],
94   ["gNavigatorBundle",    "bundle_browser"]
95 ].forEach(function (elementGlobal) {
96   var [name, id] = elementGlobal;
97   window.__defineGetter__(name, function () {
98     var element = document.getElementById(id);
99     if (!element)
100       return null;
101     delete window[name];
102     return window[name] = element;
103   });
104   window.__defineSetter__(name, function (val) {
105     delete window[name];
106     return window[name] = val;
107   });
110 // Smart getter for the findbar.  If you don't wish to force the creation of
111 // the findbar, check gFindBarInitialized first.
112 var gFindBarInitialized = false;
113 XPCOMUtils.defineLazyGetter(window, "gFindBar", function() {
114   let XULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
115   let findbar = document.createElementNS(XULNS, "findbar");
116   findbar.setAttribute("browserid", "content");
117   findbar.id = "FindToolbar";
119   let browserBottomBox = document.getElementById("browser-bottombox");
120   browserBottomBox.insertBefore(findbar, browserBottomBox.firstChild);
122   // Force a style flush to ensure that our binding is attached.
123   findbar.clientTop;
124   window.gFindBarInitialized = true;
125   return findbar;
128 __defineGetter__("gPrefService", function() {
129   delete this.gPrefService;
130   return this.gPrefService = Services.prefs;
133 __defineGetter__("AddonManager", function() {
134   Cu.import("resource://gre/modules/AddonManager.jsm");
135   return this.AddonManager;
137 __defineSetter__("AddonManager", function (val) {
138   delete this.AddonManager;
139   return this.AddonManager = val;
142 __defineGetter__("PluralForm", function() {
143   Cu.import("resource://gre/modules/PluralForm.jsm");
144   return this.PluralForm;
146 __defineSetter__("PluralForm", function (val) {
147   delete this.PluralForm;
148   return this.PluralForm = val;
151 #ifdef MOZ_SERVICES_SYNC
152 XPCOMUtils.defineLazyGetter(this, "Weave", function() {
153   let tmp = {};
154   Cu.import("resource://services-sync/service.js", tmp);
155   return tmp.Weave;
157 #endif
159 XPCOMUtils.defineLazyGetter(this, "PopupNotifications", function () {
160   let tmp = {};
161   Cu.import("resource://gre/modules/PopupNotifications.jsm", tmp);
162   return new tmp.PopupNotifications(gBrowser,
163                                     document.getElementById("notification-popup"),
164                                     document.getElementById("notification-popup-box"));
167 let gInitialPages = [
168   "about:blank",
169   "about:privatebrowsing",
170   "about:sessionrestore"
173 #include browser-fullZoom.js
174 #include inspector.js
175 #include browser-places.js
176 #include browser-tabPreviews.js
178 #ifdef MOZ_SERVICES_SYNC
179 #include browser-syncui.js
180 #endif
182 XPCOMUtils.defineLazyGetter(this, "Win7Features", function () {
183 #ifdef XP_WIN
184 #ifndef WINCE
185   const WINTASKBAR_CONTRACTID = "@mozilla.org/windows-taskbar;1";
186   if (WINTASKBAR_CONTRACTID in Cc &&
187       Cc[WINTASKBAR_CONTRACTID].getService(Ci.nsIWinTaskbar).available) {
188     let temp = {};
189     Cu.import("resource://gre/modules/WindowsPreviewPerTab.jsm", temp);
190     let AeroPeek = temp.AeroPeek;
191     return {
192       onOpenWindow: function () {
193         AeroPeek.onOpenWindow(window);
194       },
195       onCloseWindow: function () {
196         AeroPeek.onCloseWindow(window);
197       }
198     };
199   }
200 #endif
201 #endif
202   return null;
205 #ifdef MOZ_CRASHREPORTER
206 XPCOMUtils.defineLazyServiceGetter(this, "gCrashReporter",
207                                    "@mozilla.org/xre/app-info;1",
208                                    "nsICrashReporter");
209 #endif
212 * We can avoid adding multiple load event listeners and save some time by adding
213 * one listener that calls all real handlers.
215 function pageShowEventHandlers(event) {
216   // Filter out events that are not about the document load we are interested in
217   if (event.originalTarget == content.document) {
218     charsetLoadListener(event);
219     XULBrowserWindow.asyncUpdateUI();
220   }
223 function UpdateBackForwardCommands(aWebNavigation) {
224   var backBroadcaster = document.getElementById("Browser:Back");
225   var forwardBroadcaster = document.getElementById("Browser:Forward");
227   // Avoid setting attributes on broadcasters if the value hasn't changed!
228   // Remember, guys, setting attributes on elements is expensive!  They
229   // get inherited into anonymous content, broadcast to other widgets, etc.!
230   // Don't do it if the value hasn't changed! - dwh
232   var backDisabled = backBroadcaster.hasAttribute("disabled");
233   var forwardDisabled = forwardBroadcaster.hasAttribute("disabled");
234   if (backDisabled == aWebNavigation.canGoBack) {
235     if (backDisabled)
236       backBroadcaster.removeAttribute("disabled");
237     else
238       backBroadcaster.setAttribute("disabled", true);
239   }
241   if (forwardDisabled == aWebNavigation.canGoForward) {
242     if (forwardDisabled)
243       forwardBroadcaster.removeAttribute("disabled");
244     else
245       forwardBroadcaster.setAttribute("disabled", true);
246   }
249 #ifdef XP_MACOSX
251  * Click-and-Hold implementation for the Back and Forward buttons
252  * XXXmano: should this live in toolbarbutton.xml?
253  */
254 function SetClickAndHoldHandlers() {
255   var timer;
257   function timerCallback(aButton) {
258     aButton.firstChild.hidden = false;
259     aButton.open = true;
260     timer = null;
261   }
263   function mousedownHandler(aEvent) {
264     if (aEvent.button != 0 ||
265         aEvent.currentTarget.open ||
266         aEvent.currentTarget.disabled)
267       return;
269     // Prevent the menupopup from opening immediately
270     aEvent.currentTarget.firstChild.hidden = true;
272     timer = setTimeout(timerCallback, 500, aEvent.currentTarget);
273   }
275   function clickHandler(aEvent) {
276     if (aEvent.button == 0 &&
277         aEvent.target == aEvent.currentTarget &&
278         !aEvent.currentTarget.open &&
279         !aEvent.currentTarget.disabled) {
280       let cmdEvent = document.createEvent("xulcommandevent");
281       cmdEvent.initCommandEvent("command", true, true, window, 0,
282                                 aEvent.ctrlKey, aEvent.altKey, aEvent.shiftKey,
283                                 aEvent.metaKey, null);
284       aEvent.currentTarget.dispatchEvent(cmdEvent);
285     }
286   }
288   function stopTimer(aEvent) {
289     if (timer) {
290       clearTimeout(timer);
291       timer = null;
292     }
293   }
295   function _addClickAndHoldListenersOnElement(aElm) {
296     aElm.addEventListener("mousedown", mousedownHandler, true);
297     aElm.addEventListener("mouseup", stopTimer, false);
298     aElm.addEventListener("mouseout", stopTimer, false);
299     aElm.addEventListener("click", clickHandler, true);
300   }
302   // Bug 414797: Clone the dropmarker's menu into both the back and
303   // the forward buttons.
304   var unifiedButton = document.getElementById("unified-back-forward-button");
305   if (unifiedButton && !unifiedButton._clickHandlersAttached) {
306     var popup = document.getElementById("back-forward-dropmarker")
307                         .firstChild.cloneNode(true);
308     var backButton = document.getElementById("back-button");
309     backButton.setAttribute("type", "menu");
310     backButton.appendChild(popup);
311     _addClickAndHoldListenersOnElement(backButton);
312     var forwardButton = document.getElementById("forward-button");
313     popup = popup.cloneNode(true);
314     forwardButton.setAttribute("type", "menu");
315     forwardButton.appendChild(popup);
316     _addClickAndHoldListenersOnElement(forwardButton);
317     unifiedButton._clickHandlersAttached = true;
318   }
320 #endif
322 function BookmarkThisTab(aTab) {
323   PlacesCommandHook.bookmarkPage(aTab.linkedBrowser,
324                                  PlacesUtils.bookmarksMenuFolderId, true);
327 const gSessionHistoryObserver = {
328   observe: function(subject, topic, data)
329   {
330     if (topic != "browser:purge-session-history")
331       return;
333     var backCommand = document.getElementById("Browser:Back");
334     backCommand.setAttribute("disabled", "true");
335     var fwdCommand = document.getElementById("Browser:Forward");
336     fwdCommand.setAttribute("disabled", "true");
338     if (gURLBar) {
339       // Clear undo history of the URL bar
340       gURLBar.editor.transactionManager.clear()
341     }
342   }
346  * Given a starting docshell and a URI to look up, find the docshell the URI
347  * is loaded in.
348  * @param   aDocument
349  *          A document to find instead of using just a URI - this is more specific.
350  * @param   aDocShell
351  *          The doc shell to start at
352  * @param   aSoughtURI
353  *          The URI that we're looking for
354  * @returns The doc shell that the sought URI is loaded in. Can be in
355  *          subframes.
356  */
357 function findChildShell(aDocument, aDocShell, aSoughtURI) {
358   aDocShell.QueryInterface(Components.interfaces.nsIWebNavigation);
359   aDocShell.QueryInterface(Components.interfaces.nsIInterfaceRequestor);
360   var doc = aDocShell.getInterface(Components.interfaces.nsIDOMDocument);
361   if ((aDocument && doc == aDocument) ||
362       (aSoughtURI && aSoughtURI.spec == aDocShell.currentURI.spec))
363     return aDocShell;
365   var node = aDocShell.QueryInterface(Components.interfaces.nsIDocShellTreeNode);
366   for (var i = 0; i < node.childCount; ++i) {
367     var docShell = node.getChildAt(i);
368     docShell = findChildShell(aDocument, docShell, aSoughtURI);
369     if (docShell)
370       return docShell;
371   }
372   return null;
375 const gPopupBlockerObserver = {
376   _reportButton: null,
378   onUpdatePageReport: function (aEvent)
379   {
380     if (aEvent.originalTarget != gBrowser.selectedBrowser)
381       return;
383     if (!this._reportButton)
384       this._reportButton = document.getElementById("page-report-button");
386     if (!gBrowser.pageReport) {
387       // Hide the popup blocker statusbar button
388       this._reportButton.hidden = true;
390       return;
391     }
393     this._reportButton.hidden = false;
395     // Only show the notification again if we've not already shown it. Since
396     // notifications are per-browser, we don't need to worry about re-adding
397     // it.
398     if (!gBrowser.pageReport.reported) {
399       if (gPrefService.getBoolPref("privacy.popups.showBrowserMessage")) {
400         var brandBundle = document.getElementById("bundle_brand");
401         var brandShortName = brandBundle.getString("brandShortName");
402         var message;
403         var popupCount = gBrowser.pageReport.length;
404 #ifdef XP_WIN
405         var popupButtonText = gNavigatorBundle.getString("popupWarningButton");
406         var popupButtonAccesskey = gNavigatorBundle.getString("popupWarningButton.accesskey");
407 #else
408         var popupButtonText = gNavigatorBundle.getString("popupWarningButtonUnix");
409         var popupButtonAccesskey = gNavigatorBundle.getString("popupWarningButtonUnix.accesskey");
410 #endif
411         if (popupCount > 1)
412           message = gNavigatorBundle.getFormattedString("popupWarningMultiple", [brandShortName, popupCount]);
413         else
414           message = gNavigatorBundle.getFormattedString("popupWarning", [brandShortName]);
416         var notificationBox = gBrowser.getNotificationBox();
417         var notification = notificationBox.getNotificationWithValue("popup-blocked");
418         if (notification) {
419           notification.label = message;
420         }
421         else {
422           var buttons = [{
423             label: popupButtonText,
424             accessKey: popupButtonAccesskey,
425             popup: "blockedPopupOptions",
426             callback: null
427           }];
429           const priority = notificationBox.PRIORITY_WARNING_MEDIUM;
430           notificationBox.appendNotification(message, "popup-blocked",
431                                              "chrome://browser/skin/Info.png",
432                                              priority, buttons);
433         }
434       }
436       // Record the fact that we've reported this blocked popup, so we don't
437       // show it again.
438       gBrowser.pageReport.reported = true;
439     }
440   },
442   toggleAllowPopupsForSite: function (aEvent)
443   {
444     var pm = Services.perms;
445     var shouldBlock = aEvent.target.getAttribute("block") == "true";
446     var perm = shouldBlock ? pm.DENY_ACTION : pm.ALLOW_ACTION;
447     pm.add(gBrowser.currentURI, "popup", perm);
449     gBrowser.getNotificationBox().removeCurrentNotification();
450   },
452   fillPopupList: function (aEvent)
453   {
454     // XXXben - rather than using |currentURI| here, which breaks down on multi-framed sites
455     //          we should really walk the pageReport and create a list of "allow for <host>"
456     //          menuitems for the common subset of hosts present in the report, this will
457     //          make us frame-safe.
458     //
459     // XXXjst - Note that when this is fixed to work with multi-framed sites,
460     //          also back out the fix for bug 343772 where
461     //          nsGlobalWindow::CheckOpenAllow() was changed to also
462     //          check if the top window's location is whitelisted.
463     var uri = gBrowser.currentURI;
464     var blockedPopupAllowSite = document.getElementById("blockedPopupAllowSite");
465     try {
466       blockedPopupAllowSite.removeAttribute("hidden");
468       var pm = Services.perms;
469       if (pm.testPermission(uri, "popup") == pm.ALLOW_ACTION) {
470         // Offer an item to block popups for this site, if a whitelist entry exists
471         // already for it.
472         let blockString = gNavigatorBundle.getFormattedString("popupBlock", [uri.host]);
473         blockedPopupAllowSite.setAttribute("label", blockString);
474         blockedPopupAllowSite.setAttribute("block", "true");
475       }
476       else {
477         // Offer an item to allow popups for this site
478         let allowString = gNavigatorBundle.getFormattedString("popupAllow", [uri.host]);
479         blockedPopupAllowSite.setAttribute("label", allowString);
480         blockedPopupAllowSite.removeAttribute("block");
481       }
482     }
483     catch (e) {
484       blockedPopupAllowSite.setAttribute("hidden", "true");
485     }
487     if (gPrivateBrowsingUI.privateBrowsingEnabled)
488       blockedPopupAllowSite.setAttribute("disabled", "true");
489     else
490       blockedPopupAllowSite.removeAttribute("disabled");
492     var item = aEvent.target.lastChild;
493     while (item && item.getAttribute("observes") != "blockedPopupsSeparator") {
494       var next = item.previousSibling;
495       item.parentNode.removeChild(item);
496       item = next;
497     }
499     var foundUsablePopupURI = false;
500     var pageReport = gBrowser.pageReport;
501     if (pageReport) {
502       for (var i = 0; i < pageReport.length; ++i) {
503         var popupURIspec = pageReport[i].popupWindowURI.spec;
505         // Sometimes the popup URI that we get back from the pageReport
506         // isn't useful (for instance, netscape.com's popup URI ends up
507         // being "http://www.netscape.com", which isn't really the URI of
508         // the popup they're trying to show).  This isn't going to be
509         // useful to the user, so we won't create a menu item for it.
510         if (popupURIspec == "" || popupURIspec == "about:blank" ||
511             popupURIspec == uri.spec)
512           continue;
514         // Because of the short-circuit above, we may end up in a situation
515         // in which we don't have any usable popup addresses to show in
516         // the menu, and therefore we shouldn't show the separator.  However,
517         // since we got past the short-circuit, we must've found at least
518         // one usable popup URI and thus we'll turn on the separator later.
519         foundUsablePopupURI = true;
521         var menuitem = document.createElement("menuitem");
522         var label = gNavigatorBundle.getFormattedString("popupShowPopupPrefix",
523                                                         [popupURIspec]);
524         menuitem.setAttribute("label", label);
525         menuitem.setAttribute("popupWindowURI", popupURIspec);
526         menuitem.setAttribute("popupWindowFeatures", pageReport[i].popupWindowFeatures);
527         menuitem.setAttribute("popupWindowName", pageReport[i].popupWindowName);
528         menuitem.setAttribute("oncommand", "gPopupBlockerObserver.showBlockedPopup(event);");
529         menuitem.requestingWindow = pageReport[i].requestingWindow;
530         menuitem.requestingDocument = pageReport[i].requestingDocument;
531         aEvent.target.appendChild(menuitem);
532       }
533     }
535     // Show or hide the separator, depending on whether we added any
536     // showable popup addresses to the menu.
537     var blockedPopupsSeparator =
538       document.getElementById("blockedPopupsSeparator");
539     if (foundUsablePopupURI)
540       blockedPopupsSeparator.removeAttribute("hidden");
541     else
542       blockedPopupsSeparator.setAttribute("hidden", true);
544     var blockedPopupDontShowMessage = document.getElementById("blockedPopupDontShowMessage");
545     var showMessage = gPrefService.getBoolPref("privacy.popups.showBrowserMessage");
546     blockedPopupDontShowMessage.setAttribute("checked", !showMessage);
547     if (aEvent.target.localName == "popup")
548       blockedPopupDontShowMessage.setAttribute("label", gNavigatorBundle.getString("popupWarningDontShowFromMessage"));
549     else
550       blockedPopupDontShowMessage.setAttribute("label", gNavigatorBundle.getString("popupWarningDontShowFromStatusbar"));
551   },
553   showBlockedPopup: function (aEvent)
554   {
555     var target = aEvent.target;
556     var popupWindowURI = target.getAttribute("popupWindowURI");
557     var features = target.getAttribute("popupWindowFeatures");
558     var name = target.getAttribute("popupWindowName");
560     var dwi = target.requestingWindow;
562     // If we have a requesting window and the requesting document is
563     // still the current document, open the popup.
564     if (dwi && dwi.document == target.requestingDocument) {
565       dwi.open(popupWindowURI, name, features);
566     }
567   },
569   editPopupSettings: function ()
570   {
571     var host = "";
572     try {
573       host = gBrowser.currentURI.host;
574     }
575     catch (e) { }
577     var bundlePreferences = document.getElementById("bundle_preferences");
578     var params = { blockVisible   : false,
579                    sessionVisible : false,
580                    allowVisible   : true,
581                    prefilledHost  : host,
582                    permissionType : "popup",
583                    windowTitle    : bundlePreferences.getString("popuppermissionstitle"),
584                    introText      : bundlePreferences.getString("popuppermissionstext") };
585     var existingWindow = Services.wm.getMostRecentWindow("Browser:Permissions");
586     if (existingWindow) {
587       existingWindow.initWithParams(params);
588       existingWindow.focus();
589     }
590     else
591       window.openDialog("chrome://browser/content/preferences/permissions.xul",
592                         "_blank", "resizable,dialog=no,centerscreen", params);
593   },
595   dontShowMessage: function ()
596   {
597     var showMessage = gPrefService.getBoolPref("privacy.popups.showBrowserMessage");
598     var firstTime = gPrefService.getBoolPref("privacy.popups.firstTime");
600     // If the info message is showing at the top of the window, and the user has never
601     // hidden the message before, show an info box telling the user where the info
602     // will be displayed.
603     if (showMessage && firstTime)
604       this._displayPageReportFirstTime();
606     gPrefService.setBoolPref("privacy.popups.showBrowserMessage", !showMessage);
608     gBrowser.getNotificationBox().removeCurrentNotification();
609   },
611   _displayPageReportFirstTime: function ()
612   {
613     window.openDialog("chrome://browser/content/pageReportFirstTime.xul", "_blank",
614                       "dependent");
615   }
618 const gXPInstallObserver = {
619   _findChildShell: function (aDocShell, aSoughtShell)
620   {
621     if (aDocShell == aSoughtShell)
622       return aDocShell;
624     var node = aDocShell.QueryInterface(Components.interfaces.nsIDocShellTreeNode);
625     for (var i = 0; i < node.childCount; ++i) {
626       var docShell = node.getChildAt(i);
627       docShell = this._findChildShell(docShell, aSoughtShell);
628       if (docShell == aSoughtShell)
629         return docShell;
630     }
631     return null;
632   },
634   _getBrowser: function (aDocShell)
635   {
636     for (var i = 0; i < gBrowser.browsers.length; ++i) {
637       var browser = gBrowser.getBrowserAtIndex(i);
638       if (this._findChildShell(browser.docShell, aDocShell))
639         return browser;
640     }
641     return null;
642   },
644   observe: function (aSubject, aTopic, aData)
645   {
646     var brandBundle = document.getElementById("bundle_brand");
647     var installInfo = aSubject.QueryInterface(Components.interfaces.amIWebInstallInfo);
648     var win = installInfo.originatingWindow;
649     var shell = win.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
650                    .getInterface(Components.interfaces.nsIWebNavigation)
651                    .QueryInterface(Components.interfaces.nsIDocShell);
652     var browser = this._getBrowser(shell);
653     if (!browser)
654       return;
655     const anchorID = "addons-notification-icon";
656     var messageString, action;
657     var brandShortName = brandBundle.getString("brandShortName");
659     var notificationID = aTopic;
660     // Make notifications persist a minimum of 30 seconds
661     var options = {
662       timeout: Date.now() + 30000
663     };
665     switch (aTopic) {
666     case "addon-install-blocked":
667       var enabled = true;
668       try {
669         enabled = gPrefService.getBoolPref("xpinstall.enabled");
670       }
671       catch (e) {
672       }
674       if (!enabled) {
675         notificationID = "xpinstall-disabled"
676         if (PopupNotifications.getNotification(notificationID, browser))
677           return;
679         if (gPrefService.prefIsLocked("xpinstall.enabled")) {
680           messageString = gNavigatorBundle.getString("xpinstallDisabledMessageLocked");
681           buttons = [];
682         }
683         else {
684           messageString = gNavigatorBundle.getString("xpinstallDisabledMessage");
686           action = {
687             label: gNavigatorBundle.getString("xpinstallDisabledButton"),
688             accessKey: gNavigatorBundle.getString("xpinstallDisabledButton.accesskey"),
689             callback: function editPrefs() {
690               gPrefService.setBoolPref("xpinstall.enabled", true);
691             }
692           };
693         }
694       }
695       else {
696         if (PopupNotifications.getNotification(notificationID, browser))
697           return;
699         messageString = gNavigatorBundle.getFormattedString("xpinstallPromptWarning",
700                           [brandShortName, installInfo.originatingURI.host]);
702         action = {
703           label: gNavigatorBundle.getString("xpinstallPromptAllowButton"),
704           accessKey: gNavigatorBundle.getString("xpinstallPromptAllowButton.accesskey"),
705           callback: function() {
706             installInfo.install();
707           }
708         };
709       }
711       PopupNotifications.show(browser, notificationID, messageString, anchorID,
712                               action, null, options);
713       break;
714     case "addon-install-failed":
715       // TODO This isn't terribly ideal for the multiple failure case
716       installInfo.installs.forEach(function(aInstall) {
717         var host = (installInfo.originatingURI instanceof Ci.nsIStandardURL) &&
718                    installInfo.originatingURI.host;
719         if (!host)
720           host = (aInstall.sourceURI instanceof Ci.nsIStandardURL) &&
721                  aInstall.sourceURI.host;
723         var error = (host || aInstall.error == 0) ? "addonError" : "addonLocalError";
724         if (aInstall.error != 0)
725           error += aInstall.error;
726         else if (aInstall.addon.blocklistState == Ci.nsIBlocklistService.STATE_BLOCKED)
727           error += "Blocklisted";
728         else
729           error += "Incompatible";
731         messageString = gNavigatorBundle.getString(error);
732         messageString = messageString.replace("#1", aInstall.name);
733         if (host)
734           messageString = messageString.replace("#2", host);
735         messageString = messageString.replace("#3", brandShortName);
736         messageString = messageString.replace("#4", Services.appinfo.version);
738         PopupNotifications.show(browser, notificationID, messageString, anchorID,
739                                 action, null, options);
740       });
741       break;
742     case "addon-install-complete":
743       var notification = PopupNotifications.getNotification(notificationID, browser);
744       if (notification)
745         PopupNotifications.remove(notification);
747       var needsRestart = installInfo.installs.some(function(i) {
748         return (i.addon.pendingOperations & AddonManager.PENDING_INSTALL) != 0;
749       });
751       if (needsRestart) {
752         messageString = gNavigatorBundle.getString("addonsInstalledNeedsRestart");
753         action = {
754           label: gNavigatorBundle.getString("addonInstallRestartButton"),
755           accessKey: gNavigatorBundle.getString("addonInstallRestartButton.accesskey"),
756           callback: function() {
757             Application.restart();
758           }
759         };
760       }
761       else {
762         messageString = gNavigatorBundle.getString("addonsInstalled");
763         action = {
764           label: gNavigatorBundle.getString("addonInstallManage"),
765           accessKey: gNavigatorBundle.getString("addonInstallManage.accesskey"),
766           callback: function() {
767             // Calculate the add-on type that is most popular in the list of
768             // installs
769             var types = {};
770             var bestType = null;
771             installInfo.installs.forEach(function(aInstall) {
772               if (aInstall.type in types)
773                 types[aInstall.type]++;
774               else
775                 types[aInstall.type] = 1;
776               if (!bestType || types[aInstall.type] > types[bestType])
777                 bestType = aInstall.type;
778             });
780             BrowserOpenAddonsMgr("addons://list/" + bestType);
781           }
782         };
783       }
785       messageString = PluralForm.get(installInfo.installs.length, messageString);
786       messageString = messageString.replace("#1", installInfo.installs[0].name);
787       messageString = messageString.replace("#2", installInfo.installs.length);
788       messageString = messageString.replace("#3", brandShortName);
790       PopupNotifications.show(browser, notificationID, messageString, anchorID,
791                               action, null, options);
792       break;
793     }
794   }
797 // Simple gestures support
799 // As per bug #412486, web content must not be allowed to receive any
800 // simple gesture events.  Multi-touch gesture APIs are in their
801 // infancy and we do NOT want to be forced into supporting an API that
802 // will probably have to change in the future.  (The current Mac OS X
803 // API is undocumented and was reverse-engineered.)  Until support is
804 // implemented in the event dispatcher to keep these events as
805 // chrome-only, we must listen for the simple gesture events during
806 // the capturing phase and call stopPropagation on every event.
808 let gGestureSupport = {
809   /**
810    * Add or remove mouse gesture event listeners
811    *
812    * @param aAddListener
813    *        True to add/init listeners and false to remove/uninit
814    */
815   init: function GS_init(aAddListener) {
816     const gestureEvents = ["SwipeGesture",
817       "MagnifyGestureStart", "MagnifyGestureUpdate", "MagnifyGesture",
818       "RotateGestureStart", "RotateGestureUpdate", "RotateGesture",
819       "TapGesture", "PressTapGesture"];
821     let addRemove = aAddListener ? window.addEventListener :
822       window.removeEventListener;
824     gestureEvents.forEach(function (event) addRemove("Moz" + event, this, true),
825                           this);
826   },
828   /**
829    * Dispatch events based on the type of mouse gesture event. For now, make
830    * sure to stop propagation of every gesture event so that web content cannot
831    * receive gesture events.
832    *
833    * @param aEvent
834    *        The gesture event to handle
835    */
836   handleEvent: function GS_handleEvent(aEvent) {
837     aEvent.stopPropagation();
839     // Create a preference object with some defaults
840     let def = function(aThreshold, aLatched)
841       ({ threshold: aThreshold, latched: !!aLatched });
843     switch (aEvent.type) {
844       case "MozSwipeGesture":
845         aEvent.preventDefault();
846         return this.onSwipe(aEvent);
847       case "MozMagnifyGestureStart":
848         aEvent.preventDefault();
849 #ifdef XP_WIN
850         return this._setupGesture(aEvent, "pinch", def(25, 0), "out", "in");
851 #else
852         return this._setupGesture(aEvent, "pinch", def(150, 1), "out", "in");
853 #endif
854       case "MozRotateGestureStart":
855         aEvent.preventDefault();
856         return this._setupGesture(aEvent, "twist", def(25, 0), "right", "left");
857       case "MozMagnifyGestureUpdate":
858       case "MozRotateGestureUpdate":
859         aEvent.preventDefault();
860         return this._doUpdate(aEvent);
861       case "MozTapGesture":
862         aEvent.preventDefault();
863         return this._doAction(aEvent, ["tap"]);
864       case "MozPressTapGesture":
865       // Fall through to default behavior
866       return;
867     }
868   },
870   /**
871    * Called at the start of "pinch" and "twist" gestures to setup all of the
872    * information needed to process the gesture
873    *
874    * @param aEvent
875    *        The continual motion start event to handle
876    * @param aGesture
877    *        Name of the gesture to handle
878    * @param aPref
879    *        Preference object with the names of preferences and defaults
880    * @param aInc
881    *        Command to trigger for increasing motion (without gesture name)
882    * @param aDec
883    *        Command to trigger for decreasing motion (without gesture name)
884    */
885   _setupGesture: function GS__setupGesture(aEvent, aGesture, aPref, aInc, aDec) {
886     // Try to load user-set values from preferences
887     for (let [pref, def] in Iterator(aPref))
888       aPref[pref] = this._getPref(aGesture + "." + pref, def);
890     // Keep track of the total deltas and latching behavior
891     let offset = 0;
892     let latchDir = aEvent.delta > 0 ? 1 : -1;
893     let isLatched = false;
895     // Create the update function here to capture closure state
896     this._doUpdate = function GS__doUpdate(aEvent) {
897       // Update the offset with new event data
898       offset += aEvent.delta;
900       // Check if the cumulative deltas exceed the threshold
901       if (Math.abs(offset) > aPref["threshold"]) {
902         // Trigger the action if we don't care about latching; otherwise, make
903         // sure either we're not latched and going the same direction of the
904         // initial motion; or we're latched and going the opposite way
905         let sameDir = (latchDir ^ offset) >= 0;
906         if (!aPref["latched"] || (isLatched ^ sameDir)) {
907           this._doAction(aEvent, [aGesture, offset > 0 ? aInc : aDec]);
909           // We must be getting latched or leaving it, so just toggle
910           isLatched = !isLatched;
911         }
913         // Reset motion counter to prepare for more of the same gesture
914         offset = 0;
915       }
916     };
918     // The start event also contains deltas, so handle an update right away
919     this._doUpdate(aEvent);
920   },
922   /**
923    * Generator producing the powerset of the input array where the first result
924    * is the complete set and the last result (before StopIteration) is empty.
925    *
926    * @param aArray
927    *        Source array containing any number of elements
928    * @yield Array that is a subset of the input array from full set to empty
929    */
930   _power: function GS__power(aArray) {
931     // Create a bitmask based on the length of the array
932     let num = 1 << aArray.length;
933     while (--num >= 0) {
934       // Only select array elements where the current bit is set
935       yield aArray.reduce(function (aPrev, aCurr, aIndex) {
936         if (num & 1 << aIndex)
937           aPrev.push(aCurr);
938         return aPrev;
939       }, []);
940     }
941   },
943   /**
944    * Determine what action to do for the gesture based on which keys are
945    * pressed and which commands are set
946    *
947    * @param aEvent
948    *        The original gesture event to convert into a fake click event
949    * @param aGesture
950    *        Array of gesture name parts (to be joined by periods)
951    * @return Name of the command found for the event's keys and gesture. If no
952    *         command is found, no value is returned (undefined).
953    */
954   _doAction: function GS__doAction(aEvent, aGesture) {
955     // Create an array of pressed keys in a fixed order so that a command for
956     // "meta" is preferred over "ctrl" when both buttons are pressed (and a
957     // command for both don't exist)
958     let keyCombos = [];
959     ["shift", "alt", "ctrl", "meta"].forEach(function (key) {
960       if (aEvent[key + "Key"])
961         keyCombos.push(key);
962     });
964     // Try each combination of key presses in decreasing order for commands
965     for each (let subCombo in this._power(keyCombos)) {
966       // Convert a gesture and pressed keys into the corresponding command
967       // action where the preference has the gesture before "shift" before
968       // "alt" before "ctrl" before "meta" all separated by periods
969       let command;
970       try {
971         command = this._getPref(aGesture.concat(subCombo).join("."));
972       } catch (e) {}
974       if (!command)
975         continue;
977       let node = document.getElementById(command);
978       if (node) {
979         if (node.getAttribute("disabled") != "true") {
980           let cmdEvent = document.createEvent("xulcommandevent");
981           cmdEvent.initCommandEvent("command", true, true, window, 0,
982                                     aEvent.ctrlKey, aEvent.altKey, aEvent.shiftKey,
983                                     aEvent.metaKey, null);
984           node.dispatchEvent(cmdEvent);
985         }
986       } else {
987         goDoCommand(command);
988       }
990       return command;
991     }
992     return null;
993   },
995   /**
996    * Convert continual motion events into an action if it exceeds a threshold
997    * in a given direction. This function will be set by _setupGesture to
998    * capture state that needs to be shared across multiple gesture updates.
999    *
1000    * @param aEvent
1001    *        The continual motion update event to handle
1002    */
1003   _doUpdate: function(aEvent) {},
1005   /**
1006    * Convert the swipe gesture into a browser action based on the direction
1007    *
1008    * @param aEvent
1009    *        The swipe event to handle
1010    */
1011   onSwipe: function GS_onSwipe(aEvent) {
1012     // Figure out which one (and only one) direction was triggered
1013     ["UP", "RIGHT", "DOWN", "LEFT"].forEach(function (dir) {
1014       if (aEvent.direction == aEvent["DIRECTION_" + dir])
1015         return this._doAction(aEvent, ["swipe", dir.toLowerCase()]);
1016     }, this);
1017   },
1019   /**
1020    * Get a gesture preference or use a default if it doesn't exist
1021    *
1022    * @param aPref
1023    *        Name of the preference to load under the gesture branch
1024    * @param aDef
1025    *        Default value if the preference doesn't exist
1026    */
1027   _getPref: function GS__getPref(aPref, aDef) {
1028     // Preferences branch under which all gestures preferences are stored
1029     const branch = "browser.gesture.";
1031     try {
1032       // Determine what type of data to load based on default value's type
1033       let type = typeof aDef;
1034       let getFunc = "get" + (type == "boolean" ? "Bool" :
1035                              type == "number" ? "Int" : "Char") + "Pref";
1036       return gPrefService[getFunc](branch + aPref);
1037     }
1038     catch (e) {
1039       return aDef;
1040     }
1041   },
1044 function BrowserStartup() {
1045   var uriToLoad = null;
1047   // window.arguments[0]: URI to load (string), or an nsISupportsArray of
1048   //                      nsISupportsStrings to load, or a xul:tab of
1049   //                      a tabbrowser, which will be replaced by this
1050   //                      window (for this case, all other arguments are
1051   //                      ignored).
1052   //                 [1]: character set (string)
1053   //                 [2]: referrer (nsIURI)
1054   //                 [3]: postData (nsIInputStream)
1055   //                 [4]: allowThirdPartyFixup (bool)
1056   if ("arguments" in window && window.arguments[0])
1057     uriToLoad = window.arguments[0];
1059   var isLoadingBlank = uriToLoad == "about:blank";
1060   var mustLoadSidebar = false;
1062   prepareForStartup();
1064   if (uriToLoad && !isLoadingBlank) {
1065     if (uriToLoad instanceof Ci.nsISupportsArray) {
1066       let count = uriToLoad.Count();
1067       let specs = [];
1068       for (let i = 0; i < count; i++) {
1069         let urisstring = uriToLoad.GetElementAt(i).QueryInterface(Ci.nsISupportsString);
1070         specs.push(urisstring.data);
1071       }
1073       // This function throws for certain malformed URIs, so use exception handling
1074       // so that we don't disrupt startup
1075       try {
1076         gBrowser.loadTabs(specs, false, true);
1077       } catch (e) {}
1078     }
1079     else if (uriToLoad instanceof XULElement) {
1080       // swap the given tab with the default about:blank tab and then close
1081       // the original tab in the other window.
1083       // Stop the about:blank load
1084       gBrowser.stop();
1085       // make sure it has a docshell
1086       gBrowser.docShell;
1088       gBrowser.swapBrowsersAndCloseOther(gBrowser.selectedTab, uriToLoad);
1089     }
1090     else if (window.arguments.length >= 3) {
1091       loadURI(uriToLoad, window.arguments[2], window.arguments[3] || null,
1092               window.arguments[4] || false);
1093       content.focus();
1094     }
1095     // Note: loadOneOrMoreURIs *must not* be called if window.arguments.length >= 3.
1096     // Such callers expect that window.arguments[0] is handled as a single URI.
1097     else
1098       loadOneOrMoreURIs(uriToLoad);
1099   }
1101   if (window.opener && !window.opener.closed) {
1102     let openerSidebarBox = window.opener.document.getElementById("sidebar-box");
1103     // If the opener had a sidebar, open the same sidebar in our window.
1104     // The opener can be the hidden window too, if we're coming from the state
1105     // where no windows are open, and the hidden window has no sidebar box.
1106     if (openerSidebarBox && !openerSidebarBox.hidden) {
1107       let sidebarCmd = openerSidebarBox.getAttribute("sidebarcommand");
1108       let sidebarCmdElem = document.getElementById(sidebarCmd);
1110       // dynamically generated sidebars will fail this check.
1111       if (sidebarCmdElem) {
1112         let sidebarBox = document.getElementById("sidebar-box");
1113         let sidebarTitle = document.getElementById("sidebar-title");
1115         sidebarTitle.setAttribute(
1116           "value", window.opener.document.getElementById("sidebar-title").getAttribute("value"));
1117         sidebarBox.setAttribute("width", openerSidebarBox.boxObject.width);
1119         sidebarBox.setAttribute("sidebarcommand", sidebarCmd);
1120         // Note: we're setting 'src' on sidebarBox, which is a <vbox>, not on
1121         // the <browser id="sidebar">. This lets us delay the actual load until
1122         // delayedStartup().
1123         sidebarBox.setAttribute(
1124           "src", window.opener.document.getElementById("sidebar").getAttribute("src"));
1125         mustLoadSidebar = true;
1127         sidebarBox.hidden = false;
1128         document.getElementById("sidebar-splitter").hidden = false;
1129         sidebarCmdElem.setAttribute("checked", "true");
1130       }
1131     }
1132   }
1133   else {
1134     let box = document.getElementById("sidebar-box");
1135     if (box.hasAttribute("sidebarcommand")) {
1136       let commandID = box.getAttribute("sidebarcommand");
1137       if (commandID) {
1138         let command = document.getElementById(commandID);
1139         if (command) {
1140           mustLoadSidebar = true;
1141           box.hidden = false;
1142           document.getElementById("sidebar-splitter").hidden = false;
1143           command.setAttribute("checked", "true");
1144         }
1145         else {
1146           // Remove the |sidebarcommand| attribute, because the element it
1147           // refers to no longer exists, so we should assume this sidebar
1148           // panel has been uninstalled. (249883)
1149           box.removeAttribute("sidebarcommand");
1150         }
1151       }
1152     }
1153   }
1155   // Certain kinds of automigration rely on this notification to complete their
1156   // tasks BEFORE the browser window is shown.
1157   Services.obs.notifyObservers(null, "browser-window-before-show", "");
1159   // Set a sane starting width/height for all resolutions on new profiles.
1160   if (!document.documentElement.hasAttribute("width")) {
1161     let defaultWidth = 994;
1162     let defaultHeight;
1163     if (screen.availHeight <= 600) {
1164       document.documentElement.setAttribute("sizemode", "maximized");
1165       defaultWidth = 610;
1166       defaultHeight = 450;
1167     }
1168     else {
1169       // Create a narrower window for large or wide-aspect displays, to suggest
1170       // side-by-side page view.
1171       if (screen.availWidth >= 1600)
1172         defaultWidth = (screen.availWidth / 2) - 20;
1173       defaultHeight = screen.availHeight - 10;
1174 #ifdef MOZ_WIDGET_GTK2
1175       // On X, we're not currently able to account for the size of the window
1176       // border.  Use 28px as a guess (titlebar + bottom window border)
1177       defaultHeight -= 28;
1178 #endif
1179     }
1180     document.documentElement.setAttribute("width", defaultWidth);
1181     document.documentElement.setAttribute("height", defaultHeight);
1182   }
1184   if (!window.toolbar.visible) {
1185     // adjust browser UI for popups
1186     if (gURLBar) {
1187       gURLBar.setAttribute("readonly", "true");
1188       gURLBar.setAttribute("enablehistory", "false");
1189     }
1190     goSetCommandEnabled("Browser:OpenLocation", false);
1191     goSetCommandEnabled("cmd_newNavigatorTab", false);
1192   }
1194 #ifdef MENUBAR_CAN_AUTOHIDE
1195   updateAppButtonDisplay();
1196 #endif
1198   CombinedStopReload.init();
1200   allTabs.readPref();
1202   TabsOnTop.syncCommand();
1204   BookmarksMenuButton.init();
1206   setTimeout(delayedStartup, 0, isLoadingBlank, mustLoadSidebar);
1209 function HandleAppCommandEvent(evt) {
1210   evt.stopPropagation();
1211   switch (evt.command) {
1212   case "Back":
1213     BrowserBack();
1214     break;
1215   case "Forward":
1216     BrowserForward();
1217     break;
1218   case "Reload":
1219     BrowserReloadSkipCache();
1220     break;
1221   case "Stop":
1222     BrowserStop();
1223     break;
1224   case "Search":
1225     BrowserSearch.webSearch();
1226     break;
1227   case "Bookmarks":
1228     toggleSidebar('viewBookmarksSidebar');
1229     break;
1230   case "Home":
1231     BrowserHome();
1232     break;
1233   default:
1234     break;
1235   }
1238 function prepareForStartup() {
1239   gBrowser.addEventListener("DOMUpdatePageReport", gPopupBlockerObserver.onUpdatePageReport, false);
1241   gBrowser.addEventListener("PluginNotFound",     gPluginHandler, true);
1242   gBrowser.addEventListener("PluginCrashed",      gPluginHandler, true);
1243   gBrowser.addEventListener("PluginBlocklisted",  gPluginHandler, true);
1244   gBrowser.addEventListener("PluginOutdated",     gPluginHandler, true);
1245   gBrowser.addEventListener("PluginDisabled",     gPluginHandler, true);
1246   gBrowser.addEventListener("NewPluginInstalled", gPluginHandler.newPluginInstalled, true);
1248   Services.obs.addObserver(gPluginHandler.pluginCrashed, "plugin-crashed", false);
1250   window.addEventListener("AppCommand", HandleAppCommandEvent, true);
1252   var webNavigation;
1253   try {
1254     webNavigation = getWebNavigation();
1255     if (!webNavigation)
1256       throw "no XBL binding for browser";
1257   } catch (e) {
1258     alert("Error launching browser window:" + e);
1259     window.close(); // Give up.
1260     return;
1261   }
1263   // initialize observers and listeners
1264   // and give C++ access to gBrowser
1265   XULBrowserWindow.init();
1266   window.QueryInterface(Ci.nsIInterfaceRequestor)
1267         .getInterface(nsIWebNavigation)
1268         .QueryInterface(Ci.nsIDocShellTreeItem).treeOwner
1269         .QueryInterface(Ci.nsIInterfaceRequestor)
1270         .getInterface(Ci.nsIXULWindow)
1271         .XULBrowserWindow = window.XULBrowserWindow;
1272   window.QueryInterface(Ci.nsIDOMChromeWindow).browserDOMWindow =
1273     new nsBrowserAccess();
1275   // set default character set if provided
1276   if ("arguments" in window && window.arguments.length > 1 && window.arguments[1]) {
1277     if (window.arguments[1].indexOf("charset=") != -1) {
1278       var arrayArgComponents = window.arguments[1].split("=");
1279       if (arrayArgComponents) {
1280         //we should "inherit" the charset menu setting in a new window
1281         getMarkupDocumentViewer().defaultCharacterSet = arrayArgComponents[1];
1282       }
1283     }
1284   }
1286   // Manually hook up session and global history for the first browser
1287   // so that we don't have to load global history before bringing up a
1288   // window.
1289   // Wire up session and global history before any possible
1290   // progress notifications for back/forward button updating
1291   webNavigation.sessionHistory = Components.classes["@mozilla.org/browser/shistory;1"]
1292                                            .createInstance(Components.interfaces.nsISHistory);
1293   Services.obs.addObserver(gBrowser.browsers[0], "browser:purge-session-history", false);
1295   // remove the disablehistory attribute so the browser cleans up, as
1296   // though it had done this work itself
1297   gBrowser.browsers[0].removeAttribute("disablehistory");
1299   // enable global history
1300   try {
1301     gBrowser.docShell.QueryInterface(Components.interfaces.nsIDocShellHistory).useGlobalHistory = true;
1302   } catch(ex) {
1303     Components.utils.reportError("Places database may be locked: " + ex);
1304   }
1306   // hook up UI through progress listener
1307   gBrowser.addProgressListener(window.XULBrowserWindow, Components.interfaces.nsIWebProgress.NOTIFY_ALL);
1308   gBrowser.addTabsProgressListener(window.TabsProgressListener);
1310   // setup our common DOMLinkAdded listener
1311   gBrowser.addEventListener("DOMLinkAdded", DOMLinkHandler, false);
1313   // setup our MozApplicationManifest listener
1314   gBrowser.addEventListener("MozApplicationManifest",
1315                             OfflineApps, false);
1317   // setup simple gestures support
1318   gGestureSupport.init(true);
1320 #ifdef MENUBAR_CAN_AUTOHIDE
1321   // update the visibility of the titlebar buttons after the window is
1322   // displayed. (required by theme code.)
1323   window.addEventListener("MozAfterPaint", function () {
1324     window.removeEventListener("MozAfterPaint", arguments.callee, false);
1325     document.getElementById("titlebar-buttonbox").collapsed = false;
1326   }, false);
1327 #endif
1330 function delayedStartup(isLoadingBlank, mustLoadSidebar) {
1331   Services.obs.addObserver(gSessionHistoryObserver, "browser:purge-session-history", false);
1332   Services.obs.addObserver(gXPInstallObserver, "addon-install-blocked", false);
1333   Services.obs.addObserver(gXPInstallObserver, "addon-install-failed", false);
1334   Services.obs.addObserver(gXPInstallObserver, "addon-install-complete", false);
1336   BrowserOffline.init();
1337   OfflineApps.init();
1339   gBrowser.addEventListener("pageshow", function(evt) { setTimeout(pageShowEventHandlers, 0, evt); }, true);
1341   // Ensure login manager is up and running.
1342   Cc["@mozilla.org/login-manager;1"].getService(Ci.nsILoginManager);
1344   if (mustLoadSidebar) {
1345     let sidebar = document.getElementById("sidebar");
1346     let sidebarBox = document.getElementById("sidebar-box");
1347     sidebar.setAttribute("src", sidebarBox.getAttribute("src"));
1348   }
1350   UpdateUrlbarSearchSplitterState();
1352   PlacesStarButton.init();
1354   // called when we go into full screen, even if it is
1355   // initiated by a web page script
1356   window.addEventListener("fullscreen", onFullScreen, true);
1358   if (isLoadingBlank && gURLBar && isElementVisible(gURLBar))
1359     gURLBar.focus();
1360   else
1361     gBrowser.selectedBrowser.focus();
1363   gNavToolbox.customizeDone = BrowserToolboxCustomizeDone;
1364   gNavToolbox.customizeChange = BrowserToolboxCustomizeChange;
1366   // Set up Sanitize Item
1367   initializeSanitizer();
1369   // Enable/Disable auto-hide tabbar
1370   gBrowser.tabContainer.updateVisibility();
1372   gPrefService.addObserver(gHomeButton.prefDomain, gHomeButton, false);
1374   var homeButton = document.getElementById("home-button");
1375   gHomeButton.updateTooltip(homeButton);
1376   gHomeButton.updatePersonalToolbarStyle(homeButton);
1378 #ifdef HAVE_SHELL_SERVICE
1379   // Perform default browser checking (after window opens).
1380   var shell = getShellService();
1381   if (shell) {
1382     var shouldCheck = shell.shouldCheckDefaultBrowser;
1383     var willRecoverSession = false;
1384     try {
1385       var ss = Cc["@mozilla.org/browser/sessionstartup;1"].
1386                getService(Ci.nsISessionStartup);
1387       willRecoverSession =
1388         (ss.sessionType == Ci.nsISessionStartup.RECOVER_SESSION);
1389     }
1390     catch (ex) { /* never mind; suppose SessionStore is broken */ }
1391     if (shouldCheck && !shell.isDefaultBrowser(true) && !willRecoverSession) {
1392       var brandBundle = document.getElementById("bundle_brand");
1393       var shellBundle = document.getElementById("bundle_shell");
1395       var brandShortName = brandBundle.getString("brandShortName");
1396       var promptTitle = shellBundle.getString("setDefaultBrowserTitle");
1397       var promptMessage = shellBundle.getFormattedString("setDefaultBrowserMessage",
1398                                                          [brandShortName]);
1399       var checkboxLabel = shellBundle.getFormattedString("setDefaultBrowserDontAsk",
1400                                                          [brandShortName]);
1401       var checkEveryTime = { value: shouldCheck };
1402       var ps = Services.prompt;
1403       var rv = ps.confirmEx(window, promptTitle, promptMessage,
1404                             ps.STD_YES_NO_BUTTONS,
1405                             null, null, null, checkboxLabel, checkEveryTime);
1406       if (rv == 0)
1407         shell.setDefaultBrowser(true, false);
1408       shell.shouldCheckDefaultBrowser = checkEveryTime.value;
1409     }
1410   }
1411 #endif
1413   // BiDi UI
1414   gBidiUI = isBidiEnabled();
1415   if (gBidiUI) {
1416     document.getElementById("documentDirection-separator").hidden = false;
1417     document.getElementById("documentDirection-swap").hidden = false;
1418     document.getElementById("textfieldDirection-separator").hidden = false;
1419     document.getElementById("textfieldDirection-swap").hidden = false;
1420   }
1422 #ifdef XP_MACOSX
1423   // Setup click-and-hold gestures access to the session history
1424   // menus if global click-and-hold isn't turned on
1425   if (!getBoolPref("ui.click_hold_context_menus", false))
1426     SetClickAndHoldHandlers();
1427 #endif
1429   // Initialize the full zoom setting.
1430   // We do this before the session restore service gets initialized so we can
1431   // apply full zoom settings to tabs restored by the session restore service.
1432   try {
1433     FullZoom.init();
1434   }
1435   catch(ex) {
1436     Components.utils.reportError("Failed to init content pref service:\n" + ex);
1437   }
1439   let NP = {};
1440   Cu.import("resource:///modules/NetworkPrioritizer.jsm", NP);
1441   NP.trackBrowserWindow(window);
1443   // initialize the session-restore service (in case it's not already running)
1444   try {
1445     Cc["@mozilla.org/browser/sessionstore;1"]
1446       .getService(Ci.nsISessionStore)
1447       .init(window);
1448   } catch (ex) {
1449     dump("nsSessionStore could not be initialized: " + ex + "\n");
1450   }
1452   PlacesToolbarHelper.init();
1454   // bookmark-all-tabs command
1455   gBookmarkAllTabsHandler.init();
1457   // Attach a listener to watch for "command" events bubbling up from error
1458   // pages.  This lets us fix bugs like 401575 which require error page UI to
1459   // do privileged things, without letting error pages have any privilege
1460   // themselves.
1461   gBrowser.addEventListener("command", BrowserOnCommand, false);
1463   ctrlTab.readPref();
1464   gPrefService.addObserver(ctrlTab.prefName, ctrlTab, false);
1465   gPrefService.addObserver(allTabs.prefName, allTabs, false);
1467   // Initialize the microsummary service by retrieving it, prompting its factory
1468   // to create its singleton, whose constructor initializes the service.
1469   // Started 4 seconds after delayedStartup (before the livemarks service below).
1470   setTimeout(function() {
1471     try {
1472       Cc["@mozilla.org/microsummary/service;1"].getService(Ci.nsIMicrosummaryService);
1473     } catch (ex) {
1474       Components.utils.reportError("Failed to init microsummary service:\n" + ex);
1475     }
1476   }, 4000);
1478   // Delayed initialization of the livemarks update timer.
1479   // Livemark updates don't need to start until after bookmark UI
1480   // such as the toolbar has initialized. Starting 5 seconds after
1481   // delayedStartup in order to stagger this after the microsummary
1482   // service (see above) and before the download manager starts (see below).
1483   setTimeout(function() PlacesUtils.livemarks.start(), 5000);
1485   // Initialize the download manager some time after the app starts so that
1486   // auto-resume downloads begin (such as after crashing or quitting with
1487   // active downloads) and speeds up the first-load of the download manager UI.
1488   // If the user manually opens the download manager before the timeout, the
1489   // downloads will start right away, and getting the service again won't hurt.
1490   setTimeout(function() {
1491     gDownloadMgr = Cc["@mozilla.org/download-manager;1"].
1492                    getService(Ci.nsIDownloadManager);
1494     // Initialize the downloads monitor panel listener
1495     DownloadMonitorPanel.init();
1497     if (Win7Features) {
1498       let tempScope = {};
1499       Cu.import("resource://gre/modules/DownloadTaskbarProgress.jsm",
1500                 tempScope);
1501       tempScope.DownloadTaskbarProgress.onBrowserWindowLoad(window);
1502     }
1503   }, 10000);
1505   // Delayed initialization of PlacesDBUtils.
1506   // This component checks for database coherence once per day, on
1507   // an idle timer, taking corrective actions where needed.
1508   setTimeout(function() PlacesUtils.startPlacesDBUtils(), 15000);
1510 #ifndef XP_MACOSX
1511   updateEditUIVisibility();
1512   let placesContext = document.getElementById("placesContext");
1513   placesContext.addEventListener("popupshowing", updateEditUIVisibility, false);
1514   placesContext.addEventListener("popuphiding", updateEditUIVisibility, false);
1515 #endif
1517   // initialize the private browsing UI
1518   gPrivateBrowsingUI.init();
1520   gBrowser.mPanelContainer.addEventListener("InstallBrowserTheme", LightWeightThemeWebInstaller, false, true);
1521   gBrowser.mPanelContainer.addEventListener("PreviewBrowserTheme", LightWeightThemeWebInstaller, false, true);
1522   gBrowser.mPanelContainer.addEventListener("ResetBrowserThemePreview", LightWeightThemeWebInstaller, false, true);
1524   if (Win7Features)
1525     Win7Features.onOpenWindow();
1527 #ifdef MOZ_SERVICES_SYNC
1528   // initialize the sync UI
1529   gSyncUI.init();
1530 #endif
1532   Services.obs.notifyObservers(window, "browser-delayed-startup-finished", "");
1535 function BrowserShutdown()
1537   if (Win7Features)
1538     Win7Features.onCloseWindow();
1540   gPrefService.removeObserver(ctrlTab.prefName, ctrlTab);
1541   gPrefService.removeObserver(allTabs.prefName, allTabs);
1542   ctrlTab.uninit();
1543   allTabs.uninit();
1545   CombinedStopReload.uninit();
1547   gGestureSupport.init(false);
1549   FullScreen.cleanup();
1551   try {
1552     FullZoom.destroy();
1553   }
1554   catch(ex) {
1555     Components.utils.reportError(ex);
1556   }
1558   Services.obs.removeObserver(gSessionHistoryObserver, "browser:purge-session-history");
1559   Services.obs.removeObserver(gXPInstallObserver, "addon-install-blocked");
1560   Services.obs.removeObserver(gXPInstallObserver, "addon-install-failed");
1561   Services.obs.removeObserver(gXPInstallObserver, "addon-install-complete");
1562   Services.obs.removeObserver(gPluginHandler.pluginCrashed, "plugin-crashed");
1564   try {
1565     gBrowser.removeProgressListener(window.XULBrowserWindow);
1566     gBrowser.removeTabsProgressListener(window.TabsProgressListener);
1567   } catch (ex) {
1568   }
1570   PlacesStarButton.uninit();
1572   try {
1573     gPrefService.removeObserver(gHomeButton.prefDomain, gHomeButton);
1574   } catch (ex) {
1575     Components.utils.reportError(ex);
1576   }
1578   BrowserOffline.uninit();
1579   OfflineApps.uninit();
1580   DownloadMonitorPanel.uninit();
1581   gPrivateBrowsingUI.uninit();
1583   var enumerator = Services.wm.getEnumerator(null);
1584   enumerator.getNext();
1585   if (!enumerator.hasMoreElements()) {
1586     document.persist("sidebar-box", "sidebarcommand");
1587     document.persist("sidebar-box", "width");
1588     document.persist("sidebar-box", "src");
1589     document.persist("sidebar-title", "value");
1590   }
1592   window.XULBrowserWindow.destroy();
1593   window.XULBrowserWindow = null;
1594   window.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
1595         .getInterface(Components.interfaces.nsIWebNavigation)
1596         .QueryInterface(Components.interfaces.nsIDocShellTreeItem).treeOwner
1597         .QueryInterface(Components.interfaces.nsIInterfaceRequestor)
1598         .getInterface(Components.interfaces.nsIXULWindow)
1599         .XULBrowserWindow = null;
1600   window.QueryInterface(Ci.nsIDOMChromeWindow).browserDOMWindow = null;
1603 #ifdef XP_MACOSX
1604 // nonBrowserWindowStartup(), nonBrowserWindowDelayedStartup(), and
1605 // nonBrowserWindowShutdown() are used for non-browser windows in
1606 // macBrowserOverlay
1607 function nonBrowserWindowStartup()
1609   // Disable inappropriate commands / submenus
1610   var disabledItems = ['Browser:SavePage',
1611                        'Browser:SendLink', 'cmd_pageSetup', 'cmd_print', 'cmd_find', 'cmd_findAgain',
1612                        'viewToolbarsMenu', 'cmd_toggleTaskbar', 'viewSidebarMenuMenu', 'Browser:Reload',
1613                        'viewFullZoomMenu', 'pageStyleMenu', 'charsetMenu', 'View:PageSource', 'View:FullScreen',
1614                        'viewHistorySidebar', 'Browser:AddBookmarkAs', 'View:PageInfo', 'Tasks:InspectPage'];
1615   var element;
1617   for (var id in disabledItems)
1618   {
1619     element = document.getElementById(disabledItems[id]);
1620     if (element)
1621       element.setAttribute("disabled", "true");
1622   }
1624   // If no windows are active (i.e. we're the hidden window), disable the close, minimize
1625   // and zoom menu commands as well
1626   if (window.location.href == "chrome://browser/content/hiddenWindow.xul")
1627   {
1628     var hiddenWindowDisabledItems = ['cmd_close', 'minimizeWindow', 'zoomWindow'];
1629     for (var id in hiddenWindowDisabledItems)
1630     {
1631       element = document.getElementById(hiddenWindowDisabledItems[id]);
1632       if (element)
1633         element.setAttribute("disabled", "true");
1634     }
1636     // also hide the window-list separator
1637     element = document.getElementById("sep-window-list");
1638     element.setAttribute("hidden", "true");
1640     // Setup the dock menu.
1641     let dockMenuElement = document.getElementById("menu_mac_dockmenu");
1642     if (dockMenuElement != null) {
1643       let nativeMenu = Cc["@mozilla.org/widget/standalonenativemenu;1"]
1644                        .createInstance(Ci.nsIStandaloneNativeMenu);
1646       try {
1647         nativeMenu.init(dockMenuElement);
1649         let dockSupport = Cc["@mozilla.org/widget/macdocksupport;1"]
1650                           .getService(Ci.nsIMacDockSupport);
1651         dockSupport.dockMenu = nativeMenu;
1652       }
1653       catch (e) {
1654       }
1655     }
1656   }
1659   setTimeout(nonBrowserWindowDelayedStartup, 0);
1662 function nonBrowserWindowDelayedStartup()
1664   // initialise the offline listener
1665   BrowserOffline.init();
1667   // Set up Sanitize Item
1668   initializeSanitizer();
1670   // initialize the private browsing UI
1671   gPrivateBrowsingUI.init();
1673 #ifdef MOZ_SERVICES_SYNC
1674   // initialize the sync UI
1675   gSyncUI.init();
1676 #endif
1679 function nonBrowserWindowShutdown()
1681   BrowserOffline.uninit();
1683   gPrivateBrowsingUI.uninit();
1685 #endif
1687 function initializeSanitizer()
1689   const kDidSanitizeDomain = "privacy.sanitize.didShutdownSanitize";
1690   if (gPrefService.prefHasUserValue(kDidSanitizeDomain)) {
1691     gPrefService.clearUserPref(kDidSanitizeDomain);
1692     // We need to persist this preference change, since we want to
1693     // check it at next app start even if the browser exits abruptly
1694     gPrefService.savePrefFile(null);
1695   }
1697   /**
1698    * Migrate Firefox 3.0 privacy.item prefs under one of these conditions:
1699    *
1700    * a) User has customized any privacy.item prefs
1701    * b) privacy.sanitize.sanitizeOnShutdown is set
1702    */
1703   if (!gPrefService.getBoolPref("privacy.sanitize.migrateFx3Prefs")) {
1704     let itemBranch = gPrefService.getBranch("privacy.item.");
1705     let itemArray = itemBranch.getChildList("");
1707     // See if any privacy.item prefs are set
1708     let doMigrate = itemArray.some(function (name) itemBranch.prefHasUserValue(name));
1709     // Or if sanitizeOnShutdown is set
1710     if (!doMigrate)
1711       doMigrate = gPrefService.getBoolPref("privacy.sanitize.sanitizeOnShutdown");
1713     if (doMigrate) {
1714       let cpdBranch = gPrefService.getBranch("privacy.cpd.");
1715       let clearOnShutdownBranch = gPrefService.getBranch("privacy.clearOnShutdown.");
1716       itemArray.forEach(function (name) {
1717         try {
1718           // don't migrate password or offlineApps clearing in the CRH dialog since
1719           // there's no UI for those anymore. They default to false. bug 497656
1720           if (name != "passwords" && name != "offlineApps")
1721             cpdBranch.setBoolPref(name, itemBranch.getBoolPref(name));
1722           clearOnShutdownBranch.setBoolPref(name, itemBranch.getBoolPref(name));
1723         }
1724         catch(e) {
1725           Cu.reportError("Exception thrown during privacy pref migration: " + e);
1726         }
1727       });
1728     }
1730     gPrefService.setBoolPref("privacy.sanitize.migrateFx3Prefs", true);
1731   }
1734 function gotoHistoryIndex(aEvent)
1736   var index = aEvent.target.getAttribute("index");
1737   if (!index)
1738     return false;
1740   var where = whereToOpenLink(aEvent);
1742   if (where == "current") {
1743     // Normal click.  Go there in the current tab and update session history.
1745     try {
1746       gBrowser.gotoIndex(index);
1747     }
1748     catch(ex) {
1749       return false;
1750     }
1751     return true;
1752   }
1753   else {
1754     // Modified click.  Go there in a new tab/window.
1755     // This code doesn't copy history or work well with framed pages.
1757     var sessionHistory = getWebNavigation().sessionHistory;
1758     var entry = sessionHistory.getEntryAtIndex(index, false);
1759     var url = entry.URI.spec;
1760     openUILinkIn(url, where, {relatedToCurrent: true});
1761     return true;
1762   }
1765 function BrowserForward(aEvent) {
1766   var where = whereToOpenLink(aEvent, false, true);
1768   if (where == "current") {
1769     try {
1770       gBrowser.goForward();
1771     }
1772     catch(ex) {
1773     }
1774   }
1775   else {
1776     var sessionHistory = getWebNavigation().sessionHistory;
1777     var currentIndex = sessionHistory.index;
1778     var entry = sessionHistory.getEntryAtIndex(currentIndex + 1, false);
1779     var url = entry.URI.spec;
1780     openUILinkIn(url, where, {relatedToCurrent: true});
1781   }
1784 function BrowserBack(aEvent) {
1785   var where = whereToOpenLink(aEvent, false, true);
1787   if (where == "current") {
1788     try {
1789       gBrowser.goBack();
1790     }
1791     catch(ex) {
1792     }
1793   }
1794   else {
1795     var sessionHistory = getWebNavigation().sessionHistory;
1796     var currentIndex = sessionHistory.index;
1797     var entry = sessionHistory.getEntryAtIndex(currentIndex - 1, false);
1798     var url = entry.URI.spec;
1799     openUILinkIn(url, where, {relatedToCurrent: true});
1800   }
1803 function BrowserHandleBackspace()
1805   switch (gPrefService.getIntPref("browser.backspace_action")) {
1806   case 0:
1807     BrowserBack();
1808     break;
1809   case 1:
1810     goDoCommand("cmd_scrollPageUp");
1811     break;
1812   }
1815 function BrowserHandleShiftBackspace()
1817   switch (gPrefService.getIntPref("browser.backspace_action")) {
1818   case 0:
1819     BrowserForward();
1820     break;
1821   case 1:
1822     goDoCommand("cmd_scrollPageDown");
1823     break;
1824   }
1827 function BrowserStop()
1829   try {
1830     const stopFlags = nsIWebNavigation.STOP_ALL;
1831     getWebNavigation().stop(stopFlags);
1832   }
1833   catch(ex) {
1834   }
1837 function BrowserReloadOrDuplicate(aEvent) {
1838   var backgroundTabModifier = aEvent.button == 1 ||
1839 #ifdef XP_MACOSX
1840     aEvent.metaKey;
1841 #else
1842     aEvent.ctrlKey;
1843 #endif
1844   if (aEvent.shiftKey && !backgroundTabModifier) {
1845     BrowserReloadSkipCache();
1846     return;
1847   }
1849   var where = whereToOpenLink(aEvent, false, true);
1850   if (where == "current")
1851     BrowserReload();
1852   else
1853     openUILinkIn(getWebNavigation().currentURI.spec, where,
1854                  {relatedToCurrent: true});
1857 function BrowserReload() {
1858   const reloadFlags = nsIWebNavigation.LOAD_FLAGS_NONE;
1859   BrowserReloadWithFlags(reloadFlags);
1862 function BrowserReloadSkipCache() {
1863   // Bypass proxy and cache.
1864   const reloadFlags = nsIWebNavigation.LOAD_FLAGS_BYPASS_PROXY | nsIWebNavigation.LOAD_FLAGS_BYPASS_CACHE;
1865   BrowserReloadWithFlags(reloadFlags);
1868 function BrowserHome()
1870   var homePage = gHomeButton.getHomePage();
1871   loadOneOrMoreURIs(homePage);
1874 function BrowserGoHome(aEvent) {
1875   if (aEvent && "button" in aEvent &&
1876       aEvent.button == 2) // right-click: do nothing
1877     return;
1879   var homePage = gHomeButton.getHomePage();
1880   var where = whereToOpenLink(aEvent, false, true);
1881   var urls;
1883   // openUILinkIn in utilityOverlay.js doesn't handle loading multiple pages
1884   switch (where) {
1885   case "current":
1886     loadOneOrMoreURIs(homePage);
1887     break;
1888   case "tabshifted":
1889   case "tab":
1890     urls = homePage.split("|");
1891     var loadInBackground = getBoolPref("browser.tabs.loadBookmarksInBackground", false);
1892     gBrowser.loadTabs(urls, loadInBackground);
1893     break;
1894   case "window":
1895     OpenBrowserWindow();
1896     break;
1897   }
1900 function loadOneOrMoreURIs(aURIString)
1902 #ifdef XP_MACOSX
1903   // we're not a browser window, pass the URI string to a new browser window
1904   if (window.location.href != getBrowserURL())
1905   {
1906     window.openDialog(getBrowserURL(), "_blank", "all,dialog=no", aURIString);
1907     return;
1908   }
1909 #endif
1910   // This function throws for certain malformed URIs, so use exception handling
1911   // so that we don't disrupt startup
1912   try {
1913     gBrowser.loadTabs(aURIString.split("|"), false, true);
1914   }
1915   catch (e) {
1916   }
1919 function focusAndSelectUrlBar() {
1920   if (gURLBar && !gURLBar.readOnly) {
1921     if (window.fullScreen)
1922       FullScreen.mouseoverToggle(true);
1923     if (isElementVisible(gURLBar)) {
1924       gURLBar.focus();
1925       gURLBar.select();
1926       return true;
1927     }
1928   }
1929   return false;
1932 function openLocation() {
1933   if (focusAndSelectUrlBar())
1934     return;
1936 #ifdef XP_MACOSX
1937   if (window.location.href != getBrowserURL()) {
1938     var win = getTopWin();
1939     if (win) {
1940       // If there's an open browser window, it should handle this command
1941       win.focus()
1942       win.openLocation();
1943     }
1944     else {
1945       // If there are no open browser windows, open a new one
1946       win = window.openDialog("chrome://browser/content/", "_blank",
1947                               "chrome,all,dialog=no", "about:blank");
1948       win.addEventListener("load", openLocationCallback, false);
1949     }
1950     return;
1951   }
1952 #endif
1953   openDialog("chrome://browser/content/openLocation.xul", "_blank",
1954              "chrome,modal,titlebar", window);
1957 function openLocationCallback()
1959   // make sure the DOM is ready
1960   setTimeout(function() { this.openLocation(); }, 0);
1963 function BrowserOpenTab()
1965   if (!gBrowser) {
1966     // If there are no open browser windows, open a new one
1967     window.openDialog("chrome://browser/content/", "_blank",
1968                       "chrome,all,dialog=no", "about:blank");
1969     return;
1970   }
1971   gBrowser.loadOneTab("about:blank", {inBackground: false});
1972   focusAndSelectUrlBar();
1975 /* Called from the openLocation dialog. This allows that dialog to instruct
1976    its opener to open a new window and then step completely out of the way.
1977    Anything less byzantine is causing horrible crashes, rather believably,
1978    though oddly only on Linux. */
1979 function delayedOpenWindow(chrome, flags, href, postData)
1981   // The other way to use setTimeout,
1982   // setTimeout(openDialog, 10, chrome, "_blank", flags, url),
1983   // doesn't work here.  The extra "magic" extra argument setTimeout adds to
1984   // the callback function would confuse prepareForStartup() by making
1985   // window.arguments[1] be an integer instead of null.
1986   setTimeout(function() { openDialog(chrome, "_blank", flags, href, null, null, postData); }, 10);
1989 /* Required because the tab needs time to set up its content viewers and get the load of
1990    the URI kicked off before becoming the active content area. */
1991 function delayedOpenTab(aUrl, aReferrer, aCharset, aPostData, aAllowThirdPartyFixup)
1993   gBrowser.loadOneTab(aUrl, {
1994                       referrerURI: aReferrer,
1995                       charset: aCharset,
1996                       postData: aPostData,
1997                       inBackground: false,
1998                       allowThirdPartyFixup: aAllowThirdPartyFixup});
2001 var gLastOpenDirectory = {
2002   _lastDir: null,
2003   get path() {
2004     if (!this._lastDir || !this._lastDir.exists()) {
2005       try {
2006         this._lastDir = gPrefService.getComplexValue("browser.open.lastDir",
2007                                                      Ci.nsILocalFile);
2008         if (!this._lastDir.exists())
2009           this._lastDir = null;
2010       }
2011       catch(e) {}
2012     }
2013     return this._lastDir;
2014   },
2015   set path(val) {
2016     if (!val || !val.exists() || !val.isDirectory())
2017       return;
2018     this._lastDir = val.clone();
2020     // Don't save the last open directory pref inside the Private Browsing mode
2021     if (!gPrivateBrowsingUI.privateBrowsingEnabled)
2022       gPrefService.setComplexValue("browser.open.lastDir", Ci.nsILocalFile,
2023                                    this._lastDir);
2024   },
2025   reset: function() {
2026     this._lastDir = null;
2027   }
2030 function BrowserOpenFileWindow()
2032   // Get filepicker component.
2033   try {
2034     const nsIFilePicker = Components.interfaces.nsIFilePicker;
2035     var fp = Components.classes["@mozilla.org/filepicker;1"].createInstance(nsIFilePicker);
2036     fp.init(window, gNavigatorBundle.getString("openFile"), nsIFilePicker.modeOpen);
2037     fp.appendFilters(nsIFilePicker.filterAll | nsIFilePicker.filterText | nsIFilePicker.filterImages |
2038                      nsIFilePicker.filterXML | nsIFilePicker.filterHTML);
2039     fp.displayDirectory = gLastOpenDirectory.path;
2041     if (fp.show() == nsIFilePicker.returnOK) {
2042       if (fp.file && fp.file.exists())
2043         gLastOpenDirectory.path = fp.file.parent.QueryInterface(Ci.nsILocalFile);
2044       openTopWin(fp.fileURL.spec);
2045     }
2046   } catch (ex) {
2047   }
2050 function BrowserCloseTabOrWindow() {
2051 #ifdef XP_MACOSX
2052   // If we're not a browser window, just close the window
2053   if (window.location.href != getBrowserURL()) {
2054     closeWindow(true);
2055     return;
2056   }
2057 #endif
2059   // If the current tab is the last one, this will close the window.
2060   gBrowser.removeCurrentTab({animate: true});
2063 function BrowserTryToCloseWindow()
2065   if (WindowIsClosing())
2066     window.close();     // WindowIsClosing does all the necessary checks
2069 function loadURI(uri, referrer, postData, allowThirdPartyFixup)
2071   try {
2072     if (postData === undefined)
2073       postData = null;
2074     var flags = nsIWebNavigation.LOAD_FLAGS_NONE;
2075     if (allowThirdPartyFixup) {
2076       flags = nsIWebNavigation.LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP;
2077     }
2078     gBrowser.loadURIWithFlags(uri, flags, referrer, null, postData);
2079   } catch (e) {
2080   }
2083 function getShortcutOrURI(aURL, aPostDataRef) {
2084   var shortcutURL = null;
2085   var keyword = aURL;
2086   var param = "";
2088   var offset = aURL.indexOf(" ");
2089   if (offset > 0) {
2090     keyword = aURL.substr(0, offset);
2091     param = aURL.substr(offset + 1);
2092   }
2094   if (!aPostDataRef)
2095     aPostDataRef = {};
2097   var engine = Services.search.getEngineByAlias(keyword);
2098   if (engine) {
2099     var submission = engine.getSubmission(param);
2100     aPostDataRef.value = submission.postData;
2101     return submission.uri.spec;
2102   }
2104   [shortcutURL, aPostDataRef.value] =
2105     PlacesUtils.getURLAndPostDataForKeyword(keyword);
2107   if (!shortcutURL)
2108     return aURL;
2110   var postData = "";
2111   if (aPostDataRef.value)
2112     postData = unescape(aPostDataRef.value);
2114   if (/%s/i.test(shortcutURL) || /%s/i.test(postData)) {
2115     var charset = "";
2116     const re = /^(.*)\&mozcharset=([a-zA-Z][_\-a-zA-Z0-9]+)\s*$/;
2117     var matches = shortcutURL.match(re);
2118     if (matches)
2119       [, shortcutURL, charset] = matches;
2120     else {
2121       // Try to get the saved character-set.
2122       try {
2123         // makeURI throws if URI is invalid.
2124         // Will return an empty string if character-set is not found.
2125         charset = PlacesUtils.history.getCharsetForURI(makeURI(shortcutURL));
2126       } catch (e) {}
2127     }
2129     var encodedParam = "";
2130     if (charset)
2131       encodedParam = escape(convertFromUnicode(charset, param));
2132     else // Default charset is UTF-8
2133       encodedParam = encodeURIComponent(param);
2135     shortcutURL = shortcutURL.replace(/%s/g, encodedParam).replace(/%S/g, param);
2137     if (/%s/i.test(postData)) // POST keyword
2138       aPostDataRef.value = getPostDataStream(postData, param, encodedParam,
2139                                              "application/x-www-form-urlencoded");
2140   }
2141   else if (param) {
2142     // This keyword doesn't take a parameter, but one was provided. Just return
2143     // the original URL.
2144     aPostDataRef.value = null;
2146     return aURL;
2147   }
2149   return shortcutURL;
2152 function getPostDataStream(aStringData, aKeyword, aEncKeyword, aType) {
2153   var dataStream = Cc["@mozilla.org/io/string-input-stream;1"].
2154                    createInstance(Ci.nsIStringInputStream);
2155   aStringData = aStringData.replace(/%s/g, aEncKeyword).replace(/%S/g, aKeyword);
2156   dataStream.data = aStringData;
2158   var mimeStream = Cc["@mozilla.org/network/mime-input-stream;1"].
2159                    createInstance(Ci.nsIMIMEInputStream);
2160   mimeStream.addHeader("Content-Type", aType);
2161   mimeStream.addContentLength = true;
2162   mimeStream.setData(dataStream);
2163   return mimeStream.QueryInterface(Ci.nsIInputStream);
2166 function readFromClipboard()
2168   var url;
2170   try {
2171     // Get clipboard.
2172     var clipboard = Components.classes["@mozilla.org/widget/clipboard;1"]
2173                               .getService(Components.interfaces.nsIClipboard);
2175     // Create transferable that will transfer the text.
2176     var trans = Components.classes["@mozilla.org/widget/transferable;1"]
2177                           .createInstance(Components.interfaces.nsITransferable);
2179     trans.addDataFlavor("text/unicode");
2181     // If available, use selection clipboard, otherwise global one
2182     if (clipboard.supportsSelectionClipboard())
2183       clipboard.getData(trans, clipboard.kSelectionClipboard);
2184     else
2185       clipboard.getData(trans, clipboard.kGlobalClipboard);
2187     var data = {};
2188     var dataLen = {};
2189     trans.getTransferData("text/unicode", data, dataLen);
2191     if (data) {
2192       data = data.value.QueryInterface(Components.interfaces.nsISupportsString);
2193       url = data.data.substring(0, dataLen.value / 2);
2194     }
2195   } catch (ex) {
2196   }
2198   return url;
2201 function BrowserViewSourceOfDocument(aDocument)
2203   var pageCookie;
2204   var webNav;
2206   // Get the document charset
2207   var docCharset = "charset=" + aDocument.characterSet;
2209   // Get the nsIWebNavigation associated with the document
2210   try {
2211       var win;
2212       var ifRequestor;
2214       // Get the DOMWindow for the requested document.  If the DOMWindow
2215       // cannot be found, then just use the content window...
2216       //
2217       // XXX:  This is a bit of a hack...
2218       win = aDocument.defaultView;
2219       if (win == window) {
2220         win = content;
2221       }
2222       ifRequestor = win.QueryInterface(Components.interfaces.nsIInterfaceRequestor);
2224       webNav = ifRequestor.getInterface(nsIWebNavigation);
2225   } catch(err) {
2226       // If nsIWebNavigation cannot be found, just get the one for the whole
2227       // window...
2228       webNav = getWebNavigation();
2229   }
2230   //
2231   // Get the 'PageDescriptor' for the current document. This allows the
2232   // view-source to access the cached copy of the content rather than
2233   // refetching it from the network...
2234   //
2235   try{
2236     var PageLoader = webNav.QueryInterface(Components.interfaces.nsIWebPageDescriptor);
2238     pageCookie = PageLoader.currentDescriptor;
2239   } catch(err) {
2240     // If no page descriptor is available, just use the view-source URL...
2241   }
2243   top.gViewSourceUtils.viewSource(webNav.currentURI.spec, pageCookie, aDocument);
2246 // doc - document to use for source, or null for this window's document
2247 // initialTab - name of the initial tab to display, or null for the first tab
2248 // imageElement - image to load in the Media Tab of the Page Info window; can be null/omitted
2249 function BrowserPageInfo(doc, initialTab, imageElement) {
2250   var args = {doc: doc, initialTab: initialTab, imageElement: imageElement};
2251   var windows = Cc['@mozilla.org/appshell/window-mediator;1']
2252                   .getService(Ci.nsIWindowMediator)
2253                   .getEnumerator("Browser:page-info");
2255   var documentURL = doc ? doc.location : window.content.document.location;
2257   // Check for windows matching the url
2258   while (windows.hasMoreElements()) {
2259     var currentWindow = windows.getNext();
2260     if (currentWindow.document.documentElement.getAttribute("relatedUrl") == documentURL) {
2261       currentWindow.focus();
2262       currentWindow.resetPageInfo(args);
2263       return currentWindow;
2264     }
2265   }
2267   // We didn't find a matching window, so open a new one.
2268   return openDialog("chrome://browser/content/pageinfo/pageInfo.xul", "",
2269                     "chrome,toolbar,dialog=no,resizable", args);
2272 #ifdef DEBUG
2273 // Initialize the LeakDetector class.
2274 function LeakDetector(verbose)
2276   this.verbose = verbose;
2279 const NS_LEAKDETECTOR_CONTRACTID = "@mozilla.org/xpcom/leakdetector;1";
2281 if (NS_LEAKDETECTOR_CONTRACTID in Components.classes) {
2282   try {
2283     LeakDetector.prototype = Components.classes[NS_LEAKDETECTOR_CONTRACTID]
2284                                        .createInstance(Components.interfaces.nsILeakDetector);
2285   } catch (err) {
2286     LeakDetector.prototype = Object.prototype;
2287   }
2288 } else {
2289   LeakDetector.prototype = Object.prototype;
2292 var leakDetector = new LeakDetector(false);
2294 // Dumps current set of memory leaks.
2295 function dumpMemoryLeaks()
2297   leakDetector.dumpLeaks();
2300 // Traces all objects reachable from the chrome document.
2301 function traceChrome()
2303   leakDetector.traceObject(document, leakDetector.verbose);
2306 // Traces all objects reachable from the content document.
2307 function traceDocument()
2309   // keep the chrome document out of the dump.
2310   leakDetector.markObject(document, true);
2311   leakDetector.traceObject(content, leakDetector.verbose);
2312   leakDetector.markObject(document, false);
2315 // Controls whether or not we do verbose tracing.
2316 function traceVerbose(verbose)
2318   leakDetector.verbose = (verbose == "true");
2320 #endif
2322 function URLBarSetURI(aURI) {
2323   var value = gBrowser.userTypedValue;
2324   var valid = false;
2326   if (value == null) {
2327     let uri = aURI || getWebNavigation().currentURI;
2329     // Replace initial page URIs with an empty string
2330     // only if there's no opener (bug 370555).
2331     if (gInitialPages.indexOf(uri.spec) != -1)
2332       value = content.opener ? uri.spec : "";
2333     else
2334       value = losslessDecodeURI(uri);
2336     valid = (uri.spec != "about:blank");
2337   }
2339   gURLBar.value = value;
2340   SetPageProxyState(valid ? "valid" : "invalid");
2343 function losslessDecodeURI(aURI) {
2344   var value = aURI.spec;
2345   // Try to decode as UTF-8 if there's no encoding sequence that we would break.
2346   if (!/%25(?:3B|2F|3F|3A|40|26|3D|2B|24|2C|23)/i.test(value))
2347     try {
2348       value = decodeURI(value)
2349                 // 1. decodeURI decodes %25 to %, which creates unintended
2350                 //    encoding sequences. Re-encode it, unless it's part of
2351                 //    a sequence that survived decodeURI, i.e. one for:
2352                 //    ';', '/', '?', ':', '@', '&', '=', '+', '$', ',', '#'
2353                 //    (RFC 3987 section 3.2)
2354                 // 2. Re-encode whitespace so that it doesn't get eaten away
2355                 //    by the location bar (bug 410726).
2356                 .replace(/%(?!3B|2F|3F|3A|40|26|3D|2B|24|2C|23)|[\r\n\t]/ig,
2357                          encodeURIComponent);
2358     } catch (e) {}
2360   // Encode invisible characters (line and paragraph separator,
2361   // object replacement character) (bug 452979)
2362   value = value.replace(/[\v\x0c\x1c\x1d\x1e\x1f\u2028\u2029\ufffc]/g,
2363                         encodeURIComponent);
2365   // Encode default ignorable characters. (bug 546013)
2366   // This includes all bidirectional formatting characters.
2367   // (RFC 3987 sections 3.2 and 4.1 paragraph 6)
2368   value = value.replace(/[\u00ad\u034f\u115f-\u1160\u17b4-\u17b5\u180b-\u180d\u200b-\u200f\u202a-\u202e\u2060-\u206f\u3164\ufe00-\ufe0f\ufeff\uffa0\ufff0-\ufff8]|\ud834[\udd73-\udd7a]|[\udb40-\udb43][\udc00-\udfff]/g,
2369                         encodeURIComponent);
2370   return value;
2373 function UpdateUrlbarSearchSplitterState()
2375   var splitter = document.getElementById("urlbar-search-splitter");
2376   var urlbar = document.getElementById("urlbar-container");
2377   var searchbar = document.getElementById("search-container");
2379   var ibefore = null;
2380   if (urlbar && searchbar) {
2381     if (urlbar.nextSibling == searchbar)
2382       ibefore = searchbar;
2383     else if (searchbar.nextSibling == urlbar)
2384       ibefore = urlbar;
2385   }
2387   if (ibefore) {
2388     if (!splitter) {
2389       splitter = document.createElement("splitter");
2390       splitter.id = "urlbar-search-splitter";
2391       splitter.setAttribute("resizebefore", "flex");
2392       splitter.setAttribute("resizeafter", "flex");
2393       splitter.className = "chromeclass-toolbar-additional";
2394     }
2395     urlbar.parentNode.insertBefore(splitter, ibefore);
2396   } else if (splitter)
2397     splitter.parentNode.removeChild(splitter);
2400 var LocationBarHelpers = {
2401   _timeoutID: null,
2403   _searchBegin: function LocBar_searchBegin() {
2404     function delayedBegin(self) {
2405       self._timeoutID = null;
2406       document.getElementById("urlbar-throbber").setAttribute("busy", "true");
2407     }
2409     this._timeoutID = setTimeout(delayedBegin, 500, this);
2410   },
2412   _searchComplete: function LocBar_searchComplete() {
2413     // Did we finish the search before delayedBegin was invoked?
2414     if (this._timeoutID) {
2415       clearTimeout(this._timeoutID);
2416       this._timeoutID = null;
2417     }
2418     document.getElementById("urlbar-throbber").removeAttribute("busy");
2419   }
2422 function UpdatePageProxyState()
2424   if (gURLBar && gURLBar.value != gLastValidURLStr)
2425     SetPageProxyState("invalid");
2428 function SetPageProxyState(aState)
2430   if (!gURLBar)
2431     return;
2433   if (!gProxyFavIcon)
2434     gProxyFavIcon = document.getElementById("page-proxy-favicon");
2436   gURLBar.setAttribute("pageproxystate", aState);
2437   gProxyFavIcon.setAttribute("pageproxystate", aState);
2439   // the page proxy state is set to valid via OnLocationChange, which
2440   // gets called when we switch tabs.
2441   if (aState == "valid") {
2442     gLastValidURLStr = gURLBar.value;
2443     gURLBar.addEventListener("input", UpdatePageProxyState, false);
2445     PageProxySetIcon(gBrowser.getIcon());
2446   } else if (aState == "invalid") {
2447     gURLBar.removeEventListener("input", UpdatePageProxyState, false);
2448     PageProxyClearIcon();
2449   }
2452 function PageProxySetIcon (aURL)
2454   if (!gProxyFavIcon)
2455     return;
2457   if (!aURL)
2458     PageProxyClearIcon();
2459   else if (gProxyFavIcon.getAttribute("src") != aURL)
2460     gProxyFavIcon.setAttribute("src", aURL);
2463 function PageProxyClearIcon ()
2465   gProxyFavIcon.removeAttribute("src");
2468 function PageProxyClickHandler(aEvent)
2470   if (aEvent.button == 1 && gPrefService.getBoolPref("middlemouse.paste"))
2471     middleMousePaste(aEvent);
2474 function BrowserImport()
2476 #ifdef XP_MACOSX
2477   var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
2478                      .getService(Components.interfaces.nsIWindowMediator);
2479   var win = wm.getMostRecentWindow("Browser:MigrationWizard");
2480   if (win)
2481     win.focus();
2482   else {
2483     window.openDialog("chrome://browser/content/migration/migration.xul",
2484                       "migration", "centerscreen,chrome,resizable=no");
2485   }
2486 #else
2487   window.openDialog("chrome://browser/content/migration/migration.xul",
2488                     "migration", "modal,centerscreen,chrome,resizable=no");
2489 #endif
2493  * Handle command events bubbling up from error page content
2494  */
2495 function BrowserOnCommand(event) {
2496     // Don't trust synthetic events
2497     if (!event.isTrusted)
2498       return;
2500     var ot = event.originalTarget;
2501     var errorDoc = ot.ownerDocument;
2503     // If the event came from an ssl error page, it is probably either the "Add
2504     // Exception…" or "Get me out of here!" button
2505     if (/^about:certerror/.test(errorDoc.documentURI)) {
2506       if (ot == errorDoc.getElementById('exceptionDialogButton')) {
2507         var params = { exceptionAdded : false, handlePrivateBrowsing : true };
2509         try {
2510           switch (gPrefService.getIntPref("browser.ssl_override_behavior")) {
2511             case 2 : // Pre-fetch & pre-populate
2512               params.prefetchCert = true;
2513             case 1 : // Pre-populate
2514               params.location = errorDoc.location.href;
2515           }
2516         } catch (e) {
2517           Components.utils.reportError("Couldn't get ssl_override pref: " + e);
2518         }
2520         window.openDialog('chrome://pippki/content/exceptionDialog.xul',
2521                           '','chrome,centerscreen,modal', params);
2523         // If the user added the exception cert, attempt to reload the page
2524         if (params.exceptionAdded)
2525           errorDoc.location.reload();
2526       }
2527       else if (ot == errorDoc.getElementById('getMeOutOfHereButton')) {
2528         getMeOutOfHere();
2529       }
2530     }
2531     else if (/^about:blocked/.test(errorDoc.documentURI)) {
2532       // The event came from a button on a malware/phishing block page
2533       // First check whether it's malware or phishing, so that we can
2534       // use the right strings/links
2535       var isMalware = /e=malwareBlocked/.test(errorDoc.documentURI);
2537       if (ot == errorDoc.getElementById('getMeOutButton')) {
2538         getMeOutOfHere();
2539       }
2540       else if (ot == errorDoc.getElementById('reportButton')) {
2541         // This is the "Why is this site blocked" button.  For malware,
2542         // we can fetch a site-specific report, for phishing, we redirect
2543         // to the generic page describing phishing protection.
2545         if (isMalware) {
2546           // Get the stop badware "why is this blocked" report url,
2547           // append the current url, and go there.
2548           try {
2549             let reportURL = formatURL("browser.safebrowsing.malware.reportURL", true);
2550             reportURL += errorDoc.location.href;
2551             content.location = reportURL;
2552           } catch (e) {
2553             Components.utils.reportError("Couldn't get malware report URL: " + e);
2554           }
2555         }
2556         else { // It's a phishing site, not malware
2557           try {
2558             content.location = formatURL("browser.safebrowsing.warning.infoURL", true);
2559           } catch (e) {
2560             Components.utils.reportError("Couldn't get phishing info URL: " + e);
2561           }
2562         }
2563       }
2564       else if (ot == errorDoc.getElementById('ignoreWarningButton')) {
2565         // Allow users to override and continue through to the site,
2566         // but add a notify bar as a reminder, so that they don't lose
2567         // track after, e.g., tab switching.
2568         gBrowser.loadURIWithFlags(content.location.href,
2569                                   nsIWebNavigation.LOAD_FLAGS_BYPASS_CLASSIFIER,
2570                                   null, null, null);
2571         let buttons = [{
2572           label: gNavigatorBundle.getString("safebrowsing.getMeOutOfHereButton.label"),
2573           accessKey: gNavigatorBundle.getString("safebrowsing.getMeOutOfHereButton.accessKey"),
2574           callback: function() { getMeOutOfHere(); }
2575         }];
2577         let title;
2578         if (isMalware) {
2579           title = gNavigatorBundle.getString("safebrowsing.reportedAttackSite");
2580           buttons[1] = {
2581             label: gNavigatorBundle.getString("safebrowsing.notAnAttackButton.label"),
2582             accessKey: gNavigatorBundle.getString("safebrowsing.notAnAttackButton.accessKey"),
2583             callback: function() {
2584               openUILinkIn(safebrowsing.getReportURL('MalwareError'), 'tab');
2585             }
2586           };
2587         } else {
2588           title = gNavigatorBundle.getString("safebrowsing.reportedWebForgery");
2589           buttons[1] = {
2590             label: gNavigatorBundle.getString("safebrowsing.notAForgeryButton.label"),
2591             accessKey: gNavigatorBundle.getString("safebrowsing.notAForgeryButton.accessKey"),
2592             callback: function() {
2593               openUILinkIn(safebrowsing.getReportURL('Error'), 'tab');
2594             }
2595           };
2596         }
2598         let notificationBox = gBrowser.getNotificationBox();
2599         let value = "blocked-badware-page";
2601         let previousNotification = notificationBox.getNotificationWithValue(value);
2602         if (previousNotification)
2603           notificationBox.removeNotification(previousNotification);
2605         notificationBox.appendNotification(
2606           title,
2607           value,
2608           "chrome://global/skin/icons/blacklist_favicon.png",
2609           notificationBox.PRIORITY_CRITICAL_HIGH,
2610           buttons
2611         );
2612       }
2613     }
2614     else if (/^about:privatebrowsing/.test(errorDoc.documentURI)) {
2615       if (ot == errorDoc.getElementById("startPrivateBrowsing")) {
2616         gPrivateBrowsingUI.toggleMode();
2617       }
2618     }
2622  * Re-direct the browser to a known-safe page.  This function is
2623  * used when, for example, the user browses to a known malware page
2624  * and is presented with about:blocked.  The "Get me out of here!"
2625  * button should take the user to the default start page so that even
2626  * when their own homepage is infected, we can get them somewhere safe.
2627  */
2628 function getMeOutOfHere() {
2629   // Get the start page from the *default* pref branch, not the user's
2630   var prefs = Cc["@mozilla.org/preferences-service;1"]
2631              .getService(Ci.nsIPrefService).getDefaultBranch(null);
2632   var url = "about:blank";
2633   try {
2634     url = prefs.getComplexValue("browser.startup.homepage",
2635                                 Ci.nsIPrefLocalizedString).data;
2636     // If url is a pipe-delimited set of pages, just take the first one.
2637     if (url.indexOf("|") != -1)
2638       url = url.split("|")[0];
2639   } catch(e) {
2640     Components.utils.reportError("Couldn't get homepage pref: " + e);
2641   }
2642   content.location = url;
2645 function BrowserFullScreen()
2647   window.fullScreen = !window.fullScreen;
2650 function onFullScreen()
2652   FullScreen.toggle();
2655 function getWebNavigation()
2657   try {
2658     return gBrowser.webNavigation;
2659   } catch (e) {
2660     return null;
2661   }
2664 function BrowserReloadWithFlags(reloadFlags) {
2665   /* First, we'll try to use the session history object to reload so
2666    * that framesets are handled properly. If we're in a special
2667    * window (such as view-source) that has no session history, fall
2668    * back on using the web navigation's reload method.
2669    */
2671   var webNav = getWebNavigation();
2672   try {
2673     var sh = webNav.sessionHistory;
2674     if (sh)
2675       webNav = sh.QueryInterface(nsIWebNavigation);
2676   } catch (e) {
2677   }
2679   try {
2680     webNav.reload(reloadFlags);
2681   } catch (e) {
2682   }
2685 var PrintPreviewListener = {
2686   _printPreviewTab: null,
2687   _tabBeforePrintPreview: null,
2689   getPrintPreviewBrowser: function () {
2690     if (!this._printPreviewTab) {
2691       this._tabBeforePrintPreview = gBrowser.selectedTab;
2692       this._printPreviewTab = gBrowser.loadOneTab("about:blank",
2693                                                   { inBackground: false });
2694       gBrowser.selectedTab = this._printPreviewTab;
2695     }
2696     return gBrowser.getBrowserForTab(this._printPreviewTab);
2697   },
2698   getSourceBrowser: function () {
2699     return this._tabBeforePrintPreview ?
2700       this._tabBeforePrintPreview.linkedBrowser : gBrowser.selectedBrowser;
2701   },
2702   getNavToolbox: function () {
2703     return gNavToolbox;
2704   },
2705   onEnter: function () {
2706     gInPrintPreviewMode = true;
2707     this._toggleAffectedChrome();
2708   },
2709   onExit: function () {
2710     gBrowser.selectedTab = this._tabBeforePrintPreview;
2711     this._tabBeforePrintPreview = null;
2712     gBrowser.removeTab(this._printPreviewTab);
2713     this._printPreviewTab = null;
2714     gInPrintPreviewMode = false;
2715     this._toggleAffectedChrome();
2716   },
2717   _toggleAffectedChrome: function () {
2718 #ifdef MENUBAR_CAN_AUTOHIDE
2719     updateAppButtonDisplay();
2720 #endif
2722     gNavToolbox.hidden = gInPrintPreviewMode;
2724     if (gInPrintPreviewMode)
2725       this._hideChrome();
2726     else
2727       this._showChrome();
2729     if (this._chromeState.sidebarOpen)
2730       toggleSidebar(this._sidebarCommand);
2731   },
2732   _hideChrome: function () {
2733     this._chromeState = {};
2735     var sidebar = document.getElementById("sidebar-box");
2736     this._chromeState.sidebarOpen = !sidebar.hidden;
2737     this._sidebarCommand = sidebar.getAttribute("sidebarcommand");
2739     var notificationBox = gBrowser.getNotificationBox();
2740     this._chromeState.notificationsOpen = !notificationBox.notificationsHidden;
2741     notificationBox.notificationsHidden = true;
2743     document.getElementById("sidebar").setAttribute("src", "about:blank");
2744     var statusbar = document.getElementById("status-bar");
2745     this._chromeState.statusbarOpen = !statusbar.hidden;
2746     statusbar.hidden = true;
2748     this._chromeState.findOpen = gFindBarInitialized && !gFindBar.hidden;
2749     if (gFindBarInitialized)
2750       gFindBar.close();
2751   },
2752   _showChrome: function () {
2753     if (this._chromeState.notificationsOpen)
2754       gBrowser.getNotificationBox().notificationsHidden = false;
2756     if (this._chromeState.statusbarOpen)
2757       document.getElementById("status-bar").hidden = false;
2759     if (this._chromeState.findOpen)
2760       gFindBar.open();
2761   }
2764 function getMarkupDocumentViewer()
2766   return gBrowser.markupDocumentViewer;
2770  * Content area tooltip.
2771  * XXX - this must move into XBL binding/equiv! Do not want to pollute
2772  *       browser.js with functionality that can be encapsulated into
2773  *       browser widget. TEMPORARY!
2775  * NOTE: Any changes to this routine need to be mirrored in DefaultTooltipTextProvider::GetNodeText()
2776  *       (located in mozilla/embedding/browser/webBrowser/nsDocShellTreeOwner.cpp)
2777  *       which performs the same function, but for embedded clients that
2778  *       don't use a XUL/JS layer. It is important that the logic of
2779  *       these two routines be kept more or less in sync.
2780  *       (pinkerton)
2781  **/
2782 function FillInHTMLTooltip(tipElement)
2784   var retVal = false;
2785   // Don't show the tooltip if the tooltip node is a XUL element or a document.
2786   if (tipElement.namespaceURI == "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" ||
2787       !tipElement.ownerDocument)
2788     return retVal;
2790   const XLinkNS = "http://www.w3.org/1999/xlink";
2793   var titleText = null;
2794   var XLinkTitleText = null;
2795   var SVGTitleText = null;
2796 #ifdef MOZ_SVG
2797   var lookingForSVGTitle = true;
2798 #else
2799   var lookingForSVGTitle = false;
2800 #endif // MOZ_SVG
2801   var direction = tipElement.ownerDocument.dir;
2803   while (!titleText && !XLinkTitleText && !SVGTitleText && tipElement) {
2804     if (tipElement.nodeType == Node.ELEMENT_NODE) {
2805       titleText = tipElement.getAttribute("title");
2806       if ((tipElement instanceof HTMLAnchorElement && tipElement.href) ||
2807           (tipElement instanceof HTMLAreaElement && tipElement.href) ||
2808           (tipElement instanceof HTMLLinkElement && tipElement.href)
2809 #ifdef MOZ_SVG
2810           || (tipElement instanceof SVGAElement && tipElement.hasAttributeNS(XLinkNS, "href"))
2811 #endif // MOZ_SVG
2812           ) {
2813         XLinkTitleText = tipElement.getAttributeNS(XLinkNS, "title");
2814       }
2815       if (lookingForSVGTitle &&
2816           !(tipElement instanceof SVGElement &&
2817             tipElement.parentNode instanceof SVGElement &&
2818             !(tipElement.parentNode instanceof SVGForeignObjectElement))) {
2819         lookingForSVGTitle = false;
2820       }
2821       if (lookingForSVGTitle) {
2822         let length = tipElement.childNodes.length;
2823         for (let i = 0; i < length; i++) {
2824           let childNode = tipElement.childNodes[i];
2825           if (childNode instanceof SVGTitleElement) {
2826             SVGTitleText = childNode.textContent;
2827             break;
2828           }
2829         }
2830       }
2831       var defView = tipElement.ownerDocument.defaultView;
2832       // XXX Work around bug 350679:
2833       // "Tooltips can be fired in documents with no view".
2834       if (!defView)
2835         return retVal;
2836       direction = defView.getComputedStyle(tipElement, "")
2837         .getPropertyValue("direction");
2838     }
2839     tipElement = tipElement.parentNode;
2840   }
2842   var tipNode = document.getElementById("aHTMLTooltip");
2843   tipNode.style.direction = direction;
2845   [titleText, XLinkTitleText, SVGTitleText].forEach(function (t) {
2846     if (t && /\S/.test(t)) {
2848       // Per HTML 4.01 6.2 (CDATA section), literal CRs and tabs should be
2849       // replaced with spaces, and LFs should be removed entirely.
2850       // XXX Bug 322270: We don't preserve the result of entities like &#13;,
2851       // which should result in a line break in the tooltip, because we can't
2852       // distinguish that from a literal character in the source by this point.
2853       t = t.replace(/[\r\t]/g, ' ');
2854       t = t.replace(/\n/g, '');
2856       tipNode.setAttribute("label", t);
2857       retVal = true;
2858     }
2859   });
2861   return retVal;
2864 var browserDragAndDrop = {
2865   canDropLink: function (aEvent) Services.droppedLinkHandler.canDropLink(aEvent, true),
2867   dragOver: function (aEvent, statusString)
2868   {
2869     if (this.canDropLink(aEvent)) {
2870       aEvent.preventDefault();
2872       if (statusString) {
2873         var statusTextFld = document.getElementById("statusbar-display");
2874         statusTextFld.label = gNavigatorBundle.getString(statusString);
2875       }
2876     }
2877   },
2879   drop: function (aEvent, aName) Services.droppedLinkHandler.dropLink(aEvent, aName)
2882 var proxyIconDNDObserver = {
2883   onDragStart: function (aEvent, aXferData, aDragAction)
2884     {
2885       if (gProxyFavIcon.getAttribute("pageproxystate") != "valid")
2886         return;
2888       var value = content.location.href;
2889       var urlString = value + "\n" + content.document.title;
2890       var htmlString = "<a href=\"" + value + "\">" + value + "</a>";
2892       var dt = aEvent.dataTransfer;
2893       dt.setData("text/x-moz-url", urlString);
2894       dt.setData("text/uri-list", value);
2895       dt.setData("text/plain", value);
2896       dt.setData("text/html", htmlString);
2897     }
2900 var homeButtonObserver = {
2901   onDrop: function (aEvent)
2902     {
2903       setTimeout(openHomeDialog, 0, browserDragAndDrop.drop(aEvent, { }));
2904     },
2906   onDragOver: function (aEvent)
2907     {
2908       browserDragAndDrop.dragOver(aEvent, "droponhomebutton");
2909       aEvent.dropEffect = "link";
2910     },
2911   onDragLeave: function (aEvent)
2912     {
2913       var statusTextFld = document.getElementById("statusbar-display");
2914       statusTextFld.label = "";
2915     }
2918 function openHomeDialog(aURL)
2920   var promptTitle = gNavigatorBundle.getString("droponhometitle");
2921   var promptMsg   = gNavigatorBundle.getString("droponhomemsg");
2922   var pressedVal  = Services.prompt.confirmEx(window, promptTitle, promptMsg,
2923                           Services.prompt.STD_YES_NO_BUTTONS,
2924                           null, null, null, null, {value:0});
2926   if (pressedVal == 0) {
2927     try {
2928       var str = Components.classes["@mozilla.org/supports-string;1"]
2929                           .createInstance(Components.interfaces.nsISupportsString);
2930       str.data = aURL;
2931       gPrefService.setComplexValue("browser.startup.homepage",
2932                                    Components.interfaces.nsISupportsString, str);
2933       var homeButton = document.getElementById("home-button");
2934       homeButton.setAttribute("tooltiptext", aURL);
2935     } catch (ex) {
2936       dump("Failed to set the home page.\n"+ex+"\n");
2937     }
2938   }
2941 var bookmarksButtonObserver = {
2942   onDrop: function (aEvent)
2943   {
2944     let name = { };
2945     let url = browserDragAndDrop.drop(aEvent, name);
2946     try {
2947       PlacesUIUtils.showMinimalAddBookmarkUI(makeURI(url), name);
2948     } catch(ex) { }
2949   },
2951   onDragOver: function (aEvent)
2952   {
2953     browserDragAndDrop.dragOver(aEvent, "droponbookmarksbutton");
2954     aEvent.dropEffect = "link";
2955   },
2957   onDragLeave: function (aEvent)
2958   {
2959     var statusTextFld = document.getElementById("statusbar-display");
2960     statusTextFld.label = "";
2961   }
2964 var newTabButtonObserver = {
2965   onDragOver: function (aEvent)
2966   {
2967     browserDragAndDrop.dragOver(aEvent, "droponnewtabbutton");
2968   },
2970   onDragLeave: function (aEvent)
2971   {
2972     var statusTextFld = document.getElementById("statusbar-display");
2973     statusTextFld.label = "";
2974   },
2976   onDrop: function (aEvent)
2977   {
2978     let url = browserDragAndDrop.drop(aEvent, { });
2979     var postData = {};
2980     url = getShortcutOrURI(url, postData);
2981     if (url) {
2982       // allow third-party services to fixup this URL
2983       openNewTabWith(url, null, postData.value, aEvent, true);
2984     }
2985   }
2988 var newWindowButtonObserver = {
2989   onDragOver: function (aEvent)
2990   {
2991     browserDragAndDrop.dragOver(aEvent, "droponnewwindowbutton");
2992   },
2993   onDragLeave: function (aEvent)
2994   {
2995     var statusTextFld = document.getElementById("statusbar-display");
2996     statusTextFld.label = "";
2997   },
2998   onDrop: function (aEvent)
2999   {
3000     let url = browserDragAndDrop.drop(aEvent, { });
3001     var postData = {};
3002     url = getShortcutOrURI(url, postData);
3003     if (url) {
3004       // allow third-party services to fixup this URL
3005       openNewWindowWith(url, null, postData.value, true);
3006     }
3007   }
3010 var DownloadsButtonDNDObserver = {
3011   onDragOver: function (aEvent)
3012   {
3013     var statusTextFld = document.getElementById("statusbar-display");
3014     statusTextFld.label = gNavigatorBundle.getString("dropondownloadsbutton");
3015     var types = aEvent.dataTransfer.types;
3016     if (types.contains("text/x-moz-url") ||
3017         types.contains("text/uri-list") ||
3018         types.contains("text/plain"))
3019       aEvent.preventDefault();
3020   },
3022   onDragLeave: function (aEvent)
3023   {
3024     var statusTextFld = document.getElementById("statusbar-display");
3025     statusTextFld.label = "";
3026   },
3028   onDrop: function (aEvent)
3029   {
3030     let name = { };
3031     let url = browserDragAndDrop.drop(aEvent, name);
3032     if (url)
3033       saveURL(url, name, null, true, true);
3034   }
3037 const DOMLinkHandler = {
3038   handleEvent: function (event) {
3039     switch (event.type) {
3040       case "DOMLinkAdded":
3041         this.onLinkAdded(event);
3042         break;
3043     }
3044   },
3045   onLinkAdded: function (event) {
3046     var link = event.originalTarget;
3047     var rel = link.rel && link.rel.toLowerCase();
3048     if (!link || !link.ownerDocument || !rel || !link.href)
3049       return;
3051     var feedAdded = false;
3052     var iconAdded = false;
3053     var searchAdded = false;
3054     var relStrings = rel.split(/\s+/);
3055     var rels = {};
3056     for (let i = 0; i < relStrings.length; i++)
3057       rels[relStrings[i]] = true;
3059     for (let relVal in rels) {
3060       switch (relVal) {
3061         case "feed":
3062         case "alternate":
3063           if (!feedAdded) {
3064             if (!rels.feed && rels.alternate && rels.stylesheet)
3065               break;
3067             if (isValidFeed(link, link.ownerDocument.nodePrincipal, rels.feed)) {
3068               FeedHandler.addFeed(link, link.ownerDocument);
3069               feedAdded = true;
3070             }
3071           }
3072           break;
3073         case "icon":
3074           if (!iconAdded) {
3075             if (!gPrefService.getBoolPref("browser.chrome.site_icons"))
3076               break;
3078             var targetDoc = link.ownerDocument;
3079             var uri = makeURI(link.href, targetDoc.characterSet);
3081             if (gBrowser.isFailedIcon(uri))
3082               break;
3084             // Verify that the load of this icon is legal.
3085             // error pages can load their favicon, to be on the safe side,
3086             // only allow chrome:// favicons
3087             const aboutNeterr = /^about:neterror\?/;
3088             const aboutBlocked = /^about:blocked\?/;
3089             const aboutCert = /^about:certerror\?/;
3090             if (!(aboutNeterr.test(targetDoc.documentURI) ||
3091                   aboutBlocked.test(targetDoc.documentURI) ||
3092                   aboutCert.test(targetDoc.documentURI)) ||
3093                 !uri.schemeIs("chrome")) {
3094               var ssm = Cc["@mozilla.org/scriptsecuritymanager;1"].
3095                         getService(Ci.nsIScriptSecurityManager);
3096               try {
3097                 ssm.checkLoadURIWithPrincipal(targetDoc.nodePrincipal, uri,
3098                                               Ci.nsIScriptSecurityManager.DISALLOW_SCRIPT);
3099               } catch(e) {
3100                 break;
3101               }
3102             }
3104             try {
3105               var contentPolicy = Cc["@mozilla.org/layout/content-policy;1"].
3106                                   getService(Ci.nsIContentPolicy);
3107             } catch(e) {
3108               break; // Refuse to load if we can't do a security check.
3109             }
3111             // Security says okay, now ask content policy
3112             if (contentPolicy.shouldLoad(Ci.nsIContentPolicy.TYPE_IMAGE,
3113                                          uri, targetDoc.documentURIObject,
3114                                          link, link.type, null)
3115                                          != Ci.nsIContentPolicy.ACCEPT)
3116               break;
3118             var browserIndex = gBrowser.getBrowserIndexForDocument(targetDoc);
3119             // no browser? no favicon.
3120             if (browserIndex == -1)
3121               break;
3123             let tab = gBrowser.tabs[browserIndex];
3124             gBrowser.setIcon(tab, link.href);
3125             iconAdded = true;
3126           }
3127           break;
3128         case "search":
3129           if (!searchAdded) {
3130             var type = link.type && link.type.toLowerCase();
3131             type = type.replace(/^\s+|\s*(?:;.*)?$/g, "");
3133             if (type == "application/opensearchdescription+xml" && link.title &&
3134                 /^(?:https?|ftp):/i.test(link.href) &&
3135                 !gPrivateBrowsingUI.privateBrowsingEnabled) {
3136               var engine = { title: link.title, href: link.href };
3137               BrowserSearch.addEngine(engine, link.ownerDocument);
3138               searchAdded = true;
3139             }
3140           }
3141           break;
3142       }
3143     }
3144   }
3147 const BrowserSearch = {
3148   addEngine: function(engine, targetDoc) {
3149     if (!this.searchBar)
3150       return;
3152     var browser = gBrowser.getBrowserForDocument(targetDoc);
3153     // ignore search engines from subframes (see bug 479408)
3154     if (!browser)
3155       return;
3157     // Check to see whether we've already added an engine with this title
3158     if (browser.engines) {
3159       if (browser.engines.some(function (e) e.title == engine.title))
3160         return;
3161     }
3163     // Append the URI and an appropriate title to the browser data.
3164     // Use documentURIObject in the check for shouldLoadFavIcon so that we
3165     // do the right thing with about:-style error pages.  Bug 453442
3166     var iconURL = null;
3167     if (gBrowser.shouldLoadFavIcon(targetDoc.documentURIObject))
3168       iconURL = targetDoc.documentURIObject.prePath + "/favicon.ico";
3170     var hidden = false;
3171     // If this engine (identified by title) is already in the list, add it
3172     // to the list of hidden engines rather than to the main list.
3173     // XXX This will need to be changed when engines are identified by URL;
3174     // see bug 335102.
3175     if (Services.search.getEngineByName(engine.title))
3176       hidden = true;
3178     var engines = (hidden ? browser.hiddenEngines : browser.engines) || [];
3180     engines.push({ uri: engine.href,
3181                    title: engine.title,
3182                    icon: iconURL });
3184     if (hidden)
3185       browser.hiddenEngines = engines;
3186     else {
3187       browser.engines = engines;
3188       if (browser == gBrowser.selectedBrowser)
3189         this.updateSearchButton();
3190     }
3191   },
3193   /**
3194    * Update the browser UI to show whether or not additional engines are
3195    * available when a page is loaded or the user switches tabs to a page that
3196    * has search engines.
3197    */
3198   updateSearchButton: function() {
3199     var searchBar = this.searchBar;
3201     // The search bar binding might not be applied even though the element is
3202     // in the document (e.g. when the navigation toolbar is hidden), so check
3203     // for .searchButton specifically.
3204     if (!searchBar || !searchBar.searchButton)
3205       return;
3207     var engines = gBrowser.selectedBrowser.engines;
3208     if (engines && engines.length > 0)
3209       searchBar.searchButton.setAttribute("addengines", "true");
3210     else
3211       searchBar.searchButton.removeAttribute("addengines");
3212   },
3214   /**
3215    * Gives focus to the search bar, if it is present on the toolbar, or loads
3216    * the default engine's search form otherwise. For Mac, opens a new window
3217    * or focuses an existing window, if necessary.
3218    */
3219   webSearch: function BrowserSearch_webSearch() {
3220 #ifdef XP_MACOSX
3221     if (window.location.href != getBrowserURL()) {
3222       var win = getTopWin();
3223       if (win) {
3224         // If there's an open browser window, it should handle this command
3225         win.focus()
3226         win.BrowserSearch.webSearch();
3227       } else {
3228         // If there are no open browser windows, open a new one
3230         // This needs to be in a timeout so that we don't end up refocused
3231         // in the url bar
3232         function webSearchCallback() {
3233           setTimeout(BrowserSearch.webSearch, 0);
3234         }
3236         win = window.openDialog("chrome://browser/content/", "_blank",
3237                                 "chrome,all,dialog=no", "about:blank");
3238         win.addEventListener("load", webSearchCallback, false);
3239       }
3240       return;
3241     }
3242 #endif
3243     var searchBar = this.searchBar;
3244     if (searchBar && window.fullScreen)
3245       FullScreen.mouseoverToggle(true);
3247     if (isElementVisible(searchBar)) {
3248       searchBar.select();
3249       searchBar.focus();
3250     } else {
3251       openUILinkIn(Services.search.defaultEngine.searchForm, "current");
3252     }
3253   },
3255   /**
3256    * Loads a search results page, given a set of search terms. Uses the current
3257    * engine if the search bar is visible, or the default engine otherwise.
3258    *
3259    * @param searchText
3260    *        The search terms to use for the search.
3261    *
3262    * @param useNewTab
3263    *        Boolean indicating whether or not the search should load in a new
3264    *        tab.
3265    */
3266   loadSearch: function BrowserSearch_search(searchText, useNewTab) {
3267     var engine;
3269     // If the search bar is visible, use the current engine, otherwise, fall
3270     // back to the default engine.
3271     if (isElementVisible(this.searchBar))
3272       engine = Services.search.currentEngine;
3273     else
3274       engine = Services.search.defaultEngine;
3276     var submission = engine.getSubmission(searchText); // HTML response
3278     // getSubmission can return null if the engine doesn't have a URL
3279     // with a text/html response type.  This is unlikely (since
3280     // SearchService._addEngineToStore() should fail for such an engine),
3281     // but let's be on the safe side.
3282     if (!submission)
3283       return;
3285     if (useNewTab) {
3286       gBrowser.loadOneTab(submission.uri.spec, {
3287                           postData: submission.postData,
3288                           relatedToCurrent: true});
3289     } else
3290       loadURI(submission.uri.spec, null, submission.postData, false);
3291   },
3293   /**
3294    * Returns the search bar element if it is present in the toolbar, null otherwise.
3295    */
3296   get searchBar() {
3297     return document.getElementById("searchbar");
3298   },
3300   loadAddEngines: function BrowserSearch_loadAddEngines() {
3301     var newWindowPref = gPrefService.getIntPref("browser.link.open_newwindow");
3302     var where = newWindowPref == 3 ? "tab" : "window";
3303     var regionBundle = document.getElementById("bundle_browser_region");
3304     var searchEnginesURL = formatURL("browser.search.searchEnginesURL", true);
3305     openUILinkIn(searchEnginesURL, where);
3306   }
3309 function FillHistoryMenu(aParent) {
3310   // Lazily add the hover listeners on first showing and never remove them
3311   if (!aParent.hasStatusListener) {
3312     // Show history item's uri in the status bar when hovering, and clear on exit
3313     aParent.addEventListener("DOMMenuItemActive", function(aEvent) {
3314       // Only the current page should have the checked attribute, so skip it
3315       if (!aEvent.target.hasAttribute("checked"))
3316         XULBrowserWindow.setOverLink(aEvent.target.getAttribute("uri"));
3317     }, false);
3318     aParent.addEventListener("DOMMenuItemInactive", function() {
3319       XULBrowserWindow.setOverLink("");
3320     }, false);
3322     aParent.hasStatusListener = true;
3323   }
3325   // Remove old entries if any
3326   var children = aParent.childNodes;
3327   for (var i = children.length - 1; i >= 0; --i) {
3328     if (children[i].hasAttribute("index"))
3329       aParent.removeChild(children[i]);
3330   }
3332   var webNav = getWebNavigation();
3333   var sessionHistory = webNav.sessionHistory;
3335   var count = sessionHistory.count;
3336   if (count <= 1) // don't display the popup for a single item
3337     return false;
3339   const MAX_HISTORY_MENU_ITEMS = 15;
3340   var index = sessionHistory.index;
3341   var half_length = Math.floor(MAX_HISTORY_MENU_ITEMS / 2);
3342   var start = Math.max(index - half_length, 0);
3343   var end = Math.min(start == 0 ? MAX_HISTORY_MENU_ITEMS : index + half_length + 1, count);
3344   if (end == count)
3345     start = Math.max(count - MAX_HISTORY_MENU_ITEMS, 0);
3347   var tooltipBack = gNavigatorBundle.getString("tabHistory.goBack");
3348   var tooltipCurrent = gNavigatorBundle.getString("tabHistory.current");
3349   var tooltipForward = gNavigatorBundle.getString("tabHistory.goForward");
3351   for (var j = end - 1; j >= start; j--) {
3352     let item = document.createElement("menuitem");
3353     let entry = sessionHistory.getEntryAtIndex(j, false);
3354     let uri = entry.URI.spec;
3356     item.setAttribute("uri", uri);
3357     item.setAttribute("label", entry.title || uri);
3358     item.setAttribute("index", j);
3360     if (j != index) {
3361       try {
3362         let iconURL = Cc["@mozilla.org/browser/favicon-service;1"]
3363                          .getService(Ci.nsIFaviconService)
3364                          .getFaviconForPage(entry.URI).spec;
3365         item.style.listStyleImage = "url(" + iconURL + ")";
3366       } catch (ex) {}
3367     }
3369     if (j < index) {
3370       item.className = "unified-nav-back menuitem-iconic menuitem-with-favicon";
3371       item.setAttribute("tooltiptext", tooltipBack);
3372     } else if (j == index) {
3373       item.setAttribute("type", "radio");
3374       item.setAttribute("checked", "true");
3375       item.className = "unified-nav-current";
3376       item.setAttribute("tooltiptext", tooltipCurrent);
3377     } else {
3378       item.className = "unified-nav-forward menuitem-iconic menuitem-with-favicon";
3379       item.setAttribute("tooltiptext", tooltipForward);
3380     }
3382     aParent.appendChild(item);
3383   }
3384   return true;
3387 function addToUrlbarHistory(aUrlToAdd) {
3388   if (aUrlToAdd &&
3389       aUrlToAdd.indexOf(" ") == -1 &&
3390       !/[\x00-\x1F]/.test(aUrlToAdd))
3391     PlacesUIUtils.markPageAsTyped(aUrlToAdd);
3394 function toJavaScriptConsole()
3396   toOpenWindowByType("global:console", "chrome://global/content/console.xul");
3399 function BrowserDownloadsUI()
3401   Cc["@mozilla.org/download-manager-ui;1"].
3402   getService(Ci.nsIDownloadManagerUI).show(window);
3405 function toOpenWindowByType(inType, uri, features)
3407   var topWindow = Services.wm.getMostRecentWindow(inType);
3409   if (topWindow)
3410     topWindow.focus();
3411   else if (features)
3412     window.open(uri, "_blank", features);
3413   else
3414     window.open(uri, "_blank", "chrome,extrachrome,menubar,resizable,scrollbars,status,toolbar");
3417 function OpenBrowserWindow()
3419   var charsetArg = new String();
3420   var handler = Components.classes["@mozilla.org/browser/clh;1"]
3421                           .getService(Components.interfaces.nsIBrowserHandler);
3422   var defaultArgs = handler.defaultArgs;
3423   var wintype = document.documentElement.getAttribute('windowtype');
3425   // if and only if the current window is a browser window and it has a document with a character
3426   // set, then extract the current charset menu setting from the current document and use it to
3427   // initialize the new browser window...
3428   var win;
3429   if (window && (wintype == "navigator:browser") && window.content && window.content.document)
3430   {
3431     var DocCharset = window.content.document.characterSet;
3432     charsetArg = "charset="+DocCharset;
3434     //we should "inherit" the charset menu setting in a new window
3435     win = window.openDialog("chrome://browser/content/", "_blank", "chrome,all,dialog=no", defaultArgs, charsetArg);
3436   }
3437   else // forget about the charset information.
3438   {
3439     win = window.openDialog("chrome://browser/content/", "_blank", "chrome,all,dialog=no", defaultArgs);
3440   }
3442   return win;
3445 var gCustomizeSheet = false;
3446 // Returns a reference to the window in which the toolbar
3447 // customization document is loaded.
3448 function BrowserCustomizeToolbar()
3450   // Disable the toolbar context menu items
3451   var menubar = document.getElementById("main-menubar");
3452   for (var i = 0; i < menubar.childNodes.length; ++i)
3453     menubar.childNodes[i].setAttribute("disabled", true);
3455   var cmd = document.getElementById("cmd_CustomizeToolbars");
3456   cmd.setAttribute("disabled", "true");
3458   var splitter = document.getElementById("urlbar-search-splitter");
3459   if (splitter)
3460     splitter.parentNode.removeChild(splitter);
3462   CombinedStopReload.uninit();
3464   PlacesToolbarHelper.customizeStart();
3465   BookmarksMenuButton.customizeStart();
3467   var customizeURL = "chrome://global/content/customizeToolbar.xul";
3468   gCustomizeSheet = getBoolPref("toolbar.customization.usesheet", false);
3470   if (gCustomizeSheet) {
3471     var sheetFrame = document.getElementById("customizeToolbarSheetIFrame");
3472     var panel = document.getElementById("customizeToolbarSheetPopup");
3473     sheetFrame.hidden = false;
3474     sheetFrame.toolbox = gNavToolbox;
3475     sheetFrame.panel = panel;
3477     // The document might not have been loaded yet, if this is the first time.
3478     // If it is already loaded, reload it so that the onload initialization code
3479     // re-runs.
3480     if (sheetFrame.getAttribute("src") == customizeURL)
3481       sheetFrame.contentWindow.location.reload()
3482     else
3483       sheetFrame.setAttribute("src", customizeURL);
3485     // Open the panel, but make it invisible until the iframe has loaded so
3486     // that the user doesn't see a white flash.
3487     panel.style.visibility = "hidden";
3488     gNavToolbox.addEventListener("beforecustomization", function () {
3489       gNavToolbox.removeEventListener("beforecustomization", arguments.callee, false);
3490       panel.style.removeProperty("visibility");
3491     }, false);
3492     panel.openPopup(gNavToolbox, "after_start", 0, 0);
3493     return sheetFrame.contentWindow;
3494   } else {
3495     return window.openDialog(customizeURL,
3496                              "CustomizeToolbar",
3497                              "chrome,titlebar,toolbar,location,resizable,dependent",
3498                              gNavToolbox);
3499   }
3502 function BrowserToolboxCustomizeDone(aToolboxChanged) {
3503   if (gCustomizeSheet) {
3504     document.getElementById("customizeToolbarSheetIFrame").hidden = true;
3505     document.getElementById("customizeToolbarSheetPopup").hidePopup();
3506   }
3508   // Update global UI elements that may have been added or removed
3509   if (aToolboxChanged) {
3510     gURLBar = document.getElementById("urlbar");
3512     gProxyFavIcon = document.getElementById("page-proxy-favicon");
3513     gHomeButton.updateTooltip();
3514     gIdentityHandler._cacheElements();
3515     window.XULBrowserWindow.init();
3517     var backForwardDropmarker = document.getElementById("back-forward-dropmarker");
3518     if (backForwardDropmarker)
3519       backForwardDropmarker.disabled =
3520         document.getElementById('Browser:Back').hasAttribute('disabled') &&
3521         document.getElementById('Browser:Forward').hasAttribute('disabled');
3523 #ifndef XP_MACOSX
3524     updateEditUIVisibility();
3525 #endif
3526   }
3528   PlacesToolbarHelper.customizeDone();
3529   BookmarksMenuButton.customizeDone();
3531   UpdateUrlbarSearchSplitterState();
3533   CombinedStopReload.init();
3535   // Update the urlbar
3536   if (gURLBar) {
3537     URLBarSetURI();
3538     XULBrowserWindow.asyncUpdateUI();
3539     PlacesStarButton.updateState();
3540   }
3542   // Re-enable parts of the UI we disabled during the dialog
3543   var menubar = document.getElementById("main-menubar");
3544   for (var i = 0; i < menubar.childNodes.length; ++i)
3545     menubar.childNodes[i].setAttribute("disabled", false);
3546   var cmd = document.getElementById("cmd_CustomizeToolbars");
3547   cmd.removeAttribute("disabled");
3549 #ifdef XP_MACOSX
3550   // make sure to re-enable click-and-hold
3551   if (!getBoolPref("ui.click_hold_context_menus", false))
3552     SetClickAndHoldHandlers();
3553 #endif
3555   // XXX Shouldn't have to do this, but I do
3556   if (!gCustomizeSheet)
3557     window.focus();
3560 function BrowserToolboxCustomizeChange() {
3561   gHomeButton.updatePersonalToolbarStyle();
3562   BookmarksMenuButton.customizeChange();
3563   allTabs.readPref();
3567  * Update the global flag that tracks whether or not any edit UI (the Edit menu,
3568  * edit-related items in the context menu, and edit-related toolbar buttons
3569  * is visible, then update the edit commands' enabled state accordingly.  We use
3570  * this flag to skip updating the edit commands on focus or selection changes
3571  * when no UI is visible to improve performance (including pageload performance,
3572  * since focus changes when you load a new page).
3574  * If UI is visible, we use goUpdateGlobalEditMenuItems to set the commands'
3575  * enabled state so the UI will reflect it appropriately.
3577  * If the UI isn't visible, we enable all edit commands so keyboard shortcuts
3578  * still work and just lazily disable them as needed when the user presses a
3579  * shortcut.
3581  * This doesn't work on Mac, since Mac menus flash when users press their
3582  * keyboard shortcuts, so edit UI is essentially always visible on the Mac,
3583  * and we need to always update the edit commands.  Thus on Mac this function
3584  * is a no op.
3585  */
3586 function updateEditUIVisibility()
3588 #ifndef XP_MACOSX
3589   let editMenuPopupState = document.getElementById("menu_EditPopup").state;
3590   let contextMenuPopupState = document.getElementById("contentAreaContextMenu").state;
3591   let placesContextMenuPopupState = document.getElementById("placesContext").state;
3593   // The UI is visible if the Edit menu is opening or open, if the context menu
3594   // is open, or if the toolbar has been customized to include the Cut, Copy,
3595   // or Paste toolbar buttons.
3596   gEditUIVisible = editMenuPopupState == "showing" ||
3597                    editMenuPopupState == "open" ||
3598                    contextMenuPopupState == "showing" ||
3599                    contextMenuPopupState == "open" ||
3600                    placesContextMenuPopupState == "showing" ||
3601                    placesContextMenuPopupState == "open" ||
3602                    document.getElementById("cut-button") ||
3603                    document.getElementById("copy-button") ||
3604                    document.getElementById("paste-button") ? true : false;
3606   // If UI is visible, update the edit commands' enabled state to reflect
3607   // whether or not they are actually enabled for the current focus/selection.
3608   if (gEditUIVisible)
3609     goUpdateGlobalEditMenuItems();
3611   // Otherwise, enable all commands, so that keyboard shortcuts still work,
3612   // then lazily determine their actual enabled state when the user presses
3613   // a keyboard shortcut.
3614   else {
3615     goSetCommandEnabled("cmd_undo", true);
3616     goSetCommandEnabled("cmd_redo", true);
3617     goSetCommandEnabled("cmd_cut", true);
3618     goSetCommandEnabled("cmd_copy", true);
3619     goSetCommandEnabled("cmd_paste", true);
3620     goSetCommandEnabled("cmd_selectAll", true);
3621     goSetCommandEnabled("cmd_delete", true);
3622     goSetCommandEnabled("cmd_switchTextDirection", true);
3623   }
3624 #endif
3627 var FullScreen =
3629   _XULNS: "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
3630   toggle: function()
3631   {
3632     // show/hide all menubars, toolbars, and statusbars (except the full screen toolbar)
3633     this.showXULChrome("toolbar", window.fullScreen);
3634     this.showXULChrome("statusbar", window.fullScreen);
3635     document.getElementById("View:FullScreen").setAttribute("checked", !window.fullScreen);
3637     if (!window.fullScreen) {
3638       // Add a tiny toolbar to receive mouseover and dragenter events, and provide affordance.
3639       // This will help simulate the "collapse" metaphor while also requiring less code and
3640       // events than raw listening of mouse coords.
3641       let fullScrToggler = document.getElementById("fullscr-toggler");
3642       if (!fullScrToggler) {
3643         fullScrToggler = document.createElement("hbox");
3644         fullScrToggler.id = "fullscr-toggler";
3645         fullScrToggler.collapsed = true;
3646         gNavToolbox.parentNode.insertBefore(fullScrToggler, gNavToolbox.nextSibling);
3647       }
3648       fullScrToggler.addEventListener("mouseover", this._expandCallback, false);
3649       fullScrToggler.addEventListener("dragenter", this._expandCallback, false);
3651       if (gPrefService.getBoolPref("browser.fullscreen.autohide"))
3652         gBrowser.mPanelContainer.addEventListener("mousemove",
3653                                                   this._collapseCallback, false);
3655       document.addEventListener("keypress", this._keyToggleCallback, false);
3656       document.addEventListener("popupshown", this._setPopupOpen, false);
3657       document.addEventListener("popuphidden", this._setPopupOpen, false);
3658       this._shouldAnimate = true;
3659       this.mouseoverToggle(false);
3661       // Autohide prefs
3662       gPrefService.addObserver("browser.fullscreen", this, false);
3663     }
3664     else {
3665       // The user may quit fullscreen during an animation
3666       clearInterval(this._animationInterval);
3667       clearTimeout(this._animationTimeout);
3668       gNavToolbox.style.marginTop = "0px";
3669       if (this._isChromeCollapsed)
3670         this.mouseoverToggle(true);
3671       this._isAnimating = false;
3672       // This is needed if they use the context menu to quit fullscreen
3673       this._isPopupOpen = false;
3675       this.cleanup();
3676     }
3677   },
3679   cleanup: function () {
3680     if (window.fullScreen) {
3681       gBrowser.mPanelContainer.removeEventListener("mousemove",
3682                                                    this._collapseCallback, false);
3683       document.removeEventListener("keypress", this._keyToggleCallback, false);
3684       document.removeEventListener("popupshown", this._setPopupOpen, false);
3685       document.removeEventListener("popuphidden", this._setPopupOpen, false);
3686       gPrefService.removeObserver("browser.fullscreen", this);
3688       let fullScrToggler = document.getElementById("fullscr-toggler");
3689       if (fullScrToggler) {
3690         fullScrToggler.removeEventListener("mouseover", this._expandCallback, false);
3691         fullScrToggler.removeEventListener("dragenter", this._expandCallback, false);
3692       }
3693     }
3694   },
3696   observe: function(aSubject, aTopic, aData)
3697   {
3698     if (aData == "browser.fullscreen.autohide") {
3699       if (gPrefService.getBoolPref("browser.fullscreen.autohide")) {
3700         gBrowser.mPanelContainer.addEventListener("mousemove",
3701                                                   this._collapseCallback, false);
3702       }
3703       else {
3704         gBrowser.mPanelContainer.removeEventListener("mousemove",
3705                                                      this._collapseCallback, false);
3706       }
3707     }
3708   },
3710   // Event callbacks
3711   _expandCallback: function()
3712   {
3713     FullScreen.mouseoverToggle(true);
3714   },
3715   _collapseCallback: function()
3716   {
3717     FullScreen.mouseoverToggle(false);
3718   },
3719   _keyToggleCallback: function(aEvent)
3720   {
3721     // if we can use the keyboard (eg Ctrl+L or Ctrl+E) to open the toolbars, we
3722     // should provide a way to collapse them too.
3723     if (aEvent.keyCode == aEvent.DOM_VK_ESCAPE) {
3724       FullScreen._shouldAnimate = false;
3725       FullScreen.mouseoverToggle(false, true);
3726     }
3727     // F6 is another shortcut to the address bar, but its not covered in OpenLocation()
3728     else if (aEvent.keyCode == aEvent.DOM_VK_F6)
3729       FullScreen.mouseoverToggle(true);
3730   },
3732   // Checks whether we are allowed to collapse the chrome
3733   _isPopupOpen: false,
3734   _isChromeCollapsed: false,
3735   _safeToCollapse: function(forceHide)
3736   {
3737     if (!gPrefService.getBoolPref("browser.fullscreen.autohide"))
3738       return false;
3740     // a popup menu is open in chrome: don't collapse chrome
3741     if (!forceHide && this._isPopupOpen)
3742       return false;
3744     // a textbox in chrome is focused (location bar anyone?): don't collapse chrome
3745     if (document.commandDispatcher.focusedElement &&
3746         document.commandDispatcher.focusedElement.ownerDocument == document &&
3747         document.commandDispatcher.focusedElement.localName == "input") {
3748       if (forceHide)
3749         // hidden textboxes that still have focus are bad bad bad
3750         document.commandDispatcher.focusedElement.blur();
3751       else
3752         return false;
3753     }
3754     return true;
3755   },
3757   _setPopupOpen: function(aEvent)
3758   {
3759     // Popups should only veto chrome collapsing if they were opened when the chrome was not collapsed.
3760     // Otherwise, they would not affect chrome and the user would expect the chrome to go away.
3761     // e.g. we wouldn't want the autoscroll icon firing this event, so when the user
3762     // toggles chrome when moving mouse to the top, it doesn't go away again.
3763     if (aEvent.type == "popupshown" && !FullScreen._isChromeCollapsed &&
3764         aEvent.target.localName != "tooltip" && aEvent.target.localName != "window")
3765       FullScreen._isPopupOpen = true;
3766     else if (aEvent.type == "popuphidden" && aEvent.target.localName != "tooltip" &&
3767              aEvent.target.localName != "window")
3768       FullScreen._isPopupOpen = false;
3769   },
3771   // Autohide helpers for the context menu item
3772   getAutohide: function(aItem)
3773   {
3774     aItem.setAttribute("checked", gPrefService.getBoolPref("browser.fullscreen.autohide"));
3775   },
3776   setAutohide: function()
3777   {
3778     gPrefService.setBoolPref("browser.fullscreen.autohide", !gPrefService.getBoolPref("browser.fullscreen.autohide"));
3779   },
3781   // Animate the toolbars disappearing
3782   _shouldAnimate: true,
3783   _isAnimating: false,
3784   _animationTimeout: null,
3785   _animationInterval: null,
3786   _animateUp: function()
3787   {
3788     // check again, the user may have done something before the animation was due to start
3789     if (!window.fullScreen || !FullScreen._safeToCollapse(false)) {
3790       FullScreen._isAnimating = false;
3791       FullScreen._shouldAnimate = true;
3792       return;
3793     }
3795     var animateFrameAmount = 2;
3796     function animateUpFrame() {
3797       animateFrameAmount *= 2;
3798       if (animateFrameAmount >= gNavToolbox.boxObject.height) {
3799         // We've animated enough
3800         clearInterval(FullScreen._animationInterval);
3801         gNavToolbox.style.marginTop = "0px";
3802         FullScreen._isAnimating = false;
3803         FullScreen._shouldAnimate = false; // Just to make sure
3804         FullScreen.mouseoverToggle(false);
3805         return;
3806       }
3807       gNavToolbox.style.marginTop = (animateFrameAmount * -1) + "px";
3808     }
3810     FullScreen._animationInterval = setInterval(animateUpFrame, 70);
3811   },
3813   mouseoverToggle: function(aShow, forceHide)
3814   {
3815     // Don't do anything if:
3816     // a) we're already in the state we want,
3817     // b) we're animating and will become collapsed soon, or
3818     // c) we can't collapse because it would be undesirable right now
3819     if (aShow != this._isChromeCollapsed || (!aShow && this._isAnimating) ||
3820         (!aShow && !this._safeToCollapse(forceHide)))
3821       return;
3823     // browser.fullscreen.animateUp
3824     // 0 - never animate up
3825     // 1 - animate only for first collapse after entering fullscreen (default for perf's sake)
3826     // 2 - animate every time it collapses
3827     if (gPrefService.getIntPref("browser.fullscreen.animateUp") == 0)
3828       this._shouldAnimate = false;
3830     if (!aShow && this._shouldAnimate) {
3831       this._isAnimating = true;
3832       this._shouldAnimate = false;
3833       this._animationTimeout = setTimeout(this._animateUp, 800);
3834       return;
3835     }
3837     // The chrome is collapsed so don't spam needless mousemove events
3838     if (aShow) {
3839       gBrowser.mPanelContainer.addEventListener("mousemove",
3840                                                 this._collapseCallback, false);
3841     }
3842     else {
3843       gBrowser.mPanelContainer.removeEventListener("mousemove",
3844                                                    this._collapseCallback, false);
3845     }
3847     // Hiding/collapsing the toolbox interferes with the tab bar's scrollbox,
3848     // so we just move it off-screen instead. See bug 430687.
3849     gNavToolbox.style.marginTop =
3850       aShow ? "" : -gNavToolbox.getBoundingClientRect().height + "px";
3852     document.getElementById("fullscr-toggler").collapsed = aShow;
3853     this._isChromeCollapsed = !aShow;
3854     if (gPrefService.getIntPref("browser.fullscreen.animateUp") == 2)
3855       this._shouldAnimate = true;
3856   },
3858   showXULChrome: function(aTag, aShow)
3859   {
3860     var els = document.getElementsByTagNameNS(this._XULNS, aTag);
3862     for (var i = 0; i < els.length; ++i) {
3863       // XXX don't interfere with previously collapsed toolbars
3864       if (els[i].getAttribute("fullscreentoolbar") == "true") {
3865         if (!aShow) {
3867           var toolbarMode = els[i].getAttribute("mode");
3868           if (toolbarMode != "text") {
3869             els[i].setAttribute("saved-mode", toolbarMode);
3870             els[i].setAttribute("saved-iconsize",
3871                                 els[i].getAttribute("iconsize"));
3872             els[i].setAttribute("mode", "icons");
3873             els[i].setAttribute("iconsize", "small");
3874           }
3876           // Give the main nav bar the fullscreen context menu, otherwise remove it
3877           // to prevent breakage
3878           els[i].setAttribute("saved-context",
3879                               els[i].getAttribute("context"));
3880           if (els[i].id == "nav-bar")
3881             els[i].setAttribute("context", "autohide-context");
3882           else
3883             els[i].removeAttribute("context");
3885           // Set the inFullscreen attribute to allow specific styling
3886           // in fullscreen mode
3887           els[i].setAttribute("inFullscreen", true);
3888         }
3889         else {
3890           function restoreAttr(attrName) {
3891             var savedAttr = "saved-" + attrName;
3892             if (els[i].hasAttribute(savedAttr)) {
3893               els[i].setAttribute(attrName, els[i].getAttribute(savedAttr));
3894               els[i].removeAttribute(savedAttr);
3895             }
3896           }
3898           restoreAttr("mode");
3899           restoreAttr("iconsize");
3900           restoreAttr("context");
3902           els[i].removeAttribute("inFullscreen");
3903         }
3904       } else {
3905         // use moz-collapsed so it doesn't persist hidden/collapsed,
3906         // so that new windows don't have missing toolbars
3907         if (aShow)
3908           els[i].removeAttribute("moz-collapsed");
3909         else
3910           els[i].setAttribute("moz-collapsed", "true");
3911       }
3912     }
3914     if (aShow) {
3915       gNavToolbox.removeAttribute("inFullscreen");
3916       document.documentElement.removeAttribute("inFullscreen");
3917     } else {
3918       gNavToolbox.setAttribute("inFullscreen", true);
3919       document.documentElement.setAttribute("inFullscreen", true);
3920     }
3922     var controls = document.getElementsByAttribute("fullscreencontrol", "true");
3923     for (var i = 0; i < controls.length; ++i)
3924       controls[i].hidden = aShow;
3925   }
3929  * Returns true if |aMimeType| is text-based, false otherwise.
3931  * @param aMimeType
3932  *        The MIME type to check.
3934  * If adding types to this function, please also check the similar
3935  * function in findbar.xml
3936  */
3937 function mimeTypeIsTextBased(aMimeType)
3939   return /^text\/|\+xml$/.test(aMimeType) ||
3940          aMimeType == "application/x-javascript" ||
3941          aMimeType == "application/javascript" ||
3942          aMimeType == "application/xml" ||
3943          aMimeType == "mozilla.application/cached-xul";
3946 var XULBrowserWindow = {
3947   // Stored Status, Link and Loading values
3948   status: "",
3949   defaultStatus: "",
3950   jsStatus: "",
3951   jsDefaultStatus: "",
3952   overLink: "",
3953   startTime: 0,
3954   statusText: "",
3955   isBusy: false,
3957   _progressCollapseTimer: 0,
3959   QueryInterface: function (aIID) {
3960     if (aIID.equals(Ci.nsIWebProgressListener) ||
3961         aIID.equals(Ci.nsIWebProgressListener2) ||
3962         aIID.equals(Ci.nsISupportsWeakReference) ||
3963         aIID.equals(Ci.nsIXULBrowserWindow) ||
3964         aIID.equals(Ci.nsISupports))
3965       return this;
3966     throw Cr.NS_NOINTERFACE;
3967   },
3969   get statusMeter () {
3970     delete this.statusMeter;
3971     return this.statusMeter = document.getElementById("statusbar-icon");
3972   },
3973   get stopCommand () {
3974     delete this.stopCommand;
3975     return this.stopCommand = document.getElementById("Browser:Stop");
3976   },
3977   get reloadCommand () {
3978     delete this.reloadCommand;
3979     return this.reloadCommand = document.getElementById("Browser:Reload");
3980   },
3981   get statusTextField () {
3982     delete this.statusTextField;
3983     return this.statusTextField = document.getElementById("statusbar-display");
3984   },
3985   get securityButton () {
3986     delete this.securityButton;
3987     return this.securityButton = document.getElementById("security-button");
3988   },
3989   get isImage () {
3990     delete this.isImage;
3991     return this.isImage = document.getElementById("isImage");
3992   },
3993   get _uriFixup () {
3994     delete this._uriFixup;
3995     return this._uriFixup = Cc["@mozilla.org/docshell/urifixup;1"]
3996                               .getService(Ci.nsIURIFixup);
3997   },
3999   init: function () {
4000     this.throbberElement = document.getElementById("navigator-throbber");
4002     // Initialize the security button's state and tooltip text.  Remember to reset
4003     // _hostChanged, otherwise onSecurityChange will short circuit.
4004     var securityUI = gBrowser.securityUI;
4005     this._hostChanged = true;
4006     this.onSecurityChange(null, null, securityUI.state);
4007   },
4009   destroy: function () {
4010     // XXXjag to avoid leaks :-/, see bug 60729
4011     delete this.throbberElement;
4012     delete this.statusMeter;
4013     delete this.stopCommand;
4014     delete this.reloadCommand;
4015     delete this.statusTextField;
4016     delete this.securityButton;
4017     delete this.statusText;
4018   },
4020   setJSStatus: function (status) {
4021     this.jsStatus = status;
4022     this.updateStatusField();
4023   },
4025   setJSDefaultStatus: function (status) {
4026     this.jsDefaultStatus = status;
4027     this.updateStatusField();
4028   },
4030   setDefaultStatus: function (status) {
4031     this.defaultStatus = status;
4032     this.updateStatusField();
4033   },
4035   setOverLink: function (link, b) {
4036     // Encode bidirectional formatting characters.
4037     // (RFC 3987 sections 3.2 and 4.1 paragraph 6)
4038     this.overLink = link.replace(/[\u200e\u200f\u202a\u202b\u202c\u202d\u202e]/g,
4039                                  encodeURIComponent);
4040     this.updateStatusField();
4041   },
4043   updateStatusField: function () {
4044     var text = this.overLink || this.status || this.jsStatus || this.jsDefaultStatus || this.defaultStatus;
4046     // check the current value so we don't trigger an attribute change
4047     // and cause needless (slow!) UI updates
4048     if (this.statusText != text) {
4049       this.statusTextField.label = text;
4050       this.statusText = text;
4051     }
4052   },
4054   onLinkIconAvailable: function (aIconURL) {
4055     if (gProxyFavIcon && gBrowser.userTypedValue === null)
4056       PageProxySetIcon(aIconURL); // update the favicon in the URL bar
4057   },
4059   onProgressChange: function (aWebProgress, aRequest,
4060                               aCurSelfProgress, aMaxSelfProgress,
4061                               aCurTotalProgress, aMaxTotalProgress) {
4062     // Check this._busyUI to be safe, because we don't want to update
4063     // the progress meter when restoring a page from bfcache.
4064     if (aMaxTotalProgress > 0 && this._busyUI) {
4065       // This is highly optimized.  Don't touch this code unless
4066       // you are intimately familiar with the cost of setting
4067       // attrs on XUL elements. -- hyatt
4068       let percentage = (aCurTotalProgress * 100) / aMaxTotalProgress;
4069       this.statusMeter.value = percentage;
4070     }
4071   },
4073   onProgressChange64: function (aWebProgress, aRequest,
4074                                 aCurSelfProgress, aMaxSelfProgress,
4075                                 aCurTotalProgress, aMaxTotalProgress) {
4076     return this.onProgressChange(aWebProgress, aRequest,
4077       aCurSelfProgress, aMaxSelfProgress, aCurTotalProgress,
4078       aMaxTotalProgress);
4079   },
4081   onStateChange: function (aWebProgress, aRequest, aStateFlags, aStatus) {
4082     const nsIWebProgressListener = Ci.nsIWebProgressListener;
4083     const nsIChannel = Ci.nsIChannel;
4084     if (aStateFlags & nsIWebProgressListener.STATE_START &&
4085         aStateFlags & nsIWebProgressListener.STATE_IS_NETWORK) {
4087       if (aRequest && aWebProgress.DOMWindow == content)
4088         this.startDocumentLoad(aRequest);
4090       this.isBusy = true;
4092       if (!(aStateFlags & nsIWebProgressListener.STATE_RESTORING)) {
4093         this._busyUI = true;
4095         // Turn the throbber on.
4096         if (this.throbberElement)
4097           this.throbberElement.setAttribute("busy", "true");
4099         // Turn the status meter on.
4100         this.statusMeter.value = 0;  // be sure to clear the progress bar
4101         if (this._progressCollapseTimer) {
4102           clearTimeout(this._progressCollapseTimer);
4103           this._progressCollapseTimer = 0;
4104         }
4105         else
4106           this.statusMeter.parentNode.collapsed = false;
4108         // XXX: This needs to be based on window activity...
4109         this.stopCommand.removeAttribute("disabled");
4110         CombinedStopReload.switchToStop();
4111       }
4112     }
4113     else if (aStateFlags & nsIWebProgressListener.STATE_STOP) {
4114       if (aStateFlags & nsIWebProgressListener.STATE_IS_NETWORK) {
4115         if (aWebProgress.DOMWindow == content) {
4116           if (aRequest)
4117             this.endDocumentLoad(aRequest, aStatus);
4118           if (!gBrowser.mTabbedMode && !gBrowser.getIcon())
4119             gBrowser.useDefaultIcon(gBrowser.selectedTab);
4120         }
4121       }
4123       // This (thanks to the filter) is a network stop or the last
4124       // request stop outside of loading the document, stop throbbers
4125       // and progress bars and such
4126       if (aRequest) {
4127         let msg = "";
4128         let location;
4129         // Get the URI either from a channel or a pseudo-object
4130         if (aRequest instanceof nsIChannel || "URI" in aRequest) {
4131           location = aRequest.URI;
4133           // For keyword URIs clear the user typed value since they will be changed into real URIs
4134           if (location.scheme == "keyword" && aWebProgress.DOMWindow == content)
4135             gBrowser.userTypedValue = null;
4137           if (location.spec != "about:blank") {
4138             switch (aStatus) {
4139               case Components.results.NS_BINDING_ABORTED:
4140                 msg = gNavigatorBundle.getString("nv_stopped");
4141                 break;
4142               case Components.results.NS_ERROR_NET_TIMEOUT:
4143                 msg = gNavigatorBundle.getString("nv_timeout");
4144                 break;
4145             }
4146           }
4147         }
4148         // If msg is false then we did not have an error (channel may have
4149         // been null, in the case of a stray image load).
4150         if (!msg && (!location || location.spec != "about:blank"))
4151           msg = gNavigatorBundle.getString("nv_done");
4153         this.status = "";
4154         this.setDefaultStatus(msg);
4156         // Disable menu entries for images, enable otherwise
4157         if (content.document && mimeTypeIsTextBased(content.document.contentType))
4158           this.isImage.removeAttribute('disabled');
4159         else
4160           this.isImage.setAttribute('disabled', 'true');
4161       }
4163       this.isBusy = false;
4165       if (this._busyUI) {
4166         this._busyUI = false;
4168         // Turn the progress meter and throbber off.
4169         this._progressCollapseTimer = setTimeout(function (self) {
4170           self.statusMeter.parentNode.collapsed = true;
4171           self._progressCollapseTimer = 0;
4172         }, 100, this);
4174         if (this.throbberElement)
4175           this.throbberElement.removeAttribute("busy");
4177         this.stopCommand.setAttribute("disabled", "true");
4178         CombinedStopReload.switchToReload(aRequest instanceof Ci.nsIRequest);
4179       }
4180     }
4181   },
4183   onLocationChange: function (aWebProgress, aRequest, aLocationURI) {
4184     var location = aLocationURI ? aLocationURI.spec : "";
4185     this._hostChanged = true;
4187     if (document.tooltipNode) {
4188       // Optimise for the common case
4189       if (aWebProgress.DOMWindow == content) {
4190         document.getElementById("aHTMLTooltip").hidePopup();
4191         document.tooltipNode = null;
4192       }
4193       else {
4194         for (let tooltipWindow =
4195                document.tooltipNode.ownerDocument.defaultView;
4196              tooltipWindow != tooltipWindow.parent;
4197              tooltipWindow = tooltipWindow.parent) {
4198           if (tooltipWindow == aWebProgress.DOMWindow) {
4199             document.getElementById("aHTMLTooltip").hidePopup();
4200             document.tooltipNode = null;
4201             break;
4202           }
4203         }
4204       }
4205     }
4207     // This code here does not compare uris exactly when determining
4208     // whether or not the message should be hidden since the message
4209     // may be prematurely hidden when an install is invoked by a click
4210     // on a link that looks like this:
4211     //
4212     // <a href="#" onclick="return install();">Install Foo</a>
4213     //
4214     // - which fires a onLocationChange message to uri + '#'...
4215     var selectedBrowser = gBrowser.selectedBrowser;
4216     if (selectedBrowser.lastURI) {
4217       let oldSpec = selectedBrowser.lastURI.spec;
4218       let oldIndexOfHash = oldSpec.indexOf("#");
4219       if (oldIndexOfHash != -1)
4220         oldSpec = oldSpec.substr(0, oldIndexOfHash);
4221       let newSpec = location;
4222       let newIndexOfHash = newSpec.indexOf("#");
4223       if (newIndexOfHash != -1)
4224         newSpec = newSpec.substr(0, newSpec.indexOf("#"));
4225       if (newSpec != oldSpec) {
4226         // Remove all the notifications, except for those which want to
4227         // persist across the first location change.
4228         let nBox = gBrowser.getNotificationBox(selectedBrowser);
4229         nBox.removeTransientNotifications();
4231         // Only need to call locationChange if the PopupNotifications object
4232         // for this window has already been initialized (i.e. its getter no
4233         // longer exists)
4234         if (!__lookupGetter__("PopupNotifications"))
4235           PopupNotifications.locationChange();
4236       }
4237     }
4239     // Disable menu entries for images, enable otherwise
4240     if (content.document && mimeTypeIsTextBased(content.document.contentType))
4241       this.isImage.removeAttribute('disabled');
4242     else
4243       this.isImage.setAttribute('disabled', 'true');
4245     this.setOverLink("", null);
4247     // We should probably not do this if the value has changed since the user
4248     // searched
4249     // Update urlbar only if a new page was loaded on the primary content area
4250     // Do not update urlbar if there was a subframe navigation
4252     var browser = gBrowser.selectedBrowser;
4253     if (aWebProgress.DOMWindow == content) {
4254       if ((location == "about:blank" && !content.opener) ||
4255           location == "") {  // Second condition is for new tabs, otherwise
4256                              // reload function is enabled until tab is refreshed.
4257         this.reloadCommand.setAttribute("disabled", "true");
4258       } else {
4259         this.reloadCommand.removeAttribute("disabled");
4260       }
4262       if (!gBrowser.mTabbedMode && aWebProgress.isLoadingDocument)
4263         gBrowser.setIcon(gBrowser.selectedTab, null);
4265       if (gURLBar) {
4266         // Strip off "wyciwyg://" and passwords for the location bar
4267         let uri = aLocationURI;
4268         try {
4269           uri = this._uriFixup.createExposableURI(uri);
4270         } catch (e) {}
4271         URLBarSetURI(uri);
4273         // Update starring UI
4274         PlacesStarButton.updateState();
4275       }
4276     }
4277     UpdateBackForwardCommands(gBrowser.webNavigation);
4279     if (gFindBarInitialized) {
4280       if (gFindBar.findMode != gFindBar.FIND_NORMAL) {
4281         // Close the Find toolbar if we're in old-style TAF mode
4282         gFindBar.close();
4283       }
4285       // XXXmano new-findbar, do something useful once it lands.
4286       // Of course, this is especially wrong with bfcache on...
4288       // fix bug 253793 - turn off highlight when page changes
4289       gFindBar.getElement("highlight").checked = false;      
4290     }
4292     // See bug 358202, when tabs are switched during a drag operation,
4293     // timers don't fire on windows (bug 203573)
4294     if (aRequest)
4295       setTimeout(function () { XULBrowserWindow.asyncUpdateUI(); }, 0);
4296     else
4297       this.asyncUpdateUI();
4298   },
4300   asyncUpdateUI: function () {
4301     FeedHandler.updateFeeds();
4302     BrowserSearch.updateSearchButton();
4303   },
4305   onStatusChange: function (aWebProgress, aRequest, aStatus, aMessage) {
4306     this.status = aMessage;
4307     this.updateStatusField();
4308   },
4310   // Properties used to cache security state used to update the UI
4311   _state: null,
4312   _tooltipText: null,
4313   _hostChanged: false, // onLocationChange will flip this bit
4315   onSecurityChange: function (aWebProgress, aRequest, aState) {
4316     // Don't need to do anything if the data we use to update the UI hasn't
4317     // changed
4318     if (this._state == aState &&
4319         this._tooltipText == gBrowser.securityUI.tooltipText &&
4320         !this._hostChanged) {
4321 #ifdef DEBUG
4322       try {
4323         var contentHost = gBrowser.contentWindow.location.host;
4324         if (this._host !== undefined && this._host != contentHost) {
4325             Components.utils.reportError(
4326               "ASSERTION: browser.js host is inconsistent. Content window has " +
4327               "<" + contentHost + "> but cached host is <" + this._host + ">.\n"
4328             );
4329         }
4330       } catch (ex) {}
4331 #endif
4332       return;
4333     }
4334     this._state = aState;
4336 #ifdef DEBUG
4337     try {
4338       this._host = gBrowser.contentWindow.location.host;
4339     } catch(ex) {
4340       this._host = null;
4341     }
4342 #endif
4344     this._hostChanged = false;
4345     this._tooltipText = gBrowser.securityUI.tooltipText
4347     // aState is defined as a bitmask that may be extended in the future.
4348     // We filter out any unknown bits before testing for known values.
4349     const wpl = Components.interfaces.nsIWebProgressListener;
4350     const wpl_security_bits = wpl.STATE_IS_SECURE |
4351                               wpl.STATE_IS_BROKEN |
4352                               wpl.STATE_IS_INSECURE |
4353                               wpl.STATE_SECURE_HIGH |
4354                               wpl.STATE_SECURE_MED |
4355                               wpl.STATE_SECURE_LOW;
4356     var level;
4358     switch (this._state & wpl_security_bits) {
4359       case wpl.STATE_IS_SECURE | wpl.STATE_SECURE_HIGH:
4360         level = "high";
4361         break;
4362       case wpl.STATE_IS_SECURE | wpl.STATE_SECURE_MED:
4363       case wpl.STATE_IS_SECURE | wpl.STATE_SECURE_LOW:
4364         level = "low";
4365         break;
4366       case wpl.STATE_IS_BROKEN:
4367         level = "broken";
4368         break;
4369     }
4371     if (level) {
4372       this.securityButton.setAttribute("level", level);
4373       this.securityButton.hidden = false;
4374       // We don't style the Location Bar based on the the 'level' attribute
4375       // anymore, but still set it for third-party themes.
4376       if (gURLBar)
4377         gURLBar.setAttribute("level", level);
4378     } else {
4379       this.securityButton.hidden = true;
4380       this.securityButton.removeAttribute("level");
4381       if (gURLBar)
4382         gURLBar.removeAttribute("level");
4383     }
4385     this.securityButton.setAttribute("tooltiptext", this._tooltipText);
4387     // Don't pass in the actual location object, since it can cause us to
4388     // hold on to the window object too long.  Just pass in the fields we
4389     // care about. (bug 424829)
4390     var location = gBrowser.contentWindow.location;
4391     var locationObj = {};
4392     try {
4393       locationObj.host = location.host;
4394       locationObj.hostname = location.hostname;
4395       locationObj.port = location.port;
4396     } catch (ex) {
4397       // Can sometimes throw if the URL being visited has no host/hostname,
4398       // e.g. about:blank. The _state for these pages means we won't need these
4399       // properties anyways, though.
4400     }
4401     gIdentityHandler.checkIdentity(this._state, locationObj);
4402   },
4404   // simulate all change notifications after switching tabs
4405   onUpdateCurrentBrowser: function (aStateFlags, aStatus, aMessage, aTotalProgress) {
4406     if (FullZoom.updateBackgroundTabs)
4407       FullZoom.onLocationChange(gBrowser.currentURI, true);
4408     var nsIWebProgressListener = Components.interfaces.nsIWebProgressListener;
4409     var loadingDone = aStateFlags & nsIWebProgressListener.STATE_STOP;
4410     // use a pseudo-object instead of a (potentially nonexistent) channel for getting
4411     // a correct error message - and make sure that the UI is always either in
4412     // loading (STATE_START) or done (STATE_STOP) mode
4413     this.onStateChange(
4414       gBrowser.webProgress,
4415       { URI: gBrowser.currentURI },
4416       loadingDone ? nsIWebProgressListener.STATE_STOP : nsIWebProgressListener.STATE_START,
4417       aStatus
4418     );
4419     // status message and progress value are undefined if we're done with loading
4420     if (loadingDone)
4421       return;
4422     this.onStatusChange(gBrowser.webProgress, null, 0, aMessage);
4423     this.onProgressChange(gBrowser.webProgress, 0, 0, aTotalProgress, 1);
4424   },
4426   startDocumentLoad: function (aRequest) {
4427     // clear out feed data
4428     gBrowser.selectedBrowser.feeds = null;
4430     // clear out search-engine data
4431     gBrowser.selectedBrowser.engines = null;
4433     var uri = aRequest.QueryInterface(Ci.nsIChannel).URI;
4434     try {
4435       Services.obs.notifyObservers(content, "StartDocumentLoad", uri.spec);
4436     } catch (e) {
4437     }
4438   },
4440   endDocumentLoad: function (aRequest, aStatus) {
4441     var urlStr = aRequest.QueryInterface(Ci.nsIChannel).originalURI.spec;
4443     var notification = Components.isSuccessCode(aStatus) ? "EndDocumentLoad" : "FailDocumentLoad";
4444     try {
4445       Services.obs.notifyObservers(content, notification, urlStr);
4446     } catch (e) {
4447     }
4448   }
4451 var CombinedStopReload = {
4452   init: function () {
4453     if (this._initialized)
4454       return;
4456     var stop = document.getElementById("stop-button");
4457     if (!stop)
4458       return;
4460     var reload = document.getElementById("reload-button");
4461     if (!reload)
4462       return;
4464     if (!(reload.nextSibling == stop))
4465       return;
4467     this._initialized = true;
4468     if (XULBrowserWindow.stopCommand.getAttribute("disabled") != "true")
4469       reload.setAttribute("displaystop", "true");
4470     stop.addEventListener("click", this, false);
4471     this.stop = stop;
4472     this.reload = reload;
4473   },
4475   uninit: function () {
4476     if (!this._initialized)
4477       return;
4479     this._cancelTransition();
4480     this._initialized = false;
4481     this.stop.removeEventListener("click", this, false);
4482     this.reload = null;
4483     this.stop = null;
4484   },
4486   handleEvent: function (event) {
4487     // the only event we listen to is "click" on the stop button
4488     if (event.button == 0 &&
4489         !this.stop.disabled)
4490       this._stopClicked = true;
4491   },
4493   switchToStop: function () {
4494     if (!this._initialized)
4495       return;
4497     this._cancelTransition();
4498     this.reload.setAttribute("displaystop", "true");
4499   },
4501   switchToReload: function (aDelay) {
4502     if (!this._initialized)
4503       return;
4505     if (!aDelay || this._stopClicked) {
4506       this._stopClicked = false;
4507       this._cancelTransition();
4508       this.reload.removeAttribute("displaystop");
4509       return;
4510     }
4512     if (this._timer)
4513       return;
4515     this._timer = setTimeout(function (self) {
4516       self._timer = 0;
4517       self.reload.removeAttribute("displaystop");
4518     }, 650, this);
4519   },
4521   _cancelTransition: function () {
4522     if (this._timer) {
4523       clearTimeout(this._timer);
4524       this._timer = 0;
4525     }
4526   }
4529 var TabsProgressListener = {
4530 #ifdef MOZ_CRASHREPORTER
4531   onStateChange: function (aBrowser, aWebProgress, aRequest, aStateFlags, aStatus) {
4532     if (aRequest instanceof Ci.nsIChannel &&
4533         aStateFlags & Ci.nsIWebProgressListener.STATE_START &&
4534         aStateFlags & Ci.nsIWebProgressListener.STATE_IS_DOCUMENT &&
4535         gCrashReporter.enabled) {
4536       gCrashReporter.annotateCrashReport("URL", aRequest.URI.spec);
4537     }
4538   },
4539 #endif
4541   onLocationChange: function (aBrowser, aWebProgress, aRequest, aLocationURI) {
4542     // Filter out any sub-frame loads
4543     if (aBrowser.contentWindow == aWebProgress.DOMWindow)
4544       FullZoom.onLocationChange(aLocationURI, false, aBrowser);
4545   },
4547   onRefreshAttempted: function (aBrowser, aWebProgress, aURI, aDelay, aSameURI) {
4548     if (gPrefService.getBoolPref("accessibility.blockautorefresh")) {
4549       let brandBundle = document.getElementById("bundle_brand");
4550       let brandShortName = brandBundle.getString("brandShortName");
4551       let refreshButtonText =
4552         gNavigatorBundle.getString("refreshBlocked.goButton");
4553       let refreshButtonAccesskey =
4554         gNavigatorBundle.getString("refreshBlocked.goButton.accesskey");
4555       let message =
4556         gNavigatorBundle.getFormattedString(aSameURI ? "refreshBlocked.refreshLabel"
4557                                                      : "refreshBlocked.redirectLabel",
4558                                             [brandShortName]);
4559       let docShell = aWebProgress.DOMWindow
4560                                  .QueryInterface(Ci.nsIInterfaceRequestor)
4561                                  .getInterface(Ci.nsIWebNavigation)
4562                                  .QueryInterface(Ci.nsIDocShell);
4563       let notificationBox = gBrowser.getNotificationBox(aBrowser);
4564       let notification = notificationBox.getNotificationWithValue("refresh-blocked");
4565       if (notification) {
4566         notification.label = message;
4567         notification.refreshURI = aURI;
4568         notification.delay = aDelay;
4569         notification.docShell = docShell;
4570       } else {
4571         let buttons = [{
4572           label: refreshButtonText,
4573           accessKey: refreshButtonAccesskey,
4574           callback: function (aNotification, aButton) {
4575             var refreshURI = aNotification.docShell
4576                                           .QueryInterface(Ci.nsIRefreshURI);
4577             refreshURI.forceRefreshURI(aNotification.refreshURI,
4578                                        aNotification.delay, true);
4579           }
4580         }];
4581         notification =
4582           notificationBox.appendNotification(message, "refresh-blocked",
4583                                              "chrome://browser/skin/Info.png",
4584                                              notificationBox.PRIORITY_INFO_MEDIUM,
4585                                              buttons);
4586         notification.refreshURI = aURI;
4587         notification.delay = aDelay;
4588         notification.docShell = docShell;
4589       }
4590       return false;
4591     }
4592     return true;
4593   }
4596 function nsBrowserAccess() { }
4598 nsBrowserAccess.prototype = {
4599   QueryInterface: XPCOMUtils.generateQI([Ci.nsIBrowserDOMWindow, Ci.nsISupports]),
4601   openURI: function (aURI, aOpener, aWhere, aContext) {
4602     var newWindow = null;
4603     var isExternal = (aContext == Ci.nsIBrowserDOMWindow.OPEN_EXTERNAL);
4605     if (isExternal && aURI && aURI.schemeIs("chrome")) {
4606       dump("use -chrome command-line option to load external chrome urls\n");
4607       return null;
4608     }
4610     if (aWhere == Ci.nsIBrowserDOMWindow.OPEN_DEFAULTWINDOW)
4611       aWhere = gPrefService.getIntPref("browser.link.open_newwindow");
4612     switch (aWhere) {
4613       case Ci.nsIBrowserDOMWindow.OPEN_NEWWINDOW :
4614         // FIXME: Bug 408379. So how come this doesn't send the
4615         // referrer like the other loads do?
4616         var url = aURI ? aURI.spec : "about:blank";
4617         // Pass all params to openDialog to ensure that "url" isn't passed through
4618         // loadOneOrMoreURIs, which splits based on "|"
4619         newWindow = openDialog(getBrowserURL(), "_blank", "all,dialog=no", url, null, null, null);
4620         break;
4621       case Ci.nsIBrowserDOMWindow.OPEN_NEWTAB :
4622         let win, needToFocusWin;
4624         // try the current window.  if we're in a popup, fall back on the most recent browser window
4625         if (!window.document.documentElement.getAttribute("chromehidden"))
4626           win = window;
4627         else {
4628           win = Cc["@mozilla.org/browser/browserglue;1"]
4629                   .getService(Ci.nsIBrowserGlue)
4630                   .getMostRecentBrowserWindow();
4631           needToFocusWin = true;
4632         }
4634         if (!win) {
4635           // we couldn't find a suitable window, a new one needs to be opened.
4636           return null;
4637         }
4639         if (isExternal && (!aURI || aURI.spec == "about:blank")) {
4640           win.BrowserOpenTab(); // this also focuses the location bar
4641           win.focus();
4642           newWindow = win.content;
4643           break;
4644         }
4646         let loadInBackground = gPrefService.getBoolPref("browser.tabs.loadDivertedInBackground");
4647         let referrer = aOpener ? makeURI(aOpener.location.href) : null;
4649         let tab = win.gBrowser.loadOneTab(aURI ? aURI.spec : "about:blank", {
4650                                           referrerURI: referrer,
4651                                           fromExternal: isExternal,
4652                                           inBackground: loadInBackground});
4653         let browser = win.gBrowser.getBrowserForTab(tab);
4655         newWindow = browser.contentWindow;
4656         if (needToFocusWin || (!loadInBackground && isExternal))
4657           newWindow.focus();
4658         break;
4659       default : // OPEN_CURRENTWINDOW or an illegal value
4660         newWindow = content;
4661         if (aURI) {
4662           let referrer = aOpener ? makeURI(aOpener.location.href) : null;
4663           let loadflags = isExternal ?
4664                             Ci.nsIWebNavigation.LOAD_FLAGS_FROM_EXTERNAL :
4665                             Ci.nsIWebNavigation.LOAD_FLAGS_NONE;
4666           gBrowser.loadURIWithFlags(aURI.spec, loadflags, referrer, null, null);
4667         }
4668         if (!gPrefService.getBoolPref("browser.tabs.loadDivertedInBackground"))
4669           content.focus();
4670     }
4671     return newWindow;
4672   },
4674   isTabContentWindow: function (aWindow) {
4675     return gBrowser.browsers.some(function (browser) browser.contentWindow == aWindow);
4676   }
4679 function onViewToolbarsPopupShowing(aEvent) {
4680   var popup = aEvent.target;
4681   if (popup != aEvent.currentTarget)
4682     return;
4684   var i;
4686   // Empty the menu
4687   for (i = popup.childNodes.length-1; i >= 0; --i) {
4688     var deadItem = popup.childNodes[i];
4689     if (deadItem.hasAttribute("toolbarindex"))
4690       popup.removeChild(deadItem);
4691   }
4693   var firstMenuItem = popup.firstChild;
4695   for (i = 0; i < gNavToolbox.childNodes.length; ++i) {
4696     var toolbar = gNavToolbox.childNodes[i];
4697     var toolbarName = toolbar.getAttribute("toolbarname");
4698     if (toolbarName) {
4699       let menuItem = document.createElement("menuitem");
4700       let hidingAttribute = toolbar.getAttribute("type") == "menubar" ?
4701                             "autohide" : "collapsed";
4702       menuItem.setAttribute("toolbarindex", i);
4703       menuItem.setAttribute("type", "checkbox");
4704       menuItem.setAttribute("label", toolbarName);
4705       menuItem.setAttribute("checked", toolbar.getAttribute(hidingAttribute) != "true");
4706       if (popup.id != "appmenu_customizeMenu")
4707         menuItem.setAttribute("accesskey", toolbar.getAttribute("accesskey"));
4708       popup.insertBefore(menuItem, firstMenuItem);
4710       menuItem.addEventListener("command", onViewToolbarCommand, false);
4711     }
4712     toolbar = toolbar.nextSibling;
4713   }
4716 function onViewToolbarCommand(aEvent) {
4717   var index = aEvent.originalTarget.getAttribute("toolbarindex");
4718   var toolbar = gNavToolbox.childNodes[index];
4719   var visible = aEvent.originalTarget.getAttribute("checked") == "true";
4720   setToolbarVisibility(toolbar, visible);
4723 function setToolbarVisibility(toolbar, visible) {
4724   var hidingAttribute = toolbar.getAttribute("type") == "menubar" ?
4725                         "autohide" : "collapsed";
4727   toolbar.setAttribute(hidingAttribute, !visible);
4728   document.persist(toolbar.id, hidingAttribute);
4730   PlacesToolbarHelper.init();
4731   BookmarksMenuButton.updatePosition();
4733 #ifdef MENUBAR_CAN_AUTOHIDE
4734   updateAppButtonDisplay();
4735 #endif
4738 var TabsOnTop = {
4739   toggle: function () {
4740     this.enabled = !this.enabled;
4741   },
4742   syncCommand: function () {
4743     let enabled = this.enabled;
4744     document.getElementById("cmd_ToggleTabsOnTop")
4745             .setAttribute("checked", enabled);
4746     document.documentElement.setAttribute("tabsontop", enabled);
4747     document.getElementById("TabsToolbar").setAttribute("tabsontop", enabled);
4748     gBrowser.tabContainer.setAttribute("tabsontop", enabled);
4749   },
4750   get enabled () {
4751     return gNavToolbox.getAttribute("tabsontop") == "true";
4752   },
4753   set enabled (val) {
4754     gNavToolbox.setAttribute("tabsontop", !!val);
4755     this.syncCommand();
4757     return val;
4758   }
4761 #ifdef MENUBAR_CAN_AUTOHIDE
4762 function updateAppButtonDisplay() {
4763   var displayAppButton =
4764     !gInPrintPreviewMode &&
4765     window.menubar.visible &&
4766     document.getElementById("toolbar-menubar").getAttribute("autohide") == "true";
4768   document.getElementById("titlebar").hidden = !displayAppButton;
4770   if (displayAppButton)
4771     document.documentElement.setAttribute("chromemargin", "0,-1,-1,-1");
4772   else
4773     document.documentElement.removeAttribute("chromemargin");
4776 function onTitlebarMaxClick() {
4777   if (window.windowState == window.STATE_MAXIMIZED)
4778     window.restore();
4779   else
4780     window.maximize();
4782 #endif
4784 function displaySecurityInfo()
4786   BrowserPageInfo(null, "securityTab");
4790  * Opens or closes the sidebar identified by commandID.
4792  * @param commandID a string identifying the sidebar to toggle; see the
4793  *                  note below. (Optional if a sidebar is already open.)
4794  * @param forceOpen boolean indicating whether the sidebar should be
4795  *                  opened regardless of its current state (optional).
4796  * @note
4797  * We expect to find a xul:broadcaster element with the specified ID.
4798  * The following attributes on that element may be used and/or modified:
4799  *  - id           (required) the string to match commandID. The convention
4800  *                 is to use this naming scheme: 'view<sidebar-name>Sidebar'.
4801  *  - sidebarurl   (required) specifies the URL to load in this sidebar.
4802  *  - sidebartitle or label (in that order) specify the title to
4803  *                 display on the sidebar.
4804  *  - checked      indicates whether the sidebar is currently displayed.
4805  *                 Note that toggleSidebar updates this attribute when
4806  *                 it changes the sidebar's visibility.
4807  *  - group        this attribute must be set to "sidebar".
4808  */
4809 function toggleSidebar(commandID, forceOpen) {
4811   var sidebarBox = document.getElementById("sidebar-box");
4812   if (!commandID)
4813     commandID = sidebarBox.getAttribute("sidebarcommand");
4815   var sidebarBroadcaster = document.getElementById(commandID);
4816   var sidebar = document.getElementById("sidebar"); // xul:browser
4817   var sidebarTitle = document.getElementById("sidebar-title");
4818   var sidebarSplitter = document.getElementById("sidebar-splitter");
4820   if (sidebarBroadcaster.getAttribute("checked") == "true") {
4821     if (!forceOpen) {
4822       sidebarBroadcaster.removeAttribute("checked");
4823       sidebarBox.setAttribute("sidebarcommand", "");
4824       sidebarTitle.value = "";
4825       sidebar.setAttribute("src", "about:blank");
4826       sidebarBox.hidden = true;
4827       sidebarSplitter.hidden = true;
4828       content.focus();
4829     } else {
4830       fireSidebarFocusedEvent();
4831     }
4832     return;
4833   }
4835   // now we need to show the specified sidebar
4837   // ..but first update the 'checked' state of all sidebar broadcasters
4838   var broadcasters = document.getElementsByAttribute("group", "sidebar");
4839   for (var i = 0; i < broadcasters.length; ++i) {
4840     // skip elements that observe sidebar broadcasters and random
4841     // other elements
4842     if (broadcasters[i].localName != "broadcaster")
4843       continue;
4845     if (broadcasters[i] != sidebarBroadcaster)
4846       broadcasters[i].removeAttribute("checked");
4847     else
4848       sidebarBroadcaster.setAttribute("checked", "true");
4849   }
4851   sidebarBox.hidden = false;
4852   sidebarSplitter.hidden = false;
4854   var url = sidebarBroadcaster.getAttribute("sidebarurl");
4855   var title = sidebarBroadcaster.getAttribute("sidebartitle");
4856   if (!title)
4857     title = sidebarBroadcaster.getAttribute("label");
4858   sidebar.setAttribute("src", url); // kick off async load
4859   sidebarBox.setAttribute("sidebarcommand", sidebarBroadcaster.id);
4860   sidebarTitle.value = title;
4862   // We set this attribute here in addition to setting it on the <browser>
4863   // element itself, because the code in BrowserShutdown persists this
4864   // attribute, not the "src" of the <browser id="sidebar">. The reason it
4865   // does that is that we want to delay sidebar load a bit when a browser
4866   // window opens. See delayedStartup().
4867   sidebarBox.setAttribute("src", url);
4869   if (sidebar.contentDocument.location.href != url)
4870     sidebar.addEventListener("load", sidebarOnLoad, true);
4871   else // older code handled this case, so we do it too
4872     fireSidebarFocusedEvent();
4875 function sidebarOnLoad(event) {
4876   var sidebar = document.getElementById("sidebar");
4877   sidebar.removeEventListener("load", sidebarOnLoad, true);
4878   // We're handling the 'load' event before it bubbles up to the usual
4879   // (non-capturing) event handlers. Let it bubble up before firing the
4880   // SidebarFocused event.
4881   setTimeout(fireSidebarFocusedEvent, 0);
4885  * Fire a "SidebarFocused" event on the sidebar's |window| to give the sidebar
4886  * a chance to adjust focus as needed. An additional event is needed, because
4887  * we don't want to focus the sidebar when it's opened on startup or in a new
4888  * window, only when the user opens the sidebar.
4889  */
4890 function fireSidebarFocusedEvent() {
4891   var sidebar = document.getElementById("sidebar");
4892   var event = document.createEvent("Events");
4893   event.initEvent("SidebarFocused", true, false);
4894   sidebar.contentWindow.dispatchEvent(event);
4897 var gHomeButton = {
4898   prefDomain: "browser.startup.homepage",
4899   observe: function (aSubject, aTopic, aPrefName)
4900   {
4901     if (aTopic != "nsPref:changed" || aPrefName != this.prefDomain)
4902       return;
4904     this.updateTooltip();
4905   },
4907   updateTooltip: function (homeButton)
4908   {
4909     if (!homeButton)
4910       homeButton = document.getElementById("home-button");
4911     if (homeButton) {
4912       var homePage = this.getHomePage();
4913       homePage = homePage.replace(/\|/g,', ');
4914       homeButton.setAttribute("tooltiptext", homePage);
4915     }
4916   },
4918   getHomePage: function ()
4919   {
4920     var url;
4921     try {
4922       url = gPrefService.getComplexValue(this.prefDomain,
4923                                 Components.interfaces.nsIPrefLocalizedString).data;
4924     } catch (e) {
4925     }
4927     // use this if we can't find the pref
4928     if (!url) {
4929       var SBS = Cc["@mozilla.org/intl/stringbundle;1"].getService(Ci.nsIStringBundleService);
4930       var configBundle = SBS.createBundle("chrome://branding/locale/browserconfig.properties");
4931       url = configBundle.GetStringFromName(this.prefDomain);
4932     }
4934     return url;
4935   },
4937   updatePersonalToolbarStyle: function (homeButton)
4938   {
4939     if (!homeButton)
4940       homeButton = document.getElementById("home-button");
4941     if (homeButton)
4942       homeButton.className = homeButton.parentNode.id == "PersonalToolbar"
4943                                || homeButton.parentNode.parentNode.id == "PersonalToolbar" ?
4944                              homeButton.className.replace("toolbarbutton-1", "bookmark-item") :
4945                              homeButton.className.replace("bookmark-item", "toolbarbutton-1");
4946   }
4950  * Gets the selected text in the active browser. Leading and trailing
4951  * whitespace is removed, and consecutive whitespace is replaced by a single
4952  * space. A maximum of 150 characters will be returned, regardless of the value
4953  * of aCharLen.
4955  * @param aCharLen
4956  *        The maximum number of characters to return.
4957  */
4958 function getBrowserSelection(aCharLen) {
4959   // selections of more than 150 characters aren't useful
4960   const kMaxSelectionLen = 150;
4961   const charLen = Math.min(aCharLen || kMaxSelectionLen, kMaxSelectionLen);
4963   var focusedWindow = document.commandDispatcher.focusedWindow;
4964   var selection = focusedWindow.getSelection().toString();
4966   if (selection) {
4967     if (selection.length > charLen) {
4968       // only use the first charLen important chars. see bug 221361
4969       var pattern = new RegExp("^(?:\\s*.){0," + charLen + "}");
4970       pattern.test(selection);
4971       selection = RegExp.lastMatch;
4972     }
4974     selection = selection.replace(/^\s+/, "")
4975                          .replace(/\s+$/, "")
4976                          .replace(/\s+/g, " ");
4978     if (selection.length > charLen)
4979       selection = selection.substr(0, charLen);
4980   }
4981   return selection;
4984 var gWebPanelURI;
4985 function openWebPanel(aTitle, aURI)
4987     // Ensure that the web panels sidebar is open.
4988     toggleSidebar('viewWebPanelsSidebar', true);
4990     // Set the title of the panel.
4991     document.getElementById("sidebar-title").value = aTitle;
4993     // Tell the Web Panels sidebar to load the bookmark.
4994     var sidebar = document.getElementById("sidebar");
4995     if (sidebar.docShell && sidebar.contentDocument && sidebar.contentDocument.getElementById('web-panels-browser')) {
4996         sidebar.contentWindow.loadWebPanel(aURI);
4997         if (gWebPanelURI) {
4998             gWebPanelURI = "";
4999             sidebar.removeEventListener("load", asyncOpenWebPanel, true);
5000         }
5001     }
5002     else {
5003         // The panel is still being constructed.  Attach an onload handler.
5004         if (!gWebPanelURI)
5005             sidebar.addEventListener("load", asyncOpenWebPanel, true);
5006         gWebPanelURI = aURI;
5007     }
5010 function asyncOpenWebPanel(event)
5012     var sidebar = document.getElementById("sidebar");
5013     if (gWebPanelURI && sidebar.contentDocument && sidebar.contentDocument.getElementById('web-panels-browser'))
5014         sidebar.contentWindow.loadWebPanel(gWebPanelURI);
5015     gWebPanelURI = "";
5016     sidebar.removeEventListener("load", asyncOpenWebPanel, true);
5020  * - [ Dependencies ] ---------------------------------------------------------
5021  *  utilityOverlay.js:
5022  *    - gatherTextUnder
5023  */
5025  // Called whenever the user clicks in the content area,
5026  // except when left-clicking on links (special case)
5027  // should always return true for click to go through
5028  function contentAreaClick(event, fieldNormalClicks)
5030    if (!event.isTrusted || event.getPreventDefault()) {
5031      return true;
5032    }
5034    var target = event.target;
5035    var linkNode;
5037    if (target instanceof HTMLAnchorElement ||
5038        target instanceof HTMLAreaElement ||
5039        target instanceof HTMLLinkElement) {
5040      if (target.hasAttribute("href"))
5041        linkNode = target;
5043      // xxxmpc: this is kind of a hack to work around a Gecko bug (see bug 266932)
5044      // we're going to walk up the DOM looking for a parent link node,
5045      // this shouldn't be necessary, but we're matching the existing behaviour for left click
5046      var parent = target.parentNode;
5047      while (parent) {
5048        if (parent instanceof HTMLAnchorElement ||
5049            parent instanceof HTMLAreaElement ||
5050            parent instanceof HTMLLinkElement) {
5051            if (parent.hasAttribute("href"))
5052              linkNode = parent;
5053        }
5054        parent = parent.parentNode;
5055      }
5056    }
5057    else {
5058      linkNode = event.originalTarget;
5059      while (linkNode && !(linkNode instanceof HTMLAnchorElement))
5060        linkNode = linkNode.parentNode;
5061      // <a> cannot be nested.  So if we find an anchor without an
5062      // href, there is no useful <a> around the target
5063      if (linkNode && !linkNode.hasAttribute("href"))
5064        linkNode = null;
5065    }
5066    var wrapper = null;
5067    if (linkNode) {
5068      wrapper = linkNode;
5069      if (event.button == 0 && !event.ctrlKey && !event.shiftKey &&
5070          !event.altKey && !event.metaKey) {
5071        // A Web panel's links should target the main content area.  Do this
5072        // if no modifier keys are down and if there's no target or the target equals
5073        // _main (the IE convention) or _content (the Mozilla convention).
5074        // XXX Now that markLinkVisited is gone, we may not need to field _main and
5075        // _content here.
5076        target = wrapper.getAttribute("target");
5077        if (fieldNormalClicks &&
5078            (!target || target == "_content" || target  == "_main"))
5079          // IE uses _main, SeaMonkey uses _content, we support both
5080        {
5081          if (!wrapper.href)
5082            return true;
5083          if (wrapper.getAttribute("onclick"))
5084            return true;
5085          // javascript links should be executed in the current browser
5086          if (wrapper.href.substr(0, 11) === "javascript:")
5087            return true;
5088          // data links should be executed in the current browser
5089          if (wrapper.href.substr(0, 5) === "data:")
5090            return true;
5092          try {
5093            urlSecurityCheck(wrapper.href, wrapper.ownerDocument.nodePrincipal);
5094          }
5095          catch(ex) {
5096            return false;
5097          }
5099          var postData = { };
5100          var url = getShortcutOrURI(wrapper.href, postData);
5101          if (!url)
5102            return true;
5103          loadURI(url, null, postData.value, false);
5104          event.preventDefault();
5105          return false;
5106        }
5107        else if (linkNode.getAttribute("rel") == "sidebar") {
5108          // This is the Opera convention for a special link that - when clicked - allows
5109          // you to add a sidebar panel.  We support the Opera convention here.  The link's
5110          // title attribute contains the title that should be used for the sidebar panel.
5111          PlacesUIUtils.showMinimalAddBookmarkUI(makeURI(wrapper.href),
5112                                                 wrapper.getAttribute("title"),
5113                                                 null, null, true, true);
5114          event.preventDefault();
5115          return false;
5116        }
5117      }
5118      else {
5119        handleLinkClick(event, wrapper.href, linkNode);
5120      }
5122      return true;
5123    } else {
5124      // Try simple XLink
5125      var href, realHref, baseURI;
5126      linkNode = target;
5127      while (linkNode) {
5128        if (linkNode.nodeType == Node.ELEMENT_NODE) {
5129          wrapper = linkNode;
5131          realHref = wrapper.getAttributeNS("http://www.w3.org/1999/xlink", "href");
5132          if (realHref) {
5133            href = realHref;
5134            baseURI = wrapper.baseURI
5135          }
5136        }
5137        linkNode = linkNode.parentNode;
5138      }
5139      if (href) {
5140        href = makeURLAbsolute(baseURI, href);
5141        handleLinkClick(event, href, null);
5142        return true;
5143      }
5144    }
5145    if (event.button == 1 &&
5146        gPrefService.getBoolPref("middlemouse.contentLoadURL") &&
5147        !gPrefService.getBoolPref("general.autoScroll")) {
5148      middleMousePaste(event);
5149    }
5150    return true;
5153 function handleLinkClick(event, href, linkNode)
5155   var doc = event.target.ownerDocument;
5157   switch (event.button) {
5158     case 0:    // if left button clicked
5159 #ifdef XP_MACOSX
5160       if (event.metaKey) { // Cmd
5161 #else
5162       if (event.ctrlKey) {
5163 #endif
5164         openNewTabWith(href, doc, null, event, false);
5165         event.stopPropagation();
5166         return true;
5167       }
5169       if (event.shiftKey && event.altKey) {
5170         var feedService =
5171             Cc["@mozilla.org/browser/feeds/result-service;1"].
5172             getService(Ci.nsIFeedResultService);
5173         feedService.forcePreviewPage = true;
5174         loadURI(href, null, null, false);
5175         return false;
5176       }
5178       if (event.shiftKey) {
5179         openNewWindowWith(href, doc, null, false);
5180         event.stopPropagation();
5181         return true;
5182       }
5184       if (event.altKey) {
5185         saveURL(href, linkNode ? gatherTextUnder(linkNode) : "", null, true,
5186                 true, doc.documentURIObject);
5187         return true;
5188       }
5190       return false;
5191     case 1:    // if middle button clicked
5192       var tab = gPrefService.getBoolPref("browser.tabs.opentabfor.middleclick");
5193       if (tab)
5194         openNewTabWith(href, doc, null, event, false);
5195       else
5196         openNewWindowWith(href, doc, null, false);
5197       event.stopPropagation();
5198       return true;
5199   }
5200   return false;
5203 function middleMousePaste(event) {
5204   var url = getShortcutOrURI(readFromClipboard());
5205   try {
5206     makeURI(url);
5207   } catch (ex) {
5208     // Not a valid URI.
5209     return;
5210   }
5212   try {
5213     addToUrlbarHistory(url);
5214   } catch (ex) {
5215     // Things may go wrong when adding url to session history,
5216     // but don't let that interfere with the loading of the url.
5217     Cu.reportError(ex);
5218   }
5220   openUILink(url,
5221              event,
5222              true /* ignore the fact this is a middle click */);
5224   event.stopPropagation();
5227 function handleDroppedLink(event, url, name)
5229   let postData = { };
5230   let uri = getShortcutOrURI(url, postData);
5231   if (uri)
5232     loadURI(uri, null, postData.value, false);
5234   // Keep the event from being handled by the dragDrop listeners
5235   // built-in to gecko if they happen to be above us.
5236   event.preventDefault();
5239 function MultiplexHandler(event)
5240 { try {
5241     var node = event.target;
5242     var name = node.getAttribute('name');
5244     if (name == 'detectorGroup') {
5245         SetForcedDetector(true);
5246         SelectDetector(event, false);
5247     } else if (name == 'charsetGroup') {
5248         var charset = node.getAttribute('id');
5249         charset = charset.substring('charset.'.length, charset.length)
5250         SetForcedCharset(charset);
5251     } else if (name == 'charsetCustomize') {
5252         //do nothing - please remove this else statement, once the charset prefs moves to the pref window
5253     } else {
5254         SetForcedCharset(node.getAttribute('id'));
5255     }
5256     } catch(ex) { alert(ex); }
5259 function SelectDetector(event, doReload)
5261     var uri =  event.target.getAttribute("id");
5262     var prefvalue = uri.substring('chardet.'.length, uri.length);
5263     if ("off" == prefvalue) { // "off" is special value to turn off the detectors
5264         prefvalue = "";
5265     }
5267     try {
5268         var str =  Cc["@mozilla.org/supports-string;1"].
5269                    createInstance(Ci.nsISupportsString);
5271         str.data = prefvalue;
5272         gPrefService.setComplexValue("intl.charset.detector", Ci.nsISupportsString, str);
5273         if (doReload)
5274           window.content.location.reload();
5275     }
5276     catch (ex) {
5277         dump("Failed to set the intl.charset.detector preference.\n");
5278     }
5281 function SetForcedDetector(doReload)
5283     BrowserSetForcedDetector(doReload);
5286 function SetForcedCharset(charset)
5288     BrowserSetForcedCharacterSet(charset);
5291 function BrowserSetForcedCharacterSet(aCharset)
5293   var docCharset = gBrowser.docShell.QueryInterface(Ci.nsIDocCharset);
5294   docCharset.charset = aCharset;
5295   // Save the forced character-set
5296   PlacesUtils.history.setCharsetForURI(getWebNavigation().currentURI, aCharset);
5297   BrowserReloadWithFlags(nsIWebNavigation.LOAD_FLAGS_CHARSET_CHANGE);
5300 function BrowserSetForcedDetector(doReload)
5302   gBrowser.documentCharsetInfo.forcedDetector = true;
5303   if (doReload)
5304     BrowserReloadWithFlags(nsIWebNavigation.LOAD_FLAGS_CHARSET_CHANGE);
5307 function UpdateCurrentCharset()
5309     // extract the charset from DOM
5310     var wnd = document.commandDispatcher.focusedWindow;
5311     if ((window == wnd) || (wnd == null)) wnd = window.content;
5313     // Uncheck previous item
5314     if (gPrevCharset) {
5315         var pref_item = document.getElementById('charset.' + gPrevCharset);
5316         if (pref_item)
5317           pref_item.setAttribute('checked', 'false');
5318     }
5320     var menuitem = document.getElementById('charset.' + wnd.document.characterSet);
5321     if (menuitem) {
5322         menuitem.setAttribute('checked', 'true');
5323     }
5326 function UpdateCharsetDetector() {
5327   var prefvalue = "off";
5329   try {
5330     prefvalue = gPrefService.getComplexValue("intl.charset.detector", Ci.nsIPrefLocalizedString).data;
5331   }
5332   catch (ex) {}
5334   prefvalue = "chardet." + prefvalue;
5336   var menuitem = document.getElementById(prefvalue);
5337   if (menuitem)
5338     menuitem.setAttribute("checked", "true");
5341 function UpdateMenus(event) {
5342   // use setTimeout workaround to delay checkmark the menu
5343   // when onmenucomplete is ready then use it instead of oncreate
5344   // see bug 78290 for the detail
5345   UpdateCurrentCharset();
5346   setTimeout(UpdateCurrentCharset, 0);
5347   UpdateCharsetDetector();
5348   setTimeout(UpdateCharsetDetector, 0);
5351 function CreateMenu(node) {
5352   Services.obs.notifyObservers(null, "charsetmenu-selected", node);
5355 function charsetLoadListener(event) {
5356   var charset = window.content.document.characterSet;
5358   if (charset.length > 0 && (charset != gLastBrowserCharset)) {
5359     if (!gCharsetMenu)
5360       gCharsetMenu = Cc['@mozilla.org/rdf/datasource;1?name=charset-menu'].getService(Ci.nsICurrentCharsetListener);
5361     gCharsetMenu.SetCurrentCharset(charset);
5362     gPrevCharset = gLastBrowserCharset;
5363     gLastBrowserCharset = charset;
5364   }
5367 /* Begin Page Style Functions */
5368 function getAllStyleSheets(frameset) {
5369   var styleSheetsArray = Array.slice(frameset.document.styleSheets);
5370   for (let i = 0; i < frameset.frames.length; i++) {
5371     let frameSheets = getAllStyleSheets(frameset.frames[i]);
5372     styleSheetsArray = styleSheetsArray.concat(frameSheets);
5373   }
5374   return styleSheetsArray;
5377 function stylesheetFillPopup(menuPopup) {
5378   var noStyle = menuPopup.firstChild;
5379   var persistentOnly = noStyle.nextSibling;
5380   var sep = persistentOnly.nextSibling;
5381   while (sep.nextSibling)
5382     menuPopup.removeChild(sep.nextSibling);
5384   var styleSheets = getAllStyleSheets(window.content);
5385   var currentStyleSheets = {};
5386   var styleDisabled = getMarkupDocumentViewer().authorStyleDisabled;
5387   var haveAltSheets = false;
5388   var altStyleSelected = false;
5390   for (let i = 0; i < styleSheets.length; ++i) {
5391     let currentStyleSheet = styleSheets[i];
5393     if (!currentStyleSheet.title)
5394       continue;
5396     // Skip any stylesheets that don't match the screen media type.
5397     if (currentStyleSheet.media.length > 0) {
5398       let media = currentStyleSheet.media.mediaText.split(", ");
5399       if (media.indexOf("screen") == -1 &&
5400           media.indexOf("all") == -1)
5401         continue;
5402     }
5404     if (!currentStyleSheet.disabled)
5405       altStyleSelected = true;
5407     haveAltSheets = true;
5409     let lastWithSameTitle = null;
5410     if (currentStyleSheet.title in currentStyleSheets)
5411       lastWithSameTitle = currentStyleSheets[currentStyleSheet.title];
5413     if (!lastWithSameTitle) {
5414       let menuItem = document.createElement("menuitem");
5415       menuItem.setAttribute("type", "radio");
5416       menuItem.setAttribute("label", currentStyleSheet.title);
5417       menuItem.setAttribute("data", currentStyleSheet.title);
5418       menuItem.setAttribute("checked", !currentStyleSheet.disabled && !styleDisabled);
5419       menuPopup.appendChild(menuItem);
5420       currentStyleSheets[currentStyleSheet.title] = menuItem;
5421     } else if (currentStyleSheet.disabled) {
5422       lastWithSameTitle.removeAttribute("checked");
5423     }
5424   }
5426   noStyle.setAttribute("checked", styleDisabled);
5427   persistentOnly.setAttribute("checked", !altStyleSelected && !styleDisabled);
5428   persistentOnly.hidden = (window.content.document.preferredStyleSheetSet) ? haveAltSheets : false;
5429   sep.hidden = (noStyle.hidden && persistentOnly.hidden) || !haveAltSheets;
5430   return true;
5433 function stylesheetInFrame(frame, title) {
5434   return Array.some(frame.document.styleSheets,
5435                     function (stylesheet) stylesheet.title == title);
5438 function stylesheetSwitchFrame(frame, title) {
5439   var docStyleSheets = frame.document.styleSheets;
5441   for (let i = 0; i < docStyleSheets.length; ++i) {
5442     let docStyleSheet = docStyleSheets[i];
5444     if (title == "_nostyle")
5445       docStyleSheet.disabled = true;
5446     else if (docStyleSheet.title)
5447       docStyleSheet.disabled = (docStyleSheet.title != title);
5448     else if (docStyleSheet.disabled)
5449       docStyleSheet.disabled = false;
5450   }
5453 function stylesheetSwitchAll(frameset, title) {
5454   if (!title || title == "_nostyle" || stylesheetInFrame(frameset, title))
5455     stylesheetSwitchFrame(frameset, title);
5457   for (let i = 0; i < frameset.frames.length; i++)
5458     stylesheetSwitchAll(frameset.frames[i], title);
5461 function setStyleDisabled(disabled) {
5462   getMarkupDocumentViewer().authorStyleDisabled = disabled;
5464 /* End of the Page Style functions */
5466 var BrowserOffline = {
5467   /////////////////////////////////////////////////////////////////////////////
5468   // BrowserOffline Public Methods
5469   init: function ()
5470   {
5471     if (!this._uiElement)
5472       this._uiElement = document.getElementById("goOfflineMenuitem");
5474     Services.obs.addObserver(this, "network:offline-status-changed", false);
5476     this._updateOfflineUI(Services.io.offline);
5477   },
5479   uninit: function ()
5480   {
5481     try {
5482       Services.obs.removeObserver(this, "network:offline-status-changed");
5483     } catch (ex) {
5484     }
5485   },
5487   toggleOfflineStatus: function ()
5488   {
5489     var ioService = Services.io;
5491     // Stop automatic management of the offline status
5492     try {
5493       ioService.manageOfflineStatus = false;
5494     } catch (ex) {
5495     }
5497     if (!ioService.offline && !this._canGoOffline()) {
5498       this._updateOfflineUI(false);
5499       return;
5500     }
5502     ioService.offline = !ioService.offline;
5504     // Save the current state for later use as the initial state
5505     // (if there is no netLinkService)
5506     gPrefService.setBoolPref("browser.offline", ioService.offline);
5507   },
5509   /////////////////////////////////////////////////////////////////////////////
5510   // nsIObserver
5511   observe: function (aSubject, aTopic, aState)
5512   {
5513     if (aTopic != "network:offline-status-changed")
5514       return;
5516     this._updateOfflineUI(aState == "offline");
5517   },
5519   /////////////////////////////////////////////////////////////////////////////
5520   // BrowserOffline Implementation Methods
5521   _canGoOffline: function ()
5522   {
5523     try {
5524       var cancelGoOffline = Cc["@mozilla.org/supports-PRBool;1"].createInstance(Ci.nsISupportsPRBool);
5525       Services.obs.notifyObservers(cancelGoOffline, "offline-requested", null);
5527       // Something aborted the quit process.
5528       if (cancelGoOffline.data)
5529         return false;
5530     }
5531     catch (ex) {
5532     }
5534     return true;
5535   },
5537   _uiElement: null,
5538   _updateOfflineUI: function (aOffline)
5539   {
5540     var offlineLocked = gPrefService.prefIsLocked("network.online");
5541     if (offlineLocked)
5542       this._uiElement.setAttribute("disabled", "true");
5544     this._uiElement.setAttribute("checked", aOffline);
5545   }
5548 var OfflineApps = {
5549   /////////////////////////////////////////////////////////////////////////////
5550   // OfflineApps Public Methods
5551   init: function ()
5552   {
5553     Services.obs.addObserver(this, "dom-storage-warn-quota-exceeded", false);
5554     Services.obs.addObserver(this, "offline-cache-update-completed", false);
5555   },
5557   uninit: function ()
5558   {
5559     Services.obs.removeObserver(this, "dom-storage-warn-quota-exceeded");
5560     Services.obs.removeObserver(this, "offline-cache-update-completed");
5561   },
5563   handleEvent: function(event) {
5564     if (event.type == "MozApplicationManifest") {
5565       this.offlineAppRequested(event.originalTarget.defaultView);
5566     }
5567   },
5569   /////////////////////////////////////////////////////////////////////////////
5570   // OfflineApps Implementation Methods
5572   // XXX: _getBrowserWindowForContentWindow and _getBrowserForContentWindow
5573   // were taken from browser/components/feeds/src/WebContentConverter.
5574   _getBrowserWindowForContentWindow: function(aContentWindow) {
5575     return aContentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
5576                          .getInterface(Ci.nsIWebNavigation)
5577                          .QueryInterface(Ci.nsIDocShellTreeItem)
5578                          .rootTreeItem
5579                          .QueryInterface(Ci.nsIInterfaceRequestor)
5580                          .getInterface(Ci.nsIDOMWindow)
5581                          .wrappedJSObject;
5582   },
5584   _getBrowserForContentWindow: function(aBrowserWindow, aContentWindow) {
5585     // This depends on pseudo APIs of browser.js and tabbrowser.xml
5586     aContentWindow = aContentWindow.top;
5587     var browsers = aBrowserWindow.gBrowser.browsers;
5588     for (var i = 0; i < browsers.length; ++i) {
5589       if (browsers[i].contentWindow == aContentWindow)
5590         return browsers[i];
5591     }
5592     return null;
5593   },
5595   _getManifestURI: function(aWindow) {
5596     if (!aWindow.document.documentElement)
5597       return null;
5599     var attr = aWindow.document.documentElement.getAttribute("manifest");
5600     if (!attr)
5601       return null;
5603     try {
5604       var contentURI = makeURI(aWindow.location.href, null, null);
5605       return makeURI(attr, aWindow.document.characterSet, contentURI);
5606     } catch (e) {
5607       return null;
5608     }
5609   },
5611   // A cache update isn't tied to a specific window.  Try to find
5612   // the best browser in which to warn the user about space usage
5613   _getBrowserForCacheUpdate: function(aCacheUpdate) {
5614     // Prefer the current browser
5615     var uri = this._getManifestURI(content);
5616     if (uri && uri.equals(aCacheUpdate.manifestURI)) {
5617       return gBrowser.selectedBrowser;
5618     }
5620     var browsers = gBrowser.browsers;
5621     for (var i = 0; i < browsers.length; ++i) {
5622       uri = this._getManifestURI(browsers[i].contentWindow);
5623       if (uri && uri.equals(aCacheUpdate.manifestURI)) {
5624         return browsers[i];
5625       }
5626     }
5628     return null;
5629   },
5631   _warnUsage: function(aBrowser, aURI) {
5632     if (!aBrowser)
5633       return;
5635     var notificationBox = gBrowser.getNotificationBox(aBrowser);
5636     var notification = notificationBox.getNotificationWithValue("offline-app-usage");
5637     if (!notification) {
5638       var buttons = [{
5639           label: gNavigatorBundle.getString("offlineApps.manageUsage"),
5640           accessKey: gNavigatorBundle.getString("offlineApps.manageUsageAccessKey"),
5641           callback: OfflineApps.manage
5642         }];
5644       var warnQuota = gPrefService.getIntPref("offline-apps.quota.warn");
5645       const priority = notificationBox.PRIORITY_WARNING_MEDIUM;
5646       var message = gNavigatorBundle.getFormattedString("offlineApps.usage",
5647                                                         [ aURI.host,
5648                                                           warnQuota / 1024 ]);
5650       notificationBox.appendNotification(message, "offline-app-usage",
5651                                          "chrome://browser/skin/Info.png",
5652                                          priority, buttons);
5653     }
5655     // Now that we've warned once, prevent the warning from showing up
5656     // again.
5657     Services.perms.add(aURI, "offline-app",
5658                        Ci.nsIOfflineCacheUpdateService.ALLOW_NO_WARN);
5659   },
5661   // XXX: duplicated in preferences/advanced.js
5662   _getOfflineAppUsage: function (host, groups)
5663   {
5664     var cacheService = Cc["@mozilla.org/network/application-cache-service;1"].
5665                        getService(Ci.nsIApplicationCacheService);
5666     if (!groups)
5667       groups = cacheService.getGroups();
5669     var usage = 0;
5670     for (var i = 0; i < groups.length; i++) {
5671       var uri = Services.io.newURI(groups[i], null, null);
5672       if (uri.asciiHost == host) {
5673         var cache = cacheService.getActiveCache(groups[i]);
5674         usage += cache.usage;
5675       }
5676     }
5678     var storageManager = Cc["@mozilla.org/dom/storagemanager;1"].
5679                          getService(Ci.nsIDOMStorageManager);
5680     usage += storageManager.getUsage(host);
5682     return usage;
5683   },
5685   _checkUsage: function(aURI) {
5686     // if the user has already allowed excessive usage, don't bother checking
5687     if (Services.perms.testExactPermission(aURI, "offline-app") !=
5688         Ci.nsIOfflineCacheUpdateService.ALLOW_NO_WARN) {
5689       var usage = this._getOfflineAppUsage(aURI.asciiHost);
5690       var warnQuota = gPrefService.getIntPref("offline-apps.quota.warn");
5691       if (usage >= warnQuota * 1024) {
5692         return true;
5693       }
5694     }
5696     return false;
5697   },
5699   offlineAppRequested: function(aContentWindow) {
5700     if (!gPrefService.getBoolPref("browser.offline-apps.notify")) {
5701       return;
5702     }
5704     var browserWindow = this._getBrowserWindowForContentWindow(aContentWindow);
5705     var browser = this._getBrowserForContentWindow(browserWindow,
5706                                                    aContentWindow);
5708     var currentURI = aContentWindow.document.documentURIObject;
5710     // don't bother showing UI if the user has already made a decision
5711     if (Services.perms.testExactPermission(currentURI, "offline-app") != Services.perms.UNKNOWN_ACTION)
5712       return;
5714     try {
5715       if (gPrefService.getBoolPref("offline-apps.allow_by_default")) {
5716         // all pages can use offline capabilities, no need to ask the user
5717         return;
5718       }
5719     } catch(e) {
5720       // this pref isn't set by default, ignore failures
5721     }
5723     var host = currentURI.asciiHost;
5724     var notificationBox = gBrowser.getNotificationBox(browser);
5725     var notificationID = "offline-app-requested-" + host;
5726     var notification = notificationBox.getNotificationWithValue(notificationID);
5728     if (notification) {
5729       notification.documents.push(aContentWindow.document);
5730     } else {
5731       var buttons = [{
5732         label: gNavigatorBundle.getString("offlineApps.allow"),
5733         accessKey: gNavigatorBundle.getString("offlineApps.allowAccessKey"),
5734         callback: function() {
5735           for (var i = 0; i < notification.documents.length; i++) {
5736             OfflineApps.allowSite(notification.documents[i]);
5737           }
5738         }
5739       },{
5740         label: gNavigatorBundle.getString("offlineApps.never"),
5741         accessKey: gNavigatorBundle.getString("offlineApps.neverAccessKey"),
5742         callback: function() {
5743           for (var i = 0; i < notification.documents.length; i++) {
5744             OfflineApps.disallowSite(notification.documents[i]);
5745           }
5746         }
5747       },{
5748         label: gNavigatorBundle.getString("offlineApps.notNow"),
5749         accessKey: gNavigatorBundle.getString("offlineApps.notNowAccessKey"),
5750         callback: function() { /* noop */ }
5751       }];
5753       const priority = notificationBox.PRIORITY_INFO_LOW;
5754       var message = gNavigatorBundle.getFormattedString("offlineApps.available",
5755                                                         [ host ]);
5756       notification =
5757         notificationBox.appendNotification(message, notificationID,
5758                                            "chrome://browser/skin/Info.png",
5759                                            priority, buttons);
5760       notification.documents = [ aContentWindow.document ];
5761     }
5762   },
5764   allowSite: function(aDocument) {
5765     Services.perms.add(aDocument.documentURIObject, "offline-app", Services.perms.ALLOW_ACTION);
5767     // When a site is enabled while loading, manifest resources will
5768     // start fetching immediately.  This one time we need to do it
5769     // ourselves.
5770     this._startFetching(aDocument);
5771   },
5773   disallowSite: function(aDocument) {
5774     Services.perms.add(aDocument.documentURIObject, "offline-app", Services.perms.DENY_ACTION);
5775   },
5777   manage: function() {
5778     openAdvancedPreferences("networkTab");
5779   },
5781   _startFetching: function(aDocument) {
5782     if (!aDocument.documentElement)
5783       return;
5785     var manifest = aDocument.documentElement.getAttribute("manifest");
5786     if (!manifest)
5787       return;
5789     var manifestURI = makeURI(manifest, aDocument.characterSet,
5790                               aDocument.documentURIObject);
5792     var updateService = Cc["@mozilla.org/offlinecacheupdate-service;1"].
5793                         getService(Ci.nsIOfflineCacheUpdateService);
5794     updateService.scheduleUpdate(manifestURI, aDocument.documentURIObject);
5795   },
5797   /////////////////////////////////////////////////////////////////////////////
5798   // nsIObserver
5799   observe: function (aSubject, aTopic, aState)
5800   {
5801     if (aTopic == "dom-storage-warn-quota-exceeded") {
5802       if (aSubject) {
5803         var uri = makeURI(aSubject.location.href);
5805         if (OfflineApps._checkUsage(uri)) {
5806           var browserWindow =
5807             this._getBrowserWindowForContentWindow(aSubject);
5808           var browser = this._getBrowserForContentWindow(browserWindow,
5809                                                          aSubject);
5810           OfflineApps._warnUsage(browser, uri);
5811         }
5812       }
5813     } else if (aTopic == "offline-cache-update-completed") {
5814       var cacheUpdate = aSubject.QueryInterface(Ci.nsIOfflineCacheUpdate);
5816       var uri = cacheUpdate.manifestURI;
5817       if (OfflineApps._checkUsage(uri)) {
5818         var browser = this._getBrowserForCacheUpdate(cacheUpdate);
5819         if (browser) {
5820           OfflineApps._warnUsage(browser, cacheUpdate.manifestURI);
5821         }
5822       }
5823     }
5824   }
5827 function WindowIsClosing()
5829   var reallyClose = closeWindow(false, warnAboutClosingWindow);
5830   if (!reallyClose)
5831     return false;
5833   var numBrowsers = gBrowser.browsers.length;
5834   for (let i = 0; reallyClose && i < numBrowsers; ++i) {
5835     let ds = gBrowser.browsers[i].docShell;
5837     if (ds.contentViewer && !ds.contentViewer.permitUnload())
5838       reallyClose = false;
5839   }
5841   return reallyClose;
5845  * Checks if this is the last full *browser* window around. If it is, this will
5846  * be communicated like quitting. Otherwise, we warn about closing multiple tabs.
5847  * @returns true if closing can proceed, false if it got cancelled.
5848  */
5849 function warnAboutClosingWindow() {
5850   // Popups aren't considered full browser windows.
5851   if (!toolbar.visible)
5852     return gBrowser.warnAboutClosingTabs(true);
5854   // Figure out if there's at least one other browser window around.
5855   let foundOtherBrowserWindow = false;
5856   let e = Services.wm.getEnumerator("navigator:browser");
5857   while (e.hasMoreElements() && !foundOtherBrowserWindow) {
5858     let win = e.getNext();
5859     if (win != window && win.toolbar.visible)
5860       foundOtherBrowserWindow = true;
5861   }
5862   if (foundOtherBrowserWindow)
5863     return gBrowser.warnAboutClosingTabs(true);
5865   let os = Services.obs;
5867   let closingCanceled = Cc["@mozilla.org/supports-PRBool;1"].
5868                         createInstance(Ci.nsISupportsPRBool);
5869   os.notifyObservers(closingCanceled,
5870                      "browser-lastwindow-close-requested", null);
5871   if (closingCanceled.data)
5872     return false;
5874   os.notifyObservers(null, "browser-lastwindow-close-granted", null);
5876 #ifdef XP_MACOSX
5877   // OS X doesn't quit the application when the last window is closed, but keeps
5878   // the session alive. Hence don't prompt users to save tabs, but warn about
5879   // closing multiple tabs.
5880   return gBrowser.warnAboutClosingTabs(true);
5881 #else
5882   return true;
5883 #endif
5886 var MailIntegration = {
5887   sendLinkForWindow: function (aWindow) {
5888     this.sendMessage(aWindow.location.href,
5889                      aWindow.document.title);
5890   },
5892   sendMessage: function (aBody, aSubject) {
5893     // generate a mailto url based on the url and the url's title
5894     var mailtoUrl = "mailto:";
5895     if (aBody) {
5896       mailtoUrl += "?body=" + encodeURIComponent(aBody);
5897       mailtoUrl += "&subject=" + encodeURIComponent(aSubject);
5898     }
5900     var uri = makeURI(mailtoUrl);
5902     // now pass this uri to the operating system
5903     this._launchExternalUrl(uri);
5904   },
5906   // a generic method which can be used to pass arbitrary urls to the operating
5907   // system.
5908   // aURL --> a nsIURI which represents the url to launch
5909   _launchExternalUrl: function (aURL) {
5910     var extProtocolSvc =
5911        Cc["@mozilla.org/uriloader/external-protocol-service;1"]
5912          .getService(Ci.nsIExternalProtocolService);
5913     if (extProtocolSvc)
5914       extProtocolSvc.loadUrl(aURL);
5915   }
5918 function BrowserOpenAddonsMgr(aView) {
5919   switchToTabHavingURI("about:addons", true, function(browser) {
5920     if (aView)
5921       browser.contentWindow.wrappedJSObject.loadView(aView);
5922   });
5925 function AddKeywordForSearchField() {
5926   var node = document.popupNode;
5928   var charset = node.ownerDocument.characterSet;
5930   var docURI = makeURI(node.ownerDocument.URL,
5931                        charset);
5933   var formURI = makeURI(node.form.getAttribute("action"),
5934                         charset,
5935                         docURI);
5937   var spec = formURI.spec;
5939   var isURLEncoded =
5940                (node.form.method.toUpperCase() == "POST"
5941                 && (node.form.enctype == "application/x-www-form-urlencoded" ||
5942                     node.form.enctype == ""));
5944   var title = gNavigatorBundle.getFormattedString("addKeywordTitleAutoFill",
5945                                                   [node.ownerDocument.title]);
5946   var description = PlacesUIUtils.getDescriptionFromDocument(node.ownerDocument);
5948   var el, type;
5949   var formData = [];
5951   function escapeNameValuePair(aName, aValue, aIsFormUrlEncoded) {
5952     if (aIsFormUrlEncoded)
5953       return escape(aName + "=" + aValue);
5954     else
5955       return escape(aName) + "=" + escape(aValue);
5956   }
5958   for (var i=0; i < node.form.elements.length; i++) {
5959     el = node.form.elements[i];
5961     if (!el.type) // happens with fieldsets
5962       continue;
5964     if (el == node) {
5965       formData.push((isURLEncoded) ? escapeNameValuePair(el.name, "%s", true) :
5966                                      // Don't escape "%s", just append
5967                                      escapeNameValuePair(el.name, "", false) + "%s");
5968       continue;
5969     }
5971     type = el.type.toLowerCase();
5973     if (((el instanceof HTMLInputElement && el.mozIsTextField(true)) ||
5974         type == "hidden" || type == "textarea") ||
5975         ((type == "checkbox" || type == "radio") && el.checked)) {
5976       formData.push(escapeNameValuePair(el.name, el.value, isURLEncoded));
5977     } else if (el instanceof HTMLSelectElement && el.selectedIndex >= 0) {
5978       for (var j=0; j < el.options.length; j++) {
5979         if (el.options[j].selected)
5980           formData.push(escapeNameValuePair(el.name, el.options[j].value,
5981                                             isURLEncoded));
5982       }
5983     }
5984   }
5986   var postData;
5988   if (isURLEncoded)
5989     postData = formData.join("&");
5990   else
5991     spec += "?" + formData.join("&");
5993   PlacesUIUtils.showMinimalAddBookmarkUI(makeURI(spec), title, description, null,
5994                                          null, null, "", postData, charset);
5997 function SwitchDocumentDirection(aWindow) {
5998   aWindow.document.dir = (aWindow.document.dir == "ltr" ? "rtl" : "ltr");
5999   for (var run = 0; run < aWindow.frames.length; run++)
6000     SwitchDocumentDirection(aWindow.frames[run]);
6003 function getPluginInfo(pluginElement)
6005   var tagMimetype;
6006   var pluginsPage;
6007   if (pluginElement instanceof HTMLAppletElement) {
6008     tagMimetype = "application/x-java-vm";
6009   } else {
6010     if (pluginElement instanceof HTMLObjectElement) {
6011       pluginsPage = pluginElement.getAttribute("codebase");
6012     } else {
6013       pluginsPage = pluginElement.getAttribute("pluginspage");
6014     }
6016     // only attempt if a pluginsPage is defined.
6017     if (pluginsPage) {
6018       var doc = pluginElement.ownerDocument;
6019       var docShell = findChildShell(doc, gBrowser.docShell, null);
6020       try {
6021         pluginsPage = makeURI(pluginsPage, doc.characterSet, docShell.currentURI).spec;
6022       } catch (ex) {
6023         pluginsPage = "";
6024       }
6025     }
6027     tagMimetype = pluginElement.QueryInterface(Components.interfaces.nsIObjectLoadingContent)
6028                                .actualType;
6030     if (tagMimetype == "") {
6031       tagMimetype = pluginElement.type;
6032     }
6033   }
6035   return {mimetype: tagMimetype, pluginsPage: pluginsPage};
6038 var gPluginHandler = {
6040   get CrashSubmit() {
6041     delete this.CrashSubmit;
6042     Cu.import("resource://gre/modules/CrashSubmit.jsm", this);
6043     return this.CrashSubmit;
6044   },
6046   get crashReportHelpURL() {
6047     delete this.crashReportHelpURL;
6048     let url = formatURL("app.support.baseURL", true);
6049     url += "plugin-crashed";
6050     this.crashReportHelpURL = url;
6051     return this.crashReportHelpURL;
6052   },
6054   // Map the plugin's name to a filtered version more suitable for user UI.
6055   makeNicePluginName : function (aName, aFilename) {
6056     if (aName == "Shockwave Flash")
6057       return "Adobe Flash";
6059     // Clean up the plugin name by stripping off any trailing version numbers
6060     // or "plugin". EG, "Foo Bar Plugin 1.23_02" --> "Foo Bar"
6061     let newName = aName.replace(/\bplug-?in\b/i, "").replace(/[\s\d\.\-\_\(\)]+$/, "");
6062     return newName;
6063   },
6065   isTooSmall : function (plugin, overlay) {
6066     // Is the <object>'s size too small to hold what we want to show?
6067     let pluginRect = plugin.getBoundingClientRect();
6068     // XXX bug 446693. The text-shadow on the submitted-report text at
6069     //     the bottom causes scrollHeight to be larger than it should be.
6070     let overflows = (overlay.scrollWidth > pluginRect.width) ||
6071                     (overlay.scrollHeight - 5 > pluginRect.height);
6072     return overflows;
6073   },
6075   addLinkClickCallback: function (linkNode, callbackName /*callbackArgs...*/) {
6076     // XXX just doing (callback)(arg) was giving a same-origin error. bug?
6077     let self = this;
6078     let callbackArgs = Array.prototype.slice.call(arguments).slice(2);
6079     linkNode.addEventListener("click",
6080                               function(evt) {
6081                                 if (!evt.isTrusted)
6082                                   return;
6083                                 evt.preventDefault();
6084                                 if (callbackArgs.length == 0)
6085                                   callbackArgs = [ evt ];
6086                                 (self[callbackName]).apply(self, callbackArgs);
6087                               },
6088                               true);
6090     linkNode.addEventListener("keydown",
6091                               function(evt) {
6092                                 if (!evt.isTrusted)
6093                                   return;
6094                                 if (evt.keyCode == evt.DOM_VK_RETURN) {
6095                                   evt.preventDefault();
6096                                   if (callbackArgs.length == 0)
6097                                     callbackArgs = [ evt ];
6098                                   evt.preventDefault();
6099                                   (self[callbackName]).apply(self, callbackArgs);
6100                                 }
6101                               },
6102                               true);
6103   },
6105   handleEvent : function(event) {
6106     let self = gPluginHandler;
6107     let plugin = event.target;
6109     // We're expecting the target to be a plugin.
6110     if (!(plugin instanceof Ci.nsIObjectLoadingContent))
6111       return;
6113     switch (event.type) {
6114       case "PluginCrashed":
6115         self.pluginInstanceCrashed(plugin, event);
6116         break;
6118       case "PluginNotFound":
6119         // For non-object plugin tags, register a click handler to install the
6120         // plugin. Object tags can, and often do, deal with that themselves,
6121         // so don't stomp on the page developers toes.
6122         if (!(plugin instanceof HTMLObjectElement))
6123           self.addLinkClickCallback(plugin, "installSinglePlugin");
6124         /* FALLTHRU */
6125       case "PluginBlocklisted":
6126       case "PluginOutdated":
6127         let hideBarPrefName = event.type == "PluginOutdated" ?
6128                                 "plugins.hide_infobar_for_outdated_plugin" :
6129                                 "plugins.hide_infobar_for_missing_plugin";
6130         if (gPrefService.getBoolPref(hideBarPrefName))
6131           return;
6133         self.pluginUnavailable(plugin, event.type);
6134         break;
6136       case "PluginDisabled":
6137         self.addLinkClickCallback(plugin, "managePlugins");
6138         break;
6139     }
6140   },
6142   newPluginInstalled : function(event) {
6143     // browser elements are anonymous so we can't just use target.
6144     var browser = event.originalTarget;
6145     // clear the plugin list, now that at least one plugin has been installed
6146     browser.missingPlugins = null;
6148     var notificationBox = gBrowser.getNotificationBox(browser);
6149     var notification = notificationBox.getNotificationWithValue("missing-plugins");
6150     if (notification)
6151       notificationBox.removeNotification(notification);
6153     // reload the browser to make the new plugin show.
6154     browser.reload();
6155   },
6157   // Callback for user clicking on a missing (unsupported) plugin.
6158   installSinglePlugin: function (aEvent) {
6159     var missingPluginsArray = {};
6161     var pluginInfo = getPluginInfo(aEvent.target);
6162     missingPluginsArray[pluginInfo.mimetype] = pluginInfo;
6164     openDialog("chrome://mozapps/content/plugins/pluginInstallerWizard.xul",
6165                "PFSWindow", "chrome,centerscreen,resizable=yes",
6166                {plugins: missingPluginsArray, browser: gBrowser.selectedBrowser});
6167   },
6169   // Callback for user clicking on a disabled plugin
6170   managePlugins: function (aEvent) {
6171     BrowserOpenAddonsMgr("addons://list/plugin");
6172   },
6174   // Callback for user clicking "submit a report" link
6175   submitReport : function(pluginDumpID, browserDumpID) {
6176     // The crash reporter wants a DOM element it can append an IFRAME to,
6177     // which it uses to submit a form. Let's just give it gBrowser.
6178     this.CrashSubmit.submit(pluginDumpID, gBrowser, null, null);
6179     if (browserDumpID)
6180       this.CrashSubmit.submit(browserDumpID, gBrowser, null, null);
6181   },
6183   // Callback for user clicking a "reload page" link
6184   reloadPage: function (browser) {
6185     browser.reload();
6186   },
6188   // Callback for user clicking the help icon
6189   openHelpPage: function () {
6190     openHelpLink("plugin-crashed", false);
6191   },
6194   // event listener for missing/blocklisted/outdated plugins.
6195   pluginUnavailable: function (plugin, eventType) {
6196     let browser = gBrowser.getBrowserForDocument(plugin.ownerDocument
6197                                                        .defaultView.top.document);
6198     if (!browser.missingPlugins)
6199       browser.missingPlugins = {};
6201     var pluginInfo = getPluginInfo(plugin);
6202     browser.missingPlugins[pluginInfo.mimetype] = pluginInfo;
6204     var notificationBox = gBrowser.getNotificationBox(browser);
6206     // Should only display one of these warnings per page.
6207     // In order of priority, they are: outdated > missing > blocklisted
6208     let outdatedNotification = notificationBox.getNotificationWithValue("outdated-plugins");
6209     let blockedNotification  = notificationBox.getNotificationWithValue("blocked-plugins");
6210     let missingNotification  = notificationBox.getNotificationWithValue("missing-plugins");
6212     // If there is already an outdated plugin notification then do nothing
6213     if (outdatedNotification)
6214       return;
6216     function showBlocklistInfo() {
6217       var url = formatURL("extensions.blocklist.detailsURL", true);
6218       gBrowser.loadOneTab(url, {inBackground: false});
6219       return true;
6220     }
6222     function showOutdatedPluginsInfo() {
6223       gPrefService.setBoolPref("plugins.update.notifyUser", false);
6224       var url = formatURL("plugins.update.url", true);
6225       gBrowser.loadOneTab(url, {inBackground: false});
6226       return true;
6227     }
6229     function showPluginsMissing() {
6230       // get the urls of missing plugins
6231       var missingPluginsArray = gBrowser.selectedBrowser.missingPlugins;
6232       if (missingPluginsArray) {
6233         openDialog("chrome://mozapps/content/plugins/pluginInstallerWizard.xul",
6234                    "PFSWindow", "chrome,centerscreen,resizable=yes",
6235                    {plugins: missingPluginsArray, browser: gBrowser.selectedBrowser});
6236       }
6237     }
6239     let notifications = {
6240       PluginBlocklisted : {
6241                             barID   : "blocked-plugins",
6242                             iconURL : "chrome://mozapps/skin/plugins/notifyPluginBlocked.png",
6243                             message : gNavigatorBundle.getString("blockedpluginsMessage.title"),
6244                             buttons : [{
6245                                          label     : gNavigatorBundle.getString("blockedpluginsMessage.infoButton.label"),
6246                                          accessKey : gNavigatorBundle.getString("blockedpluginsMessage.infoButton.accesskey"),
6247                                          popup     : null,
6248                                          callback  : showBlocklistInfo
6249                                        },
6250                                        {
6251                                          label     : gNavigatorBundle.getString("blockedpluginsMessage.searchButton.label"),
6252                                          accessKey : gNavigatorBundle.getString("blockedpluginsMessage.searchButton.accesskey"),
6253                                          popup     : null,
6254                                          callback  : showOutdatedPluginsInfo
6255                                       }],
6256                           },
6257       PluginOutdated    : {
6258                             barID   : "outdated-plugins",
6259                             iconURL : "chrome://mozapps/skin/plugins/notifyPluginOutdated.png",
6260                             message : gNavigatorBundle.getString("outdatedpluginsMessage.title"),
6261                             buttons : [{
6262                                          label     : gNavigatorBundle.getString("outdatedpluginsMessage.updateButton.label"),
6263                                          accessKey : gNavigatorBundle.getString("outdatedpluginsMessage.updateButton.accesskey"),
6264                                          popup     : null,
6265                                          callback  : showOutdatedPluginsInfo
6266                                       }],
6267                           },
6268       PluginNotFound    : {
6269                             barID   : "missing-plugins",
6270                             iconURL : "chrome://mozapps/skin/plugins/notifyPluginGeneric.png",
6271                             message : gNavigatorBundle.getString("missingpluginsMessage.title"),
6272                             buttons : [{
6273                                          label     : gNavigatorBundle.getString("missingpluginsMessage.button.label"),
6274                                          accessKey : gNavigatorBundle.getString("missingpluginsMessage.button.accesskey"),
6275                                          popup     : null,
6276                                          callback  : showPluginsMissing
6277                                       }],
6278                           }
6279     };
6281     if (eventType == "PluginBlocklisted") {
6282       if (blockedNotification || missingNotification)
6283         return;
6284     }
6285     else if (eventType == "PluginOutdated") {
6286       // Cancel any notification about blocklisting/missing plugins
6287       if (blockedNotification)
6288         blockedNotification.close();
6289       if (missingNotification)
6290         missingNotification.close();
6291     }
6292     else if (eventType == "PluginNotFound") {
6293       if (missingNotification)
6294         return;
6296       // Cancel any notification about blocklisting plugins
6297       if (blockedNotification)
6298         blockedNotification.close();
6299     }
6301     let notify = notifications[eventType];
6302     notificationBox.appendNotification(notify.message, notify.barID, notify.iconURL,
6303                                        notificationBox.PRIORITY_WARNING_MEDIUM,
6304                                        notify.buttons);
6305   },
6307   // Crashed-plugin observer. Notified once per plugin crash, before events
6308   // are dispatched to individual plugin instances.
6309   pluginCrashed : function(subject, topic, data) {
6310     let propertyBag = subject;
6311     if (!(propertyBag instanceof Ci.nsIPropertyBag2) ||
6312         !(propertyBag instanceof Ci.nsIWritablePropertyBag2))
6313      return;
6315 #ifdef MOZ_CRASHREPORTER
6316     let pluginDumpID = propertyBag.getPropertyAsAString("pluginDumpID");
6317     let browserDumpID= propertyBag.getPropertyAsAString("browserDumpID");
6318     let shouldSubmit = gCrashReporter.submitReports;
6319     let doPrompt     = true; // XXX followup to get via gCrashReporter
6321     // Submit automatically when appropriate.
6322     if (pluginDumpID && shouldSubmit && !doPrompt) {
6323       this.submitReport(pluginDumpID, browserDumpID);
6324       // Submission is async, so we can't easily show failure UI.
6325       propertyBag.setPropertyAsBool("submittedCrashReport", true);
6326     }
6327 #endif
6328   },
6330   // Crashed-plugin event listener. Called for every instance of a
6331   // plugin in content.
6332   pluginInstanceCrashed: function (plugin, aEvent) {
6333     // Ensure the plugin and event are of the right type.
6334     if (!(aEvent instanceof Ci.nsIDOMDataContainerEvent))
6335       return;
6337     let submittedReport = aEvent.getData("submittedCrashReport");
6338     let doPrompt        = true; // XXX followup for .getData("doPrompt");
6339     let submitReports   = true; // XXX followup for .getData("submitReports");
6340     let pluginName      = aEvent.getData("pluginName");
6341     let pluginFilename  = aEvent.getData("pluginFilename");
6342     let pluginDumpID    = aEvent.getData("pluginDumpID");
6343     let browserDumpID   = aEvent.getData("browserDumpID");
6345     // Remap the plugin name to a more user-presentable form.
6346     pluginName = this.makeNicePluginName(pluginName, pluginFilename);
6348     // Force a style flush, so that we ensure our binding is attached.
6349     plugin.clientTop;
6351     let messageString = gNavigatorBundle.getFormattedString("crashedpluginsMessage.title", [pluginName]);
6353     //
6354     // Configure the crashed-plugin placeholder.
6355     //
6356     let doc = plugin.ownerDocument;
6357     let overlay = doc.getAnonymousElementByAttribute(plugin, "class", "mainBox");
6359     // The binding has role="link" here, since missing/disabled/blocked
6360     // plugin UI has a onclick handler on the whole thing. This isn't needed
6361     // for the plugin-crashed UI, because we use actual HTML links in the text.
6362     overlay.removeAttribute("role");
6364     let statusDiv = doc.getAnonymousElementByAttribute(plugin, "class", "submitStatus");
6365 #ifdef MOZ_CRASHREPORTER
6366     let status;
6368     // Determine which message to show regarding crash reports.
6369     if (submittedReport) { // submitReports && !doPrompt, handled in observer
6370       status = "submitted";
6371     }
6372     else if (!submitReports && !doPrompt) {
6373       status = "noSubmit";
6374     }
6375     else { // doPrompt
6376       status = "please";
6377       // XXX can we make the link target actually be blank?
6378       let pleaseLink = doc.getAnonymousElementByAttribute(
6379                             plugin, "class", "pleaseSubmitLink");
6380       this.addLinkClickCallback(pleaseLink, "submitReport",
6381                                 pluginDumpID, browserDumpID);
6382     }
6384     // If we don't have a minidumpID, we can't (or didn't) submit anything.
6385     // This can happen if the plugin is killed from the task manager.
6386     if (!pluginDumpID) {
6387         status = "noReport";
6388     }
6390     statusDiv.setAttribute("status", status);
6392     let bottomLinks = doc.getAnonymousElementByAttribute(plugin, "class", "msg msgBottomLinks");
6393     bottomLinks.style.display = "block";
6394     let helpIcon = doc.getAnonymousElementByAttribute(plugin, "class", "helpIcon");
6395     this.addLinkClickCallback(helpIcon, "openHelpPage");
6397     // If we're showing the link to manually trigger report submission, we'll
6398     // want to be able to update all the instances of the UI for this crash to
6399     // show an updated message when a report is submitted.
6400     if (doPrompt) {
6401       let observer = {
6402         QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
6403                                                Ci.nsISupportsWeakReference]),
6404         observe : function(subject, topic, data) {
6405           let propertyBag = subject;
6406           if (!(propertyBag instanceof Ci.nsIPropertyBag2))
6407             return;
6408           // Ignore notifications for other crashes.
6409           if (propertyBag.get("minidumpID") != pluginDumpID)
6410             return;
6411           statusDiv.setAttribute("status", data);
6412         },
6414         handleEvent : function(event) {
6415             // Not expected to be called, just here for the closure.
6416         }
6417       }
6419       // Use a weak reference, so we don't have to remove it...
6420       Services.obs.addObserver(observer, "crash-report-status", true);
6421       // ...alas, now we need something to hold a strong reference to prevent
6422       // it from being GC. But I don't want to manually manage the reference's
6423       // lifetime (which should be no greater than the page).
6424       // Clever solution? Use a closue with an event listener on the document.
6425       // When the doc goes away, so do the listener references and the closure.
6426       doc.addEventListener("mozCleverClosureHack", observer, false);
6427     }
6428 #endif
6430     let crashText = doc.getAnonymousElementByAttribute(plugin, "class", "msg msgCrashed");
6431     crashText.textContent = messageString;
6433     let browser = gBrowser.getBrowserForDocument(doc.defaultView.top.document);
6435     let link = doc.getAnonymousElementByAttribute(plugin, "class", "reloadLink");
6436     this.addLinkClickCallback(link, "reloadPage", browser);
6438     let notificationBox = gBrowser.getNotificationBox(browser);
6440     // Is the <object>'s size too small to hold what we want to show?
6441     if (this.isTooSmall(plugin, overlay)) {
6442         // Hide the overlay's contents. Use visibility style, so that it
6443         // doesn't collapse down to 0x0.
6444         overlay.style.visibility = "hidden";
6445         // If another plugin on the page was large enough to show our UI, we
6446         // don't want to show a notification bar.
6447         if (!doc.mozNoPluginCrashedNotification)
6448           showNotificationBar(pluginDumpID, browserDumpID);
6449     } else {
6450         // If a previous plugin on the page was too small and resulted in
6451         // adding a notification bar, then remove it because this plugin
6452         // instance it big enough to serve as in-content notification.
6453         hideNotificationBar();
6454         doc.mozNoPluginCrashedNotification = true;
6455     }
6457     function hideNotificationBar() {
6458       let notification = notificationBox.getNotificationWithValue("plugin-crashed");
6459       if (notification)
6460         notificationBox.removeNotification(notification, true);
6461     }
6463     function showNotificationBar(pluginDumpID, browserDumpID) {
6464       // If there's already an existing notification bar, don't do anything.
6465       let notification = notificationBox.getNotificationWithValue("plugin-crashed");
6466       if (notification)
6467         return;
6469       // Configure the notification bar
6470       let priority = notificationBox.PRIORITY_WARNING_MEDIUM;
6471       let iconURL = "chrome://mozapps/skin/plugins/notifyPluginCrashed.png";
6472       let reloadLabel = gNavigatorBundle.getString("crashedpluginsMessage.reloadButton.label");
6473       let reloadKey   = gNavigatorBundle.getString("crashedpluginsMessage.reloadButton.accesskey");
6474       let submitLabel = gNavigatorBundle.getString("crashedpluginsMessage.submitButton.label");
6475       let submitKey   = gNavigatorBundle.getString("crashedpluginsMessage.submitButton.accesskey");
6477       let buttons = [{
6478         label: reloadLabel,
6479         accessKey: reloadKey,
6480         popup: null,
6481         callback: function() { browser.reload(); },
6482       }];
6483 #ifdef MOZ_CRASHREPORTER
6484       let submitButton = {
6485         label: submitLabel,
6486         accessKey: submitKey,
6487         popup: null,
6488           callback: function() { gPluginHandler.submitReport(pluginDumpID, browserDumpID); },
6489       };
6490       if (pluginDumpID)
6491         buttons.push(submitButton);
6492 #endif
6494       let notification = notificationBox.appendNotification(messageString, "plugin-crashed",
6495                                                             iconURL, priority, buttons);
6497       // Add the "learn more" link.
6498       let XULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
6499       let link = notification.ownerDocument.createElementNS(XULNS, "label");
6500       link.className = "text-link";
6501       link.setAttribute("value", gNavigatorBundle.getString("crashedpluginsMessage.learnMore"));
6502       link.href = gPluginHandler.crashReportHelpURL;
6503       let description = notification.ownerDocument.getAnonymousElementByAttribute(notification, "anonid", "messageText");
6504       description.appendChild(link);
6506       // Remove the notfication when the page is reloaded.
6507       doc.defaultView.top.addEventListener("unload", function() {
6508         notificationBox.removeNotification(notification);
6509       }, false);
6510     }
6512   }
6515 function convertFromUnicode(charset, str)
6517   try {
6518     var unicodeConverter = Components
6519        .classes["@mozilla.org/intl/scriptableunicodeconverter"]
6520        .createInstance(Components.interfaces.nsIScriptableUnicodeConverter);
6521     unicodeConverter.charset = charset;
6522     str = unicodeConverter.ConvertFromUnicode(str);
6523     return str + unicodeConverter.Finish();
6524   } catch(ex) {
6525     return null;
6526   }
6530  * The Feed Handler object manages discovery of RSS/ATOM feeds in web pages
6531  * and shows UI when they are discovered.
6532  */
6533 var FeedHandler = {
6534   /**
6535    * The click handler for the Feed icon in the location bar. Opens the
6536    * subscription page if user is not given a choice of feeds.
6537    * (Otherwise the list of available feeds will be presented to the
6538    * user in a popup menu.)
6539    */
6540   onFeedButtonClick: function(event) {
6541     event.stopPropagation();
6543     if (event.target.hasAttribute("feed") &&
6544         event.eventPhase == Event.AT_TARGET &&
6545         (event.button == 0 || event.button == 1)) {
6546         this.subscribeToFeed(null, event);
6547     }
6548   },
6550   /**
6551    * Called when the user clicks on the Feed icon in the location bar.
6552    * Builds a menu of unique feeds associated with the page, and if there
6553    * is only one, shows the feed inline in the browser window.
6554    * @param   menuPopup
6555    *          The feed list menupopup to be populated.
6556    * @returns true if the menu should be shown, false if there was only
6557    *          one feed and the feed should be shown inline in the browser
6558    *          window (do not show the menupopup).
6559    */
6560   buildFeedList: function(menuPopup) {
6561     var feeds = gBrowser.selectedBrowser.feeds;
6562     if (feeds == null) {
6563       // XXX hack -- menu opening depends on setting of an "open"
6564       // attribute, and the menu refuses to open if that attribute is
6565       // set (because it thinks it's already open).  onpopupshowing gets
6566       // called after the attribute is unset, and it doesn't get unset
6567       // if we return false.  so we unset it here; otherwise, the menu
6568       // refuses to work past this point.
6569       menuPopup.parentNode.removeAttribute("open");
6570       return false;
6571     }
6573     while (menuPopup.firstChild)
6574       menuPopup.removeChild(menuPopup.firstChild);
6576     if (feeds.length == 1) {
6577       var feedButton = document.getElementById("feed-button");
6578       if (feedButton)
6579         feedButton.setAttribute("feed", feeds[0].href);
6580       return false;
6581     }
6583     // Build the menu showing the available feed choices for viewing.
6584     for (var i = 0; i < feeds.length; ++i) {
6585       var feedInfo = feeds[i];
6586       var menuItem = document.createElement("menuitem");
6587       var baseTitle = feedInfo.title || feedInfo.href;
6588       var labelStr = gNavigatorBundle.getFormattedString("feedShowFeedNew", [baseTitle]);
6589       menuItem.setAttribute("class", "feed-menuitem");
6590       menuItem.setAttribute("label", labelStr);
6591       menuItem.setAttribute("feed", feedInfo.href);
6592       menuItem.setAttribute("tooltiptext", feedInfo.href);
6593       menuItem.setAttribute("crop", "center");
6594       menuPopup.appendChild(menuItem);
6595     }
6596     return true;
6597   },
6599   /**
6600    * Subscribe to a given feed.  Called when
6601    *   1. Page has a single feed and user clicks feed icon in location bar
6602    *   2. Page has a single feed and user selects Subscribe menu item
6603    *   3. Page has multiple feeds and user selects from feed icon popup
6604    *   4. Page has multiple feeds and user selects from Subscribe submenu
6605    * @param   href
6606    *          The feed to subscribe to. May be null, in which case the
6607    *          event target's feed attribute is examined.
6608    * @param   event
6609    *          The event this method is handling. Used to decide where
6610    *          to open the preview UI. (Optional, unless href is null)
6611    */
6612   subscribeToFeed: function(href, event) {
6613     // Just load the feed in the content area to either subscribe or show the
6614     // preview UI
6615     if (!href)
6616       href = event.target.getAttribute("feed");
6617     urlSecurityCheck(href, gBrowser.contentPrincipal,
6618                      Ci.nsIScriptSecurityManager.DISALLOW_INHERIT_PRINCIPAL);
6619     var feedURI = makeURI(href, document.characterSet);
6620     // Use the feed scheme so X-Moz-Is-Feed will be set
6621     // The value doesn't matter
6622     if (/^https?/.test(feedURI.scheme))
6623       href = "feed:" + href;
6624     this.loadFeed(href, event);
6625   },
6627   loadFeed: function(href, event) {
6628     var feeds = gBrowser.selectedBrowser.feeds;
6629     try {
6630       openUILink(href, event, false, true, false, null);
6631     }
6632     finally {
6633       // We might default to a livebookmarks modal dialog,
6634       // so reset that if the user happens to click it again
6635       gBrowser.selectedBrowser.feeds = feeds;
6636     }
6637   },
6639   /**
6640    * Update the browser UI to show whether or not feeds are available when
6641    * a page is loaded or the user switches tabs to a page that has feeds.
6642    */
6643   updateFeeds: function() {
6644     var feedButton = document.getElementById("feed-button");
6645     if (!this._feedMenuitem)
6646       this._feedMenuitem = document.getElementById("subscribeToPageMenuitem");
6647     if (!this._feedMenupopup)
6648       this._feedMenupopup = document.getElementById("subscribeToPageMenupopup");
6650     var feeds = gBrowser.selectedBrowser.feeds;
6651     if (!feeds || feeds.length == 0) {
6652       if (feedButton) {
6653         feedButton.collapsed = true;
6654         feedButton.removeAttribute("feed");
6655       }
6656       this._feedMenuitem.setAttribute("disabled", "true");
6657       this._feedMenupopup.setAttribute("hidden", "true");
6658       this._feedMenuitem.removeAttribute("hidden");
6659     } else {
6660       if (feedButton)
6661         feedButton.collapsed = false;
6663       if (feeds.length > 1) {
6664         this._feedMenuitem.setAttribute("hidden", "true");
6665         this._feedMenupopup.removeAttribute("hidden");
6666         if (feedButton)
6667           feedButton.removeAttribute("feed");
6668       } else {
6669         if (feedButton)
6670           feedButton.setAttribute("feed", feeds[0].href);
6672         this._feedMenuitem.setAttribute("feed", feeds[0].href);
6673         this._feedMenuitem.removeAttribute("disabled");
6674         this._feedMenuitem.removeAttribute("hidden");
6675         this._feedMenupopup.setAttribute("hidden", "true");
6676       }
6677     }
6678   },
6680   addFeed: function(link, targetDoc) {
6681     // find which tab this is for, and set the attribute on the browser
6682     var browserForLink = gBrowser.getBrowserForDocument(targetDoc);
6683     if (!browserForLink) {
6684       // ignore feeds loaded in subframes (see bug 305472)
6685       return;
6686     }
6688     if (!browserForLink.feeds)
6689       browserForLink.feeds = [];
6691     browserForLink.feeds.push({ href: link.href, title: link.title });
6693     if (browserForLink == gBrowser.selectedBrowser) {
6694       var feedButton = document.getElementById("feed-button");
6695       if (feedButton)
6696         feedButton.collapsed = false;
6697     }
6698   }
6702  * Re-open a closed tab.
6703  * @param aIndex
6704  *        The index of the tab (via nsSessionStore.getClosedTabData)
6705  * @returns a reference to the reopened tab.
6706  */
6707 function undoCloseTab(aIndex) {
6708   // wallpaper patch to prevent an unnecessary blank tab (bug 343895)
6709   var blankTabToRemove = null;
6710   if (gBrowser.tabs.length == 1 &&
6711       !gPrefService.getBoolPref("browser.tabs.autoHide") &&
6712       isTabEmpty(gBrowser.selectedTab))
6713     blankTabToRemove = gBrowser.selectedTab;
6715   var tab = null;
6716   var ss = Cc["@mozilla.org/browser/sessionstore;1"].
6717            getService(Ci.nsISessionStore);
6718   if (ss.getClosedTabCount(window) > (aIndex || 0)) {
6719     tab = ss.undoCloseTab(window, aIndex || 0);
6721     if (blankTabToRemove)
6722       gBrowser.removeTab(blankTabToRemove);
6723   }
6725   return tab;
6729  * Re-open a closed window.
6730  * @param aIndex
6731  *        The index of the window (via nsSessionStore.getClosedWindowData)
6732  * @returns a reference to the reopened window.
6733  */
6734 function undoCloseWindow(aIndex) {
6735   let ss = Cc["@mozilla.org/browser/sessionstore;1"].
6736            getService(Ci.nsISessionStore);
6737   let window = null;
6738   if (ss.getClosedWindowCount() > (aIndex || 0))
6739     window = ss.undoCloseWindow(aIndex || 0);
6741   return window;
6745  * Determines if a tab is "empty", usually used in the context of determining
6746  * if it's ok to close the tab.
6747  */
6748 function isTabEmpty(aTab) {
6749   let browser = aTab.linkedBrowser;
6750   return browser.sessionHistory.count < 2 &&
6751          browser.currentURI.spec == "about:blank" &&
6752          !browser.contentDocument.body.hasChildNodes() &&
6753          !aTab.hasAttribute("busy");
6756 #ifdef MOZ_SERVICES_SYNC
6757 function BrowserOpenSyncTabs() {
6758   switchToTabHavingURI("about:sync-tabs", true);
6760 #endif
6763  * Format a URL
6764  * eg:
6765  * echo formatURL("https://addons.mozilla.org/%LOCALE%/%APP%/%VERSION%/");
6766  * > https://addons.mozilla.org/en-US/firefox/3.0a1/
6768  * Currently supported built-ins are LOCALE, APP, and any value from nsIXULAppInfo, uppercased.
6769  */
6770 function formatURL(aFormat, aIsPref) {
6771   var formatter = Cc["@mozilla.org/toolkit/URLFormatterService;1"].getService(Ci.nsIURLFormatter);
6772   return aIsPref ? formatter.formatURLPref(aFormat) : formatter.formatURL(aFormat);
6776  * This also takes care of updating the command enabled-state when tabs are
6777  * created or removed.
6778  */
6779 var gBookmarkAllTabsHandler = {
6780   init: function () {
6781     this._command = document.getElementById("Browser:BookmarkAllTabs");
6782     gBrowser.tabContainer.addEventListener("TabOpen", this, true);
6783     gBrowser.tabContainer.addEventListener("TabClose", this, true);
6784     this._updateCommandState();
6785   },
6787   _updateCommandState: function BATH__updateCommandState(aTabClose) {
6788     var numTabs = gBrowser.tabs.length;
6790     // The TabClose event is fired before the tab is removed from the DOM
6791     if (aTabClose)
6792       numTabs--;
6794     if (numTabs > 1)
6795       this._command.removeAttribute("disabled");
6796     else
6797       this._command.setAttribute("disabled", "true");
6798   },
6800   doCommand: function BATH_doCommand() {
6801     PlacesCommandHook.bookmarkCurrentPages();
6802   },
6804   // nsIDOMEventListener
6805   handleEvent: function(aEvent) {
6806     this._updateCommandState(aEvent.type == "TabClose");
6807   }
6811  * Utility object to handle manipulations of the identity indicators in the UI
6812  */
6813 var gIdentityHandler = {
6814   // Mode strings used to control CSS display
6815   IDENTITY_MODE_IDENTIFIED       : "verifiedIdentity", // High-quality identity information
6816   IDENTITY_MODE_DOMAIN_VERIFIED  : "verifiedDomain",   // Minimal SSL CA-signed domain verification
6817   IDENTITY_MODE_UNKNOWN          : "unknownIdentity",  // No trusted identity information
6818   IDENTITY_MODE_MIXED_CONTENT    : "unknownIdentity mixedContent",  // SSL with unauthenticated content
6820   // Cache the most recent SSLStatus and Location seen in checkIdentity
6821   _lastStatus : null,
6822   _lastLocation : null,
6824   // smart getters
6825   get _encryptionLabel () {
6826     delete this._encryptionLabel;
6827     this._encryptionLabel = {};
6828     this._encryptionLabel[this.IDENTITY_MODE_DOMAIN_VERIFIED] =
6829       gNavigatorBundle.getString("identity.encrypted");
6830     this._encryptionLabel[this.IDENTITY_MODE_IDENTIFIED] =
6831       gNavigatorBundle.getString("identity.encrypted");
6832     this._encryptionLabel[this.IDENTITY_MODE_UNKNOWN] =
6833       gNavigatorBundle.getString("identity.unencrypted");
6834     this._encryptionLabel[this.IDENTITY_MODE_MIXED_CONTENT] =
6835       gNavigatorBundle.getString("identity.mixed_content");
6836     return this._encryptionLabel;
6837   },
6838   get _identityPopup () {
6839     delete this._identityPopup;
6840     return this._identityPopup = document.getElementById("identity-popup");
6841   },
6842   get _identityBox () {
6843     delete this._identityBox;
6844     return this._identityBox = document.getElementById("identity-box");
6845   },
6846   get _identityPopupContentBox () {
6847     delete this._identityPopupContentBox;
6848     return this._identityPopupContentBox =
6849       document.getElementById("identity-popup-content-box");
6850   },
6851   get _identityPopupContentHost () {
6852     delete this._identityPopupContentHost;
6853     return this._identityPopupContentHost =
6854       document.getElementById("identity-popup-content-host");
6855   },
6856   get _identityPopupContentOwner () {
6857     delete this._identityPopupContentOwner;
6858     return this._identityPopupContentOwner =
6859       document.getElementById("identity-popup-content-owner");
6860   },
6861   get _identityPopupContentSupp () {
6862     delete this._identityPopupContentSupp;
6863     return this._identityPopupContentSupp =
6864       document.getElementById("identity-popup-content-supplemental");
6865   },
6866   get _identityPopupContentVerif () {
6867     delete this._identityPopupContentVerif;
6868     return this._identityPopupContentVerif =
6869       document.getElementById("identity-popup-content-verifier");
6870   },
6871   get _identityPopupEncLabel () {
6872     delete this._identityPopupEncLabel;
6873     return this._identityPopupEncLabel =
6874       document.getElementById("identity-popup-encryption-label");
6875   },
6876   get _identityIconLabel () {
6877     delete this._identityIconLabel;
6878     return this._identityIconLabel = document.getElementById("identity-icon-label");
6879   },
6880   get _overrideService () {
6881     delete this._overrideService;
6882     return this._overrideService = Cc["@mozilla.org/security/certoverride;1"]
6883                                      .getService(Ci.nsICertOverrideService);
6884   },
6885   get _identityIconCountryLabel () {
6886     delete this._identityIconCountryLabel;
6887     return this._identityIconCountryLabel = document.getElementById("identity-icon-country-label");
6888   },
6890   /**
6891    * Rebuild cache of the elements that may or may not exist depending
6892    * on whether there's a location bar.
6893    */
6894   _cacheElements : function() {
6895     delete this._identityBox;
6896     delete this._identityIconLabel;
6897     delete this._identityIconCountryLabel;
6898     this._identityBox = document.getElementById("identity-box");
6899     this._identityIconLabel = document.getElementById("identity-icon-label");
6900     this._identityIconCountryLabel = document.getElementById("identity-icon-country-label");
6901   },
6903   /**
6904    * Handler for mouseclicks on the "More Information" button in the
6905    * "identity-popup" panel.
6906    */
6907   handleMoreInfoClick : function(event) {
6908     displaySecurityInfo();
6909     event.stopPropagation();
6910   },
6912   /**
6913    * Helper to parse out the important parts of _lastStatus (of the SSL cert in
6914    * particular) for use in constructing identity UI strings
6915   */
6916   getIdentityData : function() {
6917     var result = {};
6918     var status = this._lastStatus.QueryInterface(Components.interfaces.nsISSLStatus);
6919     var cert = status.serverCert;
6921     // Human readable name of Subject
6922     result.subjectOrg = cert.organization;
6924     // SubjectName fields, broken up for individual access
6925     if (cert.subjectName) {
6926       result.subjectNameFields = {};
6927       cert.subjectName.split(",").forEach(function(v) {
6928         var field = v.split("=");
6929         this[field[0]] = field[1];
6930       }, result.subjectNameFields);
6932       // Call out city, state, and country specifically
6933       result.city = result.subjectNameFields.L;
6934       result.state = result.subjectNameFields.ST;
6935       result.country = result.subjectNameFields.C;
6936     }
6938     // Human readable name of Certificate Authority
6939     result.caOrg =  cert.issuerOrganization || cert.issuerCommonName;
6940     result.cert = cert;
6942     return result;
6943   },
6945   /**
6946    * Determine the identity of the page being displayed by examining its SSL cert
6947    * (if available) and, if necessary, update the UI to reflect this.  Intended to
6948    * be called by onSecurityChange
6949    *
6950    * @param PRUint32 state
6951    * @param JS Object location that mirrors an nsLocation (i.e. has .host and
6952    *                           .hostname and .port)
6953    */
6954   checkIdentity : function(state, location) {
6955     var currentStatus = gBrowser.securityUI
6956                                 .QueryInterface(Components.interfaces.nsISSLStatusProvider)
6957                                 .SSLStatus;
6958     this._lastStatus = currentStatus;
6959     this._lastLocation = location;
6961     let nsIWebProgressListener = Ci.nsIWebProgressListener;
6962     if (state & nsIWebProgressListener.STATE_IDENTITY_EV_TOPLEVEL)
6963       this.setMode(this.IDENTITY_MODE_IDENTIFIED);
6964     else if (state & nsIWebProgressListener.STATE_SECURE_HIGH)
6965       this.setMode(this.IDENTITY_MODE_DOMAIN_VERIFIED);
6966     else if (state & nsIWebProgressListener.STATE_IS_BROKEN)
6967       this.setMode(this.IDENTITY_MODE_MIXED_CONTENT);
6968     else
6969       this.setMode(this.IDENTITY_MODE_UNKNOWN);
6970   },
6972   /**
6973    * Return the eTLD+1 version of the current hostname
6974    */
6975   getEffectiveHost : function() {
6976     // Cache the eTLDService if this is our first time through
6977     if (!this._eTLDService)
6978       this._eTLDService = Cc["@mozilla.org/network/effective-tld-service;1"]
6979                          .getService(Ci.nsIEffectiveTLDService);
6980     try {
6981       return this._eTLDService.getBaseDomainFromHost(this._lastLocation.hostname);
6982     } catch (e) {
6983       // If something goes wrong (e.g. hostname is an IP address) just fail back
6984       // to the full domain.
6985       return this._lastLocation.hostname;
6986     }
6987   },
6989   /**
6990    * Update the UI to reflect the specified mode, which should be one of the
6991    * IDENTITY_MODE_* constants.
6992    */
6993   setMode : function(newMode) {
6994     if (!this._identityBox) {
6995       // No identity box means the identity box is not visible, in which
6996       // case there's nothing to do.
6997       return;
6998     }
7000     this._identityBox.className = newMode;
7001     this.setIdentityMessages(newMode);
7003     // Update the popup too, if it's open
7004     if (this._identityPopup.state == "open")
7005       this.setPopupMessages(newMode);
7006   },
7008   /**
7009    * Set up the messages for the primary identity UI based on the specified mode,
7010    * and the details of the SSL cert, where applicable
7011    *
7012    * @param newMode The newly set identity mode.  Should be one of the IDENTITY_MODE_* constants.
7013    */
7014   setIdentityMessages : function(newMode) {
7015     if (newMode == this.IDENTITY_MODE_DOMAIN_VERIFIED) {
7016       var iData = this.getIdentityData();
7018       // It would be sort of nice to use the CN= field in the cert, since that's
7019       // typically what we want here, but thanks to x509 certs being extensible,
7020       // it's not the only place you have to check, there can be more than one domain,
7021       // et cetera, ad nauseum.  We know the cert is valid for location.host, so
7022       // let's just use that. Check the pref to determine how much of the verified
7023       // hostname to show
7024       var icon_label = "";
7025       var icon_country_label = "";
7026       var icon_labels_dir = "ltr";
7027       switch (gPrefService.getIntPref("browser.identity.ssl_domain_display")) {
7028         case 2 : // Show full domain
7029           icon_label = this._lastLocation.hostname;
7030           break;
7031         case 1 : // Show eTLD.
7032           icon_label = this.getEffectiveHost();
7033       }
7035       // We need a port number for all lookups.  If one hasn't been specified, use
7036       // the https default
7037       var lookupHost = this._lastLocation.host;
7038       if (lookupHost.indexOf(':') < 0)
7039         lookupHost += ":443";
7041       // Verifier is either the CA Org, for a normal cert, or a special string
7042       // for certs that are trusted because of a security exception.
7043       var tooltip = gNavigatorBundle.getFormattedString("identity.identified.verifier",
7044                                                         [iData.caOrg]);
7046       // Check whether this site is a security exception. XPConnect does the right
7047       // thing here in terms of converting _lastLocation.port from string to int, but
7048       // the overrideService doesn't like undefined ports, so make sure we have
7049       // something in the default case (bug 432241).
7050       if (this._overrideService.hasMatchingOverride(this._lastLocation.hostname,
7051                                                     (this._lastLocation.port || 443),
7052                                                     iData.cert, {}, {}))
7053         tooltip = gNavigatorBundle.getString("identity.identified.verified_by_you");
7054     }
7055     else if (newMode == this.IDENTITY_MODE_IDENTIFIED) {
7056       // If it's identified, then we can populate the dialog with credentials
7057       iData = this.getIdentityData();
7058       tooltip = gNavigatorBundle.getFormattedString("identity.identified.verifier",
7059                                                     [iData.caOrg]);
7060       icon_label = iData.subjectOrg;
7061       if (iData.country)
7062         icon_country_label = "(" + iData.country + ")";
7063       // If the organization name starts with an RTL character, then
7064       // swap the positions of the organization and country code labels.
7065       // The Unicode ranges reflect the definition of the UCS2_CHAR_IS_BIDI
7066       // macro in intl/unicharutil/util/nsBidiUtils.h. When bug 218823 gets
7067       // fixed, this test should be replaced by one adhering to the
7068       // Unicode Bidirectional Algorithm proper (at the paragraph level).
7069       icon_labels_dir = /^[\u0590-\u08ff\ufb1d-\ufdff\ufe70-\ufefc]/.test(icon_label) ?
7070                         "rtl" : "ltr";
7071     }
7072     else {
7073       tooltip = gNavigatorBundle.getString("identity.unknown.tooltip");
7074       icon_label = "";
7075       icon_country_label = "";
7076       icon_labels_dir = "ltr";
7077     }
7079     // Push the appropriate strings out to the UI
7080     this._identityBox.tooltipText = tooltip;
7081     this._identityIconLabel.value = icon_label;
7082     this._identityIconCountryLabel.value = icon_country_label;
7083     // Set cropping and direction
7084     this._identityIconLabel.crop = icon_country_label ? "end" : "center";
7085     this._identityIconLabel.parentNode.style.direction = icon_labels_dir;
7086     // Hide completely if the organization label is empty
7087     this._identityIconLabel.parentNode.collapsed = icon_label ? false : true;
7088   },
7090   /**
7091    * Set up the title and content messages for the identity message popup,
7092    * based on the specified mode, and the details of the SSL cert, where
7093    * applicable
7094    *
7095    * @param newMode The newly set identity mode.  Should be one of the IDENTITY_MODE_* constants.
7096    */
7097   setPopupMessages : function(newMode) {
7099     this._identityPopup.className = newMode;
7100     this._identityPopupContentBox.className = newMode;
7102     // Set the static strings up front
7103     this._identityPopupEncLabel.textContent = this._encryptionLabel[newMode];
7105     // Initialize the optional strings to empty values
7106     var supplemental = "";
7107     var verifier = "";
7109     if (newMode == this.IDENTITY_MODE_DOMAIN_VERIFIED) {
7110       var iData = this.getIdentityData();
7111       var host = this.getEffectiveHost();
7112       var owner = gNavigatorBundle.getString("identity.ownerUnknown2");
7113       verifier = this._identityBox.tooltipText;
7114       supplemental = "";
7115     }
7116     else if (newMode == this.IDENTITY_MODE_IDENTIFIED) {
7117       // If it's identified, then we can populate the dialog with credentials
7118       iData = this.getIdentityData();
7119       host = this.getEffectiveHost();
7120       owner = iData.subjectOrg;
7121       verifier = this._identityBox.tooltipText;
7123       // Build an appropriate supplemental block out of whatever location data we have
7124       if (iData.city)
7125         supplemental += iData.city + "\n";
7126       if (iData.state && iData.country)
7127         supplemental += gNavigatorBundle.getFormattedString("identity.identified.state_and_country",
7128                                                             [iData.state, iData.country]);
7129       else if (iData.state) // State only
7130         supplemental += iData.state;
7131       else if (iData.country) // Country only
7132         supplemental += iData.country;
7133     }
7134     else {
7135       // These strings will be hidden in CSS anyhow
7136       host = "";
7137       owner = "";
7138     }
7140     // Push the appropriate strings out to the UI
7141     this._identityPopupContentHost.textContent = host;
7142     this._identityPopupContentOwner.textContent = owner;
7143     this._identityPopupContentSupp.textContent = supplemental;
7144     this._identityPopupContentVerif.textContent = verifier;
7145   },
7147   hideIdentityPopup : function() {
7148     this._identityPopup.hidePopup();
7149   },
7151   /**
7152    * Click handler for the identity-box element in primary chrome.
7153    */
7154   handleIdentityButtonEvent : function(event) {
7156     event.stopPropagation();
7158     if ((event.type == "click" && event.button != 0) ||
7159         (event.type == "keypress" && event.charCode != KeyEvent.DOM_VK_SPACE &&
7160          event.keyCode != KeyEvent.DOM_VK_RETURN))
7161       return; // Left click, space or enter only
7163     // Revert the contents of the location bar, see bug 406779
7164     gURLBar.handleRevert();
7166     // Make sure that the display:none style we set in xul is removed now that
7167     // the popup is actually needed
7168     this._identityPopup.hidden = false;
7170     // Tell the popup to consume dismiss clicks, to avoid bug 395314
7171     this._identityPopup.popupBoxObject
7172         .setConsumeRollupEvent(Ci.nsIPopupBoxObject.ROLLUP_CONSUME);
7174     // Update the popup strings
7175     this.setPopupMessages(this._identityBox.className);
7177     // Make sure the identity popup hangs toward the middle of the location bar
7178     // in RTL builds
7179     var position = (getComputedStyle(gNavToolbox, "").direction == "rtl") ? 'after_end' : 'after_start';
7181     // Add the "open" attribute to the identity box for styling
7182     this._identityBox.setAttribute("open", "true");
7183     var self = this;
7184     this._identityPopup.addEventListener("popuphidden", function (e) {
7185       e.currentTarget.removeEventListener("popuphidden", arguments.callee, false);
7186       self._identityBox.removeAttribute("open");
7187     }, false);
7189     // Now open the popup, anchored off the primary chrome element
7190     this._identityPopup.openPopup(this._identityBox, position);
7191   }
7194 let DownloadMonitorPanel = {
7195   //////////////////////////////////////////////////////////////////////////////
7196   //// DownloadMonitorPanel Member Variables
7198   _panel: null,
7199   _activeStr: null,
7200   _pausedStr: null,
7201   _lastTime: Infinity,
7202   _listening: false,
7204   get DownloadUtils() {
7205     delete this.DownloadUtils;
7206     Cu.import("resource://gre/modules/DownloadUtils.jsm", this);
7207     return this.DownloadUtils;
7208   },
7210   //////////////////////////////////////////////////////////////////////////////
7211   //// DownloadMonitorPanel Public Methods
7213   /**
7214    * Initialize the status panel and member variables
7215    */
7216   init: function DMP_init() {
7217     // Initialize "private" member variables
7218     this._panel = document.getElementById("download-monitor");
7220     // Cache the status strings
7221     this._activeStr = gNavigatorBundle.getString("activeDownloads1");
7222     this._pausedStr = gNavigatorBundle.getString("pausedDownloads1");
7224     gDownloadMgr.addListener(this);
7225     this._listening = true;
7227     this.updateStatus();
7228   },
7230   uninit: function DMP_uninit() {
7231     if (this._listening)
7232       gDownloadMgr.removeListener(this);
7233   },
7235   inited: function DMP_inited() {
7236     return this._panel != null;
7237   },
7239   /**
7240    * Update status based on the number of active and paused downloads
7241    */
7242   updateStatus: function DMP_updateStatus() {
7243     if (!this.inited())
7244       return;
7246     let numActive = gDownloadMgr.activeDownloadCount;
7248     // Hide the panel and reset the "last time" if there's no downloads
7249     if (numActive == 0) {
7250       this._panel.hidden = true;
7251       this._lastTime = Infinity;
7253       return;
7254     }
7256     // Find the download with the longest remaining time
7257     let numPaused = 0;
7258     let maxTime = -Infinity;
7259     let dls = gDownloadMgr.activeDownloads;
7260     while (dls.hasMoreElements()) {
7261       let dl = dls.getNext().QueryInterface(Ci.nsIDownload);
7262       if (dl.state == gDownloadMgr.DOWNLOAD_DOWNLOADING) {
7263         // Figure out if this download takes longer
7264         if (dl.speed > 0 && dl.size > 0)
7265           maxTime = Math.max(maxTime, (dl.size - dl.amountTransferred) / dl.speed);
7266         else
7267           maxTime = -1;
7268       }
7269       else if (dl.state == gDownloadMgr.DOWNLOAD_PAUSED)
7270         numPaused++;
7271     }
7273     // Get the remaining time string and last sec for time estimation
7274     let timeLeft;
7275     [timeLeft, this._lastTime] =
7276       this.DownloadUtils.getTimeLeft(maxTime, this._lastTime);
7278     // Figure out how many downloads are currently downloading
7279     let numDls = numActive - numPaused;
7280     let status = this._activeStr;
7282     // If all downloads are paused, show the paused message instead
7283     if (numDls == 0) {
7284       numDls = numPaused;
7285       status = this._pausedStr;
7286     }
7288     // Get the correct plural form and insert the number of downloads and time
7289     // left message if necessary
7290     status = PluralForm.get(numDls, status);
7291     status = status.replace("#1", numDls);
7292     status = status.replace("#2", timeLeft);
7294     // Update the panel and show it
7295     this._panel.label = status;
7296     this._panel.hidden = false;
7297   },
7299   //////////////////////////////////////////////////////////////////////////////
7300   //// nsIDownloadProgressListener
7302   /**
7303    * Update status for download progress changes
7304    */
7305   onProgressChange: function() {
7306     this.updateStatus();
7307   },
7309   /**
7310    * Update status for download state changes
7311    */
7312   onDownloadStateChange: function() {
7313     this.updateStatus();
7314   },
7316   onStateChange: function(aWebProgress, aRequest, aStateFlags, aStatus, aDownload) {
7317   },
7319   onSecurityChange: function(aWebProgress, aRequest, aState, aDownload) {
7320   },
7322   //////////////////////////////////////////////////////////////////////////////
7323   //// nsISupports
7325   QueryInterface: XPCOMUtils.generateQI([Ci.nsIDownloadProgressListener]),
7328 function getNotificationBox(aWindow) {
7329   var foundBrowser = gBrowser.getBrowserForDocument(aWindow.document);
7330   if (foundBrowser)
7331     return gBrowser.getNotificationBox(foundBrowser)
7332   return null;
7335 /* DEPRECATED */
7336 function getBrowser() gBrowser;
7337 function getNavToolbox() gNavToolbox;
7339 let gPrivateBrowsingUI = {
7340   _privateBrowsingService: null,
7341   _searchBarValue: null,
7342   _findBarValue: null,
7344   init: function PBUI_init() {
7345     Services.obs.addObserver(this, "private-browsing", false);
7346     Services.obs.addObserver(this, "private-browsing-transition-complete", false);
7348     this._privateBrowsingService = Cc["@mozilla.org/privatebrowsing;1"].
7349                                    getService(Ci.nsIPrivateBrowsingService);
7351     if (this.privateBrowsingEnabled)
7352       this.onEnterPrivateBrowsing(true);
7353   },
7355   uninit: function PBUI_unint() {
7356     Services.obs.removeObserver(this, "private-browsing");
7357     Services.obs.removeObserver(this, "private-browsing-transition-complete");
7358   },
7360   get _disableUIOnToggle() {
7361     if (this._privateBrowsingService.autoStarted)
7362       return false;
7364     try {
7365       return !gPrefService.getBoolPref("browser.privatebrowsing.keep_current_session");
7366     }
7367     catch (e) {
7368       return true;
7369     }
7370   },
7372   observe: function PBUI_observe(aSubject, aTopic, aData) {
7373     if (aTopic == "private-browsing") {
7374       if (aData == "enter")
7375         this.onEnterPrivateBrowsing();
7376       else if (aData == "exit")
7377         this.onExitPrivateBrowsing();
7378     }
7379     else if (aTopic == "private-browsing-transition-complete") {
7380       if (this._disableUIOnToggle) {
7381         // use setTimeout here in order to make the code testable
7382         setTimeout(function() {
7383           document.getElementById("Tools:PrivateBrowsing")
7384                   .removeAttribute("disabled");
7385         }, 0);
7386       }
7387     }
7388   },
7390   _shouldEnter: function PBUI__shouldEnter() {
7391     try {
7392       // Never prompt if the session is not going to be closed, or if user has
7393       // already requested not to be prompted.
7394       if (gPrefService.getBoolPref("browser.privatebrowsing.dont_prompt_on_enter") ||
7395           gPrefService.getBoolPref("browser.privatebrowsing.keep_current_session"))
7396         return true;
7397     }
7398     catch (ex) { }
7400     var bundleService = Cc["@mozilla.org/intl/stringbundle;1"].
7401                         getService(Ci.nsIStringBundleService);
7402     var pbBundle = bundleService.createBundle("chrome://browser/locale/browser.properties");
7403     var brandBundle = bundleService.createBundle("chrome://branding/locale/brand.properties");
7405     var appName = brandBundle.GetStringFromName("brandShortName");
7406 # On Mac, use the header as the title.
7407 #ifdef XP_MACOSX
7408     var dialogTitle = pbBundle.GetStringFromName("privateBrowsingMessageHeader");
7409     var header = "";
7410 #else
7411     var dialogTitle = pbBundle.GetStringFromName("privateBrowsingDialogTitle");
7412     var header = pbBundle.GetStringFromName("privateBrowsingMessageHeader") + "\n\n";
7413 #endif
7414     var message = pbBundle.formatStringFromName("privateBrowsingMessage", [appName], 1);
7416     var ps = Services.prompt;
7418     var flags = ps.BUTTON_TITLE_IS_STRING * ps.BUTTON_POS_0 +
7419                 ps.BUTTON_TITLE_IS_STRING * ps.BUTTON_POS_1 +
7420                 ps.BUTTON_POS_0_DEFAULT;
7422     var neverAsk = {value:false};
7423     var button0Title = pbBundle.GetStringFromName("privateBrowsingYesTitle");
7424     var button1Title = pbBundle.GetStringFromName("privateBrowsingNoTitle");
7425     var neverAskText = pbBundle.GetStringFromName("privateBrowsingNeverAsk");
7427     var result;
7428     var choice = ps.confirmEx(null, dialogTitle, header + message,
7429                               flags, button0Title, button1Title, null,
7430                               neverAskText, neverAsk);
7432     switch (choice) {
7433     case 0: // Start Private Browsing
7434       result = true;
7435       if (neverAsk.value)
7436         gPrefService.setBoolPref("browser.privatebrowsing.dont_prompt_on_enter", true);
7437       break;
7438     case 1: // Keep
7439       result = false;
7440       break;
7441     }
7443     return result;
7444   },
7446   onEnterPrivateBrowsing: function PBUI_onEnterPrivateBrowsing(aOnWindowOpen) {
7447     if (BrowserSearch.searchBar)
7448       this._searchBarValue = BrowserSearch.searchBar.textbox.value;
7450     if (gFindBarInitialized)
7451       this._findBarValue = gFindBar.getElement("findbar-textbox").value;
7453     this._setPBMenuTitle("stop");
7455     document.getElementById("menu_import").setAttribute("disabled", "true");
7457     // Disable the Clear Recent History... menu item when in PB mode
7458     // temporary fix until bug 463607 is fixed
7459     document.getElementById("Tools:Sanitize").setAttribute("disabled", "true");
7461     if (this._privateBrowsingService.autoStarted) {
7462       // Disable the menu item in auto-start mode
7463       document.getElementById("privateBrowsingItem")
7464               .setAttribute("disabled", "true");
7465       document.getElementById("Tools:PrivateBrowsing")
7466               .setAttribute("disabled", "true");
7467     }
7468     else if (window.location.href == getBrowserURL()) {
7469       // Adjust the window's title
7470       let docElement = document.documentElement;
7471       docElement.setAttribute("title",
7472         docElement.getAttribute("title_privatebrowsing"));
7473       docElement.setAttribute("titlemodifier",
7474         docElement.getAttribute("titlemodifier_privatebrowsing"));
7475       docElement.setAttribute("browsingmode", "private");
7476       gBrowser.updateTitlebar();
7477     }
7479     setTimeout(function () {
7480       DownloadMonitorPanel.updateStatus();
7481     }, 0);
7483     if (!aOnWindowOpen && this._disableUIOnToggle)
7484       document.getElementById("Tools:PrivateBrowsing")
7485               .setAttribute("disabled", "true");
7486   },
7488   onExitPrivateBrowsing: function PBUI_onExitPrivateBrowsing() {
7489     if (BrowserSearch.searchBar) {
7490       let searchBox = BrowserSearch.searchBar.textbox;
7491       searchBox.reset();
7492       if (this._searchBarValue) {
7493         searchBox.value = this._searchBarValue;
7494         this._searchBarValue = null;
7495       }
7496     }
7498     if (gURLBar) {
7499       gURLBar.editor.transactionManager.clear();
7500     }
7502     document.getElementById("menu_import").removeAttribute("disabled");
7504     // Re-enable the Clear Recent History... menu item on exit of PB mode
7505     // temporary fix until bug 463607 is fixed
7506     document.getElementById("Tools:Sanitize").removeAttribute("disabled");
7508     if (gFindBarInitialized) {
7509       let findbox = gFindBar.getElement("findbar-textbox");
7510       findbox.reset();
7511       if (this._findBarValue) {
7512         findbox.value = this._findBarValue;
7513         this._findBarValue = null;
7514       }
7515     }
7517     this._setPBMenuTitle("start");
7519     if (window.location.href == getBrowserURL()) {
7520       // Adjust the window's title
7521       let docElement = document.documentElement;
7522       docElement.setAttribute("title",
7523         docElement.getAttribute("title_normal"));
7524       docElement.setAttribute("titlemodifier",
7525         docElement.getAttribute("titlemodifier_normal"));
7526       docElement.setAttribute("browsingmode", "normal");
7527     }
7529     // Enable the menu item in after exiting the auto-start mode
7530     document.getElementById("privateBrowsingItem")
7531             .removeAttribute("disabled");
7532     document.getElementById("Tools:PrivateBrowsing")
7533             .removeAttribute("disabled");
7535     gLastOpenDirectory.reset();
7537     setTimeout(function () {
7538       DownloadMonitorPanel.updateStatus();
7539     }, 0);
7541     if (this._disableUIOnToggle)
7542       document.getElementById("Tools:PrivateBrowsing")
7543               .setAttribute("disabled", "true");
7544   },
7546   _setPBMenuTitle: function PBUI__setPBMenuTitle(aMode) {
7547     let pbMenuItem = document.getElementById("privateBrowsingItem");
7548     pbMenuItem.setAttribute("label", pbMenuItem.getAttribute(aMode + "label"));
7549     pbMenuItem.setAttribute("accesskey", pbMenuItem.getAttribute(aMode + "accesskey"));
7550   },
7552   toggleMode: function PBUI_toggleMode() {
7553     // prompt the users on entering the private mode, if needed
7554     if (!this.privateBrowsingEnabled)
7555       if (!this._shouldEnter())
7556         return;
7558     this._privateBrowsingService.privateBrowsingEnabled =
7559       !this.privateBrowsingEnabled;
7560   },
7562   get privateBrowsingEnabled() {
7563     return this._privateBrowsingService.privateBrowsingEnabled;
7564   }
7567 var LightWeightThemeWebInstaller = {
7568   handleEvent: function (event) {
7569     switch (event.type) {
7570       case "InstallBrowserTheme":
7571       case "PreviewBrowserTheme":
7572       case "ResetBrowserThemePreview":
7573         // ignore requests from background tabs
7574         if (event.target.ownerDocument.defaultView.top != content)
7575           return;
7576     }
7577     switch (event.type) {
7578       case "InstallBrowserTheme":
7579         this._installRequest(event);
7580         break;
7581       case "PreviewBrowserTheme":
7582         this._preview(event);
7583         break;
7584       case "ResetBrowserThemePreview":
7585         this._resetPreview(event);
7586         break;
7587       case "pagehide":
7588       case "TabSelect":
7589         this._resetPreview();
7590         break;
7591     }
7592   },
7594   get _manager () {
7595     var temp = {};
7596     Cu.import("resource://gre/modules/LightweightThemeManager.jsm", temp);
7597     delete this._manager;
7598     return this._manager = temp.LightweightThemeManager;
7599   },
7601   _installRequest: function (event) {
7602     var node = event.target;
7603     var data = this._getThemeFromNode(node);
7604     if (!data)
7605       return;
7607     if (this._isAllowed(node)) {
7608       this._install(data);
7609       return;
7610     }
7612     var allowButtonText =
7613       gNavigatorBundle.getString("lwthemeInstallRequest.allowButton");
7614     var allowButtonAccesskey =
7615       gNavigatorBundle.getString("lwthemeInstallRequest.allowButton.accesskey");
7616     var message =
7617       gNavigatorBundle.getFormattedString("lwthemeInstallRequest.message",
7618                                           [node.ownerDocument.location.host]);
7619     var buttons = [{
7620       label: allowButtonText,
7621       accessKey: allowButtonAccesskey,
7622       callback: function () {
7623         LightWeightThemeWebInstaller._install(data);
7624       }
7625     }];
7627     this._removePreviousNotifications();
7629     var notificationBox = gBrowser.getNotificationBox();
7630     var notificationBar =
7631       notificationBox.appendNotification(message, "lwtheme-install-request", "",
7632                                          notificationBox.PRIORITY_INFO_MEDIUM,
7633                                          buttons);
7634     notificationBar.persistence = 1;
7635   },
7637   _install: function (newTheme) {
7638     var previousTheme = this._manager.currentTheme;
7639     this._manager.currentTheme = newTheme;
7640     if (this._manager.currentTheme &&
7641         this._manager.currentTheme.id == newTheme.id)
7642       this._postInstallNotification(newTheme, previousTheme);
7643   },
7645   _postInstallNotification: function (newTheme, previousTheme) {
7646     function text(id) {
7647       return gNavigatorBundle.getString("lwthemePostInstallNotification." + id);
7648     }
7650     var buttons = [{
7651       label: text("undoButton"),
7652       accessKey: text("undoButton.accesskey"),
7653       callback: function () {
7654         LightWeightThemeWebInstaller._manager.forgetUsedTheme(newTheme.id);
7655         LightWeightThemeWebInstaller._manager.currentTheme = previousTheme;
7656       }
7657     }, {
7658       label: text("manageButton"),
7659       accessKey: text("manageButton.accesskey"),
7660       callback: function () {
7661         BrowserOpenAddonsMgr("addons://list/theme");
7662       }
7663     }];
7665     this._removePreviousNotifications();
7667     var notificationBox = gBrowser.getNotificationBox();
7668     var notificationBar =
7669       notificationBox.appendNotification(text("message"),
7670                                          "lwtheme-install-notification", "",
7671                                          notificationBox.PRIORITY_INFO_MEDIUM,
7672                                          buttons);
7673     notificationBar.persistence = 1;
7674     notificationBar.timeout = Date.now() + 20000; // 20 seconds
7675   },
7677   _removePreviousNotifications: function () {
7678     var box = gBrowser.getNotificationBox();
7680     ["lwtheme-install-request",
7681      "lwtheme-install-notification"].forEach(function (value) {
7682         var notification = box.getNotificationWithValue(value);
7683         if (notification)
7684           box.removeNotification(notification);
7685       });
7686   },
7688   _previewWindow: null,
7689   _preview: function (event) {
7690     if (!this._isAllowed(event.target))
7691       return;
7693     var data = this._getThemeFromNode(event.target);
7694     if (!data)
7695       return;
7697     this._resetPreview();
7699     this._previewWindow = event.target.ownerDocument.defaultView;
7700     this._previewWindow.addEventListener("pagehide", this, true);
7701     gBrowser.tabContainer.addEventListener("TabSelect", this, false);
7703     this._manager.previewTheme(data);
7704   },
7706   _resetPreview: function (event) {
7707     if (!this._previewWindow ||
7708         event && !this._isAllowed(event.target))
7709       return;
7711     this._previewWindow.removeEventListener("pagehide", this, true);
7712     this._previewWindow = null;
7713     gBrowser.tabContainer.removeEventListener("TabSelect", this, false);
7715     this._manager.resetPreview();
7716   },
7718   _isAllowed: function (node) {
7719     var pm = Services.perms;
7721     var uri = node.ownerDocument.documentURIObject;
7722     return pm.testPermission(uri, "install") == pm.ALLOW_ACTION;
7723   },
7725   _getThemeFromNode: function (node) {
7726     return this._manager.parseTheme(node.getAttribute("data-browsertheme"),
7727                                     node.baseURI);
7728   }
7732  * Switch to a tab that has a given URI, and focusses its browser window.
7733  * If a matching tab is in this window, it will be switched to. Otherwise, other
7734  * windows will be searched.
7736  * @param aURI
7737  *        URI to search for
7738  * @param aOpenNew
7739  *        True to open a new tab and switch to it, if no existing tab is found
7740  * @param A callback to call when the tab is open, the tab's browser will be
7741  *        passed as an argument
7742  * @return True if a tab was switched to (or opened), false otherwise
7743  */
7744 function switchToTabHavingURI(aURI, aOpenNew, aCallback) {
7745   function switchIfURIInWindow(aWindow) {
7746     if (!("gBrowser" in aWindow))
7747       return false;
7748     let browsers = aWindow.gBrowser.browsers;
7749     for (let i = 0; i < browsers.length; i++) {
7750       let browser = browsers[i];
7751       if (browser.currentURI.equals(aURI)) {
7752         gURLBar.handleRevert();
7753         // We need the current tab so we can check if we should close it
7754         let prevTab = gBrowser.selectedTab;
7755         // Focus the matching window & tab
7756         aWindow.focus();
7757         aWindow.gBrowser.tabContainer.selectedIndex = i;
7758         if (aCallback)
7759           aCallback(browser);
7760         // Close the previously selected tab if it was empty
7761         if (isTabEmpty(prevTab))
7762           gBrowser.removeTab(prevTab);
7763         return true;
7764       }
7765     }
7766     return false;
7767   }
7769   // This can be passed either nsIURI or a string.
7770   if (!(aURI instanceof Ci.nsIURI))
7771     aURI = makeURI(aURI);
7773   // Prioritise this window.
7774   if (switchIfURIInWindow(window))
7775     return true;
7777   let winEnum = Services.wm.getEnumerator("navigator:browser");
7778   while (winEnum.hasMoreElements()) {
7779     let browserWin = winEnum.getNext();
7780     // Skip closed (but not yet destroyed) windows,
7781     // and the current window (which was checked earlier).
7782     if (browserWin.closed || browserWin == window)
7783       continue;
7784     if (switchIfURIInWindow(browserWin))
7785       return true;
7786   }
7788   // No opened tab has that url.
7789   if (aOpenNew) {
7790     if (isTabEmpty(gBrowser.selectedTab))
7791       gBrowser.selectedBrowser.loadURI(aURI.spec);
7792     else
7793       gBrowser.selectedTab = gBrowser.addTab(aURI.spec);
7794     if (aCallback) {
7795       let browser = gBrowser.selectedBrowser;
7796       browser.addEventListener("pageshow", function(event) {
7797         if (event.target.location.href != aURI.spec)
7798           return;
7799         browser.removeEventListener("pageshow", arguments.callee, true);
7800         aCallback(browser);
7801       }, true);
7802     }
7803     return true;
7804   }
7806   return false;
7809 var TabContextMenu = {
7810   contextTab: null,
7811   updateContextMenu: function updateContextMenu(aPopupMenu) {
7812     this.contextTab = document.popupNode.localName == "tab" ?
7813                       document.popupNode : gBrowser.selectedTab;
7814     var disabled = gBrowser.tabs.length == 1;
7816     // Enable the "Close Tab" menuitem when the window doesn't close with the last tab.
7817     document.getElementById("context_closeTab").disabled =
7818       disabled && gBrowser.tabContainer._closeWindowWithLastTab;
7820     var menuItems = aPopupMenu.getElementsByAttribute("tbattr", "tabbrowser-multiple");
7821     for (var i = 0; i < menuItems.length; i++)
7822       menuItems[i].disabled = disabled;
7824     // Session store
7825     document.getElementById("context_undoCloseTab").disabled =
7826       Cc["@mozilla.org/browser/sessionstore;1"].
7827       getService(Ci.nsISessionStore).
7828       getClosedTabCount(window) == 0;
7830     // Only one of pin/unpin should be visible
7831     document.getElementById("context_pinTab").hidden = this.contextTab.pinned;
7832     document.getElementById("context_unpinTab").hidden = !this.contextTab.pinned;
7834     // Disable "Close other Tabs" if there is only one unpinned tab and
7835     // hide it when the user rightclicked on a pinned tab.
7836     var unpinnedTabs = gBrowser.tabs.length - gBrowser._numPinnedTabs;
7837     document.getElementById("context_closeOtherTabs").disabled = unpinnedTabs <= 1;
7838     document.getElementById("context_closeOtherTabs").hidden = this.contextTab.pinned;
7839   }
7842 XPCOMUtils.defineLazyGetter(this, "HUDConsoleUI", function () {
7843   Cu.import("resource://gre/modules/HUDService.jsm");
7844   try {
7845     return HUDService.consoleUI;
7846   }
7847   catch (ex) {
7848     Components.utils.reportError(ex);
7849   }