diff options
Diffstat (limited to 'browser/base/content/browser.js')
-rwxr-xr-x | browser/base/content/browser.js | 8185 |
1 files changed, 0 insertions, 8185 deletions
diff --git a/browser/base/content/browser.js b/browser/base/content/browser.js deleted file mode 100755 index 4b8ec864b..000000000 --- a/browser/base/content/browser.js +++ /dev/null @@ -1,8185 +0,0 @@ -/* -*- 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"], - ["UITour", "resource:///modules/UITour.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://browser/content/content-UITour.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(); - - UITour.onLocationChange(location); - - 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)); - }); - window.messageManager.addMessageListener( - "AboutPrivateBrowsing:DontShowIntroPanelAgain", - msg => { - TrackingProtection.dontShowIntroPanelAgain(); - }); - } -}; - -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; - }, -}; |