summaryrefslogtreecommitdiffstats
path: root/webbrowser/base/content/browser.js
diff options
context:
space:
mode:
Diffstat (limited to 'webbrowser/base/content/browser.js')
-rw-r--r--webbrowser/base/content/browser.js7440
1 files changed, 7440 insertions, 0 deletions
diff --git a/webbrowser/base/content/browser.js b/webbrowser/base/content/browser.js
new file mode 100644
index 0000000..7615bc9
--- /dev/null
+++ b/webbrowser/base/content/browser.js
@@ -0,0 +1,7440 @@
+# -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+var Ci = Components.interfaces;
+var Cu = Components.utils;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource:///modules/RecentWindow.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "Task",
+ "resource://gre/modules/Task.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "CharsetMenu",
+ "resource:///modules/CharsetMenu.jsm");
+
+const nsIWebNavigation = Ci.nsIWebNavigation;
+const gToolbarInfoSeparators = ["|", "-"];
+
+var gLastBrowserCharset = null;
+var gPrevCharset = null;
+var gProxyFavIcon = null;
+var gLastValidURLStr = "";
+var gInPrintPreviewMode = false;
+var gContextMenu = null; // nsContextMenu instance
+var gMultiProcessBrowser = false;
+
+#ifndef XP_MACOSX
+var gEditUIVisible = true;
+#endif
+
+[
+ ["gBrowser", "content"],
+ ["gNavToolbox", "navigator-toolbox"],
+ ["gURLBar", "urlbar"],
+ ["gNavigatorBundle", "bundle_browser"]
+].forEach(function (elementGlobal) {
+ var [name, id] = elementGlobal;
+ window.__defineGetter__(name, function () {
+ var element = document.getElementById(id);
+ if (!element)
+ return null;
+ delete window[name];
+ return window[name] = element;
+ });
+ window.__defineSetter__(name, function (val) {
+ delete window[name];
+ return window[name] = val;
+ });
+});
+
+// Smart getter for the findbar. If you don't wish to force the creation of
+// the findbar, check gFindBarInitialized first.
+var gFindBarInitialized = false;
+XPCOMUtils.defineLazyGetter(window, "gFindBar", function() {
+ let XULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+ let findbar = document.createElementNS(XULNS, "findbar");
+ findbar.id = "FindToolbar";
+
+ let browserBottomBox = document.getElementById("browser-bottombox");
+ browserBottomBox.insertBefore(findbar, browserBottomBox.firstChild);
+
+ // Force a style flush to ensure that our binding is attached.
+ findbar.clientTop;
+ findbar.browser = gBrowser.mCurrentBrowser;
+ window.gFindBarInitialized = true;
+ return findbar;
+});
+
+XPCOMUtils.defineLazyModuleGetter(this, "BrowserUtils",
+ "resource://gre/modules/BrowserUtils.jsm");
+
+XPCOMUtils.defineLazyGetter(this, "gPrefService", function() {
+ return Services.prefs;
+});
+
+this.__defineGetter__("AddonManager", function() {
+ let tmp = {};
+ Cu.import("resource://gre/modules/AddonManager.jsm", tmp);
+ return this.AddonManager = tmp.AddonManager;
+});
+this.__defineSetter__("AddonManager", function (val) {
+ delete this.AddonManager;
+ return this.AddonManager = val;
+});
+
+XPCOMUtils.defineLazyModuleGetter(this, "PluralForm",
+ "resource://gre/modules/PluralForm.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "AboutHomeUtils",
+ "resource:///modules/AboutHomeUtils.jsm");
+
+#ifdef MOZ_SERVICES_SYNC
+XPCOMUtils.defineLazyModuleGetter(this, "Weave",
+ "resource://services-sync/main.js");
+#endif
+
+XPCOMUtils.defineLazyGetter(this, "PopupNotifications", function () {
+ let tmp = {};
+ Cu.import("resource:///modules/PopupNotifications.jsm", tmp);
+ try {
+ return new tmp.PopupNotifications(gBrowser,
+ document.getElementById("notification-popup"),
+ document.getElementById("notification-popup-box"));
+ } catch (ex) {
+ Cu.reportError(ex);
+ return null;
+ }
+});
+
+XPCOMUtils.defineLazyModuleGetter(this, "PageThumbs",
+ "resource://gre/modules/PageThumbs.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "gBrowserNewTabPreloader",
+ "resource:///modules/BrowserNewTabPreloader.jsm", "BrowserNewTabPreloader");
+
+XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
+ "resource://gre/modules/PrivateBrowsingUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "FormValidationHandler",
+ "resource:///modules/FormValidationHandler.jsm");
+
+var gInitialPages = [
+ "about:blank",
+ "about:newtab",
+ "about:home",
+ "about:privatebrowsing",
+ "about:sessionrestore",
+ "about:logopage"
+];
+
+#include browser-addons.js
+#include browser-feeds.js
+#include browser-fullScreen.js
+#include browser-fullZoom.js
+#include browser-places.js
+#include browser-plugins.js
+#include browser-tabPreviews.js
+#include browser-thumbnails.js
+#include browser-uacompat.js
+
+#ifdef MOZ_WEBRTC
+#include browser-webrtcUI.js
+#endif
+
+#include browser-gestureSupport.js
+
+#ifdef MOZ_SERVICES_SYNC
+#include browser-syncui.js
+#endif
+
+XPCOMUtils.defineLazyGetter(this, "Win7Features", function () {
+#ifdef XP_WIN
+ const WINTASKBAR_CONTRACTID = "@mozilla.org/windows-taskbar;1";
+ if (WINTASKBAR_CONTRACTID in Cc &&
+ Cc[WINTASKBAR_CONTRACTID].getService(Ci.nsIWinTaskbar).available) {
+ let AeroPeek = Cu.import("resource:///modules/WindowsPreviewPerTab.jsm", {}).AeroPeek;
+ return {
+ onOpenWindow: function () {
+ AeroPeek.onOpenWindow(window);
+ },
+ onCloseWindow: function () {
+ AeroPeek.onCloseWindow(window);
+ }
+ };
+ }
+#endif
+ return null;
+});
+
+XPCOMUtils.defineLazyGetter(this, "PageMenu", function() {
+ let tmp = {};
+ Cu.import("resource:///modules/PageMenu.jsm", tmp);
+ return new tmp.PageMenu();
+});
+
+/**
+* We can avoid adding multiple load event listeners and save some time by adding
+* one listener that calls all real handlers.
+*/
+function pageShowEventHandlers(persisted) {
+ charsetLoadListener();
+ XULBrowserWindow.asyncUpdateUI();
+
+ // The PluginClickToPlay events are not fired when navigating using the
+ // BF cache. |persisted| is true when the page is loaded from the
+ // BF cache, so this code reshows the notification if necessary.
+ if (persisted)
+ gPluginHandler.reshowClickToPlayNotification();
+}
+
+function UpdateBackForwardCommands(aWebNavigation) {
+ var backBroadcaster = document.getElementById("Browser:Back");
+ var forwardBroadcaster = document.getElementById("Browser:Forward");
+
+ // Avoid setting attributes on broadcasters if the value hasn't changed!
+ // Remember, guys, setting attributes on elements is expensive! They
+ // get inherited into anonymous content, broadcast to other widgets, etc.!
+ // Don't do it if the value hasn't changed! - dwh
+
+ var backDisabled = backBroadcaster.hasAttribute("disabled");
+ var forwardDisabled = forwardBroadcaster.hasAttribute("disabled");
+ if (backDisabled == aWebNavigation.canGoBack) {
+ if (backDisabled)
+ backBroadcaster.removeAttribute("disabled");
+ else
+ backBroadcaster.setAttribute("disabled", true);
+ }
+
+ if (forwardDisabled == aWebNavigation.canGoForward) {
+ if (forwardDisabled)
+ forwardBroadcaster.removeAttribute("disabled");
+ else
+ forwardBroadcaster.setAttribute("disabled", true);
+ }
+}
+
+/**
+ * Click-and-Hold implementation for the Back and Forward buttons
+ * XXXmano: should this live in toolbarbutton.xml?
+ */
+function SetClickAndHoldHandlers() {
+ var timer;
+
+ function openMenu(aButton) {
+ cancelHold(aButton);
+ aButton.firstChild.hidden = false;
+ aButton.open = true;
+ }
+
+ function mousedownHandler(aEvent) {
+ if (aEvent.button != 0 ||
+ aEvent.currentTarget.open ||
+ aEvent.currentTarget.disabled)
+ return;
+
+ // Prevent the menupopup from opening immediately
+ aEvent.currentTarget.firstChild.hidden = true;
+
+ aEvent.currentTarget.addEventListener("mouseout", mouseoutHandler, false);
+ aEvent.currentTarget.addEventListener("mouseup", mouseupHandler, false);
+ timer = setTimeout(openMenu, 500, aEvent.currentTarget);
+ }
+
+ function mouseoutHandler(aEvent) {
+ let buttonRect = aEvent.currentTarget.getBoundingClientRect();
+ if (aEvent.clientX >= buttonRect.left &&
+ aEvent.clientX <= buttonRect.right &&
+ aEvent.clientY >= buttonRect.bottom)
+ openMenu(aEvent.currentTarget);
+ else
+ cancelHold(aEvent.currentTarget);
+ }
+
+ function mouseupHandler(aEvent) {
+ cancelHold(aEvent.currentTarget);
+ }
+
+ function cancelHold(aButton) {
+ clearTimeout(timer);
+ aButton.removeEventListener("mouseout", mouseoutHandler, false);
+ aButton.removeEventListener("mouseup", mouseupHandler, false);
+ }
+
+ function clickHandler(aEvent) {
+ if (aEvent.button == 0 &&
+ aEvent.target == aEvent.currentTarget &&
+ !aEvent.currentTarget.open &&
+ !aEvent.currentTarget.disabled) {
+ let cmdEvent = document.createEvent("xulcommandevent");
+ cmdEvent.initCommandEvent("command", true, true, window, 0,
+ aEvent.ctrlKey, aEvent.altKey, aEvent.shiftKey,
+ aEvent.metaKey, null);
+ aEvent.currentTarget.dispatchEvent(cmdEvent);
+ }
+ }
+
+ function _addClickAndHoldListenersOnElement(aElm) {
+ aElm.addEventListener("mousedown", mousedownHandler, true);
+ aElm.addEventListener("click", clickHandler, true);
+ }
+
+ // Bug 414797: Clone unified-back-forward-button's context menu into both the
+ // back and the forward buttons.
+ var unifiedButton = document.getElementById("unified-back-forward-button");
+ if (unifiedButton && !unifiedButton._clickHandlersAttached) {
+ unifiedButton._clickHandlersAttached = true;
+
+ let popup = document.getElementById("backForwardMenu").cloneNode(true);
+ popup.removeAttribute("id");
+ // Prevent the context attribute on unified-back-forward-button from being
+ // inherited.
+ popup.setAttribute("context", "");
+
+ let backButton = document.getElementById("back-button");
+ backButton.setAttribute("type", "menu");
+ backButton.appendChild(popup);
+ _addClickAndHoldListenersOnElement(backButton);
+
+ let forwardButton = document.getElementById("forward-button");
+ popup = popup.cloneNode(true);
+ forwardButton.setAttribute("type", "menu");
+ forwardButton.appendChild(popup);
+ _addClickAndHoldListenersOnElement(forwardButton);
+ }
+}
+
+const gSessionHistoryObserver = {
+ observe: function(subject, topic, data)
+ {
+ if (topic != "browser:purge-session-history")
+ return;
+
+ var backCommand = document.getElementById("Browser:Back");
+ backCommand.setAttribute("disabled", "true");
+ var fwdCommand = document.getElementById("Browser:Forward");
+ fwdCommand.setAttribute("disabled", "true");
+
+ // Hide session restore button on about:home
+ window.messageManager.broadcastAsyncMessage("Browser:HideSessionRestoreButton");
+
+ if (gURLBar) {
+ // Clear undo history of the URL bar
+ gURLBar.editor.transactionManager.clear()
+ }
+ }
+};
+
+var gFindBarSettings = {
+ messageName: "Findbar:Keypress",
+ prefName: "accessibility.typeaheadfind",
+ findAsYouType: null,
+
+ init: function() {
+ window.messageManager.addMessageListener(this.messageName, this);
+
+ gPrefService.addObserver(this.prefName, this, false);
+ this.writeFindAsYouType();
+ },
+
+ uninit: function() {
+ window.messageManager.removeMessageListener(this.messageName, this);
+
+ try {
+ gPrefService.removeObserver(this.prefName, this);
+ } catch (ex) {
+ Cu.reportError(ex);
+ }
+ },
+
+ observe: function(aSubject, aTopic, aData) {
+ if (aTopic != "nsPref:changed") {
+ return;
+ }
+
+ this.writeFindAsYouType();
+ },
+
+ writeFindAsYouType: function() {
+ this.findAsYouType = gPrefService.getBoolPref(this.prefName);
+ },
+
+ receiveMessage: function(aMessage) {
+ switch (aMessage.name) {
+ case this.messageName:
+ // If the find bar for chrome window's context is not yet alive,
+ // only initialize it if there's a possibility FindAsYouType
+ // will be used.
+ // There's no point in doing it for most random keypresses.
+ if (!gFindBarInitialized && aMessage.data.shouldFastFind) {
+ let shouldFastFind = this.findAsYouType;
+ if (!shouldFastFind) {
+ // Please keep in sync with toolkit/content/widgets/findbar.xml
+ const FAYT_LINKS_KEY = "'";
+ const FAYT_TEXT_KEY = "/";
+ let charCode = aMessage.data.fakeEvent.charCode;
+ let key = charCode ? String.fromCharCode(charCode) : null;
+ shouldFastFind = key == FAYT_LINKS_KEY || key == FAYT_TEXT_KEY;
+ }
+ if (shouldFastFind) {
+ // Make sure we return the result.
+ return gFindBar.receiveMessage(aMessage);
+ }
+ }
+ break;
+ }
+ }
+};
+
+var gURLBarSettings = {
+ prefSuggest: "browser.urlbar.suggest.",
+ /*
+ For searching in the source code:
+ browser.urlbar.suggest.bookmark
+ browser.urlbar.suggest.history
+ browser.urlbar.suggest.openpage
+ */
+ prefSuggests: [
+ "bookmark",
+ "history",
+ "openpage"
+ ],
+ prefKeyword: "keyword.enabled",
+
+ observe: function(aSubject, aTopic, aData) {
+ if (aTopic != "nsPref:changed")
+ return;
+
+ this.writePlaceholder();
+ },
+
+ writePlaceholder: function() {
+ if (!gURLBar) {
+ return;
+ }
+
+ let attribute = "placeholder";
+ let prefs = this.prefSuggests.map(pref => {
+ return this.prefSuggest + pref;
+ });
+ prefs.push(this.prefKeyword);
+ let placeholderDefault = prefs.some(pref => {
+ return gPrefService.getBoolPref(pref);
+ });
+
+ if (placeholderDefault) {
+ gURLBar.setAttribute(
+ attribute, gNavigatorBundle.getString("urlbar.placeholder"));
+ } else {
+ gURLBar.setAttribute(
+ attribute, gNavigatorBundle.getString("urlbar.placeholderURLOnly"));
+ }
+ }
+};
+
+/**
+ * Given a starting docshell and a URI to look up, find the docshell the URI
+ * is loaded in.
+ * @param aDocument
+ * A document to find instead of using just a URI - this is more specific.
+ * @param aDocShell
+ * The doc shell to start at
+ * @param aSoughtURI
+ * The URI that we're looking for
+ * @returns The doc shell that the sought URI is loaded in. Can be in
+ * subframes.
+ */
+function findChildShell(aDocument, aDocShell, aSoughtURI) {
+ aDocShell.QueryInterface(Components.interfaces.nsIWebNavigation);
+ aDocShell.QueryInterface(Components.interfaces.nsIInterfaceRequestor);
+ var doc = aDocShell.getInterface(Components.interfaces.nsIDOMDocument);
+ if ((aDocument && doc == aDocument) ||
+ (aSoughtURI && aSoughtURI.spec == aDocShell.currentURI.spec))
+ return aDocShell;
+
+ var node = aDocShell.QueryInterface(Components.interfaces.nsIDocShellTreeItem);
+ for (var i = 0; i < node.childCount; ++i) {
+ var docShell = node.getChildAt(i);
+ docShell = findChildShell(aDocument, docShell, aSoughtURI);
+ if (docShell)
+ return docShell;
+ }
+ return null;
+}
+
+var gPopupBlockerObserver = {
+ _reportButton: null,
+
+ onReportButtonClick: function (aEvent)
+ {
+ if (aEvent.button != 0 || aEvent.target != this._reportButton)
+ return;
+
+ document.getElementById("blockedPopupOptions")
+ .openPopup(this._reportButton, "after_end", 0, 2, false, false, aEvent);
+ },
+
+ handleEvent: function (aEvent)
+ {
+ if (aEvent.originalTarget != gBrowser.selectedBrowser)
+ return;
+
+ if (!this._reportButton && gURLBar)
+ this._reportButton = document.getElementById("page-report-button");
+
+ if (!gBrowser.selectedBrowser.blockedPopups ||
+ !gBrowser.selectedBrowser.blockedPopups.length) {
+ // Hide the icon in the location bar (if the location bar exists)
+ if (gURLBar)
+ this._reportButton.hidden = true;
+ return;
+ }
+
+ if (gURLBar)
+ this._reportButton.hidden = false;
+
+ // Only show the notification again if we've not already shown it. Since
+ // notifications are per-browser, we don't need to worry about re-adding
+ // it.
+ if (!gBrowser.selectedBrowser.blockedPopups.reported) {
+ if (gPrefService.getBoolPref("privacy.popups.showBrowserMessage")) {
+ var brandBundle = document.getElementById("bundle_brand");
+ var brandShortName = brandBundle.getString("brandShortName");
+ var popupCount = gBrowser.selectedBrowser.blockedPopups.length;
+ var popupButtonText = gNavigatorBundle.getString("popupWarningButton");
+ var popupButtonAccesskey = gNavigatorBundle.getString("popupWarningButton.accesskey");
+ var messageBase = gNavigatorBundle.getString("popupWarning.message");
+ var message = PluralForm.get(popupCount, messageBase)
+ .replace("#1", brandShortName)
+ .replace("#2", popupCount);
+
+ var notificationBox = gBrowser.getNotificationBox();
+ var notification = notificationBox.getNotificationWithValue("popup-blocked");
+ if (notification) {
+ notification.label = message;
+ }
+ else {
+ var buttons = [{
+ label: popupButtonText,
+ accessKey: popupButtonAccesskey,
+ popup: "blockedPopupOptions",
+ callback: null
+ }];
+
+ const priority = notificationBox.PRIORITY_WARNING_MEDIUM;
+ notificationBox.appendNotification(message, "popup-blocked",
+ "chrome://browser/skin/Info.png",
+ priority, buttons);
+ }
+ }
+
+ // Record the fact that we've reported this blocked popup, so we don't
+ // show it again.
+ gBrowser.selectedBrowser.blockedPopups.reported = true;
+ }
+ },
+
+ toggleAllowPopupsForSite: function (aEvent)
+ {
+ var pm = Services.perms;
+ var shouldBlock = aEvent.target.getAttribute("block") == "true";
+ var perm = shouldBlock ? pm.DENY_ACTION : pm.ALLOW_ACTION;
+ pm.add(gBrowser.currentURI, "popup", perm);
+
+ gBrowser.getNotificationBox().removeCurrentNotification();
+ },
+
+ fillPopupList: function (aEvent)
+ {
+ // XXXben - rather than using |currentURI| here, which breaks down on multi-framed sites
+ // we should really walk the blockedPopups and create a list of "allow for <host>"
+ // menuitems for the common subset of hosts present in the report, this will
+ // make us frame-safe.
+ //
+ // XXXjst - Note that when this is fixed to work with multi-framed sites,
+ // also back out the fix for bug 343772 where
+ // nsGlobalWindow::CheckOpenAllow() was changed to also
+ // check if the top window's location is whitelisted.
+ let browser = gBrowser.selectedBrowser;
+ var uri = browser.currentURI;
+ var blockedPopupAllowSite = document.getElementById("blockedPopupAllowSite");
+ try {
+ blockedPopupAllowSite.removeAttribute("hidden");
+
+ var pm = Services.perms;
+ if (pm.testPermission(uri, "popup") == pm.ALLOW_ACTION) {
+ // Offer an item to block popups for this site, if a whitelist entry exists
+ // already for it.
+ let blockString = gNavigatorBundle.getFormattedString("popupBlock", [uri.host || uri.spec]);
+ blockedPopupAllowSite.setAttribute("label", blockString);
+ blockedPopupAllowSite.setAttribute("block", "true");
+ }
+ else {
+ // Offer an item to allow popups for this site
+ let allowString = gNavigatorBundle.getFormattedString("popupAllow", [uri.host || uri.spec]);
+ blockedPopupAllowSite.setAttribute("label", allowString);
+ blockedPopupAllowSite.removeAttribute("block");
+ }
+ }
+ catch (e) {
+ blockedPopupAllowSite.setAttribute("hidden", "true");
+ }
+
+ if (PrivateBrowsingUtils.isWindowPrivate(window))
+ blockedPopupAllowSite.setAttribute("disabled", "true");
+ else
+ blockedPopupAllowSite.removeAttribute("disabled");
+
+ let blockedPopupDontShowMessage = document.getElementById("blockedPopupDontShowMessage");
+ let showMessage = gPrefService.getBoolPref("privacy.popups.showBrowserMessage");
+ blockedPopupDontShowMessage.setAttribute("checked", !showMessage);
+ if (aEvent.target.anchorNode.id == "page-report-button") {
+ aEvent.target.anchorNode.setAttribute("open", "true");
+ blockedPopupDontShowMessage.setAttribute("label", gNavigatorBundle.getString("popupWarningDontShowFromLocationbar"));
+ } else {
+ blockedPopupDontShowMessage.setAttribute("label", gNavigatorBundle.getString("popupWarningDontShowFromMessage"));
+ }
+
+ let blockedPopupsSeparator =
+ document.getElementById("blockedPopupsSeparator");
+ blockedPopupsSeparator.setAttribute("hidden", true);
+
+ gBrowser.selectedBrowser.retrieveListOfBlockedPopups().then(blockedPopups => {
+ let foundUsablePopupURI = false;
+ if (blockedPopups) {
+ for (let i = 0; i < blockedPopups.length; i++) {
+ let blockedPopup = blockedPopups[i];
+
+ // popupWindowURI will be null if the file picker popup is blocked.
+ // xxxdz this should make the option say "Show file picker" and do it (Bug 590306)
+ if (!blockedPopup.popupWindowURIspec)
+ continue;
+
+ var popupURIspec = blockedPopup.popupWindowURIspec;
+
+ // Sometimes the popup URI that we get back from the blockedPopup
+ // isn't useful (for instance, netscape.com's popup URI ends up
+ // being "http://www.netscape.com", which isn't really the URI of
+ // the popup they're trying to show). This isn't going to be
+ // useful to the user, so we won't create a menu item for it.
+ if (popupURIspec == "" || popupURIspec == "about:blank" ||
+ popupURIspec == "<self>" ||
+ popupURIspec == uri.spec)
+ continue;
+
+ // Because of the short-circuit above, we may end up in a situation
+ // in which we don't have any usable popup addresses to show in
+ // the menu, and therefore we shouldn't show the separator. However,
+ // since we got past the short-circuit, we must've found at least
+ // one usable popup URI and thus we'll turn on the separator later.
+ foundUsablePopupURI = true;
+
+ var menuitem = document.createElement("menuitem");
+ var label = gNavigatorBundle.getFormattedString("popupShowPopupPrefix",
+ [popupURIspec]);
+ menuitem.setAttribute("label", label);
+ menuitem.setAttribute("oncommand", "gPopupBlockerObserver.showBlockedPopup(event);");
+ menuitem.setAttribute("popupReportIndex", i);
+ menuitem.popupReportBrowser = browser;
+ aEvent.target.appendChild(menuitem);
+ }
+ }
+
+ // Show the separator if we added any
+ // showable popup addresses to the menu.
+ if (foundUsablePopupURI)
+ blockedPopupsSeparator.removeAttribute("hidden");
+ }, null);
+ },
+
+ onPopupHiding: function (aEvent) {
+ if (aEvent.target.anchorNode.id == "page-report-button")
+ aEvent.target.anchorNode.removeAttribute("open");
+
+ let item = aEvent.target.lastChild;
+ while (item && item.getAttribute("observes") != "blockedPopupsSeparator") {
+ let next = item.previousSibling;
+ item.parentNode.removeChild(item);
+ item = next;
+ }
+ },
+
+ showBlockedPopup: function (aEvent)
+ {
+ var target = aEvent.target;
+ var popupReportIndex = target.getAttribute("popupReportIndex");
+ let browser = target.popupReportBrowser;
+ browser.unblockPopup(popupReportIndex);
+ },
+
+ editPopupSettings: function ()
+ {
+ var host = "";
+ try {
+ host = gBrowser.currentURI.host;
+ }
+ catch (e) { }
+
+ var bundlePreferences = document.getElementById("bundle_preferences");
+ var params = { blockVisible : false,
+ sessionVisible : false,
+ allowVisible : true,
+ prefilledHost : host,
+ permissionType : "popup",
+ windowTitle : bundlePreferences.getString("popuppermissionstitle"),
+ introText : bundlePreferences.getString("popuppermissionstext") };
+ var existingWindow = Services.wm.getMostRecentWindow("Browser:Permissions");
+ if (existingWindow) {
+ existingWindow.initWithParams(params);
+ existingWindow.focus();
+ }
+ else
+ window.openDialog("chrome://browser/content/preferences/permissions.xul",
+ "_blank", "resizable,dialog=no,centerscreen", params);
+ },
+
+ dontShowMessage: function ()
+ {
+ var showMessage = gPrefService.getBoolPref("privacy.popups.showBrowserMessage");
+ gPrefService.setBoolPref("privacy.popups.showBrowserMessage", !showMessage);
+ gBrowser.getNotificationBox().removeCurrentNotification();
+ }
+};
+
+const gXSSObserver = {
+
+ observe: function (aSubject, aTopic, aData)
+ {
+
+ // Don't do anything if the notification is disabled.
+ if (!gPrefService.getBoolPref("security.xssfilter.displayWarning"))
+ return;
+
+ // Parse incoming XSS array
+ aSubject.QueryInterface(Ci.nsIArray);
+ var policy = aSubject.queryElementAt(0, Ci.nsISupportsString).data;
+ var content = aSubject.queryElementAt(1, Ci.nsISupportsString).data;
+ var domain = aSubject.queryElementAt(2, Ci.nsISupportsString).data;
+ var url = aSubject.queryElementAt(3, Ci.nsISupportsCString).data;
+ var blockMode = aSubject.queryElementAt(4, Ci.nsISupportsPRBool).data;
+
+ // If it is a block mode event, do not display the infobar
+ if (blockMode)
+ return;
+
+ var nb = gBrowser.getNotificationBox();
+ const priority = nb.PRIORITY_WARNING_MEDIUM;
+
+ var buttons = [{
+ label: 'View Unsafe Content',
+ accessKey: 'V',
+ popup: null,
+ callback: function () {
+ alert(content);
+ }
+ }];
+
+ if (domain !== "")
+ buttons.push({
+ label: 'Add Domain Exception',
+ accessKey: 'A',
+ popup: null,
+ callback: function () {
+ let whitelist = gPrefService.getCharPref("security.xssfilter.whitelist");
+ if (whitelist != "") {
+ whitelist = whitelist + "," + domain;
+ } else {
+ whitelist = domain;
+ }
+ // Write the updated whitelist. Since this is observed by the XSS filter,
+ // it will automatically sync to the back-end and update immediately.
+ gPrefService.setCharPref("security.xssfilter.whitelist", whitelist);
+ // After setting this, we automatically reload the page.
+ BrowserReloadSkipCache();
+ }
+ });
+
+ nb.appendNotification("The XSS Filter has detected a potential XSS attack. Type: " +
+ policy, 'popup-blocked', 'chrome://browser/skin/Info.png',
+ priority, buttons);
+ }
+};
+
+var gBrowserInit = {
+ delayedStartupFinished: false,
+
+ onLoad: function() {
+ gMultiProcessBrowser = gPrefService.getBoolPref("browser.tabs.remote");
+
+ var mustLoadSidebar = false;
+
+ Cc["@mozilla.org/eventlistenerservice;1"]
+ .getService(Ci.nsIEventListenerService)
+ .addSystemEventListener(gBrowser, "click", contentAreaClick, true);
+
+ gBrowser.addEventListener("DOMUpdatePageReport", gPopupBlockerObserver, false);
+
+ // Note that the XBL binding is untrusted
+ gBrowser.addEventListener("PluginBindingAttached", gPluginHandler, true, true);
+ gBrowser.addEventListener("PluginCrashed", gPluginHandler, true);
+ gBrowser.addEventListener("PluginOutdated", gPluginHandler, true);
+ gBrowser.addEventListener("PluginInstantiated", gPluginHandler, true);
+ gBrowser.addEventListener("PluginRemoved", gPluginHandler, true);
+
+ Services.obs.addObserver(gPluginHandler.pluginCrashed, "plugin-crashed", false);
+
+ window.addEventListener("AppCommand", HandleAppCommandEvent, true);
+
+ // These routines add message listeners. They must run before
+ // loading the frame script to ensure that we don't miss any
+ // message sent between when the frame script is loaded and when
+ // the listener is registered.
+#ifdef MOZ_DEVTOOLS
+ DevToolsTheme.init();
+#endif
+ gFindBarSettings.init();
+
+ messageManager.loadFrameScript("chrome://browser/content/content.js", true);
+ messageManager.loadFrameScript("chrome://browser/content/content-sessionStore.js", true);
+
+ // initialize observers and listeners
+ // and give C++ access to gBrowser
+ XULBrowserWindow.init();
+ window.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(nsIWebNavigation)
+ .QueryInterface(Ci.nsIDocShellTreeItem).treeOwner
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIXULWindow)
+ .XULBrowserWindow = window.XULBrowserWindow;
+ window.QueryInterface(Ci.nsIDOMChromeWindow).browserDOMWindow =
+ new nsBrowserAccess();
+
+ // set default character set if provided
+ // window.arguments[1]: character set (string)
+ if ("arguments" in window && window.arguments.length > 1 && window.arguments[1]) {
+ if (window.arguments[1].startsWith("charset=")) {
+ var arrayArgComponents = window.arguments[1].split("=");
+ if (arrayArgComponents) {
+ //we should "inherit" the charset menu setting in a new window
+ //TFE FIXME: this is now a wrappednative and can't be set this way.
+ //getMarkupDocumentViewer().defaultCharacterSet = arrayArgComponents[1];
+ }
+ }
+ }
+
+ // Manually hook up session and global history for the first browser
+ // so that we don't have to load global history before bringing up a
+ // window.
+ // Wire up session and global history before any possible
+ // progress notifications for back/forward button updating
+ gBrowser.webNavigation.sessionHistory = Cc["@mozilla.org/browser/shistory;1"].
+ createInstance(Ci.nsISHistory);
+ Services.obs.addObserver(gBrowser.browsers[0], "browser:purge-session-history", false);
+
+ // remove the disablehistory attribute so the browser cleans up, as
+ // though it had done this work itself
+ gBrowser.browsers[0].removeAttribute("disablehistory");
+
+ // enable global history
+ try {
+ if (!gMultiProcessBrowser)
+ gBrowser.docShell.useGlobalHistory = true;
+ } catch(ex) {
+ Cu.reportError("Places database may be locked: " + ex);
+ }
+
+ // hook up UI through progress listener
+ gBrowser.addProgressListener(window.XULBrowserWindow);
+ gBrowser.addTabsProgressListener(window.TabsProgressListener);
+
+ // setup our common DOMLinkAdded listener
+ gBrowser.addEventListener("DOMLinkAdded", DOMLinkHandler, false);
+
+ // setup our MozApplicationManifest listener
+ gBrowser.addEventListener("MozApplicationManifest",
+ OfflineApps, false);
+
+ // setup simple gestures support
+ gGestureSupport.init(true);
+
+ // setup history swipe animation
+ gHistorySwipeAnimation.init();
+
+ if (window.opener && !window.opener.closed) {
+ let openerSidebarBox = window.opener.document.getElementById("sidebar-box");
+ // If the opener had a sidebar, open the same sidebar in our window.
+ // The opener can be the hidden window too, if we're coming from the state
+ // where no windows are open, and the hidden window has no sidebar box.
+ if (openerSidebarBox && !openerSidebarBox.hidden) {
+ let sidebarCmd = openerSidebarBox.getAttribute("sidebarcommand");
+ let sidebarCmdElem = document.getElementById(sidebarCmd);
+
+ // dynamically generated sidebars will fail this check.
+ if (sidebarCmdElem) {
+ let sidebarBox = document.getElementById("sidebar-box");
+ let sidebarTitle = document.getElementById("sidebar-title");
+
+ sidebarTitle.setAttribute(
+ "value", window.opener.document.getElementById("sidebar-title").getAttribute("value"));
+ sidebarBox.setAttribute("width", openerSidebarBox.boxObject.width);
+
+ sidebarBox.setAttribute("sidebarcommand", sidebarCmd);
+ // Note: we're setting 'src' on sidebarBox, which is a <vbox>, not on
+ // the <browser id="sidebar">. This lets us delay the actual load until
+ // delayedStartup().
+ sidebarBox.setAttribute(
+ "src", window.opener.document.getElementById("sidebar").getAttribute("src"));
+ mustLoadSidebar = true;
+
+ sidebarBox.hidden = false;
+ document.getElementById("sidebar-splitter").hidden = false;
+ sidebarCmdElem.setAttribute("checked", "true");
+ }
+ }
+ }
+ else {
+ let box = document.getElementById("sidebar-box");
+ if (box.hasAttribute("sidebarcommand")) {
+ let commandID = box.getAttribute("sidebarcommand");
+ if (commandID) {
+ let command = document.getElementById(commandID);
+ if (command) {
+ mustLoadSidebar = true;
+ box.hidden = false;
+ document.getElementById("sidebar-splitter").hidden = false;
+ command.setAttribute("checked", "true");
+ }
+ else {
+ // Remove the |sidebarcommand| attribute, because the element it
+ // refers to no longer exists, so we should assume this sidebar
+ // panel has been uninstalled. (249883)
+ box.removeAttribute("sidebarcommand");
+ }
+ }
+ }
+ }
+
+ // Certain kinds of automigration rely on this notification to complete their
+ // tasks BEFORE the browser window is shown.
+ Services.obs.notifyObservers(null, "browser-window-before-show", "");
+
+ // Set a sane starting width/height for all resolutions on new profiles.
+ if (!document.documentElement.hasAttribute("width")) {
+ let defaultWidth;
+ let defaultHeight;
+
+ // Very small: maximize the window
+ // Portrait : use about full width and 3/4 height, to view entire pages
+ // at once (without being obnoxiously tall)
+ // Widescreen: use about half width, to suggest side-by-side page view
+ // Otherwise : use 3/4 height and width
+ if (screen.availHeight <= 600) {
+ document.documentElement.setAttribute("sizemode", "maximized");
+ defaultWidth = 610;
+ defaultHeight = 450;
+ }
+ else {
+ if (screen.availWidth <= screen.availHeight) {
+ defaultWidth = screen.availWidth * .9;
+ defaultHeight = screen.availHeight * .75;
+ }
+ else if (screen.availWidth >= 2048) {
+ defaultWidth = (screen.availWidth / 2) - 20;
+ defaultHeight = screen.availHeight - 10;
+ }
+ else {
+ defaultWidth = screen.availWidth * .75;
+ defaultHeight = screen.availHeight * .75;
+ }
+
+#ifdef MOZ_WIDGET_GTK2
+ // On X, we're not currently able to account for the size of the window
+ // border. Use 28px as a guess (titlebar + bottom window border)
+ defaultHeight -= 28;
+#endif
+ }
+ document.documentElement.setAttribute("width", defaultWidth);
+ document.documentElement.setAttribute("height", defaultHeight);
+ }
+
+ if (!gShowPageResizers)
+ document.getElementById("status-bar").setAttribute("hideresizer", "true");
+
+ if (!window.toolbar.visible) {
+ // adjust browser UI for popups
+ if (gURLBar) {
+ gURLBar.setAttribute("readonly", "true");
+ gURLBar.setAttribute("enablehistory", "false");
+ }
+ goSetCommandEnabled("cmd_newNavigatorTab", false);
+ }
+
+#ifdef MENUBAR_CAN_AUTOHIDE
+ updateAppButtonDisplay();
+#endif
+
+ // Misc. inits.
+ CombinedStopReload.init();
+ allTabs.readPref();
+ TabsOnTop.init();
+ AudioIndicator.init();
+ gPrivateBrowsingUI.init();
+ TabsInTitlebar.init();
+ retrieveToolbarIconsizesFromTheme();
+ ToolbarIconColor.init();
+ UserAgentCompatibility.init();
+
+#ifdef XP_WIN
+ if (window.matchMedia("(-moz-os-version: windows-win8)").matches &&
+ window.matchMedia("(-moz-windows-default-theme)").matches) {
+ let windows8WindowFrameColor = Cu.import("resource:///modules/Windows8WindowFrameColor.jsm", {}).Windows8WindowFrameColor;
+
+ var windowFrameColor;
+ windowFrameColor = windows8WindowFrameColor.get_win8();
+
+ // Formula from Microsoft's UWP guideline.
+ let backgroundLuminance = (windowFrameColor[0] * 2 +
+ windowFrameColor[1] * 5 +
+ windowFrameColor[2]) / 8;
+ if (backgroundLuminance <= 128) {
+ document.documentElement.setAttribute("darkwindowframe", "true");
+ }
+ }
+#endif
+
+ // Wait until chrome is painted before executing code not critical to making the window visible
+ this._boundDelayedStartup = this._delayedStartup.bind(this, mustLoadSidebar);
+ window.addEventListener("MozAfterPaint", this._boundDelayedStartup);
+
+ this._loadHandled = true;
+ },
+
+ _cancelDelayedStartup: function () {
+ window.removeEventListener("MozAfterPaint", this._boundDelayedStartup);
+ this._boundDelayedStartup = null;
+ },
+
+ _delayedStartup: function(mustLoadSidebar) {
+ let tmp = {};
+
+ this._cancelDelayedStartup();
+
+ let uriToLoad = this._getUriToLoad();
+ var isLoadingBlank = isBlankPageURL(uriToLoad);
+
+ // This pageshow listener needs to be registered before we may call
+ // swapBrowsersAndCloseOther() to receive pageshow events fired by that.
+ gBrowser.addEventListener("pageshow", function(event) {
+ // Filter out events that are not about the document load we are interested in
+ if (content && event.target == content.document)
+ setTimeout(pageShowEventHandlers, 0, event.persisted);
+ }, true);
+
+ if (uriToLoad && uriToLoad != "about:blank") {
+ if (uriToLoad instanceof Ci.nsISupportsArray) {
+ let count = uriToLoad.Count();
+ let specs = [];
+ for (let i = 0; i < count; i++) {
+ let urisstring = uriToLoad.GetElementAt(i).QueryInterface(Ci.nsISupportsString);
+ specs.push(urisstring.data);
+ }
+
+ // This function throws for certain malformed URIs, so use exception handling
+ // so that we don't disrupt startup
+ try {
+ gBrowser.loadTabs(specs, false, true);
+ } catch (e) {}
+ }
+ else if (uriToLoad instanceof XULElement) {
+ // swap the given tab with the default about:blank tab and then close
+ // the original tab in the other window.
+
+ // Stop the about:blank load
+ gBrowser.stop();
+ // make sure it has a docshell
+ gBrowser.docShell;
+
+ gBrowser.swapBrowsersAndCloseOther(gBrowser.selectedTab, uriToLoad);
+ }
+ // window.arguments[2]: referrer (nsIURI | string)
+ // [3]: postData (nsIInputStream)
+ // [4]: allowThirdPartyFixup (bool)
+ // [5]: referrerPolicy (int)
+ // [6]: originPrincipal (nsIPrincipal)
+ // [7]: triggeringPrincipal (nsIPrincipal)
+ else if (window.arguments.length >= 3) {
+ let referrerURI = window.arguments[2];
+ if (typeof(referrerURI) == "string") {
+ try {
+ referrerURI = makeURI(referrerURI);
+ } catch (e) {
+ referrerURI = null;
+ }
+ }
+ let referrerPolicy = (window.arguments[5] != undefined ?
+ window.arguments[5] : Ci.nsIHttpChannel.REFERRER_POLICY_DEFAULT);
+ loadURI(uriToLoad, referrerURI, window.arguments[3] || null,
+ window.arguments[4] || false, referrerPolicy,
+ // pass the origin principal (if any) and force its use to create
+ // an initial about:blank viewer if present:
+ window.arguments[6], !!window.arguments[6], window.arguments[7]);
+ window.focus();
+ }
+ // Note: loadOneOrMoreURIs *must not* be called if window.arguments.length >= 3.
+ // Such callers expect that window.arguments[0] is handled as a single URI.
+ else
+ loadOneOrMoreURIs(uriToLoad);
+ }
+
+ Services.obs.addObserver(gSessionHistoryObserver, "browser:purge-session-history", false);
+ Services.obs.addObserver(gXPInstallObserver, "addon-install-disabled", false);
+ Services.obs.addObserver(gXPInstallObserver, "addon-install-started", false);
+ Services.obs.addObserver(gXPInstallObserver, "addon-install-blocked", false);
+ Services.obs.addObserver(gXPInstallObserver, "addon-install-origin-blocked", false);
+ Services.obs.addObserver(gXPInstallObserver, "addon-install-failed", false);
+ Services.obs.addObserver(gXPInstallObserver, "addon-install-complete", false);
+ Services.obs.addObserver(gXSSObserver, "xss-on-violate-policy", false);
+
+ gPrefService.addObserver(gURLBarSettings.prefSuggest, gURLBarSettings, false);
+ gPrefService.addObserver(gURLBarSettings.prefKeyword, gURLBarSettings, false);
+
+ gURLBarSettings.writePlaceholder();
+
+ BrowserOffline.init();
+ OfflineApps.init();
+ IndexedDBPromptHelper.init();
+ AddonManager.addAddonListener(AddonsMgrListener);
+#ifdef MOZ_WEBRTC
+ WebrtcIndicator.init();
+#endif
+
+ // Ensure login manager is up and running.
+ Services.logins;
+
+ if (mustLoadSidebar) {
+ let sidebar = document.getElementById("sidebar");
+ let sidebarBox = document.getElementById("sidebar-box");
+ sidebar.setAttribute("src", sidebarBox.getAttribute("src"));
+ }
+
+ UpdateUrlbarSearchSplitterState();
+
+ if (!isLoadingBlank || !focusAndSelectUrlBar())
+ gBrowser.selectedBrowser.focus();
+
+ gNavToolbox.customizeDone = BrowserToolboxCustomizeDone;
+ gNavToolbox.customizeChange = BrowserToolboxCustomizeChange;
+
+ // Set up Sanitize Item
+ this._initializeSanitizer();
+
+ // Enable/Disable auto-hide tabbar
+ gBrowser.tabContainer.updateVisibility();
+
+ gPrefService.addObserver(gHomeButton.prefDomain, gHomeButton, false);
+
+ var homeButton = document.getElementById("home-button");
+ gHomeButton.updateTooltip(homeButton);
+ gHomeButton.updatePersonalToolbarStyle(homeButton);
+
+ // BiDi UI
+ gBidiUI = isBidiEnabled();
+ if (gBidiUI) {
+ document.getElementById("documentDirection-separator").hidden = false;
+ document.getElementById("documentDirection-swap").hidden = false;
+ document.getElementById("textfieldDirection-separator").hidden = false;
+ document.getElementById("textfieldDirection-swap").hidden = false;
+ }
+
+ // Setup click-and-hold gestures access to the session history
+ // menus if global click-and-hold isn't turned on
+ if (!Services.prefs.getBoolPref("ui.click_hold_context_menus", false))
+ SetClickAndHoldHandlers();
+
+ // Initialize the full zoom setting.
+ // We do this before the session restore service gets initialized so we can
+ // apply full zoom settings to tabs restored by the session restore service.
+ FullZoom.init();
+
+ // Bug 666804 - NetworkPrioritizer support for e10s
+ if (!gMultiProcessBrowser) {
+ let NP = {};
+ Cu.import("resource:///modules/NetworkPrioritizer.jsm", NP);
+ NP.trackBrowserWindow(window);
+ }
+
+ // initialize the session-restore service (in case it's not already running)
+ let ss = Cc["@mozilla.org/browser/sessionstore;1"].getService(Ci.nsISessionStore);
+ let ssPromise = ss.init(window);
+
+ PlacesToolbarHelper.init();
+
+ ctrlTab.readPref();
+ gPrefService.addObserver(ctrlTab.prefName, ctrlTab, false);
+ gPrefService.addObserver(allTabs.prefName, allTabs, false);
+
+ // Initialize the download manager some time after the app starts so that
+ // auto-resume downloads begin (such as after crashing or quitting with
+ // active downloads) and speeds up the first-load of the download manager UI.
+ // If the user manually opens the download manager before the timeout, the
+ // downloads will start right away, and getting the service again won't hurt.
+ setTimeout(function() {
+ try {
+ Cu.import("resource:///modules/DownloadsCommon.jsm", {})
+ .DownloadsCommon.initializeAllDataLinks();
+ Cu.import("resource:///modules/DownloadsTaskbar.jsm", {})
+ .DownloadsTaskbar.registerIndicator(window);
+ } catch(ex) {
+ Cu.reportError(ex);
+ }
+ }, 10000);
+
+ // Load the Login Manager data from disk off the main thread, some time
+ // after startup. If the data is required before the timeout, for example
+ // because a restored page contains a password field, it will be loaded on
+ // the main thread, and this initialization request will be ignored.
+ setTimeout(function() {
+ try {
+ Services.logins;
+ } catch (ex) {
+ Cu.reportError(ex);
+ }
+ }, 3000);
+
+ // The object handling the downloads indicator is also initialized here in the
+ // delayed startup function, but the actual indicator element is not loaded
+ // unless there are downloads to be displayed.
+ DownloadsButton.initializeIndicator();
+
+#ifndef XP_MACOSX
+ updateEditUIVisibility();
+ let placesContext = document.getElementById("placesContext");
+ placesContext.addEventListener("popupshowing", updateEditUIVisibility, false);
+ placesContext.addEventListener("popuphiding", updateEditUIVisibility, false);
+#endif
+
+#ifdef MOZ_PERSONAS
+ gBrowser.mPanelContainer.addEventListener("InstallBrowserTheme", LightWeightThemeWebInstaller, false, true);
+ gBrowser.mPanelContainer.addEventListener("PreviewBrowserTheme", LightWeightThemeWebInstaller, false, true);
+ gBrowser.mPanelContainer.addEventListener("ResetBrowserThemePreview", LightWeightThemeWebInstaller, false, true);
+#endif
+
+ // Bug 666808 - AeroPeek support for e10s
+ if (!gMultiProcessBrowser) {
+ if (Win7Features)
+ Win7Features.onOpenWindow();
+ }
+
+ // called when we go into full screen, even if initiated by a web page script
+ window.addEventListener("fullscreen", onFullScreen, true);
+
+ // Called when we enter DOM full-screen mode. Note we can already be in browser
+ // full-screen mode when we enter DOM full-screen mode.
+ window.addEventListener("MozDOMFullscreen:NewOrigin", onMozEnteredDomFullscreen, true);
+
+ if (window.fullScreen)
+ onFullScreen();
+ if (document.mozFullScreen)
+ onMozEnteredDomFullscreen();
+
+#ifdef MOZ_SERVICES_SYNC
+ // initialize the sync UI
+ gSyncUI.init();
+#endif
+
+ gBrowserThumbnails.init();
+
+ setUrlAndSearchBarWidthForConditionalForwardButton();
+ window.addEventListener("resize", function resizeHandler(event) {
+ if (event.target == window)
+ setUrlAndSearchBarWidthForConditionalForwardButton();
+ });
+
+ // Enable Error Console?
+ let consoleEnabled = gPrefService.getBoolPref("devtools.errorconsole.enabled");
+ if (consoleEnabled) {
+ let cmd = document.getElementById("Tools:ErrorConsole");
+ cmd.removeAttribute("disabled");
+ cmd.removeAttribute("hidden");
+ }
+
+#ifdef MENUBAR_CAN_AUTOHIDE
+ // If the user (or the locale) hasn't enabled the top-level "Character
+ // Encoding" menu via the "browser.menu.showCharacterEncoding" preference,
+ // hide it.
+ if ("true" != gPrefService.getComplexValue("browser.menu.showCharacterEncoding",
+ Ci.nsIPrefLocalizedString).data)
+ document.getElementById("appmenu_charsetMenu").hidden = true;
+#endif
+
+ let appMenuButton = document.getElementById("appmenu-button");
+ let appMenuPopup = document.getElementById("appmenu-popup");
+ if (appMenuButton && appMenuPopup) {
+ let appMenuOpening = null;
+ appMenuButton.addEventListener("mousedown", function(event) {
+ if (event.button == 0)
+ appMenuOpening = new Date();
+ }, false);
+ appMenuPopup.addEventListener("popupshown", function(event) {
+ if (event.target != appMenuPopup || !appMenuOpening)
+ return;
+ let duration = new Date() - appMenuOpening;
+ appMenuOpening = null;
+ }, false);
+ }
+
+ window.addEventListener("mousemove", MousePosTracker, false);
+ window.addEventListener("dragover", MousePosTracker, false);
+
+ // End startup crash tracking after a delay to catch crashes while restoring
+ // tabs and to postpone saving the pref to disk.
+ try {
+ const startupCrashEndDelay = 30 * 1000;
+ setTimeout(Services.startup.trackStartupCrashEnd, startupCrashEndDelay);
+ } catch (ex) {
+ Cu.reportError("Could not end startup crash tracking: " + ex);
+ }
+
+ ssPromise.then(() =>{
+ // Bail out if the window has been closed in the meantime.
+ if (window.closed) {
+ return;
+ }
+ if ("TabView" in window) {
+ TabView.init();
+ }
+ // XXX: do we still need this?...
+ setTimeout(function () { BrowserChromeTest.markAsReady(); }, 0);
+ });
+
+ this.delayedStartupFinished = true;
+
+ Services.obs.notifyObservers(window, "browser-delayed-startup-finished", "");
+ },
+
+ // Returns the URI(s) to load at startup.
+ _getUriToLoad: function () {
+ // window.arguments[0]: URI to load (string), or an nsISupportsArray of
+ // nsISupportsStrings to load, or a xul:tab of
+ // a tabbrowser, which will be replaced by this
+ // window (for this case, all other arguments are
+ // ignored).
+ if (!window.arguments || !window.arguments[0])
+ return null;
+
+ let uri = window.arguments[0];
+ let sessionStartup = Cc["@mozilla.org/browser/sessionstartup;1"]
+ .getService(Ci.nsISessionStartup);
+ let defaultArgs = Cc["@mozilla.org/browser/clh;1"]
+ .getService(Ci.nsIBrowserHandler)
+ .defaultArgs;
+
+ // If the given URI matches defaultArgs (the default homepage) we want
+ // to block its load if we're going to restore a session anyway.
+ if (uri == defaultArgs && sessionStartup.willOverrideHomepage)
+ return null;
+
+ return uri;
+ },
+
+ onUnload: function() {
+ // In certain scenarios it's possible for unload to be fired before onload,
+ // (e.g. if the window is being closed after browser.js loads but before the
+ // load completes). In that case, there's nothing to do here.
+ if (!this._loadHandled)
+ return;
+
+ // First clean up services initialized in gBrowserInit.onLoad (or those whose
+ // uninit methods don't depend on the services having been initialized).
+
+ allTabs.uninit();
+
+ CombinedStopReload.uninit();
+
+ gGestureSupport.init(false);
+
+ gHistorySwipeAnimation.uninit();
+
+ FullScreen.cleanup();
+
+ Services.obs.removeObserver(gPluginHandler.pluginCrashed, "plugin-crashed");
+
+ try {
+ gBrowser.removeProgressListener(window.XULBrowserWindow);
+ gBrowser.removeTabsProgressListener(window.TabsProgressListener);
+ } catch (ex) {
+ }
+
+ BookmarkingUI.uninit();
+
+ TabsOnTop.uninit();
+
+ AudioIndicator.uninit();
+
+ TabsInTitlebar.uninit();
+
+ ToolbarIconColor.uninit();
+
+#ifdef MOZ_DEVTOOLS
+ DevToolsTheme.uninit();
+#endif
+ gFindBarSettings.uninit();
+
+ UserAgentCompatibility.uninit();
+
+ var enumerator = Services.wm.getEnumerator(null);
+ enumerator.getNext();
+ if (!enumerator.hasMoreElements()) {
+ document.persist("sidebar-box", "sidebarcommand");
+ document.persist("sidebar-box", "width");
+ document.persist("sidebar-box", "src");
+ document.persist("sidebar-title", "value");
+ }
+
+ // Now either cancel delayedStartup, or clean up the services initialized from
+ // it.
+ if (this._boundDelayedStartup) {
+ this._cancelDelayedStartup();
+ } else {
+ if (Win7Features)
+ Win7Features.onCloseWindow();
+
+ gPrefService.removeObserver(ctrlTab.prefName, ctrlTab);
+ gPrefService.removeObserver(allTabs.prefName, allTabs);
+ ctrlTab.uninit();
+ if ("TabView" in window) {
+ TabView.uninit();
+ }
+ gBrowserThumbnails.uninit();
+ FullZoom.destroy();
+
+ Services.obs.removeObserver(gSessionHistoryObserver, "browser:purge-session-history");
+ Services.obs.removeObserver(gXPInstallObserver, "addon-install-disabled");
+ Services.obs.removeObserver(gXPInstallObserver, "addon-install-started");
+ Services.obs.removeObserver(gXPInstallObserver, "addon-install-blocked");
+ Services.obs.removeObserver(gXPInstallObserver, "addon-install-origin-blocked");
+ Services.obs.removeObserver(gXPInstallObserver, "addon-install-failed");
+ Services.obs.removeObserver(gXPInstallObserver, "addon-install-complete");
+ Services.obs.removeObserver(gXSSObserver, "xss-on-violate-policy");
+
+ try {
+ gPrefService.removeObserver(gURLBarSettings.prefSuggest, gURLBarSettings);
+ gPrefService.removeObserver(gURLBarSettings.prefKeyword, gURLBarSettings);
+ } catch (ex) {
+ Cu.reportError(ex);
+ }
+
+ try {
+ gPrefService.removeObserver(gHomeButton.prefDomain, gHomeButton);
+ } catch (ex) {
+ Cu.reportError(ex);
+ }
+
+ BrowserOffline.uninit();
+ OfflineApps.uninit();
+ IndexedDBPromptHelper.uninit();
+ AddonManager.removeAddonListener(AddonsMgrListener);
+ }
+
+ // Final window teardown, do this last.
+ window.XULBrowserWindow.destroy();
+ window.XULBrowserWindow = null;
+ window.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIDocShellTreeItem).treeOwner
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIXULWindow)
+ .XULBrowserWindow = null;
+ window.QueryInterface(Ci.nsIDOMChromeWindow).browserDOMWindow = null;
+ },
+
+#ifdef XP_MACOSX
+ // nonBrowserWindowStartup(), nonBrowserWindowDelayedStartup(), and
+ // nonBrowserWindowShutdown() are used for non-browser windows in
+ // macBrowserOverlay
+ nonBrowserWindowStartup: function() {
+ // Disable inappropriate commands / submenus
+ var disabledItems = ['Browser:SavePage',
+ 'Browser:SendLink', 'cmd_pageSetup', 'cmd_print', 'cmd_find', 'cmd_findAgain',
+ 'viewToolbarsMenu', 'viewSidebarMenuMenu', 'Browser:Reload',
+ 'viewFullZoomMenu', 'pageStyleMenu', 'charsetMenu', 'View:PageSource', 'View:FullScreen',
+ 'viewHistorySidebar', 'Browser:AddBookmarkAs', 'Browser:BookmarkAllTabs',
+ 'View:PageInfo', 'Browser:ToggleAddonBar'];
+ var element;
+
+ for (let disabledItem of disabledItems) {
+ element = document.getElementById(disabledItem);
+ if (element)
+ element.setAttribute("disabled", "true");
+ }
+
+ // If no windows are active (i.e. we're the hidden window), disable the close, minimize
+ // and zoom menu commands as well
+ if (window.location.href == "chrome://browser/content/hiddenWindow.xul") {
+ var hiddenWindowDisabledItems = ['cmd_close', 'minimizeWindow', 'zoomWindow'];
+ for (let hiddenWindowDisabledItem of hiddenWindowDisabledItems) {
+ element = document.getElementById(hiddenWindowDisabledItem);
+ if (element)
+ element.setAttribute("disabled", "true");
+ }
+
+ // also hide the window-list separator
+ element = document.getElementById("sep-window-list");
+ element.setAttribute("hidden", "true");
+
+ // Setup the dock menu.
+ let dockMenuElement = document.getElementById("menu_mac_dockmenu");
+ if (dockMenuElement != null) {
+ let nativeMenu = Cc["@mozilla.org/widget/standalonenativemenu;1"]
+ .createInstance(Ci.nsIStandaloneNativeMenu);
+
+ try {
+ nativeMenu.init(dockMenuElement);
+
+ let dockSupport = Cc["@mozilla.org/widget/macdocksupport;1"]
+ .getService(Ci.nsIMacDockSupport);
+ dockSupport.dockMenu = nativeMenu;
+ }
+ catch (e) {
+ }
+ }
+ }
+
+ if (PrivateBrowsingUtils.permanentPrivateBrowsing) {
+ document.getElementById("macDockMenuNewWindow").hidden = true;
+ }
+
+ this._delayedStartupTimeoutId = setTimeout(this.nonBrowserWindowDelayedStartup.bind(this), 0);
+ },
+
+ nonBrowserWindowDelayedStartup: function() {
+ this._delayedStartupTimeoutId = null;
+
+ // initialise the offline listener
+ BrowserOffline.init();
+
+ // Set up Sanitize Item
+ this._initializeSanitizer();
+
+ // initialize the private browsing UI
+ gPrivateBrowsingUI.init();
+
+#ifdef MOZ_SERVICES_SYNC
+ // initialize the sync UI
+ gSyncUI.init();
+#endif
+ },
+
+ nonBrowserWindowShutdown: function() {
+ // If nonBrowserWindowDelayedStartup hasn't run yet, we have no work to do -
+ // just cancel the pending timeout and return;
+ if (this._delayedStartupTimeoutId) {
+ clearTimeout(this._delayedStartupTimeoutId);
+ return;
+ }
+
+ BrowserOffline.uninit();
+ },
+#endif
+
+ _initializeSanitizer: function() {
+ const kDidSanitizeDomain = "privacy.sanitize.didShutdownSanitize";
+ if (gPrefService.prefHasUserValue(kDidSanitizeDomain)) {
+ gPrefService.clearUserPref(kDidSanitizeDomain);
+ // We need to persist this preference change, since we want to
+ // check it at next app start even if the browser exits abruptly
+ gPrefService.savePrefFile(null);
+ }
+
+ /**
+ * Migrate Firefox 3.0 privacy.item prefs under one of these conditions:
+ *
+ * a) User has customized any privacy.item prefs
+ * b) privacy.sanitize.sanitizeOnShutdown is set
+ */
+ if (!gPrefService.getBoolPref("privacy.sanitize.migrateFx3Prefs")) {
+ let itemBranch = gPrefService.getBranch("privacy.item.");
+ let itemArray = itemBranch.getChildList("");
+
+ // See if any privacy.item prefs are set
+ let doMigrate = itemArray.some(function (name) itemBranch.prefHasUserValue(name));
+ // Or if sanitizeOnShutdown is set
+ if (!doMigrate)
+ doMigrate = gPrefService.getBoolPref("privacy.sanitize.sanitizeOnShutdown");
+
+ if (doMigrate) {
+ let cpdBranch = gPrefService.getBranch("privacy.cpd.");
+ let clearOnShutdownBranch = gPrefService.getBranch("privacy.clearOnShutdown.");
+ for (let name of itemArray) {
+ try {
+ // don't migrate password or offlineApps clearing in the CRH dialog since
+ // there's no UI for those anymore. They default to false. bug 497656
+ if (name != "passwords" && name != "offlineApps")
+ cpdBranch.setBoolPref(name, itemBranch.getBoolPref(name));
+ clearOnShutdownBranch.setBoolPref(name, itemBranch.getBoolPref(name));
+ }
+ catch(e) {
+ Cu.reportError("Exception thrown during privacy pref migration: " + e);
+ }
+ }
+ }
+
+ gPrefService.setBoolPref("privacy.sanitize.migrateFx3Prefs", true);
+ }
+ },
+}
+
+
+/* Legacy global init functions */
+var BrowserStartup = gBrowserInit.onLoad.bind(gBrowserInit);
+var BrowserShutdown = gBrowserInit.onUnload.bind(gBrowserInit);
+#ifdef XP_MACOSX
+var nonBrowserWindowStartup = gBrowserInit.nonBrowserWindowStartup.bind(gBrowserInit);
+var nonBrowserWindowDelayedStartup = gBrowserInit.nonBrowserWindowDelayedStartup.bind(gBrowserInit);
+var nonBrowserWindowShutdown = gBrowserInit.nonBrowserWindowShutdown.bind(gBrowserInit);
+#endif
+
+function HandleAppCommandEvent(evt) {
+ switch (evt.command) {
+ case "Back":
+ BrowserBack();
+ break;
+ case "Forward":
+ BrowserForward();
+ break;
+ case "Reload":
+ BrowserReloadSkipCache();
+ break;
+ case "Stop":
+ if (XULBrowserWindow.stopCommand.getAttribute("disabled") != "true")
+ BrowserStop();
+ break;
+ case "Search":
+ BrowserSearch.webSearch();
+ break;
+ case "Bookmarks":
+ toggleSidebar('viewBookmarksSidebar');
+ break;
+ case "Home":
+ BrowserHome();
+ break;
+ case "New":
+ BrowserOpenTab();
+ break;
+ case "Close":
+ BrowserCloseTabOrWindow();
+ break;
+ case "Find":
+ gFindBar.onFindCommand();
+ break;
+ case "Help":
+ openHelpLink('firefox-help');
+ break;
+ case "Open":
+ BrowserOpenFileWindow();
+ break;
+ case "Print":
+ PrintUtils.print();
+ break;
+ case "Save":
+ saveDocument(window.content.document);
+ break;
+ case "SendMail":
+ MailIntegration.sendLinkForWindow(window.content);
+ break;
+ default:
+ return;
+ }
+ evt.stopPropagation();
+ evt.preventDefault();
+}
+
+function gotoHistoryIndex(aEvent) {
+ let index = aEvent.target.getAttribute("index");
+ if (!index)
+ return false;
+
+ let where = whereToOpenLink(aEvent);
+
+ if (where == "current") {
+ // Normal click. Go there in the current tab and update session history.
+
+ try {
+ gBrowser.gotoIndex(index);
+ }
+ catch(ex) {
+ return false;
+ }
+ return true;
+ }
+ // Modified click. Go there in a new tab/window.
+
+ duplicateTabIn(gBrowser.selectedTab, where, index - gBrowser.sessionHistory.index);
+ return true;
+}
+
+function BrowserForward(aEvent) {
+ let where = whereToOpenLink(aEvent, false, true);
+
+ if (where == "current") {
+ try {
+ gBrowser.goForward();
+ }
+ catch(ex) {
+ }
+ }
+ else {
+ duplicateTabIn(gBrowser.selectedTab, where, 1);
+ }
+}
+
+function BrowserBack(aEvent) {
+ let where = whereToOpenLink(aEvent, false, true);
+
+ if (where == "current") {
+ try {
+ gBrowser.goBack();
+ }
+ catch(ex) {
+ }
+ }
+ else {
+ duplicateTabIn(gBrowser.selectedTab, where, -1);
+ }
+}
+
+function BrowserHandleBackspace()
+{
+ switch (gPrefService.getIntPref("browser.backspace_action")) {
+ case 0:
+ BrowserBack();
+ break;
+ case 1:
+ goDoCommand("cmd_scrollPageUp");
+ break;
+ }
+}
+
+function BrowserHandleShiftBackspace()
+{
+ switch (gPrefService.getIntPref("browser.backspace_action")) {
+ case 0:
+ BrowserForward();
+ break;
+ case 1:
+ goDoCommand("cmd_scrollPageDown");
+ break;
+ }
+}
+
+function BrowserStop() {
+ const stopFlags = nsIWebNavigation.STOP_ALL;
+ gBrowser.webNavigation.stop(stopFlags);
+}
+
+function BrowserReloadOrDuplicate(aEvent) {
+ var backgroundTabModifier = aEvent.button == 1 ||
+#ifdef XP_MACOSX
+ aEvent.metaKey;
+#else
+ aEvent.ctrlKey;
+#endif
+ if (aEvent.shiftKey && !backgroundTabModifier) {
+ BrowserReloadSkipCache();
+ return;
+ }
+
+ let where = whereToOpenLink(aEvent, false, true);
+ if (where == "current")
+ BrowserReload();
+ else
+ duplicateTabIn(gBrowser.selectedTab, where);
+}
+
+function BrowserReload() {
+ const reloadFlags = nsIWebNavigation.LOAD_FLAGS_NONE;
+ BrowserReloadWithFlags(reloadFlags);
+}
+
+function BrowserReloadSkipCache() {
+ // Bypass proxy and cache.
+ const reloadFlags = nsIWebNavigation.LOAD_FLAGS_BYPASS_PROXY | nsIWebNavigation.LOAD_FLAGS_BYPASS_CACHE;
+ BrowserReloadWithFlags(reloadFlags);
+}
+
+var BrowserHome = BrowserGoHome;
+function BrowserGoHome(aEvent) {
+ if (aEvent && "button" in aEvent &&
+ aEvent.button == 2) // right-click: do nothing
+ return;
+
+ var homePage = gHomeButton.getHomePage();
+ var where = whereToOpenLink(aEvent, false, true);
+ var urls;
+
+ // Home page should open in a new tab when current tab is an app tab
+ if (where == "current" &&
+ gBrowser &&
+ gBrowser.selectedTab.pinned)
+ where = "tab";
+
+ // openUILinkIn in utilityOverlay.js doesn't handle loading multiple pages
+ switch (where) {
+ case "current":
+ loadOneOrMoreURIs(homePage);
+ break;
+ case "tabshifted":
+ case "tab":
+ urls = homePage.split("|");
+ var loadInBackground = Services.prefs.getBoolPref("browser.tabs.loadBookmarksInBackground", false);
+ gBrowser.loadTabs(urls, loadInBackground);
+ break;
+ case "window":
+ OpenBrowserWindow();
+ break;
+ }
+}
+
+function loadOneOrMoreURIs(aURIString)
+{
+#ifdef XP_MACOSX
+ // we're not a browser window, pass the URI string to a new browser window
+ if (window.location.href != getBrowserURL())
+ {
+ window.openDialog(getBrowserURL(), "_blank", "all,dialog=no", aURIString);
+ return;
+ }
+#endif
+ // This function throws for certain malformed URIs, so use exception handling
+ // so that we don't disrupt startup
+ try {
+ gBrowser.loadTabs(aURIString.split("|"), false, true);
+ }
+ catch (e) {
+ }
+}
+
+function focusAndSelectUrlBar() {
+ if (gURLBar) {
+ if (window.fullScreen)
+ FullScreen.showNavToolbox();
+
+ gURLBar.select();
+ if (document.activeElement == gURLBar.inputField)
+ return true;
+ }
+ return false;
+}
+
+function openLocation() {
+ if (focusAndSelectUrlBar())
+ return;
+
+#ifdef XP_MACOSX
+ if (window.location.href != getBrowserURL()) {
+ var win = getTopWin();
+ if (win) {
+ // If there's an open browser window, it should handle this command
+ win.focus()
+ win.openLocation();
+ }
+ else {
+ // If there are no open browser windows, open a new one
+ win = window.openDialog("chrome://browser/content/", "_blank",
+ "chrome,all,dialog=no", BROWSER_NEW_TAB_URL);
+ win.addEventListener("load", openLocationCallback, false);
+ }
+ return;
+ }
+#endif
+ openDialog("chrome://browser/content/openLocation.xul", "_blank",
+ "chrome,modal,titlebar", window);
+}
+
+function openLocationCallback()
+{
+ // make sure the DOM is ready
+ setTimeout(function() { this.openLocation(); }, 0);
+}
+
+function BrowserOpenTab()
+{
+ openUILinkIn(BROWSER_NEW_TAB_URL, "tab");
+}
+
+/* Called from the openLocation dialog. This allows that dialog to instruct
+ its opener to open a new window and then step completely out of the way.
+ Anything less byzantine is causing horrible crashes, rather believably,
+ though oddly only on Linux. */
+function delayedOpenWindow(chrome, flags, href, postData)
+{
+ // The other way to use setTimeout,
+ // setTimeout(openDialog, 10, chrome, "_blank", flags, url),
+ // doesn't work here. The extra "magic" extra argument setTimeout adds to
+ // the callback function would confuse gBrowserInit.onLoad() by making
+ // window.arguments[1] be an integer instead of null.
+ setTimeout(function() { openDialog(chrome, "_blank", flags, href, null, null, postData); }, 10);
+}
+
+/* Required because the tab needs time to set up its content viewers and get the load of
+ the URI kicked off before becoming the active content area. */
+function delayedOpenTab(aUrl, aReferrer, aCharset, aPostData, aAllowThirdPartyFixup)
+{
+ gBrowser.loadOneTab(aUrl, {
+ referrerURI: aReferrer,
+ charset: aCharset,
+ postData: aPostData,
+ inBackground: false,
+ allowThirdPartyFixup: aAllowThirdPartyFixup});
+}
+
+var gLastOpenDirectory = {
+ _lastDir: null,
+ get path() {
+ if (!this._lastDir || !this._lastDir.exists()) {
+ try {
+ this._lastDir = gPrefService.getComplexValue("browser.open.lastDir",
+ Ci.nsILocalFile);
+ if (!this._lastDir.exists())
+ this._lastDir = null;
+ }
+ catch(e) {}
+ }
+ return this._lastDir;
+ },
+ set path(val) {
+ try {
+ if (!val || !val.isDirectory())
+ return;
+ } catch(e) {
+ return;
+ }
+ this._lastDir = val.clone();
+
+ // Don't save the last open directory pref inside the Private Browsing mode
+ if (!PrivateBrowsingUtils.isWindowPrivate(window))
+ gPrefService.setComplexValue("browser.open.lastDir", Ci.nsILocalFile,
+ this._lastDir);
+ },
+ reset: function() {
+ this._lastDir = null;
+ }
+};
+
+function BrowserOpenFileWindow()
+{
+ // Get filepicker component.
+ try {
+ const nsIFilePicker = Ci.nsIFilePicker;
+ let fp = Cc["@mozilla.org/filepicker;1"].createInstance(nsIFilePicker);
+ let fpCallback = function fpCallback_done(aResult) {
+ if (aResult == nsIFilePicker.returnOK) {
+ try {
+ if (fp.file) {
+ gLastOpenDirectory.path =
+ fp.file.parent.QueryInterface(Ci.nsILocalFile);
+ }
+ } catch (ex) {
+ }
+ openUILinkIn(fp.fileURL.spec, "current");
+ }
+ };
+
+ fp.init(window, gNavigatorBundle.getString("openFile"),
+ nsIFilePicker.modeOpen);
+ fp.appendFilters(nsIFilePicker.filterAll | nsIFilePicker.filterText |
+ nsIFilePicker.filterImages | nsIFilePicker.filterXML |
+ nsIFilePicker.filterHTML);
+ fp.displayDirectory = gLastOpenDirectory.path;
+ fp.open(fpCallback);
+ } catch (ex) {
+ }
+}
+
+function BrowserCloseTabOrWindow() {
+#ifdef XP_MACOSX
+ // If we're not a browser window, just close the window
+ if (window.location.href != getBrowserURL()) {
+ closeWindow(true);
+ return;
+ }
+#endif
+
+ // If the current tab is the last one, this will close the window.
+ gBrowser.removeCurrentTab({animate: true});
+}
+
+function BrowserTryToCloseWindow()
+{
+ if (WindowIsClosing())
+ window.close(); // WindowIsClosing does all the necessary checks
+}
+
+function loadURI(uri, referrer, postData, allowThirdPartyFixup, referrerPolicy,
+ originPrincipal, forceAboutBlankViewerInCurrent,
+ triggeringPrincipal) {
+ if (postData === undefined)
+ postData = null;
+
+ var flags = nsIWebNavigation.LOAD_FLAGS_NONE;
+ if (allowThirdPartyFixup) {
+ flags |= nsIWebNavigation.LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP;
+ flags |= nsIWebNavigation.LOAD_FLAGS_FIXUP_SCHEME_TYPOS;
+ }
+
+ try {
+ gBrowser.loadURIWithFlags(uri, {
+ flags: flags,
+ referrerURI: referrer,
+ referrerPolicy: referrerPolicy,
+ postData: postData,
+ originPrincipal: originPrincipal,
+ triggeringPrincipal: triggeringPrincipal,
+ forceAboutBlankViewerInCurrent: forceAboutBlankViewerInCurrent,
+ });
+ } catch (e) {}
+}
+
+/**
+ * Given a urlbar value, discerns between URIs, keywords and aliases.
+ *
+ * @param url
+ * The urlbar value.
+ * @param callback (optional, deprecated)
+ * The callback function invoked when done. This parameter is
+ * deprecated, please use the Promise that is returned.
+ *
+ * @return Promise<{ postData, url, mayInheritPrincipal }>
+ */
+function getShortcutOrURIAndPostData(url, callback = null) {
+ if (callback) {
+ Deprecated.warning("Please use the Promise returned by " +
+ "getShortcutOrURIAndPostData() instead of passing a " +
+ "callback",
+ "https://bugzilla.mozilla.org/show_bug.cgi?id=1100294");
+ }
+
+ return Task.spawn(function* () {
+ let mayInheritPrincipal = false;
+ let postData = null;
+ let shortcutURL = null;
+ let keyword = url;
+ let param = "";
+
+ let offset = url.indexOf(" ");
+ if (offset > 0) {
+ keyword = url.substr(0, offset);
+ param = url.substr(offset + 1);
+ }
+
+ let engine = Services.search.getEngineByAlias(keyword);
+ if (engine) {
+ let submission = engine.getSubmission(param, null, "keyword");
+ postData = submission.postData;
+ return { postData: submission.postData, url: submission.uri.spec,
+ mayInheritPrincipal };
+ }
+
+ let entry = yield PlacesUtils.keywords.fetch(keyword);
+ if (entry) {
+ shortcutURL = entry.url.href;
+ postData = entry.postData;
+ }
+
+ if (!shortcutURL) {
+ return { postData, url, mayInheritPrincipal };
+ }
+
+ let escapedPostData = "";
+ if (postData)
+ escapedPostData = unescape(postData);
+
+ if (/%s/i.test(shortcutURL) || /%s/i.test(escapedPostData)) {
+ let charset = "";
+ const re = /^(.*)\&mozcharset=([a-zA-Z][_\-a-zA-Z0-9]+)\s*$/;
+ let matches = shortcutURL.match(re);
+
+ if (matches) {
+ [, shortcutURL, charset] = matches;
+ } else {
+ let uri;
+ try {
+ // makeURI() throws if URI is invalid.
+ uri = makeURI(shortcutURL);
+ } catch (ex) {}
+
+ if (uri) {
+ // Try to get the saved character-set.
+ // Will return an empty string if character-set is not found.
+ charset = yield PlacesUtils.getCharsetForURI(uri);
+ }
+ }
+
+ // encodeURIComponent produces UTF-8, and cannot be used for other charsets.
+ // escape() works in those cases, but it doesn't uri-encode +, @, and /.
+ // Therefore we need to manually replace these ASCII characters by their
+ // encodeURIComponent result, to match the behavior of nsEscape() with
+ // url_XPAlphas
+ let encodedParam = "";
+ if (charset && charset != "UTF-8")
+ encodedParam = escape(convertFromUnicode(charset, param)).
+ replace(/[+@\/]+/g, encodeURIComponent);
+ else // Default charset is UTF-8
+ encodedParam = encodeURIComponent(param);
+
+ shortcutURL = shortcutURL.replace(/%s/g, encodedParam).replace(/%S/g, param);
+
+ if (/%s/i.test(escapedPostData)) // POST keyword
+ postData = getPostDataStream(escapedPostData, param, encodedParam,
+ "application/x-www-form-urlencoded");
+
+ // This URL came from a bookmark, so it's safe to let it inherit the current
+ // document's principal.
+ mayInheritPrincipal = true;
+
+ return { postData, url: shortcutURL, mayInheritPrincipal };
+ }
+
+ if (param) {
+ // This keyword doesn't take a parameter, but one was provided. Just return
+ // the original URL.
+ postData = null;
+
+ return { postData, url, mayInheritPrincipal };
+ }
+
+ // This URL came from a bookmark, so it's safe to let it inherit the current
+ // document's principal.
+ mayInheritPrincipal = true;
+
+ return { postData, url: shortcutURL, mayInheritPrincipal };
+ }).then(data => {
+ if (callback) {
+ callback(data);
+ }
+
+ return data;
+ });
+}
+
+function getPostDataStream(aStringData, aKeyword, aEncKeyword, aType) {
+ var dataStream = Cc["@mozilla.org/io/string-input-stream;1"].
+ createInstance(Ci.nsIStringInputStream);
+ aStringData = aStringData.replace(/%s/g, aEncKeyword).replace(/%S/g, aKeyword);
+ dataStream.data = aStringData;
+
+ var mimeStream = Cc["@mozilla.org/network/mime-input-stream;1"].
+ createInstance(Ci.nsIMIMEInputStream);
+ mimeStream.addHeader("Content-Type", aType);
+ mimeStream.addContentLength = true;
+ mimeStream.setData(dataStream);
+ return mimeStream.QueryInterface(Ci.nsIInputStream);
+}
+
+function getLoadContext() {
+ return window.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsILoadContext);
+}
+
+function readFromClipboard()
+{
+ var url;
+
+ try {
+ // Create transferable that will transfer the text.
+ var trans = Components.classes["@mozilla.org/widget/transferable;1"]
+ .createInstance(Components.interfaces.nsITransferable);
+ trans.init(getLoadContext());
+
+ trans.addDataFlavor("text/unicode");
+
+ // If available, use selection clipboard, otherwise global one
+ if (Services.clipboard.supportsSelectionClipboard())
+ Services.clipboard.getData(trans, Services.clipboard.kSelectionClipboard);
+ else
+ Services.clipboard.getData(trans, Services.clipboard.kGlobalClipboard);
+
+ var data = {};
+ var dataLen = {};
+ trans.getTransferData("text/unicode", data, dataLen);
+
+ if (data) {
+ data = data.value.QueryInterface(Components.interfaces.nsISupportsString);
+ url = data.data.substring(0, dataLen.value / 2);
+ }
+ } catch (ex) {
+ }
+
+ return url;
+}
+
+function BrowserViewSourceOfDocument(aArgsOrDocument)
+{
+ let args;
+
+ if (aArgsOrDocument instanceof Document) {
+ let doc = aArgsOrDocument;
+
+ let requestor = doc.defaultView
+ .QueryInterface(Ci.nsIInterfaceRequestor);
+ let browser = requestor.getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIDocShell)
+ .chromeEventHandler;
+ let outerWindowID = requestor.getInterface(Ci.nsIDOMWindowUtils)
+ .outerWindowID;
+ let URL = browser.currentURI.spec;
+ args = { browser, outerWindowID, URL };
+ } else {
+ args = aArgsOrDocument;
+ }
+
+ let viewInternal = () => {
+ top.gViewSourceUtils.viewSource(args);
+ }
+
+ // Check if external view source is enabled. If so, try it. If it fails,
+ // fallback to internal view source.
+ if (Services.prefs.getBoolPref("view_source.editor.external")) {
+ top.gViewSourceUtils
+ .openInExternalEditor(args, null, null, null, result => {
+ if (!result) {
+ viewInternal();
+ }
+ });
+ } else {
+ // Display using internal view source
+ viewInternal();
+ }
+}
+
+// doc - document to use for source, or null for this window's document
+// initialTab - name of the initial tab to display, or null for the first tab
+// imageElement - image to load in the Media Tab of the Page Info window; can be null/omitted
+function BrowserPageInfo(doc, initialTab, imageElement) {
+ var args = {doc: doc, initialTab: initialTab, imageElement: imageElement};
+ var windows = Services.wm.getEnumerator("Browser:page-info");
+
+ var documentURL = doc ? doc.location : window.content.document.location;
+
+ // Check for windows matching the url
+ while (windows.hasMoreElements()) {
+ var currentWindow = windows.getNext();
+ if (currentWindow.document.documentElement.getAttribute("relatedUrl") == documentURL) {
+ currentWindow.focus();
+ currentWindow.resetPageInfo(args);
+ return currentWindow;
+ }
+ }
+
+ // We didn't find a matching window, so open a new one.
+ return openDialog("chrome://browser/content/pageinfo/pageInfo.xul", "",
+ "chrome,toolbar,dialog=no,resizable", args);
+}
+
+function URLBarSetURI(aURI) {
+ var value = gBrowser.userTypedValue;
+ var valid = false;
+
+ if (value == null) {
+ let uri = aURI || gBrowser.currentURI;
+ // Strip off "wyciwyg://" and passwords for the location bar
+ try {
+ uri = Services.uriFixup.createExposableURI(uri);
+ } catch (e) {}
+
+ // Replace initial page URIs with an empty string
+ // only if there's no opener (bug 370555).
+ // Bug 863515 - Make content.opener checks work in electrolysis.
+ if (gInitialPages.indexOf(uri.spec) != -1)
+ value = !gMultiProcessBrowser && content.opener ? uri.spec : "";
+ else
+ value = losslessDecodeURI(uri);
+
+ valid = !isBlankPageURL(uri.spec);
+ }
+
+ let isDifferentValidValue = valid && value != gURLBar.value;
+ gURLBar.value = value;
+ gURLBar.valueIsTyped = !valid;
+ if (isDifferentValidValue) {
+ gURLBar.selectionStart = gURLBar.selectionEnd = 0;
+ }
+
+ SetPageProxyState(valid ? "valid" : "invalid");
+}
+
+function losslessDecodeURI(aURI) {
+ let scheme = aURI.scheme;
+ let decodeASCIIOnly = !(/(https|http|file|ftp)/i.test(scheme));
+
+ var value = aURI.spec;
+
+ // Try to decode as UTF-8 if there's no encoding sequence that we would break.
+ if (!/%25(?:3B|2F|3F|3A|40|26|3D|2B|24|2C|23)/i.test(value)) {
+ if (decodeASCIIOnly) {
+ // This only decodes ASCII characters (hex) 20-7e, except 25 (%).
+ // This avoids both cases stipulated below (%-related issues, and \r, \n
+ // and \t, which would be %0d, %0a and %09, respectively) as well as any
+ // non-US-ascii characters.
+ value = value.replace(/%(2[0-4]|2[6-9a-f]|[3-6][0-9a-f]|7[0-9a-e])/g, decodeURI);
+ } else {
+ try {
+ value = decodeURI(value)
+ // 1. decodeURI decodes %25 to %, which creates unintended
+ // encoding sequences. Re-encode it, unless it's part of
+ // a sequence that survived decodeURI, i.e. one for:
+ // ';', '/', '?', ':', '@', '&', '=', '+', '$', ',', '#'
+ // (RFC 3987 section 3.2)
+ // 2. Re-encode whitespace so that it doesn't get eaten away
+ // by the location bar (bug 410726).
+ .replace(/%(?!3B|2F|3F|3A|40|26|3D|2B|24|2C|23)|[\r\n\t]/ig,
+ encodeURIComponent);
+ } catch (e) {}
+ }
+ }
+
+ // Encode invisible characters (C0/C1 control characters, U+007F [DEL],
+ // U+00A0 [no-break space], line and paragraph separator,
+ // object replacement character) (bug 452979, bug 909264)
+ value = value.replace(/[\u0000-\u001f\u007f-\u00a0\u2028\u2029\ufffc]/g,
+ encodeURIComponent);
+
+ // Encode default ignorable characters (bug 546013)
+ // except ZWNJ (U+200C) and ZWJ (U+200D) (bug 582186).
+ // This includes all bidirectional formatting characters.
+ // (RFC 3987 sections 3.2 and 4.1 paragraph 6)
+ value = value.replace(/[\u00ad\u034f\u115f-\u1160\u17b4-\u17b5\u180b-\u180d\u200b\u200e-\u200f\u202a-\u202e\u2060-\u206f\u3164\ufe00-\ufe0f\ufeff\uffa0\ufff0-\ufff8]|\ud834[\udd73-\udd7a]|[\udb40-\udb43][\udc00-\udfff]/g,
+ encodeURIComponent);
+ return value;
+}
+
+function UpdateUrlbarSearchSplitterState()
+{
+ var splitter = document.getElementById("urlbar-search-splitter");
+ var urlbar = document.getElementById("urlbar-container");
+ var searchbar = document.getElementById("search-container");
+ var stop = document.getElementById("stop-button");
+
+ var ibefore = null;
+ if (urlbar && searchbar) {
+ if (urlbar.nextSibling == searchbar ||
+ urlbar.getAttribute("combined") &&
+ stop && stop.nextSibling == searchbar)
+ ibefore = searchbar;
+ else if (searchbar.nextSibling == urlbar)
+ ibefore = urlbar;
+ }
+
+ if (ibefore) {
+ if (!splitter) {
+ splitter = document.createElement("splitter");
+ splitter.id = "urlbar-search-splitter";
+ splitter.setAttribute("resizebefore", "flex");
+ splitter.setAttribute("resizeafter", "flex");
+ splitter.setAttribute("skipintoolbarset", "true");
+ splitter.className = "chromeclass-toolbar-additional";
+ }
+ urlbar.parentNode.insertBefore(splitter, ibefore);
+ } else if (splitter)
+ splitter.parentNode.removeChild(splitter);
+}
+
+function setUrlAndSearchBarWidthForConditionalForwardButton() {
+ // Workaround for bug 694084: Showing/hiding the conditional forward button resizes
+ // the search bar when the url/search bar splitter hasn't been used.
+ var urlbarContainer = document.getElementById("urlbar-container");
+ var searchbarContainer = document.getElementById("search-container");
+ if (!urlbarContainer ||
+ !searchbarContainer ||
+ urlbarContainer.hasAttribute("width") ||
+ searchbarContainer.hasAttribute("width") ||
+ urlbarContainer.parentNode != searchbarContainer.parentNode)
+ return;
+ urlbarContainer.style.width = searchbarContainer.style.width = "";
+ var urlbarWidth = urlbarContainer.clientWidth;
+ var searchbarWidth = searchbarContainer.clientWidth;
+ urlbarContainer.style.width = urlbarWidth + "px";
+ searchbarContainer.style.width = searchbarWidth + "px";
+}
+
+function UpdatePageProxyState()
+{
+ if (gURLBar && gURLBar.value != gLastValidURLStr)
+ SetPageProxyState("invalid");
+}
+
+function SetPageProxyState(aState)
+{
+ BookmarkingUI.onPageProxyStateChanged(aState);
+
+ if (!gURLBar)
+ return;
+
+ if (!gProxyFavIcon)
+ gProxyFavIcon = document.getElementById("page-proxy-favicon");
+
+ gURLBar.setAttribute("pageproxystate", aState);
+ gProxyFavIcon.setAttribute("pageproxystate", aState);
+
+ // the page proxy state is set to valid via OnLocationChange, which
+ // gets called when we switch tabs.
+ if (aState == "valid") {
+ gLastValidURLStr = gURLBar.value;
+ gURLBar.addEventListener("input", UpdatePageProxyState, false);
+ PageProxySetIcon(gBrowser.getIcon());
+ } else if (aState == "invalid") {
+ gURLBar.removeEventListener("input", UpdatePageProxyState, false);
+ PageProxyClearIcon();
+ }
+}
+
+function PageProxySetIcon (aURL)
+{
+ if (!gProxyFavIcon)
+ return;
+
+ if (gBrowser.selectedBrowser.contentDocument instanceof ImageDocument) {
+ // PageProxyClearIcon();
+ gProxyFavIcon.setAttribute("src", "chrome://browser/skin/imagedocument.png");
+ return;
+ }
+
+ if (!aURL)
+ PageProxyClearIcon();
+ else if (gProxyFavIcon.getAttribute("src") != aURL)
+ gProxyFavIcon.setAttribute("src", aURL);
+}
+
+function PageProxyClearIcon ()
+{
+ gProxyFavIcon.removeAttribute("src");
+}
+
+
+function PageProxyClickHandler(aEvent)
+{
+ if (aEvent.button == 1 && gPrefService.getBoolPref("middlemouse.paste"))
+ middleMousePaste(aEvent);
+}
+
+/**
+ * Handle load of some pages (about:*) so that we can make modifications
+ * to the DOM for unprivileged pages.
+ */
+function BrowserOnAboutPageLoad(doc) {
+
+ /* === about:home === */
+
+ if (doc.documentURI.toLowerCase() == "about:home") {
+ if (!PrivateBrowsingUtils.isWindowPrivate(window)) {
+ let wrapper = {};
+ Cu.import("resource:///modules/sessionstore/SessionStore.jsm", wrapper);
+ let ss = wrapper.SessionStore;
+ ss.promiseInitialized.then(function() {
+ if (ss.canRestoreLastSession) {
+ doc.getElementById("launcher").setAttribute("session", "true");
+ }
+ }).then(null, function onError(x) {
+ Cu.reportError("Error in SessionStore init while processing 'about:home': " + x);
+ });
+ }
+
+ // Inject search engine and snippets URL.
+ let docElt = doc.documentElement;
+ if (AboutHomeUtils.showKnowYourRights) {
+ docElt.setAttribute("showKnowYourRights", "true");
+ // Set pref to indicate we've shown the notification.
+ let currentVersion = Services.prefs.getIntPref("browser.rights.version");
+ Services.prefs.setBoolPref("browser.rights." + currentVersion + ".shown", true);
+ }
+
+ function updateSearchEngine() {
+ let engine = AboutHomeUtils.defaultSearchEngine;
+ docElt.setAttribute("searchEngineName", engine.name);
+ docElt.setAttribute("searchEnginePostData", engine.postDataString || "");
+ docElt.setAttribute("searchEngineURL", engine.searchURL);
+ }
+ Services.search.init(updateSearchEngine);
+
+ // Listen for the event that's triggered when the user changes search engine.
+ // At this point we simply reload about:home to reflect the change.
+ Services.obs.addObserver(updateSearchEngine, "browser-search-engine-modified", false);
+
+ // Remove the observer when the page is reloaded or closed.
+ doc.defaultView.addEventListener("pagehide", function removeObserver() {
+ doc.defaultView.removeEventListener("pagehide", removeObserver);
+ Services.obs.removeObserver(updateSearchEngine, "browser-search-engine-modified");
+ }, false);
+ }
+
+ /* === about:newtab === */
+
+ if (doc.documentURI.toLowerCase() == "about:newtab") {
+
+ let docElt = doc.documentElement;
+
+ function updateSearchEngine() {
+ let engine = AboutHomeUtils.defaultSearchEngine;
+ docElt.setAttribute("searchEngineName", engine.name);
+ docElt.setAttribute("searchEnginePostData", engine.postDataString || "");
+ docElt.setAttribute("searchEngineURL", engine.searchURL);
+ }
+ Services.search.init(updateSearchEngine);
+
+ // Listen for the event that's triggered when the user changes search engine.
+ // At this point we simply reload about:newtab to reflect the change.
+ Services.obs.addObserver(updateSearchEngine, "browser-search-engine-modified", false);
+
+ // Remove the observer when the page is reloaded or closed.
+ doc.defaultView.addEventListener("pagehide", function removeObserver() {
+ doc.defaultView.removeEventListener("pagehide", removeObserver);
+ Services.obs.removeObserver(updateSearchEngine, "browser-search-engine-modified");
+ }, false);
+ }
+
+}
+
+/**
+ * Handle command events bubbling up from error page content
+ */
+var BrowserOnClick = {
+ handleEvent: function BrowserOnClick_handleEvent(aEvent) {
+ if (!aEvent.isTrusted || // Don't trust synthetic events
+ aEvent.button == 2 || aEvent.target.localName != "button") {
+ return;
+ }
+
+ let originalTarget = aEvent.originalTarget;
+ let ownerDoc = originalTarget.ownerDocument;
+
+ // If the event came from an ssl error page, it is probably either the "Add
+ // Exception…" or "Get me out of here!" button
+ if (ownerDoc.documentURI.startsWith("about:certerror")) {
+ this.onAboutCertError(originalTarget, ownerDoc);
+ }
+ else if (ownerDoc.documentURI.startsWith("about:neterror")) {
+ this.onAboutNetError(originalTarget, ownerDoc);
+ }
+ else if (ownerDoc.documentURI.toLowerCase() == "about:home") {
+ this.onAboutHome(originalTarget, ownerDoc);
+ }
+ },
+
+ onAboutCertError: function BrowserOnClick_onAboutCertError(aTargetElm, aOwnerDoc) {
+ let elmId = aTargetElm.getAttribute("id");
+ let isTopFrame = (aOwnerDoc.defaultView.parent === aOwnerDoc.defaultView);
+
+ switch (elmId) {
+ case "exceptionDialogButton":
+ let params = { exceptionAdded : false };
+
+ try {
+ switch (Services.prefs.getIntPref("browser.ssl_override_behavior")) {
+ case 2 : // Pre-fetch & pre-populate
+ params.prefetchCert = true;
+ case 1 : // Pre-populate
+ params.location = aOwnerDoc.location.href;
+ }
+ } catch (e) {
+ Components.utils.reportError("Couldn't get ssl_override pref: " + e);
+ }
+
+ window.openDialog('chrome://pippki/content/exceptionDialog.xul',
+ '','chrome,centerscreen,modal', params);
+
+ // If the user added the exception cert, attempt to reload the page
+ if (params.exceptionAdded) {
+ aOwnerDoc.location.reload();
+ }
+ break;
+
+ case "getMeOutOfHereButton":
+ getMeOutOfHere();
+ break;
+
+ case "technicalContent":
+ break;
+
+ case "expertContent":
+ break;
+
+ }
+ },
+
+ onAboutNetError: function BrowserOnClick_onAboutNetError(aTargetElm, aOwnerDoc) {
+ let elmId = aTargetElm.getAttribute("id");
+ if (elmId != "errorTryAgain" || !/e=netOffline/.test(aOwnerDoc.documentURI))
+ return;
+ Services.io.offline = false;
+ },
+
+ onAboutHome: function BrowserOnClick_onAboutHome(aTargetElm, aOwnerDoc) {
+ let elmId = aTargetElm.getAttribute("id");
+
+ switch (elmId) {
+ case "restorePreviousSession":
+ let ss = Cc["@mozilla.org/browser/sessionstore;1"].
+ getService(Ci.nsISessionStore);
+ if (ss.canRestoreLastSession) {
+ ss.restoreLastSession();
+ }
+ aOwnerDoc.getElementById("launcher").removeAttribute("session");
+ break;
+
+ case "downloads":
+ BrowserDownloadsUI();
+ break;
+
+ case "bookmarks":
+ PlacesCommandHook.showPlacesOrganizer("AllBookmarks");
+ break;
+
+ case "history":
+ PlacesCommandHook.showPlacesOrganizer("History");
+ break;
+
+ case "addons":
+ BrowserOpenAddonsMgr();
+ break;
+
+ case "sync":
+ openPreferences("paneSync");
+ break;
+
+ case "settings":
+ openPreferences();
+ break;
+ }
+ },
+};
+
+/**
+ * Re-direct the browser to a known-safe page. This function is
+ * used when, for example, the user browses to a known malware page
+ * and is presented with about:blocked. The "Get me out of here!"
+ * button should take the user to the default start page so that even
+ * when their own homepage is infected, we can get them somewhere safe.
+ */
+function getMeOutOfHere() {
+ try {
+ let toBlank = Services.prefs.getBoolPref("browser.escape_to_blank");
+ if (toBlank) {
+ content.location = "about:logopage";
+ return;
+ }
+ } catch(e) {
+ Components.utils.reportError("Couldn't get escape pref: " + e);
+ }
+ // Get the start page from the *default* pref branch, not the user's
+ var prefs = Services.prefs.getDefaultBranch(null);
+ var url = BROWSER_NEW_TAB_URL;
+ try {
+ url = prefs.getComplexValue("browser.startup.homepage",
+ Ci.nsIPrefLocalizedString).data;
+ // If url is a pipe-delimited set of pages, just take the first one.
+ if (url.includes("|"))
+ url = url.split("|")[0];
+ } catch(e) {
+ Components.utils.reportError("Couldn't get homepage pref: " + e);
+ }
+ content.location = url;
+}
+
+function BrowserFullScreen()
+{
+ window.fullScreen = !window.fullScreen;
+}
+
+function onFullScreen() {
+ FullScreen.toggle();
+}
+
+function onMozEnteredDomFullscreen(event) {
+ FullScreen.enterDomFullscreen(event);
+}
+
+function getWebNavigation()
+{
+ return gBrowser.webNavigation;
+}
+
+function BrowserReloadWithFlags(reloadFlags) {
+
+ // Reset DOS mitigation for auth prompts when user initiates a reload.
+ let browser = gBrowser.selectedBrowser;
+ delete browser.authPromptCounter;
+
+ /* First, we'll try to use the session history object to reload so
+ * that framesets are handled properly. If we're in a special
+ * window (such as view-source) that has no session history, fall
+ * back on using the web navigation's reload method.
+ */
+
+ var webNav = gBrowser.webNavigation;
+ try {
+ var sh = webNav.sessionHistory;
+ if (sh)
+ webNav = sh.QueryInterface(nsIWebNavigation);
+ } catch (e) {
+ }
+
+ try {
+ webNav.reload(reloadFlags);
+ } catch (e) {
+ }
+}
+
+var PrintPreviewListener = {
+ _printPreviewTab: null,
+ _tabBeforePrintPreview: null,
+
+ getPrintPreviewBrowser: function () {
+ if (!this._printPreviewTab) {
+ this._tabBeforePrintPreview = gBrowser.selectedTab;
+ this._printPreviewTab = gBrowser.loadOneTab("about:blank",
+ { inBackground: false });
+ gBrowser.selectedTab = this._printPreviewTab;
+ }
+ return gBrowser.getBrowserForTab(this._printPreviewTab);
+ },
+ getSourceBrowser: function () {
+ return this._tabBeforePrintPreview ?
+ this._tabBeforePrintPreview.linkedBrowser : gBrowser.selectedBrowser;
+ },
+ getNavToolbox: function () {
+ return gNavToolbox;
+ },
+ onEnter: function () {
+ // We might have accidentally switched tabs since the user invoked print
+ // preview
+ if (gBrowser.selectedTab != this._printPreviewTab) {
+ gBrowser.selectedTab = this._printPreviewTab;
+ }
+ gInPrintPreviewMode = true;
+ this._toggleAffectedChrome();
+ },
+ onExit: function () {
+ gBrowser.selectedTab = this._tabBeforePrintPreview;
+ this._tabBeforePrintPreview = null;
+ gInPrintPreviewMode = false;
+ this._toggleAffectedChrome();
+ gBrowser.removeTab(this._printPreviewTab);
+ this._printPreviewTab = null;
+ },
+ _toggleAffectedChrome: function () {
+ gNavToolbox.collapsed = gInPrintPreviewMode;
+
+ if (gInPrintPreviewMode)
+ this._hideChrome();
+ else
+ this._showChrome();
+
+ if (this._chromeState.sidebarOpen)
+ toggleSidebar(this._sidebarCommand);
+
+#ifdef MENUBAR_CAN_AUTOHIDE
+ updateAppButtonDisplay();
+#endif
+ },
+ _hideChrome: function () {
+ this._chromeState = {};
+
+ var sidebar = document.getElementById("sidebar-box");
+ this._chromeState.sidebarOpen = !sidebar.hidden;
+ this._sidebarCommand = sidebar.getAttribute("sidebarcommand");
+
+ var notificationBox = gBrowser.getNotificationBox();
+ this._chromeState.notificationsOpen = !notificationBox.notificationsHidden;
+ notificationBox.notificationsHidden = true;
+
+ document.getElementById("sidebar").setAttribute("src", "about:blank");
+ var addonBar = document.getElementById("addon-bar");
+ this._chromeState.addonBarOpen = !addonBar.collapsed;
+ addonBar.collapsed = true;
+ gBrowser.updateWindowResizers();
+
+ this._chromeState.findOpen = gFindBarInitialized && !gFindBar.hidden;
+ if (gFindBarInitialized)
+ gFindBar.close();
+
+ var globalNotificationBox = document.getElementById("global-notificationbox");
+ this._chromeState.globalNotificationsOpen = !globalNotificationBox.notificationsHidden;
+ globalNotificationBox.notificationsHidden = true;
+
+ this._chromeState.syncNotificationsOpen = false;
+ var syncNotifications = document.getElementById("sync-notifications");
+ if (syncNotifications) {
+ this._chromeState.syncNotificationsOpen = !syncNotifications.notificationsHidden;
+ syncNotifications.notificationsHidden = true;
+ }
+ },
+ _showChrome: function () {
+ if (this._chromeState.notificationsOpen)
+ gBrowser.getNotificationBox().notificationsHidden = false;
+
+ if (this._chromeState.addonBarOpen) {
+ document.getElementById("addon-bar").collapsed = false;
+ gBrowser.updateWindowResizers();
+ }
+
+ if (this._chromeState.findOpen)
+ gFindBar.open();
+
+ if (this._chromeState.globalNotificationsOpen)
+ document.getElementById("global-notificationbox").notificationsHidden = false;
+
+ if (this._chromeState.syncNotificationsOpen)
+ document.getElementById("sync-notifications").notificationsHidden = false;
+ }
+}
+
+function getMarkupDocumentViewer()
+{
+ return gBrowser.markupDocumentViewer;
+}
+
+// This function is obsolete. Newer code should use <tooltip page="true"/> instead.
+function FillInHTMLTooltip(tipElement)
+{
+ document.getElementById("aHTMLTooltip").fillInPageTooltip(tipElement);
+}
+
+var browserDragAndDrop = {
+ canDropLink: function (aEvent) Services.droppedLinkHandler.canDropLink(aEvent, true),
+
+ dragOver: function (aEvent)
+ {
+ if (this.canDropLink(aEvent)) {
+ aEvent.preventDefault();
+ }
+ },
+
+ dropLinks: function (aEvent, aDisallowInherit) {
+ return Services.droppedLinkHandler.dropLinks(aEvent, aDisallowInherit);
+ }
+};
+
+var homeButtonObserver = {
+ onDrop: function (aEvent)
+ {
+ // disallow setting home pages that inherit the principal
+ let links = browserDragAndDrop.dropLinks(aEvent, true);
+ if (links.length) {
+ setTimeout(openHomeDialog, 0, links.map(link => link.url).join("|"));
+ }
+ },
+
+ onDragOver: function (aEvent)
+ {
+ browserDragAndDrop.dragOver(aEvent);
+ aEvent.dropEffect = "link";
+ },
+ onDragExit: function (aEvent)
+ {
+ }
+}
+
+function openHomeDialog(aURL)
+{
+ var promptTitle = gNavigatorBundle.getString("droponhometitle");
+ var promptMsg;
+ if (aURL.includes("|")) {
+ promptMsg = gNavigatorBundle.getString("droponhomemsgMultiple");
+ } else {
+ promptMsg = gNavigatorBundle.getString("droponhomemsg");
+ }
+
+ var pressedVal = Services.prompt.confirmEx(window, promptTitle, promptMsg,
+ Services.prompt.STD_YES_NO_BUTTONS,
+ null, null, null, null, {value:0});
+
+ if (pressedVal == 0) {
+ try {
+ var homepageStr = Components.classes["@mozilla.org/supports-string;1"]
+ .createInstance(Components.interfaces.nsISupportsString);
+ homepageStr.data = aURL;
+ gPrefService.setComplexValue("browser.startup.homepage",
+ Components.interfaces.nsISupportsString, homepageStr);
+ } catch (ex) {
+ dump("Failed to set the home page.\n"+ex+"\n");
+ }
+ }
+}
+
+var bookmarksButtonObserver = {
+ onDrop: function (aEvent)
+ {
+ let name = { };
+ let url = browserDragAndDrop.drop(aEvent, name);
+ try {
+ PlacesUIUtils.showBookmarkDialog({ action: "add"
+ , type: "bookmark"
+ , uri: makeURI(url)
+ , title: name
+ , hiddenRows: [ "description"
+ , "location"
+ , "loadInSidebar"
+ , "keyword" ]
+ }, window);
+ } catch(ex) { }
+ },
+
+ onDragOver: function (aEvent)
+ {
+ browserDragAndDrop.dragOver(aEvent);
+ aEvent.dropEffect = "link";
+ },
+
+ onDragExit: function (aEvent)
+ {
+ }
+}
+
+var newTabButtonObserver = {
+ onDragOver: function (aEvent)
+ {
+ browserDragAndDrop.dragOver(aEvent);
+ },
+
+ onDragExit: function (aEvent)
+ {
+ },
+
+ onDrop: function (aEvent)
+ {
+ let links = browserDragAndDrop.dropLinks(aEvent);
+ Task.spawn(function*() {
+ for (let link of links) {
+ let data = yield getShortcutOrURIAndPostData(link.url);
+ if (data.url) {
+ // allow third-party services to fixup this URL
+ openNewTabWith(data.url, null, data.postData, aEvent, true);
+ }
+ }
+ });
+ }
+}
+
+var newWindowButtonObserver = {
+ onDragOver: function (aEvent)
+ {
+ browserDragAndDrop.dragOver(aEvent);
+ },
+ onDragExit: function (aEvent)
+ {
+ },
+ onDrop: function (aEvent)
+ {
+ let links = browserDragAndDrop.dropLinks(aEvent);
+ Task.spawn(function*() {
+ for (let link of links) {
+ let data = yield getShortcutOrURIAndPostData(link.url);
+ if (data.url) {
+ // allow third-party services to fixup this URL
+ openNewWindowWith(data.url, null, data.postData, true);
+ }
+ }
+ });
+ }
+}
+
+const DOMLinkHandler = {
+ handleEvent: function (event) {
+ switch (event.type) {
+ case "DOMLinkAdded":
+ this.onLinkAdded(event);
+ break;
+ }
+ },
+ getLinkIconURI: function(aLink) {
+ let targetDoc = aLink.ownerDocument;
+ var uri = makeURI(aLink.href, targetDoc.characterSet);
+
+ // Verify that the load of this icon is legal.
+ // Some error or special pages can load their favicon.
+ // To be on the safe side, only allow chrome:// favicons.
+ var isAllowedPage = [
+ /^about:neterror\?/,
+ /^about:blocked\?/,
+ /^about:certerror\?/,
+ /^about:home$/,
+ ].some(function (re) re.test(targetDoc.documentURI));
+
+ if (!isAllowedPage || !uri.schemeIs("chrome")) {
+ var ssm = Services.scriptSecurityManager;
+ try {
+ ssm.checkLoadURIWithPrincipal(targetDoc.nodePrincipal, uri,
+ Ci.nsIScriptSecurityManager.DISALLOW_SCRIPT);
+ } catch(e) {
+ return null;
+ }
+ }
+
+ try {
+ var contentPolicy = Cc["@mozilla.org/layout/content-policy;1"].
+ getService(Ci.nsIContentPolicy);
+ } catch(e) {
+ return null; // Refuse to load if we can't do a security check.
+ }
+
+ // Security says okay, now ask content policy
+ if (contentPolicy.shouldLoad(Ci.nsIContentPolicy.TYPE_IMAGE,
+ uri, targetDoc.documentURIObject,
+ aLink, aLink.type, null)
+ != Ci.nsIContentPolicy.ACCEPT)
+ return null;
+
+ try {
+ uri.userPass = "";
+ } catch(e) {
+ // some URIs are immutable
+ }
+ return uri;
+ },
+ onLinkAdded: function (event) {
+ var link = event.originalTarget;
+ var rel = link.rel && link.rel.toLowerCase();
+ if (!link || !link.ownerDocument || !rel || !link.href)
+ return;
+
+ var feedAdded = false;
+ var iconAdded = false;
+ var searchAdded = false;
+ var rels = {};
+ for (let relString of rel.split(/\s+/))
+ rels[relString] = true;
+
+ for (let relVal in rels) {
+ switch (relVal) {
+ case "feed":
+ case "alternate":
+ if (!feedAdded) {
+ if (!rels.feed && rels.alternate && rels.stylesheet)
+ break;
+
+ if (isValidFeed(link, link.ownerDocument.nodePrincipal, "feed" in rels)) {
+ FeedHandler.addFeed(link, link.ownerDocument);
+ feedAdded = true;
+ }
+ }
+ break;
+ case "icon":
+ if (!iconAdded) {
+ if (!gPrefService.getBoolPref("browser.chrome.site_icons"))
+ break;
+
+ var uri = this.getLinkIconURI(link);
+ if (!uri)
+ break;
+
+ if (gBrowser.isFailedIcon(uri))
+ break;
+
+ var browserIndex = gBrowser.getBrowserIndexForDocument(link.ownerDocument);
+ // no browser? no favicon.
+ if (browserIndex == -1)
+ break;
+
+ let tab = gBrowser.tabs[browserIndex];
+ gBrowser.setIcon(tab, uri.spec, link.ownerDocument.nodePrincipal);
+ iconAdded = true;
+ }
+ break;
+ case "search":
+ if (!searchAdded) {
+ var type = link.type && link.type.toLowerCase();
+ type = type.replace(/^\s+|\s*(?:;.*)?$/g, "");
+
+ if (type == "application/opensearchdescription+xml" && link.title &&
+ /^(?:https?|ftp):/i.test(link.href) &&
+ !PrivateBrowsingUtils.isWindowPrivate(window)) {
+ var engine = { title: link.title, href: link.href };
+ Services.search.init(function () {
+ BrowserSearch.addEngine(engine, link.ownerDocument);
+ });
+ searchAdded = true;
+ }
+ }
+ break;
+ }
+ }
+ }
+}
+
+const BrowserSearch = {
+ addEngine: function(engine, targetDoc) {
+ if (!this.searchBar)
+ return;
+
+ var browser = gBrowser.getBrowserForDocument(targetDoc);
+ // ignore search engines from subframes (see bug 479408)
+ if (!browser)
+ return;
+
+ // Check to see whether we've already added an engine with this title
+ if (browser.engines) {
+ if (browser.engines.some(function (e) e.title == engine.title))
+ return;
+ }
+
+ // Append the URI and an appropriate title to the browser data.
+ // Use documentURIObject in the check for shouldLoadFavIcon so that we
+ // do the right thing with about:-style error pages. Bug 453442
+ var iconURL = null;
+ if (gBrowser.shouldLoadFavIcon(targetDoc.documentURIObject))
+ iconURL = targetDoc.documentURIObject.prePath + "/favicon.ico";
+
+ var hidden = false;
+ // If this engine (identified by title) is already in the list, add it
+ // to the list of hidden engines rather than to the main list.
+ // XXX This will need to be changed when engines are identified by URL;
+ // see bug 335102.
+ if (Services.search.getEngineByName(engine.title))
+ hidden = true;
+
+ var engines = (hidden ? browser.hiddenEngines : browser.engines) || [];
+
+ engines.push({ uri: engine.href,
+ title: engine.title,
+ icon: iconURL });
+
+ if (hidden)
+ browser.hiddenEngines = engines;
+ else
+ browser.engines = engines;
+ },
+
+ /**
+ * Gives focus to the search bar, if it is present on the toolbar, or loads
+ * the default engine's search form otherwise. For Mac, opens a new window
+ * or focuses an existing window, if necessary.
+ */
+ webSearch: function BrowserSearch_webSearch() {
+#ifdef XP_MACOSX
+ if (window.location.href != getBrowserURL()) {
+ var win = getTopWin();
+ if (win) {
+ // If there's an open browser window, it should handle this command
+ win.focus();
+ win.BrowserSearch.webSearch();
+ } else {
+ // If there are no open browser windows, open a new one
+ var observer = function observer(subject, topic, data) {
+ if (subject == win) {
+ BrowserSearch.webSearch();
+ Services.obs.removeObserver(observer, "browser-delayed-startup-finished");
+ }
+ }
+ win = window.openDialog(getBrowserURL(), "_blank",
+ "chrome,all,dialog=no", "about:blank");
+ Services.obs.addObserver(observer, "browser-delayed-startup-finished", false);
+ }
+ return;
+ }
+#endif
+ var searchBar = this.searchBar;
+ if (searchBar && window.fullScreen)
+ FullScreen.showNavToolbox();
+ if (searchBar)
+ searchBar.select();
+ if (!searchBar || document.activeElement != searchBar.textbox.inputField)
+ openUILinkIn(Services.search.defaultEngine.searchForm, "current");
+ },
+
+ /**
+ * Loads a search results page, given a set of search terms. Uses the current
+ * engine if the search bar is visible, or the default engine otherwise.
+ *
+ * @param searchText
+ * The search terms to use for the search.
+ *
+ * @param useNewTab
+ * Boolean indicating whether or not the search should load in a new
+ * tab.
+ *
+ * @param purpose [optional]
+ * A string meant to indicate the context of the search request. This
+ * allows the search service to provide a different nsISearchSubmission
+ * depending on e.g. where the search is triggered in the UI.
+ *
+ * @return string Name of the search engine used to perform a search or null
+ * if a search was not performed.
+ */
+ loadSearch: function BrowserSearch_search(searchText, useNewTab, purpose) {
+ var engine;
+
+ // If the search bar is visible, use the current engine, otherwise, fall
+ // back to the default engine.
+ if (isElementVisible(this.searchBar))
+ engine = Services.search.currentEngine;
+ else
+ engine = Services.search.defaultEngine;
+
+ var submission = engine.getSubmission(searchText, null, purpose); // HTML response
+
+ // getSubmission can return null if the engine doesn't have a URL
+ // with a text/html response type. This is unlikely (since
+ // SearchService._addEngineToStore() should fail for such an engine),
+ // but let's be on the safe side.
+ if (!submission) {
+ return null;
+ }
+
+ let inBackground = Services.prefs.getBoolPref("browser.search.context.loadInBackground");
+ openLinkIn(submission.uri.spec,
+ useNewTab ? "tab" : "current",
+ { postData: submission.postData,
+ inBackground: inBackground,
+ relatedToCurrent: true });
+
+ return engine.name;
+ },
+
+ /**
+ * Perform a search initiated from the context menu.
+ *
+ * This should only be called from the context menu. See
+ * BrowserSearch.loadSearch for the preferred API.
+ */
+ loadSearchFromContext: function (terms) {
+ let engine = BrowserSearch.loadSearch(terms, true, "contextmenu");
+ },
+
+ /**
+ * Returns the search bar element if it is present in the toolbar, null otherwise.
+ */
+ get searchBar() {
+ return document.getElementById("searchbar");
+ },
+
+ loadAddEngines: function BrowserSearch_loadAddEngines() {
+ var newWindowPref = gPrefService.getIntPref("browser.link.open_newwindow");
+ var where = newWindowPref == 3 ? "tab" : "window";
+ var searchEnginesURL = formatURL("browser.search.searchEnginesURL", true);
+ openUILinkIn(searchEnginesURL, where);
+ },
+};
+
+XPCOMUtils.defineConstant(this, "BrowserSearch", BrowserSearch);
+
+function FillHistoryMenu(aParent) {
+ // Lazily add the hover listeners on first showing and never remove them
+ if (!aParent.hasStatusListener) {
+ // Show history item's uri in the status bar when hovering, and clear on exit
+ aParent.addEventListener("DOMMenuItemActive", function(aEvent) {
+ // Only the current page should have the checked attribute, so skip it
+ if (!aEvent.target.hasAttribute("checked"))
+ XULBrowserWindow.setOverLink(aEvent.target.getAttribute("uri"));
+ }, false);
+ aParent.addEventListener("DOMMenuItemInactive", function() {
+ XULBrowserWindow.setOverLink("");
+ }, false);
+
+ aParent.hasStatusListener = true;
+ }
+
+ // Remove old entries if any
+ var children = aParent.childNodes;
+ for (var i = children.length - 1; i >= 0; --i) {
+ if (children[i].hasAttribute("index"))
+ aParent.removeChild(children[i]);
+ }
+
+ var webNav = gBrowser.webNavigation;
+ var sessionHistory = webNav.sessionHistory;
+
+ var count = sessionHistory.count;
+ if (count <= 1) // don't display the popup for a single item
+ return false;
+
+ const MAX_HISTORY_MENU_ITEMS = 15;
+ var index = sessionHistory.index;
+ var half_length = Math.floor(MAX_HISTORY_MENU_ITEMS / 2);
+ var start = Math.max(index - half_length, 0);
+ var end = Math.min(start == 0 ? MAX_HISTORY_MENU_ITEMS : index + half_length + 1, count);
+ if (end == count)
+ start = Math.max(count - MAX_HISTORY_MENU_ITEMS, 0);
+
+ var tooltipBack = gNavigatorBundle.getString("tabHistory.goBack");
+ var tooltipCurrent = gNavigatorBundle.getString("tabHistory.current");
+ var tooltipForward = gNavigatorBundle.getString("tabHistory.goForward");
+
+ for (var j = end - 1; j >= start; j--) {
+ let item = document.createElement("menuitem");
+ let entry = sessionHistory.getEntryAtIndex(j, false);
+ let uri = entry.URI.spec;
+
+ item.setAttribute("uri", uri);
+ item.setAttribute("label", entry.title || uri);
+ item.setAttribute("index", j);
+
+ if (j != index) {
+ PlacesUtils.favicons.getFaviconURLForPage(entry.URI, function (aURI) {
+ if (aURI) {
+ let iconURL = PlacesUtils.favicons.getFaviconLinkForIcon(aURI).spec;
+ item.style.listStyleImage = "url(" + iconURL + ")";
+ }
+ });
+ }
+
+ if (j < index) {
+ item.className = "unified-nav-back menuitem-iconic menuitem-with-favicon";
+ item.setAttribute("tooltiptext", tooltipBack);
+ } else if (j == index) {
+ item.setAttribute("type", "radio");
+ item.setAttribute("checked", "true");
+ item.className = "unified-nav-current";
+ item.setAttribute("tooltiptext", tooltipCurrent);
+ } else {
+ item.className = "unified-nav-forward menuitem-iconic menuitem-with-favicon";
+ item.setAttribute("tooltiptext", tooltipForward);
+ }
+
+ aParent.appendChild(item);
+ }
+ return true;
+}
+
+function addToUrlbarHistory(aUrlToAdd) {
+ if (!PrivateBrowsingUtils.isWindowPrivate(window) &&
+ aUrlToAdd &&
+ !aUrlToAdd.includes(" ") &&
+ !/[\x00-\x1F]/.test(aUrlToAdd))
+ PlacesUIUtils.markPageAsTyped(aUrlToAdd);
+}
+
+function toJavaScriptConsole()
+{
+ toOpenWindowByType("global:console", "chrome://global/content/console.xul");
+}
+
+function BrowserDownloadsUI()
+{
+ if (PrivateBrowsingUtils.isWindowPrivate(window)) {
+ openUILinkIn("about:downloads", "tab");
+ } else {
+ PlacesCommandHook.showPlacesOrganizer("Downloads");
+ }
+}
+
+function toOpenWindowByType(inType, uri, features)
+{
+ var topWindow = Services.wm.getMostRecentWindow(inType);
+
+ if (topWindow)
+ topWindow.focus();
+ else if (features)
+ window.open(uri, "_blank", features);
+ else
+ window.open(uri, "_blank", "chrome,extrachrome,menubar,resizable,scrollbars,status,toolbar");
+}
+
+function OpenBrowserWindow(options)
+{
+ function newDocumentShown(doc, topic, data) {
+ if (topic == "document-shown" &&
+ doc != document &&
+ doc.defaultView == win) {
+ Services.obs.removeObserver(newDocumentShown, "document-shown");
+ }
+ };
+ Services.obs.addObserver(newDocumentShown, "document-shown", false);
+
+ var charsetArg = new String();
+ var handler = Components.classes["@mozilla.org/browser/clh;1"]
+ .getService(Components.interfaces.nsIBrowserHandler);
+ var defaultArgs = handler.defaultArgs;
+ var wintype = document.documentElement.getAttribute('windowtype');
+
+ var extraFeatures = "";
+ if (options && options.private) {
+ extraFeatures = ",private";
+ if (!PrivateBrowsingUtils.permanentPrivateBrowsing) {
+ // Force the new window to load about:privatebrowsing instead of the default home page
+ defaultArgs = "about:privatebrowsing";
+ }
+ } else {
+ extraFeatures = ",non-private";
+ }
+
+ // if and only if the current window is a browser window and it has a document with a character
+ // set, then extract the current charset menu setting from the current document and use it to
+ // initialize the new browser window...
+ var win;
+ if (window && (wintype == "navigator:browser") && window.content && window.content.document)
+ {
+ var DocCharset = window.content.document.characterSet;
+ charsetArg = "charset="+DocCharset;
+
+ //we should "inherit" the charset menu setting in a new window
+ win = window.openDialog("chrome://browser/content/", "_blank", "chrome,all,dialog=no" + extraFeatures, defaultArgs, charsetArg);
+ }
+ else // forget about the charset information.
+ {
+ win = window.openDialog("chrome://browser/content/", "_blank", "chrome,all,dialog=no" + extraFeatures, defaultArgs);
+ }
+
+ return win;
+}
+
+var gCustomizeSheet = false;
+function BrowserCustomizeToolbar() {
+ // Disable the toolbar context menu items
+ var menubar = document.getElementById("main-menubar");
+ for (let childNode of menubar.childNodes)
+ childNode.setAttribute("disabled", true);
+
+ var cmd = document.getElementById("cmd_CustomizeToolbars");
+ cmd.setAttribute("disabled", "true");
+
+ var splitter = document.getElementById("urlbar-search-splitter");
+ if (splitter)
+ splitter.parentNode.removeChild(splitter);
+
+ CombinedStopReload.uninit();
+
+ PlacesToolbarHelper.customizeStart();
+ BookmarkingUI.customizeStart();
+ DownloadsButton.customizeStart();
+
+ TabsInTitlebar.allowedBy("customizing-toolbars", false);
+
+ var customizeURL = "chrome://global/content/customizeToolbar.xul";
+ gCustomizeSheet = Services.prefs.getBoolPref("toolbar.customization.usesheet", false);
+
+ if (gCustomizeSheet) {
+ let sheetFrame = document.createElement("iframe");
+ let panel = document.getElementById("customizeToolbarSheetPopup");
+ sheetFrame.id = "customizeToolbarSheetIFrame";
+ sheetFrame.toolbox = gNavToolbox;
+ sheetFrame.panel = panel;
+ sheetFrame.setAttribute("style", panel.getAttribute("sheetstyle"));
+ panel.appendChild(sheetFrame);
+
+ // Open the panel, but make it invisible until the iframe has loaded so
+ // that the user doesn't see a white flash.
+ panel.style.visibility = "hidden";
+ gNavToolbox.addEventListener("beforecustomization", function onBeforeCustomization() {
+ gNavToolbox.removeEventListener("beforecustomization", onBeforeCustomization, false);
+ panel.style.removeProperty("visibility");
+ }, false);
+
+ sheetFrame.setAttribute("src", customizeURL);
+
+ panel.openPopup(gNavToolbox, "after_start", 0, 0);
+ } else {
+ window.openDialog(customizeURL,
+ "CustomizeToolbar",
+ "chrome,titlebar,toolbar,location,resizable,dependent",
+ gNavToolbox);
+ }
+}
+
+function BrowserToolboxCustomizeDone(aToolboxChanged) {
+ if (gCustomizeSheet) {
+ document.getElementById("customizeToolbarSheetPopup").hidePopup();
+ let iframe = document.getElementById("customizeToolbarSheetIFrame");
+ iframe.parentNode.removeChild(iframe);
+ }
+
+ // Update global UI elements that may have been added or removed
+ if (aToolboxChanged) {
+ gURLBar = document.getElementById("urlbar");
+
+ gProxyFavIcon = document.getElementById("page-proxy-favicon");
+ gHomeButton.updateTooltip();
+ gIdentityHandler._cacheElements();
+ window.XULBrowserWindow.init();
+
+#ifndef XP_MACOSX
+ updateEditUIVisibility();
+#endif
+
+ // Hacky: update the PopupNotifications' object's reference to the iconBox,
+ // if it already exists, since it may have changed if the URL bar was
+ // added/removed.
+ if (!window.__lookupGetter__("PopupNotifications"))
+ PopupNotifications.iconBox = document.getElementById("notification-popup-box");
+ }
+
+ PlacesToolbarHelper.customizeDone();
+ BookmarkingUI.customizeDone();
+ DownloadsButton.customizeDone();
+
+ // The url bar splitter state is dependent on whether stop/reload
+ // and the location bar are combined, so we need this ordering
+ CombinedStopReload.init();
+ UpdateUrlbarSearchSplitterState();
+ setUrlAndSearchBarWidthForConditionalForwardButton();
+
+ // Update the urlbar
+ if (gURLBar) {
+ gURLBarSettings.writePlaceholder();
+ URLBarSetURI();
+ XULBrowserWindow.asyncUpdateUI();
+ BookmarkingUI.updateStarState();
+ }
+
+ TabsInTitlebar.allowedBy("customizing-toolbars", true);
+
+ // Re-enable parts of the UI we disabled during the dialog
+ var menubar = document.getElementById("main-menubar");
+ for (let childNode of menubar.childNodes)
+ childNode.setAttribute("disabled", false);
+ var cmd = document.getElementById("cmd_CustomizeToolbars");
+ cmd.removeAttribute("disabled");
+
+ // make sure to re-enable click-and-hold
+ if (!Services.prefs.getBoolPref("ui.click_hold_context_menus", false))
+ SetClickAndHoldHandlers();
+
+ gBrowser.selectedBrowser.focus();
+}
+
+function BrowserToolboxCustomizeChange(aType) {
+ switch (aType) {
+ case "iconsize":
+ case "mode":
+ retrieveToolbarIconsizesFromTheme();
+ break;
+ default:
+ gHomeButton.updatePersonalToolbarStyle();
+ BookmarkingUI.customizeChange();
+ allTabs.readPref();
+ }
+}
+
+/**
+ * Allows themes to override the "iconsize" attribute on toolbars.
+ */
+function retrieveToolbarIconsizesFromTheme() {
+ function retrieveToolbarIconsize(aToolbar) {
+ if (aToolbar.localName != "toolbar")
+ return;
+
+ // The theme indicates that it wants to override the "iconsize" attribute
+ // by specifying a special value for the "counter-reset" property on the
+ // toolbar. A custom property cannot be used because getComputedStyle can
+ // only return the values of standard CSS properties.
+ let counterReset = getComputedStyle(aToolbar).counterReset;
+ if (counterReset == "smallicons 0")
+ aToolbar.setAttribute("iconsize", "small");
+ else if (counterReset == "largeicons 0")
+ aToolbar.setAttribute("iconsize", "large");
+ }
+
+ Array.forEach(gNavToolbox.childNodes, retrieveToolbarIconsize);
+ gNavToolbox.externalToolbars.forEach(retrieveToolbarIconsize);
+}
+
+/**
+ * Update the global flag that tracks whether or not any edit UI (the Edit menu,
+ * edit-related items in the context menu, and edit-related toolbar buttons
+ * is visible, then update the edit commands' enabled state accordingly. We use
+ * this flag to skip updating the edit commands on focus or selection changes
+ * when no UI is visible to improve performance (including pageload performance,
+ * since focus changes when you load a new page).
+ *
+ * If UI is visible, we use goUpdateGlobalEditMenuItems to set the commands'
+ * enabled state so the UI will reflect it appropriately.
+ *
+ * If the UI isn't visible, we enable all edit commands so keyboard shortcuts
+ * still work and just lazily disable them as needed when the user presses a
+ * shortcut.
+ *
+ * This doesn't work on Mac, since Mac menus flash when users press their
+ * keyboard shortcuts, so edit UI is essentially always visible on the Mac,
+ * and we need to always update the edit commands. Thus on Mac this function
+ * is a no op.
+ */
+function updateEditUIVisibility()
+{
+#ifndef XP_MACOSX
+ let editMenuPopupState = document.getElementById("menu_EditPopup").state;
+ let contextMenuPopupState = document.getElementById("contentAreaContextMenu").state;
+ let placesContextMenuPopupState = document.getElementById("placesContext").state;
+#ifdef MENUBAR_CAN_AUTOHIDE
+ let appMenuPopupState = document.getElementById("appmenu-popup").state;
+#endif
+
+ // The UI is visible if the Edit menu is opening or open, if the context menu
+ // is open, or if the toolbar has been customized to include the Cut, Copy,
+ // or Paste toolbar buttons.
+ gEditUIVisible = editMenuPopupState == "showing" ||
+ editMenuPopupState == "open" ||
+ contextMenuPopupState == "showing" ||
+ contextMenuPopupState == "open" ||
+ placesContextMenuPopupState == "showing" ||
+ placesContextMenuPopupState == "open" ||
+#ifdef MENUBAR_CAN_AUTOHIDE
+ appMenuPopupState == "showing" ||
+ appMenuPopupState == "open" ||
+#endif
+ document.getElementById("cut-button") ||
+ document.getElementById("copy-button") ||
+ document.getElementById("paste-button") ? true : false;
+
+ // If UI is visible, update the edit commands' enabled state to reflect
+ // whether or not they are actually enabled for the current focus/selection.
+ if (gEditUIVisible)
+ goUpdateGlobalEditMenuItems();
+
+ // Otherwise, enable all commands, so that keyboard shortcuts still work,
+ // then lazily determine their actual enabled state when the user presses
+ // a keyboard shortcut.
+ else {
+ goSetCommandEnabled("cmd_undo", true);
+ goSetCommandEnabled("cmd_redo", true);
+ goSetCommandEnabled("cmd_cut", true);
+ goSetCommandEnabled("cmd_copy", true);
+ goSetCommandEnabled("cmd_paste", true);
+ goSetCommandEnabled("cmd_selectAll", true);
+ goSetCommandEnabled("cmd_delete", true);
+ goSetCommandEnabled("cmd_switchTextDirection", true);
+ }
+#endif
+}
+
+/**
+ * Makes the Character Encoding menu enabled or disabled as appropriate.
+ * To be called when the View menu or the app menu is opened.
+ */
+function updateCharacterEncodingMenuState()
+{
+ let charsetMenu = document.getElementById("charsetMenu");
+ let appCharsetMenu = document.getElementById("appmenu_charsetMenu");
+ let appDevCharsetMenu =
+ document.getElementById("appmenu_developer_charsetMenu");
+ // gBrowser is null on Mac when the menubar shows in the context of
+ // non-browser windows. The above elements may be null depending on
+ // what parts of the menubar are present. E.g. no app menu on Mac.
+ if (gBrowser &&
+ gBrowser.docShell &&
+ gBrowser.docShell.mayEnableCharacterEncodingMenu) {
+ if (charsetMenu) {
+ charsetMenu.removeAttribute("disabled");
+ }
+ if (appCharsetMenu) {
+ appCharsetMenu.removeAttribute("disabled");
+ }
+ if (appDevCharsetMenu) {
+ appDevCharsetMenu.removeAttribute("disabled");
+ }
+ } else {
+ if (charsetMenu) {
+ charsetMenu.setAttribute("disabled", "true");
+ }
+ if (appCharsetMenu) {
+ appCharsetMenu.setAttribute("disabled", "true");
+ }
+ if (appDevCharsetMenu) {
+ appDevCharsetMenu.setAttribute("disabled", "true");
+ }
+ }
+}
+
+var XULBrowserWindow = {
+ // Stored Status, Link and Loading values
+ status: "",
+ defaultStatus: "",
+ overLink: "",
+ startTime: 0,
+ statusText: "",
+ isBusy: false,
+/* Pale Moon: Don't hide navigation controls and toolbars for "special" pages. SBaD, M!
+ inContentWhitelist: ["about:addons", "about:downloads", "about:permissions",
+ "about:sync-progress"],*/
+ inContentWhitelist: [],
+
+ QueryInterface: function (aIID) {
+ if (aIID.equals(Ci.nsIWebProgressListener) ||
+ aIID.equals(Ci.nsIWebProgressListener2) ||
+ aIID.equals(Ci.nsISupportsWeakReference) ||
+ aIID.equals(Ci.nsIXULBrowserWindow) ||
+ aIID.equals(Ci.nsISupports))
+ return this;
+ throw Cr.NS_NOINTERFACE;
+ },
+
+ get stopCommand () {
+ delete this.stopCommand;
+ return this.stopCommand = document.getElementById("Browser:Stop");
+ },
+ get reloadCommand () {
+ delete this.reloadCommand;
+ return this.reloadCommand = document.getElementById("Browser:Reload");
+ },
+ get statusTextField () {
+ delete this.statusTextField;
+ return this.statusTextField = document.getElementById("statusbar-display");
+ },
+ get isImage () {
+ delete this.isImage;
+ return this.isImage = document.getElementById("isImage");
+ },
+
+ init: function () {
+ this.throbberElement = document.getElementById("navigator-throbber");
+
+ // Bug 666809 - SecurityUI support for e10s
+ if (gMultiProcessBrowser)
+ return;
+
+ // Initialize the security button's state and tooltip text. Remember to reset
+ // _hostChanged, otherwise onSecurityChange will short circuit.
+ var securityUI = gBrowser.securityUI;
+ this._hostChanged = true;
+ this.onSecurityChange(null, null, securityUI.state);
+ },
+
+ destroy: function () {
+ // XXXjag to avoid leaks :-/, see bug 60729
+ delete this.throbberElement;
+ delete this.stopCommand;
+ delete this.reloadCommand;
+ delete this.statusTextField;
+ delete this.statusText;
+ },
+
+ setJSStatus: function () {
+ // unsupported
+ },
+
+ setDefaultStatus: function (status) {
+ this.defaultStatus = status;
+ this.updateStatusField();
+ },
+
+ setOverLink: function (url, anchorElt) {
+ // Encode bidirectional formatting characters.
+ // (RFC 3987 sections 3.2 and 4.1 paragraph 6)
+ url = url.replace(/[\u200e\u200f\u202a\u202b\u202c\u202d\u202e]/g,
+ encodeURIComponent);
+
+ if (gURLBar && gURLBar._mayTrimURLs /* corresponds to browser.urlbar.trimURLs */)
+ url = trimURL(url);
+
+ this.overLink = url;
+ LinkTargetDisplay.update();
+ },
+
+ updateStatusField: function () {
+ var text, type, types = ["overLink"];
+ if (this._busyUI)
+ types.push("status");
+ types.push("defaultStatus");
+ for (type of types) {
+ text = this[type];
+ if (text)
+ break;
+ }
+
+ // check the current value so we don't trigger an attribute change
+ // and cause needless (slow!) UI updates
+ if (this.statusText != text) {
+ let field = this.statusTextField;
+ field.setAttribute("previoustype", field.getAttribute("type"));
+ field.setAttribute("type", type);
+ field.label = text;
+ field.setAttribute("crop", type == "overLink" ? "center" : "end");
+ this.statusText = text;
+ }
+ },
+
+ // Called before links are navigated to to allow us to retarget them if needed.
+ onBeforeLinkTraversal: function(originalTarget, linkURI, linkNode, isAppTab) {
+ let target = this._onBeforeLinkTraversal(originalTarget, linkURI, linkNode, isAppTab);
+ return target;
+ },
+
+ _onBeforeLinkTraversal: function(originalTarget, linkURI, linkNode, isAppTab) {
+ // Don't modify non-default targets or targets that aren't in top-level app
+ // tab docshells (isAppTab will be false for app tab subframes).
+ if (originalTarget != "" || !isAppTab)
+ return originalTarget;
+
+ // External links from within app tabs should always open in new tabs
+ // instead of replacing the app tab's page (Bug 575561)
+ let linkHost;
+ let docHost;
+ try {
+ linkHost = linkURI.host;
+ docHost = linkNode.ownerDocument.documentURIObject.host;
+ } catch(e) {
+ // nsIURI.host can throw for non-nsStandardURL nsIURIs.
+ // If we fail to get either host, just return originalTarget.
+ return originalTarget;
+ }
+
+ if (docHost == linkHost)
+ return originalTarget;
+
+ // Special case: ignore "www" prefix if it is part of host string
+ let [longHost, shortHost] =
+ linkHost.length > docHost.length ? [linkHost, docHost] : [docHost, linkHost];
+ if (longHost == "www." + shortHost)
+ return originalTarget;
+
+ return "_blank";
+ },
+
+ onLinkIconAvailable: function (aIconURL) {
+ if (gProxyFavIcon && gBrowser.userTypedValue === null) {
+ PageProxySetIcon(aIconURL); // update the favicon in the URL bar
+ }
+ },
+
+ onProgressChange: function (aWebProgress, aRequest,
+ aCurSelfProgress, aMaxSelfProgress,
+ aCurTotalProgress, aMaxTotalProgress) {
+ // Do nothing.
+ },
+
+ onProgressChange64: function (aWebProgress, aRequest,
+ aCurSelfProgress, aMaxSelfProgress,
+ aCurTotalProgress, aMaxTotalProgress) {
+ return this.onProgressChange(aWebProgress, aRequest,
+ aCurSelfProgress, aMaxSelfProgress, aCurTotalProgress,
+ aMaxTotalProgress);
+ },
+
+ // This function fires only for the currently selected tab.
+ onStateChange: function (aWebProgress, aRequest, aStateFlags, aStatus) {
+ const nsIWebProgressListener = Ci.nsIWebProgressListener;
+ const nsIChannel = Ci.nsIChannel;
+
+ if (aStateFlags & nsIWebProgressListener.STATE_START &&
+ aStateFlags & nsIWebProgressListener.STATE_IS_NETWORK) {
+
+ if (aRequest && aWebProgress.isTopLevel) {
+ // clear out feed data
+ gBrowser.selectedBrowser.feeds = null;
+
+ // clear out search-engine data
+ gBrowser.selectedBrowser.engines = null;
+ }
+
+ this.isBusy = true;
+
+ if (!(aStateFlags & nsIWebProgressListener.STATE_RESTORING)) {
+ this._busyUI = true;
+
+ // Turn the throbber on.
+ if (this.throbberElement)
+ this.throbberElement.setAttribute("busy", "true");
+
+ // XXX: This needs to be based on window activity...
+ this.stopCommand.removeAttribute("disabled");
+ CombinedStopReload.switchToStop();
+ }
+ }
+ else if (aStateFlags & nsIWebProgressListener.STATE_STOP) {
+ // This (thanks to the filter) is a network stop or the last
+ // request stop outside of loading the document, stop throbbers
+ // and progress bars and such
+ if (aRequest) {
+ let msg = "";
+ let location;
+ // Get the URI either from a channel or a pseudo-object
+ if (aRequest instanceof nsIChannel || "URI" in aRequest) {
+ location = aRequest.URI;
+
+ // For keyword URIs clear the user typed value since they will be changed into real URIs
+ if (location.scheme == "keyword" && aWebProgress.isTopLevel)
+ gBrowser.userTypedValue = null;
+
+ if (location.spec != "about:blank") {
+ switch (aStatus) {
+ case Components.results.NS_ERROR_NET_TIMEOUT:
+ msg = gNavigatorBundle.getString("nv_timeout");
+ break;
+ }
+ }
+ }
+
+ this.status = "";
+ this.setDefaultStatus(msg);
+
+ // Disable menu entries for images, enable otherwise
+ if (!gMultiProcessBrowser && content.document && BrowserUtils.mimeTypeIsTextBased(content.document.contentType))
+ this.isImage.removeAttribute('disabled');
+ else
+ this.isImage.setAttribute('disabled', 'true');
+ }
+
+ this.isBusy = false;
+
+ if (this._busyUI) {
+ this._busyUI = false;
+
+ // Turn the throbber off.
+ if (this.throbberElement)
+ this.throbberElement.removeAttribute("busy");
+
+ this.stopCommand.setAttribute("disabled", "true");
+ CombinedStopReload.switchToReload(aRequest instanceof Ci.nsIRequest);
+ }
+ }
+ },
+
+ onLocationChange: function (aWebProgress, aRequest, aLocationURI, aFlags) {
+ var location = aLocationURI ? aLocationURI.spec : "";
+ this._hostChanged = true;
+
+ // If displayed, hide the form validation popup.
+ FormValidationHandler.hidePopup();
+
+ let pageTooltip = document.getElementById("aHTMLTooltip");
+ let tooltipNode = pageTooltip.triggerNode;
+ if (tooltipNode) {
+ // Optimise for the common case
+ if (aWebProgress.isTopLevel) {
+ pageTooltip.hidePopup();
+ }
+ else {
+ for (let tooltipWindow = tooltipNode.ownerDocument.defaultView;
+ tooltipWindow != tooltipWindow.parent;
+ tooltipWindow = tooltipWindow.parent) {
+ if (tooltipWindow == aWebProgress.DOMWindow) {
+ pageTooltip.hidePopup();
+ break;
+ }
+ }
+ }
+ }
+
+ // Disable menu entries for images, enable otherwise
+ if (!gMultiProcessBrowser && content.document && BrowserUtils.mimeTypeIsTextBased(content.document.contentType))
+ this.isImage.removeAttribute('disabled');
+ else
+ this.isImage.setAttribute('disabled', 'true');
+
+ this.hideOverLinkImmediately = true;
+ this.setOverLink("", null);
+ this.hideOverLinkImmediately = false;
+
+ // We should probably not do this if the value has changed since the user
+ // searched
+ // Update urlbar only if a new page was loaded on the primary content area
+ // Do not update urlbar if there was a subframe navigation
+
+ var browser = gBrowser.selectedBrowser;
+ if (aWebProgress.isTopLevel) {
+ if ((location == "about:blank" && (gMultiProcessBrowser || !content.opener)) ||
+ location == "") { // Second condition is for new tabs, otherwise
+ // reload function is enabled until tab is refreshed.
+ this.reloadCommand.setAttribute("disabled", "true");
+ } else {
+ this.reloadCommand.removeAttribute("disabled");
+ }
+
+ if (gURLBar) {
+ URLBarSetURI(aLocationURI);
+
+ // Update starring UI
+ BookmarkingUI.updateStarState();
+ }
+
+ // Show or hide browser chrome based on the whitelist
+ if (this.hideChromeForLocation(location)) {
+ document.documentElement.setAttribute("disablechrome", "true");
+ } else {
+ let ss = Cc["@mozilla.org/browser/sessionstore;1"].getService(Ci.nsISessionStore);
+ if (ss.getTabValue(gBrowser.selectedTab, "appOrigin"))
+ document.documentElement.setAttribute("disablechrome", "true");
+ else
+ document.documentElement.removeAttribute("disablechrome");
+ }
+
+ // Utility functions for disabling find
+ var shouldDisableFind = function shouldDisableFind(aDocument) {
+ let docElt = aDocument.documentElement;
+ return docElt && docElt.getAttribute("disablefastfind") == "true";
+ }
+
+ var disableFindCommands = function disableFindCommands(aDisable) {
+ let findCommands = [document.getElementById("cmd_find"),
+ document.getElementById("cmd_findAgain"),
+ document.getElementById("cmd_findPrevious")];
+ for (let elt of findCommands) {
+ if (aDisable)
+ elt.setAttribute("disabled", "true");
+ else
+ elt.removeAttribute("disabled");
+ }
+ if (gFindBarInitialized) {
+ if (!gFindBar.hidden && aDisable) {
+ gFindBar.hidden = true;
+ this._findbarTemporarilyHidden = true;
+ } else if (this._findbarTemporarilyHidden && !aDisable) {
+ gFindBar.hidden = false;
+ this._findbarTemporarilyHidden = false;
+ }
+ }
+ }.bind(this);
+
+ var onContentRSChange = function onContentRSChange(e) {
+ if (e.target.readyState != "interactive" && e.target.readyState != "complete")
+ return;
+
+ e.target.removeEventListener("readystatechange", onContentRSChange);
+ disableFindCommands(shouldDisableFind(e.target));
+ }
+
+ // Disable find commands in documents that ask for them to be disabled.
+ if (!gMultiProcessBrowser && aLocationURI &&
+ (aLocationURI.schemeIs("about") || aLocationURI.schemeIs("chrome"))) {
+ // Don't need to re-enable/disable find commands for same-document location changes
+ // (e.g. the replaceStates in about:addons)
+ if (!(aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT)) {
+ if (content.document.readyState == "interactive" || content.document.readyState == "complete")
+ disableFindCommands(shouldDisableFind(content.document));
+ else {
+ content.document.addEventListener("readystatechange", onContentRSChange);
+ }
+ }
+ } else
+ disableFindCommands(false);
+
+ if (gFindBarInitialized) {
+ if (gFindBar.findMode != gFindBar.FIND_NORMAL) {
+ // Close the Find toolbar if we're in old-style TAF mode
+ gFindBar.close();
+ }
+
+ // XXX
+ // See: https://github.com/MoonchildProductions/Pale-Moon/issues/364
+ // An actual preference: findbar.highlightAll
+ /*
+ if (!(gPrefService.getBoolPref("accessibility.typeaheadfind.highlightallremember") ||
+ gPrefService.getBoolPref("accessibility.typeaheadfind.highlightallbydefault"))) {
+ // fix bug 253793 - turn off highlight when page changes
+ gFindBar.getElement("highlight").checked = false;
+ }
+ */
+ }
+ }
+ UpdateBackForwardCommands(gBrowser.webNavigation);
+
+ gGestureSupport.restoreRotationState();
+
+ // See bug 358202, when tabs are switched during a drag operation,
+ // timers don't fire on windows (bug 203573)
+ if (aRequest)
+ setTimeout(function () { XULBrowserWindow.asyncUpdateUI(); }, 0);
+ else
+ this.asyncUpdateUI();
+ },
+
+ asyncUpdateUI: function () {
+ FeedHandler.updateFeeds();
+ },
+
+ hideChromeForLocation: function(aLocation) {
+ aLocation = aLocation.toLowerCase();
+ return this.inContentWhitelist.some(function(aSpec) {
+ return aSpec == aLocation;
+ });
+ },
+
+ onStatusChange: function (aWebProgress, aRequest, aStatus, aMessage) {
+ this.status = aMessage;
+ this.updateStatusField();
+ },
+
+ // Properties used to cache security state used to update the UI
+ _state: null,
+ _hostChanged: false, // onLocationChange will flip this bit
+
+ onSecurityChange: function (aWebProgress, aRequest, aState) {
+ // Don't need to do anything if the data we use to update the UI hasn't
+ // changed
+ if (this._state == aState &&
+ !this._hostChanged) {
+#ifdef DEBUG
+ try {
+ var contentHost = gBrowser.contentWindow.location.host;
+ if (this._host !== undefined && this._host != contentHost) {
+ Components.utils.reportError(
+ "ASSERTION: browser.js host is inconsistent. Content window has " +
+ "<" + contentHost + "> but cached host is <" + this._host + ">.\n"
+ );
+ }
+ } catch (ex) {}
+#endif
+ return;
+ }
+ this._state = aState;
+
+#ifdef DEBUG
+ try {
+ this._host = gBrowser.contentWindow.location.host;
+ } catch(ex) {
+ this._host = null;
+ }
+#endif
+
+ this._hostChanged = false;
+
+ // aState is defined as a bitmask that may be extended in the future.
+ // We filter out any unknown bits before testing for known values.
+ const wpl = Components.interfaces.nsIWebProgressListener;
+ const wpl_security_bits = wpl.STATE_IS_SECURE |
+ wpl.STATE_IS_BROKEN |
+ wpl.STATE_IS_INSECURE;
+ var level;
+
+ switch (this._state & wpl_security_bits) {
+ case wpl.STATE_IS_SECURE:
+ level = "high";
+ break;
+ case wpl.STATE_IS_BROKEN:
+ level = "broken";
+ break;
+ }
+
+ if (level) {
+ // We don't style the Location Bar based on the the 'level' attribute
+ // anymore, but still set it for third-party themes.
+ if (gURLBar)
+ gURLBar.setAttribute("level", level);
+ } else {
+ if (gURLBar)
+ gURLBar.removeAttribute("level");
+ }
+
+ if (gMultiProcessBrowser)
+ return;
+
+ // Don't pass in the actual location object, since it can cause us to
+ // hold on to the window object too long. Just pass in the fields we
+ // care about. (bug 424829)
+ var location = gBrowser.contentWindow.location;
+ var locationObj = {};
+ try {
+ // about:blank can be used by webpages so pretend it is http
+ locationObj.protocol = location == "about:blank" ? "http:" : location.protocol;
+ locationObj.host = location.host;
+ locationObj.hostname = location.hostname;
+ locationObj.port = location.port;
+ } catch (ex) {
+ // Can sometimes throw if the URL being visited has no host/hostname,
+ // e.g. about:blank. The _state for these pages means we won't need these
+ // properties anyways, though.
+ }
+ gIdentityHandler.checkIdentity(this._state, locationObj);
+ },
+
+ // simulate all change notifications after switching tabs
+ onUpdateCurrentBrowser: function XWB_onUpdateCurrentBrowser(aStateFlags, aStatus, aMessage, aTotalProgress) {
+ if (FullZoom.updateBackgroundTabs)
+ FullZoom.onLocationChange(gBrowser.currentURI, true);
+ var nsIWebProgressListener = Components.interfaces.nsIWebProgressListener;
+ var loadingDone = aStateFlags & nsIWebProgressListener.STATE_STOP;
+ // use a pseudo-object instead of a (potentially nonexistent) channel for getting
+ // a correct error message - and make sure that the UI is always either in
+ // loading (STATE_START) or done (STATE_STOP) mode
+ this.onStateChange(
+ gBrowser.webProgress,
+ { URI: gBrowser.currentURI },
+ loadingDone ? nsIWebProgressListener.STATE_STOP : nsIWebProgressListener.STATE_START,
+ aStatus
+ );
+ // status message and progress value are undefined if we're done with loading
+ if (loadingDone)
+ return;
+ this.onStatusChange(gBrowser.webProgress, null, 0, aMessage);
+ }
+};
+
+var LinkTargetDisplay = {
+ get DELAY_SHOW() {
+ delete this.DELAY_SHOW;
+ return this.DELAY_SHOW = Services.prefs.getIntPref("browser.overlink-delay");
+ },
+
+ DELAY_HIDE: 250,
+ _timer: 0,
+
+ get _isVisible () XULBrowserWindow.statusTextField.label != "",
+
+ update: function () {
+ clearTimeout(this._timer);
+ window.removeEventListener("mousemove", this, true);
+
+ if (!XULBrowserWindow.overLink) {
+ if (XULBrowserWindow.hideOverLinkImmediately)
+ this._hide();
+ else
+ this._timer = setTimeout(this._hide.bind(this), this.DELAY_HIDE);
+ return;
+ }
+
+ if (this._isVisible) {
+ XULBrowserWindow.updateStatusField();
+ } else {
+ // Let the display appear when the mouse doesn't move within the delay
+ this._showDelayed();
+ window.addEventListener("mousemove", this, true);
+ }
+ },
+
+ handleEvent: function (event) {
+ switch (event.type) {
+ case "mousemove":
+ // Restart the delay since the mouse was moved
+ clearTimeout(this._timer);
+ this._showDelayed();
+ break;
+ }
+ },
+
+ _showDelayed: function () {
+ this._timer = setTimeout(function (self) {
+ XULBrowserWindow.updateStatusField();
+ window.removeEventListener("mousemove", self, true);
+ }, this.DELAY_SHOW, this);
+ },
+
+ _hide: function () {
+ clearTimeout(this._timer);
+
+ XULBrowserWindow.updateStatusField();
+ }
+};
+
+var CombinedStopReload = {
+ init: function () {
+ if (this._initialized)
+ return;
+
+ var urlbar = document.getElementById("urlbar-container");
+ var reload = document.getElementById("reload-button");
+ var stop = document.getElementById("stop-button");
+
+ if (urlbar) {
+ if (urlbar.parentNode.getAttribute("mode") != "icons" ||
+ !reload || urlbar.nextSibling != reload ||
+ !stop || reload.nextSibling != stop)
+ urlbar.removeAttribute("combined");
+ else {
+ urlbar.setAttribute("combined", "true");
+ reload = document.getElementById("urlbar-reload-button");
+ stop = document.getElementById("urlbar-stop-button");
+ }
+ }
+ if (!stop || !reload || reload.nextSibling != stop)
+ return;
+
+ this._initialized = true;
+ if (XULBrowserWindow.stopCommand.getAttribute("disabled") != "true")
+ reload.setAttribute("displaystop", "true");
+ stop.addEventListener("click", this, false);
+ this.reload = reload;
+ this.stop = stop;
+ },
+
+ uninit: function () {
+ if (!this._initialized)
+ return;
+
+ this._cancelTransition();
+ this._initialized = false;
+ this.stop.removeEventListener("click", this, false);
+ this.reload = null;
+ this.stop = null;
+ },
+
+ handleEvent: function (event) {
+ // the only event we listen to is "click" on the stop button
+ if (event.button == 0 &&
+ !this.stop.disabled)
+ this._stopClicked = true;
+ },
+
+ switchToStop: function () {
+ if (!this._initialized)
+ return;
+
+ this._cancelTransition();
+ this.reload.setAttribute("displaystop", "true");
+ },
+
+ switchToReload: function (aDelay) {
+ if (!this._initialized)
+ return;
+
+ this.reload.removeAttribute("displaystop");
+
+ if (!aDelay || this._stopClicked) {
+ this._stopClicked = false;
+ this._cancelTransition();
+ this.reload.disabled = XULBrowserWindow.reloadCommand
+ .getAttribute("disabled") == "true";
+ return;
+ }
+
+ if (this._timer)
+ return;
+
+ // Temporarily disable the reload button to prevent the user from
+ // accidentally reloading the page when intending to click the stop button
+ this.reload.disabled = true;
+ this._timer = setTimeout(function (self) {
+ self._timer = 0;
+ self.reload.disabled = XULBrowserWindow.reloadCommand
+ .getAttribute("disabled") == "true";
+ }, 650, this);
+ },
+
+ _cancelTransition: function () {
+ if (this._timer) {
+ clearTimeout(this._timer);
+ this._timer = 0;
+ }
+ }
+};
+
+var TabsProgressListener = {
+ onStateChange: function (aBrowser, aWebProgress, aRequest, aStateFlags, aStatus) {
+
+ // Attach a listener to watch for "click" events bubbling up from error
+ // pages and other similar page. This lets us fix bugs like 401575 which
+ // require error page UI to do privileged things, without letting error
+ // pages have any privilege themselves.
+ // We can't look for this during onLocationChange since at that point the
+ // document URI is not yet the about:-uri of the error page.
+
+ let doc = gMultiProcessBrowser ? null : aWebProgress.DOMWindow.document;
+ if (!gMultiProcessBrowser &&
+ aStateFlags & Ci.nsIWebProgressListener.STATE_STOP &&
+ Components.isSuccessCode(aStatus) &&
+ doc.documentURI.startsWith("about:") &&
+ !doc.documentURI.toLowerCase().startsWith("about:blank") &&
+ !doc.documentElement.hasAttribute("hasBrowserHandlers")) {
+ // STATE_STOP may be received twice for documents, thus store an
+ // attribute to ensure handling it just once.
+ doc.documentElement.setAttribute("hasBrowserHandlers", "true");
+ aBrowser.addEventListener("click", BrowserOnClick, true);
+ aBrowser.addEventListener("pagehide", function onPageHide(event) {
+ if (event.target.defaultView.frameElement)
+ return;
+ aBrowser.removeEventListener("click", BrowserOnClick, true);
+ aBrowser.removeEventListener("pagehide", onPageHide, true);
+ if (event.target.documentElement)
+ event.target.documentElement.removeAttribute("hasBrowserHandlers");
+ }, true);
+
+ // We also want to make changes to page UI for unprivileged about pages.
+ BrowserOnAboutPageLoad(doc);
+ }
+ },
+
+ onLocationChange: function (aBrowser, aWebProgress, aRequest, aLocationURI,
+ aFlags) {
+ // Filter out location changes caused by anchor navigation
+ // or history.push/pop/replaceState.
+ if (aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT)
+ return;
+
+ // Only need to call locationChange if the PopupNotifications object
+ // for this window has already been initialized (i.e. its getter no
+ // longer exists)
+ if (!Object.getOwnPropertyDescriptor(window, "PopupNotifications").get)
+ PopupNotifications.locationChange(aBrowser);
+
+ gBrowser.getNotificationBox(aBrowser).removeTransientNotifications();
+
+ // Filter out location changes in sub documents.
+ if (aWebProgress.isTopLevel) {
+ // Initialize the click-to-play state.
+ aBrowser._clickToPlayPluginsActivated = new Map();
+ aBrowser._clickToPlayAllPluginsActivated = false;
+ aBrowser._pluginScriptedState = gPluginHandler.PLUGIN_SCRIPTED_STATE_NONE;
+
+ FullZoom.onLocationChange(aLocationURI, false, aBrowser);
+ }
+ },
+
+ onRefreshAttempted: function (aBrowser, aWebProgress, aURI, aDelay, aSameURI) {
+ if (gPrefService.getBoolPref("accessibility.blockautorefresh")) {
+ let brandBundle = document.getElementById("bundle_brand");
+ let brandShortName = brandBundle.getString("brandShortName");
+ let refreshButtonText =
+ gNavigatorBundle.getString("refreshBlocked.goButton");
+ let refreshButtonAccesskey =
+ gNavigatorBundle.getString("refreshBlocked.goButton.accesskey");
+ let message =
+ gNavigatorBundle.getFormattedString(aSameURI ? "refreshBlocked.refreshLabel"
+ : "refreshBlocked.redirectLabel",
+ [brandShortName]);
+ let docShell = aWebProgress.DOMWindow
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIDocShell);
+ let notificationBox = gBrowser.getNotificationBox(aBrowser);
+ let notification = notificationBox.getNotificationWithValue("refresh-blocked");
+ if (notification) {
+ notification.label = message;
+ notification.refreshURI = aURI;
+ notification.delay = aDelay;
+ notification.docShell = docShell;
+ } else {
+ let buttons = [{
+ label: refreshButtonText,
+ accessKey: refreshButtonAccesskey,
+ callback: function (aNotification, aButton) {
+ var refreshURI = aNotification.docShell
+ .QueryInterface(Ci.nsIRefreshURI);
+ refreshURI.forceRefreshURI(aNotification.refreshURI,
+ aNotification.delay, true);
+ }
+ }];
+ notification =
+ notificationBox.appendNotification(message, "refresh-blocked",
+ "chrome://browser/skin/Info.png",
+ notificationBox.PRIORITY_INFO_MEDIUM,
+ buttons);
+ notification.refreshURI = aURI;
+ notification.delay = aDelay;
+ notification.docShell = docShell;
+ }
+ return false;
+ }
+ return true;
+ }
+}
+
+function nsBrowserAccess() { }
+
+nsBrowserAccess.prototype = {
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIBrowserDOMWindow, Ci.nsISupports]),
+
+ openURI: function (aURI, aOpener, aWhere, aContext) {
+ var newWindow = null;
+ var isExternal = !!(aContext & Ci.nsIBrowserDOMWindow.OPEN_EXTERNAL);
+
+ if (aOpener && isExternal) {
+ Cu.reportError("nsBrowserAccess.openURI did not expect an opener to be " +
+ "passed if the context is OPEN_EXTERNAL.");
+ throw Cr.NS_ERROR_FAILURE;
+ }
+
+ if (isExternal && aURI && aURI.schemeIs("chrome")) {
+ dump("use -chrome command-line option to load external chrome urls\n");
+ return null;
+ }
+
+ if (aWhere == Ci.nsIBrowserDOMWindow.OPEN_DEFAULTWINDOW) {
+ if (isExternal &&
+ gPrefService.prefHasUserValue("browser.link.open_newwindow.override.external"))
+ aWhere = gPrefService.getIntPref("browser.link.open_newwindow.override.external");
+ else
+ aWhere = gPrefService.getIntPref("browser.link.open_newwindow");
+ }
+
+ let referrer = aOpener ? makeURI(aOpener.location.href) : null;
+ let triggeringPrincipal = null;
+ let referrerPolicy = Ci.nsIHttpChannel.REFERRER_POLICY_DEFAULT;
+ if (aOpener && aOpener.document) {
+ referrerPolicy = aOpener.document.referrerPolicy;
+ triggeringPrincipal = aOpener.document.nodePrincipal;
+ }
+
+ switch (aWhere) {
+ case Ci.nsIBrowserDOMWindow.OPEN_NEWWINDOW :
+ // FIXME: Bug 408379. So how come this doesn't send the
+ // referrer like the other loads do?
+ var url = aURI ? aURI.spec : "about:blank";
+ // Pass all params to openDialog to ensure that "url" isn't passed through
+ // loadOneOrMoreURIs, which splits based on "|"
+ newWindow = openDialog(getBrowserURL(), "_blank", "all,dialog=no", url, null, null, null);
+ break;
+ case Ci.nsIBrowserDOMWindow.OPEN_NEWTAB :
+ let win, needToFocusWin;
+
+ // try the current window. if we're in a popup, fall back on the most recent browser window
+ if (window.toolbar.visible)
+ win = window;
+ else {
+ let isPrivate = PrivateBrowsingUtils.isWindowPrivate(aOpener || window);
+ win = RecentWindow.getMostRecentBrowserWindow({private: isPrivate});
+ needToFocusWin = true;
+ }
+
+ if (!win) {
+ // we couldn't find a suitable window, a new one needs to be opened.
+ return null;
+ }
+
+ if (isExternal && (!aURI || aURI.spec == "about:blank")) {
+ win.BrowserOpenTab(); // this also focuses the location bar
+ win.focus();
+ newWindow = win.content;
+ break;
+ }
+
+ let loadInBackground = gPrefService.getBoolPref("browser.tabs.loadDivertedInBackground");
+ let openerWindow = (aContext & Ci.nsIBrowserDOMWindow.OPEN_NO_OPENER) ? null : aOpener;
+
+ let tab = win.gBrowser.loadOneTab(aURI ? aURI.spec : "about:blank", {
+ triggeringPrincipal: triggeringPrincipal,
+ referrerURI: referrer,
+ referrerPolicy: referrerPolicy,
+ fromExternal: isExternal,
+ inBackground: loadInBackground,
+ opener: openerWindow });
+ let browser = win.gBrowser.getBrowserForTab(tab);
+
+ if (gPrefService.getBoolPref("browser.tabs.noWindowActivationOnExternal")) {
+ isExternal = false; // this is a hack, but it works
+ }
+
+ newWindow = browser.contentWindow;
+ if (needToFocusWin || (!loadInBackground && isExternal))
+ newWindow.focus();
+ break;
+ default : // OPEN_CURRENTWINDOW or an illegal value
+ newWindow = content;
+ if (aURI) {
+ let loadflags = isExternal ?
+ Ci.nsIWebNavigation.LOAD_FLAGS_FROM_EXTERNAL :
+ Ci.nsIWebNavigation.LOAD_FLAGS_NONE;
+ gBrowser.loadURIWithFlags(aURI.spec, {
+ flags: loadflags,
+ triggeringPrincipal: triggeringPrincipal,
+ referrerURI: referrer,
+ referrerPolicy: referrerPolicy,
+ });
+ }
+ if (!gPrefService.getBoolPref("browser.tabs.loadDivertedInBackground"))
+ window.focus();
+ }
+ return newWindow;
+ },
+
+ isTabContentWindow: function (aWindow) {
+ return gBrowser.browsers.some(function (browser) browser.contentWindow == aWindow);
+ }
+}
+
+function onViewToolbarsPopupShowing(aEvent, aInsertPoint) {
+ var popup = aEvent.target;
+ if (popup != aEvent.currentTarget)
+ return;
+
+ // Empty the menu
+ for (var i = popup.childNodes.length-1; i >= 0; --i) {
+ var deadItem = popup.childNodes[i];
+ if (deadItem.hasAttribute("toolbarId"))
+ popup.removeChild(deadItem);
+ }
+
+ var firstMenuItem = aInsertPoint || popup.firstChild;
+
+ let toolbarNodes = Array.slice(gNavToolbox.childNodes);
+ toolbarNodes.push(document.getElementById("addon-bar"));
+
+ for (let toolbar of toolbarNodes) {
+ let toolbarName = toolbar.getAttribute("toolbarname");
+ if (toolbarName) {
+ let menuItem = document.createElement("menuitem");
+ let hidingAttribute = toolbar.getAttribute("type") == "menubar" ?
+ "autohide" : "collapsed";
+ menuItem.setAttribute("id", "toggle_" + toolbar.id);
+ menuItem.setAttribute("toolbarId", toolbar.id);
+ menuItem.setAttribute("type", "checkbox");
+ menuItem.setAttribute("label", toolbarName);
+ menuItem.setAttribute("checked", toolbar.getAttribute(hidingAttribute) != "true");
+ if (popup.id != "appmenu_customizeMenu")
+ menuItem.setAttribute("accesskey", toolbar.getAttribute("accesskey"));
+ if (popup.id != "toolbar-context-menu")
+ menuItem.setAttribute("key", toolbar.getAttribute("key"));
+
+ popup.insertBefore(menuItem, firstMenuItem);
+
+ menuItem.addEventListener("command", onViewToolbarCommand, false);
+ }
+ }
+}
+
+function onViewToolbarCommand(aEvent) {
+ var toolbarId = aEvent.originalTarget.getAttribute("toolbarId");
+ var toolbar = document.getElementById(toolbarId);
+ var isVisible = aEvent.originalTarget.getAttribute("checked") == "true";
+ setToolbarVisibility(toolbar, isVisible);
+}
+
+function setToolbarVisibility(toolbar, isVisible) {
+ var hidingAttribute = toolbar.getAttribute("type") == "menubar" ?
+ "autohide" : "collapsed";
+
+ toolbar.setAttribute(hidingAttribute, !isVisible);
+ document.persist(toolbar.id, hidingAttribute);
+
+ // Customizable toolbars - persist the hiding attribute.
+ if (toolbar.hasAttribute("customindex")) {
+ var toolbox = toolbar.parentNode;
+ var name = toolbar.getAttribute("toolbarname");
+ if (toolbox.toolbarset) {
+ try {
+ // Checking all attributes starting with "toolbar".
+ Array.prototype.slice.call(toolbox.toolbarset.attributes, 0)
+ .find(x => {
+ if (x.name.startsWith("toolbar")) {
+ var toolbarInfo = x.value;
+ var infoSplit = toolbarInfo.split(gToolbarInfoSeparators[0]);
+ if (infoSplit[0] == name) {
+ infoSplit[1] = [
+ infoSplit[1].split(gToolbarInfoSeparators[1], 1), !isVisible
+ ].join(gToolbarInfoSeparators[1]);
+ toolbox.toolbarset.setAttribute(
+ x.name, infoSplit.join(gToolbarInfoSeparators[0]));
+ document.persist(toolbox.toolbarset.id, x.name);
+ }
+ }
+ });
+ } catch (e) {
+ Components.utils.reportError(
+ "Customizable toolbars - persist the hiding attribute: " + e);
+ }
+ }
+ }
+
+ PlacesToolbarHelper.init();
+ BookmarkingUI.onToolbarVisibilityChange();
+ gBrowser.updateWindowResizers();
+
+#ifdef MENUBAR_CAN_AUTOHIDE
+ updateAppButtonDisplay();
+#endif
+
+ if (isVisible)
+ ToolbarIconColor.inferFromText();
+}
+
+var AudioIndicator = {
+ init: function () {
+ Services.prefs.addObserver(this._prefName, this, false);
+ this.syncUI();
+ },
+
+ uninit: function () {
+ Services.prefs.removeObserver(this._prefName, this);
+ },
+
+ toggle: function () {
+ this.enabled = !Services.prefs.getBoolPref(this._prefName);
+ },
+
+ syncUI: function () {
+ document.getElementById("context_toggleMuteTab").setAttribute("hidden", this.enabled);
+ document.getElementById("key_toggleMute").setAttribute("disabled", this.enabled);
+ },
+
+ get enabled () {
+ return !Services.prefs.getBoolPref(this._prefName);
+ },
+
+ set enabled (val) {
+ Services.prefs.setBoolPref(this._prefName, !!val);
+ return val;
+ },
+
+ observe: function (subject, topic, data) {
+ if (topic == "nsPref:changed")
+ this.syncUI();
+ },
+
+ _prefName: "browser.tabs.showAudioPlayingIcon"
+}
+
+var TabsOnTop = {
+ init: function TabsOnTop_init() {
+ Services.prefs.addObserver(this._prefName, this, false);
+// Pale Moon: Stop Being a Derp, Mozilla (#3)
+ // Only show the toggle UI if the user disabled tabs on top.
+// if (Services.prefs.getBoolPref(this._prefName)) {
+// for (let item of document.querySelectorAll("menuitem[command=cmd_ToggleTabsOnTop]"))
+// item.parentNode.removeChild(item);
+// }
+ },
+
+ uninit: function TabsOnTop_uninit() {
+ Services.prefs.removeObserver(this._prefName, this);
+ },
+
+ toggle: function () {
+ this.enabled = !Services.prefs.getBoolPref(this._prefName);
+ },
+
+ syncUI: function () {
+ let userEnabled = Services.prefs.getBoolPref(this._prefName);
+ let enabled = userEnabled && gBrowser.tabContainer.visible;
+
+ document.getElementById("cmd_ToggleTabsOnTop")
+ .setAttribute("checked", userEnabled);
+
+ document.documentElement.setAttribute("tabsontop", enabled);
+ document.getElementById("navigator-toolbox").setAttribute("tabsontop", enabled);
+ document.getElementById("TabsToolbar").setAttribute("tabsontop", enabled);
+ document.getElementById("nav-bar").setAttribute("tabsontop", enabled);
+ gBrowser.tabContainer.setAttribute("tabsontop", enabled);
+ TabsInTitlebar.allowedBy("tabs-on-top", enabled);
+ },
+
+ get enabled () {
+ return gNavToolbox.getAttribute("tabsontop") == "true";
+ },
+
+ set enabled (val) {
+ Services.prefs.setBoolPref(this._prefName, !!val);
+ return val;
+ },
+
+ observe: function (subject, topic, data) {
+ if (topic == "nsPref:changed")
+ this.syncUI();
+ },
+
+ _prefName: "browser.tabs.onTop"
+}
+
+var TabsInTitlebar = {
+ init: function () {
+#ifdef CAN_DRAW_IN_TITLEBAR
+ this._readPref();
+ Services.prefs.addObserver(this._prefName, this, false);
+
+ // Don't trust the initial value of the sizemode attribute; wait for
+ // the resize event (handled in tabbrowser.xml).
+ this.allowedBy("sizemode", false);
+
+ this._initialized = true;
+#endif
+ },
+
+ allowedBy: function (condition, allow) {
+#ifdef CAN_DRAW_IN_TITLEBAR
+ if (allow) {
+ if (condition in this._disallowed) {
+ delete this._disallowed[condition];
+ this._update();
+ }
+ } else {
+ if (!(condition in this._disallowed)) {
+ this._disallowed[condition] = null;
+ this._update();
+ }
+ }
+#endif
+ },
+
+ get enabled() {
+ return document.documentElement.getAttribute("tabsintitlebar") == "true";
+ },
+
+#ifdef CAN_DRAW_IN_TITLEBAR
+ observe: function (subject, topic, data) {
+ if (topic == "nsPref:changed")
+ this._readPref();
+ },
+
+ _initialized: false,
+ _disallowed: {},
+ _prefName: "browser.tabs.drawInTitlebar",
+
+ _readPref: function () {
+ this.allowedBy("pref",
+ Services.prefs.getBoolPref(this._prefName));
+ },
+
+ _update: function () {
+ function $(id) document.getElementById(id);
+ function rect(ele) ele.getBoundingClientRect();
+
+ if (!this._initialized || window.fullScreen)
+ return;
+
+ let allowed = true;
+ for (let something in this._disallowed) {
+ allowed = false;
+ break;
+ }
+
+ if (allowed == this.enabled)
+ return;
+
+ let titlebar = $("titlebar");
+
+ if (allowed) {
+ let tabsToolbar = $("TabsToolbar");
+
+#ifdef MENUBAR_CAN_AUTOHIDE
+ let appmenuButtonBox = $("appmenu-button-container");
+ this._sizePlaceholder("appmenu-button", rect(appmenuButtonBox).width);
+#endif
+ let captionButtonsBox = $("titlebar-buttonbox");
+ this._sizePlaceholder("caption-buttons", rect(captionButtonsBox).width);
+
+ let tabsToolbarRect = rect(tabsToolbar);
+ let titlebarTop = rect($("titlebar-content")).top;
+ titlebar.style.marginBottom = - Math.min(tabsToolbarRect.top - titlebarTop,
+ tabsToolbarRect.height) + "px";
+
+ document.documentElement.setAttribute("tabsintitlebar", "true");
+
+ if (!this._draghandle) {
+ let tmp = {};
+ Components.utils.import("resource://gre/modules/WindowDraggingUtils.jsm", tmp);
+ this._draghandle = new tmp.WindowDraggingElement(tabsToolbar);
+ this._draghandle.mouseDownCheck = function () {
+ return !this._dragBindingAlive && TabsInTitlebar.enabled;
+ };
+ }
+ } else {
+ document.documentElement.removeAttribute("tabsintitlebar");
+
+ titlebar.style.marginBottom = "";
+ }
+
+ ToolbarIconColor.inferFromText();
+ },
+
+ _sizePlaceholder: function (type, width) {
+ Array.forEach(document.querySelectorAll(".titlebar-placeholder[type='"+ type +"']"),
+ function (node) { node.width = width; });
+ },
+#endif
+
+ uninit: function () {
+#ifdef CAN_DRAW_IN_TITLEBAR
+ this._initialized = false;
+ Services.prefs.removeObserver(this._prefName, this);
+#endif
+ }
+};
+
+#ifdef MENUBAR_CAN_AUTOHIDE
+function updateAppButtonDisplay() {
+ var displayAppButton =
+ !gInPrintPreviewMode &&
+ window.menubar.visible &&
+ document.getElementById("toolbar-menubar").getAttribute("autohide") == "true";
+
+#ifdef CAN_DRAW_IN_TITLEBAR
+ document.getElementById("titlebar").hidden = !displayAppButton;
+
+ if (displayAppButton)
+ document.documentElement.setAttribute("chromemargin", "0,2,2,2");
+ else
+ document.documentElement.removeAttribute("chromemargin");
+
+ TabsInTitlebar.allowedBy("drawing-in-titlebar", displayAppButton);
+#else
+ document.getElementById("appmenu-toolbar-button").hidden =
+ !displayAppButton;
+#endif
+}
+#endif
+
+#ifdef CAN_DRAW_IN_TITLEBAR
+function onTitlebarMaxClick() {
+ if (window.windowState == window.STATE_MAXIMIZED)
+ window.restore();
+ else
+ window.maximize();
+}
+#endif
+
+function displaySecurityInfo()
+{
+ BrowserPageInfo(null, "securityTab");
+}
+
+/**
+ * Opens or closes the sidebar identified by commandID.
+ *
+ * @param commandID a string identifying the sidebar to toggle; see the
+ * note below. (Optional if a sidebar is already open.)
+ * @param forceOpen boolean indicating whether the sidebar should be
+ * opened regardless of its current state (optional).
+ * @note
+ * We expect to find a xul:broadcaster element with the specified ID.
+ * The following attributes on that element may be used and/or modified:
+ * - id (required) the string to match commandID. The convention
+ * is to use this naming scheme: 'view<sidebar-name>Sidebar'.
+ * - sidebarurl (required) specifies the URL to load in this sidebar.
+ * - sidebartitle or label (in that order) specify the title to
+ * display on the sidebar.
+ * - checked indicates whether the sidebar is currently displayed.
+ * Note that toggleSidebar updates this attribute when
+ * it changes the sidebar's visibility.
+ * - group this attribute must be set to "sidebar".
+ */
+function toggleSidebar(commandID, forceOpen) {
+
+ var sidebarBox = document.getElementById("sidebar-box");
+ if (!commandID)
+ commandID = sidebarBox.getAttribute("sidebarcommand");
+
+ var sidebarBroadcaster = document.getElementById(commandID);
+ var sidebar = document.getElementById("sidebar"); // xul:browser
+ var sidebarTitle = document.getElementById("sidebar-title");
+ var sidebarSplitter = document.getElementById("sidebar-splitter");
+
+ if (sidebarBroadcaster.getAttribute("checked") == "true") {
+ if (!forceOpen) {
+ // Replace the document currently displayed in the sidebar with about:blank
+ // so that we can free memory by unloading the page. We need to explicitly
+ // create a new content viewer because the old one doesn't get destroyed
+ // until about:blank has loaded (which does not happen as long as the
+ // element is hidden).
+ sidebar.setAttribute("src", "about:blank");
+ sidebar.docShell.createAboutBlankContentViewer(null);
+
+ sidebarBroadcaster.removeAttribute("checked");
+ sidebarBox.setAttribute("sidebarcommand", "");
+ sidebarTitle.value = "";
+ sidebarBox.hidden = true;
+ sidebarSplitter.hidden = true;
+ gBrowser.selectedBrowser.focus();
+ } else {
+ fireSidebarFocusedEvent();
+ }
+ return;
+ }
+
+ // now we need to show the specified sidebar
+
+ // ..but first update the 'checked' state of all sidebar broadcasters
+ var broadcasters = document.getElementsByAttribute("group", "sidebar");
+ for (let broadcaster of broadcasters) {
+ // skip elements that observe sidebar broadcasters and random
+ // other elements
+ if (broadcaster.localName != "broadcaster")
+ continue;
+
+ if (broadcaster != sidebarBroadcaster)
+ broadcaster.removeAttribute("checked");
+ else
+ sidebarBroadcaster.setAttribute("checked", "true");
+ }
+
+ sidebarBox.hidden = false;
+ sidebarSplitter.hidden = false;
+
+ var url = sidebarBroadcaster.getAttribute("sidebarurl");
+ var title = sidebarBroadcaster.getAttribute("sidebartitle");
+ if (!title)
+ title = sidebarBroadcaster.getAttribute("label");
+ sidebar.setAttribute("src", url); // kick off async load
+ sidebarBox.setAttribute("sidebarcommand", sidebarBroadcaster.id);
+ sidebarTitle.value = title;
+
+ // We set this attribute here in addition to setting it on the <browser>
+ // element itself, because the code in gBrowserInit.onUnload persists this
+ // attribute, not the "src" of the <browser id="sidebar">. The reason it
+ // does that is that we want to delay sidebar load a bit when a browser
+ // window opens. See delayedStartup().
+ sidebarBox.setAttribute("src", url);
+
+ if (sidebar.contentDocument.location.href != url)
+ sidebar.addEventListener("load", sidebarOnLoad, true);
+ else // older code handled this case, so we do it too
+ fireSidebarFocusedEvent();
+}
+
+function sidebarOnLoad(event) {
+ var sidebar = document.getElementById("sidebar");
+ sidebar.removeEventListener("load", sidebarOnLoad, true);
+ // We're handling the 'load' event before it bubbles up to the usual
+ // (non-capturing) event handlers. Let it bubble up before firing the
+ // SidebarFocused event.
+ setTimeout(fireSidebarFocusedEvent, 0);
+}
+
+/**
+ * Fire a "SidebarFocused" event on the sidebar's |window| to give the sidebar
+ * a chance to adjust focus as needed. An additional event is needed, because
+ * we don't want to focus the sidebar when it's opened on startup or in a new
+ * window, only when the user opens the sidebar.
+ */
+function fireSidebarFocusedEvent() {
+ var sidebar = document.getElementById("sidebar");
+ var event = document.createEvent("Events");
+ event.initEvent("SidebarFocused", true, false);
+ sidebar.contentWindow.dispatchEvent(event);
+}
+
+var gHomeButton = {
+ prefDomain: "browser.startup.homepage",
+ observe: function (aSubject, aTopic, aPrefName)
+ {
+ if (aTopic != "nsPref:changed" || aPrefName != this.prefDomain)
+ return;
+
+ this.updateTooltip();
+ },
+
+ updateTooltip: function (homeButton)
+ {
+ if (!homeButton)
+ homeButton = document.getElementById("home-button");
+ if (homeButton) {
+ var homePage = this.getHomePage();
+ homePage = homePage.replace(/\|/g,', ');
+ if (homePage.toLowerCase() == "about:home")
+ homeButton.setAttribute("tooltiptext", homeButton.getAttribute("aboutHomeOverrideTooltip"));
+ else
+ homeButton.setAttribute("tooltiptext", homePage);
+ }
+ },
+
+ getHomePage: function ()
+ {
+ var url;
+ try {
+ url = gPrefService.getComplexValue(this.prefDomain,
+ Components.interfaces.nsIPrefLocalizedString).data;
+ } catch (e) {
+ }
+
+ // use this if we can't find the pref
+ if (!url) {
+ var configBundle = Services.strings
+ .createBundle("chrome://branding/locale/browserconfig.properties");
+ url = configBundle.GetStringFromName(this.prefDomain);
+ }
+
+ return url;
+ },
+
+ updatePersonalToolbarStyle: function (homeButton)
+ {
+ if (!homeButton)
+ homeButton = document.getElementById("home-button");
+ if (homeButton)
+ homeButton.className = homeButton.parentNode.id == "PersonalToolbar"
+ || homeButton.parentNode.parentNode.id == "PersonalToolbar" ?
+ homeButton.className.replace("toolbarbutton-1", "bookmark-item") :
+ homeButton.className.replace("bookmark-item", "toolbarbutton-1");
+ }
+};
+
+/**
+ * Gets the selected text in the active browser. Leading and trailing
+ * whitespace is removed, and consecutive whitespace is replaced by a single
+ * space. A maximum of 150 characters will be returned, regardless of the value
+ * of aCharLen.
+ *
+ * @param aCharLen
+ * The maximum number of characters to return.
+ */
+function getBrowserSelection(aCharLen) {
+ // selections of more than 150 characters aren't useful
+ const kMaxSelectionLen = 150;
+ const charLen = Math.min(aCharLen || kMaxSelectionLen, kMaxSelectionLen);
+ let commandDispatcher = document.commandDispatcher;
+
+ var focusedWindow = commandDispatcher.focusedWindow;
+ var selection = focusedWindow.getSelection().toString();
+ // try getting a selected text in text input.
+ if (!selection) {
+ let element = commandDispatcher.focusedElement;
+ var isOnTextInput = function isOnTextInput(elem) {
+ // we avoid to return a value if a selection is in password field.
+ // ref. bug 565717
+ return elem instanceof HTMLTextAreaElement ||
+ (elem instanceof HTMLInputElement && elem.mozIsTextField(true));
+ };
+
+ if (isOnTextInput(element)) {
+ selection = element.QueryInterface(Ci.nsIDOMNSEditableElement)
+ .editor.selection.toString();
+ }
+ }
+
+ if (selection) {
+ if (selection.length > charLen) {
+ // only use the first charLen important chars. see bug 221361
+ var pattern = new RegExp("^(?:\\s*.){0," + charLen + "}");
+ pattern.test(selection);
+ selection = RegExp.lastMatch;
+ }
+
+ selection = selection.trim().replace(/\s+/g, " ");
+
+ if (selection.length > charLen)
+ selection = selection.substr(0, charLen);
+ }
+ return selection;
+}
+
+var gWebPanelURI;
+function openWebPanel(aTitle, aURI)
+{
+ // Ensure that the web panels sidebar is open.
+ toggleSidebar('viewWebPanelsSidebar', true);
+
+ // Set the title of the panel.
+ document.getElementById("sidebar-title").value = aTitle;
+
+ // Tell the Web Panels sidebar to load the bookmark.
+ var sidebar = document.getElementById("sidebar");
+ if (sidebar.docShell && sidebar.contentDocument && sidebar.contentDocument.getElementById('web-panels-browser')) {
+ sidebar.contentWindow.loadWebPanel(aURI);
+ if (gWebPanelURI) {
+ gWebPanelURI = "";
+ sidebar.removeEventListener("load", asyncOpenWebPanel, true);
+ }
+ }
+ else {
+ // The panel is still being constructed. Attach an onload handler.
+ if (!gWebPanelURI)
+ sidebar.addEventListener("load", asyncOpenWebPanel, true);
+ gWebPanelURI = aURI;
+ }
+}
+
+function asyncOpenWebPanel(event)
+{
+ var sidebar = document.getElementById("sidebar");
+ if (gWebPanelURI && sidebar.contentDocument && sidebar.contentDocument.getElementById('web-panels-browser'))
+ sidebar.contentWindow.loadWebPanel(gWebPanelURI);
+ gWebPanelURI = "";
+ sidebar.removeEventListener("load", asyncOpenWebPanel, true);
+}
+
+/*
+ * - [ Dependencies ] ---------------------------------------------------------
+ * utilityOverlay.js:
+ * - gatherTextUnder
+ */
+
+/**
+ * Extracts linkNode and href for the current click target.
+ *
+ * @param event
+ * The click event.
+ * @return [href, linkNode].
+ *
+ * @note linkNode will be null if the click wasn't on an anchor
+ * element (or XLink).
+ */
+function hrefAndLinkNodeForClickEvent(event)
+{
+ function isHTMLLink(aNode)
+ {
+ // Be consistent with what nsContextMenu.js does.
+ return ((aNode instanceof HTMLAnchorElement && aNode.href) ||
+ (aNode instanceof HTMLAreaElement && aNode.href) ||
+ aNode instanceof HTMLLinkElement);
+ }
+
+ let node = event.target;
+ while (node && !isHTMLLink(node)) {
+ node = node.parentNode;
+ }
+
+ if (node)
+ return [node.href, node];
+
+ // If there is no linkNode, try simple XLink.
+ let href, baseURI;
+ node = event.target;
+ while (node && !href) {
+ if (node.nodeType == Node.ELEMENT_NODE &&
+ (node.localName == "a" ||
+ node.namespaceURI == "http://www.w3.org/1998/Math/MathML")) {
+ href = node.getAttributeNS("http://www.w3.org/1999/xlink", "href");
+ if (href) {
+ baseURI = node.baseURI;
+ break;
+ }
+ }
+ node = node.parentNode;
+ }
+
+ // In case of XLink, we don't return the node we got href from since
+ // callers expect <a>-like elements.
+ return [href ? makeURLAbsolute(baseURI, href) : null, null];
+}
+
+/**
+ * Called whenever the user clicks in the content area.
+ *
+ * @param event
+ * The click event.
+ * @param isPanelClick
+ * Whether the event comes from a web panel.
+ * @note default event is prevented if the click is handled.
+ */
+function contentAreaClick(event, isPanelClick)
+{
+ if (!event.isTrusted || event.defaultPrevented || event.button == 2)
+ return;
+
+ let [href, linkNode] = hrefAndLinkNodeForClickEvent(event);
+ if (!href) {
+ // Not a link, handle middle mouse navigation.
+ if (event.button == 1 &&
+ gPrefService.getBoolPref("middlemouse.contentLoadURL") &&
+ !gPrefService.getBoolPref("general.autoScroll")) {
+ middleMousePaste(event);
+ event.preventDefault();
+ }
+ return;
+ }
+
+ // This code only applies if we have a linkNode (i.e. clicks on real anchor
+ // elements, as opposed to XLink).
+ if (linkNode && event.button == 0 &&
+ !event.ctrlKey && !event.shiftKey && !event.altKey && !event.metaKey) {
+ // A Web panel's links should target the main content area. Do this
+ // if no modifier keys are down and if there's no target or the target
+ // equals _main (the IE convention) or _content (the Mozilla convention).
+ let target = linkNode.target;
+ let mainTarget = !target || target == "_content" || target == "_main";
+ if (isPanelClick && mainTarget) {
+ // javascript and data links should be executed in the current browser.
+ if (linkNode.getAttribute("onclick") ||
+ href.startsWith("javascript:") ||
+ href.startsWith("data:"))
+ return;
+
+ try {
+ urlSecurityCheck(href, linkNode.ownerDocument.nodePrincipal);
+ }
+ catch(ex) {
+ // Prevent loading unsecure destinations.
+ event.preventDefault();
+ return;
+ }
+
+ loadURI(href, null, null, false);
+ event.preventDefault();
+ return;
+ }
+
+ if (linkNode.getAttribute("rel") == "sidebar") {
+ // This is the Opera convention for a special link that, when clicked,
+ // allows to add a sidebar panel. The link's title attribute contains
+ // the title that should be used for the sidebar panel.
+ PlacesUIUtils.showBookmarkDialog({ action: "add"
+ , type: "bookmark"
+ , uri: makeURI(href)
+ , title: linkNode.getAttribute("title")
+ , loadBookmarkInSidebar: true
+ , hiddenRows: [ "description"
+ , "location"
+ , "keyword" ]
+ }, window);
+ event.preventDefault();
+ return;
+ }
+ }
+
+ handleLinkClick(event, href, linkNode);
+
+ // Mark the page as a user followed link. This is done so that history can
+ // distinguish automatic embed visits from user activated ones. For example
+ // pages loaded in frames are embed visits and lost with the session, while
+ // visits across frames should be preserved.
+ try {
+ if (!PrivateBrowsingUtils.isWindowPrivate(window))
+ PlacesUIUtils.markPageAsFollowedLink(href);
+ } catch (ex) { /* Skip invalid URIs. */ }
+}
+
+/**
+ * Handles clicks on links.
+ *
+ * @return true if the click event was handled, false otherwise.
+ */
+function handleLinkClick(event, href, linkNode) {
+ if (event.button == 2) // right click
+ return false;
+
+ var where = whereToOpenLink(event);
+ if (where == "current")
+ return false;
+
+ var doc = event.target.ownerDocument;
+
+ if (where == "save") {
+ saveURL(href, linkNode ? gatherTextUnder(linkNode) : "", null, true,
+ true, doc.documentURIObject, doc);
+ event.preventDefault();
+ return true;
+ }
+
+ urlSecurityCheck(href, doc.nodePrincipal);
+ openLinkIn(href, where, { referrerURI: doc.documentURIObject,
+ charset: doc.characterSet,
+ referrerPolicy: doc.referrerPolicy,
+ originPrincipal: doc.nodePrincipal,
+ triggeringPrincipal: doc.nodePrincipal });
+ event.preventDefault();
+ return true;
+}
+
+function middleMousePaste(event) {
+ let clipboard = readFromClipboard();
+ if (!clipboard)
+ return;
+
+ // Strip embedded newlines and surrounding whitespace, to match the URL
+ // bar's behavior (stripsurroundingwhitespace)
+ clipboard = clipboard.replace(/\s*\n\s*/g, "");
+
+ // if it's not the current tab, we don't need to do anything because the
+ // browser doesn't exist.
+ let where = whereToOpenLink(event, true, false);
+ let lastLocationChange;
+ if (where == "current") {
+ lastLocationChange = gBrowser.selectedBrowser.lastLocationChange;
+ }
+
+ getShortcutOrURIAndPostData(clipboard).then(data => {
+ try {
+ makeURI(data.url);
+ } catch (ex) {
+ // Not a valid URI.
+ return;
+ }
+
+ try {
+ addToUrlbarHistory(data.url);
+ } catch (ex) {
+ // Things may go wrong when adding url to session history,
+ // but don't let that interfere with the loading of the url.
+ Cu.reportError(ex);
+ }
+
+ if (where != "current" ||
+ lastLocationChange == gBrowser.selectedBrowser.lastLocationChange) {
+ openUILink(data.url, event,
+ { ignoreButton: true,
+ disallowInheritPrincipal: !data.mayInheritPrincipal,
+ initiatingDoc: event ? event.target.ownerDocument : null });
+ }
+ });
+
+ event.stopPropagation();
+}
+
+// handleDroppedLink has the following 2 overloads:
+// handleDroppedLink(event, url, name)
+// handleDroppedLink(event, links)
+function handleDroppedLink(event, urlOrLinks, name)
+{
+ let links;
+ if (Array.isArray(urlOrLinks)) {
+ links = urlOrLinks;
+ } else {
+ links = [{ url: urlOrLinks, name, type: "" }];
+ }
+
+ let lastLocationChange = gBrowser.selectedBrowser.lastLocationChange;
+
+ let inBackground = Services.prefs.getBoolPref("browser.tabs.loadInBackground");
+ if (event.shiftKey)
+ inBackground = !inBackground;
+
+ Task.spawn(function*() {
+ let urls = [];
+ let postDatas = [];
+ for (let link of links) {
+ let data = yield getShortcutOrURIAndPostData(link.url);
+ urls.push(data.url);
+ postDatas.push(data.postData);
+ }
+ if (lastLocationChange == gBrowser.selectedBrowser.lastLocationChange) {
+ gBrowser.loadTabs(urls, {
+ inBackground,
+ replace: true,
+ allowThirdPartyFixup: false,
+ postDatas,
+ });
+ }
+ });
+
+ // Keep the event from being handled by the dragDrop listeners
+ // built-in to goanna if they happen to be above us.
+ event.preventDefault();
+};
+
+function MultiplexHandler(event)
+{ try {
+ var node = event.target;
+ var name = node.getAttribute('name');
+
+ if (name == 'detectorGroup') {
+ BrowserCharsetReload();
+ SelectDetector(event, false);
+ } else if (name == 'charsetGroup') {
+ var charset = node.getAttribute('id');
+ charset = charset.substring(charset.indexOf('charset.') + 'charset.'.length);
+ BrowserSetForcedCharacterSet(charset);
+ } else if (name == 'charsetCustomize') {
+ //do nothing - please remove this else statement, once the charset prefs moves to the pref window
+ } else {
+ BrowserSetForcedCharacterSet(node.getAttribute('id'));
+ }
+ } catch(ex) { alert(ex); }
+}
+
+function SelectDetector(event, doReload)
+{
+ var uri = event.target.getAttribute("id");
+ var prefvalue = uri.substring(uri.indexOf('chardet.') + 'chardet.'.length);
+ if ("off" == prefvalue) { // "off" is special value to turn off the detectors
+ prefvalue = "";
+ }
+
+ try {
+ var str = Cc["@mozilla.org/supports-string;1"].
+ createInstance(Ci.nsISupportsString);
+
+ str.data = prefvalue;
+ gPrefService.setComplexValue("intl.charset.detector", Ci.nsISupportsString, str);
+ if (doReload)
+ window.content.location.reload();
+ }
+ catch (ex) {
+ dump("Failed to set the intl.charset.detector preference.\n");
+ }
+}
+
+function BrowserSetForcedCharacterSet(aCharset)
+{
+ gBrowser.docShell.charset = aCharset;
+ // Save the forced character-set
+ if (!PrivateBrowsingUtils.isWindowPrivate(window))
+ PlacesUtils.setCharsetForURI(getWebNavigation().currentURI, aCharset);
+ BrowserCharsetReload();
+}
+
+function BrowserCharsetReload()
+{
+ BrowserReloadWithFlags(nsIWebNavigation.LOAD_FLAGS_CHARSET_CHANGE);
+}
+
+function charsetMenuGetElement(parent, id) {
+ return parent.getElementsByAttribute("id", id)[0];
+}
+
+function UpdateCurrentCharset(target) {
+ // extract the charset from DOM
+ var wnd = document.commandDispatcher.focusedWindow;
+ if ((window == wnd) || (wnd == null)) wnd = window.content;
+
+ // Uncheck previous item
+ if (gPrevCharset) {
+ var pref_item = charsetMenuGetElement(target, "charset." + gPrevCharset);
+ if (pref_item)
+ pref_item.setAttribute('checked', 'false');
+ }
+
+ var menuitem = charsetMenuGetElement(target, "charset." + wnd.document.characterSet);
+ if (menuitem) {
+ menuitem.setAttribute('checked', 'true');
+ }
+}
+
+function UpdateCharsetDetector(target) {
+ var prefvalue;
+
+ try {
+ prefvalue = gPrefService.getComplexValue("intl.charset.detector", Ci.nsIPrefLocalizedString).data;
+ }
+ catch (ex) {}
+
+ if (!prefvalue)
+ prefvalue = "off";
+
+ var menuitem = charsetMenuGetElement(target, "chardet." + prefvalue);
+ if (menuitem)
+ menuitem.setAttribute("checked", "true");
+}
+
+function UpdateMenus(event) {
+ UpdateCurrentCharset(event.target);
+ UpdateCharsetDetector(event.target);
+}
+
+function charsetLoadListener() {
+ var charset = window.content.document.characterSet;
+
+ if (charset.length > 0 && (charset != gLastBrowserCharset)) {
+ gPrevCharset = gLastBrowserCharset;
+ gLastBrowserCharset = charset;
+ }
+}
+
+var gPageStyleMenu = {
+
+ _getAllStyleSheets: function (frameset) {
+ var styleSheetsArray = Array.slice(frameset.document.styleSheets);
+ for (let i = 0; i < frameset.frames.length; i++) {
+ let frameSheets = this._getAllStyleSheets(frameset.frames[i]);
+ styleSheetsArray = styleSheetsArray.concat(frameSheets);
+ }
+ return styleSheetsArray;
+ },
+
+ fillPopup: function (menuPopup) {
+ var noStyle = menuPopup.firstChild;
+ var persistentOnly = noStyle.nextSibling;
+ var sep = persistentOnly.nextSibling;
+ while (sep.nextSibling)
+ menuPopup.removeChild(sep.nextSibling);
+
+ var styleSheets = this._getAllStyleSheets(window.content);
+ var currentStyleSheets = {};
+ var styleDisabled = getMarkupDocumentViewer().authorStyleDisabled;
+ var haveAltSheets = false;
+ var altStyleSelected = false;
+
+ for (let currentStyleSheet of styleSheets) {
+ if (!currentStyleSheet.title)
+ continue;
+
+ // Skip any stylesheets whose media attribute doesn't match.
+ if (currentStyleSheet.media.length > 0) {
+ let mediaQueryList = currentStyleSheet.media.mediaText;
+ if (!window.content.matchMedia(mediaQueryList).matches)
+ continue;
+ }
+
+ if (!currentStyleSheet.disabled)
+ altStyleSelected = true;
+
+ haveAltSheets = true;
+
+ let lastWithSameTitle = null;
+ if (currentStyleSheet.title in currentStyleSheets)
+ lastWithSameTitle = currentStyleSheets[currentStyleSheet.title];
+
+ if (!lastWithSameTitle) {
+ let menuItem = document.createElement("menuitem");
+ menuItem.setAttribute("type", "radio");
+ menuItem.setAttribute("label", currentStyleSheet.title);
+ menuItem.setAttribute("data", currentStyleSheet.title);
+ menuItem.setAttribute("checked", !currentStyleSheet.disabled && !styleDisabled);
+ menuItem.setAttribute("oncommand", "gPageStyleMenu.switchStyleSheet(this.getAttribute('data'));");
+ menuPopup.appendChild(menuItem);
+ currentStyleSheets[currentStyleSheet.title] = menuItem;
+ } else if (currentStyleSheet.disabled) {
+ lastWithSameTitle.removeAttribute("checked");
+ }
+ }
+
+ noStyle.setAttribute("checked", styleDisabled);
+ persistentOnly.setAttribute("checked", !altStyleSelected && !styleDisabled);
+ persistentOnly.hidden = (window.content.document.preferredStyleSheetSet) ? haveAltSheets : false;
+ sep.hidden = (noStyle.hidden && persistentOnly.hidden) || !haveAltSheets;
+ },
+
+ _stylesheetInFrame: function (frame, title) {
+ return Array.some(frame.document.styleSheets,
+ function (stylesheet) stylesheet.title == title);
+ },
+
+ _stylesheetSwitchFrame: function (frame, title) {
+ var docStyleSheets = frame.document.styleSheets;
+
+ for (let i = 0; i < docStyleSheets.length; ++i) {
+ let docStyleSheet = docStyleSheets[i];
+
+ if (docStyleSheet.title)
+ docStyleSheet.disabled = (docStyleSheet.title != title);
+ else if (docStyleSheet.disabled)
+ docStyleSheet.disabled = false;
+ }
+ },
+
+ _stylesheetSwitchAll: function (frameset, title) {
+ if (!title || this._stylesheetInFrame(frameset, title))
+ this._stylesheetSwitchFrame(frameset, title);
+
+ for (let i = 0; i < frameset.frames.length; i++)
+ this._stylesheetSwitchAll(frameset.frames[i], title);
+ },
+
+ switchStyleSheet: function (title, contentWindow) {
+ getMarkupDocumentViewer().authorStyleDisabled = false;
+ this._stylesheetSwitchAll(contentWindow || content, title);
+ },
+
+ disableStyle: function () {
+ getMarkupDocumentViewer().authorStyleDisabled = true;
+ },
+};
+
+/* Legacy global page-style functions */
+var getAllStyleSheets = gPageStyleMenu._getAllStyleSheets.bind(gPageStyleMenu);
+var stylesheetFillPopup = gPageStyleMenu.fillPopup.bind(gPageStyleMenu);
+function stylesheetSwitchAll(contentWindow, title) {
+ gPageStyleMenu.switchStyleSheet(title, contentWindow);
+}
+function setStyleDisabled(disabled) {
+ if (disabled)
+ gPageStyleMenu.disableStyle();
+}
+
+
+var BrowserOffline = {
+ _inited: false,
+
+ /////////////////////////////////////////////////////////////////////////////
+ // BrowserOffline Public Methods
+ init: function ()
+ {
+ if (!this._uiElement)
+ this._uiElement = document.getElementById("workOfflineMenuitemState");
+
+ Services.obs.addObserver(this, "network:offline-status-changed", false);
+
+ this._updateOfflineUI(Services.io.offline);
+
+ this._inited = true;
+ },
+
+ uninit: function ()
+ {
+ if (this._inited) {
+ Services.obs.removeObserver(this, "network:offline-status-changed");
+ }
+ },
+
+ toggleOfflineStatus: function ()
+ {
+ var ioService = Services.io;
+
+ // Stop automatic management of the offline status
+ try {
+ ioService.manageOfflineStatus = false;
+ } catch (ex) {
+ }
+
+ if (!ioService.offline && !this._canGoOffline()) {
+ this._updateOfflineUI(false);
+ return;
+ }
+
+ ioService.offline = !ioService.offline;
+ },
+
+ /////////////////////////////////////////////////////////////////////////////
+ // nsIObserver
+ observe: function (aSubject, aTopic, aState)
+ {
+ if (aTopic != "network:offline-status-changed")
+ return;
+
+ this._updateOfflineUI(aState == "offline");
+ },
+
+ /////////////////////////////////////////////////////////////////////////////
+ // BrowserOffline Implementation Methods
+ _canGoOffline: function ()
+ {
+ try {
+ var cancelGoOffline = Cc["@mozilla.org/supports-PRBool;1"].createInstance(Ci.nsISupportsPRBool);
+ Services.obs.notifyObservers(cancelGoOffline, "offline-requested", null);
+
+ // Something aborted the quit process.
+ if (cancelGoOffline.data)
+ return false;
+ }
+ catch (ex) {
+ }
+
+ return true;
+ },
+
+ _uiElement: null,
+ _updateOfflineUI: function (aOffline)
+ {
+ var offlineLocked = gPrefService.prefIsLocked("network.online");
+ if (offlineLocked)
+ this._uiElement.setAttribute("disabled", "true");
+
+ this._uiElement.setAttribute("checked", aOffline);
+ }
+};
+
+var OfflineApps = {
+ /////////////////////////////////////////////////////////////////////////////
+ // OfflineApps Public Methods
+ init: function ()
+ {
+ Services.obs.addObserver(this, "offline-cache-update-completed", false);
+ },
+
+ uninit: function ()
+ {
+ Services.obs.removeObserver(this, "offline-cache-update-completed");
+ },
+
+ handleEvent: function(event) {
+ if (event.type == "MozApplicationManifest") {
+ this.offlineAppRequested(event.originalTarget.defaultView);
+ }
+ },
+
+ /////////////////////////////////////////////////////////////////////////////
+ // OfflineApps Implementation Methods
+
+ // XXX: _getBrowserWindowForContentWindow and _getBrowserForContentWindow
+ // were taken from browser/components/feeds/src/WebContentConverter.
+ _getBrowserWindowForContentWindow: function(aContentWindow) {
+ return aContentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIDocShellTreeItem)
+ .rootTreeItem
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindow)
+ .wrappedJSObject;
+ },
+
+ _getBrowserForContentWindow: function(aBrowserWindow, aContentWindow) {
+ // This depends on pseudo APIs of browser.js and tabbrowser.xml
+ aContentWindow = aContentWindow.top;
+ var browsers = aBrowserWindow.gBrowser.browsers;
+ for (let browser of browsers) {
+ if (browser.contentWindow == aContentWindow)
+ return browser;
+ }
+ // handle other browser/iframe elements that may need popupnotifications
+ let browser = aContentWindow
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIDocShell)
+ .chromeEventHandler;
+ if (browser.getAttribute("popupnotificationanchor"))
+ return browser;
+ return null;
+ },
+
+ _getManifestURI: function(aWindow) {
+ if (!aWindow.document.documentElement)
+ return null;
+
+ var attr = aWindow.document.documentElement.getAttribute("manifest");
+ if (!attr)
+ return null;
+
+ try {
+ var contentURI = makeURI(aWindow.location.href, null, null);
+ return makeURI(attr, aWindow.document.characterSet, contentURI);
+ } catch (e) {
+ return null;
+ }
+ },
+
+ // A cache update isn't tied to a specific window. Try to find
+ // the best browser in which to warn the user about space usage
+ _getBrowserForCacheUpdate: function(aCacheUpdate) {
+ // Prefer the current browser
+ var uri = this._getManifestURI(content);
+ if (uri && uri.equals(aCacheUpdate.manifestURI)) {
+ return gBrowser.selectedBrowser;
+ }
+
+ var browsers = gBrowser.browsers;
+ for (let browser of browsers) {
+ uri = this._getManifestURI(browser.contentWindow);
+ if (uri && uri.equals(aCacheUpdate.manifestURI)) {
+ return browser;
+ }
+ }
+
+ // is this from a non-tab browser/iframe?
+ browsers = document.querySelectorAll("iframe[popupnotificationanchor] | browser[popupnotificationanchor]");
+ for (let browser of browsers) {
+ uri = this._getManifestURI(browser.contentWindow);
+ if (uri && uri.equals(aCacheUpdate.manifestURI)) {
+ return browser;
+ }
+ }
+
+ return null;
+ },
+
+ _warnUsage: function(aBrowser, aURI) {
+ if (!aBrowser)
+ return;
+
+ let mainAction = {
+ label: gNavigatorBundle.getString("offlineApps.manageUsage"),
+ accessKey: gNavigatorBundle.getString("offlineApps.manageUsageAccessKey"),
+ callback: OfflineApps.manage
+ };
+
+ let warnQuota = gPrefService.getIntPref("offline-apps.quota.warn");
+ let message = gNavigatorBundle.getFormattedString("offlineApps.usage",
+ [ aURI.host,
+ warnQuota / 1024 ]);
+
+ let anchorID = "indexedDB-notification-icon";
+ PopupNotifications.show(aBrowser, "offline-app-usage", message,
+ anchorID, mainAction);
+
+ // Now that we've warned once, prevent the warning from showing up
+ // again.
+ Services.perms.add(aURI, "offline-app",
+ Ci.nsIOfflineCacheUpdateService.ALLOW_NO_WARN);
+ },
+
+ // XXX: duplicated in preferences/advanced.js
+ _getOfflineAppUsage: function (host, groups)
+ {
+ var cacheService = Cc["@mozilla.org/network/application-cache-service;1"].
+ getService(Ci.nsIApplicationCacheService);
+ if (!groups)
+ groups = cacheService.getGroups();
+
+ var usage = 0;
+ for (let group of groups) {
+ var uri = Services.io.newURI(group, null, null);
+ if (uri.asciiHost == host) {
+ var cache = cacheService.getActiveCache(group);
+ usage += cache.usage;
+ }
+ }
+
+ return usage;
+ },
+
+ _checkUsage: function(aURI) {
+ // if the user has already allowed excessive usage, don't bother checking
+ if (Services.perms.testExactPermission(aURI, "offline-app") !=
+ Ci.nsIOfflineCacheUpdateService.ALLOW_NO_WARN) {
+ var usage = this._getOfflineAppUsage(aURI.asciiHost);
+ var warnQuota = gPrefService.getIntPref("offline-apps.quota.warn");
+ if (usage >= warnQuota * 1024) {
+ return true;
+ }
+ }
+
+ return false;
+ },
+
+ offlineAppRequested: function(aContentWindow) {
+ if (!gPrefService.getBoolPref("browser.offline-apps.notify")) {
+ return;
+ }
+
+ let browserWindow = this._getBrowserWindowForContentWindow(aContentWindow);
+ let browser = this._getBrowserForContentWindow(browserWindow,
+ aContentWindow);
+
+ let currentURI = aContentWindow.document.documentURIObject;
+
+ // don't bother showing UI if the user has already made a decision
+ if (Services.perms.testExactPermission(currentURI, "offline-app") != Services.perms.UNKNOWN_ACTION)
+ return;
+
+ try {
+ if (gPrefService.getBoolPref("offline-apps.allow_by_default")) {
+ // all pages can use offline capabilities, no need to ask the user
+ return;
+ }
+ } catch(e) {
+ // this pref isn't set by default, ignore failures
+ }
+
+ let host = currentURI.asciiHost;
+ let notificationID = "offline-app-requested-" + host;
+ let notification = PopupNotifications.getNotification(notificationID, browser);
+
+ if (notification) {
+ notification.options.documents.push(aContentWindow.document);
+ } else {
+ let mainAction = {
+ label: gNavigatorBundle.getString("offlineApps.allow"),
+ accessKey: gNavigatorBundle.getString("offlineApps.allowAccessKey"),
+ callback: function() {
+ for (let document of notification.options.documents) {
+ OfflineApps.allowSite(document);
+ }
+ }
+ };
+ let secondaryActions = [{
+ label: gNavigatorBundle.getString("offlineApps.never"),
+ accessKey: gNavigatorBundle.getString("offlineApps.neverAccessKey"),
+ callback: function() {
+ for (let document of notification.options.documents) {
+ OfflineApps.disallowSite(document);
+ }
+ }
+ }];
+ let message = gNavigatorBundle.getFormattedString("offlineApps.available",
+ [ host ]);
+ let anchorID = "indexedDB-notification-icon";
+ let options= {
+ documents : [ aContentWindow.document ]
+ };
+ notification = PopupNotifications.show(browser, notificationID, message,
+ anchorID, mainAction,
+ secondaryActions, options);
+ }
+ },
+
+ allowSite: function(aDocument) {
+ Services.perms.add(aDocument.documentURIObject, "offline-app", Services.perms.ALLOW_ACTION);
+
+ // When a site is enabled while loading, manifest resources will
+ // start fetching immediately. This one time we need to do it
+ // ourselves.
+ this._startFetching(aDocument);
+ },
+
+ disallowSite: function(aDocument) {
+ Services.perms.add(aDocument.documentURIObject, "offline-app", Services.perms.DENY_ACTION);
+ },
+
+ manage: function() {
+ openAdvancedPreferences("networkTab");
+ },
+
+ _startFetching: function(aDocument) {
+ if (!aDocument.documentElement)
+ return;
+
+ var manifest = aDocument.documentElement.getAttribute("manifest");
+ if (!manifest)
+ return;
+
+ var manifestURI = makeURI(manifest, aDocument.characterSet,
+ aDocument.documentURIObject);
+
+ var updateService = Cc["@mozilla.org/offlinecacheupdate-service;1"].
+ getService(Ci.nsIOfflineCacheUpdateService);
+ updateService.scheduleUpdate(manifestURI, aDocument.documentURIObject, window);
+ },
+
+ /////////////////////////////////////////////////////////////////////////////
+ // nsIObserver
+ observe: function (aSubject, aTopic, aState)
+ {
+ if (aTopic == "offline-cache-update-completed") {
+ var cacheUpdate = aSubject.QueryInterface(Ci.nsIOfflineCacheUpdate);
+
+ var uri = cacheUpdate.manifestURI;
+ if (OfflineApps._checkUsage(uri)) {
+ var browser = this._getBrowserForCacheUpdate(cacheUpdate);
+ if (browser) {
+ OfflineApps._warnUsage(browser, cacheUpdate.manifestURI);
+ }
+ }
+ }
+ }
+};
+
+var IndexedDBPromptHelper = {
+ _permissionsPrompt: "indexedDB-permissions-prompt",
+ _permissionsResponse: "indexedDB-permissions-response",
+
+ _quotaPrompt: "indexedDB-quota-prompt",
+ _quotaResponse: "indexedDB-quota-response",
+ _quotaCancel: "indexedDB-quota-cancel",
+
+ _notificationIcon: "indexedDB-notification-icon",
+
+ init:
+ function IndexedDBPromptHelper_init() {
+ Services.obs.addObserver(this, this._permissionsPrompt, false);
+ Services.obs.addObserver(this, this._quotaPrompt, false);
+ Services.obs.addObserver(this, this._quotaCancel, false);
+ },
+
+ uninit:
+ function IndexedDBPromptHelper_uninit() {
+ Services.obs.removeObserver(this, this._permissionsPrompt);
+ Services.obs.removeObserver(this, this._quotaPrompt);
+ Services.obs.removeObserver(this, this._quotaCancel);
+ },
+
+ observe:
+ function IndexedDBPromptHelper_observe(subject, topic, data) {
+ if (topic != this._permissionsPrompt &&
+ topic != this._quotaPrompt &&
+ topic != this._quotaCancel) {
+ throw new Error("Unexpected topic!");
+ }
+
+ var requestor = subject.QueryInterface(Ci.nsIInterfaceRequestor);
+
+ var contentWindow = requestor.getInterface(Ci.nsIDOMWindow);
+ var contentDocument = contentWindow.document;
+ var browserWindow =
+ OfflineApps._getBrowserWindowForContentWindow(contentWindow);
+
+ if (browserWindow != window) {
+ // Must belong to some other window.
+ return;
+ }
+
+ var browser =
+ OfflineApps._getBrowserForContentWindow(browserWindow, contentWindow);
+
+ var host = contentDocument.documentURIObject.asciiHost;
+
+ var message;
+ var responseTopic;
+ if (topic == this._permissionsPrompt) {
+ message = gNavigatorBundle.getFormattedString("offlineApps.available",
+ [ host ]);
+ responseTopic = this._permissionsResponse;
+ }
+ else if (topic == this._quotaPrompt) {
+ message = gNavigatorBundle.getFormattedString("indexedDB.usage",
+ [ host, data ]);
+ responseTopic = this._quotaResponse;
+ }
+ else if (topic == this._quotaCancel) {
+ responseTopic = this._quotaResponse;
+ }
+
+ const hiddenTimeoutDuration = 30000; // 30 seconds
+ const firstTimeoutDuration = 300000; // 5 minutes
+
+ var timeoutId;
+
+ var observer = requestor.getInterface(Ci.nsIObserver);
+
+ var mainAction = {
+ label: gNavigatorBundle.getString("offlineApps.allow"),
+ accessKey: gNavigatorBundle.getString("offlineApps.allowAccessKey"),
+ callback: function() {
+ clearTimeout(timeoutId);
+ observer.observe(null, responseTopic,
+ Ci.nsIPermissionManager.ALLOW_ACTION);
+ }
+ };
+
+ var secondaryActions = [
+ {
+ label: gNavigatorBundle.getString("offlineApps.never"),
+ accessKey: gNavigatorBundle.getString("offlineApps.neverAccessKey"),
+ callback: function() {
+ clearTimeout(timeoutId);
+ observer.observe(null, responseTopic,
+ Ci.nsIPermissionManager.DENY_ACTION);
+ }
+ }
+ ];
+
+ // This will be set to the result of PopupNotifications.show() below, or to
+ // the result of PopupNotifications.getNotification() if this is a
+ // quotaCancel notification.
+ var notification;
+
+ function timeoutNotification() {
+ // Remove the notification.
+ if (notification) {
+ notification.remove();
+ }
+
+ // Clear all of our timeout stuff. We may be called directly, not just
+ // when the timeout actually elapses.
+ clearTimeout(timeoutId);
+
+ // And tell the page that the popup timed out.
+ observer.observe(null, responseTopic,
+ Ci.nsIPermissionManager.UNKNOWN_ACTION);
+ }
+
+ var options = {
+ eventCallback: function(state) {
+ // Don't do anything if the timeout has not been set yet.
+ if (!timeoutId) {
+ return;
+ }
+
+ // If the popup is being dismissed start the short timeout.
+ if (state == "dismissed") {
+ clearTimeout(timeoutId);
+ timeoutId = setTimeout(timeoutNotification, hiddenTimeoutDuration);
+ return;
+ }
+
+ // If the popup is being re-shown then clear the timeout allowing
+ // unlimited waiting.
+ if (state == "shown") {
+ clearTimeout(timeoutId);
+ }
+ }
+ };
+
+ if (topic == this._quotaCancel) {
+ notification = PopupNotifications.getNotification(this._quotaPrompt,
+ browser);
+ timeoutNotification();
+ return;
+ }
+
+ notification = PopupNotifications.show(browser, topic, message,
+ this._notificationIcon, mainAction,
+ secondaryActions, options);
+
+ // Set the timeoutId after the popup has been created, and use the long
+ // timeout value. If the user doesn't notice the popup after this amount of
+ // time then it is most likely not visible and we want to alert the page.
+ timeoutId = setTimeout(timeoutNotification, firstTimeoutDuration);
+ }
+};
+
+function WindowIsClosing()
+{
+ let event = document.createEvent("Events");
+ event.initEvent("WindowIsClosing", true, true);
+ if (!window.dispatchEvent(event))
+ return false;
+
+ if (!closeWindow(false, warnAboutClosingWindow))
+ return false;
+
+ for (let browser of gBrowser.browsers) {
+ let ds = browser.docShell;
+ if (ds.contentViewer && !ds.contentViewer.permitUnload())
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * Checks if this is the last full *browser* window around. If it is, this will
+ * be communicated like quitting. Otherwise, we warn about closing multiple tabs.
+ * @returns true if closing can proceed, false if it got cancelled.
+ */
+function warnAboutClosingWindow() {
+ // Popups aren't considered full browser windows.
+ let isPBWindow = PrivateBrowsingUtils.isWindowPrivate(window);
+ if (!isPBWindow && !toolbar.visible)
+ return gBrowser.warnAboutClosingTabs(gBrowser.closingTabsEnum.ALL);
+
+ // Figure out if there's at least one other browser window around.
+ let e = Services.wm.getEnumerator("navigator:browser");
+ let otherPBWindowExists = false;
+ let nonPopupPresent = false;
+ while (e.hasMoreElements()) {
+ let win = e.getNext();
+ if (win != window) {
+ if (isPBWindow && PrivateBrowsingUtils.isWindowPrivate(win))
+ otherPBWindowExists = true;
+ if (win.toolbar.visible)
+ nonPopupPresent = true;
+ // If the current window is not in private browsing mode we don't need to
+ // look for other pb windows, we can leave the loop when finding the
+ // first non-popup window. If however the current window is in private
+ // browsing mode then we need at least one other pb and one non-popup
+ // window to break out early.
+ if ((!isPBWindow || otherPBWindowExists) && nonPopupPresent)
+ break;
+ }
+ }
+
+ if (isPBWindow && !otherPBWindowExists) {
+ let exitingCanceled = Cc["@mozilla.org/supports-PRBool;1"].
+ createInstance(Ci.nsISupportsPRBool);
+ exitingCanceled.data = false;
+ Services.obs.notifyObservers(exitingCanceled,
+ "last-pb-context-exiting",
+ null);
+ if (exitingCanceled.data)
+ return false;
+ }
+
+ if (nonPopupPresent) {
+ return isPBWindow || gBrowser.warnAboutClosingTabs(gBrowser.closingTabsEnum.ALL);
+ }
+
+ let os = Services.obs;
+
+ let closingCanceled = Cc["@mozilla.org/supports-PRBool;1"].
+ createInstance(Ci.nsISupportsPRBool);
+ os.notifyObservers(closingCanceled,
+ "browser-lastwindow-close-requested", null);
+ if (closingCanceled.data)
+ return false;
+
+ os.notifyObservers(null, "browser-lastwindow-close-granted", null);
+
+#ifdef XP_MACOSX
+ // OS X doesn't quit the application when the last window is closed, but keeps
+ // the session alive. Hence don't prompt users to save tabs, but warn about
+ // closing multiple tabs.
+ return isPBWindow || gBrowser.warnAboutClosingTabs(gBrowser.closingTabsEnum.ALL);
+#else
+ return true;
+#endif
+}
+
+var MailIntegration = {
+ sendLinkForWindow: function (aWindow) {
+ this.sendMessage(aWindow.location.href,
+ aWindow.document.title);
+ },
+
+ sendMessage: function (aBody, aSubject) {
+ // generate a mailto url based on the url and the url's title
+ var mailtoUrl = "mailto:";
+ if (aBody) {
+ mailtoUrl += "?body=" + encodeURIComponent(aBody);
+ mailtoUrl += "&subject=" + encodeURIComponent(aSubject);
+ }
+
+ var uri = makeURI(mailtoUrl);
+
+ // now pass this uri to the operating system
+ this._launchExternalUrl(uri);
+ },
+
+ // a generic method which can be used to pass arbitrary urls to the operating
+ // system.
+ // aURL --> a nsIURI which represents the url to launch
+ _launchExternalUrl: function (aURL) {
+ var extProtocolSvc =
+ Cc["@mozilla.org/uriloader/external-protocol-service;1"]
+ .getService(Ci.nsIExternalProtocolService);
+ if (extProtocolSvc)
+ extProtocolSvc.loadUrl(aURL);
+ }
+};
+
+function BrowserOpenAddonsMgr(aView) {
+ if (aView) {
+ let emWindow;
+ let browserWindow;
+
+ var receivePong = function receivePong(aSubject, aTopic, aData) {
+ let browserWin = aSubject.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIDocShellTreeItem)
+ .rootTreeItem
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindow);
+ if (!emWindow || browserWin == window /* favor the current window */) {
+ emWindow = aSubject;
+ browserWindow = browserWin;
+ }
+ }
+ Services.obs.addObserver(receivePong, "EM-pong", false);
+ Services.obs.notifyObservers(null, "EM-ping", "");
+ Services.obs.removeObserver(receivePong, "EM-pong");
+
+ if (emWindow) {
+ emWindow.loadView(aView);
+ browserWindow.gBrowser.selectedTab =
+ browserWindow.gBrowser._getTabForContentWindow(emWindow);
+ emWindow.focus();
+ return;
+ }
+ }
+
+ var newLoad = !switchToTabHavingURI("about:addons", true);
+
+ if (aView) {
+ // This must be a new load, else the ping/pong would have
+ // found the window above.
+ Services.obs.addObserver(function observer(aSubject, aTopic, aData) {
+ Services.obs.removeObserver(observer, aTopic);
+ aSubject.loadView(aView);
+ }, "EM-loaded", false);
+ }
+}
+
+function BrowserOpenPermissionsMgr() {
+ switchToTabHavingURI("about:permissions", true);
+}
+
+function AddKeywordForSearchField() {
+ var node = document.popupNode;
+
+ var charset = node.ownerDocument.characterSet;
+
+ var docURI = makeURI(node.ownerDocument.URL,
+ charset);
+
+ var formURI = makeURI(node.form.getAttribute("action"),
+ charset,
+ docURI);
+
+ var spec = formURI.spec;
+
+ var isURLEncoded =
+ (node.form.method.toUpperCase() == "POST"
+ && (node.form.enctype == "application/x-www-form-urlencoded" ||
+ node.form.enctype == ""));
+
+ var title = gNavigatorBundle.getFormattedString("addKeywordTitleAutoFill",
+ [node.ownerDocument.title]);
+ var description = PlacesUIUtils.getDescriptionFromDocument(node.ownerDocument);
+
+ var formData = [];
+
+ function escapeNameValuePair(aName, aValue, aIsFormUrlEncoded) {
+ if (aIsFormUrlEncoded)
+ return escape(aName + "=" + aValue);
+ else
+ return escape(aName) + "=" + escape(aValue);
+ }
+
+ for (let el of node.form.elements) {
+ if (!el.type) // happens with fieldsets
+ continue;
+
+ if (el == node) {
+ formData.push((isURLEncoded) ? escapeNameValuePair(el.name, "%s", true) :
+ // Don't escape "%s", just append
+ escapeNameValuePair(el.name, "", false) + "%s");
+ continue;
+ }
+
+ let type = el.type.toLowerCase();
+
+ if (((el instanceof HTMLInputElement && el.mozIsTextField(true)) ||
+ type == "hidden" || type == "textarea") ||
+ ((type == "checkbox" || type == "radio") && el.checked)) {
+ formData.push(escapeNameValuePair(el.name, el.value, isURLEncoded));
+ } else if (el instanceof HTMLSelectElement && el.selectedIndex >= 0) {
+ for (var j=0; j < el.options.length; j++) {
+ if (el.options[j].selected)
+ formData.push(escapeNameValuePair(el.name, el.options[j].value,
+ isURLEncoded));
+ }
+ }
+ }
+
+ var postData;
+
+ if (isURLEncoded)
+ postData = formData.join("&");
+ else
+ spec += "?" + formData.join("&");
+
+ PlacesUIUtils.showBookmarkDialog({ action: "add"
+ , type: "bookmark"
+ , uri: makeURI(spec)
+ , title: title
+ , description: description
+ , keyword: ""
+ , postData: postData
+ , charSet: charset
+ , hiddenRows: [ "location"
+ , "description"
+ , "tags"
+ , "loadInSidebar" ]
+ }, window);
+}
+
+function SwitchDocumentDirection(aWindow) {
+ // document.dir can also be "auto", in which case it won't change
+ if (aWindow.document.dir == "ltr" || aWindow.document.dir == "") {
+ aWindow.document.dir = "rtl";
+ } else if (aWindow.document.dir == "rtl") {
+ aWindow.document.dir = "ltr";
+ }
+ for (var run = 0; run < aWindow.frames.length; run++)
+ SwitchDocumentDirection(aWindow.frames[run]);
+}
+
+function convertFromUnicode(charset, str)
+{
+ try {
+ var unicodeConverter = Components
+ .classes["@mozilla.org/intl/scriptableunicodeconverter"]
+ .createInstance(Components.interfaces.nsIScriptableUnicodeConverter);
+ unicodeConverter.charset = charset;
+ str = unicodeConverter.ConvertFromUnicode(str);
+ return str + unicodeConverter.Finish();
+ } catch(ex) {
+ return null;
+ }
+}
+
+/**
+ * Re-open a closed tab.
+ * @param aIndex
+ * The index of the tab (via nsSessionStore.getClosedTabData)
+ * @returns a reference to the reopened tab.
+ */
+function undoCloseTab(aIndex) {
+ // wallpaper patch to prevent an unnecessary blank tab (bug 343895)
+ var blankTabToRemove = null;
+ if (gBrowser.tabs.length == 1 &&
+ !gPrefService.getBoolPref("browser.tabs.autoHide") &&
+ isTabEmpty(gBrowser.selectedTab))
+ blankTabToRemove = gBrowser.selectedTab;
+
+ var tab = null;
+ var ss = Cc["@mozilla.org/browser/sessionstore;1"].
+ getService(Ci.nsISessionStore);
+ if (ss.getClosedTabCount(window) > (aIndex || 0)) {
+ tab = ss.undoCloseTab(window, aIndex || 0);
+
+ if (blankTabToRemove)
+ gBrowser.removeTab(blankTabToRemove);
+ }
+
+ return tab;
+}
+
+/**
+ * Re-open a closed window.
+ * @param aIndex
+ * The index of the window (via nsSessionStore.getClosedWindowData)
+ * @returns a reference to the reopened window.
+ */
+function undoCloseWindow(aIndex) {
+ let ss = Cc["@mozilla.org/browser/sessionstore;1"].
+ getService(Ci.nsISessionStore);
+ let window = null;
+ if (ss.getClosedWindowCount() > (aIndex || 0))
+ window = ss.undoCloseWindow(aIndex || 0);
+
+ return window;
+}
+
+/*
+ * Determines if a tab is "empty", usually used in the context of determining
+ * if it's ok to close the tab.
+ */
+function isTabEmpty(aTab) {
+ if (aTab.hasAttribute("busy"))
+ return false;
+
+ let browser = aTab.linkedBrowser;
+ if (!isBlankPageURL(browser.currentURI.spec))
+ return false;
+
+ // Bug 863515 - Make content.opener checks work in electrolysis.
+ if (!gMultiProcessBrowser && browser.contentWindow.opener)
+ return false;
+
+ if (browser.sessionHistory && browser.sessionHistory.count >= 2)
+ return false;
+
+ return true;
+}
+
+#ifdef MOZ_SERVICES_SYNC
+function BrowserOpenSyncTabs() {
+ switchToTabHavingURI("about:sync-tabs", true);
+}
+#endif
+
+/**
+ * Format a URL
+ * eg:
+ * echo formatURL("https://addons.mozilla.org/%LOCALE%/%APP%/%VERSION%/");
+ * > https://addons.mozilla.org/en-US/firefox/3.0a1/
+ *
+ * Currently supported built-ins are LOCALE, APP, and any value from nsIXULAppInfo, uppercased.
+ */
+function formatURL(aFormat, aIsPref) {
+ var formatter = Cc["@mozilla.org/toolkit/URLFormatterService;1"].getService(Ci.nsIURLFormatter);
+ return aIsPref ? formatter.formatURLPref(aFormat) : formatter.formatURL(aFormat);
+}
+
+/**
+ * Utility object to handle manipulations of the identity indicators in the UI
+ */
+var gIdentityHandler = {
+ // Mode strings used to control CSS display
+ IDENTITY_MODE_IDENTIFIED : "verifiedIdentity", // High-quality identity information
+ IDENTITY_MODE_DOMAIN_VERIFIED : "verifiedDomain", // Minimal SSL CA-signed domain verification
+ IDENTITY_MODE_UNKNOWN : "unknownIdentity", // No trusted identity information
+ IDENTITY_MODE_MIXED_CONTENT : "unknownIdentity mixedContent", // SSL with unauthenticated content
+ IDENTITY_MODE_MIXED_ACTIVE_CONTENT : "unknownIdentity mixedContent mixedActiveContent", // SSL with unauthenticated content
+ IDENTITY_MODE_CHROMEUI : "chromeUI", // Part of the product's UI
+
+ // Cache the most recent SSLStatus and Location seen in checkIdentity
+ _lastStatus : null,
+ _lastLocation : null,
+ _mode : "unknownIdentity",
+
+ // smart getters
+ get _encryptionLabel () {
+ delete this._encryptionLabel;
+ this._encryptionLabel = {};
+ this._encryptionLabel[this.IDENTITY_MODE_DOMAIN_VERIFIED] =
+ gNavigatorBundle.getString("identity.encrypted");
+ this._encryptionLabel[this.IDENTITY_MODE_IDENTIFIED] =
+ gNavigatorBundle.getString("identity.encrypted");
+ this._encryptionLabel[this.IDENTITY_MODE_UNKNOWN] =
+ gNavigatorBundle.getString("identity.unencrypted");
+ this._encryptionLabel[this.IDENTITY_MODE_MIXED_CONTENT] =
+ gNavigatorBundle.getString("identity.mixed_content");
+ this._encryptionLabel[this.IDENTITY_MODE_MIXED_ACTIVE_CONTENT] =
+ gNavigatorBundle.getString("identity.mixed_content");
+ return this._encryptionLabel;
+ },
+ get _identityPopup () {
+ delete this._identityPopup;
+ return this._identityPopup = document.getElementById("identity-popup");
+ },
+ get _identityBox () {
+ delete this._identityBox;
+ return this._identityBox = document.getElementById("identity-box");
+ },
+ get _identityPopupContentBox () {
+ delete this._identityPopupContentBox;
+ return this._identityPopupContentBox =
+ document.getElementById("identity-popup-content-box");
+ },
+ get _identityPopupContentHost () {
+ delete this._identityPopupContentHost;
+ return this._identityPopupContentHost =
+ document.getElementById("identity-popup-content-host");
+ },
+ get _identityPopupContentOwner () {
+ delete this._identityPopupContentOwner;
+ return this._identityPopupContentOwner =
+ document.getElementById("identity-popup-content-owner");
+ },
+ get _identityPopupContentSupp () {
+ delete this._identityPopupContentSupp;
+ return this._identityPopupContentSupp =
+ document.getElementById("identity-popup-content-supplemental");
+ },
+ get _identityPopupContentVerif () {
+ delete this._identityPopupContentVerif;
+ return this._identityPopupContentVerif =
+ document.getElementById("identity-popup-content-verifier");
+ },
+ get _identityPopupEncLabel () {
+ delete this._identityPopupEncLabel;
+ return this._identityPopupEncLabel =
+ document.getElementById("identity-popup-encryption-label");
+ },
+ get _identityIconLabel () {
+ delete this._identityIconLabel;
+ return this._identityIconLabel = document.getElementById("identity-icon-label");
+ },
+ get _overrideService () {
+ delete this._overrideService;
+ return this._overrideService = Cc["@mozilla.org/security/certoverride;1"]
+ .getService(Ci.nsICertOverrideService);
+ },
+ get _identityIconCountryLabel () {
+ delete this._identityIconCountryLabel;
+ return this._identityIconCountryLabel = document.getElementById("identity-icon-country-label");
+ },
+ get _identityIcon () {
+ delete this._identityIcon;
+ return this._identityIcon = document.getElementById("page-proxy-favicon");
+ },
+
+ /**
+ * Rebuild cache of the elements that may or may not exist depending
+ * on whether there's a location bar.
+ */
+ _cacheElements : function() {
+ delete this._identityBox;
+ delete this._identityIconLabel;
+ delete this._identityIconCountryLabel;
+ delete this._identityIcon;
+ this._identityBox = document.getElementById("identity-box");
+ this._identityIconLabel = document.getElementById("identity-icon-label");
+ this._identityIconCountryLabel = document.getElementById("identity-icon-country-label");
+ this._identityIcon = document.getElementById("page-proxy-favicon");
+ },
+
+ /**
+ * Handler for mouseclicks on the "More Information" button in the
+ * "identity-popup" panel.
+ */
+ handleMoreInfoClick : function(event) {
+ displaySecurityInfo();
+ event.stopPropagation();
+ },
+
+ /**
+ * Helper to parse out the important parts of _lastStatus (of the SSL cert in
+ * particular) for use in constructing identity UI strings
+ */
+ getIdentityData : function() {
+ var result = {};
+ var status = this._lastStatus.QueryInterface(Components.interfaces.nsISSLStatus);
+ var cert = status.serverCert;
+
+ // Human readable name of Subject
+ result.subjectOrg = cert.organization;
+
+ // SubjectName fields, broken up for individual access
+ if (cert.subjectName) {
+ result.subjectNameFields = {};
+ cert.subjectName.split(",").forEach(function(v) {
+ var field = v.split("=");
+ this[field[0]] = field[1];
+ }, result.subjectNameFields);
+
+ // Call out city, state, and country specifically
+ result.city = result.subjectNameFields.L;
+ result.state = result.subjectNameFields.ST;
+ result.country = result.subjectNameFields.C;
+ }
+
+ // Human readable name of Certificate Authority
+ result.caOrg = cert.issuerOrganization || cert.issuerCommonName;
+ result.cert = cert;
+
+ return result;
+ },
+
+ /**
+ * Determine the identity of the page being displayed by examining its SSL cert
+ * (if available) and, if necessary, update the UI to reflect this. Intended to
+ * be called by onSecurityChange
+ *
+ * @param PRUint32 state
+ * @param JS Object location that mirrors an nsLocation (i.e. has .host and
+ * .hostname and .port)
+ */
+ checkIdentity : function(state, location) {
+ var currentStatus = gBrowser.securityUI
+ .QueryInterface(Components.interfaces.nsISSLStatusProvider)
+ .SSLStatus;
+ this._lastStatus = currentStatus;
+ this._lastLocation = location;
+
+ let nsIWebProgressListener = Ci.nsIWebProgressListener;
+ if (location.protocol == "chrome:" || location.protocol == "about:") {
+ this.setMode(this.IDENTITY_MODE_CHROMEUI);
+ } else if (state & nsIWebProgressListener.STATE_IDENTITY_EV_TOPLEVEL) {
+ this.setMode(this.IDENTITY_MODE_IDENTIFIED);
+ } else if (state & nsIWebProgressListener.STATE_IS_SECURE) {
+ this.setMode(this.IDENTITY_MODE_DOMAIN_VERIFIED);
+ } else if (state & nsIWebProgressListener.STATE_IS_BROKEN) {
+ if ((state & nsIWebProgressListener.STATE_LOADED_MIXED_ACTIVE_CONTENT) &&
+ gPrefService.getBoolPref("security.mixed_content.block_active_content")) {
+ this.setMode(this.IDENTITY_MODE_MIXED_ACTIVE_CONTENT);
+ } else {
+ this.setMode(this.IDENTITY_MODE_MIXED_CONTENT);
+ }
+ } else {
+ this.setMode(this.IDENTITY_MODE_UNKNOWN);
+ }
+
+ // Ensure the doorhanger is shown when mixed active content is blocked.
+ if (state & nsIWebProgressListener.STATE_BLOCKED_MIXED_ACTIVE_CONTENT)
+ this.showMixedContentDoorhanger();
+ },
+
+ /**
+ * Display the Mixed Content Blocker doohanger, providing an option
+ * to the user to override mixed content blocking
+ */
+ showMixedContentDoorhanger : function() {
+ // If we've already got an active notification, bail out to avoid showing it repeatedly.
+ if (PopupNotifications.getNotification("mixed-content-blocked", gBrowser.selectedBrowser))
+ return;
+
+ let brandBundle = document.getElementById("bundle_brand");
+ let brandShortName = brandBundle.getString("brandShortName");
+ let messageString = gNavigatorBundle.getFormattedString("mixedContentBlocked.message", [brandShortName]);
+ let action = {
+ label: gNavigatorBundle.getString("mixedContentBlocked.keepBlockingButton.label"),
+ accessKey: gNavigatorBundle.getString("mixedContentBlocked.keepBlockingButton.accesskey"),
+ callback: function() { /* NOP */ }
+ };
+ let secondaryActions = [
+ {
+ label: gNavigatorBundle.getString("mixedContentBlocked.unblock.label"),
+ accessKey: gNavigatorBundle.getString("mixedContentBlocked.unblock.accesskey"),
+ callback: function() {
+ // Reload the page with the content unblocked
+ BrowserReloadWithFlags(nsIWebNavigation.LOAD_FLAGS_ALLOW_MIXED_CONTENT);
+ }
+ }
+ ];
+ let options = {
+ dismissed: true,
+ learnMoreURL: Services.urlFormatter.formatURLPref("browser.mixedcontent.warning.infoURL"),
+ };
+ PopupNotifications.show(gBrowser.selectedBrowser, "mixed-content-blocked",
+ messageString, "mixed-content-blocked-notification-icon",
+ action, secondaryActions, options);
+ },
+
+ /**
+ * Return the eTLD+1 version of the current hostname
+ */
+ getEffectiveHost : function() {
+ try {
+ let baseDomain =
+ Services.eTLD.getBaseDomainFromHost(this._lastLocation.hostname);
+ return this._IDNService.convertToDisplayIDN(baseDomain, {});
+ } catch (e) {
+ // If something goes wrong (e.g. hostname is an IP address) just fail back
+ // to the full domain.
+ return this._lastLocation.hostname;
+ }
+ },
+
+ /**
+ * Update the UI to reflect the specified mode, which should be one of the
+ * IDENTITY_MODE_* constants.
+ */
+ setMode : function(newMode) {
+ if (!this._identityBox) {
+ // No identity box means the identity box is not visible, in which
+ // case there's nothing to do.
+ return;
+ }
+
+ this._identityBox.className = newMode;
+ this.setIdentityMessages(newMode);
+
+ // Update the popup too, if it's open
+ if (this._identityPopup.state == "open")
+ this.setPopupMessages(newMode);
+
+ this._mode = newMode;
+ },
+
+ /**
+ * Set up the messages for the primary identity UI based on the specified mode,
+ * and the details of the SSL cert, where applicable
+ *
+ * @param newMode The newly set identity mode. Should be one of the IDENTITY_MODE_* constants.
+ */
+ setIdentityMessages : function(newMode) {
+ let icon_label = "";
+ let tooltip = "";
+ let icon_country_label = "";
+ let icon_labels_dir = "ltr";
+
+ if (!this._IDNService)
+ this._IDNService = Cc["@mozilla.org/network/idn-service;1"]
+ .getService(Ci.nsIIDNService);
+ let punyID = gPrefService.getIntPref("browser.identity.display_punycode", 1);
+
+ switch (newMode) {
+ case this.IDENTITY_MODE_DOMAIN_VERIFIED: {
+ let iData = this.getIdentityData();
+
+ let label_display = "";
+
+ //Pale Moon: honor browser.identity.ssl_domain_display!
+ switch (gPrefService.getIntPref("browser.identity.ssl_domain_display")) {
+ case 2 : // Show full domain
+ label_display = this._lastLocation.hostname;
+ break;
+ case 1 : // Show eTLD.
+ label_display = this.getEffectiveHost();
+ }
+
+ if (punyID >= 1) {
+ // Display punycode version in identity panel
+ icon_label = this._IDNService.convertUTF8toACE(label_display);
+ } else {
+ icon_label = label_display;
+ }
+
+ // Verifier is either the CA Org, for a normal cert, or a special string
+ // for certs that are trusted because of a security exception.
+ tooltip = gNavigatorBundle.getFormattedString("identity.identified.verifier",
+ [iData.caOrg]);
+
+ // Check whether this site is a security exception. XPConnect does the right
+ // thing here in terms of converting _lastLocation.port from string to int, but
+ // the overrideService doesn't like undefined ports, so make sure we have
+ // something in the default case (bug 432241).
+ // .hostname can return an empty string in some exceptional cases -
+ // hasMatchingOverride does not handle that, so avoid calling it.
+ // Updating the tooltip value in those cases isn't critical.
+ // FIXME: Fixing bug 646690 would probably makes this check unnecessary
+ if (this._lastLocation.hostname &&
+ this._overrideService.hasMatchingOverride(this._lastLocation.hostname,
+ (this._lastLocation.port || 443),
+ iData.cert, {}, {}))
+ tooltip = gNavigatorBundle.getString("identity.identified.verified_by_you");
+ break; }
+ case this.IDENTITY_MODE_IDENTIFIED: {
+ // If it's identified, then we can populate the dialog with credentials
+ let iData = this.getIdentityData();
+ tooltip = gNavigatorBundle.getFormattedString("identity.identified.verifier",
+ [iData.caOrg]);
+ icon_label = iData.subjectOrg;
+ if (iData.country)
+ icon_country_label = "(" + iData.country + ")";
+
+ // If the organization name starts with an RTL character, then
+ // swap the positions of the organization and country code labels.
+ // The Unicode ranges reflect the definition of the UCS2_CHAR_IS_BIDI
+ // macro in intl/unicharutil/util/nsBidiUtils.h. When bug 218823 gets
+ // fixed, this test should be replaced by one adhering to the
+ // Unicode Bidirectional Algorithm proper (at the paragraph level).
+ icon_labels_dir = /^[\u0590-\u08ff\ufb1d-\ufdff\ufe70-\ufefc]/.test(icon_label) ?
+ "rtl" : "ltr";
+ break; }
+ case this.IDENTITY_MODE_CHROMEUI:
+ break;
+ default:
+ tooltip = gNavigatorBundle.getString("identity.unknown.tooltip");
+ if (punyID == 2) {
+ // Check for IDN and display if so...
+ let rawHost = this._IDNService.convertUTF8toACE(this._lastLocation.hostname);
+ if (this._IDNService.isACE(rawHost)) {
+ icon_label = rawHost;
+ }
+ }
+ }
+
+ // Push the appropriate strings out to the UI
+ this._identityBox.tooltipText = tooltip;
+ this._identityIconLabel.value = icon_label;
+ this._identityIconCountryLabel.value = icon_country_label;
+ // Set cropping and direction
+ this._identityIconLabel.crop = icon_country_label ? "end" : "center";
+ this._identityIconLabel.parentNode.style.direction = icon_labels_dir;
+ // Hide completely if the organization label is empty
+ this._identityIconLabel.parentNode.collapsed = icon_label ? false : true;
+ },
+
+ /**
+ * Set up the title and content messages for the identity message popup,
+ * based on the specified mode, and the details of the SSL cert, where
+ * applicable
+ *
+ * @param newMode The newly set identity mode. Should be one of the IDENTITY_MODE_* constants.
+ */
+ setPopupMessages : function(newMode) {
+
+ this._identityPopup.className = newMode;
+ this._identityPopupContentBox.className = newMode;
+
+ // Set the static strings up front
+ this._identityPopupEncLabel.textContent = this._encryptionLabel[newMode];
+
+ // Initialize the optional strings to empty values
+ let supplemental = "";
+ let verifier = "";
+ let host = "";
+ let owner = "";
+
+ switch (newMode) {
+ case this.IDENTITY_MODE_DOMAIN_VERIFIED:
+ host = this.getEffectiveHost();
+ owner = gNavigatorBundle.getString("identity.ownerUnknown2");
+ verifier = this._identityBox.tooltipText;
+ break;
+ case this.IDENTITY_MODE_IDENTIFIED: {
+ // If it's identified, then we can populate the dialog with credentials
+ let iData = this.getIdentityData();
+ host = this.getEffectiveHost();
+ owner = iData.subjectOrg;
+ verifier = this._identityBox.tooltipText;
+
+ // Build an appropriate supplemental block out of whatever location data we have
+ if (iData.city)
+ supplemental += iData.city + "\n";
+ if (iData.state && iData.country)
+ supplemental += gNavigatorBundle.getFormattedString("identity.identified.state_and_country",
+ [iData.state, iData.country]);
+ else if (iData.state) // State only
+ supplemental += iData.state;
+ else if (iData.country) // Country only
+ supplemental += iData.country;
+ break; }
+ }
+
+ // Push the appropriate strings out to the UI
+ this._identityPopupContentHost.textContent = host;
+ this._identityPopupContentOwner.textContent = owner;
+ this._identityPopupContentSupp.textContent = supplemental;
+ this._identityPopupContentVerif.textContent = verifier;
+ },
+
+ hideIdentityPopup : function() {
+ this._identityPopup.hidePopup();
+ },
+
+ /**
+ * Click handler for the identity-box element in primary chrome.
+ */
+ handleIdentityButtonEvent : function(event) {
+ event.stopPropagation();
+
+ if ((event.type == "click" && event.button != 0) ||
+ (event.type == "keypress" && event.charCode != KeyEvent.DOM_VK_SPACE &&
+ event.keyCode != KeyEvent.DOM_VK_RETURN)) {
+ return; // Left click, space or enter only
+ }
+
+ // Don't allow left click, space or enter if the location
+ // is chrome UI or the location has been modified.
+ if (this._mode == this.IDENTITY_MODE_CHROMEUI ||
+ gURLBar.getAttribute("pageproxystate") != "valid") {
+ return;
+ }
+
+ // Make sure that the display:none style we set in xul is removed now that
+ // the popup is actually needed
+ this._identityPopup.hidden = false;
+
+ // Update the popup strings
+ this.setPopupMessages(this._identityBox.className);
+
+ // Add the "open" attribute to the identity box for styling
+ this._identityBox.setAttribute("open", "true");
+ var self = this;
+ this._identityPopup.addEventListener("popuphidden", function onPopupHidden(e) {
+ e.currentTarget.removeEventListener("popuphidden", onPopupHidden, false);
+ self._identityBox.removeAttribute("open");
+ }, false);
+
+ // Now open the popup, anchored off the primary chrome element
+ this._identityPopup.openPopup(this._identityIcon, "bottomcenter topleft");
+ },
+
+ onPopupShown : function(event) {
+ document.getElementById('identity-popup-more-info-button').focus();
+ },
+
+ onDragStart: function (event) {
+ if (gURLBar.getAttribute("pageproxystate") != "valid")
+ return;
+
+ var value = content.location.href;
+ var urlString = value + "\n" + content.document.title;
+ var htmlString = "<a href=\"" + value + "\">" + value + "</a>";
+
+ var dt = event.dataTransfer;
+ dt.setData("text/x-moz-url", urlString);
+ dt.setData("text/uri-list", value);
+ dt.setData("text/plain", value);
+ dt.setData("text/html", htmlString);
+ dt.setDragImage(gProxyFavIcon, 16, 16);
+ }
+};
+
+function getNotificationBox(aWindow) {
+ var foundBrowser = gBrowser.getBrowserForDocument(aWindow.document);
+ if (foundBrowser)
+ return gBrowser.getNotificationBox(foundBrowser)
+ return null;
+};
+
+function getTabModalPromptBox(aWindow) {
+ var foundBrowser = gBrowser.getBrowserForDocument(aWindow.document);
+ if (foundBrowser)
+ return gBrowser.getTabModalPromptBox(foundBrowser);
+ return null;
+};
+
+/* DEPRECATED */
+function getBrowser() gBrowser;
+function getNavToolbox() gNavToolbox;
+
+var gPrivateBrowsingUI = {
+ init: function PBUI_init() {
+ // Do nothing for normal windows
+ if (!PrivateBrowsingUtils.isWindowPrivate(window)) {
+ return;
+ }
+
+ // Disable the Clear Recent History... menu item when in PB mode
+ // temporary fix until bug 463607 is fixed
+ document.getElementById("Tools:Sanitize").setAttribute("disabled", "true");
+
+ if (window.location.href == getBrowserURL()) {
+#ifdef XP_MACOSX
+ if (!PrivateBrowsingUtils.permanentPrivateBrowsing) {
+ document.documentElement.setAttribute("drawintitlebar", true);
+ }
+#endif
+
+ // Adjust the window's title
+ let docElement = document.documentElement;
+ if (!PrivateBrowsingUtils.permanentPrivateBrowsing) {
+ docElement.setAttribute("title",
+ docElement.getAttribute("title_privatebrowsing"));
+ docElement.setAttribute("titlemodifier",
+ docElement.getAttribute("titlemodifier_privatebrowsing"));
+ }
+ docElement.setAttribute("privatebrowsingmode",
+ PrivateBrowsingUtils.permanentPrivateBrowsing ? "permanent" : "temporary");
+ gBrowser.updateTitlebar();
+
+ if (PrivateBrowsingUtils.permanentPrivateBrowsing) {
+ // Adjust the New Window menu entries
+ [
+ { normal: "menu_newNavigator", private: "menu_newPrivateWindow" },
+ { normal: "appmenu_newNavigator", private: "appmenu_newPrivateWindow" },
+ ].forEach(function(menu) {
+ let newWindow = document.getElementById(menu.normal);
+ let newPrivateWindow = document.getElementById(menu.private);
+ if (newWindow && newPrivateWindow) {
+ newPrivateWindow.hidden = true;
+ newWindow.label = newPrivateWindow.label;
+ newWindow.accessKey = newPrivateWindow.accessKey;
+ newWindow.command = newPrivateWindow.command;
+ }
+ });
+ }
+ }
+
+ if (gURLBar &&
+ !PrivateBrowsingUtils.permanentPrivateBrowsing) {
+ // Disable switch to tab autocompletion for private windows
+ // (not for "Always use private browsing" mode)
+ gURLBar.setAttribute("autocompletesearchparam", "");
+ }
+ }
+};
+
+
+/**
+ * Switch to a tab that has a given URI, and focusses its browser window.
+ * If a matching tab is in this window, it will be switched to. Otherwise, other
+ * windows will be searched.
+ *
+ * @param aURI
+ * URI to search for
+ * @param aOpenNew
+ * True to open a new tab and switch to it, if no existing tab is found.
+ * If no suitable window is found, a new one will be opened.
+ * @return True if an existing tab was found, false otherwise
+ */
+function switchToTabHavingURI(aURI, aOpenNew) {
+ // This will switch to the tab in aWindow having aURI, if present.
+ function switchIfURIInWindow(aWindow) {
+ // Only switch to the tab if neither the source and desination window are
+ // private and they are not in permanent private borwsing mode
+ if ((PrivateBrowsingUtils.isWindowPrivate(window) ||
+ PrivateBrowsingUtils.isWindowPrivate(aWindow)) &&
+ !PrivateBrowsingUtils.permanentPrivateBrowsing) {
+ return false;
+ }
+
+ let browsers = aWindow.gBrowser.browsers;
+ for (let i = 0; i < browsers.length; i++) {
+ let browser = browsers[i];
+ if (browser.currentURI.equals(aURI)) {
+ // Focus the matching window & tab
+ aWindow.focus();
+ aWindow.gBrowser.tabContainer.selectedIndex = i;
+ return true;
+ }
+ }
+ return false;
+ }
+
+ // This can be passed either nsIURI or a string.
+ if (!(aURI instanceof Ci.nsIURI))
+ aURI = Services.io.newURI(aURI, null, null);
+
+ let isBrowserWindow = !!window.gBrowser;
+
+ // Prioritise this window.
+ if (isBrowserWindow && switchIfURIInWindow(window))
+ return true;
+
+ let winEnum = Services.wm.getEnumerator("navigator:browser");
+ while (winEnum.hasMoreElements()) {
+ let browserWin = winEnum.getNext();
+ // Skip closed (but not yet destroyed) windows,
+ // and the current window (which was checked earlier).
+ if (browserWin.closed || browserWin == window)
+ continue;
+ if (switchIfURIInWindow(browserWin))
+ return true;
+ }
+
+ // No opened tab has that url.
+ if (aOpenNew) {
+ if (isBrowserWindow && isTabEmpty(gBrowser.selectedTab))
+ gBrowser.selectedBrowser.loadURI(aURI.spec);
+ else
+ openUILinkIn(aURI.spec, "tab");
+ }
+
+ return false;
+}
+
+function restoreLastSession() {
+ let ss = Cc["@mozilla.org/browser/sessionstore;1"].
+ getService(Ci.nsISessionStore);
+ ss.restoreLastSession();
+}
+
+var TabContextMenu = {
+ contextTab: null,
+ _updateToggleMuteMenuItem(aTab, aConditionFn) {
+ ["muted", "soundplaying"].forEach(attr => {
+ if (!aConditionFn || aConditionFn(attr)) {
+ if (aTab.hasAttribute(attr)) {
+ aTab.toggleMuteMenuItem.setAttribute(attr, "true");
+ } else {
+ aTab.toggleMuteMenuItem.removeAttribute(attr);
+ }
+ }
+ });
+ },
+ updateContextMenu: function updateContextMenu(aPopupMenu) {
+ this.contextTab = aPopupMenu.triggerNode.localName == "tab" ?
+ aPopupMenu.triggerNode : gBrowser.selectedTab;
+ let disabled = gBrowser.tabs.length == 1;
+
+ // Enable the "Close Tab" menuitem when the window doesn't close with the last tab.
+ document.getElementById("context_closeTab").disabled =
+ disabled && gBrowser.tabContainer._closeWindowWithLastTab;
+
+ var menuItems = aPopupMenu.getElementsByAttribute("tbattr", "tabbrowser-multiple");
+ for (let menuItem of menuItems)
+ menuItem.disabled = disabled;
+
+ disabled = gBrowser.visibleTabs.length == 1;
+ menuItems = aPopupMenu.getElementsByAttribute("tbattr", "tabbrowser-multiple-visible");
+ for (let menuItem of menuItems)
+ menuItem.disabled = disabled;
+
+ // Session store
+ document.getElementById("context_undoCloseTab").disabled =
+ Cc["@mozilla.org/browser/sessionstore;1"].
+ getService(Ci.nsISessionStore).
+ getClosedTabCount(window) == 0;
+
+ // Only one of pin/unpin should be visible
+ document.getElementById("context_pinTab").hidden = this.contextTab.pinned;
+ document.getElementById("context_unpinTab").hidden = !this.contextTab.pinned;
+
+ // Disable "Close Tabs to the Right" if there are no tabs
+ // following it and hide it when the user rightclicked on a pinned
+ // tab.
+ document.getElementById("context_closeTabsToTheEnd").disabled =
+ gBrowser.getTabsToTheEndFrom(this.contextTab).length == 0;
+ document.getElementById("context_closeTabsToTheEnd").hidden = this.contextTab.pinned;
+
+ // Disable "Close other Tabs" if there is only one unpinned tab and
+ // hide it when the user rightclicked on a pinned tab.
+ let unpinnedTabs = gBrowser.visibleTabs.length - gBrowser._numPinnedTabs;
+ document.getElementById("context_closeOtherTabs").disabled = unpinnedTabs <= 1;
+ document.getElementById("context_closeOtherTabs").hidden = this.contextTab.pinned;
+
+ // Hide "Bookmark All Tabs" for a pinned tab. Update its state if visible.
+ let bookmarkAllTabs = document.getElementById("context_bookmarkAllTabs");
+ bookmarkAllTabs.hidden = this.contextTab.pinned;
+ if (!bookmarkAllTabs.hidden)
+ PlacesCommandHook.updateBookmarkAllTabsCommand();
+
+ // Adjust the state of the toggle mute menu item.
+ let toggleMute = document.getElementById("context_toggleMuteTab");
+ if (this.contextTab.hasAttribute("muted")) {
+ toggleMute.label = gNavigatorBundle.getString("unmuteTab.label");
+ toggleMute.accessKey = gNavigatorBundle.getString("unmuteTab.accesskey");
+ } else {
+ toggleMute.label = gNavigatorBundle.getString("muteTab.label");
+ toggleMute.accessKey = gNavigatorBundle.getString("muteTab.accesskey");
+ }
+
+ this.contextTab.toggleMuteMenuItem = toggleMute;
+ this._updateToggleMuteMenuItem(this.contextTab);
+
+ this.contextTab.addEventListener("TabAttrModified", this, false);
+ aPopupMenu.addEventListener("popuphiding", this, false);
+ },
+ handleEvent(aEvent) {
+ switch (aEvent.type) {
+ case "popuphiding":
+ gBrowser.removeEventListener("TabAttrModified", this);
+ aEvent.target.removeEventListener("popuphiding", this);
+ break;
+ case "TabAttrModified":
+ let tab = aEvent.target;
+ this._updateToggleMuteMenuItem(tab,
+ attr => aEvent.detail.changed.indexOf(attr) >= 0);
+ break;
+ }
+ }
+};
+
+#ifdef MOZ_DEVTOOLS
+// Note: Do not delete! It is used for: base/content/nsContextMenu.js
+XPCOMUtils.defineLazyModuleGetter(this, "gDevTools",
+ "resource://devtools/client/framework/gDevTools.jsm");
+#endif
+
+// Prompt user to restart the browser in safe mode or normally
+function restart(safeMode)
+{
+ let promptTitleString = null;
+ let promptMessageString = null;
+ let restartTextString = null;
+ if (safeMode) {
+ promptTitleString = "safeModeRestartPromptTitle";
+ promptMessageString = "safeModeRestartPromptMessage";
+ restartTextString = "safeModeRestartButton";
+ } else {
+ promptTitleString = "restartPromptTitle";
+ promptMessageString = "restartPromptMessage";
+ restartTextString = "restartButton";
+ }
+
+ let flags = Ci.nsIAppStartup.eAttemptQuit;
+
+ // Prompt the user to confirm
+ let promptTitle = gNavigatorBundle.getString(promptTitleString);
+ let brandBundle = document.getElementById("bundle_brand");
+ let brandShortName = brandBundle.getString("brandShortName");
+ let promptMessage =
+ gNavigatorBundle.getFormattedString(promptMessageString, [brandShortName]);
+ let restartText = gNavigatorBundle.getString(restartTextString);
+ let buttonFlags = (Services.prompt.BUTTON_POS_0 *
+ Services.prompt.BUTTON_TITLE_IS_STRING) +
+ (Services.prompt.BUTTON_POS_1 *
+ Services.prompt.BUTTON_TITLE_CANCEL) +
+ Services.prompt.BUTTON_POS_0_DEFAULT;
+
+ let rv = Services.prompt.confirmEx(window, promptTitle, promptMessage,
+ buttonFlags, restartText, null, null,
+ null, {});
+
+ if (rv == 0) {
+ // Notify all windows that an application quit has been requested.
+ let cancelQuit = Components.classes["@mozilla.org/supports-PRBool;1"]
+ .createInstance(Ci.nsISupportsPRBool);
+ Services.obs.notifyObservers(cancelQuit, "quit-application-requested", "restart");
+
+ // Something aborted the quit process.
+ if (cancelQuit.data) {
+ return;
+ }
+
+ if (safeMode) {
+ Services.startup.restartInSafeMode(flags);
+ } else {
+ Services.startup.quit(flags | Ci.nsIAppStartup.eRestart);
+ }
+ }
+}
+
+/* duplicateTabIn duplicates tab in a place specified by the parameter |where|.
+ *
+ * |where| can be:
+ * "tab" new tab
+ * "tabshifted" same as "tab" but in background if default is to select new
+ * tabs, and vice versa
+ * "window" new window
+ *
+ * delta is the offset to the history entry that you want to load.
+ */
+function duplicateTabIn(aTab, where, delta) {
+ let newTab = Cc['@mozilla.org/browser/sessionstore;1']
+ .getService(Ci.nsISessionStore)
+ .duplicateTab(window, aTab, delta);
+
+ switch (where) {
+ case "window":
+ gBrowser.hideTab(newTab);
+ gBrowser.replaceTabWithWindow(newTab);
+ break;
+ case "tabshifted":
+ // A background tab has been opened, nothing else to do here.
+ break;
+ case "tab":
+ gBrowser.selectedTab = newTab;
+ break;
+ }
+}
+
+function toggleAddonBar() {
+ let addonBar = document.getElementById("addon-bar");
+ setToolbarVisibility(addonBar, addonBar.collapsed);
+}
+
+XPCOMUtils.defineLazyGetter(window, "gShowPageResizers", function () {
+#ifdef XP_WIN
+ // Only show resizers on Windows 2000 and XP
+ return parseFloat(Services.sysinfo.getProperty("version")) < 6;
+#else
+ return false;
+#endif
+});
+
+var MousePosTracker = {
+ _listeners: [],
+ _x: 0,
+ _y: 0,
+ get _windowUtils() {
+ delete this._windowUtils;
+ return this._windowUtils = window.getInterface(Ci.nsIDOMWindowUtils);
+ },
+
+ addListener: function (listener) {
+ if (this._listeners.indexOf(listener) >= 0)
+ return;
+
+ listener._hover = false;
+ this._listeners.push(listener);
+
+ this._callListener(listener);
+ },
+
+ removeListener: function (listener) {
+ var index = this._listeners.indexOf(listener);
+ if (index < 0)
+ return;
+
+ this._listeners.splice(index, 1);
+ },
+
+ handleEvent: function (event) {
+ var fullZoom = this._windowUtils.fullZoom;
+ this._x = event.screenX / fullZoom - window.mozInnerScreenX;
+ this._y = event.screenY / fullZoom - window.mozInnerScreenY;
+
+ this._listeners.forEach(function (listener) {
+ try {
+ this._callListener(listener);
+ } catch (e) {
+ Cu.reportError(e);
+ }
+ }, this);
+ },
+
+ _callListener: function (listener) {
+ let rect = listener.getMouseTargetRect();
+ let hover = this._x >= rect.left &&
+ this._x <= rect.right &&
+ this._y >= rect.top &&
+ this._y <= rect.bottom;
+
+ if (hover == listener._hover)
+ return;
+
+ listener._hover = hover;
+
+ if (hover) {
+ if (listener.onMouseEnter)
+ listener.onMouseEnter();
+ } else {
+ if (listener.onMouseLeave)
+ listener.onMouseLeave();
+ }
+ }
+};
+
+var BrowserChromeTest = {
+ _cb: null,
+ _ready: false,
+ markAsReady: function () {
+ this._ready = true;
+ if (this._cb) {
+ this._cb();
+ this._cb = null;
+ }
+ },
+ runWhenReady: function (cb) {
+ if (this._ready)
+ cb();
+ else
+ this._cb = cb;
+ }
+};
+
+var ToolbarIconColor = {
+ init: function () {
+ this._initialized = true;
+
+ window.addEventListener("activate", this);
+ window.addEventListener("deactivate", this);
+ Services.obs.addObserver(this, "lightweight-theme-styling-update", false);
+ gPrefService.addObserver("ui.colorChanged", this, false);
+
+ // If the window isn't active now, we assume that it has never been active
+ // before and will soon become active such that inferFromText will be
+ // called from the initial activate event.
+ if (Services.focus.activeWindow == window)
+ this.inferFromText();
+ },
+
+ uninit: function () {
+ this._initialized = false;
+
+ window.removeEventListener("activate", this);
+ window.removeEventListener("deactivate", this);
+ Services.obs.removeObserver(this, "lightweight-theme-styling-update");
+ gPrefService.removeObserver("ui.colorChanged", this);
+ },
+
+ handleEvent: function (event) {
+ switch (event.type) {
+ case "activate":
+ case "deactivate":
+ this.inferFromText();
+ break;
+ }
+ },
+
+ observe: function (aSubject, aTopic, aData) {
+ switch (aTopic) {
+ case "lightweight-theme-styling-update":
+ // inferFromText needs to run after LightweightThemeConsumer.jsm's
+ // lightweight-theme-styling-update observer.
+ setTimeout(() => { this.inferFromText(); }, 0);
+ break;
+ case "nsPref:changed":
+ // system color change
+ var colorChangedPref = false;
+ try {
+ colorChangedPref = gPrefService.getBoolPref("ui.colorChanged");
+ } catch(e) { }
+ // if pref indicates change, call inferFromText() on a small delay
+ if (colorChangedPref == true)
+ setTimeout(() => { this.inferFromText(); }, 300);
+ break;
+ default:
+ console.error("ToolbarIconColor: Uncaught topic " + aTopic);
+ }
+ },
+
+ inferFromText: function () {
+ if (!this._initialized)
+ return;
+
+ function parseRGB(aColorString) {
+ let rgb = aColorString.match(/^rgba?\((\d+), (\d+), (\d+)/);
+ rgb.shift();
+ return rgb.map(x => parseInt(x));
+ }
+
+ let toolbarSelector = "toolbar:not([collapsed=true])";
+#ifdef XP_MACOSX
+ toolbarSelector += ":not([type=menubar])";
+#endif
+
+ // The getComputedStyle calls and setting the brighttext are separated in
+ // two loops to avoid flushing layout and making it dirty repeatedly.
+
+ let luminances = new Map;
+ for (let toolbar of document.querySelectorAll(toolbarSelector)) {
+ let [r, g, b] = parseRGB(getComputedStyle(toolbar).color);
+ let luminance = (2 * r + 5 * g + b) / 8;
+ luminances.set(toolbar, luminance);
+ }
+
+ for (let [toolbar, luminance] of luminances) {
+ if (luminance <= 128)
+ toolbar.removeAttribute("brighttext");
+ else
+ toolbar.setAttribute("brighttext", "true");
+ }
+
+ // Clear pref if set, since we're done applying the color changes.
+ gPrefService.clearUserPref("ui.colorChanged");
+ }
+}