summaryrefslogtreecommitdiffstats
path: root/application/basilisk/base/content/browser.js
diff options
context:
space:
mode:
authorwolfbeast <mcwerewolf@gmail.com>2018-06-04 13:17:38 +0200
committerwolfbeast <mcwerewolf@gmail.com>2018-06-04 13:17:38 +0200
commita1be17c1cea81ebb1e8b131a662c698d78f3f7f2 (patch)
treea92f7de513be600cc07bac458183e9af40e00c06 /application/basilisk/base/content/browser.js
parentbf11fdd304898ac675e39b01b280d39550e419d0 (diff)
downloadUXP-a1be17c1cea81ebb1e8b131a662c698d78f3f7f2.tar
UXP-a1be17c1cea81ebb1e8b131a662c698d78f3f7f2.tar.gz
UXP-a1be17c1cea81ebb1e8b131a662c698d78f3f7f2.tar.lz
UXP-a1be17c1cea81ebb1e8b131a662c698d78f3f7f2.tar.xz
UXP-a1be17c1cea81ebb1e8b131a662c698d78f3f7f2.zip
Issue #303 Part 1: Move basilisk files from /browser to /application/basilisk
Diffstat (limited to 'application/basilisk/base/content/browser.js')
-rw-r--r--application/basilisk/base/content/browser.js8177
1 files changed, 8177 insertions, 0 deletions
diff --git a/application/basilisk/base/content/browser.js b/application/basilisk/base/content/browser.js
new file mode 100644
index 000000000..64c0d86f5
--- /dev/null
+++ b/application/basilisk/base/content/browser.js
@@ -0,0 +1,8177 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 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;
+var Cc = Components.classes;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/ContextualIdentityService.jsm");
+Cu.import("resource://gre/modules/NotificationDB.jsm");
+
+// lazy module getters
+[
+ ["AboutHome", "resource:///modules/AboutHome.jsm"],
+ ["AddonWatcher", "resource://gre/modules/AddonWatcher.jsm"],
+ ["AppConstants", "resource://gre/modules/AppConstants.jsm"],
+ ["BrowserUITelemetry", "resource:///modules/BrowserUITelemetry.jsm"],
+ ["BrowserUsageTelemetry", "resource:///modules/BrowserUsageTelemetry.jsm"],
+ ["BrowserUtils", "resource://gre/modules/BrowserUtils.jsm"],
+ ["CastingApps", "resource:///modules/CastingApps.jsm"],
+ ["CharsetMenu", "resource://gre/modules/CharsetMenu.jsm"],
+ ["Color", "resource://gre/modules/Color.jsm"],
+ ["ContentSearch", "resource:///modules/ContentSearch.jsm"],
+ ["Deprecated", "resource://gre/modules/Deprecated.jsm"],
+ ["E10SUtils", "resource:///modules/E10SUtils.jsm"],
+ ["FormValidationHandler", "resource:///modules/FormValidationHandler.jsm"],
+ ["GMPInstallManager", "resource://gre/modules/GMPInstallManager.jsm"],
+ ["LightweightThemeManager", "resource://gre/modules/LightweightThemeManager.jsm"],
+ ["Log", "resource://gre/modules/Log.jsm"],
+ ["LoginManagerParent", "resource://gre/modules/LoginManagerParent.jsm"],
+ ["NewTabUtils", "resource://gre/modules/NewTabUtils.jsm"],
+ ["PageThumbs", "resource://gre/modules/PageThumbs.jsm"],
+ ["PluralForm", "resource://gre/modules/PluralForm.jsm"],
+ ["Preferences", "resource://gre/modules/Preferences.jsm"],
+ ["PrivateBrowsingUtils", "resource://gre/modules/PrivateBrowsingUtils.jsm"],
+ ["ProcessHangMonitor", "resource:///modules/ProcessHangMonitor.jsm"],
+ ["PromiseUtils", "resource://gre/modules/PromiseUtils.jsm"],
+ ["ReaderMode", "resource://gre/modules/ReaderMode.jsm"],
+ ["ReaderParent", "resource:///modules/ReaderParent.jsm"],
+ ["RecentWindow", "resource:///modules/RecentWindow.jsm"],
+ ["SessionStore", "resource:///modules/sessionstore/SessionStore.jsm"],
+ ["ShortcutUtils", "resource://gre/modules/ShortcutUtils.jsm"],
+ ["SimpleServiceDiscovery", "resource://gre/modules/SimpleServiceDiscovery.jsm"],
+ ["SitePermissions", "resource:///modules/SitePermissions.jsm"],
+ ["TabCrashHandler", "resource:///modules/ContentCrashHandlers.jsm"],
+ ["Task", "resource://gre/modules/Task.jsm"],
+ ["TelemetryStopwatch", "resource://gre/modules/TelemetryStopwatch.jsm"],
+ ["Translation", "resource:///modules/translation/Translation.jsm"],
+ ["UpdateUtils", "resource://gre/modules/UpdateUtils.jsm"],
+ ["Weave", "resource://services-sync/main.js"],
+ ["fxAccounts", "resource://gre/modules/FxAccounts.jsm"],
+#ifdef MOZ_DEVTOOLS
+ // Note: Do not delete! It is used for: base/content/nsContextMenu.js
+ ["gDevTools", "resource://devtools/client/framework/gDevTools.jsm"],
+#endif
+ ["webrtcUI", "resource:///modules/webrtcUI.jsm", ]
+].forEach(([name, resource]) => XPCOMUtils.defineLazyModuleGetter(this, name, resource));
+
+#ifdef MOZ_SAFE_BROWSING
+ XPCOMUtils.defineLazyModuleGetter(this, "SafeBrowsing",
+ "resource://gre/modules/SafeBrowsing.jsm");
+#endif
+
+// lazy service getters
+[
+ ["Favicons", "@mozilla.org/browser/favicon-service;1", "mozIAsyncFavicons"],
+ ["WindowsUIUtils", "@mozilla.org/windows-ui-utils;1", "nsIWindowsUIUtils"],
+ ["gAboutNewTabService", "@mozilla.org/browser/aboutnewtab-service;1", "nsIAboutNewTabService"],
+ ["gDNSService", "@mozilla.org/network/dns-service;1", "nsIDNSService"],
+].forEach(([name, cc, ci]) => XPCOMUtils.defineLazyServiceGetter(this, name, cc, ci));
+
+XPCOMUtils.defineLazyServiceGetter(this, "gSerializationHelper",
+ "@mozilla.org/network/serialization-helper;1",
+ "nsISerializationHelper");
+
+XPCOMUtils.defineLazyGetter(this, "BrowserToolboxProcess", function() {
+ let tmp = {};
+ Cu.import("resource://devtools/client/framework/ToolboxProcess.jsm", tmp);
+ return tmp.BrowserToolboxProcess;
+});
+
+XPCOMUtils.defineLazyGetter(this, "gBrowserBundle", function() {
+ return Services.strings.createBundle('chrome://browser/locale/browser.properties');
+});
+
+XPCOMUtils.defineLazyGetter(this, "gCustomizeMode", function() {
+ let scope = {};
+ Cu.import("resource:///modules/CustomizeMode.jsm", scope);
+ return new scope.CustomizeMode(window);
+});
+
+XPCOMUtils.defineLazyGetter(this, "gPrefService", function() {
+ return Services.prefs;
+});
+
+XPCOMUtils.defineLazyGetter(this, "InlineSpellCheckerUI", function() {
+ let tmp = {};
+ Cu.import("resource://gre/modules/InlineSpellChecker.jsm", tmp);
+ return new tmp.InlineSpellChecker();
+});
+
+XPCOMUtils.defineLazyGetter(this, "PageMenuParent", function() {
+ let tmp = {};
+ Cu.import("resource://gre/modules/PageMenu.jsm", tmp);
+ return new tmp.PageMenuParent();
+});
+
+XPCOMUtils.defineLazyGetter(this, "PopupNotifications", function () {
+ let tmp = {};
+ Cu.import("resource://gre/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.defineLazyGetter(this, "Win7Features", function () {
+ if (AppConstants.platform != "win")
+ return null;
+
+ 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);
+ }
+ };
+ }
+ return null;
+});
+
+const nsIWebNavigation = Ci.nsIWebNavigation;
+
+var gLastBrowserCharset = null;
+var gLastValidURLStr = "";
+var gInPrintPreviewMode = false;
+var gContextMenu = null; // nsContextMenu instance
+var gMultiProcessBrowser =
+ window.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsILoadContext)
+ .useRemoteTabs;
+var gAppInfo = Cc["@mozilla.org/xre/app-info;1"]
+ .getService(Ci.nsIXULAppInfo)
+ .QueryInterface(Ci.nsIXULRuntime);
+
+if (AppConstants.platform != "macosx") {
+ var gEditUIVisible = true;
+}
+
+/* globals gBrowser, gNavToolbox, gURLBar, gNavigatorBundle*/
+[
+ ["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.
+
+this.__defineGetter__("gFindBar", function() {
+ return window.gBrowser.getFindBar();
+});
+
+this.__defineGetter__("gFindBarInitialized", function() {
+ return window.gBrowser.isFindBarInitialized();
+});
+
+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;
+});
+
+
+var gInitialPages = [
+ "about:blank",
+ "about:newtab",
+ "about:home",
+ "about:privatebrowsing",
+ "about:welcomeback",
+ "about:sessionrestore",
+ "about:logopage"
+];
+
+function* browserWindows() {
+ let windows = Services.wm.getEnumerator("navigator:browser");
+ while (windows.hasMoreElements())
+ yield windows.getNext();
+}
+
+/**
+* We can avoid adding multiple load event listeners and save some time by adding
+* one listener that calls all real handlers.
+*/
+function pageShowEventHandlers(persisted) {
+ XULBrowserWindow.asyncUpdateUI();
+}
+
+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() {
+ // Bug 414797: Clone the back/forward buttons' context menu into both buttons.
+ let popup = document.getElementById("backForwardMenu").cloneNode(true);
+ popup.removeAttribute("id");
+ // Prevent the back/forward buttons' context attributes from being inherited.
+ popup.setAttribute("context", "");
+
+ let backButton = document.getElementById("back-button");
+ backButton.setAttribute("type", "menu");
+ backButton.appendChild(popup);
+ gClickAndHoldListenersOnElement.add(backButton);
+
+ let forwardButton = document.getElementById("forward-button");
+ popup = popup.cloneNode(true);
+ forwardButton.setAttribute("type", "menu");
+ forwardButton.appendChild(popup);
+ gClickAndHoldListenersOnElement.add(forwardButton);
+}
+
+
+const gClickAndHoldListenersOnElement = {
+ _timers: new Map(),
+
+ _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", this, false);
+ aEvent.currentTarget.addEventListener("mouseup", this, false);
+ this._timers.set(aEvent.currentTarget, setTimeout((b) => this._openMenu(b), 500, aEvent.currentTarget));
+ },
+
+ _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);
+
+ // This is here to cancel the XUL default event
+ // dom.click() triggers a command even if there is a click handler
+ // however this can now be prevented with preventDefault().
+ aEvent.preventDefault();
+ }
+ },
+
+ _openMenu(aButton) {
+ this._cancelHold(aButton);
+ aButton.firstChild.hidden = false;
+ aButton.open = true;
+ },
+
+ _mouseoutHandler(aEvent) {
+ let buttonRect = aEvent.currentTarget.getBoundingClientRect();
+ if (aEvent.clientX >= buttonRect.left &&
+ aEvent.clientX <= buttonRect.right &&
+ aEvent.clientY >= buttonRect.bottom)
+ this._openMenu(aEvent.currentTarget);
+ else
+ this._cancelHold(aEvent.currentTarget);
+ },
+
+ _mouseupHandler(aEvent) {
+ this._cancelHold(aEvent.currentTarget);
+ },
+
+ _cancelHold(aButton) {
+ clearTimeout(this._timers.get(aButton));
+ aButton.removeEventListener("mouseout", this, false);
+ aButton.removeEventListener("mouseup", this, false);
+ },
+
+ handleEvent(e) {
+ switch (e.type) {
+ case "mouseout":
+ this._mouseoutHandler(e);
+ break;
+ case "mousedown":
+ this._mousedownHandler(e);
+ break;
+ case "click":
+ this._clickHandler(e);
+ break;
+ case "mouseup":
+ this._mouseupHandler(e);
+ break;
+ }
+ },
+
+ remove(aButton) {
+ aButton.removeEventListener("mousedown", this, true);
+ aButton.removeEventListener("click", this, true);
+ },
+
+ add(aElm) {
+ this._timers.delete(aElm);
+
+ aElm.addEventListener("mousedown", this, true);
+ aElm.addEventListener("click", this, true);
+ }
+};
+
+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");
+
+ // Clear undo history of the URL bar
+ gURLBar.editor.transactionManager.clear()
+ }
+};
+
+/**
+ * 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,
+
+ onReportButtonMousedown: function (aEvent)
+ {
+ // If this method is called on the same event tick as the popup gets
+ // hidden, do nothing to avoid re-opening the popup.
+ if (aEvent.button != 0 || aEvent.target != this._reportButton || this.isPopupHidingTick)
+ 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)
+ 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)
+ this._reportButton.hidden = true;
+
+ // Hide the notification box (if it's visible).
+ let notificationBox = gBrowser.getNotificationBox();
+ let notification = notificationBox.getNotificationWithValue("popup-blocked");
+ if (notification) {
+ notificationBox.removeNotification(notification, false);
+ }
+ return;
+ }
+
+ 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 stringKey = "popupWarningButton";
+
+ var popupButtonText = gNavigatorBundle.getString(stringKey);
+ var popupButtonAccesskey = gNavigatorBundle.getString(stringKey + ".accesskey");
+
+ var messageBase = gNavigatorBundle.getString("popupWarning.message");
+ var message = PluralForm.get(popupCount, messageBase)
+ .replace("#1", brandShortName)
+ .replace("#2", popupCount);
+
+ let notificationBox = gBrowser.getNotificationBox();
+ let 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);
+
+ if (!shouldBlock)
+ this.showAllBlockedPopups(gBrowser.selectedBrowser);
+
+ 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");
+
+ this.isPopupHidingTick = true;
+ setTimeout(() => this.isPopupHidingTick = false, 0);
+
+ 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);
+ },
+
+ showAllBlockedPopups: function (aBrowser)
+ {
+ aBrowser.retrieveListOfBlockedPopups().then(popups => {
+ for (let i = 0; i < popups.length; i++) {
+ if (popups[i].popupWindowURIspec)
+ aBrowser.unblockPopup(i);
+ }
+ }, null);
+ },
+
+ 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();
+ }
+};
+
+function gKeywordURIFixup({ target: browser, data: fixupInfo }) {
+ let deserializeURI = (spec) => spec ? makeURI(spec) : null;
+
+ // We get called irrespective of whether we did a keyword search, or
+ // whether the original input would be vaguely interpretable as a URL,
+ // so figure that out first.
+ let alternativeURI = deserializeURI(fixupInfo.fixedURI);
+ if (!fixupInfo.keywordProviderName || !alternativeURI || !alternativeURI.host) {
+ return;
+ }
+
+ // At this point we're still only just about to load this URI.
+ // When the async DNS lookup comes back, we may be in any of these states:
+ // 1) still on the previous URI, waiting for the preferredURI (keyword
+ // search) to respond;
+ // 2) at the keyword search URI (preferredURI)
+ // 3) at some other page because the user stopped navigation.
+ // We keep track of the currentURI to detect case (1) in the DNS lookup
+ // callback.
+ let previousURI = browser.currentURI;
+ let preferredURI = deserializeURI(fixupInfo.preferredURI);
+
+ // now swap for a weak ref so we don't hang on to browser needlessly
+ // even if the DNS query takes forever
+ let weakBrowser = Cu.getWeakReference(browser);
+ browser = null;
+
+ // Additionally, we need the host of the parsed url
+ let hostName = alternativeURI.host;
+ // and the ascii-only host for the pref:
+ let asciiHost = alternativeURI.asciiHost;
+ // Normalize out a single trailing dot - NB: not using endsWith/lastIndexOf
+ // because we need to be sure this last dot is the *only* dot, too.
+ // More generally, this is used for the pref and should stay in sync with
+ // the code in nsDefaultURIFixup::KeywordURIFixup .
+ if (asciiHost.indexOf('.') == asciiHost.length - 1) {
+ asciiHost = asciiHost.slice(0, -1);
+ }
+
+ let isIPv4Address = host => {
+ let parts = host.split(".");
+ if (parts.length != 4) {
+ return false;
+ }
+ return parts.every(part => {
+ let n = parseInt(part, 10);
+ return n >= 0 && n <= 255;
+ });
+ };
+ // Avoid showing fixup information if we're suggesting an IP. Note that
+ // decimal representations of IPs are normalized to a 'regular'
+ // dot-separated IP address by network code, but that only happens for
+ // numbers that don't overflow. Longer numbers do not get normalized,
+ // but still work to access IP addresses. So for instance,
+ // 1097347366913 (ff7f000001) gets resolved by using the final bytes,
+ // making it the same as 7f000001, which is 127.0.0.1 aka localhost.
+ // While 2130706433 would get normalized by network, 1097347366913
+ // does not, and we have to deal with both cases here:
+ if (isIPv4Address(asciiHost) || /^(?:\d+|0x[a-f0-9]+)$/i.test(asciiHost))
+ return;
+
+ let onLookupComplete = (request, record, status) => {
+ let browser = weakBrowser.get();
+ if (!Components.isSuccessCode(status) || !browser)
+ return;
+
+ let currentURI = browser.currentURI;
+ // If we're in case (3) (see above), don't show an info bar.
+ if (!currentURI.equals(previousURI) &&
+ !currentURI.equals(preferredURI)) {
+ return;
+ }
+
+ // show infobar offering to visit the host
+ let notificationBox = gBrowser.getNotificationBox(browser);
+ if (notificationBox.getNotificationWithValue("keyword-uri-fixup"))
+ return;
+
+ let message = gNavigatorBundle.getFormattedString(
+ "keywordURIFixup.message", [hostName]);
+ let yesMessage = gNavigatorBundle.getFormattedString(
+ "keywordURIFixup.goTo", [hostName])
+
+ let buttons = [
+ {
+ label: yesMessage,
+ accessKey: gNavigatorBundle.getString("keywordURIFixup.goTo.accesskey"),
+ callback: function() {
+ // Do not set this preference while in private browsing.
+ if (!PrivateBrowsingUtils.isWindowPrivate(window)) {
+ let pref = "browser.fixup.domainwhitelist." + asciiHost;
+ Services.prefs.setBoolPref(pref, true);
+ }
+ openUILinkIn(alternativeURI.spec, "current");
+ }
+ },
+ {
+ label: gNavigatorBundle.getString("keywordURIFixup.dismiss"),
+ accessKey: gNavigatorBundle.getString("keywordURIFixup.dismiss.accesskey"),
+ callback: function() {
+ let notification = notificationBox.getNotificationWithValue("keyword-uri-fixup");
+ notificationBox.removeNotification(notification, true);
+ }
+ }
+ ];
+ let notification =
+ notificationBox.appendNotification(message, "keyword-uri-fixup", null,
+ notificationBox.PRIORITY_INFO_HIGH,
+ buttons);
+ notification.persistence = 1;
+ };
+
+ try {
+ gDNSService.asyncResolve(hostName, 0, onLookupComplete, Services.tm.mainThread);
+ } catch (ex) {
+ // Do nothing if the URL is invalid (we don't want to show a notification in that case).
+ if (ex.result != Cr.NS_ERROR_UNKNOWN_HOST) {
+ // ... otherwise, report:
+ Cu.reportError(ex);
+ }
+ }
+}
+
+// A shared function used by both remote and non-remote browser XBL bindings to
+// load a URI or redirect it to the correct process.
+function _loadURIWithFlags(browser, uri, params) {
+ if (!uri) {
+ uri = "about:blank";
+ }
+ let triggeringPrincipal = params.triggeringPrincipal || null;
+ let flags = params.flags || 0;
+ let referrer = params.referrerURI;
+ let referrerPolicy = ('referrerPolicy' in params ? params.referrerPolicy :
+ Ci.nsIHttpChannel.REFERRER_POLICY_DEFAULT);
+ let postData = params.postData;
+
+ let wasRemote = browser.isRemoteBrowser;
+
+ let process = browser.isRemoteBrowser ? Ci.nsIXULRuntime.PROCESS_TYPE_CONTENT
+ : Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT;
+ let mustChangeProcess = gMultiProcessBrowser &&
+ !E10SUtils.canLoadURIInProcess(uri, process);
+ if ((!wasRemote && !mustChangeProcess) ||
+ (wasRemote && mustChangeProcess)) {
+ browser.inLoadURI = true;
+ }
+ try {
+ if (!mustChangeProcess) {
+ if (params.userContextId) {
+ browser.webNavigation.setOriginAttributesBeforeLoading({ userContextId: params.userContextId });
+ }
+
+ browser.webNavigation.loadURIWithOptions(uri, flags,
+ referrer, referrerPolicy,
+ postData, null, null, triggeringPrincipal);
+ } else {
+ // Check if the current browser is allowed to unload.
+ let {permitUnload, timedOut} = browser.permitUnload();
+ if (!timedOut && !permitUnload) {
+ return;
+ }
+
+ if (postData) {
+ postData = NetUtil.readInputStreamToString(postData, postData.available());
+ }
+
+ let loadParams = {
+ uri: uri,
+ triggeringPrincipal: triggeringPrincipal
+ ? gSerializationHelper.serializeToString(triggeringPrincipal)
+ : null,
+ flags: flags,
+ referrer: referrer ? referrer.spec : null,
+ referrerPolicy: referrerPolicy,
+ postData: postData
+ }
+
+ if (params.userContextId) {
+ loadParams.userContextId = params.userContextId;
+ }
+
+ LoadInOtherProcess(browser, loadParams);
+ }
+ } catch (e) {
+ // If anything goes wrong when switching remoteness, just switch remoteness
+ // manually and load the URI.
+ // We might lose history that way but at least the browser loaded a page.
+ // This might be necessary if SessionStore wasn't initialized yet i.e.
+ // when the homepage is a non-remote page.
+ if (mustChangeProcess) {
+ Cu.reportError(e);
+ gBrowser.updateBrowserRemotenessByURL(browser, uri);
+
+ if (params.userContextId) {
+ browser.webNavigation.setOriginAttributesBeforeLoading({ userContextId: params.userContextId });
+ }
+
+ browser.webNavigation.loadURIWithOptions(uri, flags, referrer, referrerPolicy,
+ postData, null, null, triggeringPrincipal);
+ } else {
+ throw e;
+ }
+ } finally {
+ if ((!wasRemote && !mustChangeProcess) ||
+ (wasRemote && mustChangeProcess)) {
+ browser.inLoadURI = false;
+ }
+ }
+}
+
+// Starts a new load in the browser first switching the browser to the correct
+// process
+function LoadInOtherProcess(browser, loadOptions, historyIndex = -1) {
+ let tab = gBrowser.getTabForBrowser(browser);
+ SessionStore.navigateAndRestore(tab, loadOptions, historyIndex);
+}
+
+// Called when a docshell has attempted to load a page in an incorrect process.
+// This function is responsible for loading the page in the correct process.
+function RedirectLoad({ target: browser, data }) {
+ // We should only start the redirection if the browser window has finished
+ // starting up. Otherwise, we should wait until the startup is done.
+ if (gBrowserInit.delayedStartupFinished) {
+ LoadInOtherProcess(browser, data.loadOptions, data.historyIndex);
+ } else {
+ let delayedStartupFinished = (subject, topic) => {
+ if (topic == "browser-delayed-startup-finished" &&
+ subject == window) {
+ Services.obs.removeObserver(delayedStartupFinished, topic);
+ LoadInOtherProcess(browser, data.loadOptions, data.historyIndex);
+ }
+ };
+ Services.obs.addObserver(delayedStartupFinished,
+ "browser-delayed-startup-finished",
+ false);
+ }
+}
+
+addEventListener("DOMContentLoaded", function onDCL() {
+ removeEventListener("DOMContentLoaded", onDCL);
+
+ // There are some windows, like macBrowserOverlay.xul, that
+ // load browser.js, but never load tabbrowser.xml. We can ignore
+ // those cases.
+ if (!gBrowser || !gBrowser.updateBrowserRemoteness) {
+ return;
+ }
+
+ 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();
+
+ let initBrowser =
+ document.getAnonymousElementByAttribute(gBrowser, "anonid", "initialBrowser");
+
+ // The window's first argument is a tab if and only if we are swapping tabs.
+ // We must set the browser's usercontextid before updateBrowserRemoteness(),
+ // so that the newly created remote tab child has the correct usercontextid.
+ if (window.arguments) {
+ let tabToOpen = window.arguments[0];
+ if (tabToOpen instanceof XULElement && tabToOpen.hasAttribute("usercontextid")) {
+ initBrowser.setAttribute("usercontextid", tabToOpen.getAttribute("usercontextid"));
+ }
+ }
+
+ gBrowser.updateBrowserRemoteness(initBrowser, gMultiProcessBrowser);
+});
+
+var gBrowserInit = {
+ delayedStartupFinished: false,
+
+ onLoad: function() {
+ gBrowser.addEventListener("DOMUpdatePageReport", gPopupBlockerObserver, false);
+
+ Services.obs.addObserver(gPluginHandler.NPAPIPluginCrashed, "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.
+ DOMLinkHandler.init();
+ gPageStyleMenu.init();
+ LanguageDetectionListener.init();
+ BrowserOnClick.init();
+ FeedHandler.init();
+ DevEdition.init();
+ AboutPrivateBrowsingListener.init();
+ TrackingProtection.init();
+ RefreshBlocker.init();
+ CaptivePortalWatcher.init();
+
+ let mm = window.getGroupMessageManager("browsers");
+ mm.loadFrameScript("chrome://browser/content/tab-content.js", true);
+ mm.loadFrameScript("chrome://browser/content/content.js", true);
+ mm.loadFrameScript("chrome://global/content/manifestMessages.js", true);
+
+ // initialize observers and listeners
+ // and give C++ access to gBrowser
+ XULBrowserWindow.init();
+
+ window.messageManager.addMessageListener("Browser:LoadURI", RedirectLoad);
+
+ if (!gMultiProcessBrowser) {
+ // There is a Content:Click message manually sent from content.
+ Cc["@mozilla.org/eventlistenerservice;1"]
+ .getService(Ci.nsIEventListenerService)
+ .addSystemEventListener(gBrowser, "click", contentAreaClick, true);
+ }
+
+ // hook up UI through progress listener
+ gBrowser.addProgressListener(window.XULBrowserWindow);
+ gBrowser.addTabsProgressListener(window.TabsProgressListener);
+
+ // setup simple gestures support
+ gGestureSupport.init(true);
+
+ // setup history swipe animation
+ gHistorySwipeAnimation.init();
+
+ SidebarUI.init();
+
+ // Certain kinds of automigration rely on this notification to complete
+ // their tasks BEFORE the browser window is shown. SessionStore uses it to
+ // restore tabs into windows AFTER important parts like gMultiProcessBrowser
+ // have been initialized.
+ Services.obs.notifyObservers(window, "browser-window-before-show", "");
+
+ // Set a sane starting width/height for all resolutions on new profiles.
+ if (!document.documentElement.hasAttribute("width")) {
+ const TARGET_WIDTH = 1280;
+ const TARGET_HEIGHT = 1040;
+ let width = Math.min(screen.availWidth * .9, TARGET_WIDTH);
+ let height = Math.min(screen.availHeight * .9, TARGET_HEIGHT);
+
+ document.documentElement.setAttribute("width", width);
+ document.documentElement.setAttribute("height", height);
+
+ if (width < TARGET_WIDTH && height < TARGET_HEIGHT) {
+ document.documentElement.setAttribute("sizemode", "maximized");
+ }
+ }
+
+ if (!window.toolbar.visible) {
+ // adjust browser UI for popups
+ gURLBar.setAttribute("readonly", "true");
+ gURLBar.setAttribute("enablehistory", "false");
+ }
+
+ // Misc. inits.
+ TabletModeUpdater.init();
+ CombinedStopReload.init();
+ gPrivateBrowsingUI.init();
+
+ if (window.matchMedia("(-moz-os-version: windows-win8)").matches &&
+ window.matchMedia("(-moz-windows-default-theme)").matches) {
+ let windowFrameColor = new Color(...Cu.import("resource:///modules/Windows8WindowFrameColor.jsm", {})
+ .Windows8WindowFrameColor.get());
+ // Check if window frame color is dark.
+ if ((windowFrameColor.r * 2 +
+ windowFrameColor.g * 5 +
+ windowFrameColor.b) <= 128 * 8) {
+ document.documentElement.setAttribute("darkwindowframe", "true");
+ }
+ }
+
+ ToolbarIconColor.init();
+
+ // Wait until chrome is painted before executing code not critical to making the window visible
+ this._boundDelayedStartup = this._delayedStartup.bind(this);
+ window.addEventListener("MozAfterPaint", this._boundDelayedStartup);
+
+ this._loadHandled = true;
+ },
+
+ _cancelDelayedStartup: function () {
+ window.removeEventListener("MozAfterPaint", this._boundDelayedStartup);
+ this._boundDelayedStartup = null;
+ },
+
+ _delayedStartup: function() {
+ let tmp = {};
+ Cu.import("resource://gre/modules/TelemetryTimestamps.jsm", tmp);
+ let TelemetryTimestamps = tmp.TelemetryTimestamps;
+ TelemetryTimestamps.add("delayedStartupStarted");
+
+ this._cancelDelayedStartup();
+
+ // We need to set the OfflineApps message listeners up before we
+ // load homepages, which might need them.
+ OfflineApps.init();
+
+ // This pageshow listener needs to be registered before we may call
+ // swapBrowsersAndCloseOther() to receive pageshow events fired by that.
+ let mm = window.messageManager;
+ mm.addMessageListener("PageVisibility:Show", function(message) {
+ if (message.target == gBrowser.selectedBrowser) {
+ setTimeout(pageShowEventHandlers, 0, message.data.persisted);
+ }
+ });
+
+ gBrowser.addEventListener("AboutTabCrashedLoad", function(event) {
+ let ownerDoc = event.originalTarget;
+
+ if (!ownerDoc.documentURI.startsWith("about:tabcrashed")) {
+ return;
+ }
+
+ let browser = gBrowser.getBrowserForDocument(event.target);
+ // Reset the zoom for the tabcrashed page.
+ ZoomManager.setZoomForBrowser(browser, 1);
+ }, false, true);
+
+ gBrowser.addEventListener("InsecureLoginFormsStateChange", function() {
+ gIdentityHandler.refreshForInsecureLoginForms();
+ });
+
+ let uriToLoad = this._getUriToLoad();
+ if (uriToLoad && uriToLoad != "about:blank") {
+ if (uriToLoad instanceof Ci.nsIArray) {
+ let count = uriToLoad.length;
+ let specs = [];
+ for (let i = 0; i < count; i++) {
+ let urisstring = uriToLoad.queryElementAt(i, 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.
+ let tabToOpen = uriToLoad;
+
+ // If this tab was passed as a window argument, clear the
+ // reference to it from the arguments array.
+ if (window.arguments[0] == tabToOpen) {
+ window.arguments[0] = null;
+ }
+
+ // Stop the about:blank load
+ gBrowser.stop();
+ // make sure it has a docshell
+ gBrowser.docShell;
+
+ // We must set usercontextid before updateBrowserRemoteness()
+ // so that the newly created remote tab child has correct usercontextid
+ if (tabToOpen.hasAttribute("usercontextid")) {
+ let usercontextid = tabToOpen.getAttribute("usercontextid");
+ gBrowser.selectedBrowser.setAttribute("usercontextid", usercontextid);
+ }
+
+ // If the browser that we're swapping in was remote, then we'd better
+ // be able to support remote browsers, and then make our selectedTab
+ // remote.
+ try {
+ if (tabToOpen.linkedBrowser.isRemoteBrowser) {
+ if (!gMultiProcessBrowser) {
+ throw new Error("Cannot drag a remote browser into a window " +
+ "without the remote tabs load context.");
+ }
+ gBrowser.updateBrowserRemoteness(gBrowser.selectedBrowser, true);
+ } else if (gBrowser.selectedBrowser.isRemoteBrowser) {
+ // If the browser is remote, then it's implied that
+ // gMultiProcessBrowser is true. We need to flip the remoteness
+ // of this tab to false in order for the tab drag to work.
+ gBrowser.updateBrowserRemoteness(gBrowser.selectedBrowser, false);
+ }
+ gBrowser.swapBrowsersAndCloseOther(gBrowser.selectedTab, tabToOpen);
+ } catch (e) {
+ Cu.reportError(e);
+ }
+ }
+ // window.arguments[2]: referrer (nsIURI | string)
+ // [3]: postData (nsIInputStream)
+ // [4]: allowThirdPartyFixup (bool)
+ // [5]: referrerPolicy (int)
+ // [6]: userContextId (int)
+ // [7]: originPrincipal (nsIPrincipal)
+ // [8]: 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);
+ let userContextId = (window.arguments[6] != undefined ?
+ window.arguments[6] : Ci.nsIScriptSecurityManager.DEFAULT_USER_CONTEXT_ID);
+ loadURI(uriToLoad, referrerURI, window.arguments[3] || null,
+ window.arguments[4] || false, referrerPolicy, userContextId,
+ // pass the origin principal (if any) and force its use to create
+ // an initial about:blank viewer if present:
+ window.arguments[7], !!window.arguments[7], window.arguments[8]);
+ 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);
+ }
+ }
+
+#ifdef MOZ_SAFE_BROWSING
+ // Bug 778855 - Perf regression if we do this here. To be addressed in bug 779008.
+ setTimeout(function() { SafeBrowsing.init(); }, 2000);
+#endif
+
+ Services.obs.addObserver(gIdentityHandler, "perm-changed", false);
+ 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-confirmation", false);
+ Services.obs.addObserver(gXPInstallObserver, "addon-install-complete", false);
+ window.messageManager.addMessageListener("Browser:URIFixup", gKeywordURIFixup);
+
+ BrowserOffline.init();
+ IndexedDBPromptHelper.init();
+
+ // 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();
+ PanelUI.init();
+ LightweightThemeListener.init();
+
+ Services.telemetry.getHistogramById("E10S_WINDOW").add(gMultiProcessBrowser);
+
+ SidebarUI.startDelayedLoad();
+
+ UpdateUrlbarSearchSplitterState();
+
+ if (!(isBlankPageURL(uriToLoad) || uriToLoad == "about:privatebrowsing") ||
+ !focusAndSelectUrlBar()) {
+ if (gBrowser.selectedBrowser.isRemoteBrowser) {
+ // If the initial browser is remote, in order to optimize for first paint,
+ // we'll defer switching focus to that browser until it has painted.
+ let focusedElement = document.commandDispatcher.focusedElement;
+ let mm = window.messageManager;
+ mm.addMessageListener("Browser:FirstPaint", function onFirstPaint() {
+ mm.removeMessageListener("Browser:FirstPaint", onFirstPaint);
+ // If focus didn't move while we were waiting for first paint, we're okay
+ // to move to the browser.
+ if (document.commandDispatcher.focusedElement == focusedElement) {
+ gBrowser.selectedBrowser.focus();
+ }
+ });
+ } else {
+ // If the initial browser is not remote, we can focus the browser
+ // immediately with no paint performance impact.
+ gBrowser.selectedBrowser.focus();
+ }
+ }
+
+ // Enable/Disable auto-hide tabbar
+ gBrowser.tabContainer.updateVisibility();
+
+ BookmarkingUI.init();
+ AutoShowBookmarksToolbar.init();
+
+ gPrefService.addObserver(gHomeButton.prefDomain, gHomeButton, false);
+
+ var homeButton = document.getElementById("home-button");
+ gHomeButton.updateTooltip(homeButton);
+
+ let safeMode = document.getElementById("helpSafeMode");
+ if (Services.appinfo.inSafeMode) {
+ safeMode.label = safeMode.getAttribute("stoplabel");
+ safeMode.accesskey = safeMode.getAttribute("stopaccesskey");
+ }
+
+ // 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 (!getBoolPref("ui.click_hold_context_menus", false))
+ SetClickAndHoldHandlers();
+
+ let NP = {};
+ Cu.import("resource:///modules/NetworkPrioritizer.jsm", NP);
+ NP.trackBrowserWindow(window);
+
+ PlacesToolbarHelper.init();
+
+ ctrlTab.readPref();
+ gPrefService.addObserver(ctrlTab.prefName, ctrlTab, 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 initializing 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();
+
+ if (AppConstants.platform != "macosx") {
+ updateEditUIVisibility();
+ let placesContext = document.getElementById("placesContext");
+ placesContext.addEventListener("popupshowing", updateEditUIVisibility, false);
+ placesContext.addEventListener("popuphiding", updateEditUIVisibility, false);
+ }
+
+ LightWeightThemeWebInstaller.init();
+
+ if (Win7Features)
+ Win7Features.onOpenWindow();
+
+ PointerlockFsWarning.init();
+ FullScreen.init();
+ PointerLock.init();
+
+ // initialize the sync UI
+ gSyncUI.init();
+ gFxAccounts.init();
+
+ if (AppConstants.MOZ_DATA_REPORTING)
+ gDataNotificationInfoBar.init();
+
+ gBrowserThumbnails.init();
+
+ gMenuButtonBadgeManager.init();
+
+ gMenuButtonUpdateBadge.init();
+
+ window.addEventListener("mousemove", MousePosTracker, false);
+ window.addEventListener("dragover", MousePosTracker, false);
+
+ gNavToolbox.addEventListener("customizationstarting", CustomizationHandler);
+ gNavToolbox.addEventListener("customizationchange", CustomizationHandler);
+ gNavToolbox.addEventListener("customizationending", CustomizationHandler);
+
+ // 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);
+ }
+
+ // Delay this a minute because there's no rush
+ setTimeout(() => {
+ this.gmpInstallManager = new GMPInstallManager();
+ // We don't really care about the results, if someone is interested they
+ // can check the log.
+ this.gmpInstallManager.simpleCheckAndInstall().then(null, () => {});
+ }, 1000 * 60);
+
+ // Report via telemetry whether we're able to play MP4/H.264/AAC video.
+ // We suspect that some Windows users have a broken or have not installed
+ // Windows Media Foundation, and we'd like to know how many. We'd also like
+ // to know how good our coverage is on other platforms.
+ // Note: we delay by 90 seconds reporting this, as calling canPlayType()
+ // on Windows will cause DLLs to load, i.e. cause disk I/O.
+ setTimeout(() => {
+ let v = document.createElementNS("http://www.w3.org/1999/xhtml", "video");
+ let aacWorks = v.canPlayType("audio/mp4") != "";
+ Services.telemetry.getHistogramById("VIDEO_CAN_CREATE_AAC_DECODER").add(aacWorks);
+ let h264Works = v.canPlayType("video/mp4") != "";
+ Services.telemetry.getHistogramById("VIDEO_CAN_CREATE_H264_DECODER").add(h264Works);
+ }, 90 * 1000);
+
+ SessionStore.promiseInitialized.then(() => {
+ // Bail out if the window has been closed in the meantime.
+ if (window.closed) {
+ return;
+ }
+
+ // Enable the Restore Last Session command if needed
+ RestoreLastSessionObserver.init();
+
+ // Start monitoring slow add-ons
+ AddonWatcher.init();
+
+ // Telemetry for master-password - we do this after 5 seconds as it
+ // can cause IO if NSS/PSM has not already initialized.
+ setTimeout(() => {
+ if (window.closed) {
+ return;
+ }
+ let secmodDB = Cc["@mozilla.org/security/pkcs11moduledb;1"]
+ .getService(Ci.nsIPKCS11ModuleDB);
+ let slot = secmodDB.findSlotByName("");
+ let mpEnabled = slot &&
+ slot.status != Ci.nsIPKCS11Slot.SLOT_UNINITIALIZED &&
+ slot.status != Ci.nsIPKCS11Slot.SLOT_READY;
+ if (mpEnabled) {
+ Services.telemetry.getHistogramById("MASTER_PASSWORD_ENABLED").add(mpEnabled);
+ }
+ }, 5000);
+
+ PanicButtonNotifier.init();
+ });
+
+ gBrowser.tabContainer.addEventListener("TabSelect", function() {
+ for (let panel of document.querySelectorAll("panel[tabspecific='true']")) {
+ if (panel.state == "open") {
+ panel.hidePopup();
+ }
+ }
+ });
+
+ this.delayedStartupFinished = true;
+
+ Services.obs.notifyObservers(window, "browser-delayed-startup-finished", "");
+ TelemetryTimestamps.add("delayedStartupFinished");
+ },
+
+ // Returns the URI(s) to load at startup.
+ _getUriToLoad: function () {
+ // window.arguments[0]: URI to load (string), or an nsIArray 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).
+
+ CombinedStopReload.uninit();
+
+ gGestureSupport.init(false);
+
+ gHistorySwipeAnimation.uninit();
+
+ FullScreen.uninit();
+
+ gFxAccounts.uninit();
+
+ Services.obs.removeObserver(gPluginHandler.NPAPIPluginCrashed, "plugin-crashed");
+
+ try {
+ gBrowser.removeProgressListener(window.XULBrowserWindow);
+ gBrowser.removeTabsProgressListener(window.TabsProgressListener);
+ } catch (ex) {
+ }
+
+ PlacesToolbarHelper.uninit();
+
+ BookmarkingUI.uninit();
+
+ TabsInTitlebar.uninit();
+
+ ToolbarIconColor.uninit();
+
+ TabletModeUpdater.uninit();
+
+ gTabletModePageCounter.finish();
+
+ BrowserOnClick.uninit();
+
+ FeedHandler.uninit();
+
+ DevEdition.uninit();
+
+ TrackingProtection.uninit();
+
+ RefreshBlocker.uninit();
+
+ CaptivePortalWatcher.uninit();
+
+ gMenuButtonUpdateBadge.uninit();
+
+ gMenuButtonBadgeManager.uninit();
+
+ SidebarUI.uninit();
+
+ // 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);
+ ctrlTab.uninit();
+ gBrowserThumbnails.uninit();
+ FullZoom.destroy();
+
+ Services.obs.removeObserver(gIdentityHandler, "perm-changed");
+ 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-confirmation");
+ Services.obs.removeObserver(gXPInstallObserver, "addon-install-complete");
+ window.messageManager.removeMessageListener("Browser:URIFixup", gKeywordURIFixup);
+ window.messageManager.removeMessageListener("Browser:LoadURI", RedirectLoad);
+
+ try {
+ gPrefService.removeObserver(gHomeButton.prefDomain, gHomeButton);
+ } catch (ex) {
+ Cu.reportError(ex);
+ }
+
+ if (this.gmpInstallManager) {
+ this.gmpInstallManager.uninit();
+ }
+
+ BrowserOffline.uninit();
+ IndexedDBPromptHelper.uninit();
+ LightweightThemeListener.uninit();
+ PanelUI.uninit();
+ AutoShowBookmarksToolbar.uninit();
+ }
+
+ // Final window teardown, do this last.
+ 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;
+ },
+};
+
+if (AppConstants.platform == "macosx") {
+ // nonBrowserWindowStartup(), nonBrowserWindowDelayedStartup(), and
+ // nonBrowserWindowShutdown() are used for non-browser windows in
+ // macBrowserOverlay
+ gBrowserInit.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'];
+ 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);
+ };
+
+ gBrowserInit.nonBrowserWindowDelayedStartup = function() {
+ this._delayedStartupTimeoutId = null;
+
+ // initialise the offline listener
+ BrowserOffline.init();
+
+ // initialize the private browsing UI
+ gPrivateBrowsingUI.init();
+
+ // initialize the sync UI
+ gSyncUI.init();
+ };
+
+ gBrowserInit.nonBrowserWindowShutdown = function() {
+ let dockSupport = Cc["@mozilla.org/widget/macdocksupport;1"]
+ .getService(Ci.nsIMacDockSupport);
+ dockSupport.dockMenu = null;
+
+ // 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();
+ };
+}
+
+
+/* Legacy global init functions */
+var BrowserStartup = gBrowserInit.onLoad.bind(gBrowserInit);
+var BrowserShutdown = gBrowserInit.onUnload.bind(gBrowserInit);
+
+if (AppConstants.platform == "macosx") {
+ var nonBrowserWindowStartup = gBrowserInit.nonBrowserWindowStartup.bind(gBrowserInit);
+ var nonBrowserWindowDelayedStartup = gBrowserInit.nonBrowserWindowDelayedStartup.bind(gBrowserInit);
+ var nonBrowserWindowShutdown = gBrowserInit.nonBrowserWindowShutdown.bind(gBrowserInit);
+}
+
+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":
+ SidebarUI.toggle("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.printWindow(gBrowser.selectedBrowser.outerWindowID,
+ gBrowser.selectedBrowser);
+ break;
+ case "Save":
+ saveBrowser(gBrowser.selectedBrowser);
+ break;
+ case "SendMail":
+ MailIntegration.sendLinkForBrowser(gBrowser.selectedBrowser);
+ 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.
+
+ let historyindex = aEvent.target.getAttribute("historyindex");
+ duplicateTabIn(gBrowser.selectedTab, where, Number(historyindex));
+ 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) {
+ let metaKeyPressed = AppConstants.platform == "macosx"
+ ? aEvent.metaKey
+ : aEvent.ctrlKey;
+ var backgroundTabModifier = aEvent.button == 1 || metaKeyPressed;
+
+ if (aEvent.shiftKey && !backgroundTabModifier) {
+ BrowserReloadSkipCache();
+ return;
+ }
+
+ let where = whereToOpenLink(aEvent, false, true);
+ if (where == "current")
+ BrowserReload();
+ else
+ duplicateTabIn(gBrowser.selectedTab, where);
+}
+
+function BrowserReload() {
+ if (gBrowser.currentURI.schemeIs("view-source")) {
+ // Bug 1167797: For view source, we always skip the cache
+ return BrowserReloadSkipCache();
+ }
+ 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 = getBoolPref("browser.tabs.loadBookmarksInBackground", false);
+ gBrowser.loadTabs(urls, loadInBackground);
+ break;
+ case "window":
+ OpenBrowserWindow();
+ break;
+ }
+}
+
+function loadOneOrMoreURIs(aURIString)
+{
+ // 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;
+ }
+
+ // 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() {
+ // In customize mode, the url bar is disabled. If a new tab is opened or the
+ // user switches to a different tab, this function gets called before we've
+ // finished leaving customize mode, and the url bar will still be disabled.
+ // We can't focus it when it's disabled, so we need to re-run ourselves when
+ // we've finished leaving customize mode.
+ if (CustomizationHandler.isExitingCustomizeMode) {
+ gNavToolbox.addEventListener("aftercustomization", function afterCustomize() {
+ gNavToolbox.removeEventListener("aftercustomization", afterCustomize);
+ focusAndSelectUrlBar();
+ });
+
+ return true;
+ }
+
+ if (gURLBar) {
+ if (window.fullScreen)
+ FullScreen.showNavToolbox();
+
+ gURLBar.select();
+ if (document.activeElement == gURLBar.inputField)
+ return true;
+ }
+ return false;
+}
+
+function openLocation() {
+ if (focusAndSelectUrlBar())
+ return;
+
+ 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
+ window.openDialog("chrome://browser/content/", "_blank",
+ "chrome,all,dialog=no", BROWSER_NEW_TAB_URL);
+ }
+ }
+}
+
+function BrowserOpenTab(event) {
+ let where = "tab";
+ let relatedToCurrent = false;
+
+ if (event) {
+ where = whereToOpenLink(event, false, true);
+
+ switch (where) {
+ case "tab":
+ case "tabshifted":
+ // When accel-click or middle-click are used, open the new tab as
+ // related to the current tab.
+ relatedToCurrent = true;
+ break;
+ case "current":
+ where = "tab";
+ break;
+ }
+ }
+
+ openUILinkIn(BROWSER_NEW_TAB_URL, where, { relatedToCurrent });
+}
+
+/* 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() {
+ // If we're not a browser window, just close the window
+ if (window.location.href != getBrowserURL()) {
+ closeWindow(true);
+ return;
+ }
+
+ // 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,
+ userContextId, originPrincipal, forceAboutBlankViewerInCurrent,
+ triggeringPrincipal) {
+ try {
+ openLinkIn(uri, "current",
+ { referrerURI: referrer,
+ referrerPolicy: referrerPolicy,
+ postData: postData,
+ allowThirdPartyFixup: allowThirdPartyFixup,
+ userContextId: userContextId,
+ originPrincipal,
+ triggeringPrincipal,
+ forceAboutBlankViewerInCurrent,
+ });
+ } catch (e) {}
+}
+
+/**
+ * Given a string, will generate a more appropriate urlbar value if a Places
+ * keyword or a search alias is found at the beginning of it.
+ *
+ * @param url
+ * A string that may begin with a keyword or an alias.
+ *
+ * @return {Promise}
+ * @resolves { url, postData, mayInheritPrincipal }. If it's not possible
+ * to discern a keyword or an alias, url will be the input string.
+ */
+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;
+ // Split on the first whitespace.
+ let [keyword, param = ""] = url.trim().split(/\s(.+)/, 2);
+
+ if (!keyword) {
+ return { url, postData, mayInheritPrincipal };
+ }
+
+ let engine = Services.search.getEngineByAlias(keyword);
+ if (engine) {
+ let submission = engine.getSubmission(param, null, "keyword");
+ return { url: submission.uri.spec,
+ postData: submission.postData,
+ mayInheritPrincipal };
+ }
+
+ // A corrupt Places database could make this throw, breaking navigation
+ // from the location bar.
+ let entry = null;
+ try {
+ entry = yield PlacesUtils.keywords.fetch(keyword);
+ } catch (ex) {
+ Cu.reportError(`Unable to fetch Places keyword "${keyword}": ${ex}`);
+ }
+ if (!entry || !entry.url) {
+ // This is not a Places keyword.
+ return { url, postData, mayInheritPrincipal };
+ }
+
+ try {
+ [url, postData] =
+ yield BrowserUtils.parseUrlAndPostData(entry.url.href,
+ entry.postData,
+ param);
+ if (postData) {
+ postData = getPostDataStream(postData);
+ }
+
+ // Since this URL came from a bookmark, it's safe to let it inherit the
+ // current document's principal.
+ mayInheritPrincipal = true;
+ } catch (ex) {
+ // It was not possible to bind the param, just use the original url value.
+ }
+
+ return { url, postData, mayInheritPrincipal };
+ }).then(data => {
+ if (callback) {
+ callback(data);
+ }
+ return data;
+ });
+}
+
+function getPostDataStream(aPostDataString,
+ aType = "application/x-www-form-urlencoded") {
+ let dataStream = Cc["@mozilla.org/io/string-input-stream;1"]
+ .createInstance(Ci.nsIStringInputStream);
+ dataStream.data = aPostDataString;
+
+ let 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;
+}
+
+/**
+ * Open the View Source dialog.
+ *
+ * @param aArgsOrDocument
+ * Either an object or a Document. Passing a Document is deprecated,
+ * and is not supported with e10s. This function will throw if
+ * aArgsOrDocument is a CPOW.
+ *
+ * If aArgsOrDocument is an object, that object can take the
+ * following properties:
+ *
+ * URL (required):
+ * A string URL for the page we'd like to view the source of.
+ * browser (optional):
+ * The browser containing the document that we would like to view the
+ * source of. This is required if outerWindowID is passed.
+ * outerWindowID (optional):
+ * The outerWindowID of the content window containing the document that
+ * we want to view the source of. You only need to provide this if you
+ * want to attempt to retrieve the document source from the network
+ * cache.
+ * lineNumber (optional):
+ * The line number to focus on once the source is loaded.
+ */
+function BrowserViewSourceOfDocument(aArgsOrDocument) {
+ let args;
+
+ if (aArgsOrDocument instanceof Document) {
+ let doc = aArgsOrDocument;
+ // Deprecated API - callers should pass args object instead.
+ if (Cu.isCrossProcessWrapper(doc)) {
+ throw new Error("BrowserViewSourceOfDocument cannot accept a CPOW " +
+ "as a document.");
+ }
+
+ 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 = () => {
+ let inTab = Services.prefs.getBoolPref("view_source.tab");
+ if (inTab) {
+ let tabBrowser = gBrowser;
+ let forceNotRemote = false;
+ if (!tabBrowser) {
+ if (!args.browser) {
+ throw new Error("BrowserViewSourceOfDocument should be passed the " +
+ "subject browser if called from a window without " +
+ "gBrowser defined.");
+ }
+ forceNotRemote = !args.browser.isRemoteBrowser;
+ } else {
+ // Some internal URLs (such as specific chrome: and about: URLs that are
+ // not yet remote ready) cannot be loaded in a remote browser. View
+ // source in tab expects the new view source browser's remoteness to match
+ // that of the original URL, so disable remoteness if necessary for this
+ // URL.
+ let contentProcess = Ci.nsIXULRuntime.PROCESS_TYPE_CONTENT
+ forceNotRemote =
+ gMultiProcessBrowser &&
+ !E10SUtils.canLoadURIInProcess(args.URL, contentProcess)
+ }
+
+ // In the case of popups, we need to find a non-popup browser window.
+ if (!tabBrowser || !window.toolbar.visible) {
+ // This returns only non-popup browser windows by default.
+ let browserWindow = RecentWindow.getMostRecentBrowserWindow();
+ tabBrowser = browserWindow.gBrowser;
+ }
+
+ // `viewSourceInBrowser` will load the source content from the page
+ // descriptor for the tab (when possible) or fallback to the network if
+ // that fails. Either way, the view source module will manage the tab's
+ // location, so use "about:blank" here to avoid unnecessary redundant
+ // requests.
+ let tab = tabBrowser.loadOneTab("about:blank", {
+ relatedToCurrent: true,
+ inBackground: false,
+ forceNotRemote,
+ relatedBrowser: args.browser
+ });
+ args.viewSourceBrowser = tabBrowser.getBrowserForTab(tab);
+ top.gViewSourceUtils.viewSourceInBrowser(args);
+ } else {
+ 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();
+ }
+}
+
+/**
+ * Opens the View Source dialog for the source loaded in the root
+ * top-level document of the browser. This is really just a
+ * convenience wrapper around BrowserViewSourceOfDocument.
+ *
+ * @param browser
+ * The browser that we want to load the source of.
+ */
+function BrowserViewSource(browser) {
+ BrowserViewSourceOfDocument({
+ browser: browser,
+ outerWindowID: browser.outerWindowID,
+ URL: browser.currentURI.spec,
+ });
+}
+
+// documentURL - URL of the document to view, 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
+// frameOuterWindowID - the id of the frame that the context menu opened in; can be null/omitted
+// browser - the browser containing the document we're interested in inspecting; can be null/omitted
+function BrowserPageInfo(documentURL, initialTab, imageElement, frameOuterWindowID, browser) {
+ if (documentURL instanceof HTMLDocument) {
+ Deprecated.warning("Please pass the location URL instead of the document " +
+ "to BrowserPageInfo() as the first argument.",
+ "https://bugzilla.mozilla.org/show_bug.cgi?id=1238180");
+ documentURL = documentURL.location;
+ }
+
+ let args = { initialTab, imageElement, frameOuterWindowID, browser };
+ var windows = Services.wm.getEnumerator("Browser:page-info");
+
+ documentURL = documentURL || window.gBrowser.selectedBrowser.currentURI.spec;
+
+ // Check for windows matching the url
+ while (windows.hasMoreElements()) {
+ var currentWindow = windows.getNext();
+ if (currentWindow.closed) {
+ continue;
+ }
+ 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
+ // 1. only if there's no opener (bug 370555).
+ // 2. if remote newtab is enabled and it's the default remote newtab page
+ let defaultRemoteURL = gAboutNewTabService.remoteEnabled &&
+ uri.spec === gAboutNewTabService.newTabURL;
+ if ((gInitialPages.includes(uri.spec) || defaultRemoteURL) &&
+ checkEmptyPageOrigin(gBrowser.selectedBrowser, uri)) {
+ value = "";
+ } else {
+ // We should deal with losslessDecodeURI throwing for exotic URIs
+ try {
+ value = losslessDecodeURI(uri);
+ } catch (ex) {
+ value = "about:blank";
+ }
+ }
+
+ 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;
+ if (scheme == "moz-action")
+ throw new Error("losslessDecodeURI should never get a moz-action URI");
+
+ var value = aURI.spec;
+
+ let decodeASCIIOnly = !["https", "http", "file", "ftp"].includes(scheme);
+ // 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 select whitespace so that it doesn't get eaten
+ // away by the location bar (bug 410726). Re-encode all
+ // adjacent whitespace, to prevent spoofing attempts where
+ // invisible characters would push part of the URL to
+ // overflow the location bar (bug 1395508).
+ .replace(/%(?!3B|2F|3F|3A|40|26|3D|2B|24|2C|23)|[\r\n\t]|\s(?=\s)|\s$/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\u061c\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");
+
+ if (document.documentElement.getAttribute("customizing") == "true") {
+ if (splitter) {
+ splitter.remove();
+ }
+ return;
+ }
+
+ // If the splitter is already in the right place, we don't need to do anything:
+ if (splitter &&
+ ((splitter.nextSibling == searchbar && splitter.previousSibling == urlbar) ||
+ (splitter.nextSibling == urlbar && splitter.previousSibling == searchbar))) {
+ return;
+ }
+
+ var ibefore = null;
+ if (urlbar && searchbar) {
+ if (urlbar.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.setAttribute("overflows", "false");
+ splitter.className = "chromeclass-toolbar-additional";
+ }
+ urlbar.parentNode.insertBefore(splitter, ibefore);
+ } else if (splitter)
+ splitter.parentNode.removeChild(splitter);
+}
+
+function UpdatePageProxyState()
+{
+ if (gURLBar && gURLBar.value != gLastValidURLStr)
+ SetPageProxyState("invalid");
+}
+
+function SetPageProxyState(aState)
+{
+ if (!gURLBar)
+ return;
+
+ gURLBar.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);
+ } else if (aState == "invalid") {
+ gURLBar.removeEventListener("input", UpdatePageProxyState, false);
+ }
+}
+
+function PageProxyClickHandler(aEvent)
+{
+ if (aEvent.button == 1 && gPrefService.getBoolPref("middlemouse.paste"))
+ middleMousePaste(aEvent);
+}
+
+var gMenuButtonBadgeManager = {
+ BADGEID_APPUPDATE: "update",
+ BADGEID_DOWNLOAD: "download",
+ BADGEID_FXA: "fxa",
+
+ fxaBadge: null,
+ downloadBadge: null,
+ appUpdateBadge: null,
+
+ init: function () {
+ PanelUI.panel.addEventListener("popupshowing", this, true);
+ },
+
+ uninit: function () {
+ PanelUI.panel.removeEventListener("popupshowing", this, true);
+ },
+
+ handleEvent: function (e) {
+ if (e.type === "popupshowing") {
+ this.clearBadges();
+ }
+ },
+
+ _showBadge: function () {
+ let badgeToShow = this.downloadBadge || this.appUpdateBadge || this.fxaBadge;
+
+ if (badgeToShow) {
+ PanelUI.menuButton.setAttribute("badge-status", badgeToShow);
+ } else {
+ PanelUI.menuButton.removeAttribute("badge-status");
+ }
+ },
+
+ _changeBadge: function (badgeId, badgeStatus = null) {
+ if (badgeId == this.BADGEID_APPUPDATE) {
+ this.appUpdateBadge = badgeStatus;
+ } else if (badgeId == this.BADGEID_DOWNLOAD) {
+ this.downloadBadge = badgeStatus;
+ } else if (badgeId == this.BADGEID_FXA) {
+ this.fxaBadge = badgeStatus;
+ } else {
+ Cu.reportError("The badge ID '" + badgeId + "' is unknown!");
+ }
+ this._showBadge();
+ },
+
+ addBadge: function (badgeId, badgeStatus) {
+ if (!badgeStatus) {
+ Cu.reportError("badgeStatus must be defined");
+ return;
+ }
+ this._changeBadge(badgeId, badgeStatus);
+ },
+
+ removeBadge: function (badgeId) {
+ this._changeBadge(badgeId);
+ },
+
+ clearBadges: function () {
+ this.appUpdateBadge = null;
+ this.downloadBadge = null;
+ this.fxaBadge = null;
+ this._showBadge();
+ }
+};
+
+// Setup the hamburger button badges for updates, if enabled.
+var gMenuButtonUpdateBadge = {
+ enabled: false,
+ badgeWaitTime: 0,
+ timer: null,
+ cancelObserverRegistered: false,
+
+ init: function () {
+ try {
+ this.enabled = Services.prefs.getBoolPref("app.update.badge");
+ } catch (e) {}
+ if (this.enabled) {
+ try {
+ this.badgeWaitTime = Services.prefs.getIntPref("app.update.badgeWaitTime");
+ } catch (e) {
+ this.badgeWaitTime = 345600; // 4 days
+ }
+ Services.obs.addObserver(this, "update-staged", false);
+ Services.obs.addObserver(this, "update-downloaded", false);
+ }
+ },
+
+ uninit: function () {
+ if (this.timer)
+ this.timer.cancel();
+ if (this.enabled) {
+ Services.obs.removeObserver(this, "update-staged");
+ Services.obs.removeObserver(this, "update-downloaded");
+ this.enabled = false;
+ }
+ if (this.cancelObserverRegistered) {
+ Services.obs.removeObserver(this, "update-canceled");
+ this.cancelObserverRegistered = false;
+ }
+ },
+
+ onMenuPanelCommand: function(event) {
+ if (event.originalTarget.getAttribute("update-status") === "succeeded") {
+ // restart the app
+ let cancelQuit = Cc["@mozilla.org/supports-PRBool;1"]
+ .createInstance(Ci.nsISupportsPRBool);
+ Services.obs.notifyObservers(cancelQuit, "quit-application-requested", "restart");
+
+ if (!cancelQuit.data) {
+ Services.startup.quit(Services.startup.eAttemptQuit | Services.startup.eRestart);
+ }
+ } else {
+ // open the page for manual update
+ let url = Services.urlFormatter.formatURLPref("app.update.url.manual");
+ openUILinkIn(url, "tab");
+ }
+ },
+
+ observe: function (subject, topic, status) {
+ if (topic == "update-canceled") {
+ this.reset();
+ return;
+ }
+ if (status == "failed") {
+ // Background update has failed, let's show the UI responsible for
+ // prompting the user to update manually.
+ this.uninit();
+ this.displayBadge(false);
+ return;
+ }
+
+ // Give the user badgeWaitTime seconds to react before prompting.
+ this.timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+ this.timer.initWithCallback(this, this.badgeWaitTime * 1000,
+ this.timer.TYPE_ONE_SHOT);
+ // The timer callback will call uninit() when it completes.
+ },
+
+ notify: function () {
+ // If the update is successfully applied, or if the updater has fallen back
+ // to non-staged updates, add a badge to the hamburger menu to indicate an
+ // update will be applied once the browser restarts.
+ this.uninit();
+ this.displayBadge(true);
+ },
+
+ displayBadge: function (succeeded) {
+ let status = succeeded ? "succeeded" : "failed";
+ let badgeStatus = "update-" + status;
+ gMenuButtonBadgeManager.addBadge(gMenuButtonBadgeManager.BADGEID_APPUPDATE, badgeStatus);
+
+ let stringId;
+ let updateButtonText;
+ if (succeeded) {
+ let brandBundle = document.getElementById("bundle_brand");
+ let brandShortName = brandBundle.getString("brandShortName");
+ stringId = "appmenu.restartNeeded.description";
+ updateButtonText = gNavigatorBundle.getFormattedString(stringId,
+ [brandShortName]);
+ Services.obs.addObserver(this, "update-canceled", false);
+ this.cancelObserverRegistered = true;
+ } else {
+ stringId = "appmenu.updateFailed.description";
+ updateButtonText = gNavigatorBundle.getString(stringId);
+ }
+
+ let updateButton = document.getElementById("PanelUI-update-status");
+ updateButton.setAttribute("label", updateButtonText);
+ updateButton.setAttribute("update-status", status);
+ updateButton.hidden = false;
+ },
+
+ reset: function () {
+ gMenuButtonBadgeManager.removeBadge(
+ gMenuButtonBadgeManager.BADGEID_APPUPDATE);
+ let updateButton = document.getElementById("PanelUI-update-status");
+ updateButton.hidden = true;
+ this.uninit();
+ this.init();
+ }
+};
+
+// Values for telemtery bins: see TLS_ERROR_REPORT_UI in Histograms.json
+const TLS_ERROR_REPORT_TELEMETRY_AUTO_CHECKED = 2;
+const TLS_ERROR_REPORT_TELEMETRY_AUTO_UNCHECKED = 3;
+const TLS_ERROR_REPORT_TELEMETRY_MANUAL_SEND = 4;
+const TLS_ERROR_REPORT_TELEMETRY_AUTO_SEND = 5;
+
+const PREF_SSL_IMPACT_ROOTS = ["security.tls.version.", "security.ssl3."];
+
+const PREF_SSL_IMPACT = PREF_SSL_IMPACT_ROOTS.reduce((prefs, root) => {
+ return prefs.concat(Services.prefs.getChildList(root));
+}, []);
+
+/**
+ * Handle command events bubbling up from error page content
+ * or from about:newtab or from remote error pages that invoke
+ * us via async messaging.
+ */
+var BrowserOnClick = {
+ init: function () {
+ let mm = window.messageManager;
+ mm.addMessageListener("Browser:CertExceptionError", this);
+ mm.addMessageListener("Browser:OpenCaptivePortalPage", this);
+ mm.addMessageListener("Browser:SiteBlockedError", this);
+ mm.addMessageListener("Browser:EnableOnlineMode", this);
+ mm.addMessageListener("Browser:SendSSLErrorReport", this);
+ mm.addMessageListener("Browser:SetSSLErrorReportAuto", this);
+ mm.addMessageListener("Browser:ResetSSLPreferences", this);
+ mm.addMessageListener("Browser:SSLErrorReportTelemetry", this);
+ mm.addMessageListener("Browser:OverrideWeakCrypto", this);
+ mm.addMessageListener("Browser:SSLErrorGoBack", this);
+
+ Services.obs.addObserver(this, "captive-portal-login-abort", false);
+ Services.obs.addObserver(this, "captive-portal-login-success", false);
+ },
+
+ uninit: function () {
+ let mm = window.messageManager;
+ mm.removeMessageListener("Browser:CertExceptionError", this);
+ mm.removeMessageListener("Browser:SiteBlockedError", this);
+ mm.removeMessageListener("Browser:EnableOnlineMode", this);
+ mm.removeMessageListener("Browser:SendSSLErrorReport", this);
+ mm.removeMessageListener("Browser:SetSSLErrorReportAuto", this);
+ mm.removeMessageListener("Browser:ResetSSLPreferences", this);
+ mm.removeMessageListener("Browser:SSLErrorReportTelemetry", this);
+ mm.removeMessageListener("Browser:OverrideWeakCrypto", this);
+ mm.removeMessageListener("Browser:SSLErrorGoBack", this);
+
+ Services.obs.removeObserver(this, "captive-portal-login-abort");
+ Services.obs.removeObserver(this, "captive-portal-login-success");
+ },
+
+ observe: function(aSubject, aTopic, aData) {
+ switch (aTopic) {
+ case "captive-portal-login-abort":
+ case "captive-portal-login-success":
+ // Broadcast when a captive portal is freed so that error pages
+ // can refresh themselves.
+ window.messageManager.broadcastAsyncMessage("Browser:CaptivePortalFreed");
+ break;
+ }
+ },
+
+ receiveMessage: function (msg) {
+ switch (msg.name) {
+ case "Browser:CertExceptionError":
+ this.onCertError(msg.target, msg.data.elementId,
+ msg.data.isTopFrame, msg.data.location,
+ msg.data.securityInfoAsString);
+ break;
+ case "Browser:OpenCaptivePortalPage":
+ CaptivePortalWatcher.ensureCaptivePortalTab();
+ break;
+ case "Browser:SiteBlockedError":
+ this.onAboutBlocked(msg.data.elementId, msg.data.reason,
+ msg.data.isTopFrame, msg.data.location);
+ break;
+ case "Browser:EnableOnlineMode":
+ if (Services.io.offline) {
+ // Reset network state and refresh the page.
+ Services.io.offline = false;
+ msg.target.reload();
+ }
+ break;
+ case "Browser:SendSSLErrorReport":
+ this.onSSLErrorReport(msg.target,
+ msg.data.uri,
+ msg.data.securityInfo);
+ break;
+ case "Browser:ResetSSLPreferences":
+ for (let prefName of PREF_SSL_IMPACT) {
+ Services.prefs.clearUserPref(prefName);
+ }
+ msg.target.reload();
+ break;
+ case "Browser:SetSSLErrorReportAuto":
+ Services.prefs.setBoolPref("security.ssl.errorReporting.automatic", msg.json.automatic);
+ let bin = TLS_ERROR_REPORT_TELEMETRY_AUTO_UNCHECKED;
+ if (msg.json.automatic) {
+ bin = TLS_ERROR_REPORT_TELEMETRY_AUTO_CHECKED;
+ }
+ Services.telemetry.getHistogramById("TLS_ERROR_REPORT_UI").add(bin);
+ break;
+ case "Browser:SSLErrorReportTelemetry":
+ let reportStatus = msg.data.reportStatus;
+ Services.telemetry.getHistogramById("TLS_ERROR_REPORT_UI")
+ .add(reportStatus);
+ break;
+ case "Browser:OverrideWeakCrypto":
+ let weakCryptoOverride = Cc["@mozilla.org/security/weakcryptooverride;1"]
+ .getService(Ci.nsIWeakCryptoOverride);
+ weakCryptoOverride.addWeakCryptoOverride(
+ msg.data.uri.host,
+ PrivateBrowsingUtils.isBrowserPrivate(gBrowser.selectedBrowser));
+ break;
+ case "Browser:SSLErrorGoBack":
+ goBackFromErrorPage();
+ break;
+ }
+ },
+
+ onSSLErrorReport: function(browser, uri, securityInfo) {
+ if (!Services.prefs.getBoolPref("security.ssl.errorReporting.enabled")) {
+ Cu.reportError("User requested certificate error report sending, but certificate error reporting is disabled");
+ return;
+ }
+
+ let serhelper = Cc["@mozilla.org/network/serialization-helper;1"]
+ .getService(Ci.nsISerializationHelper);
+ let transportSecurityInfo = serhelper.deserializeObject(securityInfo);
+ transportSecurityInfo.QueryInterface(Ci.nsITransportSecurityInfo)
+
+ let errorReporter = Cc["@mozilla.org/securityreporter;1"]
+ .getService(Ci.nsISecurityReporter);
+ errorReporter.reportTLSError(transportSecurityInfo,
+ uri.host, uri.port);
+ },
+
+ onCertError: function (browser, elementId, isTopFrame, location, securityInfoAsString) {
+ let secHistogram = Services.telemetry.getHistogramById("SECURITY_UI");
+ let securityInfo;
+
+ switch (elementId) {
+ case "exceptionDialogButton":
+ if (isTopFrame) {
+ secHistogram.add(Ci.nsISecurityUITelemetry.WARNING_BAD_CERT_TOP_CLICK_ADD_EXCEPTION);
+ }
+
+ securityInfo = getSecurityInfo(securityInfoAsString);
+ let sslStatus = securityInfo.QueryInterface(Ci.nsISSLStatusProvider)
+ .SSLStatus;
+ let params = { exceptionAdded : false,
+ sslStatus : sslStatus };
+
+ try {
+ switch (Services.prefs.getIntPref("browser.ssl_override_behavior")) {
+ case 2 : // Pre-fetch & pre-populate
+ params.prefetchCert = true;
+ case 1 : // Pre-populate
+ params.location = location;
+ }
+ } 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) {
+ browser.reload();
+ }
+ break;
+
+ case "returnButton":
+ if (isTopFrame) {
+ secHistogram.add(Ci.nsISecurityUITelemetry.WARNING_BAD_CERT_TOP_GET_ME_OUT_OF_HERE);
+ }
+ goBackFromErrorPage();
+ break;
+
+ case "advancedButton":
+ if (isTopFrame) {
+ secHistogram.add(Ci.nsISecurityUITelemetry.WARNING_BAD_CERT_TOP_UNDERSTAND_RISKS);
+ }
+
+ securityInfo = getSecurityInfo(securityInfoAsString);
+ let errorInfo = getDetailedCertErrorInfo(location,
+ securityInfo);
+ browser.messageManager.sendAsyncMessage( "CertErrorDetails", {
+ code: securityInfo.errorCode,
+ info: errorInfo
+ });
+ break;
+
+ case "copyToClipboard":
+ const gClipboardHelper = Cc["@mozilla.org/widget/clipboardhelper;1"]
+ .getService(Ci.nsIClipboardHelper);
+ securityInfo = getSecurityInfo(securityInfoAsString);
+ let detailedInfo = getDetailedCertErrorInfo(location,
+ securityInfo);
+ gClipboardHelper.copyString(detailedInfo);
+ break;
+
+ }
+ },
+
+ onAboutBlocked: function (elementId, reason, isTopFrame, location) {
+ // Depending on what page we are displaying here (malware/phishing/unwanted)
+ // use the right strings and links for each.
+ let bucketName = "";
+ let sendTelemetry = false;
+ if (reason === 'malware') {
+ sendTelemetry = true;
+ bucketName = "WARNING_MALWARE_PAGE_";
+ } else if (reason === 'phishing') {
+ sendTelemetry = true;
+ bucketName = "WARNING_PHISHING_PAGE_";
+ } else if (reason === 'unwanted') {
+ sendTelemetry = true;
+ bucketName = "WARNING_UNWANTED_PAGE_";
+ }
+ let secHistogram = Services.telemetry.getHistogramById("SECURITY_UI");
+ let nsISecTel = Ci.nsISecurityUITelemetry;
+ bucketName += isTopFrame ? "TOP_" : "FRAME_";
+ switch (elementId) {
+ case "getMeOutButton":
+ if (sendTelemetry) {
+ secHistogram.add(nsISecTel[bucketName + "GET_ME_OUT_OF_HERE"]);
+ }
+ getMeOutOfHere();
+ break;
+
+ case "reportButton":
+ // This is the "Why is this site blocked" button. We redirect
+ // to the generic page describing phishing/malware protection.
+
+ // We log even if malware/phishing/unwanted info URL couldn't be found:
+ // the measurement is for how many users clicked the WHY BLOCKED button
+ if (sendTelemetry) {
+ secHistogram.add(nsISecTel[bucketName + "WHY_BLOCKED"]);
+ }
+ openHelpLink("phishing-malware", false, "current");
+ break;
+
+ case "ignoreWarningButton":
+ if (gPrefService.getBoolPref("browser.safebrowsing.allowOverride")) {
+ if (sendTelemetry) {
+ secHistogram.add(nsISecTel[bucketName + "IGNORE_WARNING"]);
+ }
+ this.ignoreWarningButton(reason);
+ }
+ break;
+ }
+ },
+
+ ignoreWarningButton: function (reason) {
+ // Allow users to override and continue through to the site,
+ // but add a notify bar as a reminder, so that they don't lose
+ // track after, e.g., tab switching.
+ gBrowser.loadURIWithFlags(gBrowser.currentURI.spec,
+ nsIWebNavigation.LOAD_FLAGS_BYPASS_CLASSIFIER,
+ null, null, null);
+
+ Services.perms.add(gBrowser.currentURI, "safe-browsing",
+ Ci.nsIPermissionManager.ALLOW_ACTION,
+ Ci.nsIPermissionManager.EXPIRE_SESSION);
+
+ let buttons = [{
+ label: gNavigatorBundle.getString("safebrowsing.getMeOutOfHereButton.label"),
+ accessKey: gNavigatorBundle.getString("safebrowsing.getMeOutOfHereButton.accessKey"),
+ callback: function() { getMeOutOfHere(); }
+ }];
+
+ let title;
+ if (reason === 'malware') {
+ title = gNavigatorBundle.getString("safebrowsing.reportedAttackSite");
+ buttons[1] = {
+ label: gNavigatorBundle.getString("safebrowsing.notAnAttackButton.label"),
+ accessKey: gNavigatorBundle.getString("safebrowsing.notAnAttackButton.accessKey"),
+ callback: function() {
+ openUILinkIn(gSafeBrowsing.getReportURL('MalwareMistake'), 'tab');
+ }
+ };
+ } else if (reason === 'phishing') {
+ title = gNavigatorBundle.getString("safebrowsing.deceptiveSite");
+ buttons[1] = {
+ label: gNavigatorBundle.getString("safebrowsing.notADeceptiveSiteButton.label"),
+ accessKey: gNavigatorBundle.getString("safebrowsing.notADeceptiveSiteButton.accessKey"),
+ callback: function() {
+ openUILinkIn(gSafeBrowsing.getReportURL('PhishMistake'), 'tab');
+ }
+ };
+ } else if (reason === 'unwanted') {
+ title = gNavigatorBundle.getString("safebrowsing.reportedUnwantedSite");
+ // There is no button for reporting errors since Google doesn't currently
+ // provide a URL endpoint for these reports.
+ }
+
+ let notificationBox = gBrowser.getNotificationBox();
+ let value = "blocked-badware-page";
+
+ let previousNotification = notificationBox.getNotificationWithValue(value);
+ if (previousNotification) {
+ notificationBox.removeNotification(previousNotification);
+ }
+
+ let notification = notificationBox.appendNotification(
+ title,
+ value,
+ "chrome://global/skin/icons/blacklist_favicon.png",
+ notificationBox.PRIORITY_CRITICAL_HIGH,
+ buttons
+ );
+ // Persist the notification until the user removes so it
+ // doesn't get removed on redirects.
+ notification.persistence = -1;
+ },
+};
+
+/**
+ * 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() {
+ gBrowser.loadURI(getDefaultHomePage());
+}
+
+/**
+ * Re-direct the browser to the previous page or a known-safe page if no
+ * previous page is found in history. This function is used when the user
+ * browses to a secure page with certificate issues and is presented with
+ * about:certerror. The "Go Back" button should take the user to the previous
+ * or a default start page so that even when their own homepage is on a server
+ * that has certificate errors, we can get them somewhere safe.
+ */
+function goBackFromErrorPage() {
+ const ss = Cc["@mozilla.org/browser/sessionstore;1"].
+ getService(Ci.nsISessionStore);
+ let state = JSON.parse(ss.getTabState(gBrowser.selectedTab));
+ if (state.index == 1) {
+ // If the unsafe page is the first or the only one in history, go to the
+ // start page.
+ gBrowser.loadURI(getDefaultHomePage());
+ } else {
+ BrowserBack();
+ }
+}
+
+/**
+ * Return the default start page for the cases when the user's own homepage is
+ * infected, so we can get them somewhere safe.
+ */
+function getDefaultHomePage() {
+ // 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);
+ }
+ return url;
+}
+
+function BrowserFullScreen()
+{
+ window.fullScreen = !window.fullScreen;
+}
+
+function mirrorShow(popup) {
+ let services = [];
+ if (Services.prefs.getBoolPref("browser.casting.enabled")) {
+ services = CastingApps.getServicesForMirroring();
+ }
+ popup.ownerDocument.getElementById("menu_mirrorTabCmd").hidden = !services.length;
+}
+
+function mirrorMenuItemClicked(event) {
+ gBrowser.selectedBrowser.messageManager.sendAsyncMessage("SecondScreen:tab-mirror",
+ {service: event.originalTarget._service});
+}
+
+function populateMirrorTabMenu(popup) {
+ popup.innerHTML = null;
+ if (!Services.prefs.getBoolPref("browser.casting.enabled")) {
+ return;
+ }
+ let doc = popup.ownerDocument;
+ let services = CastingApps.getServicesForMirroring();
+ services.forEach(service => {
+ let item = doc.createElement("menuitem");
+ item.setAttribute("label", service.friendlyName);
+ item._service = service;
+ item.addEventListener("command", mirrorMenuItemClicked);
+ popup.appendChild(item);
+ });
+}
+
+function getWebNavigation()
+{
+ return gBrowser.webNavigation;
+}
+
+function BrowserReloadWithFlags(reloadFlags) {
+ let url = gBrowser.currentURI.spec;
+ if (gBrowser.updateBrowserRemotenessByURL(gBrowser.selectedBrowser, url)) {
+ // If the remoteness has changed, the new browser doesn't have any
+ // information of what was loaded before, so we need to load the previous
+ // URL again.
+ gBrowser.loadURIWithFlags(url, reloadFlags);
+ return;
+ }
+
+ let windowUtils = window.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindowUtils);
+
+ gBrowser.selectedBrowser
+ .messageManager
+ .sendAsyncMessage("Browser:Reload",
+ { flags: reloadFlags,
+ handlingUserInput: windowUtils.isHandlingUserInput });
+}
+
+function getSecurityInfo(securityInfoAsString) {
+ if (!securityInfoAsString)
+ return null;
+
+ const serhelper = Cc["@mozilla.org/network/serialization-helper;1"]
+ .getService(Ci.nsISerializationHelper);
+ let securityInfo = serhelper.deserializeObject(securityInfoAsString);
+ securityInfo.QueryInterface(Ci.nsITransportSecurityInfo);
+
+ return securityInfo;
+}
+
+/**
+ * Returns a string with detailed information about the certificate validation
+ * failure from the specified URI that can be used to send a report.
+ */
+function getDetailedCertErrorInfo(location, securityInfo) {
+ if (!securityInfo)
+ return "";
+
+ let certErrorDetails = location;
+ let code = securityInfo.errorCode;
+ let errors = Cc["@mozilla.org/nss_errors_service;1"]
+ .getService(Ci.nsINSSErrorsService);
+
+ certErrorDetails += "\r\n\r\n" + errors.getErrorMessage(errors.getXPCOMFromNSSError(code));
+
+ const sss = Cc["@mozilla.org/ssservice;1"]
+ .getService(Ci.nsISiteSecurityService);
+ // SiteSecurityService uses different storage if the channel is
+ // private. Thus we must give isSecureHost correct flags or we
+ // might get incorrect results.
+ let flags = PrivateBrowsingUtils.isWindowPrivate(window) ?
+ Ci.nsISocketProvider.NO_PERMANENT_STORAGE : 0;
+
+ let uri = Services.io.newURI(location, null, null);
+
+ let hasHSTS = sss.isSecureHost(sss.HEADER_HSTS, uri.host, flags);
+ let hasHPKP = sss.isSecureHost(sss.HEADER_HPKP, uri.host, flags);
+ certErrorDetails += "\r\n\r\n" +
+ gNavigatorBundle.getFormattedString("certErrorDetailsHSTS.label",
+ [hasHSTS]);
+ certErrorDetails += "\r\n" +
+ gNavigatorBundle.getFormattedString("certErrorDetailsKeyPinning.label",
+ [hasHPKP]);
+
+ let certChain = "";
+ if (securityInfo.failedCertChain) {
+ let certs = securityInfo.failedCertChain.getEnumerator();
+ while (certs.hasMoreElements()) {
+ let cert = certs.getNext();
+ cert.QueryInterface(Ci.nsIX509Cert);
+ certChain += getPEMString(cert);
+ }
+ }
+
+ certErrorDetails += "\r\n\r\n" +
+ gNavigatorBundle.getString("certErrorDetailsCertChain.label") +
+ "\r\n\r\n" + certChain;
+
+ return certErrorDetails;
+}
+
+// TODO: can we pull getDERString and getPEMString in from pippki.js instead of
+// duplicating them here?
+function getDERString(cert)
+{
+ var length = {};
+ var derArray = cert.getRawDER(length);
+ var derString = '';
+ for (var i = 0; i < derArray.length; i++) {
+ derString += String.fromCharCode(derArray[i]);
+ }
+ return derString;
+}
+
+function getPEMString(cert)
+{
+ var derb64 = btoa(getDERString(cert));
+ // Wrap the Base64 string into lines of 64 characters,
+ // with CRLF line breaks (as specified in RFC 1421).
+ var wrapped = derb64.replace(/(\S{64}(?!$))/g, "$1\r\n");
+ return "-----BEGIN CERTIFICATE-----\r\n"
+ + wrapped
+ + "\r\n-----END CERTIFICATE-----\r\n";
+}
+
+var PrintPreviewListener = {
+ _printPreviewTab: null,
+ _tabBeforePrintPreview: null,
+ _simplifyPageTab: null,
+
+ getPrintPreviewBrowser: function () {
+ if (!this._printPreviewTab) {
+ let browser = gBrowser.selectedTab.linkedBrowser;
+ let forceNotRemote = gMultiProcessBrowser && !browser.isRemoteBrowser;
+ this._tabBeforePrintPreview = gBrowser.selectedTab;
+ this._printPreviewTab = gBrowser.loadOneTab("about:blank",
+ { inBackground: false,
+ forceNotRemote,
+ relatedBrowser: browser });
+ gBrowser.selectedTab = this._printPreviewTab;
+ }
+ return gBrowser.getBrowserForTab(this._printPreviewTab);
+ },
+ createSimplifiedBrowser: function () {
+ this._simplifyPageTab = gBrowser.loadOneTab("about:blank",
+ { inBackground: true });
+ return this.getSimplifiedSourceBrowser();
+ },
+ getSourceBrowser: function () {
+ return this._tabBeforePrintPreview ?
+ this._tabBeforePrintPreview.linkedBrowser : gBrowser.selectedBrowser;
+ },
+ getSimplifiedSourceBrowser: function () {
+ return this._simplifyPageTab ?
+ gBrowser.getBrowserForTab(this._simplifyPageTab) : null;
+ },
+ 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();
+ if (this._simplifyPageTab) {
+ gBrowser.removeTab(this._simplifyPageTab);
+ this._simplifyPageTab = null;
+ }
+ gBrowser.removeTab(this._printPreviewTab);
+ gBrowser.deactivatePrintPreviewBrowsers();
+ this._printPreviewTab = null;
+ },
+ _toggleAffectedChrome: function () {
+ gNavToolbox.collapsed = gInPrintPreviewMode;
+
+ if (gInPrintPreviewMode)
+ this._hideChrome();
+ else
+ this._showChrome();
+
+ TabsInTitlebar.allowedBy("print-preview", !gInPrintPreviewMode);
+ },
+ _hideChrome: function () {
+ this._chromeState = {};
+
+ this._chromeState.sidebarOpen = SidebarUI.isOpen;
+ this._sidebarCommand = SidebarUI.currentID;
+ SidebarUI.hide();
+
+ var notificationBox = gBrowser.getNotificationBox();
+ this._chromeState.notificationsOpen = !notificationBox.notificationsHidden;
+ notificationBox.notificationsHidden = true;
+
+ 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.findOpen)
+ gFindBar.open();
+
+ if (this._chromeState.globalNotificationsOpen)
+ document.getElementById("global-notificationbox").notificationsHidden = false;
+
+ if (this._chromeState.syncNotificationsOpen)
+ document.getElementById("sync-notifications").notificationsHidden = false;
+
+ if (this._chromeState.sidebarOpen)
+ SidebarUI.show(this._sidebarCommand);
+ },
+
+ activateBrowser(browser) {
+ gBrowser.activateBrowserForPrintPreview(browser);
+ },
+}
+
+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: 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)
+ {
+ if (gPrefService.prefIsLocked("browser.startup.homepage")) {
+ return;
+ }
+ 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 newTabButtonObserver = {
+ onDragOver(aEvent) {
+ browserDragAndDrop.dragOver(aEvent);
+ },
+ onDragExit(aEvent) {},
+ onDrop: Task.async(function* (aEvent) {
+ let links = browserDragAndDrop.dropLinks(aEvent);
+ for (let link of links) {
+ if (link.url) {
+ let data = yield getShortcutOrURIAndPostData(link.url);
+ // Allow third-party services to fixup this URL.
+ openNewTabWith(data.url, null, data.postData, aEvent, true);
+ }
+ }
+ })
+}
+
+var newWindowButtonObserver = {
+ onDragOver(aEvent) {
+ browserDragAndDrop.dragOver(aEvent);
+ },
+ onDragExit(aEvent) {},
+ onDrop: Task.async(function* (aEvent) {
+ let links = browserDragAndDrop.dropLinks(aEvent);
+ for (let link of links) {
+ if (link.url) {
+ let data = yield getShortcutOrURIAndPostData(link.url);
+ // Allow third-party services to fixup this URL.
+ openNewWindowWith(data.url, null, data.postData, true);
+ }
+ }
+ })
+}
+
+const DOMLinkHandler = {
+ init: function() {
+ let mm = window.messageManager;
+ mm.addMessageListener("Link:AddFeed", this);
+ mm.addMessageListener("Link:SetIcon", this);
+ mm.addMessageListener("Link:AddSearch", this);
+ },
+
+ receiveMessage: function (aMsg) {
+ switch (aMsg.name) {
+ case "Link:AddFeed":
+ let link = {type: aMsg.data.type, href: aMsg.data.href, title: aMsg.data.title};
+ FeedHandler.addFeed(link, aMsg.target);
+ break;
+
+ case "Link:SetIcon":
+ this.setIcon(aMsg.target, aMsg.data.url, aMsg.data.loadingPrincipal);
+ break;
+
+ case "Link:AddSearch":
+ this.addSearch(aMsg.target, aMsg.data.engine, aMsg.data.url);
+ break;
+ }
+ },
+
+ setIcon: function(aBrowser, aURL, aLoadingPrincipal) {
+ if (gBrowser.isFailedIcon(aURL))
+ return false;
+
+ let tab = gBrowser.getTabForBrowser(aBrowser);
+ if (!tab)
+ return false;
+
+ gBrowser.setIcon(tab, aURL, aLoadingPrincipal);
+ return true;
+ },
+
+ addSearch: function(aBrowser, aEngine, aURL) {
+ let tab = gBrowser.getTabForBrowser(aBrowser);
+ if (!tab)
+ return;
+
+ BrowserSearch.addEngine(aBrowser, aEngine, makeURI(aURL));
+ },
+}
+
+const BrowserSearch = {
+ addEngine: function(browser, engine, uri) {
+ // Check to see whether we've already added an engine with this title
+ if (browser.engines) {
+ if (browser.engines.some(e => e.title == engine.title))
+ return;
+ }
+
+ 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,
+ get icon() { return browser.mIconURL; }
+ });
+
+ if (hidden)
+ browser.hiddenEngines = engines;
+ else {
+ browser.engines = engines;
+ if (browser == gBrowser.selectedBrowser)
+ this.updateOpenSearchBadge();
+ }
+ },
+
+ /**
+ * Update the browser UI to show whether or not additional engines are
+ * available when a page is loaded or the user switches tabs to a page that
+ * has search engines.
+ */
+ updateOpenSearchBadge: function() {
+ var searchBar = this.searchBar;
+ if (!searchBar)
+ return;
+
+ var engines = gBrowser.selectedBrowser.engines;
+ if (engines && engines.length > 0)
+ searchBar.setAttribute("addengines", "true");
+ else
+ searchBar.removeAttribute("addengines");
+ },
+
+ /**
+ * 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() {
+ 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;
+ }
+
+ let focusUrlBarIfSearchFieldIsNotActive = function(aSearchBar) {
+ if (!aSearchBar || document.activeElement != aSearchBar.textbox.inputField) {
+ focusAndSelectUrlBar();
+ }
+ };
+
+ let searchBar = this.searchBar;
+ let placement = CustomizableUI.getPlacementOfWidget("search-container");
+ let focusSearchBar = () => {
+ searchBar = this.searchBar;
+ searchBar.select();
+ focusUrlBarIfSearchFieldIsNotActive(searchBar);
+ };
+ if (placement && placement.area == CustomizableUI.AREA_PANEL) {
+ // The panel is not constructed until the first time it is shown.
+ PanelUI.show().then(focusSearchBar);
+ return;
+ }
+ if (placement && placement.area == CustomizableUI.AREA_NAVBAR && searchBar &&
+ searchBar.parentNode.getAttribute("overflowedItem") == "true") {
+ let navBar = document.getElementById(CustomizableUI.AREA_NAVBAR);
+ navBar.overflowable.show().then(() => {
+ focusSearchBar();
+ });
+ return;
+ }
+ if (searchBar) {
+ if (window.fullScreen)
+ FullScreen.showNavToolbox();
+ searchBar.select();
+ }
+ focusUrlBarIfSearchFieldIsNotActive(searchBar);
+ },
+
+ /**
+ * 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 engine The search engine used to perform a search, or null if no
+ * search was performed.
+ */
+ _loadSearch: function (searchText, useNewTab, purpose) {
+ let 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;
+
+ let 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;
+ },
+
+ /**
+ * Just like _loadSearch, but preserving an old API.
+ *
+ * @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) {
+ let engine = BrowserSearch._loadSearch(searchText, useNewTab, purpose);
+ if (!engine) {
+ return null;
+ }
+ 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");
+ if (engine) {
+ BrowserSearch.recordSearchInTelemetry(engine, "contextmenu");
+ }
+ },
+
+ pasteAndSearch: function (event) {
+ BrowserSearch.searchBar.select();
+ goDoCommand("cmd_paste");
+ BrowserSearch.searchBar.handleSearchCommand(event);
+ },
+
+ /**
+ * Returns the search bar element if it is present in the toolbar, null otherwise.
+ */
+ get searchBar() {
+ return document.getElementById("searchbar");
+ },
+
+ get searchEnginesURL() {
+ return formatURL("browser.search.searchEnginesURL", true);
+ },
+
+ loadAddEngines: function BrowserSearch_loadAddEngines() {
+ var newWindowPref = gPrefService.getIntPref("browser.link.open_newwindow");
+ var where = newWindowPref == 3 ? "tab" : "window";
+ openUILinkIn(this.searchEnginesURL, where);
+ },
+
+ _getSearchEngineId: function (engine) {
+ if (engine && engine.identifier) {
+ return engine.identifier;
+ }
+
+ if (!engine || (engine.name === undefined) ||
+ !Services.prefs.getBoolPref("toolkit.telemetry.enabled"))
+ return "other";
+
+ return "other-" + engine.name;
+ },
+
+ /**
+ * Helper to record a search with Telemetry.
+ *
+ * Telemetry records only search counts and nothing pertaining to the search itself.
+ *
+ * @param engine
+ * (nsISearchEngine) The engine handling the search.
+ * @param source
+ * (string) Where the search originated from. See BrowserUsageTelemetry for
+ * allowed values.
+ * @param details [optional]
+ * An optional parameter passed to |BrowserUsageTelemetry.recordSearch|.
+ * See its documentation for allowed options.
+ * Additionally, if the search was a suggested search, |details.selection|
+ * indicates where the item was in the suggestion list and how the user
+ * selected it: {selection: {index: The selected index, kind: "key" or "mouse"}}
+ */
+ recordSearchInTelemetry: function (engine, source, details={}) {
+ BrowserUITelemetry.countSearchEvent(source, null, details.selection);
+ try {
+ BrowserUsageTelemetry.recordSearch(engine, source, details);
+ } catch (ex) {
+ Cu.reportError(ex);
+ }
+ },
+
+ /**
+ * Helper to record a one-off search with Telemetry.
+ *
+ * Telemetry records only search counts and nothing pertaining to the search itself.
+ *
+ * @param engine
+ * (nsISearchEngine) The engine handling the search.
+ * @param source
+ * (string) Where the search originated from. See BrowserUsageTelemetry for
+ * allowed values.
+ * @param type
+ * (string) Indicates how the user selected the search item.
+ * @param where
+ * (string) Where was the search link opened (e.g. new tab, current tab, ..).
+ */
+ recordOneoffSearchInTelemetry: function (engine, source, type, where) {
+ let id = this._getSearchEngineId(engine) + "." + source;
+ BrowserUITelemetry.countOneoffSearchEvent(id, type, where);
+ try {
+ const details = {type, isOneOff: true};
+ BrowserUsageTelemetry.recordSearch(engine, source, details);
+ } catch (ex) {
+ Cu.reportError(ex);
+ }
+ }
+};
+
+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
+ let children = aParent.childNodes;
+ for (var i = children.length - 1; i >= 0; --i) {
+ if (children[i].hasAttribute("index"))
+ aParent.removeChild(children[i]);
+ }
+
+ const MAX_HISTORY_MENU_ITEMS = 15;
+
+ const tooltipBack = gNavigatorBundle.getString("tabHistory.goBack");
+ const tooltipCurrent = gNavigatorBundle.getString("tabHistory.current");
+ const tooltipForward = gNavigatorBundle.getString("tabHistory.goForward");
+
+ function updateSessionHistory(sessionHistory, initial)
+ {
+ let count = sessionHistory.entries.length;
+
+ if (!initial) {
+ if (count <= 1) {
+ // if there is only one entry now, close the popup.
+ aParent.hidePopup();
+ return;
+ } else if (aParent.id != "backForwardMenu" && !aParent.parentNode.open) {
+ // if the popup wasn't open before, but now needs to be, reopen the menu.
+ // It should trigger FillHistoryMenu again. This might happen with the
+ // delay from click-and-hold menus but skip this for the context menu
+ // (backForwardMenu) rather than figuring out how the menu should be
+ // positioned and opened as it is an extreme edgecase.
+ aParent.parentNode.open = true;
+ return;
+ }
+ }
+
+ let index = sessionHistory.index;
+ let half_length = Math.floor(MAX_HISTORY_MENU_ITEMS / 2);
+ let start = Math.max(index - half_length, 0);
+ let 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);
+ }
+
+ let existingIndex = 0;
+
+ for (let j = end - 1; j >= start; j--) {
+ let entry = sessionHistory.entries[j];
+ let uri = entry.url;
+
+ let item = existingIndex < children.length ?
+ children[existingIndex] : document.createElement("menuitem");
+
+ let entryURI = BrowserUtils.makeURI(entry.url, entry.charset, null);
+ item.setAttribute("uri", uri);
+ item.setAttribute("label", entry.title || uri);
+ item.setAttribute("index", j);
+
+ // Cache this so that gotoHistoryIndex doesn't need the original index
+ item.setAttribute("historyindex", j - index);
+
+ if (j != index) {
+ PlacesUtils.favicons.getFaviconURLForPage(entryURI, 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);
+ }
+
+ if (!item.parentNode) {
+ aParent.appendChild(item);
+ }
+
+ existingIndex++;
+ }
+
+ if (!initial) {
+ let existingLength = children.length;
+ while (existingIndex < existingLength) {
+ aParent.removeChild(aParent.lastChild);
+ existingIndex++;
+ }
+ }
+ }
+
+ let sessionHistory = SessionStore.getSessionHistory(gBrowser.selectedTab, updateSessionHistory);
+ if (!sessionHistory)
+ return false;
+
+ // don't display the popup for a single item
+ if (sessionHistory.entries.length <= 1)
+ return false;
+
+ updateSessionHistory(sessionHistory, true);
+ 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)
+{
+ var telemetryObj = {};
+ TelemetryStopwatch.start("FX_NEW_WINDOW_MS", telemetryObj);
+
+ function newDocumentShown(doc, topic, data) {
+ if (topic == "document-shown" &&
+ doc != document &&
+ doc.defaultView == win) {
+ Services.obs.removeObserver(newDocumentShown, "document-shown");
+ Services.obs.removeObserver(windowClosed, "domwindowclosed");
+ TelemetryStopwatch.finish("FX_NEW_WINDOW_MS", telemetryObj);
+ }
+ }
+
+ function windowClosed(subject) {
+ if (subject == win) {
+ Services.obs.removeObserver(newDocumentShown, "document-shown");
+ Services.obs.removeObserver(windowClosed, "domwindowclosed");
+ }
+ }
+
+ // Make sure to remove the 'document-shown' observer in case the window
+ // is being closed right after it was opened to avoid leaking.
+ Services.obs.addObserver(newDocumentShown, "document-shown", false);
+ Services.obs.addObserver(windowClosed, "domwindowclosed", 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 (options && options.remote) {
+ extraFeatures += ",remote";
+ } else if (options && options.remote === false) {
+ extraFeatures += ",non-remote";
+ }
+
+ // 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;
+}
+
+// Only here for backwards compat, we should remove this soon
+function BrowserCustomizeToolbar() {
+ gCustomizeMode.enter();
+}
+
+/**
+ * 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()
+{
+ if (AppConstants.platform == "macosx")
+ return;
+
+ let editMenuPopupState = document.getElementById("menu_EditPopup").state;
+ let contextMenuPopupState = document.getElementById("contentAreaContextMenu").state;
+ let placesContextMenuPopupState = document.getElementById("placesContext").state;
+
+ // 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" ||
+ document.getElementById("edit-controls") ? 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);
+ }
+}
+
+/**
+ * Opens a new tab with the userContextId specified as an attribute of
+ * sourceEvent. This attribute is propagated to the top level originAttributes
+ * living on the tab's docShell.
+ *
+ * @param event
+ * A click event on a userContext File Menu option
+ */
+function openNewUserContextTab(event)
+{
+ openUILinkIn(BROWSER_NEW_TAB_URL, "tab", {
+ userContextId: parseInt(event.target.getAttribute('data-usercontextid')),
+ });
+}
+
+/**
+ * Updates File Menu User Context UI visibility depending on
+ * privacy.userContext.enabled pref state.
+ */
+function updateUserContextUIVisibility()
+{
+ let menu = document.getElementById("menu_newUserContext");
+ menu.hidden = !Services.prefs.getBoolPref("privacy.userContext.enabled");
+ if (PrivateBrowsingUtils.isWindowPrivate(window)) {
+ menu.setAttribute("disabled", "true");
+ }
+}
+
+/**
+ * Updates the User Context UI indicators if the browser is in a non-default context
+ */
+function updateUserContextUIIndicator()
+{
+ let hbox = document.getElementById("userContext-icons");
+
+ let userContextId = gBrowser.selectedBrowser.getAttribute("usercontextid");
+ if (!userContextId) {
+ hbox.setAttribute("data-identity-color", "");
+ hbox.hidden = true;
+ return;
+ }
+
+ let identity = ContextualIdentityService.getIdentityFromId(userContextId);
+ if (!identity) {
+ hbox.setAttribute("data-identity-color", "");
+ hbox.hidden = true;
+ return;
+ }
+
+ hbox.setAttribute("data-identity-color", identity.color);
+
+ let label = document.getElementById("userContext-label");
+ label.setAttribute("value", ContextualIdentityService.getUserContextLabel(userContextId));
+
+ let indicator = document.getElementById("userContext-indicator");
+ indicator.setAttribute("data-identity-icon", identity.icon);
+
+ hbox.hidden = false;
+}
+
+/**
+ * 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");
+ // 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.selectedBrowser.mayEnableCharacterEncodingMenu) {
+ if (charsetMenu) {
+ charsetMenu.removeAttribute("disabled");
+ }
+ } else if (charsetMenu) {
+ charsetMenu.setAttribute("disabled", "true");
+ }
+}
+
+var XULBrowserWindow = {
+ // Stored Status, Link and Loading values
+ status: "",
+ defaultStatus: "",
+ overLink: "",
+ startTime: 0,
+ statusText: "",
+ isBusy: false,
+ // Left here for add-on compatibility, see bug 752434
+ 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 () {
+ return gBrowser.getStatusPanel();
+ },
+ get isImage () {
+ delete this.isImage;
+ return this.isImage = document.getElementById("isImage");
+ },
+ get canViewSource () {
+ delete this.canViewSource;
+ return this.canViewSource = document.getElementById("canViewSource");
+ },
+
+ init: function () {
+ // Initialize the security button's state and tooltip text.
+ var securityUI = gBrowser.securityUI;
+ this.onSecurityChange(null, null, securityUI.state, true);
+ },
+
+ setJSStatus: function () {
+ // unsupported
+ },
+
+ forceInitialBrowserRemote: function() {
+ let initBrowser =
+ document.getAnonymousElementByAttribute(gBrowser, "anonid", "initialBrowser");
+ return initBrowser.frameLoader.tabParent;
+ },
+
+ forceInitialBrowserNonRemote: function(aOpener) {
+ let initBrowser =
+ document.getAnonymousElementByAttribute(gBrowser, "anonid", "initialBrowser");
+ gBrowser.updateBrowserRemoteness(initBrowser, false, aOpener);
+ },
+
+ 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();
+ },
+
+ showTooltip: function (x, y, tooltip, direction) {
+ if (Cc["@mozilla.org/widget/dragservice;1"].getService(Ci.nsIDragService).
+ getCurrentSession()) {
+ return;
+ }
+
+ // The x,y coordinates are relative to the <browser> element using
+ // the chrome zoom level.
+ let elt = document.getElementById("remoteBrowserTooltip");
+ elt.label = tooltip;
+ elt.style.direction = direction;
+
+ let anchor = gBrowser.selectedBrowser;
+ elt.openPopupAtScreen(anchor.boxObject.screenX + x, anchor.boxObject.screenY + y, false, null);
+ },
+
+ hideTooltip: function () {
+ let elt = document.getElementById("remoteBrowserTooltip");
+ elt.hidePopup();
+ },
+
+ getTabCount: function () {
+ return gBrowser.tabs.length;
+ },
+
+ 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) {
+ return BrowserUtils.onBeforeLinkTraversal(originalTarget, linkURI, linkNode, isAppTab);
+ },
+
+ // Check whether this URI should load in the current process
+ shouldLoadURI: function(aDocShell, aURI, aReferrer) {
+ if (!gMultiProcessBrowser)
+ return true;
+
+ let browser = aDocShell.QueryInterface(Ci.nsIDocShellTreeItem)
+ .sameTypeRootTreeItem
+ .QueryInterface(Ci.nsIDocShell)
+ .chromeEventHandler;
+
+ // Ignore loads that aren't in the main tabbrowser
+ if (browser.localName != "browser" || !browser.getTabBrowser || browser.getTabBrowser() != gBrowser)
+ return true;
+
+ if (!E10SUtils.shouldLoadURI(aDocShell, aURI, aReferrer)) {
+ E10SUtils.redirectLoad(aDocShell, aURI, aReferrer);
+ return false;
+ }
+
+ return true;
+ },
+
+ 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;
+
+ let browser = gBrowser.selectedBrowser;
+
+ if (aStateFlags & nsIWebProgressListener.STATE_START &&
+ aStateFlags & nsIWebProgressListener.STATE_IS_NETWORK) {
+
+ if (aRequest && aWebProgress.isTopLevel) {
+ // clear out feed data
+ browser.feeds = null;
+
+ // clear out search-engine data
+ browser.engines = null;
+ }
+
+ this.isBusy = true;
+
+ if (!(aStateFlags & nsIWebProgressListener.STATE_RESTORING)) {
+ this._busyUI = 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;
+ let canViewSource = true;
+ // 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;
+
+ canViewSource = !Services.prefs.getBoolPref("view_source.tab") ||
+ location.scheme != "view-source";
+
+ 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 (browser.documentContentType && BrowserUtils.mimeTypeIsTextBased(browser.documentContentType)) {
+ this.isImage.removeAttribute('disabled');
+ } else {
+ canViewSource = false;
+ this.isImage.setAttribute('disabled', 'true');
+ }
+
+ if (canViewSource) {
+ this.canViewSource.removeAttribute('disabled');
+ } else {
+ this.canViewSource.setAttribute('disabled', 'true');
+ }
+ }
+
+ this.isBusy = false;
+
+ if (this._busyUI) {
+ this._busyUI = false;
+
+ this.stopCommand.setAttribute("disabled", "true");
+ CombinedStopReload.switchToReload(aRequest instanceof Ci.nsIRequest);
+ }
+ }
+ },
+
+ onLocationChange: function (aWebProgress, aRequest, aLocationURI, aFlags) {
+ var location = aLocationURI ? aLocationURI.spec : "";
+
+ // 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.ownerGlobal;
+ tooltipWindow != tooltipWindow.parent;
+ tooltipWindow = tooltipWindow.parent) {
+ if (tooltipWindow == aWebProgress.DOMWindow) {
+ pageTooltip.hidePopup();
+ break;
+ }
+ }
+ }
+ }
+
+ let browser = gBrowser.selectedBrowser;
+
+ // Disable menu entries for images, enable otherwise
+ if (browser.documentContentType && BrowserUtils.mimeTypeIsTextBased(browser.documentContentType))
+ 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
+
+ if (aWebProgress.isTopLevel) {
+ if ((location == "about:blank" && checkEmptyPageOrigin()) ||
+ 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");
+ }
+
+ URLBarSetURI(aLocationURI);
+
+ BookmarkingUI.onLocationChange();
+
+ gIdentityHandler.onLocationChange();
+
+
+ gTabletModePageCounter.inc();
+
+ // 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");
+ }
+ }
+
+ 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);
+
+ // Try not to instantiate gCustomizeMode as much as possible,
+ // so don't use CustomizeMode.jsm to check for URI or customizing.
+ if (location == "about:blank" &&
+ gBrowser.selectedTab.hasAttribute("customizemode")) {
+ gCustomizeMode.enter();
+ } else if (CustomizationHandler.isEnteringCustomizeMode ||
+ CustomizationHandler.isCustomizing()) {
+ gCustomizeMode.exit();
+ }
+ }
+ UpdateBackForwardCommands(gBrowser.webNavigation);
+ ReaderParent.updateReaderButton(gBrowser.selectedBrowser);
+
+ 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();
+ BrowserSearch.updateOpenSearchBadge();
+ },
+
+ // Left here for add-on compatibility, see bug 752434
+ hideChromeForLocation: function() {},
+
+ onStatusChange: function (aWebProgress, aRequest, aStatus, aMessage) {
+ this.status = aMessage;
+ this.updateStatusField();
+ },
+
+ // Properties used to cache security state used to update the UI
+ _state: null,
+ _lastLocation: null,
+
+ // This is called in multiple ways:
+ // 1. Due to the nsIWebProgressListener.onSecurityChange notification.
+ // 2. Called by tabbrowser.xml when updating the current browser.
+ // 3. Called directly during this object's initializations.
+ // aRequest will be null always in case 2 and 3, and sometimes in case 1 (for
+ // instance, there won't be a request when STATE_BLOCKED_TRACKING_CONTENT is observed).
+ onSecurityChange: function (aWebProgress, aRequest, aState, aIsSimulated) {
+ // Don't need to do anything if the data we use to update the UI hasn't
+ // changed
+ let uri = gBrowser.currentURI;
+ let spec = uri.spec;
+ if (this._state == aState &&
+ this._lastLocation == spec)
+ return;
+ this._state = aState;
+ this._lastLocation = spec;
+
+ if (typeof(aIsSimulated) != "boolean" && typeof(aIsSimulated) != "undefined") {
+ throw "onSecurityChange: aIsSimulated receieved an unexpected type";
+ }
+
+ // Make sure the "https" part of the URL is striked out or not,
+ // depending on the current mixed active content blocking state.
+ gURLBar.formatValue();
+
+ try {
+ uri = Services.uriFixup.createExposableURI(uri);
+ } catch (e) {}
+ gIdentityHandler.updateIdentity(this._state, uri);
+ TrackingProtection.onSecurityChange(this._state, aIsSimulated);
+ },
+
+ // 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 () {
+ return 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;
+
+ let reload = document.getElementById("urlbar-reload-button");
+ let 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 = {
+ // Keep track of which browsers we've started load timers for, since
+ // we won't see STATE_START events for pre-rendered tabs.
+ _startedLoadTimer: new WeakSet(),
+
+ onStateChange: function (aBrowser, aWebProgress, aRequest, aStateFlags, aStatus) {
+ // Collect telemetry data about tab load times.
+ if (aWebProgress.isTopLevel && (!aRequest.originalURI || aRequest.originalURI.spec.scheme != "about")) {
+ if (aStateFlags & Ci.nsIWebProgressListener.STATE_IS_WINDOW) {
+ if (aStateFlags & Ci.nsIWebProgressListener.STATE_START) {
+ this._startedLoadTimer.add(aBrowser);
+ TelemetryStopwatch.start("FX_PAGE_LOAD_MS", aBrowser);
+ Services.telemetry.getHistogramById("FX_TOTAL_TOP_VISITS").add(true);
+ } else if (aStateFlags & Ci.nsIWebProgressListener.STATE_STOP &&
+ this._startedLoadTimer.has(aBrowser)) {
+ this._startedLoadTimer.delete(aBrowser);
+ TelemetryStopwatch.finish("FX_PAGE_LOAD_MS", aBrowser);
+ }
+ } else if (aStateFlags & Ci.nsIWebProgressListener.STATE_STOP &&
+ aStatus == Cr.NS_BINDING_ABORTED &&
+ this._startedLoadTimer.has(aBrowser)) {
+ this._startedLoadTimer.delete(aBrowser);
+ TelemetryStopwatch.cancel("FX_PAGE_LOAD_MS", aBrowser);
+ }
+ }
+
+ // We used to listen for clicks in the browser here, but when that
+ // became unnecessary, removing the code below caused focus issues.
+ // This code should be removed. Tracked in bug 1337794.
+ let isRemoteBrowser = aBrowser.isRemoteBrowser;
+ // We check isRemoteBrowser here to avoid requesting the doc CPOW
+ let doc = isRemoteBrowser ? null : aWebProgress.DOMWindow.document;
+
+ if (!isRemoteBrowser &&
+ aStateFlags & Ci.nsIWebProgressListener.STATE_STOP &&
+ Components.isSuccessCode(aStatus) &&
+ doc.documentURI.startsWith("about:") &&
+ !doc.documentURI.toLowerCase().startsWith("about:blank") &&
+ !doc.documentURI.toLowerCase().startsWith("about:home") &&
+ !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("pagehide", function onPageHide(event) {
+ if (event.target.defaultView.frameElement)
+ return;
+ aBrowser.removeEventListener("pagehide", onPageHide, true);
+ if (event.target.documentElement)
+ event.target.documentElement.removeAttribute("hasBrowserHandlers");
+ }, true);
+ }
+ },
+
+ 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) {
+ // Reader mode actually cares about these:
+ let mm = gBrowser.selectedBrowser.messageManager;
+ mm.sendAsyncMessage("Reader:PushState", {isArticle: gBrowser.selectedBrowser.isArticle});
+ return;
+ }
+
+ // Filter out location changes in sub documents.
+ if (!aWebProgress.isTopLevel)
+ 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);
+
+ let tab = gBrowser.getTabForBrowser(aBrowser);
+ if (tab && tab._sharingState) {
+ gBrowser.setBrowserSharing(aBrowser, {});
+ webrtcUI.forgetStreamsFromBrowser(aBrowser);
+ }
+
+ gBrowser.getNotificationBox(aBrowser).removeTransientNotifications();
+
+ FullZoom.onLocationChange(aLocationURI, false, aBrowser);
+ },
+}
+
+function nsBrowserAccess() { }
+
+nsBrowserAccess.prototype = {
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIBrowserDOMWindow, Ci.nsISupports]),
+
+ _openURIInNewTab: function(aURI, aReferrer, aReferrerPolicy, aIsPrivate,
+ aIsExternal, aForceNotRemote=false,
+ aUserContextId=Ci.nsIScriptSecurityManager.DEFAULT_USER_CONTEXT_ID,
+ aOpener = null, aTriggeringPrincipal = null) {
+ 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 {
+ win = RecentWindow.getMostRecentBrowserWindow({private: aIsPrivate});
+ needToFocusWin = true;
+ }
+
+ if (!win) {
+ // we couldn't find a suitable window, a new one needs to be opened.
+ return null;
+ }
+
+ if (aIsExternal && (!aURI || aURI.spec == "about:blank")) {
+ win.BrowserOpenTab(); // this also focuses the location bar
+ win.focus();
+ return win.gBrowser.selectedBrowser;
+ }
+
+ let loadInBackground = gPrefService.getBoolPref("browser.tabs.loadDivertedInBackground");
+
+ let tab = win.gBrowser.loadOneTab(aURI ? aURI.spec : "about:blank", {
+ triggeringPrincipal: aTriggeringPrincipal,
+ referrerURI: aReferrer,
+ referrerPolicy: aReferrerPolicy,
+ userContextId: aUserContextId,
+ fromExternal: aIsExternal,
+ inBackground: loadInBackground,
+ forceNotRemote: aForceNotRemote,
+ opener: aOpener,
+ });
+ let browser = win.gBrowser.getBrowserForTab(tab);
+
+ if (needToFocusWin || (!loadInBackground && aIsExternal))
+ win.focus();
+
+ return browser;
+ },
+
+ openURI: function (aURI, aOpener, aWhere, aFlags) {
+ // This function should only ever be called if we're opening a URI
+ // from a non-remote browser window (via nsContentTreeOwner).
+ if (aOpener && Cu.isCrossProcessWrapper(aOpener)) {
+ Cu.reportError("nsBrowserAccess.openURI was passed a CPOW for aOpener. " +
+ "openURI should only ever be called from non-remote browsers.");
+ throw Cr.NS_ERROR_FAILURE;
+ }
+
+ var newWindow = null;
+ var isExternal = !!(aFlags & 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;
+ }
+ let isPrivate = aOpener
+ ? PrivateBrowsingUtils.isContentWindowPrivate(aOpener)
+ : PrivateBrowsingUtils.isWindowPrivate(window);
+
+ 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";
+ let features = "all,dialog=no";
+ if (isPrivate) {
+ features += ",private";
+ }
+ // Pass all params to openDialog to ensure that "url" isn't passed through
+ // loadOneOrMoreURIs, which splits based on "|"
+ newWindow = openDialog(getBrowserURL(), "_blank", features, url, null, null, null);
+ break;
+ case Ci.nsIBrowserDOMWindow.OPEN_NEWTAB :
+ // If we have an opener, that means that the caller is expecting access
+ // to the nsIDOMWindow of the opened tab right away. For e10s windows,
+ // this means forcing the newly opened browser to be non-remote so that
+ // we can hand back the nsIDOMWindow. The XULBrowserWindow.shouldLoadURI
+ // will do the job of shuttling off the newly opened browser to run in
+ // the right process once it starts loading a URI.
+ let forceNotRemote = !!aOpener;
+ let userContextId = aOpener && aOpener.document
+ ? aOpener.document.nodePrincipal.originAttributes.userContextId
+ : Ci.nsIScriptSecurityManager.DEFAULT_USER_CONTEXT_ID;
+ let openerWindow = (aFlags & Ci.nsIBrowserDOMWindow.OPEN_NO_OPENER) ? null : aOpener;
+ let browser = this._openURIInNewTab(aURI, referrer, referrerPolicy,
+ isPrivate, isExternal,
+ forceNotRemote, userContextId,
+ openerWindow, triggeringPrincipal);
+ if (browser)
+ newWindow = browser.contentWindow;
+ 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, {
+ triggeringPrincipal,
+ flags: loadflags,
+ referrerURI: referrer,
+ referrerPolicy: referrerPolicy,
+ });
+ }
+ if (!gPrefService.getBoolPref("browser.tabs.loadDivertedInBackground"))
+ window.focus();
+ }
+ return newWindow;
+ },
+
+ openURIInFrame: function browser_openURIInFrame(aURI, aParams, aWhere, aFlags) {
+ if (aWhere != Ci.nsIBrowserDOMWindow.OPEN_NEWTAB) {
+ dump("Error: openURIInFrame can only open in new tabs");
+ return null;
+ }
+
+ var isExternal = !!(aFlags & Ci.nsIBrowserDOMWindow.OPEN_EXTERNAL);
+
+ var userContextId = aParams.openerOriginAttributes &&
+ ("userContextId" in aParams.openerOriginAttributes)
+ ? aParams.openerOriginAttributes.userContextId
+ : Ci.nsIScriptSecurityManager.DEFAULT_USER_CONTEXT_ID
+
+ let browser = this._openURIInNewTab(aURI, aParams.referrer,
+ aParams.referrerPolicy,
+ aParams.isPrivate,
+ isExternal, false,
+ userContextId, null,
+ aParams.triggeringPrincipal);
+ if (browser)
+ return browser.QueryInterface(Ci.nsIFrameLoaderOwner);
+
+ return null;
+ },
+
+ isTabContentWindow: function (aWindow) {
+ return gBrowser.browsers.some(browser => browser.contentWindow == aWindow);
+ },
+
+ canClose() {
+ return CanCloseWindow();
+ },
+}
+
+function getTogglableToolbars() {
+ let toolbarNodes = Array.slice(gNavToolbox.childNodes);
+ toolbarNodes = toolbarNodes.concat(gNavToolbox.externalToolbars);
+ toolbarNodes = toolbarNodes.filter(node => node.getAttribute("toolbarname"));
+ return toolbarNodes;
+}
+
+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 = getTogglableToolbars();
+
+ for (let toolbar of toolbarNodes) {
+ 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", toolbar.getAttribute("toolbarname"));
+ menuItem.setAttribute("checked", toolbar.getAttribute(hidingAttribute) != "true");
+ 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);
+ }
+
+
+ let moveToPanel = popup.querySelector(".customize-context-moveToPanel");
+ let removeFromToolbar = popup.querySelector(".customize-context-removeFromToolbar");
+ // View -> Toolbars menu doesn't have the moveToPanel or removeFromToolbar items.
+ if (!moveToPanel || !removeFromToolbar) {
+ return;
+ }
+
+ // triggerNode can be a nested child element of a toolbaritem.
+ let toolbarItem = popup.triggerNode;
+
+ if (toolbarItem && toolbarItem.localName == "toolbarpaletteitem") {
+ toolbarItem = toolbarItem.firstChild;
+ } else if (toolbarItem && toolbarItem.localName != "toolbar") {
+ while (toolbarItem && toolbarItem.parentNode) {
+ let parent = toolbarItem.parentNode;
+ if ((parent.classList && parent.classList.contains("customization-target")) ||
+ parent.getAttribute("overflowfortoolbar") || // Needs to work in the overflow list as well.
+ parent.localName == "toolbarpaletteitem" ||
+ parent.localName == "toolbar")
+ break;
+ toolbarItem = parent;
+ }
+ } else {
+ toolbarItem = null;
+ }
+
+ let showTabStripItems = toolbarItem && toolbarItem.id == "tabbrowser-tabs";
+ for (let node of popup.querySelectorAll('menuitem[contexttype="toolbaritem"]')) {
+ node.hidden = showTabStripItems;
+ }
+
+ for (let node of popup.querySelectorAll('menuitem[contexttype="tabbar"]')) {
+ node.hidden = !showTabStripItems;
+ }
+
+ if (showTabStripItems) {
+ PlacesCommandHook.updateBookmarkAllTabsCommand();
+
+ let haveMultipleTabs = gBrowser.visibleTabs.length > 1;
+ document.getElementById("toolbar-context-reloadAllTabs").disabled = !haveMultipleTabs;
+
+ document.getElementById("toolbar-context-undoCloseTab").disabled =
+ SessionStore.getClosedTabCount(window) == 0;
+ return;
+ }
+
+ // In some cases, we will exit the above loop with toolbarItem being the
+ // xul:document. That has no parentNode, and we should disable the items in
+ // this case.
+ let movable = toolbarItem && toolbarItem.parentNode &&
+ CustomizableUI.isWidgetRemovable(toolbarItem);
+ if (movable) {
+ moveToPanel.removeAttribute("disabled");
+ removeFromToolbar.removeAttribute("disabled");
+ } else {
+ moveToPanel.setAttribute("disabled", true);
+ removeFromToolbar.setAttribute("disabled", true);
+ }
+}
+
+function onViewToolbarCommand(aEvent) {
+ var toolbarId = aEvent.originalTarget.getAttribute("toolbarId");
+ var isVisible = aEvent.originalTarget.getAttribute("checked") == "true";
+ CustomizableUI.setToolbarVisibility(toolbarId, isVisible);
+}
+
+function setToolbarVisibility(toolbar, isVisible, persist=true) {
+ let hidingAttribute;
+ if (toolbar.getAttribute("type") == "menubar") {
+ hidingAttribute = "autohide";
+ if (AppConstants.platform == "linux") {
+ Services.prefs.setBoolPref("ui.key.menuAccessKeyFocuses", !isVisible);
+ }
+ } else {
+ hidingAttribute = "collapsed";
+ }
+
+ toolbar.setAttribute(hidingAttribute, !isVisible);
+ if (persist) {
+ document.persist(toolbar.id, hidingAttribute);
+ }
+
+ let eventParams = {
+ detail: {
+ visible: isVisible
+ },
+ bubbles: true
+ };
+ let event = new CustomEvent("toolbarvisibilitychange", eventParams);
+ toolbar.dispatchEvent(event);
+
+ PlacesToolbarHelper.init();
+ BookmarkingUI.onToolbarVisibilityChange();
+ if (isVisible)
+ ToolbarIconColor.inferFromText();
+}
+
+var TabletModeUpdater = {
+ init() {
+ if (AppConstants.isPlatformAndVersionAtLeast("win", "10")) {
+ this.update(WindowsUIUtils.inTabletMode);
+ Services.obs.addObserver(this, "tablet-mode-change", false);
+ }
+ },
+
+ uninit() {
+ if (AppConstants.isPlatformAndVersionAtLeast("win", "10")) {
+ Services.obs.removeObserver(this, "tablet-mode-change");
+ }
+ },
+
+ observe(subject, topic, data) {
+ this.update(data == "tablet-mode");
+ },
+
+ update(isInTabletMode) {
+ let wasInTabletMode = document.documentElement.hasAttribute("tabletmode");
+ if (isInTabletMode) {
+ document.documentElement.setAttribute("tabletmode", "true");
+ } else {
+ document.documentElement.removeAttribute("tabletmode");
+ }
+ if (wasInTabletMode != isInTabletMode) {
+ TabsInTitlebar.updateAppearance(true);
+ }
+ },
+};
+
+var gTabletModePageCounter = {
+ enabled: false,
+ inc() {
+ this.enabled = AppConstants.isPlatformAndVersionAtLeast("win", "10.0");
+ if (!this.enabled) {
+ this.inc = () => {};
+ return;
+ }
+ this.inc = this._realInc;
+ this.inc();
+ },
+
+ _desktopCount: 0,
+ _tabletCount: 0,
+ _realInc() {
+ let inTabletMode = document.documentElement.hasAttribute("tabletmode");
+ this[inTabletMode ? "_tabletCount" : "_desktopCount"]++;
+ },
+
+ finish() {
+ if (this.enabled) {
+ let histogram = Services.telemetry.getKeyedHistogramById("FX_TABLETMODE_PAGE_LOAD");
+ histogram.add("tablet", this._tabletCount);
+ histogram.add("desktop", this._desktopCount);
+ }
+ },
+};
+
+function displaySecurityInfo()
+{
+ BrowserPageInfo(null, "securityTab");
+}
+
+
+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 (["about:home", "about:newtab"].includes(homePage.toLowerCase()))
+ 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;
+ },
+};
+
+const nodeToTooltipMap = {
+ "bookmarks-menu-button": "bookmarksMenuButton.tooltip",
+ "new-window-button": "newWindowButton.tooltip",
+ "new-tab-button": "newTabButton.tooltip",
+ "tabs-newtab-button": "newTabButton.tooltip",
+ "fullscreen-button": "fullscreenButton.tooltip",
+ "downloads-button": "downloads.tooltip",
+};
+const nodeToShortcutMap = {
+ "bookmarks-menu-button": "manBookmarkKb",
+ "new-window-button": "key_newNavigator",
+ "new-tab-button": "key_newNavigatorTab",
+ "tabs-newtab-button": "key_newNavigatorTab",
+ "fullscreen-button": "key_fullScreen",
+ "downloads-button": "key_openDownloads"
+};
+
+if (AppConstants.platform == "macosx") {
+ nodeToTooltipMap["print-button"] = "printButton.tooltip";
+ nodeToShortcutMap["print-button"] = "printKb";
+}
+
+const gDynamicTooltipCache = new Map();
+function UpdateDynamicShortcutTooltipText(aTooltip) {
+ let nodeId = aTooltip.triggerNode.id || aTooltip.triggerNode.getAttribute("anonid");
+ if (!gDynamicTooltipCache.has(nodeId) && nodeId in nodeToTooltipMap) {
+ let strId = nodeToTooltipMap[nodeId];
+ let args = [];
+ if (nodeId in nodeToShortcutMap) {
+ let shortcutId = nodeToShortcutMap[nodeId];
+ let shortcut = document.getElementById(shortcutId);
+ if (shortcut) {
+ args.push(ShortcutUtils.prettifyShortcut(shortcut));
+ }
+ }
+ gDynamicTooltipCache.set(nodeId, gNavigatorBundle.getFormattedString(strId, args));
+ }
+ aTooltip.setAttribute("label", gDynamicTooltipCache.get(nodeId));
+}
+
+function getBrowserSelection(aCharLen) {
+ Deprecated.warning("getBrowserSelection",
+ "https://bugzilla.mozilla.org/show_bug.cgi?id=1134769");
+
+ let focusedElement = document.activeElement;
+ if (focusedElement && focusedElement.localName == "browser" &&
+ focusedElement.isRemoteBrowser) {
+ throw "getBrowserSelection doesn't support child process windows";
+ }
+
+ return BrowserUtils.getSelectionDetails(window, aCharLen).text;
+}
+
+var gWebPanelURI;
+function openWebPanel(title, uri) {
+ // Ensure that the web panels sidebar is open.
+ SidebarUI.show("viewWebPanelsSidebar");
+
+ // Set the title of the panel.
+ SidebarUI.title = title;
+
+ // Tell the Web Panels sidebar to load the bookmark.
+ if (SidebarUI.browser.docShell && SidebarUI.browser.contentDocument &&
+ SidebarUI.browser.contentDocument.getElementById("web-panels-browser")) {
+ SidebarUI.browser.contentWindow.loadWebPanel(uri);
+ if (gWebPanelURI) {
+ gWebPanelURI = "";
+ SidebarUI.browser.removeEventListener("load", asyncOpenWebPanel, true);
+ }
+ } else {
+ // The panel is still being constructed. Attach an onload handler.
+ if (!gWebPanelURI) {
+ SidebarUI.browser.addEventListener("load", asyncOpenWebPanel, true);
+ }
+ gWebPanelURI = uri;
+ }
+}
+
+function asyncOpenWebPanel(event) {
+ if (gWebPanelURI && SidebarUI.browser.contentDocument &&
+ SidebarUI.browser.contentDocument.getElementById("web-panels-browser")) {
+ SidebarUI.browser.contentWindow.loadWebPanel(gWebPanelURI);
+ }
+ gWebPanelURI = "";
+ SidebarUI.browser.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.getAttribute("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;
+ }
+
+ var referrerURI = doc.documentURIObject;
+ // if the mixedContentChannel is present and the referring URI passes
+ // a same origin check with the target URI, we can preserve the users
+ // decision of disabling MCB on a page for it's child tabs.
+ var persistAllowMixedContentInChildTab = false;
+
+ if (where == "tab" && gBrowser.docShell.mixedContentChannel) {
+ const sm = Services.scriptSecurityManager;
+ try {
+ var targetURI = makeURI(href);
+ sm.checkSameOriginURI(referrerURI, targetURI, false);
+ persistAllowMixedContentInChildTab = true;
+ }
+ catch (e) { }
+ }
+
+ // first get document wide referrer policy, then
+ // get referrer attribute from clicked link and parse it and
+ // allow per element referrer to overrule the document wide referrer if enabled
+ let referrerPolicy = doc.referrerPolicy;
+ if (Services.prefs.getBoolPref("network.http.enablePerElementReferrer") &&
+ linkNode) {
+ let referrerAttrValue = Services.netUtils.parseAttributePolicyString(linkNode.
+ getAttribute("referrerpolicy"));
+ if (referrerAttrValue != Ci.nsIHttpChannel.REFERRER_POLICY_UNSET) {
+ referrerPolicy = referrerAttrValue;
+ }
+ }
+
+ urlSecurityCheck(href, doc.nodePrincipal);
+ let params = {
+ charset: doc.characterSet,
+ allowMixedContent: persistAllowMixedContentInChildTab,
+ referrerURI: referrerURI,
+ referrerPolicy: referrerPolicy,
+ noReferrer: BrowserUtils.linkHasNoReferrer(linkNode),
+ originPrincipal: doc.nodePrincipal,
+ triggeringPrincipal: doc.nodePrincipal,
+ };
+
+ // The new tab/window must use the same userContextId
+ if (doc.nodePrincipal.originAttributes.userContextId) {
+ params.userContextId = doc.nodePrincipal.originAttributes.userContextId;
+ }
+
+ openLinkIn(href, where, params);
+ 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, "");
+
+ clipboard = stripUnsafeProtocolOnPaste(clipboard);
+
+ // 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 });
+ }
+ });
+
+ event.stopPropagation();
+}
+
+function stripUnsafeProtocolOnPaste(pasteData) {
+ // Don't allow pasting javascript URIs since we don't support
+ // LOAD_FLAGS_DISALLOW_INHERIT_PRINCIPAL for those.
+ return pasteData.replace(/\r?\n/g, "").replace(/^(?:\W*javascript:)+/i, "");
+}
+
+// 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 userContextId = gBrowser.selectedBrowser.getAttribute("usercontextid");
+
+ // event is null if links are dropped in content process.
+ // inBackground should be false, as it's loading into current browser.
+ let inBackground = false;
+ if (event) {
+ 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,
+ userContextId,
+ });
+ }
+ });
+
+ // If links are dropped in content process, event.preventDefault() should be
+ // called in content process.
+ if (event) {
+ // Keep the event from being handled by the dragDrop listeners
+ // built-in to gecko if they happen to be above us.
+ event.preventDefault();
+ }
+}
+
+function BrowserSetForcedCharacterSet(aCharset)
+{
+ if (aCharset) {
+ gBrowser.selectedBrowser.characterSet = 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 UpdateCurrentCharset(target) {
+ let selectedCharset = CharsetMenu.foldCharset(gBrowser.selectedBrowser.characterSet);
+ for (let menuItem of target.getElementsByTagName("menuitem")) {
+ let isSelected = menuItem.getAttribute("charset") === selectedCharset;
+ menuItem.setAttribute("checked", isSelected);
+ }
+}
+
+var gPageStyleMenu = {
+ // This maps from a <browser> element (or, more specifically, a
+ // browser's permanentKey) to an Object that contains the most recent
+ // information about the browser content's stylesheets. That Object
+ // is populated via the PageStyle:StyleSheets message from the content
+ // process. The Object should have the following structure:
+ //
+ // filteredStyleSheets (Array):
+ // An Array of objects with a filtered list representing all stylesheets
+ // that the current page offers. Each object has the following members:
+ //
+ // title (String):
+ // The title of the stylesheet
+ //
+ // disabled (bool):
+ // Whether or not the stylesheet is currently applied
+ //
+ // href (String):
+ // The URL of the stylesheet. Stylesheets loaded via a data URL will
+ // have this property set to null.
+ //
+ // authorStyleDisabled (bool):
+ // Whether or not the user currently has "No Style" selected for
+ // the current page.
+ //
+ // preferredStyleSheetSet (bool):
+ // Whether or not the user currently has the "Default" style selected
+ // for the current page.
+ //
+ _pageStyleSheets: new WeakMap(),
+
+ init: function() {
+ let mm = window.messageManager;
+ mm.addMessageListener("PageStyle:StyleSheets", (msg) => {
+ this._pageStyleSheets.set(msg.target.permanentKey, msg.data);
+ });
+ },
+
+ /**
+ * Returns an array of Objects representing stylesheets in a
+ * browser. Note that the pageshow event needs to fire in content
+ * before this information will be available.
+ *
+ * @param browser (optional)
+ * The <xul:browser> to search for stylesheets. If omitted, this
+ * defaults to the currently selected tab's browser.
+ * @returns Array
+ * An Array of Objects representing stylesheets in the browser.
+ * See the documentation for gPageStyleMenu for a description
+ * of the Object structure.
+ */
+ getBrowserStyleSheets: function (browser) {
+ if (!browser) {
+ browser = gBrowser.selectedBrowser;
+ }
+
+ let data = this._pageStyleSheets.get(browser.permanentKey);
+ if (!data) {
+ return [];
+ }
+ return data.filteredStyleSheets;
+ },
+
+ _getStyleSheetInfo: function (browser) {
+ let data = this._pageStyleSheets.get(browser.permanentKey);
+ if (!data) {
+ return {
+ filteredStyleSheets: [],
+ authorStyleDisabled: false,
+ preferredStyleSheetSet: true
+ };
+ }
+
+ return data;
+ },
+
+ fillPopup: function (menuPopup) {
+ let styleSheetInfo = this._getStyleSheetInfo(gBrowser.selectedBrowser);
+ var noStyle = menuPopup.firstChild;
+ var persistentOnly = noStyle.nextSibling;
+ var sep = persistentOnly.nextSibling;
+ while (sep.nextSibling)
+ menuPopup.removeChild(sep.nextSibling);
+
+ let styleSheets = styleSheetInfo.filteredStyleSheets;
+ var currentStyleSheets = {};
+ var styleDisabled = styleSheetInfo.authorStyleDisabled;
+ var haveAltSheets = false;
+ var altStyleSelected = false;
+
+ for (let currentStyleSheet of styleSheets) {
+ 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 = styleSheetInfo.preferredStyleSheetSet ? haveAltSheets : false;
+ sep.hidden = (noStyle.hidden && persistentOnly.hidden) || !haveAltSheets;
+ },
+
+ switchStyleSheet: function (title) {
+ let mm = gBrowser.selectedBrowser.messageManager;
+ mm.sendAsyncMessage("PageStyle:Switch", {title: title});
+ },
+
+ disableStyle: function () {
+ let mm = gBrowser.selectedBrowser.messageManager;
+ mm.sendAsyncMessage("PageStyle:Disable");
+ },
+};
+
+/* Legacy global page-style functions */
+var stylesheetFillPopup = gPageStyleMenu.fillPopup.bind(gPageStyleMenu);
+function stylesheetSwitchAll(contentWindow, title) {
+ // We ignore the contentWindow param. Add-ons don't appear to use
+ // it, and it's difficult to support in e10s (where it will be a
+ // CPOW).
+ gPageStyleMenu.switchStyleSheet(title);
+}
+function setStyleDisabled(disabled) {
+ if (disabled)
+ gPageStyleMenu.disableStyle();
+}
+
+
+var LanguageDetectionListener = {
+ init: function() {
+ window.messageManager.addMessageListener("Translation:DocumentState", msg => {
+ Translation.documentStateReceived(msg.target, msg.data);
+ });
+ }
+};
+
+
+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;
+
+ 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 notification is also received because of a loss in connectivity,
+ // which we ignore by updating the UI to the current value of io.offline
+ this._updateOfflineUI(Services.io.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 = {
+ warnUsage(browser, uri) {
+ if (!browser)
+ return;
+
+ let mainAction = {
+ label: gNavigatorBundle.getString("offlineApps.manageUsage"),
+ accessKey: gNavigatorBundle.getString("offlineApps.manageUsageAccessKey"),
+ callback: this.manage
+ };
+
+ let warnQuotaKB = Services.prefs.getIntPref("offline-apps.quota.warn");
+ // This message shows the quota in MB, and so we divide the quota (in kb) by 1024.
+ let message = gNavigatorBundle.getFormattedString("offlineApps.usage",
+ [ uri.host,
+ warnQuotaKB / 1024 ]);
+
+ let anchorID = "indexedDB-notification-icon";
+ PopupNotifications.show(browser, "offline-app-usage", message,
+ anchorID, mainAction);
+
+ // Now that we've warned once, prevent the warning from showing up
+ // again.
+ Services.perms.add(uri, "offline-app",
+ Ci.nsIOfflineCacheUpdateService.ALLOW_NO_WARN);
+ },
+
+ // XXX: duplicated in preferences/advanced.js
+ _getOfflineAppUsage(host, groups) {
+ let cacheService = Cc["@mozilla.org/network/application-cache-service;1"].
+ getService(Ci.nsIApplicationCacheService);
+ if (!groups) {
+ try {
+ groups = cacheService.getGroups();
+ } catch (ex) {
+ return 0;
+ }
+ }
+
+ let usage = 0;
+ for (let group of groups) {
+ let uri = Services.io.newURI(group, null, null);
+ if (uri.asciiHost == host) {
+ let cache = cacheService.getActiveCache(group);
+ usage += cache.usage;
+ }
+ }
+
+ return usage;
+ },
+
+ _usedMoreThanWarnQuota(uri) {
+ // if the user has already allowed excessive usage, don't bother checking
+ if (Services.perms.testExactPermission(uri, "offline-app") !=
+ Ci.nsIOfflineCacheUpdateService.ALLOW_NO_WARN) {
+ let usageBytes = this._getOfflineAppUsage(uri.asciiHost);
+ let warnQuotaKB = Services.prefs.getIntPref("offline-apps.quota.warn");
+ // The pref is in kb, the usage we get is in bytes, so multiply the quota
+ // to compare correctly:
+ if (usageBytes >= warnQuotaKB * 1024) {
+ return true;
+ }
+ }
+
+ return false;
+ },
+
+ requestPermission(browser, docId, uri) {
+ let host = uri.asciiHost;
+ let notificationID = "offline-app-requested-" + host;
+ let notification = PopupNotifications.getNotification(notificationID, browser);
+
+ if (notification) {
+ notification.options.controlledItems.push([
+ Cu.getWeakReference(browser), docId, uri
+ ]);
+ } else {
+ let mainAction = {
+ label: gNavigatorBundle.getString("offlineApps.allow"),
+ accessKey: gNavigatorBundle.getString("offlineApps.allowAccessKey"),
+ callback: function() {
+ for (let [browser, docId, uri] of notification.options.controlledItems) {
+ OfflineApps.allowSite(browser, docId, uri);
+ }
+ }
+ };
+ let secondaryActions = [{
+ label: gNavigatorBundle.getString("offlineApps.never"),
+ accessKey: gNavigatorBundle.getString("offlineApps.neverAccessKey"),
+ callback: function() {
+ for (let [, , uri] of notification.options.controlledItems) {
+ OfflineApps.disallowSite(uri);
+ }
+ }
+ }];
+ let message = gNavigatorBundle.getFormattedString("offlineApps.available",
+ [host]);
+ let anchorID = "indexedDB-notification-icon";
+ let options = {
+ controlledItems : [[Cu.getWeakReference(browser), docId, uri]]
+ };
+ notification = PopupNotifications.show(browser, notificationID, message,
+ anchorID, mainAction,
+ secondaryActions, options);
+ }
+ },
+
+ disallowSite(uri) {
+ Services.perms.add(uri, "offline-app", Services.perms.DENY_ACTION);
+ },
+
+ allowSite(browserRef, docId, uri) {
+ Services.perms.add(uri, "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.
+ let browser = browserRef.get();
+ if (browser && browser.messageManager) {
+ browser.messageManager.sendAsyncMessage("OfflineApps:StartFetching", {
+ docId,
+ });
+ }
+ },
+
+ manage() {
+ openAdvancedPreferences("networkTab");
+ },
+
+ receiveMessage(msg) {
+ switch (msg.name) {
+ case "OfflineApps:CheckUsage":
+ let uri = makeURI(msg.data.uri);
+ if (this._usedMoreThanWarnQuota(uri)) {
+ this.warnUsage(msg.target, uri);
+ }
+ break;
+ case "OfflineApps:RequestPermission":
+ this.requestPermission(msg.target, msg.data.docId, makeURI(msg.data.uri));
+ break;
+ }
+ },
+
+ init() {
+ let mm = window.messageManager;
+ mm.addMessageListener("OfflineApps:CheckUsage", this);
+ mm.addMessageListener("OfflineApps:RequestPermission", this);
+ },
+};
+
+var IndexedDBPromptHelper = {
+ _permissionsPrompt: "indexedDB-permissions-prompt",
+ _permissionsResponse: "indexedDB-permissions-response",
+
+ _notificationIcon: "indexedDB-notification-icon",
+
+ init:
+ function IndexedDBPromptHelper_init() {
+ Services.obs.addObserver(this, this._permissionsPrompt, false);
+ },
+
+ uninit:
+ function IndexedDBPromptHelper_uninit() {
+ Services.obs.removeObserver(this, this._permissionsPrompt);
+ },
+
+ observe:
+ function IndexedDBPromptHelper_observe(subject, topic, data) {
+ if (topic != this._permissionsPrompt) {
+ throw new Error("Unexpected topic!");
+ }
+
+ var requestor = subject.QueryInterface(Ci.nsIInterfaceRequestor);
+
+ var browser = requestor.getInterface(Ci.nsIDOMNode);
+ if (browser.ownerGlobal != window) {
+ // Only listen for notifications for browsers in our chrome window.
+ return;
+ }
+
+ var host = browser.currentURI.asciiHost;
+
+ var message;
+ var responseTopic;
+ if (topic == this._permissionsPrompt) {
+ message = gNavigatorBundle.getFormattedString("offlineApps.available",
+ [ host ]);
+ responseTopic = this._permissionsResponse;
+ }
+
+ 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().
+ 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);
+ }
+ }
+ };
+
+ 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 CanCloseWindow()
+{
+ // Avoid redundant calls to canClose from showing multiple
+ // PermitUnload dialogs.
+ if (Services.startup.shuttingDown || window.skipNextCanClose) {
+ return true;
+ }
+
+ for (let browser of gBrowser.browsers) {
+ let {permitUnload, timedOut} = browser.permitUnload();
+ if (timedOut) {
+ return true;
+ }
+ if (!permitUnload) {
+ return false;
+ }
+ }
+ return true;
+}
+
+function WindowIsClosing()
+{
+ if (!closeWindow(false, warnAboutClosingWindow))
+ return false;
+
+ // In theory we should exit here and the Window's internal Close
+ // method should trigger canClose on nsBrowserAccess. However, by
+ // that point it's too late to be able to show a prompt for
+ // PermitUnload. So we do it here, when we still can.
+ if (CanCloseWindow()) {
+ // This flag ensures that the later canClose call does nothing.
+ // It's only needed to make tests pass, since they detect the
+ // prompt even when it's not actually shown.
+ window.skipNextCanClose = true;
+ return true;
+ }
+
+ return false;
+}
+
+/**
+ * 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; we also ignore private windows.
+ let isPBWindow = PrivateBrowsingUtils.isWindowPrivate(window) &&
+ !PrivateBrowsingUtils.permanentPrivateBrowsing;
+ if (!isPBWindow && !toolbar.visible)
+ return gBrowser.warnAboutClosingTabs(gBrowser.closingTabsEnum.ALL);
+
+ // Figure out if there's at least one other browser window around.
+ let otherPBWindowExists = false;
+ let nonPopupPresent = false;
+ for (let win of browserWindows()) {
+ if (!win.closed && 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);
+
+ // 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 AppConstants.platform != "macosx"
+ || (isPBWindow || gBrowser.warnAboutClosingTabs(gBrowser.closingTabsEnum.ALL));
+}
+
+var MailIntegration = {
+ sendLinkForBrowser: function (aBrowser) {
+ this.sendMessage(aBrowser.currentURI.spec, aBrowser.contentTitle);
+ },
+
+ 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) {
+ return new Promise(resolve => {
+ 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();
+ resolve(emWindow);
+ return;
+ }
+ }
+
+ 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);
+ resolve(aSubject);
+ }, "EM-loaded", false);
+ } else {
+ resolve();
+ }
+ });
+}
+
+function AddKeywordForSearchField() {
+ let mm = gBrowser.selectedBrowser.messageManager;
+
+ let onMessage = (message) => {
+ mm.removeMessageListener("ContextMenu:SearchFieldBookmarkData:Result", onMessage);
+
+ let bookmarkData = message.data;
+ let title = gNavigatorBundle.getFormattedString("addKeywordTitleAutoFill",
+ [bookmarkData.title]);
+ PlacesUIUtils.showBookmarkDialog({ action: "add"
+ , type: "bookmark"
+ , uri: makeURI(bookmarkData.spec)
+ , title: title
+ , description: bookmarkData.description
+ , keyword: ""
+ , postData: bookmarkData.postData
+ , charSet: bookmarkData.charset
+ , hiddenRows: [ "location"
+ , "description"
+ , "tags"
+ , "loadInSidebar" ]
+ }, window);
+ }
+ mm.addMessageListener("ContextMenu:SearchFieldBookmarkData:Result", onMessage);
+
+ mm.sendAsyncMessage("ContextMenu:SearchFieldBookmarkData", {}, { target: gContextMenu.target });
+}
+
+/**
+ * Re-open a closed tab.
+ * @param aIndex
+ * The index of the tab (via SessionStore.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 && isTabEmpty(gBrowser.selectedTab))
+ blankTabToRemove = gBrowser.selectedTab;
+
+ var tab = null;
+ if (SessionStore.getClosedTabCount(window) > (aIndex || 0)) {
+ tab = SessionStore.undoCloseTab(window, aIndex || 0);
+
+ if (blankTabToRemove)
+ gBrowser.removeTab(blankTabToRemove);
+ }
+
+ return tab;
+}
+
+/**
+ * Re-open a closed window.
+ * @param aIndex
+ * The index of the window (via SessionStore.getClosedWindowData)
+ * @returns a reference to the reopened window.
+ */
+function undoCloseWindow(aIndex) {
+ let window = null;
+ if (SessionStore.getClosedWindowCount() > (aIndex || 0))
+ window = SessionStore.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;
+
+ if (aTab.hasAttribute("customizemode"))
+ return false;
+
+ let browser = aTab.linkedBrowser;
+ if (!isBlankPageURL(browser.currentURI.spec))
+ return false;
+
+ if (!checkEmptyPageOrigin(browser))
+ return false;
+
+ if (browser.canGoForward || browser.canGoBack)
+ return false;
+
+ return true;
+}
+
+/**
+ * Check whether a page can be considered as 'empty', that its URI
+ * reflects its origin, and that if it's loaded in a tab, that tab
+ * could be considered 'empty' (e.g. like the result of opening
+ * a 'blank' new tab).
+ *
+ * We have to do more than just check the URI, because especially
+ * for things like about:blank, it is possible that the opener or
+ * some other page has control over the contents of the page.
+ *
+ * @param browser {Browser}
+ * The browser whose page we're checking (the selected browser
+ * in this window if omitted).
+ * @param uri {nsIURI}
+ * The URI against which we're checking (the browser's currentURI
+ * if omitted).
+ *
+ * @return false if the page was opened by or is controlled by arbitrary web
+ * content, unless that content corresponds with the URI.
+ * true if the page is blank and controlled by a principal matching
+ * that URI (or the system principal if the principal has no URI)
+ */
+function checkEmptyPageOrigin(browser = gBrowser.selectedBrowser,
+ uri = browser.currentURI) {
+ // If another page opened this page with e.g. window.open, this page might
+ // be controlled by its opener - return false.
+ if (browser.hasContentOpener) {
+ return false;
+ }
+ let contentPrincipal = browser.contentPrincipal;
+ // Not all principals have URIs...
+ if (contentPrincipal.URI) {
+ // There are two specialcases involving about:blank. One is where
+ // the user has manually loaded it and it got created with a null
+ // principal. The other involves the case where we load
+ // some other empty page in a browser and the current page is the
+ // initial about:blank page (which has that as its principal, not
+ // just URI in which case it could be web-based). Especially in
+ // e10s, we need to tackle that case specifically to avoid race
+ // conditions when updating the URL bar.
+ if ((uri.spec == "about:blank" && contentPrincipal.isNullPrincipal) ||
+ contentPrincipal.URI.spec == "about:blank") {
+ return true;
+ }
+ return contentPrincipal.URI.equals(uri);
+ }
+ // ... so for those that don't have them, enforce that the page has the
+ // system principal (this matches e.g. on about:newtab).
+ let ssm = Services.scriptSecurityManager;
+ return ssm.isSystemPrincipal(contentPrincipal);
+}
+
+function BrowserOpenSyncTabs() {
+ gSyncUI.openSyncedTabsPanel();
+}
+
+/**
+ * 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 = {
+ /**
+ * nsIURI for which the identity UI is displayed. This has been already
+ * processed by nsIURIFixup.createExposableURI.
+ */
+ _uri: null,
+
+ /**
+ * We only know the connection type if this._uri has a defined "host" part.
+ *
+ * These URIs, like "about:" and "data:" URIs, will usually be treated as a
+ * non-secure connection, unless they refer to an internally implemented
+ * browser page or resolve to "file:" URIs.
+ */
+ _uriHasHost: false,
+
+ /**
+ * Whether this._uri refers to an internally implemented browser page.
+ *
+ * Note that this is set for some "about:" pages, but general "chrome:" URIs
+ * are not included in this category by default.
+ */
+ _isSecureInternalUI: false,
+
+ /**
+ * nsISSLStatus metadata provided by gBrowser.securityUI the last time the
+ * identity UI was updated, or null if the connection is not secure.
+ */
+ _sslStatus: null,
+
+ /**
+ * Bitmask provided by nsIWebProgressListener.onSecurityChange.
+ */
+ _state: 0,
+
+ /**
+ * This flag gets set if the identity popup was opened by a keypress,
+ * to be able to focus it on the popupshown event.
+ */
+ _popupTriggeredByKeyboard: false,
+
+ /**
+ * Whether a permission is just removed from permission list.
+ */
+ _permissionJustRemoved: false,
+
+ get _isBroken() {
+ return this._state & Ci.nsIWebProgressListener.STATE_IS_BROKEN;
+ },
+
+ get _isSecure() {
+ // If a <browser> is included within a chrome document, then this._state
+ // will refer to the security state for the <browser> and not the top level
+ // document. In this case, don't upgrade the security state in the UI
+ // with the secure state of the embedded <browser>.
+ return !this._isURILoadedFromFile && this._state & Ci.nsIWebProgressListener.STATE_IS_SECURE;
+ },
+
+ get _isEV() {
+ // If a <browser> is included within a chrome document, then this._state
+ // will refer to the security state for the <browser> and not the top level
+ // document. In this case, don't upgrade the security state in the UI
+ // with the EV state of the embedded <browser>.
+ return !this._isURILoadedFromFile && this._state & Ci.nsIWebProgressListener.STATE_IDENTITY_EV_TOPLEVEL;
+ },
+
+ get _isMixedActiveContentLoaded() {
+ return this._state & Ci.nsIWebProgressListener.STATE_LOADED_MIXED_ACTIVE_CONTENT;
+ },
+
+ get _isMixedActiveContentBlocked() {
+ return this._state & Ci.nsIWebProgressListener.STATE_BLOCKED_MIXED_ACTIVE_CONTENT;
+ },
+
+ get _isMixedPassiveContentLoaded() {
+ return this._state & Ci.nsIWebProgressListener.STATE_LOADED_MIXED_DISPLAY_CONTENT;
+ },
+
+ get _isCertUserOverridden() {
+ return this._state & Ci.nsIWebProgressListener.STATE_CERT_USER_OVERRIDDEN;
+ },
+
+ get _hasInsecureLoginForms() {
+ // checks if the page has been flagged for an insecure login. Also checks
+ // if the pref to degrade the UI is set to true
+ return LoginManagerParent.hasInsecureLoginForms(gBrowser.selectedBrowser) &&
+ Services.prefs.getBoolPref("security.insecure_password.ui.enabled");
+ },
+
+ // smart getters
+ 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 _identityPopupMultiView () {
+ delete _identityPopupMultiView;
+ return document.getElementById("identity-popup-multiView");
+ },
+ get _identityPopupContentHosts () {
+ delete this._identityPopupContentHosts;
+ let selector = ".identity-popup-headline.host";
+ return this._identityPopupContentHosts = [
+ ...this._identityPopupMultiView._mainView.querySelectorAll(selector),
+ ...document.querySelectorAll(selector)
+ ];
+ },
+ get _identityPopupContentHostless () {
+ delete this._identityPopupContentHostless;
+ let selector = ".identity-popup-headline.hostless";
+ return this._identityPopupContentHostless = [
+ ...this._identityPopupMultiView._mainView.querySelectorAll(selector),
+ ...document.querySelectorAll(selector)
+ ];
+ },
+ 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 _identityPopupMixedContentLearnMore () {
+ delete this._identityPopupMixedContentLearnMore;
+ return this._identityPopupMixedContentLearnMore =
+ document.getElementById("identity-popup-mcb-learn-more");
+ },
+ get _identityPopupInsecureLoginFormsLearnMore () {
+ delete this._identityPopupInsecureLoginFormsLearnMore;
+ return this._identityPopupInsecureLoginFormsLearnMore =
+ document.getElementById("identity-popup-insecure-login-forms-learn-more");
+ },
+ get _identityIconLabels () {
+ delete this._identityIconLabels;
+ return this._identityIconLabels = document.getElementById("identity-icon-labels");
+ },
+ get _identityIconLabel () {
+ delete this._identityIconLabel;
+ return this._identityIconLabel = document.getElementById("identity-icon-label");
+ },
+ get _connectionIcon () {
+ delete this._connectionIcon;
+ return this._connectionIcon = document.getElementById("connection-icon");
+ },
+ 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("identity-icon");
+ },
+ get _permissionList () {
+ delete this._permissionList;
+ return this._permissionList = document.getElementById("identity-popup-permission-list");
+ },
+ get _permissionEmptyHint() {
+ delete this._permissionEmptyHint;
+ return this._permissionEmptyHint = document.getElementById("identity-popup-permission-empty-hint");
+ },
+ get _permissionReloadHint () {
+ delete this._permissionReloadHint;
+ return this._permissionReloadHint = document.getElementById("identity-popup-permission-reload-hint");
+ },
+ get _permissionAnchors () {
+ delete this._permissionAnchors;
+ let permissionAnchors = {};
+ for (let anchor of document.getElementById("blocked-permissions-container").children) {
+ permissionAnchors[anchor.getAttribute("data-permission-id")] = anchor;
+ }
+ return this._permissionAnchors = permissionAnchors;
+ },
+
+ /**
+ * Handler for mouseclicks on the "More Information" button in the
+ * "identity-popup" panel.
+ */
+ handleMoreInfoClick : function(event) {
+ displaySecurityInfo();
+ event.stopPropagation();
+ this._identityPopup.hidePopup();
+ },
+
+ toggleSubView(name, anchor) {
+ let view = this._identityPopupMultiView;
+ if (view.showingSubView) {
+ view.showMainView();
+ } else {
+ view.showSubView(`identity-popup-${name}View`, anchor);
+ }
+
+ // If an element is focused that's not the anchor, clear the focus.
+ // Elements of hidden views have -moz-user-focus:ignore but setting that
+ // per CSS selector doesn't blur a focused element in those hidden views.
+ if (Services.focus.focusedElement != anchor) {
+ Services.focus.clearFocus(window);
+ }
+ },
+
+ disableMixedContentProtection() {
+ // Use telemetry to measure how often unblocking happens
+ const kMIXED_CONTENT_UNBLOCK_EVENT = 2;
+ let histogram =
+ Services.telemetry.getHistogramById(
+ "MIXED_CONTENT_UNBLOCK_COUNTER");
+ histogram.add(kMIXED_CONTENT_UNBLOCK_EVENT);
+ // Reload the page with the content unblocked
+ BrowserReloadWithFlags(
+ Ci.nsIWebNavigation.LOAD_FLAGS_ALLOW_MIXED_CONTENT);
+ this._identityPopup.hidePopup();
+ },
+
+ enableMixedContentProtection() {
+ gBrowser.selectedBrowser.messageManager.sendAsyncMessage(
+ "MixedContent:ReenableProtection", {});
+ BrowserReload();
+ this._identityPopup.hidePopup();
+ },
+
+ removeCertException() {
+ if (!this._uriHasHost) {
+ Cu.reportError("Trying to revoke a cert exception on a URI without a host?");
+ return;
+ }
+ let host = this._uri.host;
+ let port = this._uri.port > 0 ? this._uri.port : 443;
+ this._overrideService.clearValidityOverride(host, port);
+ BrowserReloadSkipCache();
+ this._identityPopup.hidePopup();
+ },
+
+ /**
+ * Helper to parse out the important parts of _sslStatus (of the SSL cert in
+ * particular) for use in constructing identity UI strings
+ */
+ getIdentityData : function() {
+ var result = {};
+ var cert = this._sslStatus.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;
+ },
+
+ /**
+ * Update the identity user interface for the page currently being displayed.
+ *
+ * This examines the SSL certificate metadata, if available, as well as the
+ * connection type and other security-related state information for the page.
+ *
+ * @param state
+ * Bitmask provided by nsIWebProgressListener.onSecurityChange.
+ * @param uri
+ * nsIURI for which the identity UI should be displayed, already
+ * processed by nsIURIFixup.createExposableURI.
+ */
+ updateIdentity(state, uri) {
+ let shouldHidePopup = this._uri && (this._uri.spec != uri.spec);
+ this._state = state;
+
+ // Firstly, populate the state properties required to display the UI. See
+ // the documentation of the individual properties for details.
+ this.setURI(uri);
+ this._sslStatus = gBrowser.securityUI
+ .QueryInterface(Ci.nsISSLStatusProvider)
+ .SSLStatus;
+ if (this._sslStatus) {
+ this._sslStatus.QueryInterface(Ci.nsISSLStatus);
+ }
+
+ // Then, update the user interface with the available data.
+ this.refreshIdentityBlock();
+ // Handle a location change while the Control Center is focused
+ // by closing the popup (bug 1207542)
+ if (shouldHidePopup) {
+ this._identityPopup.hidePopup();
+ }
+ this.showWeakCryptoInfoBar();
+
+ // NOTE: We do NOT update the identity popup (the control center) when
+ // we receive a new security state on the existing page (i.e. from a
+ // subframe). If the user opened the popup and looks at the provided
+ // information we don't want to suddenly change the panel contents.
+ },
+
+ /**
+ * This is called asynchronously when requested by the Logins module, after
+ * the insecure login forms state for the page has been updated.
+ */
+ refreshForInsecureLoginForms() {
+ // Check this._uri because we don't want to refresh the user interface if
+ // this is called before the first page load in the window for any reason.
+ if (!this._uri) {
+ Cu.reportError("Unexpected early call to refreshForInsecureLoginForms.");
+ return;
+ }
+ this.refreshIdentityBlock();
+ },
+
+ updateSharingIndicator() {
+ let tab = gBrowser.selectedTab;
+ let sharing = tab.getAttribute("sharing");
+ if (sharing)
+ this._identityBox.setAttribute("sharing", sharing);
+ else
+ this._identityBox.removeAttribute("sharing");
+
+ this._sharingState = tab._sharingState;
+
+ if (this._identityPopup.state == "open") {
+ this._handleHeightChange(() => this.updateSitePermissions());
+ }
+ },
+
+ /**
+ * Attempt to provide proper IDN treatment for host names
+ */
+ getEffectiveHost: function() {
+ if (!this._IDNService)
+ this._IDNService = Cc["@mozilla.org/network/idn-service;1"]
+ .getService(Ci.nsIIDNService);
+ try {
+ return this._IDNService.convertToDisplayIDN(this._uri.host, {});
+ } catch (e) {
+ // If something goes wrong (e.g. host is an IP address) just fail back
+ // to the full domain.
+ return this._uri.host;
+ }
+ },
+
+ /**
+ * Return the CSS class name to set on the "fullscreen-warning" element to
+ * display information about connection security in the notification shown
+ * when a site enters the fullscreen mode.
+ */
+ get pointerlockFsWarningClassName() {
+ // Note that the fullscreen warning does not handle _isSecureInternalUI.
+ if (this._uriHasHost && this._isEV) {
+ return "verifiedIdentity";
+ }
+ if (this._uriHasHost && this._isSecure) {
+ return "verifiedDomain";
+ }
+ return "unknownIdentity";
+ },
+
+ /**
+ * Updates the identity block user interface with the data from this object.
+ */
+ refreshIdentityBlock() {
+ if (!this._identityBox) {
+ return;
+ }
+
+ let icon_label = "";
+ let tooltip = "";
+ let icon_country_label = "";
+ let icon_labels_dir = "ltr";
+
+ if (this._isSecureInternalUI) {
+ this._identityBox.className = "chromeUI";
+ let brandBundle = document.getElementById("bundle_brand");
+ icon_label = brandBundle.getString("brandShorterName");
+ } else if (this._uriHasHost && this._isEV) {
+ this._identityBox.className = "verifiedIdentity";
+ if (this._isMixedActiveContentBlocked) {
+ this._identityBox.classList.add("mixedActiveBlocked");
+ }
+
+ if (!this._isCertUserOverridden) {
+ // 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";
+ }
+
+ } else if (this._uriHasHost && this._isSecure) {
+ this._identityBox.className = "verifiedDomain";
+ if (this._isMixedActiveContentBlocked) {
+ this._identityBox.classList.add("mixedActiveBlocked");
+ }
+ if (!this._isCertUserOverridden) {
+ // It's a normal cert, verifier is the CA Org.
+ tooltip = gNavigatorBundle.getFormattedString("identity.identified.verifier",
+ [this.getIdentityData().caOrg]);
+ }
+ } else {
+ this._identityBox.className = "unknownIdentity";
+ if (this._isBroken) {
+ if (this._isMixedActiveContentLoaded) {
+ this._identityBox.classList.add("mixedActiveContent");
+ } else if (this._isMixedActiveContentBlocked) {
+ this._identityBox.classList.add("mixedDisplayContentLoadedActiveBlocked");
+ } else if (this._isMixedPassiveContentLoaded) {
+ this._identityBox.classList.add("mixedDisplayContent");
+ } else {
+ this._identityBox.classList.add("weakCipher");
+ }
+ }
+ if (this._hasInsecureLoginForms) {
+ // Insecure login forms can only be present on "unknown identity"
+ // pages, either already insecure or with mixed active content loaded.
+ this._identityBox.classList.add("insecureLoginForms");
+ }
+ }
+
+ if (this._isCertUserOverridden) {
+ this._identityBox.classList.add("certUserOverridden");
+ // Cert is trusted because of a security exception, verifier is a special string.
+ tooltip = gNavigatorBundle.getString("identity.identified.verified_by_you");
+ }
+
+ let permissionAnchors = this._permissionAnchors;
+
+ // hide all permission icons
+ for (let icon of Object.values(permissionAnchors)) {
+ icon.removeAttribute("showing");
+ }
+
+ // keeps track if we should show an indicator that there are active permissions
+ let hasGrantedPermissions = false;
+
+ // show permission icons
+ for (let permission of SitePermissions.getAllByURI(this._uri)) {
+ if (permission.state === SitePermissions.BLOCK) {
+
+ let icon = permissionAnchors[permission.id];
+ if (icon) {
+ icon.setAttribute("showing", "true");
+ }
+
+ } else if (permission.state === SitePermissions.ALLOW ||
+ permission.state === SitePermissions.SESSION) {
+ hasGrantedPermissions = true;
+ }
+ }
+
+ if (hasGrantedPermissions) {
+ this._identityBox.classList.add("grantedPermissions");
+ }
+
+ // Push the appropriate strings out to the UI
+ this._connectionIcon.tooltipText = tooltip;
+ this._identityIconLabels.tooltipText = tooltip;
+ this._identityIcon.tooltipText = gNavigatorBundle.getString("identity.icon.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;
+ },
+
+ /**
+ * Show the weak crypto notification bar.
+ */
+ showWeakCryptoInfoBar() {
+ if (!this._uriHasHost || !this._isBroken || !this._sslStatus.cipherName ||
+ this._sslStatus.cipherName.indexOf("_RC4_") < 0) {
+ return;
+ }
+
+ let notificationBox = gBrowser.getNotificationBox();
+ let notification = notificationBox.getNotificationWithValue("weak-crypto");
+ if (notification) {
+ return;
+ }
+
+ let brandBundle = document.getElementById("bundle_brand");
+ let brandShortName = brandBundle.getString("brandShortName");
+ let message = gNavigatorBundle.getFormattedString("weakCryptoOverriding.message",
+ [brandShortName]);
+
+ let host = this._uri.host;
+ let port = 443;
+ try {
+ if (this._uri.port > 0) {
+ port = this._uri.port;
+ }
+ } catch (e) {}
+
+ let buttons = [{
+ label: gNavigatorBundle.getString("revokeOverride.label"),
+ accessKey: gNavigatorBundle.getString("revokeOverride.accesskey"),
+ callback: function (aNotification, aButton) {
+ try {
+ let weakCryptoOverride = Cc["@mozilla.org/security/weakcryptooverride;1"]
+ .getService(Ci.nsIWeakCryptoOverride);
+ weakCryptoOverride.removeWeakCryptoOverride(host, port,
+ PrivateBrowsingUtils.isBrowserPrivate(gBrowser.selectedBrowser));
+ BrowserReloadWithFlags(nsIWebNavigation.LOAD_FLAGS_BYPASS_CACHE);
+ } catch (e) {
+ Cu.reportError(e);
+ }
+ }
+ }];
+
+ const priority = notificationBox.PRIORITY_WARNING_MEDIUM;
+ notificationBox.appendNotification(message, "weak-crypto", null,
+ priority, buttons);
+ },
+
+ /**
+ * 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
+ */
+ refreshIdentityPopup() {
+ // Update "Learn More" for Mixed Content Blocking and Insecure Login Forms.
+ let baseURL = Services.urlFormatter.formatURLPref("app.support.baseURL");
+ this._identityPopupMixedContentLearnMore
+ .setAttribute("href", baseURL + "mixed-content");
+ this._identityPopupInsecureLoginFormsLearnMore
+ .setAttribute("href", baseURL + "insecure-password");
+
+ // Determine connection security information.
+ let connection = "not-secure";
+ if (this._isSecureInternalUI) {
+ connection = "chrome";
+ } else if (this._isURILoadedFromFile) {
+ connection = "file";
+ } else if (this._isEV) {
+ connection = "secure-ev";
+ } else if (this._isCertUserOverridden) {
+ connection = "secure-cert-user-overridden";
+ } else if (this._isSecure) {
+ connection = "secure";
+ }
+
+ // Determine if there are insecure login forms.
+ let loginforms = "secure";
+ if (this._hasInsecureLoginForms) {
+ loginforms = "insecure";
+ }
+
+ // Determine the mixed content state.
+ let mixedcontent = [];
+ if (this._isMixedPassiveContentLoaded) {
+ mixedcontent.push("passive-loaded");
+ }
+ if (this._isMixedActiveContentLoaded) {
+ mixedcontent.push("active-loaded");
+ } else if (this._isMixedActiveContentBlocked) {
+ mixedcontent.push("active-blocked");
+ }
+ mixedcontent = mixedcontent.join(" ");
+
+ // We have no specific flags for weak ciphers (yet). If a connection is
+ // broken and we can't detect any mixed content loaded then it's a weak
+ // cipher.
+ let ciphers = "";
+ if (this._isBroken && !this._isMixedActiveContentLoaded && !this._isMixedPassiveContentLoaded) {
+ ciphers = "weak";
+ }
+
+ // Update all elements.
+ let elementIDs = [
+ "identity-popup",
+ "identity-popup-securityView-body",
+ ];
+
+ function updateAttribute(elem, attr, value) {
+ if (value) {
+ elem.setAttribute(attr, value);
+ } else {
+ elem.removeAttribute(attr);
+ }
+ }
+
+ for (let id of elementIDs) {
+ let element = document.getElementById(id);
+ updateAttribute(element, "connection", connection);
+ updateAttribute(element, "loginforms", loginforms);
+ updateAttribute(element, "ciphers", ciphers);
+ updateAttribute(element, "mixedcontent", mixedcontent);
+ updateAttribute(element, "isbroken", this._isBroken);
+ }
+
+ // Initialize the optional strings to empty values
+ let supplemental = "";
+ let verifier = "";
+ let host = "";
+ let owner = "";
+ let hostless = false;
+
+ try {
+ host = this.getEffectiveHost();
+ } catch (e) {
+ // Some URIs might have no hosts.
+ }
+
+ // Fallback for special protocols.
+ if (!host) {
+ host = this._uri.specIgnoringRef;
+ // Special URIs without a host (eg, about:) should crop the end so
+ // the protocol can be seen.
+ hostless = true;
+ }
+
+ // Fill in the CA name if we have a valid TLS certificate.
+ if (this._isSecure || this._isCertUserOverridden) {
+ verifier = this._identityIconLabels.tooltipText;
+ }
+
+ // Fill in organization information if we have a valid EV certificate.
+ if (this._isEV) {
+ let iData = this.getIdentityData();
+ host = owner = iData.subjectOrg;
+ verifier = this._identityIconLabels.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;
+ }
+
+ // Push the appropriate strings out to the UI.
+ this._identityPopupContentHosts.forEach((el) => {
+ el.textContent = host;
+ el.hidden = hostless;
+ });
+ this._identityPopupContentHostless.forEach((el) => {
+ el.setAttribute("value", host);
+ el.hidden = !hostless;
+ });
+ this._identityPopupContentOwner.textContent = owner;
+ this._identityPopupContentSupp.textContent = supplemental;
+ this._identityPopupContentVerif.textContent = verifier;
+
+ // Update per-site permissions section.
+ this.updateSitePermissions();
+ },
+
+ setURI(uri) {
+ this._uri = uri;
+
+ try {
+ this._uri.host;
+ this._uriHasHost = true;
+ } catch (ex) {
+ this._uriHasHost = false;
+ }
+
+ let whitelist = /^(?:accounts|addons|cache|config|crashes|customizing|downloads|healthreport|home|license|newaddon|permissions|preferences|privatebrowsing|rights|searchreset|sessionrestore|support|welcomeback)(?:[?#]|$)/i;
+ this._isSecureInternalUI = uri.schemeIs("about") && whitelist.test(uri.path);
+
+ // Create a channel for the sole purpose of getting the resolved URI
+ // of the request to determine if it's loaded from the file system.
+ this._isURILoadedFromFile = false;
+ let chanOptions = {uri: this._uri, loadUsingSystemPrincipal: true};
+ let resolvedURI;
+ try {
+ resolvedURI = NetUtil.newChannel(chanOptions).URI;
+ if (resolvedURI.schemeIs("jar")) {
+ // Given a URI "jar:<jar-file-uri>!/<jar-entry>"
+ // create a new URI using <jar-file-uri>!/<jar-entry>
+ resolvedURI = NetUtil.newURI(resolvedURI.path);
+ }
+ // Check the URI again after resolving.
+ this._isURILoadedFromFile = resolvedURI.schemeIs("file");
+ } catch (ex) {
+ // NetUtil's methods will throw for malformed URIs and the like
+ }
+ },
+
+ /**
+ * 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 has been modified.
+ if (gURLBar.getAttribute("pageproxystate") != "valid") {
+ return;
+ }
+
+ this._popupTriggeredByKeyboard = event.type == "keypress";
+
+ // 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.refreshIdentityPopup();
+
+ // Add the "open" attribute to the identity box for styling
+ this._identityBox.setAttribute("open", "true");
+
+ // Now open the popup, anchored off the primary chrome element
+ this._identityPopup.openPopup(this._identityIcon, "bottomcenter topleft");
+ },
+
+ onPopupShown(event) {
+ if (event.target == this._identityPopup) {
+ if (this._popupTriggeredByKeyboard) {
+ // Move focus to the next available element in the identity popup.
+ // This is required by role=alertdialog and fixes an issue where
+ // an already open panel would steal focus from the identity popup.
+ document.commandDispatcher.advanceFocusIntoSubtree(this._identityPopup);
+ }
+
+ window.addEventListener("focus", this, true);
+ }
+ },
+
+ onPopupHidden(event) {
+ if (event.target == this._identityPopup) {
+ window.removeEventListener("focus", this, true);
+ this._identityBox.removeAttribute("open");
+ }
+ },
+
+ handleEvent(event) {
+ let elem = document.activeElement;
+ let position = elem.compareDocumentPosition(this._identityPopup);
+
+ if (!(position & (Node.DOCUMENT_POSITION_CONTAINS |
+ Node.DOCUMENT_POSITION_CONTAINED_BY)) &&
+ !this._identityPopup.hasAttribute("noautohide")) {
+ // Hide the panel when focusing an element that is
+ // neither an ancestor nor descendant unless the panel has
+ // @noautohide (e.g. for a tour).
+ this._identityPopup.hidePopup();
+ }
+ },
+
+ observe(subject, topic, data) {
+ if (topic == "perm-changed") {
+ this.refreshIdentityBlock();
+ }
+ },
+
+ onDragStart: function (event) {
+ if (gURLBar.getAttribute("pageproxystate") != "valid")
+ return;
+
+ let value = gBrowser.currentURI.spec;
+ let urlString = value + "\n" + gBrowser.contentTitle;
+ let htmlString = "<a href=\"" + value + "\">" + value + "</a>";
+
+ let 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(this._identityIcon, 16, 16);
+ },
+
+ onLocationChange: function () {
+ this._permissionJustRemoved = false;
+ this.updatePermissionHint();
+ },
+
+ updatePermissionHint: function () {
+ if (!this._permissionList.hasChildNodes() && !this._permissionJustRemoved) {
+ this._permissionEmptyHint.removeAttribute("hidden");
+ } else {
+ this._permissionEmptyHint.setAttribute("hidden", "true");
+ }
+
+ if (this._permissionJustRemoved) {
+ this._permissionReloadHint.removeAttribute("hidden");
+ } else {
+ this._permissionReloadHint.setAttribute("hidden", "true");
+ }
+ },
+
+ updateSitePermissions: function () {
+ while (this._permissionList.hasChildNodes())
+ this._permissionList.removeChild(this._permissionList.lastChild);
+
+ let uri = gBrowser.currentURI;
+
+ let permissions = SitePermissions.getPermissionDetailsByURI(uri);
+ if (this._sharingState) {
+ // If WebRTC device or screen permissions are in use, we need to find
+ // the associated permission item to set the inUse field to true.
+ for (let id of ["camera", "microphone", "screen"]) {
+ if (this._sharingState[id]) {
+ let found = false;
+ for (let permission of permissions) {
+ if (permission.id != id)
+ continue;
+ found = true;
+ permission.inUse = true;
+ break;
+ }
+ if (!found) {
+ // If the permission item we were looking for doesn't exist,
+ // the user has temporarily allowed sharing and we need to add
+ // an item in the permissions array to reflect this.
+ let permission = SitePermissions.getPermissionItem(id);
+ permission.inUse = true;
+ permissions.push(permission);
+ }
+ }
+ }
+ }
+ for (let permission of permissions) {
+ let item = this._createPermissionItem(permission);
+ this._permissionList.appendChild(item);
+ }
+
+ this.updatePermissionHint();
+ },
+
+ _handleHeightChange: function(aFunction, aWillShowReloadHint) {
+ let heightBefore = getComputedStyle(this._permissionList).height;
+ aFunction();
+ let heightAfter = getComputedStyle(this._permissionList).height;
+ // Showing the reload hint increases the height, we need to account for it.
+ if (aWillShowReloadHint) {
+ heightAfter = parseInt(heightAfter) +
+ parseInt(getComputedStyle(this._permissionList.nextSibling).height);
+ }
+ let heightChange = parseInt(heightAfter) - parseInt(heightBefore);
+ if (heightChange)
+ this._identityPopupMultiView.setHeightToFit(heightChange);
+ },
+
+ _createPermissionItem: function (aPermission) {
+ let container = document.createElement("hbox");
+ container.setAttribute("class", "identity-popup-permission-item");
+ container.setAttribute("align", "center");
+
+ let img = document.createElement("image");
+ let classes = "identity-popup-permission-icon " + aPermission.id + "-icon";
+ if (aPermission.state == SitePermissions.BLOCK)
+ classes += " blocked-permission-icon";
+ if (aPermission.inUse)
+ classes += " in-use";
+ img.setAttribute("class", classes);
+
+ let nameLabel = document.createElement("label");
+ nameLabel.setAttribute("flex", "1");
+ nameLabel.setAttribute("class", "identity-popup-permission-label");
+ nameLabel.textContent = SitePermissions.getPermissionLabel(aPermission.id);
+
+ let stateLabel = document.createElement("label");
+ stateLabel.setAttribute("flex", "1");
+ stateLabel.setAttribute("class", "identity-popup-permission-state-label");
+ stateLabel.textContent = SitePermissions.getStateLabel(
+ aPermission.id, aPermission.state, aPermission.inUse || false);
+
+ let button = document.createElement("button");
+ button.setAttribute("class", "identity-popup-permission-remove-button");
+ let tooltiptext = gNavigatorBundle.getString("permissions.remove.tooltip");
+ button.setAttribute("tooltiptext", tooltiptext);
+ button.addEventListener("command", () => {
+ this._handleHeightChange(() =>
+ this._permissionList.removeChild(container), !this._permissionJustRemoved);
+ if (aPermission.inUse &&
+ ["camera", "microphone", "screen"].includes(aPermission.id)) {
+ let windowId = this._sharingState.windowId;
+ if (aPermission.id == "screen") {
+ windowId = "screen:" + windowId;
+ } else {
+ // If we set persistent permissions or the sharing has
+ // started due to existing persistent permissions, we need
+ // to handle removing these even for frames with different hostnames.
+ let uris = gBrowser.selectedBrowser._devicePermissionURIs || [];
+ for (let uri of uris) {
+ // It's not possible to stop sharing one of camera/microphone
+ // without the other.
+ for (let id of ["camera", "microphone"]) {
+ if (this._sharingState[id] &&
+ SitePermissions.get(uri, id) == SitePermissions.ALLOW)
+ SitePermissions.remove(uri, id);
+ }
+ }
+ }
+ let mm = gBrowser.selectedBrowser.messageManager;
+ mm.sendAsyncMessage("webrtc:StopSharing", windowId);
+ }
+ SitePermissions.remove(gBrowser.currentURI, aPermission.id);
+ this._permissionJustRemoved = true;
+ this.updatePermissionHint();
+
+ // Set telemetry values for clearing a permission
+ let histogram = Services.telemetry.getKeyedHistogramById("WEB_PERMISSION_CLEARED");
+
+ let permissionType = 0;
+ if (aPermission.state == SitePermissions.ALLOW) {
+ // 1 : clear permanently allowed permission
+ permissionType = 1;
+ } else if (aPermission.state == SitePermissions.BLOCK) {
+ // 2 : clear permanently blocked permission
+ permissionType = 2;
+ }
+ // 3 : TODO clear temporary allowed permission
+ // 4 : TODO clear temporary blocked permission
+
+ histogram.add("(all)", permissionType);
+ histogram.add(aPermission.id, permissionType);
+ });
+
+ container.appendChild(img);
+ container.appendChild(nameLabel);
+ container.appendChild(stateLabel);
+ container.appendChild(button);
+
+ return container;
+ }
+};
+
+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() {
+ return gBrowser;
+}
+function getNavToolbox() {
+ return 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()) {
+ // 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" },
+ ].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;
+ }
+ });
+ }
+ }
+
+ let urlBarSearchParam = gURLBar.getAttribute("autocompletesearchparam") || "";
+ if (!PrivateBrowsingUtils.permanentPrivateBrowsing &&
+ !urlBarSearchParam.includes("disable-private-actions")) {
+ // Disable switch to tab autocompletion for private windows.
+ // We leave it enabled for permanent private browsing mode though.
+ urlBarSearchParam += " disable-private-actions";
+ }
+ if (!urlBarSearchParam.includes("private-window")) {
+ urlBarSearchParam += " private-window";
+ }
+ gURLBar.setAttribute("autocompletesearchparam", urlBarSearchParam);
+ }
+};
+
+var gRemoteTabsUI = {
+ init: function() {
+ if (window.location.href != getBrowserURL() &&
+ // Also check hidden window for the Mac no-window case
+ window.location.href != "chrome://browser/content/hiddenWindow.xul") {
+ return;
+ }
+
+ if (AppConstants.platform == "macosx" &&
+ Services.prefs.getBoolPref("layers.acceleration.disabled")) {
+ // On OS X, "Disable Hardware Acceleration" also disables OMTC and forces
+ // a fallback to Basic Layers. This is incompatible with e10s.
+ return;
+ }
+
+ let newNonRemoteWindow = document.getElementById("menu_newNonRemoteWindow");
+ let autostart = Services.appinfo.browserTabsRemoteAutostart;
+ newNonRemoteWindow.hidden = !autostart;
+ }
+};
+
+/**
+ * Switch to a tab that has a given URI, and focuses 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.
+ * @param aOpenParams
+ * If switching to this URI results in us opening a tab, aOpenParams
+ * will be the parameter object that gets passed to openUILinkIn. Please
+ * see the documentation for openUILinkIn to see what parameters can be
+ * passed via this object.
+ * This object also allows:
+ * - 'ignoreFragment' property to be set to true to exclude fragment-portion
+ * matching when comparing URIs.
+ * If set to "whenComparing", the fragment will be unmodified.
+ * If set to "whenComparingAndReplace", the fragment will be replaced.
+ * - 'ignoreQueryString' boolean property to be set to true to exclude query string
+ * matching when comparing URIs.
+ * - 'replaceQueryString' boolean property to be set to true to exclude query string
+ * matching when comparing URIs and overwrite the initial query string with
+ * the one from the new URI.
+ * @return True if an existing tab was found, false otherwise
+ */
+function switchToTabHavingURI(aURI, aOpenNew, aOpenParams={}) {
+ // Certain URLs can be switched to irrespective of the source or destination
+ // window being in private browsing mode:
+ const kPrivateBrowsingWhitelist = new Set([
+ "about:addons",
+ ]);
+
+ let ignoreFragment = aOpenParams.ignoreFragment;
+ let ignoreQueryString = aOpenParams.ignoreQueryString;
+ let replaceQueryString = aOpenParams.replaceQueryString;
+
+ // These properties are only used by switchToTabHavingURI and should
+ // not be used as a parameter for the new load.
+ delete aOpenParams.ignoreFragment;
+ delete aOpenParams.ignoreQueryString;
+ delete aOpenParams.replaceQueryString;
+
+ // This will switch to the tab in aWindow having aURI, if present.
+ function switchIfURIInWindow(aWindow) {
+ // Only switch to the tab if neither the source nor the destination window
+ // are private and they are not in permanent private browsing mode
+ if (!kPrivateBrowsingWhitelist.has(aURI.spec) &&
+ (PrivateBrowsingUtils.isWindowPrivate(window) ||
+ PrivateBrowsingUtils.isWindowPrivate(aWindow)) &&
+ !PrivateBrowsingUtils.permanentPrivateBrowsing) {
+ return false;
+ }
+
+ // Remove the query string, fragment, both, or neither from a given url.
+ function cleanURL(url, removeQuery, removeFragment) {
+ let ret = url;
+ if (removeFragment) {
+ ret = ret.split("#")[0];
+ if (removeQuery) {
+ // This removes a query, if present before the fragment.
+ ret = ret.split("?")[0];
+ }
+ } else if (removeQuery) {
+ // This is needed in case there is a fragment after the query.
+ let fragment = ret.split("#")[1];
+ ret = ret.split("?")[0].concat(
+ (fragment != undefined) ? "#".concat(fragment) : "");
+ }
+ return ret;
+ }
+
+ // Need to handle nsSimpleURIs here too (e.g. about:...), which don't
+ // work correctly with URL objects - so treat them as strings
+ let ignoreFragmentWhenComparing = typeof ignoreFragment == "string" &&
+ ignoreFragment.startsWith("whenComparing");
+ let requestedCompare = cleanURL(
+ aURI.spec, ignoreQueryString || replaceQueryString, ignoreFragmentWhenComparing);
+ let browsers = aWindow.gBrowser.browsers;
+ for (let i = 0; i < browsers.length; i++) {
+ let browser = browsers[i];
+ let browserCompare = cleanURL(
+ browser.currentURI.spec, ignoreQueryString || replaceQueryString, ignoreFragmentWhenComparing);
+ if (requestedCompare == browserCompare) {
+ aWindow.focus();
+ if (ignoreFragment == "whenComparingAndReplace" || replaceQueryString) {
+ browser.loadURI(aURI.spec);
+ }
+ 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;
+
+ for (let browserWin of browserWindows()) {
+ // 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))
+ openUILinkIn(aURI.spec, "current", aOpenParams);
+ else
+ openUILinkIn(aURI.spec, "tab", aOpenParams);
+ }
+
+ return false;
+}
+
+var RestoreLastSessionObserver = {
+ init: function () {
+ if (SessionStore.canRestoreLastSession &&
+ !PrivateBrowsingUtils.isWindowPrivate(window)) {
+ Services.obs.addObserver(this, "sessionstore-last-session-cleared", true);
+ goSetCommandEnabled("Browser:RestoreLastSession", true);
+ }
+ },
+
+ observe: function () {
+ // The last session can only be restored once so there's
+ // no way we need to re-enable our menu item.
+ Services.obs.removeObserver(this, "sessionstore-last-session-cleared");
+ goSetCommandEnabled("Browser:RestoreLastSession", false);
+ },
+
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
+ Ci.nsISupportsWeakReference])
+};
+
+function restoreLastSession() {
+ SessionStore.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;
+
+ 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 =
+ SessionStore.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);
+
+ gFxAccounts.updateTabContextMenu(aPopupMenu);
+ },
+ 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;
+ }
+ }
+};
+
+// Prompt user to restart the browser in safe mode
+function safeModeRestart() {
+ if (Services.appinfo.inSafeMode) {
+ let cancelQuit = Cc["@mozilla.org/supports-PRBool;1"].
+ createInstance(Ci.nsISupportsPRBool);
+ Services.obs.notifyObservers(cancelQuit, "quit-application-requested", "restart");
+
+ if (cancelQuit.data)
+ return;
+
+ Services.startup.quit(Ci.nsIAppStartup.eRestart | Ci.nsIAppStartup.eAttemptQuit);
+ return;
+ }
+
+ Services.obs.notifyObservers(null, "restart-in-safe-mode", "");
+}
+
+/* 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) {
+ switch (where) {
+ case "window":
+ let otherWin = OpenBrowserWindow();
+ let delayedStartupFinished = (subject, topic) => {
+ if (topic == "browser-delayed-startup-finished" &&
+ subject == otherWin) {
+ Services.obs.removeObserver(delayedStartupFinished, topic);
+ let otherGBrowser = otherWin.gBrowser;
+ let otherTab = otherGBrowser.selectedTab;
+ SessionStore.duplicateTab(otherWin, aTab, delta);
+ otherGBrowser.removeTab(otherTab, { animate: false });
+ }
+ };
+
+ Services.obs.addObserver(delayedStartupFinished,
+ "browser-delayed-startup-finished",
+ false);
+ break;
+ case "tabshifted":
+ SessionStore.duplicateTab(window, aTab, delta);
+ // A background tab has been opened, nothing else to do here.
+ break;
+ case "tab":
+ let newTab = SessionStore.duplicateTab(window, aTab, delta);
+ gBrowser.selectedTab = newTab;
+ break;
+ }
+}
+
+var MousePosTracker = {
+ _listeners: new Set(),
+ _x: 0,
+ _y: 0,
+ get _windowUtils() {
+ delete this._windowUtils;
+ return this._windowUtils = window.getInterface(Ci.nsIDOMWindowUtils);
+ },
+
+ addListener: function (listener) {
+ if (this._listeners.has(listener))
+ return;
+
+ listener._hover = false;
+ this._listeners.add(listener);
+
+ this._callListener(listener);
+ },
+
+ removeListener: function (listener) {
+ this._listeners.delete(listener);
+ },
+
+ 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 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 = "#navigator-toolbox > toolbar:not([collapsed=true]):not(#addon-bar)";
+ if (AppConstants.platform == "macosx")
+ toolbarSelector += ":not([type=menubar])";
+
+ // 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");
+ }
+}
+
+var PanicButtonNotifier = {
+ init: function() {
+ this._initialized = true;
+ if (window.PanicButtonNotifierShouldNotify) {
+ delete window.PanicButtonNotifierShouldNotify;
+ this.notify();
+ }
+ },
+ notify: function() {
+ if (!this._initialized) {
+ window.PanicButtonNotifierShouldNotify = true;
+ return;
+ }
+ // Display notification panel here...
+ try {
+ let popup = document.getElementById("panic-button-success-notification");
+ popup.hidden = false;
+ let widget = CustomizableUI.getWidget("panic-button").forWindow(window);
+ let anchor = widget.anchor;
+ anchor = document.getAnonymousElementByAttribute(anchor, "class", "toolbarbutton-icon");
+ popup.openPopup(anchor, popup.getAttribute("position"));
+ } catch (ex) {
+ Cu.reportError(ex);
+ }
+ },
+ close: function() {
+ let popup = document.getElementById("panic-button-success-notification");
+ popup.hidePopup();
+ },
+};
+
+var AboutPrivateBrowsingListener = {
+ init: function () {
+ window.messageManager.addMessageListener(
+ "AboutPrivateBrowsing:OpenPrivateWindow",
+ msg => {
+ OpenBrowserWindow({private: true});
+ });
+ window.messageManager.addMessageListener(
+ "AboutPrivateBrowsing:ToggleTrackingProtection",
+ msg => {
+ const PREF = "privacy.trackingprotection.pbmode.enabled";
+ Services.prefs.setBoolPref(PREF, !Services.prefs.getBoolPref(PREF));
+ });
+ }
+};
+
+function TabModalPromptBox(browser) {
+ this._weakBrowserRef = Cu.getWeakReference(browser);
+}
+
+TabModalPromptBox.prototype = {
+ _promptCloseCallback(onCloseCallback, principalToAllowFocusFor, allowFocusCheckbox, ...args) {
+ if (principalToAllowFocusFor && allowFocusCheckbox &&
+ allowFocusCheckbox.checked) {
+ Services.perms.addFromPrincipal(principalToAllowFocusFor, "focus-tab-by-prompt",
+ Services.perms.ALLOW_ACTION);
+ }
+ onCloseCallback.apply(this, args);
+ },
+
+ appendPrompt(args, onCloseCallback) {
+ const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+ let newPrompt = document.createElementNS(XUL_NS, "tabmodalprompt");
+ let browser = this.browser;
+ browser.parentNode.insertBefore(newPrompt, browser.nextSibling);
+ browser.setAttribute("tabmodalPromptShowing", true);
+
+ newPrompt.clientTop; // style flush to assure binding is attached
+
+ let prompts = this.listPrompts();
+ if (prompts.length > 1) {
+ // Let's hide ourself behind the current prompt.
+ newPrompt.hidden = true;
+ }
+
+ let principalToAllowFocusFor = this._allowTabFocusByPromptPrincipal;
+ delete this._allowTabFocusByPromptPrincipal;
+
+ let allowFocusCheckbox; // Define outside the if block so we can bind it into the callback.
+ let hostForAllowFocusCheckbox = "";
+ try {
+ hostForAllowFocusCheckbox = principalToAllowFocusFor.URI.host;
+ } catch (ex) { /* Ignore exceptions for host-less URIs */ }
+ if (hostForAllowFocusCheckbox) {
+ let allowFocusRow = document.createElementNS(XUL_NS, "row");
+ allowFocusCheckbox = document.createElementNS(XUL_NS, "checkbox");
+ let spacer = document.createElementNS(XUL_NS, "spacer");
+ allowFocusRow.appendChild(spacer);
+ let label = gBrowser.mStringBundle.getFormattedString("tabs.allowTabFocusByPromptForSite",
+ [hostForAllowFocusCheckbox]);
+ allowFocusCheckbox.setAttribute("label", label);
+ allowFocusRow.appendChild(allowFocusCheckbox);
+ newPrompt.appendChild(allowFocusRow);
+ }
+
+ let tab = gBrowser.getTabForBrowser(browser);
+ let closeCB = this._promptCloseCallback.bind(null, onCloseCallback, principalToAllowFocusFor,
+ allowFocusCheckbox);
+ newPrompt.init(args, tab, closeCB);
+ return newPrompt;
+ },
+
+ removePrompt(aPrompt) {
+ let browser = this.browser;
+ browser.parentNode.removeChild(aPrompt);
+
+ let prompts = this.listPrompts();
+ if (prompts.length) {
+ let prompt = prompts[prompts.length - 1];
+ prompt.hidden = false;
+ prompt.Dialog.setDefaultFocus();
+ } else {
+ browser.removeAttribute("tabmodalPromptShowing");
+ browser.focus();
+ }
+ },
+
+ listPrompts(aPrompt) {
+ // Get the nodelist, then return as an array
+ const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+ let els = this.browser.parentNode.getElementsByTagNameNS(XUL_NS, "tabmodalprompt");
+ return Array.from(els);
+ },
+
+ onNextPromptShowAllowFocusCheckboxFor(principal) {
+ this._allowTabFocusByPromptPrincipal = principal;
+ },
+
+ get browser() {
+ let browser = this._weakBrowserRef.get();
+ if (!browser) {
+ throw "Stale promptbox! The associated browser is gone.";
+ }
+ return browser;
+ },
+};