/* 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 { XPCOMUtils } = ChromeUtils.import( "resource://gre/modules/XPCOMUtils.jsm" ); var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm"); var { AppConstants } = ChromeUtils.import( "resource://gre/modules/AppConstants.jsm" ); ChromeUtils.import("resource://gre/modules/NotificationDB.jsm"); // lazy module getters XPCOMUtils.defineLazyModuleGetters(this, { AddonManager: "resource://gre/modules/AddonManager.jsm", NewTabPagePreloading: "resource:///modules/NewTabPagePreloading.jsm", BrowserUsageTelemetry: "resource:///modules/BrowserUsageTelemetry.jsm", BrowserUtils: "resource://gre/modules/BrowserUtils.jsm", BrowserWindowTracker: "resource:///modules/BrowserWindowTracker.jsm", CharsetMenu: "resource://gre/modules/CharsetMenu.jsm", Color: "resource://gre/modules/Color.jsm", ContentSearch: "resource:///modules/ContentSearch.jsm", ContextualIdentityService: "resource://gre/modules/ContextualIdentityService.jsm", CustomizableUI: "resource:///modules/CustomizableUI.jsm", Deprecated: "resource://gre/modules/Deprecated.jsm", DownloadsCommon: "resource:///modules/DownloadsCommon.jsm", DownloadUtils: "resource://gre/modules/DownloadUtils.jsm", E10SUtils: "resource://gre/modules/E10SUtils.jsm", ExtensionsUI: "resource:///modules/ExtensionsUI.jsm", FormValidationHandler: "resource:///modules/FormValidationHandler.jsm", HomePage: "resource:///modules/HomePage.jsm", LightweightThemeConsumer: "resource://gre/modules/LightweightThemeConsumer.jsm", Log: "resource://gre/modules/Log.jsm", LoginHelper: "resource://gre/modules/LoginHelper.jsm", LoginManagerParent: "resource://gre/modules/LoginManagerParent.jsm", MigrationUtils: "resource:///modules/MigrationUtils.jsm", NetUtil: "resource://gre/modules/NetUtil.jsm", NewTabUtils: "resource://gre/modules/NewTabUtils.jsm", OpenInTabsUtils: "resource:///modules/OpenInTabsUtils.jsm", PageActions: "resource:///modules/PageActions.jsm", PageThumbs: "resource://gre/modules/PageThumbs.jsm", PanelMultiView: "resource:///modules/PanelMultiView.jsm", PanelView: "resource:///modules/PanelMultiView.jsm", PictureInPicture: "resource://gre/modules/PictureInPicture.jsm", PlacesUtils: "resource://gre/modules/PlacesUtils.jsm", PlacesUIUtils: "resource:///modules/PlacesUIUtils.jsm", PlacesTransactions: "resource://gre/modules/PlacesTransactions.jsm", PluralForm: "resource://gre/modules/PluralForm.jsm", PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.jsm", ProcessHangMonitor: "resource:///modules/ProcessHangMonitor.jsm", PromiseUtils: "resource://gre/modules/PromiseUtils.jsm", // TODO (Bug 1529552): Remove once old urlbar code goes away. ReaderMode: "resource://gre/modules/ReaderMode.jsm", ReaderParent: "resource:///modules/ReaderParent.jsm", RFPHelper: "resource://gre/modules/RFPHelper.jsm", SafeBrowsing: "resource://gre/modules/SafeBrowsing.jsm", Sanitizer: "resource:///modules/Sanitizer.jsm", SessionStartup: "resource:///modules/sessionstore/SessionStartup.jsm", SessionStore: "resource:///modules/sessionstore/SessionStore.jsm", ShortcutUtils: "resource://gre/modules/ShortcutUtils.jsm", SimpleServiceDiscovery: "resource://gre/modules/SimpleServiceDiscovery.jsm", SiteDataManager: "resource:///modules/SiteDataManager.jsm", SitePermissions: "resource:///modules/SitePermissions.jsm", SubframeCrashHandler: "resource:///modules/ContentCrashHandlers.jsm", TabModalPrompt: "chrome://global/content/tabprompts.jsm", TabCrashHandler: "resource:///modules/ContentCrashHandlers.jsm", TelemetryEnvironment: "resource://gre/modules/TelemetryEnvironment.jsm", Translation: "resource:///modules/translation/Translation.jsm", UpdateUtils: "resource://gre/modules/UpdateUtils.jsm", UrlbarInput: "resource:///modules/UrlbarInput.jsm", UrlbarPrefs: "resource:///modules/UrlbarPrefs.jsm", UrlbarTokenizer: "resource:///modules/UrlbarTokenizer.jsm", UrlbarUtils: "resource:///modules/UrlbarUtils.jsm", UrlbarValueFormatter: "resource:///modules/UrlbarValueFormatter.jsm", Weave: "resource://services-sync/main.js", WebNavigationFrames: "resource://gre/modules/WebNavigationFrames.jsm", fxAccounts: "resource://gre/modules/FxAccounts.jsm", webrtcUI: "resource:///modules/webrtcUI.jsm", ZoomUI: "resource:///modules/ZoomUI.jsm", }); if (AppConstants.MOZ_CRASHREPORTER) { ChromeUtils.defineModuleGetter( this, "PluginCrashReporter", "resource:///modules/ContentCrashHandlers.jsm" ); } XPCOMUtils.defineLazyScriptGetter( this, "PlacesTreeView", "chrome://browser/content/places/treeView.js" ); XPCOMUtils.defineLazyScriptGetter( this, ["PlacesInsertionPoint", "PlacesController", "PlacesControllerDragHelper"], "chrome://browser/content/places/controller.js" ); XPCOMUtils.defineLazyScriptGetter( this, "PrintUtils", "chrome://global/content/printUtils.js" ); XPCOMUtils.defineLazyScriptGetter( this, "ZoomManager", "chrome://global/content/viewZoomOverlay.js" ); XPCOMUtils.defineLazyScriptGetter( this, "FullZoom", "chrome://browser/content/browser-fullZoom.js" ); XPCOMUtils.defineLazyScriptGetter( this, "PanelUI", "chrome://browser/content/customizableui/panelUI.js" ); XPCOMUtils.defineLazyScriptGetter( this, "gViewSourceUtils", "chrome://global/content/viewSourceUtils.js" ); XPCOMUtils.defineLazyScriptGetter( this, "gTabsPanel", "chrome://browser/content/browser-allTabsMenu.js" ); XPCOMUtils.defineLazyScriptGetter( this, ["gExtensionsNotifications", "gXPInstallObserver"], "chrome://browser/content/browser-addons.js" ); XPCOMUtils.defineLazyScriptGetter( this, "ctrlTab", "chrome://browser/content/browser-ctrlTab.js" ); XPCOMUtils.defineLazyScriptGetter( this, ["CustomizationHandler", "AutoHideMenubar"], "chrome://browser/content/browser-customization.js" ); XPCOMUtils.defineLazyScriptGetter( this, ["PointerLock", "FullScreen"], "chrome://browser/content/browser-fullScreenAndPointerLock.js" ); XPCOMUtils.defineLazyScriptGetter( this, "gIdentityHandler", "chrome://browser/content/browser-siteIdentity.js" ); XPCOMUtils.defineLazyScriptGetter( this, "gProtectionsHandler", "chrome://browser/content/browser-siteProtections.js" ); XPCOMUtils.defineLazyScriptGetter( this, ["gGestureSupport", "gHistorySwipeAnimation"], "chrome://browser/content/browser-gestureSupport.js" ); XPCOMUtils.defineLazyScriptGetter( this, "gSync", "chrome://browser/content/browser-sync.js" ); XPCOMUtils.defineLazyScriptGetter( this, "gBrowserThumbnails", "chrome://browser/content/browser-thumbnails.js" ); XPCOMUtils.defineLazyScriptGetter( this, ["setContextMenuContentData", "openContextMenu", "nsContextMenu"], "chrome://browser/content/nsContextMenu.js" ); XPCOMUtils.defineLazyScriptGetter( this, [ "DownloadsPanel", "DownloadsOverlayLoader", "DownloadsSubview", "DownloadsView", "DownloadsViewUI", "DownloadsViewController", "DownloadsSummary", "DownloadsFooter", "DownloadsBlockedSubview", ], "chrome://browser/content/downloads/downloads.js" ); XPCOMUtils.defineLazyScriptGetter( this, ["DownloadsButton", "DownloadsIndicatorView"], "chrome://browser/content/downloads/indicator.js" ); XPCOMUtils.defineLazyScriptGetter( this, "gEditItemOverlay", "chrome://browser/content/places/editBookmark.js" ); XPCOMUtils.defineLazyScriptGetter( this, "SearchOneOffs", "chrome://browser/content/search/search-one-offs.js" ); if (AppConstants.NIGHTLY_BUILD) { XPCOMUtils.defineLazyScriptGetter( this, "gGfxUtils", "chrome://browser/content/browser-graphics-utils.js" ); } XPCOMUtils.defineLazyScriptGetter( this, "ToolbarKeyboardNavigator", "chrome://browser/content/browser-toolbarKeyNav.js" ); // lazy service getters XPCOMUtils.defineLazyServiceGetters(this, { classifierService: [ "@mozilla.org/url-classifier/dbservice;1", "nsIURIClassifier", ], Favicons: ["@mozilla.org/browser/favicon-service;1", "nsIFaviconService"], gAboutNewTabService: [ "@mozilla.org/browser/aboutnewtab-service;1", "nsIAboutNewTabService", ], gDNSService: ["@mozilla.org/network/dns-service;1", "nsIDNSService"], gSerializationHelper: [ "@mozilla.org/network/serialization-helper;1", "nsISerializationHelper", ], #ifdef ENABLE_MARIONETTE Marionette: ["@mozilla.org/remote/marionette;1", "nsIMarionette"], #endif WindowsUIUtils: ["@mozilla.org/windows-ui-utils;1", "nsIWindowsUIUtils"], }); if (AppConstants.MOZ_CRASHREPORTER) { XPCOMUtils.defineLazyServiceGetter( this, "gCrashReporter", "@mozilla.org/xre/app-info;1", "nsICrashReporter" ); } XPCOMUtils.defineLazyGetter(this, "RTL_UI", () => { return Services.locale.isAppLocaleRTL; }); XPCOMUtils.defineLazyGetter(this, "gBrowserBundle", () => { return Services.strings.createBundle( "chrome://browser/locale/browser.properties" ); }); XPCOMUtils.defineLazyGetter(this, "gTabBrowserBundle", () => { return Services.strings.createBundle( "chrome://browser/locale/tabbrowser.properties" ); }); XPCOMUtils.defineLazyGetter(this, "gCustomizeMode", () => { let { CustomizeMode } = ChromeUtils.import( "resource:///modules/CustomizeMode.jsm" ); return new CustomizeMode(window); }); XPCOMUtils.defineLazyGetter(this, "gNavToolbox", () => { return document.getElementById("navigator-toolbox"); }); XPCOMUtils.defineLazyGetter(this, "gURLBar", () => { return new UrlbarInput({ textbox: document.getElementById("urlbar"), }); }); XPCOMUtils.defineLazyGetter(this, "ReferrerInfo", () => Components.Constructor( "@mozilla.org/referrer-info;1", "nsIReferrerInfo", "init" ) ); // High priority notification bars shown at the top of the window. XPCOMUtils.defineLazyGetter(this, "gHighPriorityNotificationBox", () => { return new MozElements.NotificationBox(element => { element.classList.add("global-notificationbox"); element.setAttribute("notificationside", "top"); document.getElementById("appcontent").prepend(element); }); }); // Regular notification bars shown at the bottom of the window. XPCOMUtils.defineLazyGetter(this, "gNotificationBox", () => { return new MozElements.NotificationBox(element => { element.classList.add("global-notificationbox"); element.setAttribute("notificationside", "bottom"); document.getElementById("browser-bottombox").appendChild(element); }); }); XPCOMUtils.defineLazyGetter(this, "InlineSpellCheckerUI", () => { let { InlineSpellChecker } = ChromeUtils.import( "resource://gre/modules/InlineSpellChecker.jsm" ); return new InlineSpellChecker(); }); XPCOMUtils.defineLazyGetter(this, "PageMenuParent", () => { // eslint-disable-next-line no-shadow let { PageMenuParent } = ChromeUtils.import( "resource://gre/modules/PageMenu.jsm" ); return new PageMenuParent(); }); XPCOMUtils.defineLazyGetter(this, "PopupNotifications", () => { // eslint-disable-next-line no-shadow let { PopupNotifications } = ChromeUtils.import( "resource://gre/modules/PopupNotifications.jsm" ); try { // Hide all notifications while the URL is being edited and the address bar // has focus, including the virtual focus in the results popup. // We also have to hide notifications explicitly when the window is // minimized because of the effects of the "noautohide" attribute on Linux. // This can be removed once bug 545265 and bug 1320361 are fixed. let shouldSuppress = () => { return ( window.windowState == window.STATE_MINIMIZED || (gURLBar.getAttribute("pageproxystate") != "valid" && gURLBar.focused) ); }; return new PopupNotifications( gBrowser, document.getElementById("notification-popup"), document.getElementById("notification-popup-box"), { shouldSuppress } ); } catch (ex) { Cu.reportError(ex); return null; } }); XPCOMUtils.defineLazyGetter(this, "Win7Features", () => { 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 } = ChromeUtils.import( "resource:///modules/WindowsPreviewPerTab.jsm" ); return { onOpenWindow() { AeroPeek.onOpenWindow(window); this.handledOpening = true; }, onCloseWindow() { if (this.handledOpening) { AeroPeek.onCloseWindow(window); } }, handledOpening: false, }; } return null; }); XPCOMUtils.defineLazyPreferenceGetter( this, "gToolbarKeyNavEnabled", "browser.toolbars.keyboard_navigation", false, (aPref, aOldVal, aNewVal) => { if (aNewVal) { ToolbarKeyboardNavigator.init(); } else { ToolbarKeyboardNavigator.uninit(); } } ); XPCOMUtils.defineLazyPreferenceGetter( this, "gFxaToolbarEnabled", "identity.fxaccounts.toolbar.enabled", false, (aPref, aOldVal, aNewVal) => { showFxaToolbarMenu(aNewVal); } ); XPCOMUtils.defineLazyPreferenceGetter( this, "gFxaToolbarAccessed", "identity.fxaccounts.toolbar.accessed", false, (aPref, aOldVal, aNewVal) => { showFxaToolbarMenu(gFxaToolbarEnabled); } ); customElements.setElementCreationCallback("translation-notification", () => { Services.scriptloader.loadSubScript( "chrome://browser/content/translation-notification.js", window ); }); var gBrowser; var gLastValidURLStr = ""; var gInPrintPreviewMode = false; var gContextMenu = null; // nsContextMenu instance var gMultiProcessBrowser = window.docShell.QueryInterface(Ci.nsILoadContext) .useRemoteTabs; var gFissionBrowser = window.docShell.QueryInterface(Ci.nsILoadContext) .useRemoteSubframes; var gBrowserAllowScriptsToCloseInitialTabs = false; if (AppConstants.platform != "macosx") { var gEditUIVisible = true; } // Smart getter for the findbar. If you don't wish to force the creation of // the findbar, check gFindBarInitialized first. Object.defineProperty(this, "gFindBar", { enumerable: true, get() { return gBrowser.getCachedFindBar(); }, }); Object.defineProperty(this, "gFindBarInitialized", { enumerable: true, get() { return gBrowser.isFindBarInitialized(); }, }); Object.defineProperty(this, "gFindBarPromise", { enumerable: true, get() { return gBrowser.getFindBar(); }, }); async function gLazyFindCommand(cmd, ...args) { let fb = await gFindBarPromise; // We could be closed by now, or the tab with XBL binding could have gone away: if (fb && fb[cmd]) { fb[cmd].apply(fb, args); } } var gPageIcons = { "about:home": "chrome://branding/content/icon32.png", "about:newtab": "chrome://branding/content/icon32.png", "about:welcome": "chrome://branding/content/icon32.png", "about:newinstall": "chrome://branding/content/icon32.png", "about:privatebrowsing": "chrome://browser/skin/privatebrowsing/favicon.svg", }; var gInitialPages = [ "about:blank", "about:newtab", "about:home", "about:privatebrowsing", "about:sessionrestore", "about:newinstall", ]; function isInitialPage(url) { if (!(url instanceof Ci.nsIURI)) { try { url = Services.io.newURI(url); } catch (ex) { return false; } } let nonQuery = url.prePath + url.filePath; return gInitialPages.includes(nonQuery) || nonQuery == BROWSER_NEW_TAB_URL; } function browserWindows() { return Services.wm.getEnumerator("navigator:browser"); } // This is a stringbundle-like interface to gBrowserBundle, formerly a getter for // the "bundle_browser" element. var gNavigatorBundle = { getString(key) { return gBrowserBundle.GetStringFromName(key); }, getFormattedString(key, array) { return gBrowserBundle.formatStringFromName(key, array); }, }; function showFxaToolbarMenu(enable) { // We only show the Firefox Account toolbar menu if the feature is enabled and // if sync is enabled. const syncEnabled = Services.prefs.getBoolPref( "identity.fxaccounts.enabled", false ); const mainWindowEl = document.documentElement; const fxaPanelEl = document.getElementById("PanelUI-fxa"); mainWindowEl.setAttribute("fxastatus", "not_configured"); fxaPanelEl.addEventListener("ViewShowing", gSync.updateSendToDeviceTitle); Services.telemetry.setEventRecordingEnabled("fxa_app_menu", true); if (enable && syncEnabled) { mainWindowEl.setAttribute("fxatoolbarmenu", "visible"); // We have to manually update the sync state UI when toggling the FxA toolbar // because it could show an invalid icon if the user is logged in and no sync // event was performed yet. gSync.maybeUpdateUIState(); Services.telemetry.setEventRecordingEnabled("fxa_avatar_menu", true); // We set an attribute here so that we can toggle the custom // badge depending on whether the FxA menu was ever accessed. if (!gFxaToolbarAccessed) { mainWindowEl.setAttribute("fxa_avatar_badged", "badged"); } else { mainWindowEl.removeAttribute("fxa_avatar_badged"); } } else { mainWindowEl.removeAttribute("fxatoolbarmenu"); } } function UpdateBackForwardCommands(aWebNavigation) { var backCommand = document.getElementById("Browser:Back"); var forwardCommand = document.getElementById("Browser:Forward"); // Avoid setting attributes on commands 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 = backCommand.hasAttribute("disabled"); var forwardDisabled = forwardCommand.hasAttribute("disabled"); if (backDisabled == aWebNavigation.canGoBack) { if (backDisabled) { backCommand.removeAttribute("disabled"); } else { backCommand.setAttribute("disabled", true); } } if (forwardDisabled == aWebNavigation.canGoForward) { if (forwardDisabled) { forwardCommand.removeAttribute("disabled"); } else { forwardCommand.setAttribute("disabled", true); } } } /** * Click-and-Hold implementation for the Back and Forward buttons * XXXmano: should this live in toolbarbutton.js? */ 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.prepend(popup); gClickAndHoldListenersOnElement.add(backButton); let forwardButton = document.getElementById("forward-button"); popup = popup.cloneNode(true); forwardButton.setAttribute("type", "menu"); forwardButton.prepend(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.menupopup.hidden = true; aEvent.currentTarget.addEventListener("mouseout", this); aEvent.currentTarget.addEventListener("mouseup", this); 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.mozInputSource ); 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.firstElementChild.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); aButton.removeEventListener("mouseup", this); }, _keypressHandler(aEvent) { if (aEvent.key == " " || aEvent.key == "Enter") { // Normally, command events get fired for keyboard activation. However, // we've set type="menu", so that doesn't happen. Handle this the same // way we handle clicks. aEvent.target.click(); } }, 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; case "keypress": this._keypressHandler(e); break; } }, remove(aButton) { aButton.removeEventListener("mousedown", this, true); aButton.removeEventListener("click", this, true); aButton.removeEventListener("keypress", this, true); }, add(aElm) { this._timers.delete(aElm); aElm.addEventListener("mousedown", this, true); aElm.addEventListener("click", this, true); aElm.addEventListener("keypress", this, true); }, }; const gSessionHistoryObserver = { observe(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"); // Clear undo history of the URL bar gURLBar.editor.transactionManager.clear(); }, }; const gStoragePressureObserver = { _lastNotificationTime: -1, async observe(subject, topic, data) { if (topic != "QuotaManager::StoragePressure") { return; } const NOTIFICATION_VALUE = "storage-pressure-notification"; if ( gHighPriorityNotificationBox.getNotificationWithValue(NOTIFICATION_VALUE) ) { // Do not display the 2nd notification when there is already one return; } // Don't display notification twice within the given interval. // This is because // - not to annoy user // - give user some time to clean space. // Even user sees notification and starts acting, it still takes some time. const MIN_NOTIFICATION_INTERVAL_MS = Services.prefs.getIntPref( "browser.storageManager.pressureNotification.minIntervalMS" ); let duration = Date.now() - this._lastNotificationTime; if (duration <= MIN_NOTIFICATION_INTERVAL_MS) { return; } this._lastNotificationTime = Date.now(); MozXULElement.insertFTLIfNeeded("branding/brand.ftl"); MozXULElement.insertFTLIfNeeded("browser/preferences/preferences.ftl"); const BYTES_IN_GIGABYTE = 1073741824; const USAGE_THRESHOLD_BYTES = BYTES_IN_GIGABYTE * Services.prefs.getIntPref( "browser.storageManager.pressureNotification.usageThresholdGB" ); let msg = ""; let buttons = []; let usage = subject.QueryInterface(Ci.nsISupportsPRUint64).data; buttons.push({ "l10n-id": "space-alert-learn-more-button", callback(notificationBar, button) { let learnMoreURL = Services.urlFormatter.formatURLPref("app.support.baseURL") + "storage-permissions"; // This is a content URL, loaded from trusted UX. openTrustedLinkIn(learnMoreURL, "tab"); }, }); if (usage < USAGE_THRESHOLD_BYTES) { // The firefox-used space < 5GB, then warn user to free some disk space. // This is because this usage is small and not the main cause for space issue. // In order to avoid the bad and wrong impression among users that // firefox eats disk space a lot, indicate users to clean up other disk space. [msg] = await document.l10n.formatValues([ { id: "space-alert-under-5gb-message" }, ]); buttons.push({ "l10n-id": "space-alert-under-5gb-ok-button", callback() {}, }); } else { // The firefox-used space >= 5GB, then guide users to about:preferences // to clear some data stored on firefox by websites. [msg] = await document.l10n.formatValues([ { id: "space-alert-over-5gb-message" }, ]); buttons.push({ "l10n-id": "space-alert-over-5gb-pref-button", callback(notificationBar, button) { // The advanced subpanes are only supported in the old organization, which will // be removed by bug 1349689. openPreferences("privacy-sitedata"); }, }); } gHighPriorityNotificationBox.appendNotification( msg, NOTIFICATION_VALUE, null, gHighPriorityNotificationBox.PRIORITY_WARNING_HIGH, buttons, null ); // This seems to be necessary to get the buttons to display correctly // See: https://bugzilla.mozilla.org/show_bug.cgi?id=1504216 document.l10n.translateFragment( gHighPriorityNotificationBox.currentNotification ); }, }; var gPopupBlockerObserver = { handleEvent(aEvent) { if (aEvent.originalTarget != gBrowser.selectedBrowser) { return; } gIdentityHandler.refreshIdentityBlock(); if ( !gBrowser.selectedBrowser.blockedPopups || !gBrowser.selectedBrowser.blockedPopups.length ) { // 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; } // 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 (Services.prefs.getBoolPref("privacy.popups.showBrowserMessage")) { var brandBundle = document.getElementById("bundle_brand"); var brandShortName = brandBundle.getString("brandShortName"); var popupCount = gBrowser.selectedBrowser.blockedPopups.length; var stringKey = AppConstants.platform == "win" ? "popupWarningButton" : "popupWarningButtonUnix"; var popupButtonText = gNavigatorBundle.getString(stringKey); var popupButtonAccesskey = gNavigatorBundle.getString( stringKey + ".accesskey" ); let messageBase; if (popupCount < this.maxReportedPopups) { messageBase = gNavigatorBundle.getString("popupWarning.message"); } else { messageBase = gNavigatorBundle.getString( "popupWarning.exceeded.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/notification-icons/popup.svg", 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(aEvent) { var pm = Services.perms; var shouldBlock = aEvent.target.getAttribute("block") == "true"; var perm = shouldBlock ? pm.DENY_ACTION : pm.ALLOW_ACTION; pm.addFromPrincipal(gBrowser.contentPrincipal, "popup", perm); if (!shouldBlock) { this.showAllBlockedPopups(gBrowser.selectedBrowser); } gBrowser.getNotificationBox().removeCurrentNotification(); }, fillPopupList(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 " // 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.contentPrincipal.URI || browser.currentURI; var blockedPopupAllowSite = document.getElementById( "blockedPopupAllowSite" ); try { blockedPopupAllowSite.removeAttribute("hidden"); let uriHost = uri.asciiHost ? uri.host : uri.spec; var pm = Services.perms; if ( pm.testPermissionFromPrincipal(browser.contentPrincipal, "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", [ uriHost, ]); blockedPopupAllowSite.setAttribute("label", blockString); blockedPopupAllowSite.setAttribute("block", "true"); } else { // Offer an item to allow popups for this site let allowString = gNavigatorBundle.getFormattedString("popupAllow", [ uriHost, ]); 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 = Services.prefs.getBoolPref( "privacy.popups.showBrowserMessage" ); blockedPopupDontShowMessage.setAttribute("checked", !showMessage); 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 == "" || 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.createXULElement("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(aEvent) { let item = aEvent.target.lastElementChild; while (item && item.id != "blockedPopupsSeparator") { let next = item.previousElementSibling; item.remove(); item = next; } }, showBlockedPopup(aEvent) { var target = aEvent.target; var popupReportIndex = target.getAttribute("popupReportIndex"); let browser = target.popupReportBrowser; browser.unblockPopup(popupReportIndex); }, showAllBlockedPopups(aBrowser) { aBrowser.retrieveListOfBlockedPopups().then(popups => { for (let i = 0; i < popups.length; i++) { if (popups[i].popupWindowURIspec) { aBrowser.unblockPopup(i); } } }, null); }, editPopupSettings() { openPreferences("privacy-permissions-block-popups"); }, dontShowMessage() { var showMessage = Services.prefs.getBoolPref( "privacy.popups.showBrowserMessage" ); Services.prefs.setBoolPref( "privacy.popups.showBrowserMessage", !showMessage ); gBrowser.getNotificationBox().removeCurrentNotification(); }, }; XPCOMUtils.defineLazyPreferenceGetter( gPopupBlockerObserver, "maxReportedPopups", "privacy.popups.maxReported" ); function gKeywordURIFixup({ target: browser, data: fixupInfo }) { let deserializeURI = url => { if (url instanceof Ci.nsIURI) { return url; } return url ? makeURI(url) : 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; } let contentPrincipal = browser.contentPrincipal; // 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.displayHost; // 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 onLookupCompleteListener = { onLookupComplete(request, record, status) { let browserRef = weakBrowser.get(); if (!Components.isSuccessCode(status) || !browserRef) { return; } let currentURI = browserRef.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(browserRef); 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() { // Do not set this preference while in private browsing. if (!PrivateBrowsingUtils.isWindowPrivate(window)) { let pref = "browser.fixup.domainwhitelist." + asciiHost; Services.prefs.setBoolPref(pref, true); } openTrustedLinkIn(alternativeURI.spec, "current"); }, }, { label: gNavigatorBundle.getString("keywordURIFixup.dismiss"), accessKey: gNavigatorBundle.getString( "keywordURIFixup.dismiss.accesskey" ), callback() { 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, onLookupCompleteListener, Services.tm.mainThread, contentPrincipal.originAttributes ); } 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); } } } function serializeInputStream(aStream) { let data = { content: NetUtil.readInputStreamToString(aStream, aStream.available()), }; if (aStream instanceof Ci.nsIMIMEInputStream) { data.headers = new Map(); aStream.visitHeaders((name, value) => { data.headers.set(name, value); }); } return data; } /** * Handles URIs when we want to deal with them in chrome code rather than pass * them down to a content browser. This can avoid unnecessary process switching * for the browser. * @param aBrowser the browser that is attempting to load the URI * @param aUri the nsIURI that is being loaded * @returns true if the URI is handled, otherwise false */ function handleUriInChrome(aBrowser, aUri) { if (aUri.scheme == "file") { try { let mimeType = Cc["@mozilla.org/mime;1"] .getService(Ci.nsIMIMEService) .getTypeFromURI(aUri); if (mimeType == "application/x-xpinstall") { let systemPrincipal = Services.scriptSecurityManager.getSystemPrincipal(); AddonManager.getInstallForURL(aUri.spec, { telemetryInfo: { source: "file-url" }, }).then(install => { AddonManager.installAddonFromWebpage( mimeType, aBrowser, systemPrincipal, install ); }); return true; } } catch (e) { return false; } } return false; } /* Creates a null principal using the userContextId from the current selected tab or a passed in tab argument */ function _createNullPrincipalFromTabUserContextId(tab = gBrowser.selectedTab) { let userContextId; if (tab.hasAttribute("usercontextid")) { userContextId = tab.getAttribute("usercontextid"); } return Services.scriptSecurityManager.createNullPrincipal({ userContextId, }); } // 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 _loadURI(browser, uri, params = {}) { if (!uri) { uri = "about:blank"; } let { triggeringPrincipal, referrerInfo, postData, userContextId, csp, } = params || {}; let loadFlags = params.loadFlags || params.flags || Ci.nsIWebNavigation.LOAD_FLAGS_NONE; if (!triggeringPrincipal) { throw new Error("Must load with a triggering Principal"); } let { uriObject, requiredRemoteType, mustChangeProcess, newFrameloader, } = E10SUtils.shouldLoadURIInBrowser( browser, uri, gMultiProcessBrowser, gFissionBrowser, loadFlags ); if (uriObject && handleUriInChrome(browser, uriObject)) { // If we've handled the URI in Chrome then just return here. return; } if (newFrameloader) { // If a new frameloader is needed for process reselection because this used // to be a preloaded browser, clear the preloaded state now. browser.removeAttribute("preloadedState"); } // !requiredRemoteType means we're loading in the parent/this process. if (!requiredRemoteType) { browser.isNavigating = true; } let loadURIOptions = { triggeringPrincipal, csp, loadFlags, referrerInfo, postData, }; try { if (!mustChangeProcess) { if (userContextId) { browser.webNavigation.setOriginAttributesBeforeLoading({ userContextId, privateBrowsingId: PrivateBrowsingUtils.isBrowserPrivate(browser) ? 1 : 0, }); } browser.webNavigation.loadURI(uri, loadURIOptions); } else { // Check if the current browser is allowed to unload. let { permitUnload, timedOut } = browser.permitUnload(); if (!timedOut && !permitUnload) { return; } if (postData) { postData = serializeInputStream(postData); } let loadParams = { uri, triggeringPrincipal: triggeringPrincipal ? E10SUtils.serializePrincipal(triggeringPrincipal) : null, flags: loadFlags, referrerInfo: E10SUtils.serializeReferrerInfo(referrerInfo), remoteType: requiredRemoteType, postData, newFrameloader, csp: csp ? gSerializationHelper.serializeToString(csp) : null, }; if (userContextId) { loadParams.userContextId = 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 (userContextId) { browser.webNavigation.setOriginAttributesBeforeLoading({ userContextId, privateBrowsingId: PrivateBrowsingUtils.isBrowserPrivate(browser) ? 1 : 0, }); } browser.webNavigation.loadURI(uri, loadURIOptions); } else { throw e; } } finally { if (!requiredRemoteType) { browser.isNavigating = 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 }) { if (browser.getAttribute("preloadedState") === "consumed") { browser.removeAttribute("preloadedState"); data.loadOptions.newFrameloader = true; } if (data.loadOptions.reloadInFreshProcess) { // Convert the fresh process load option into a large allocation remote type // to use common processing from this point. data.loadOptions.remoteType = E10SUtils.LARGE_ALLOCATION_REMOTE_TYPE; data.loadOptions.newFrameloader = true; } else if (browser.remoteType == E10SUtils.LARGE_ALLOCATION_REMOTE_TYPE) { // If we're in a Large-Allocation process, we prefer switching back into a // normal content process, as that way we can clean up the L-A process. data.loadOptions.remoteType = E10SUtils.getRemoteTypeForURI( data.loadOptions.uri, gMultiProcessBrowser, gFissionBrowser ); } // 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" ); } } let _resolveDelayedStartup; var delayedStartupPromise = new Promise(resolve => { _resolveDelayedStartup = resolve; }); var gBrowserInit = { delayedStartupFinished: false, idleTasksFinishedPromise: null, idleTaskPromiseResolve: null, _tabToAdopt: undefined, getTabToAdopt() { if (this._tabToAdopt !== undefined) { return this._tabToAdopt; } if (window.arguments && window.arguments[0] instanceof window.XULElement) { this._tabToAdopt = window.arguments[0]; // Clear the reference of the tab being adopted from the arguments. window.arguments[0] = null; } else { // There was no tab to adopt in the arguments, set _tabToAdopt to null // to avoid checking it again. this._tabToAdopt = null; } return this._tabToAdopt; }, _clearTabToAdopt() { this._tabToAdopt = null; }, // Used to check if the new window is still adopting an existing tab as its first tab // (e.g. from the WebExtensions internals). isAdoptingTab() { return !!this.getTabToAdopt(); }, onBeforeInitialXULLayout() { // Set a sane starting width/height for all resolutions on new profiles. if (Services.prefs.getBoolPref("privacy.resistFingerprinting")) { // When the fingerprinting resistance is enabled, making sure that we don't // have a maximum window to interfere with generating rounded window dimensions. document.documentElement.setAttribute("sizemode", "normal"); } else if (!document.documentElement.hasAttribute("width")) { const TARGET_WIDTH = 1280; const TARGET_HEIGHT = 1040; let width = Math.min(screen.availWidth * 0.9, TARGET_WIDTH); let height = Math.min(screen.availHeight * 0.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"); } } // Run menubar initialization first, to avoid TabsInTitlebar code picking // up mutations from it and causing a reflow. AutoHideMenubar.init(); // Update the chromemargin attribute so the window can be sized correctly. window.TabBarVisibility.update(); TabsInTitlebar.init(); new LightweightThemeConsumer(document); if (AppConstants.platform == "win") { if ( window.matchMedia("(-moz-os-version: windows-win8)").matches && window.matchMedia("(-moz-windows-default-theme)").matches ) { let windowFrameColor = new Color( ...ChromeUtils.import( "resource:///modules/Windows8WindowFrameColor.jsm", {} ).Windows8WindowFrameColor.get() ); // Default to black for foreground text. if (!windowFrameColor.isContrastRatioAcceptable(new Color(0, 0, 0))) { document.documentElement.setAttribute("darkwindowframe", "true"); } } else if (AppConstants.isPlatformAndVersionAtLeast("win", "10")) { TelemetryEnvironment.onInitialized().then(() => { // 17763 is the build number of Windows 10 version 1809 if ( TelemetryEnvironment.currentEnvironment.system.os .windowsBuildNumber < 17763 ) { document.documentElement.setAttribute( "always-use-accent-color-for-window-border", "" ); } }); } } // Call this after we set attributes that might change toolbars' computed // text color. ToolbarIconColor.init(); }, onDOMContentLoaded() { // This needs setting up before we create the first remote browser. window.docShell.treeOwner .QueryInterface(Ci.nsIInterfaceRequestor) .getInterface(Ci.nsIAppWindow).XULBrowserWindow = window.XULBrowserWindow; window.browserDOMWindow = new nsBrowserAccess(); gBrowser = window._gBrowser; delete window._gBrowser; gBrowser.init(); BrowserWindowTracker.track(window); gNavToolbox.palette = document.getElementById("BrowserToolbarPalette"); gNavToolbox.palette.remove(); let areas = CustomizableUI.areas; areas.splice(areas.indexOf(CustomizableUI.AREA_FIXED_OVERFLOW_PANEL), 1); for (let area of areas) { let node = document.getElementById(area); CustomizableUI.registerToolbarNode(node); } BrowserSearch.initPlaceHolder(); // Hack to ensure that the various initial pages favicon is loaded // instantaneously, to avoid flickering and improve perceived performance. this._callWithURIToLoad(uriToLoad => { let url; try { url = Services.io.newURI(uriToLoad); } catch (e) { return; } let nonQuery = url.prePath + url.filePath; if (nonQuery in gPageIcons) { gBrowser.setIcon(gBrowser.selectedTab, gPageIcons[nonQuery]); } }); this._setInitialFocus(); showFxaToolbarMenu(gFxaToolbarEnabled); }, onLoad() { gBrowser.addEventListener("DOMUpdateBlockedPopups", gPopupBlockerObserver); Services.obs.addObserver( gPluginHandler.NPAPIPluginCrashed, "plugin-crashed" ); 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. DOMEventHandler.init(); gPageStyleMenu.init(); LanguageDetectionListener.init(); BrowserOnClick.init(); CaptivePortalWatcher.init(); ZoomUI.init(window); let mm = window.getGroupMessageManager("browsers"); mm.loadFrameScript("chrome://browser/content/tab-content.js", true, true); mm.loadFrameScript("chrome://browser/content/content.js", true, true); mm.loadFrameScript( "chrome://global/content/content-HybridContentTelemetry.js", true ); window.messageManager.addMessageListener("Browser:LoadURI", RedirectLoad); if (!gMultiProcessBrowser) { // There is a Content:Click message manually sent from content. Services.els.addSystemEventListener( gBrowser.tabpanels, "click", contentAreaClick, true ); } // hook up UI through progress listener gBrowser.addProgressListener(window.XULBrowserWindow); gBrowser.addTabsProgressListener(window.TabsProgressListener); SidebarUI.init(); // We do this in onload because we want to ensure the button's state // doesn't flicker as the window is being shown. DownloadsButton.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"); if (!window.toolbar.visible) { // adjust browser UI for popups gURLBar.readOnly = true; } // Misc. inits. gUIDensity.init(); TabletModeUpdater.init(); CombinedStopReload.ensureInitialized(); gPrivateBrowsingUI.init(); BrowserSearch.init(); BrowserPageActions.init(); gAccessibilityServiceIndicator.init(); AccessibilityRefreshBlocker.init(); if (gToolbarKeyNavEnabled) { ToolbarKeyboardNavigator.init(); } #ifdef ENABLE_MARIONETTE gRemoteControl.updateVisualCue(Marionette.running); #endif // If we are given a tab to swap in, take care of it before first paint to // avoid an about:blank flash. let tabToAdopt = this.getTabToAdopt(); if (tabToAdopt) { let evt = new CustomEvent("before-initial-tab-adopted", { bubbles: true, }); gBrowser.tabpanels.dispatchEvent(evt); // Stop the about:blank load gBrowser.stop(); // make sure it has a docshell gBrowser.docShell; // Remove the speculative focus from the urlbar to let the url be formatted. gURLBar.removeAttribute("focused"); try { gBrowser.swapBrowsersAndCloseOther(gBrowser.selectedTab, tabToAdopt); } catch (e) { Cu.reportError(e); } // Clear the reference to the tab once its adoption has been completed. this._clearTabToAdopt(); } // 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); if (!PrivateBrowsingUtils.enabled) { document.getElementById("Tools:PrivateBrowsing").hidden = true; // Setting disabled doesn't disable the shortcut, so we just remove // the keybinding. document.getElementById("key_privatebrowsing").remove(); } this._loadHandled = true; }, _cancelDelayedStartup() { window.removeEventListener("MozAfterPaint", this._boundDelayedStartup); this._boundDelayedStartup = null; }, _delayedStartup() { let { TelemetryTimestamps } = ChromeUtils.import( "resource://gre/modules/TelemetryTimestamps.jsm" ); TelemetryTimestamps.add("delayedStartupStarted"); this._cancelDelayedStartup(); // Bug 1531854 - The hidden window is force-created here // until all of its dependencies are handled. Services.appShell.hiddenDOMWindow; gBrowser.addEventListener( "InsecureLoginFormsStateChange", function() { gIdentityHandler.refreshForInsecureLoginForms(); }, true ); gBrowser.addEventListener( "PermissionStateChange", function() { gIdentityHandler.refreshIdentityBlock(); }, true ); // Get the service so that it initializes and registers listeners for new // tab pages in order to be ready for any early-loading about:newtab pages, // e.g., start/home page, command line / startup uris to load, sessionstore gAboutNewTabService.QueryInterface(Ci.nsISupports); this._handleURIToLoad(); Services.obs.addObserver(gIdentityHandler, "perm-changed"); #ifdef ENABLE_MARIONETTE Services.obs.addObserver(gRemoteControl, "remote-active"); #endif Services.obs.addObserver( gSessionHistoryObserver, "browser:purge-session-history" ); Services.obs.addObserver( gStoragePressureObserver, "QuotaManager::StoragePressure" ); Services.obs.addObserver(gXPInstallObserver, "addon-install-disabled"); Services.obs.addObserver(gXPInstallObserver, "addon-install-started"); Services.obs.addObserver(gXPInstallObserver, "addon-install-blocked"); Services.obs.addObserver( gXPInstallObserver, "addon-install-origin-blocked" ); Services.obs.addObserver(gXPInstallObserver, "addon-install-failed"); Services.obs.addObserver(gXPInstallObserver, "addon-install-confirmation"); Services.obs.addObserver(gXPInstallObserver, "addon-install-complete"); window.messageManager.addMessageListener( "Browser:URIFixup", gKeywordURIFixup ); BrowserOffline.init(); IndexedDBPromptHelper.init(); CanvasPermissionPromptHelper.init(); WebAuthnPromptHelper.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(); UpdateUrlbarSearchSplitterState(); BookmarkingUI.init(); BrowserSearch.delayedStartupInit(); AutoShowBookmarksToolbar.init(); ContentBlocking.init(); let safeMode = document.getElementById("helpSafeMode"); if (Services.appinfo.inSafeMode) { document.l10n.setAttributes(safeMode, "menu-help-safe-mode-with-addons"); } // BiDi UI gBidiUI = isBidiEnabled(); if (gBidiUI) { document.getElementById("documentDirection-separator").hidden = false; document.getElementById("documentDirection-swap").hidden = false; document.getElementById("textfieldDirection-separator").hidden = false; document.getElementById("textfieldDirection-swap").hidden = false; } // Setup click-and-hold gestures access to the session history // menus if global click-and-hold isn't turned on if (!Services.prefs.getBoolPref("ui.click_hold_context_menus", false)) { SetClickAndHoldHandlers(); } PlacesToolbarHelper.init(); ctrlTab.readPref(); Services.prefs.addObserver(ctrlTab.prefName, ctrlTab); // The object handling the downloads indicator is 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); placesContext.addEventListener("popuphiding", updateEditUIVisibility); } FullScreen.init(); PointerLock.init(); if (AppConstants.isPlatformAndVersionAtLeast("win", "10")) { MenuTouchModeObserver.init(); } if (AppConstants.MOZ_DATA_REPORTING) { gDataNotificationInfoBar.init(); } if (!AppConstants.MOZILLA_OFFICIAL) { DevelopmentHelpers.init(); } gExtensionsNotifications.init(); let wasMinimized = window.windowState == window.STATE_MINIMIZED; window.addEventListener("sizemodechange", () => { let isMinimized = window.windowState == window.STATE_MINIMIZED; if (wasMinimized != isMinimized) { wasMinimized = isMinimized; UpdatePopupNotificationsVisibility(); } }); window.addEventListener("mousemove", MousePosTracker); window.addEventListener("dragover", MousePosTracker); gNavToolbox.addEventListener("customizationstarting", CustomizationHandler); gNavToolbox.addEventListener("aftercustomization", CustomizationHandler); 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(); SidebarUI.startDelayedLoad(); PanicButtonNotifier.init(); }); gBrowser.tabContainer.addEventListener("TabSelect", function() { for (let panel of document.querySelectorAll( "panel[tabspecific='true']" )) { if (panel.state == "open") { panel.hidePopup(); } } }); CaptivePortalWatcher.delayedStartup(); this.delayedStartupFinished = true; _resolveDelayedStartup(); SessionStore.promiseAllWindowsRestored.then(() => { this._schedulePerWindowIdleTasks(); document.documentElement.setAttribute("sessionrestored", "true"); }); Services.obs.notifyObservers(window, "browser-delayed-startup-finished"); TelemetryTimestamps.add("delayedStartupFinished"); if (!Services.policies.isAllowed("hideShowMenuBar")) { document.getElementById("toolbar-menubar").removeAttribute("toolbarname"); } }, _setInitialFocus() { let initiallyFocusedElement = document.commandDispatcher.focusedElement; let firstBrowserPaintDeferred = {}; firstBrowserPaintDeferred.promise = new Promise(resolve => { firstBrowserPaintDeferred.resolve = resolve; }); let mm = window.messageManager; mm.addMessageListener("Browser:FirstPaint", function onFirstPaint() { mm.removeMessageListener("Browser:FirstPaint", onFirstPaint); firstBrowserPaintDeferred.resolve(); }); let initialBrowser = gBrowser.selectedBrowser; mm.addMessageListener( "Browser:FirstNonBlankPaint", function onFirstNonBlankPaint() { mm.removeMessageListener( "Browser:FirstNonBlankPaint", onFirstNonBlankPaint ); initialBrowser.removeAttribute("blank"); } ); // To prevent flickering of the urlbar-history-dropmarker in the general // case, the urlbar has the 'focused' attribute set by default. // If we are not fully sure the urlbar will be focused in this window, // we should remove the attribute before first paint. let shouldRemoveFocusedAttribute = true; this._callWithURIToLoad(uriToLoad => { if (isBlankPageURL(uriToLoad) || uriToLoad == "about:privatebrowsing") { focusAndSelectUrlBar(); shouldRemoveFocusedAttribute = false; return; } 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. firstBrowserPaintDeferred.promise.then(() => { // If focus didn't move while we were waiting for first paint, we're okay // to move to the browser. if ( document.commandDispatcher.focusedElement == initiallyFocusedElement ) { 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(); } }); // Delay removing the attribute using requestAnimationFrame to avoid // invalidating styles multiple times in a row if uriToLoadPromise // resolves before first paint. if (shouldRemoveFocusedAttribute) { window.requestAnimationFrame(() => { if (shouldRemoveFocusedAttribute) { gURLBar.removeAttribute("focused"); } }); } }, _handleURIToLoad() { this._callWithURIToLoad(uriToLoad => { if (!uriToLoad) { // We don't check whether window.arguments[5] (userContextId) is set // because tabbrowser.js takes care of that for the initial tab. return; } // We don't check if uriToLoad is a XULElement because this case has // already been handled before first paint, and the argument cleared. if (Array.isArray(uriToLoad)) { // This function throws for certain malformed URIs, so use exception handling // so that we don't disrupt startup try { gBrowser.loadTabs(uriToLoad, { inBackground: false, replace: true, // See below for the semantics of window.arguments. Only the minimum is supported. userContextId: window.arguments[5], triggeringPrincipal: window.arguments[8] || Services.scriptSecurityManager.getSystemPrincipal(), allowInheritPrincipal: window.arguments[9], csp: window.arguments[10], fromExternal: true, }); } catch (e) {} } else if (window.arguments.length >= 3) { // window.arguments[1]: unused (bug 871161) // [2]: referrerInfo (nsIReferrerInfo) // [3]: postData (nsIInputStream) // [4]: allowThirdPartyFixup (bool) // [5]: userContextId (int) // [6]: originPrincipal (nsIPrincipal) // [7]: originStoragePrincipal (nsIPrincipal) // [8]: triggeringPrincipal (nsIPrincipal) // [9]: allowInheritPrincipal (bool) // [10]: csp (nsIContentSecurityPolicy) let userContextId = window.arguments[5] != undefined ? window.arguments[5] : Ci.nsIScriptSecurityManager.DEFAULT_USER_CONTEXT_ID; loadURI( uriToLoad, window.arguments[2] || null, window.arguments[3] || null, window.arguments[4] || false, userContextId, // pass the origin principal (if any) and force its use to create // an initial about:blank viewer if present: window.arguments[6], window.arguments[7], !!window.arguments[6], window.arguments[8], // TODO fix allowInheritPrincipal to default to false. // Default to true unless explicitly set to false because of bug 1475201. window.arguments[9] !== false, window.arguments[10] ); window.focus(); } else { // Note: loadOneOrMoreURIs *must not* be called if window.arguments.length >= 3. // Such callers expect that window.arguments[0] is handled as a single URI. loadOneOrMoreURIs( uriToLoad, Services.scriptSecurityManager.getSystemPrincipal(), null ); } }); }, /** * Use this function as an entry point to schedule tasks that * need to run once per window after startup, and can be scheduled * by using an idle callback. * * The functions scheduled here will fire from idle callbacks * once every window has finished being restored by session * restore, and after the equivalent only-once tasks * have run (from _scheduleStartupIdleTasks in BrowserGlue.jsm). */ _schedulePerWindowIdleTasks() { // Bail out if the window has been closed in the meantime. if (window.closed) { return; } function scheduleIdleTask(func, options) { requestIdleCallback(function idleTaskRunner() { if (!window.closed) { func(); } }, options); } scheduleIdleTask(() => { // Initialize the Sync UI gSync.init(); }); scheduleIdleTask(() => { // Initialize the all tabs menu gTabsPanel.init(); }); scheduleIdleTask(() => { CombinedStopReload.startAnimationPrefMonitoring(); }); scheduleIdleTask(() => { // setup simple gestures support gGestureSupport.init(true); // setup history swipe animation gHistorySwipeAnimation.init(); }); scheduleIdleTask(() => { gBrowserThumbnails.init(); }); // Show the addons private browsing panel the first time a private window. scheduleIdleTask(() => { ExtensionsUI.showPrivateBrowsingNotification(window); }); scheduleIdleTask( () => { // 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. try { DownloadsCommon.initializeAllDataLinks(); ChromeUtils.import( "resource:///modules/DownloadsTaskbar.jsm", {} ).DownloadsTaskbar.registerIndicator(window); } catch (ex) { Cu.reportError(ex); } }, { timeout: 10000 } ); if (Win7Features) { scheduleIdleTask(() => Win7Features.onOpenWindow()); } scheduleIdleTask(() => { if (Services.prefs.getBoolPref("privacy.resistFingerprinting")) { return; } setTimeout(() => { if (window.closed) { return; } let browser = gBrowser.selectedBrowser; let browserBounds = window.windowUtils.getBoundsWithoutFlushing( browser ); Services.telemetry.keyedScalarAdd( "resistfingerprinting.content_window_size", `${browserBounds.width}x${browserBounds.height}`, 1 ); }, 300 * 1000); }); scheduleIdleTask(async () => { NewTabPagePreloading.maybeCreatePreloadedBrowser(window); }); // This should always go last, since the idle tasks (except for the ones with // timeouts) should execute in order. Note that this observer notification is // not guaranteed to fire, since the window could close before we get here. scheduleIdleTask(() => { this.idleTaskPromiseResolve(); Services.obs.notifyObservers( window, "browser-idle-startup-tasks-finished" ); }); }, // Returns the URI(s) to load at startup if it is immediately known, or a // promise resolving to the URI to load. get uriToLoadPromise() { delete this.uriToLoadPromise; return (this.uriToLoadPromise = (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 defaultArgs = Cc["@mozilla.org/browser/clh;1"].getService( Ci.nsIBrowserHandler ).defaultArgs; // If the given URI is different from the homepage, we want to load it. if (uri != defaultArgs) { if (uri instanceof Ci.nsIArray) { // Transform the nsIArray of nsISupportsString's into a JS Array of // JS strings. return Array.from( uri.enumerate(Ci.nsISupportsString), supportStr => supportStr.data ); } else if (uri instanceof Ci.nsISupportsString) { return uri.data; } return uri; } // The URI appears to be the the homepage. We want to load it only if // session restore isn't about to override the homepage. let willOverride = SessionStartup.willOverrideHomepage; if (typeof willOverride == "boolean") { return willOverride ? null : uri; } return willOverride.then(willOverrideHomepage => willOverrideHomepage ? null : uri ); })()); }, // Calls the given callback with the URI to load at startup. // Synchronously if possible, or after uriToLoadPromise resolves otherwise. _callWithURIToLoad(callback) { let uriToLoad = this.uriToLoadPromise; if (uriToLoad && uriToLoad.then) { uriToLoad.then(callback); } else { callback(uriToLoad); } }, onUnload() { gUIDensity.uninit(); TabsInTitlebar.uninit(); ToolbarIconColor.uninit(); // 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(); gSync.uninit(); gExtensionsNotifications.uninit(); Services.obs.removeObserver( gPluginHandler.NPAPIPluginCrashed, "plugin-crashed" ); try { gBrowser.removeProgressListener(window.XULBrowserWindow); gBrowser.removeTabsProgressListener(window.TabsProgressListener); } catch (ex) {} PlacesToolbarHelper.uninit(); BookmarkingUI.uninit(); TabletModeUpdater.uninit(); gTabletModePageCounter.finish(); BrowserOnClick.uninit(); CaptivePortalWatcher.uninit(); SidebarUI.uninit(); DownloadsButton.uninit(); gAccessibilityServiceIndicator.uninit(); AccessibilityRefreshBlocker.uninit(); if (gToolbarKeyNavEnabled) { ToolbarKeyboardNavigator.uninit(); } BrowserSearch.uninit(); NewTabPagePreloading.removePreloadedBrowser(window); // Now either cancel delayedStartup, or clean up the services initialized from // it. if (this._boundDelayedStartup) { this._cancelDelayedStartup(); } else { if (Win7Features) { Win7Features.onCloseWindow(); } Services.prefs.removeObserver(ctrlTab.prefName, ctrlTab); ctrlTab.uninit(); gBrowserThumbnails.uninit(); ContentBlocking.uninit(); FullZoom.destroy(); Services.obs.removeObserver(gIdentityHandler, "perm-changed"); #ifdef ENABLE_MARIONETTE Services.obs.removeObserver(gRemoteControl, "remote-active"); #endif Services.obs.removeObserver( gSessionHistoryObserver, "browser:purge-session-history" ); Services.obs.removeObserver( gStoragePressureObserver, "QuotaManager::StoragePressure" ); 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 ); if (AppConstants.isPlatformAndVersionAtLeast("win", "10")) { MenuTouchModeObserver.uninit(); } BrowserOffline.uninit(); IndexedDBPromptHelper.uninit(); CanvasPermissionPromptHelper.uninit(); WebAuthnPromptHelper.uninit(); PanelUI.uninit(); AutoShowBookmarksToolbar.uninit(); } // Final window teardown, do this last. gBrowser.destroy(); window.XULBrowserWindow = null; window.docShell.treeOwner .QueryInterface(Ci.nsIInterfaceRequestor) .getInterface(Ci.nsIAppWindow).XULBrowserWindow = null; window.browserDOMWindow = null; }, }; gBrowserInit.idleTasksFinishedPromise = new Promise(resolve => { gBrowserInit.idleTaskPromiseResolve = resolve; }); 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": gLazyFindCommand("onFindCommand"); break; case "Help": openHelpLink(""); 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) { aEvent = getRootEvent(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 (Services.prefs.getIntPref("browser.backspace_action")) { case 0: BrowserBack(); break; case 1: goDoCommand("cmd_scrollPageUp"); break; } } function BrowserHandleShiftBackspace() { switch (Services.prefs.getIntPref("browser.backspace_action")) { case 0: BrowserForward(); break; case 1: goDoCommand("cmd_scrollPageDown"); break; } } function BrowserStop() { gBrowser.webNavigation.stop(Ci.nsIWebNavigation.STOP_ALL); } function BrowserReloadOrDuplicate(aEvent) { aEvent = getRootEvent(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 = Ci.nsIWebNavigation.LOAD_FLAGS_NONE; BrowserReloadWithFlags(reloadFlags); } function BrowserReloadSkipCache() { // Bypass proxy and cache. const reloadFlags = Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_PROXY | Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_CACHE; BrowserReloadWithFlags(reloadFlags); } function BrowserHome(aEvent) { if (aEvent && "button" in aEvent && aEvent.button == 2) { // right-click: do nothing return; } var homePage = HomePage.get(window); var where = whereToOpenLink(aEvent, false, true); var urls; var notifyObservers; // Home page should open in a new tab when current tab is an app tab if (where == "current" && gBrowser && gBrowser.selectedTab.pinned) { where = "tab"; } // openTrustedLinkIn in utilityOverlay.js doesn't handle loading multiple pages switch (where) { case "current": // If we're going to load an initial page in the current tab as the // home page, we set initialPageLoadedFromURLBar so that the URL // bar is cleared properly (even during a remoteness flip). if (isInitialPage(homePage)) { gBrowser.selectedBrowser.initialPageLoadedFromUserAction = homePage; } loadOneOrMoreURIs( homePage, Services.scriptSecurityManager.getSystemPrincipal(), null ); if (isBlankPageURL(homePage)) { focusAndSelectUrlBar(); } else { gBrowser.selectedBrowser.focus(); } notifyObservers = true; break; case "tabshifted": case "tab": urls = homePage.split("|"); var loadInBackground = Services.prefs.getBoolPref( "browser.tabs.loadBookmarksInBackground", false ); // The homepage observer event should only be triggered when the homepage opens // in the foreground. This is mostly to support the homepage changed by extension // doorhanger which doesn't currently support background pages. This may change in // bug 1438396. notifyObservers = !loadInBackground; gBrowser.loadTabs(urls, { inBackground: loadInBackground, triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(), csp: null, }); break; case "window": // OpenBrowserWindow will trigger the observer event, so no need to do so here. notifyObservers = false; OpenBrowserWindow(); break; } if (notifyObservers) { // A notification for when a user has triggered their homepage. This is used // to display a doorhanger explaining that an extension has modified the // homepage, if necessary. Observers are only notified if the homepage // becomes the active page. Services.obs.notifyObservers(null, "browser-open-homepage-start"); } } function loadOneOrMoreURIs(aURIString, aTriggeringPrincipal, aCsp) { // we're not a browser window, pass the URI string to a new browser window if (window.location.href != AppConstants.BROWSER_CHROME_URL) { window.openDialog( AppConstants.BROWSER_CHROME_URL, "_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("|"), { inBackground: false, replace: true, triggeringPrincipal: aTriggeringPrincipal, csp: aCsp, }); } catch (e) {} } /** * Focuses and expands the location bar input field and selects its contents. */ 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.isCustomizing()) { gNavToolbox.addEventListener("aftercustomization", focusAndSelectUrlBar, { once: true, }); return; } if (window.fullScreen) { FullScreen.showNavToolbox(); } gURLBar.select(); } function openLocation() { if (window.location.href == AppConstants.BROWSER_CHROME_URL) { focusAndSelectUrlBar(); gURLBar.view.autoOpen(); return; } // If there's an open browser window, redirect the command there. let win = getTopWin(); if (win) { win.focus(); win.openLocation(); return; } // There are no open browser windows; open a new one. window.openDialog( AppConstants.BROWSER_CHROME_URL, "_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; } } // A notification intended to be useful for modular peformance tracking // starting as close as is reasonably possible to the time when the user // expressed the intent to open a new tab. Since there are a lot of // entry points, this won't catch every single tab created, but most // initiated by the user should go through here. // // Note 1: This notification gets notified with a promise that resolves // with the linked browser when the tab gets created // Note 2: This is also used to notify a user that an extension has changed // the New Tab page. Services.obs.notifyObservers( { wrappedJSObject: new Promise(resolve => { openTrustedLinkIn(BROWSER_NEW_TAB_URL, where, { relatedToCurrent, resolveOnNewTabCreated: resolve, }); }), }, "browser-open-newtab-start" ); } var gLastOpenDirectory = { _lastDir: null, get path() { if (!this._lastDir || !this._lastDir.exists()) { try { this._lastDir = Services.prefs.getComplexValue( "browser.open.lastDir", Ci.nsIFile ); 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)) { Services.prefs.setComplexValue( "browser.open.lastDir", Ci.nsIFile, this._lastDir ); } }, reset() { 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.nsIFile); } } catch (ex) {} openTrustedLinkIn(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(event) { // If we're not a browser window, just close the window. if (window.location.href != AppConstants.BROWSER_CHROME_URL) { closeWindow(true); return; } // In a multi-select context, close all selected tabs if (gBrowser.multiSelectedTabsCount) { gBrowser.removeMultiSelectedTabs(); return; } // Keyboard shortcuts that would close a tab that is pinned select the first // unpinned tab instead. if ( event && (event.ctrlKey || event.metaKey || event.altKey) && gBrowser.selectedTab.pinned ) { if (gBrowser.visibleTabs.length > gBrowser._numPinnedTabs) { gBrowser.tabContainer.selectedIndex = gBrowser._numPinnedTabs; } 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, referrerInfo, postData, allowThirdPartyFixup, userContextId, originPrincipal, originStoragePrincipal, forceAboutBlankViewerInCurrent, triggeringPrincipal, allowInheritPrincipal = false, csp = null ) { if (!triggeringPrincipal) { throw new Error("Must load with a triggering Principal"); } try { openLinkIn(uri, "current", { referrerInfo, postData, allowThirdPartyFixup, userContextId, originPrincipal, originStoragePrincipal, triggeringPrincipal, csp, forceAboutBlankViewerInCurrent, allowInheritPrincipal, }); } catch (e) { Cu.reportError(e); } } function getLoadContext() { return window.docShell.QueryInterface(Ci.nsILoadContext); } function readFromClipboard() { var url; try { // Create transferable that will transfer the text. var trans = Cc["@mozilla.org/widget/transferable;1"].createInstance( Ci.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 = {}; trans.getTransferData("text/unicode", data); if (data) { data = data.value.QueryInterface(Ci.nsISupportsString); url = data.data; } } catch (ex) {} return url; } /** * Open the View Source dialog. * * @param args * An object with 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. */ async function BrowserViewSourceOfDocument(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")) { try { await top.gViewSourceUtils.openInExternalEditor(args); return; } catch (data) {} } let tabBrowser = gBrowser; let preferredRemoteType; if (args.browser) { preferredRemoteType = args.browser.remoteType; } else { if (!tabBrowser) { throw new Error( "BrowserViewSourceOfDocument should be passed the " + "subject browser if called from a window without " + "gBrowser defined." ); } // 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. preferredRemoteType = E10SUtils.getRemoteTypeForURI( args.URL, gMultiProcessBrowser, gFissionBrowser ); } // 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 = BrowserWindowTracker.getTopWindow(); tabBrowser = browserWindow.gBrowser; } const inNewWindow = !Services.prefs.getBoolPref("view_source.tab"); // `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: inNewWindow, skipAnimation: inNewWindow, preferredRemoteType, sameProcessAsFrameLoader: args.browser ? args.browser.frameLoader : null, triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(), }); args.viewSourceBrowser = tabBrowser.getBrowserForTab(tab); top.gViewSourceUtils.viewSourceInBrowser(args); if (inNewWindow) { tabBrowser.hideTab(tab); tabBrowser.replaceTabWithWindow(tab); } } /** * 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, 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 }; documentURL = documentURL || window.gBrowser.selectedBrowser.currentURI.spec; // Check for windows matching the url for (let currentWindow of Services.wm.getEnumerator("Browser:page-info")) { 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.xhtml", "", "chrome,toolbar,dialog=no,resizable", args ); } /** * Sets the URI to display in the location bar. * * @param aURI [optional] * nsIURI to set. If this is unspecified, the current URI will be used. * @param updatePopupNotifications [optional] * Passed though to SetPageProxyState, indicates whether the * PopupNotifications need updated. */ function URLBarSetURI(aURI, updatePopupNotifications) { var value = gBrowser.userTypedValue; var valid = false; // Explicitly check for nulled out value. We don't want to reset the URL // bar if the user has deleted the URL and we'd just put the same URL // back. See bug 304198. if (value === null) { let uri = aURI || gBrowser.currentURI; // Strip off usernames and passwords for the location bar try { uri = Services.io.createExposableURI(uri); } catch (e) {} // Replace initial page URIs with an empty string // only if there's no opener (bug 370555). if ( isInitialPage(uri) && 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) || uri.schemeIs("moz-extension"); } else if ( isInitialPage(value) && checkEmptyPageOrigin(gBrowser.selectedBrowser) ) { value = ""; valid = true; } let isDifferentValidValue = valid && value != gURLBar.value; gURLBar.value = value; gURLBar.valueIsTyped = !valid; gURLBar.removeAttribute("usertyping"); if (isDifferentValidValue) { // The selection is enforced only for new values, to avoid overriding the // cursor position when the user switches windows while typing. gURLBar.selectionStart = gURLBar.selectionEnd = 0; } SetPageProxyState(valid ? "valid" : "invalid", updatePopupNotifications); } 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.displaySpec; 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$/gi, 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( // eslint-disable-next-line no-control-regex /[\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( // eslint-disable-next-line no-misleading-character-class /[\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.nextElementSibling == searchbar && splitter.previousElementSibling == urlbar) || (splitter.nextElementSibling == urlbar && splitter.previousElementSibling == searchbar)) ) { return; } var ibefore = null; if (urlbar && searchbar) { if (urlbar.nextElementSibling == searchbar) { ibefore = searchbar; } else if (searchbar.nextElementSibling == urlbar) { ibefore = urlbar; } } if (ibefore) { if (!splitter) { splitter = document.createXULElement("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.remove(); } } function UpdatePageProxyState() { if (gURLBar && gURLBar.value != gLastValidURLStr) { SetPageProxyState("invalid", true); } } /** * Updates the user interface to indicate whether the URI in the location bar is * different than the loaded page, because it's being edited or because a search * result is currently selected and is displayed in the location bar. * * @param aState * The string "valid" indicates that the security indicators and other * related user interface elments should be shown because the URI in the * location bar matches the loaded page. The string "invalid" indicates * that the URI in the location bar is different than the loaded page. * @param updatePopupNotifications * Boolean that indicates whether we should update the PopupNotifications * visibility due to this change, otherwise avoid doing so as it is being * handled somewhere else. */ function SetPageProxyState(aState, updatePopupNotifications) { if (!gURLBar) { return; } let oldPageProxyState = gURLBar.getAttribute("pageproxystate"); gURLBar.setPageProxyState(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); } else if (aState == "invalid") { gURLBar.removeEventListener("input", UpdatePageProxyState); } // After we've ensured that we've applied the listeners and updated the value // of gLastValidURLStr, return early if the actual state hasn't changed. if (oldPageProxyState == aState || !updatePopupNotifications) { return; } UpdatePopupNotificationsVisibility(); } function UpdatePopupNotificationsVisibility() { // Only need to do something if the PopupNotifications object for this window // has already been initialized (i.e. its getter no longer exists). if (Object.getOwnPropertyDescriptor(window, "PopupNotifications").get) { return; } // Notify PopupNotifications that the visible anchors may have changed. This // also checks the suppression state according to the "shouldSuppress" // function defined earlier in this file. PopupNotifications.anchorVisibilityChange(); } function PageProxyClickHandler(aEvent) { if (aEvent.button == 1 && Services.prefs.getBoolPref("middlemouse.paste")) { middleMousePaste(aEvent); } } // 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 SEC_ERROR_BASE = Ci.nsINSSErrorsService.NSS_SEC_ERROR_BASE; const SEC_ERROR_UNKNOWN_ISSUER = SEC_ERROR_BASE + 13; const PREF_SSL_IMPACT_ROOTS = ["security.tls.version.", "security.ssl3."]; /** * 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() { let mm = window.messageManager; mm.addMessageListener("Browser:CertExceptionError", this); mm.addMessageListener("Browser:EnableOnlineMode", this); mm.addMessageListener("Browser:ResetSSLPreferences", this); mm.addMessageListener("Browser:SSLErrorReportTelemetry", this); mm.addMessageListener("Browser:SSLErrorGoBack", this); mm.addMessageListener("Browser:PrimeMitm", this); mm.addMessageListener("Browser:ResetEnterpriseRootsPref", this); }, uninit() { let mm = window.messageManager; mm.removeMessageListener("Browser:CertExceptionError", this); mm.removeMessageListener("Browser:EnableOnlineMode", this); mm.removeMessageListener("Browser:ResetSSLPreferences", this); mm.removeMessageListener("Browser:SSLErrorReportTelemetry", this); mm.removeMessageListener("Browser:SSLErrorGoBack", this); mm.removeMessageListener("Browser:PrimeMitm", this); mm.removeMessageListener("Browser:ResetEnterpriseRootsPref", this); }, receiveMessage(msg) { switch (msg.name) { case "Browser:CertExceptionError": this.onCertError( msg.target, msg.data.elementId, msg.data.isTopFrame, msg.data.location, msg.data.securityInfoAsString, msg.data.frameId ); 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:ResetSSLPreferences": let prefSSLImpact = PREF_SSL_IMPACT_ROOTS.reduce((prefs, root) => { return prefs.concat(Services.prefs.getChildList(root)); }, []); for (let prefName of prefSSLImpact) { Services.prefs.clearUserPref(prefName); } msg.target.reload(); break; case "Browser:SSLErrorReportTelemetry": let reportStatus = msg.data.reportStatus; Services.telemetry .getHistogramById("TLS_ERROR_REPORT_UI") .add(reportStatus); break; case "Browser:SSLErrorGoBack": goBackFromErrorPage(); break; case "Browser:PrimeMitm": this.primeMitm(msg.target); break; case "Browser:ResetEnterpriseRootsPref": Services.prefs.clearUserPref("security.enterprise_roots.enabled"); Services.prefs.clearUserPref("security.enterprise_roots.auto-enabled"); break; } }, /** * This function does a canary request to a reliable, maintained endpoint, in * order to help network code detect a system-wide man-in-the-middle. */ primeMitm(browser) { // If we already have a mitm canary issuer stored, then don't bother with the // extra request. This will be cleared on every update ping. if (Services.prefs.getStringPref("security.pki.mitm_canary_issuer", null)) { return; } let url = Services.prefs.getStringPref( "security.certerrors.mitm.priming.endpoint" ); let request = new XMLHttpRequest({ mozAnon: true }); request.open("HEAD", url); request.channel.loadFlags |= Ci.nsIRequest.LOAD_BYPASS_CACHE; request.channel.loadFlags |= Ci.nsIRequest.INHIBIT_CACHING; request.addEventListener("error", event => { // Make sure the user is still on the cert error page. if (!browser.documentURI.spec.startsWith("about:certerror")) { return; } let secInfo = request.channel.securityInfo.QueryInterface( Ci.nsITransportSecurityInfo ); if (secInfo.errorCode != SEC_ERROR_UNKNOWN_ISSUER) { return; } // When we get to this point there's already something deeply wrong, it's very likely // that there is indeed a system-wide MitM. if (secInfo.serverCert && secInfo.serverCert.issuerName) { // Grab the issuer of the certificate used in the exchange and store it so that our // network-level MitM detection code has a comparison baseline. Services.prefs.setStringPref( "security.pki.mitm_canary_issuer", secInfo.serverCert.issuerName ); // MitM issues are sometimes caused by software not registering their root certs in the // Firefox root store. We might opt for using third party roots from the system root store. if ( Services.prefs.getBoolPref( "security.certerrors.mitm.auto_enable_enterprise_roots" ) ) { if ( !Services.prefs.getBoolPref("security.enterprise_roots.enabled") ) { // Loading enterprise roots happens on a background thread, so wait for import to finish. BrowserUtils.promiseObserved("psm:enterprise-certs-imported").then( () => { if (browser.documentURI.spec.startsWith("about:certerror")) { browser.reload(); } } ); Services.prefs.setBoolPref( "security.enterprise_roots.enabled", true ); // Record that this pref was automatically set. Services.prefs.setBoolPref( "security.enterprise_roots.auto-enabled", true ); } } else { // Need to reload the page to make sure network code picks up the canary issuer pref. browser.reload(); } } }); request.send(null); }, onCertError( browser, elementId, isTopFrame, location, securityInfoAsString, frameId ) { let securityInfo; let cert; switch (elementId) { case "viewCertificate": securityInfo = getSecurityInfo(securityInfoAsString); cert = securityInfo.serverCert; Services.ww.openWindow( window, "chrome://pippki/content/certViewer.xhtml", "_blank", "centerscreen,chrome", cert ); break; case "exceptionDialogButton": securityInfo = getSecurityInfo(securityInfoAsString); let overrideService = Cc[ "@mozilla.org/security/certoverride;1" ].getService(Ci.nsICertOverrideService); let flags = 0; if (securityInfo.isUntrusted) { flags |= overrideService.ERROR_UNTRUSTED; } if (securityInfo.isDomainMismatch) { flags |= overrideService.ERROR_MISMATCH; } if (securityInfo.isNotValidAtThisTime) { flags |= overrideService.ERROR_TIME; } let uri = Services.uriFixup.createFixupURI(location, 0); let permanentOverride = !PrivateBrowsingUtils.isBrowserPrivate(browser) && Services.prefs.getBoolPref("security.certerrors.permanentOverride"); cert = securityInfo.serverCert; overrideService.rememberValidityOverride( uri.asciiHost, uri.port, cert, flags, !permanentOverride ); browser.reload(); break; case "returnButton": goBackFromErrorPage(); break; case "advancedPanelReturnButton": goBackFromErrorPage(); break; case "advancedButton": securityInfo = getSecurityInfo(securityInfoAsString); let errorInfo = getDetailedCertErrorInfo(location, securityInfo); let validityInfo = { notAfter: securityInfo.serverCert.validity.notAfter / 1000, notBefore: securityInfo.serverCert.validity.notBefore / 1000, }; browser.messageManager.sendAsyncMessage("CertErrorDetails", { code: securityInfo.errorCode, info: errorInfo, codeString: securityInfo.errorCodeString, certIsUntrusted: securityInfo.isUntrusted, certSubjectAltNames: securityInfo.serverCert.subjectAltNames, validity: validityInfo, url: location, isDomainMismatch: securityInfo.isDomainMismatch, isNotValidAtThisTime: securityInfo.isNotValidAtThisTime, frameId, }); 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; } }, ignoreWarningLink(reason, blockedInfo) { let triggeringPrincipal = E10SUtils.deserializePrincipal( blockedInfo.triggeringPrincipal, _createNullPrincipalFromTabUserContextId() ); // 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.loadURI(gBrowser.currentURI.spec, { triggeringPrincipal, flags: Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_CLASSIFIER, }); // We can't use gBrowser.contentPrincipal which is principal of about:blocked // Create one from uri with current principal origin attributes let principal = Services.scriptSecurityManager.createCodebasePrincipal( gBrowser.currentURI, gBrowser.contentPrincipal.originAttributes ); Services.perms.addFromPrincipal( principal, "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() { getMeOutOfHere(); }, }, ]; let title; if (reason === "malware") { let reportUrl = gSafeBrowsing.getReportURL("MalwareMistake", blockedInfo); title = gNavigatorBundle.getString("safebrowsing.reportedAttackSite"); // There's no button if we can not get report url, for example if the provider // of blockedInfo is not Google if (reportUrl) { buttons[1] = { label: gNavigatorBundle.getString( "safebrowsing.notAnAttackButton.label" ), accessKey: gNavigatorBundle.getString( "safebrowsing.notAnAttackButton.accessKey" ), callback() { openTrustedLinkIn(reportUrl, "tab"); }, }; } } else if (reason === "phishing") { let reportUrl = gSafeBrowsing.getReportURL("PhishMistake", blockedInfo); title = gNavigatorBundle.getString("safebrowsing.deceptiveSite"); // There's no button if we can not get report url, for example if the provider // of blockedInfo is not Google if (reportUrl) { buttons[1] = { label: gNavigatorBundle.getString( "safebrowsing.notADeceptiveSiteButton.label" ), accessKey: gNavigatorBundle.getString( "safebrowsing.notADeceptiveSiteButton.accessKey" ), callback() { openTrustedLinkIn(reportUrl, "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. } else if (reason === "harmful") { title = gNavigatorBundle.getString("safebrowsing.reportedHarmfulSite"); // There is no button for reporting errors since Google doesn't currently // provide a URL endpoint for these reports. } SafeBrowsingNotificationBox.show(title, buttons); }, }; /** * 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(), { triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(), // Also needs to load homepage }); } /** * 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() { let state = JSON.parse(SessionStore.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(), { triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(), }); } 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() { let url = BROWSER_NEW_TAB_URL; if (PrivateBrowsingUtils.isWindowPrivate(window)) { return url; } url = HomePage.getDefault(); // If url is a pipe-delimited set of pages, just take the first one. if (url.includes("|")) { url = url.split("|")[0]; } return url; } function BrowserFullScreen() { window.fullScreen = !window.fullScreen; } function getWebNavigation() { return gBrowser.webNavigation; } function BrowserReloadWithFlags(reloadFlags) { let unchangedRemoteness = []; for (let tab of gBrowser.selectedTabs) { let browser = tab.linkedBrowser; let url = browser.currentURI.spec; if (gBrowser.updateBrowserRemotenessByURL(browser, 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. if (tab.linkedPanel) { loadBrowserURI(browser, url); } else { // Shift to fully loaded browser and make // sure load handler is instantiated. tab.addEventListener( "SSTabRestoring", () => loadBrowserURI(browser, url), { once: true } ); gBrowser._insertBrowser(tab); } } else { unchangedRemoteness.push(tab); } } if (unchangedRemoteness.length == 0) { return; } // Reset temporary permissions on the remaining tabs to reload. // This is done here because we only want to reset // permissions on user reload. for (let tab of unchangedRemoteness) { SitePermissions.clearTemporaryPermissions(tab.linkedBrowser); // Also reset DOS mitigations for the basic auth prompt on reload. delete tab.linkedBrowser.authPromptAbuseCounter; } PanelMultiView.hidePopup(gIdentityHandler._identityPopup); let handlingUserInput = window.windowUtils.isHandlingUserInput; for (let tab of unchangedRemoteness) { if (tab.linkedPanel) { sendReloadMessage(tab); } else { // Shift to fully loaded browser and make // sure load handler is instantiated. tab.addEventListener("SSTabRestoring", () => sendReloadMessage(tab), { once: true, }); gBrowser._insertBrowser(tab); } } function loadBrowserURI(browser, url) { browser.loadURI(url, { flags: reloadFlags, triggeringPrincipal: browser.contentPrincipal, }); } function sendReloadMessage(tab) { tab.linkedBrowser.messageManager.sendAsyncMessage("Browser:Reload", { flags: reloadFlags, handlingUserInput, }); } } function getSecurityInfo(securityInfoAsString) { if (!securityInfoAsString) { return null; } let securityInfo = gSerializationHelper.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 isSecureURI 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); let hasHSTS = sss.isSecureURI(sss.HEADER_HSTS, uri, flags); let hasHPKP = sss.isSecureURI(sss.HEADER_HPKP, uri, flags); certErrorDetails += "\r\n\r\n" + gNavigatorBundle.getFormattedString("certErrorDetailsHSTS.label", [ hasHSTS, ]); certErrorDetails += "\r\n" + gNavigatorBundle.getFormattedString("certErrorDetailsKeyPinning.label", [ hasHPKP, ]); certErrorDetails; return certErrorDetails; } // TODO: can we pull getPEMString in from pippki.js instead of // duplicating them here? function getPEMString(cert) { var derb64 = cert.getBase64DERString(); // 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, _simplifiedPrintPreviewTab: null, _tabBeforePrintPreview: null, _simplifyPageTab: null, _lastRequestedPrintPreviewTab: null, _createPPBrowser() { let browser = this.getSourceBrowser(); let preferredRemoteType = browser.remoteType; return gBrowser.loadOneTab("about:printpreview", { inBackground: true, preferredRemoteType, sameProcessAsFrameLoader: browser.frameLoader, triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(), }); }, getPrintPreviewBrowser() { if (!this._printPreviewTab) { this._printPreviewTab = this._createPPBrowser(); } gBrowser._allowTabChange = true; this._lastRequestedPrintPreviewTab = gBrowser.selectedTab = this._printPreviewTab; gBrowser._allowTabChange = false; return gBrowser.getBrowserForTab(this._printPreviewTab); }, getSimplifiedPrintPreviewBrowser() { if (!this._simplifiedPrintPreviewTab) { this._simplifiedPrintPreviewTab = this._createPPBrowser(); } gBrowser._allowTabChange = true; this._lastRequestedPrintPreviewTab = gBrowser.selectedTab = this._simplifiedPrintPreviewTab; gBrowser._allowTabChange = false; return gBrowser.getBrowserForTab(this._simplifiedPrintPreviewTab); }, createSimplifiedBrowser() { let browser = this.getSourceBrowser(); this._simplifyPageTab = gBrowser.loadOneTab("about:printpreview", { inBackground: true, sameProcessAsFrameLoader: browser.frameLoader, triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(), }); return this.getSimplifiedSourceBrowser(); }, getSourceBrowser() { if (!this._tabBeforePrintPreview) { this._tabBeforePrintPreview = gBrowser.selectedTab; } return this._tabBeforePrintPreview.linkedBrowser; }, getSimplifiedSourceBrowser() { return this._simplifyPageTab ? gBrowser.getBrowserForTab(this._simplifyPageTab) : null; }, getNavToolbox() { return gNavToolbox; }, onEnter() { // We might have accidentally switched tabs since the user invoked print // preview if (gBrowser.selectedTab != this._lastRequestedPrintPreviewTab) { gBrowser.selectedTab = this._lastRequestedPrintPreviewTab; } gInPrintPreviewMode = true; this._toggleAffectedChrome(); }, onExit() { gBrowser._allowTabChange = true; gBrowser.selectedTab = this._tabBeforePrintPreview; gBrowser._allowTabChange = false; this._tabBeforePrintPreview = null; gInPrintPreviewMode = false; this._toggleAffectedChrome(); let tabsToRemove = [ "_simplifyPageTab", "_printPreviewTab", "_simplifiedPrintPreviewTab", ]; for (let tabProp of tabsToRemove) { if (this[tabProp]) { gBrowser.removeTab(this[tabProp]); this[tabProp] = null; } } gBrowser.deactivatePrintPreviewBrowsers(); this._lastRequestedPrintPreviewTab = null; }, _toggleAffectedChrome() { gNavToolbox.collapsed = gInPrintPreviewMode; if (gInPrintPreviewMode) { this._hideChrome(); } else { this._showChrome(); } TabsInTitlebar.allowedBy("print-preview", !gInPrintPreviewMode); }, _hideChrome() { this._chromeState = {}; this._chromeState.sidebarOpen = SidebarUI.isOpen; this._sidebarCommand = SidebarUI.currentID; SidebarUI.hide(); this._chromeState.findOpen = gFindBarInitialized && !gFindBar.hidden; if (gFindBarInitialized) { gFindBar.close(); } gBrowser.getNotificationBox().stack.hidden = true; gNotificationBox.stack.hidden = true; }, _showChrome() { gNotificationBox.stack.hidden = false; gBrowser.getNotificationBox().stack.hidden = false; if (this._chromeState.findOpen) { gLazyFindCommand("open"); } if (this._chromeState.sidebarOpen) { SidebarUI.show(this._sidebarCommand); } }, activateBrowser(browser) { gBrowser.activateBrowserForPrintPreview(browser); }, }; var browserDragAndDrop = { canDropLink: aEvent => Services.droppedLinkHandler.canDropLink(aEvent, true), dragOver(aEvent) { if (this.canDropLink(aEvent)) { aEvent.preventDefault(); } }, getTriggeringPrincipal(aEvent) { return Services.droppedLinkHandler.getTriggeringPrincipal(aEvent); }, getCSP(aEvent) { return Services.droppedLinkHandler.getCSP(aEvent); }, validateURIsForDrop(aEvent, aURIs) { return Services.droppedLinkHandler.validateURIsForDrop(aEvent, aURIs); }, dropLinks(aEvent, aDisallowInherit) { return Services.droppedLinkHandler.dropLinks(aEvent, aDisallowInherit); }, }; var homeButtonObserver = { onDrop(aEvent) { // disallow setting home pages that inherit the principal let links = browserDragAndDrop.dropLinks(aEvent, true); if (links.length) { let urls = []; for (let link of links) { if (link.url.includes("|")) { urls.push(...link.url.split("|")); } else { urls.push(link.url); } } try { browserDragAndDrop.validateURIsForDrop(aEvent, urls); } catch (e) { return; } setTimeout(openHomeDialog, 0, urls.join("|")); } }, onDragOver(aEvent) { if (HomePage.locked) { return; } browserDragAndDrop.dragOver(aEvent); aEvent.dropEffect = "link"; }, onDragExit(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 { HomePage.set(aURL); } catch (ex) { dump("Failed to set the home page.\n" + ex + "\n"); } } } var newTabButtonObserver = { onDragOver(aEvent) { browserDragAndDrop.dragOver(aEvent); }, onDragExit(aEvent) {}, async onDrop(aEvent) { let shiftKey = aEvent.shiftKey; let links = browserDragAndDrop.dropLinks(aEvent); let triggeringPrincipal = browserDragAndDrop.getTriggeringPrincipal(aEvent); let csp = browserDragAndDrop.getCSP(aEvent); if ( links.length >= Services.prefs.getIntPref("browser.tabs.maxOpenBeforeWarn") ) { // Sync dialog cannot be used inside drop event handler. let answer = await OpenInTabsUtils.promiseConfirmOpenInTabs( links.length, window ); if (!answer) { return; } } for (let link of links) { if (link.url) { let data = await UrlbarUtils.getShortcutOrURIAndPostData(link.url); // Allow third-party services to fixup this URL. openNewTabWith(data.url, shiftKey, { // TODO fix allowInheritPrincipal // (this is required by javascript: drop to the new window) Bug 1475201 allowInheritPrincipal: true, postData: data.postData, allowThirdPartyFixup: true, triggeringPrincipal, csp, }); } } }, }; var newWindowButtonObserver = { onDragOver(aEvent) { browserDragAndDrop.dragOver(aEvent); }, onDragExit(aEvent) {}, async onDrop(aEvent) { let links = browserDragAndDrop.dropLinks(aEvent); let triggeringPrincipal = browserDragAndDrop.getTriggeringPrincipal(aEvent); let csp = browserDragAndDrop.getCSP(aEvent); if ( links.length >= Services.prefs.getIntPref("browser.tabs.maxOpenBeforeWarn") ) { // Sync dialog cannot be used inside drop event handler. let answer = await OpenInTabsUtils.promiseConfirmOpenInTabs( links.length, window ); if (!answer) { return; } } for (let link of links) { if (link.url) { let data = await UrlbarUtils.getShortcutOrURIAndPostData(link.url); // Allow third-party services to fixup this URL. openNewWindowWith(data.url, { // TODO fix allowInheritPrincipal // (this is required by javascript: drop to the new window) Bug 1475201 allowInheritPrincipal: true, postData: data.postData, allowThirdPartyFixup: true, triggeringPrincipal, csp, }); } } }, }; const DOMEventHandler = { init() { let mm = window.messageManager; mm.addMessageListener("Link:LoadingIcon", this); mm.addMessageListener("Link:SetIcon", this); mm.addMessageListener("Link:SetFailedIcon", this); mm.addMessageListener("Link:AddSearch", this); mm.addMessageListener("Meta:SetPageInfo", this); }, receiveMessage(aMsg) { switch (aMsg.name) { case "Link:LoadingIcon": if (aMsg.data.canUseForTab) { this.setPendingIcon(aMsg.target); } break; case "Link:SetIcon": this.setIconFromLink( aMsg.target, aMsg.data.pageURL, aMsg.data.originalURL, aMsg.data.canUseForTab, aMsg.data.expiration, aMsg.data.iconURL ); break; case "Link:SetFailedIcon": if (aMsg.data.canUseForTab) { this.clearPendingIcon(aMsg.target); } break; case "Link:AddSearch": this.addSearch(aMsg.target, aMsg.data.engine, aMsg.data.url); break; case "Meta:SetPageInfo": this.setPageInfo(aMsg.data); break; } }, setPageInfo(aData) { const { url, description, previewImageURL } = aData; gBrowser.setPageInfo(url, description, previewImageURL); return true; }, setPendingIcon(aBrowser) { let tab = gBrowser.getTabForBrowser(aBrowser); if (tab.hasAttribute("busy")) { tab.setAttribute("pendingicon", "true"); } }, clearPendingIcon(aBrowser) { let tab = gBrowser.getTabForBrowser(aBrowser); tab.removeAttribute("pendingicon"); }, setIconFromLink( aBrowser, aPageURL, aOriginalURL, aCanUseForTab, aExpiration, aIconURL ) { let tab = gBrowser.getTabForBrowser(aBrowser); if (!tab) { return; } if (aCanUseForTab) { this.clearPendingIcon(aBrowser); } let iconURI; try { iconURI = Services.io.newURI(aIconURL); } catch (ex) { Cu.reportError(ex); return; } if (iconURI.scheme != "data") { try { Services.scriptSecurityManager.checkLoadURIWithPrincipal( aBrowser.contentPrincipal, iconURI, Services.scriptSecurityManager.ALLOW_CHROME ); } catch (ex) { return; } } try { PlacesUIUtils.loadFavicon( aBrowser, Services.scriptSecurityManager.getSystemPrincipal(), makeURI(aPageURL), makeURI(aOriginalURL), aExpiration, iconURI ); } catch (ex) { Cu.reportError(ex); } if (aCanUseForTab) { gBrowser.setIcon(tab, aIconURL, aOriginalURL); } }, addSearch(aBrowser, aEngine, aURL) { let tab = gBrowser.getTabForBrowser(aBrowser); if (!tab) { return; } BrowserSearch.addEngine(aBrowser, aEngine, makeURI(aURL)); }, }; const BrowserSearch = { _searchInitComplete: false, init() { Services.obs.addObserver(this, "browser-search-engine-modified"); }, delayedStartupInit() { // Asynchronously initialize the search service if necessary, to get the // current engine for working out the placeholder. Services.search.getDefault().then(defaultEngine => { // Delay the update for this until so that we don't change it while // the user is looking at it / isn't expecting it. this._updateURLBarPlaceholder(defaultEngine.name, true); this._searchInitComplete = true; }); }, uninit() { Services.obs.removeObserver(this, "browser-search-engine-modified"); }, observe(engine, topic, data) { // There are two kinds of search engine objects, nsISearchEngine objects and // plain { uri, title, icon } objects. `engine` in this method is the // former. The browser.engines and browser.hiddenEngines arrays are the // latter, and they're the engines offered by the the page in the browser. // // The two types of engines are currently related by their titles/names, // although that may change; see bug 335102. let engineName = engine.wrappedJSObject.name; switch (data) { case "engine-removed": // An engine was removed from the search service. If a page is offering // the engine, then the engine needs to be added back to the corresponding // browser's offered engines. this._addMaybeOfferedEngine(engineName); break; case "engine-added": // An engine was added to the search service. If a page is offering the // engine, then the engine needs to be removed from the corresponding // browser's offered engines. this._removeMaybeOfferedEngine(engineName); break; case "engine-default": if (this._searchInitComplete) { this._updateURLBarPlaceholder(engineName); } break; } }, _addMaybeOfferedEngine(engineName) { let selectedBrowserOffersEngine = false; for (let browser of gBrowser.browsers) { for (let i = 0; i < (browser.hiddenEngines || []).length; i++) { if (browser.hiddenEngines[i].title == engineName) { if (!browser.engines) { browser.engines = []; } browser.engines.push(browser.hiddenEngines[i]); browser.hiddenEngines.splice(i, 1); if (browser == gBrowser.selectedBrowser) { selectedBrowserOffersEngine = true; } break; } } } if (selectedBrowserOffersEngine) { this.updateOpenSearchBadge(); } }, _removeMaybeOfferedEngine(engineName) { let selectedBrowserOffersEngine = false; for (let browser of gBrowser.browsers) { for (let i = 0; i < (browser.engines || []).length; i++) { if (browser.engines[i].title == engineName) { if (!browser.hiddenEngines) { browser.hiddenEngines = []; } browser.hiddenEngines.push(browser.engines[i]); browser.engines.splice(i, 1); if (browser == gBrowser.selectedBrowser) { selectedBrowserOffersEngine = true; } break; } } } if (selectedBrowserOffersEngine) { this.updateOpenSearchBadge(); } }, /** * Initializes the urlbar placeholder to the pre-saved engine name. We do this * via a preference, to avoid needing to synchronously init the search service. * * This should be called around the time of DOMContentLoaded, so that it is * initialized quickly before the user sees anything. * * Note: If the preference doesn't exist, we don't do anything as the default * placeholder is a string which doesn't have the engine name. */ initPlaceHolder() { let engineName = Services.prefs.getStringPref( "browser.urlbar.placeholderName", "" ); if (engineName) { // We can do this directly, since we know we're at DOMContentLoaded. this._setURLBarPlaceholder(engineName); } }, /** * Updates the URLBar placeholder for the specified engine, delaying the * update if required. This also saves the current engine name in preferences * for the next restart. * * Note: The engine name will only be displayed for built-in engines, as we * know they should have short names. * * @param {String} engineName The search engine name to use for the update. * @param {Boolean} delayUpdate Set to true, to delay update until the * placeholder is not displayed. */ async _updateURLBarPlaceholder(engineName, delayUpdate = false) { if (!engineName) { throw new Error("Expected an engineName to be specified"); } let defaultEngines = await Services.search.getDefaultEngines(); if ( defaultEngines.some(defaultEngine => defaultEngine.name == engineName) ) { Services.prefs.setStringPref( "browser.urlbar.placeholderName", engineName ); } else { Services.prefs.clearUserPref("browser.urlbar.placeholderName"); // Set the engine name to an empty string for non-default engines, which'll // make sure we display the default placeholder string. engineName = ""; } // Only delay if requested, and we're not displaying text in the URL bar // currently. if (delayUpdate && !gURLBar.value) { // Delays changing the URL Bar placeholder until the user is not going to be // seeing it, e.g. when there is a value entered in the bar, or if there is // a tab switch to a tab which has a url loaded. let placeholderUpdateListener = () => { if (gURLBar.value) { this._setURLBarPlaceholder(engineName); gURLBar.removeEventListener("input", placeholderUpdateListener); gBrowser.tabContainer.removeEventListener( "TabSelect", placeholderUpdateListener ); } }; gURLBar.addEventListener("input", placeholderUpdateListener); gBrowser.tabContainer.addEventListener( "TabSelect", placeholderUpdateListener ); } else { this._setURLBarPlaceholder(engineName); } }, /** * Sets the URLBar placeholder to either something based on the engine name, * or the default placeholder. * * @param {String} name The name of the engine to use, an empty string if to * use the default placeholder. */ _setURLBarPlaceholder(name) { let placeholder; if (name) { placeholder = gBrowserBundle.formatStringFromName("urlbar.placeholder", [ name, ]); } else { placeholder = gURLBar.getAttribute("defaultPlaceholder"); } gURLBar.placeholder = placeholder; }, addEngine(browser, engine, uri) { if (!this._searchInitComplete) { // We haven't finished initialising search yet. This means we can't // call getEngineByName here. Since this is only on start-up and unlikely // to happen in the normal case, we'll just return early rather than // trying to handle it asynchronously. return; } // 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() { BrowserPageActions.addSearchEngine.updateEngines(); 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"); } }, /** * Focuses the search bar if present on the toolbar, or the address bar, * putting it in search mode. Will do so in an existing non-popup browser * window or open a new one if necessary. */ webSearch: function BrowserSearch_webSearch() { if ( window.location.href != AppConstants.BROWSER_CHROME_URL || gURLBar.readOnly ) { let win = getTopWin(true); 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(subject, topic, data) { if (subject == win) { BrowserSearch.webSearch(); Services.obs.removeObserver( observer, "browser-delayed-startup-finished" ); } }; win = window.openDialog( AppConstants.BROWSER_CHROME_URL, "_blank", "chrome,all,dialog=no", "about:blank" ); Services.obs.addObserver(observer, "browser-delayed-startup-finished"); } return; } let focusUrlBarIfSearchFieldIsNotActive = function(aSearchBar) { if (!aSearchBar || document.activeElement != aSearchBar.textbox) { // Limit the results to search suggestions, like the search bar. gURLBar.search(UrlbarTokenizer.RESTRICT.SEARCH); } }; let searchBar = this.searchBar; let placement = CustomizableUI.getPlacementOfWidget("search-container"); let focusSearchBar = () => { searchBar = this.searchBar; searchBar.select(); focusUrlBarIfSearchFieldIsNotActive(searchBar); }; if ( placement && searchBar && ((searchBar.parentNode.getAttribute("overflowedItem") == "true" && placement.area == CustomizableUI.AREA_NAVBAR) || placement.area == CustomizableUI.AREA_FIXED_OVERFLOW_PANEL) ) { 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(searchText, useNewTab, purpose, triggeringPrincipal, csp) { if (!triggeringPrincipal) { throw new Error( "Required argument triggeringPrincipal missing within _loadSearch" ); } let 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, relatedToCurrent: true, triggeringPrincipal, csp, }); return engine; }, /** * 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(terms, triggeringPrincipal, csp) { let engine = BrowserSearch._loadSearch( terms, true, "contextmenu", Services.scriptSecurityManager.createNullPrincipal( triggeringPrincipal.originAttributes ), csp ); if (engine) { BrowserSearch.recordSearchInTelemetry(engine, "contextmenu"); } }, pasteAndSearch(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 = Services.prefs.getIntPref( "browser.link.open_newwindow" ); var where = newWindowPref == 3 ? "tab" : "window"; openTrustedLinkIn(this.searchEnginesURL, where); }, /** * 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(engine, source, details = {}) { try { BrowserUsageTelemetry.recordSearch(gBrowser, 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. */ recordOneoffSearchInTelemetry(engine, source, type) { try { const details = { type, isOneOff: true }; BrowserUsageTelemetry.recordSearch(gBrowser, engine, source, details); } catch (ex) { Cu.reportError(ex); } }, }; XPCOMUtils.defineConstant(this, "BrowserSearch", BrowserSearch); function CreateContainerTabMenu(event) { createUserContextMenu(event, { useAccessKeys: false, showDefaultTab: true, }); } 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")); } }); aParent.addEventListener("DOMMenuItemInactive", function() { XULBrowserWindow.setOverLink(""); }); aParent.hasStatusListener = true; } // Remove old entries if any let children = aParent.children; 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.createXULElement("menuitem"); 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) { // Use list-style-image rather than the image attribute in order to // allow CSS to override this. item.style.listStyleImage = `url(page-icon:${uri})`; } 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.lastElementChild); 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 BrowserDownloadsUI() { if (PrivateBrowsingUtils.isWindowPrivate(window)) { openTrustedLinkIn("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" ); } } /** * Open a new browser window. * * @param {Object} options * { * private: A boolean indicating if the window should be * private * remote: A boolean indicating if the window should run * remote browser tabs or not. If omitted, the window * will choose the profile default state. * fission: A boolean indicating if the window should run * with fission enabled or not. If omitted, the window * will choose the profile default state. * } * @return a reference to the new window. */ function OpenBrowserWindow(options) { var telemetryObj = {}; TelemetryStopwatch.start("FX_NEW_WINDOW_MS", telemetryObj); var handler = Cc["@mozilla.org/browser/clh;1"].getService( Ci.nsIBrowserHandler ); var defaultArgs = handler.defaultArgs; var wintype = document.documentElement.getAttribute("windowtype"); var extraFeatures = ""; if (options && options.private && PrivateBrowsingUtils.enabled) { 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 (options && options.fission) { extraFeatures += ",fission"; } else if (options && options.fission === false) { extraFeatures += ",non-fission"; } // If the window is maximized, we want to skip the animation, since we're // going to be taking up most of the screen anyways, and we want to optimize // for showing the user a useful window as soon as possible. if (window.windowState == window.STATE_MAXIMIZED) { extraFeatures += ",suppressanimation"; } // 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; let charsetArg = "charset=" + DocCharset; // we should "inherit" the charset menu setting in a new window win = window.openDialog( AppConstants.BROWSER_CHROME_URL, "_blank", "chrome,all,dialog=no" + extraFeatures, defaultArgs, charsetArg ); } else { // forget about the charset information. win = window.openDialog( AppConstants.BROWSER_CHROME_URL, "_blank", "chrome,all,dialog=no" + extraFeatures, defaultArgs ); } win.addEventListener( "MozAfterPaint", () => { TelemetryStopwatch.finish("FX_NEW_WINDOW_MS", telemetryObj); if ( Services.prefs.getIntPref("browser.startup.page") == 1 && defaultArgs == HomePage.get() ) { // A notification for when a user has triggered their homepage. This is used // to display a doorhanger explaining that an extension has modified the // homepage, if necessary. Services.obs.notifyObservers(win, "browser-open-homepage-start"); } }, { once: true } ); return win; } /** * 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; let oldVisible = gEditUIVisible; // 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"; const kOpenPopupStates = ["showing", "open"]; if (!gEditUIVisible) { // Now check the edit-controls toolbar buttons. let placement = CustomizableUI.getPlacementOfWidget("edit-controls"); let areaType = placement ? CustomizableUI.getAreaType(placement.area) : ""; if (areaType == CustomizableUI.TYPE_MENU_PANEL) { let customizablePanel = PanelUI.overflowPanel; gEditUIVisible = kOpenPopupStates.includes(customizablePanel.state); } else if ( areaType == CustomizableUI.TYPE_TOOLBAR && window.toolbar.visible ) { // The edit controls are on a toolbar, so they are visible, // unless they're in a panel that isn't visible... if (placement.area == "nav-bar") { let editControls = document.getElementById("edit-controls"); gEditUIVisible = !editControls.hasAttribute("overflowedItem") || kOpenPopupStates.includes( document.getElementById("widget-overflow").state ); } else { gEditUIVisible = true; } } } // Now check the main menu panel if (!gEditUIVisible) { gEditUIVisible = kOpenPopupStates.includes(PanelUI.panel.state); } // No need to update commands if the edit UI visibility has not changed. if (gEditUIVisible == oldVisible) { return; } // 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(); } else { // Otherwise, enable all commands, so that keyboard shortcuts still work, // then lazily determine their actual enabled state when the user presses // a keyboard shortcut. 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) { openTrustedLinkIn(BROWSER_NEW_TAB_URL, "tab", { userContextId: parseInt(event.target.getAttribute("data-usercontextid")), }); } /** * Updates User Context Menu Item UI visibility depending on * privacy.userContext.enabled pref state. */ function updateFileMenuUserContextUIVisibility(id) { let menu = document.getElementById(id); menu.hidden = !Services.prefs.getBoolPref( "privacy.userContext.enabled", false ); // Visibility of File menu item shouldn't change frequently. 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() { function replaceContainerClass(classType, element, value) { let prefix = "identity-" + classType + "-"; if (value && element.classList.contains(prefix + value)) { return; } for (let className of element.classList) { if (className.startsWith(prefix)) { element.classList.remove(className); } } if (value) { element.classList.add(prefix + value); } } let hbox = document.getElementById("userContext-icons"); let userContextId = gBrowser.selectedBrowser.getAttribute("usercontextid"); if (!userContextId) { replaceContainerClass("color", hbox, ""); hbox.hidden = true; return; } let identity = ContextualIdentityService.getPublicIdentityFromId( userContextId ); if (!identity) { replaceContainerClass("color", hbox, ""); hbox.hidden = true; return; } replaceContainerClass("color", hbox, identity.color); let label = document.getElementById("userContext-label"); label.setAttribute( "value", ContextualIdentityService.getUserContextLabel(userContextId) ); let indicator = document.getElementById("userContext-indicator"); replaceContainerClass("icon", indicator, 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, isBusy: false, busyUI: false, QueryInterface: ChromeUtils.generateQI([ "nsIWebProgressListener", "nsIWebProgressListener2", "nsISupportsWeakReference", "nsIXULBrowserWindow", ]), 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 _elementsForTextBasedTypes() { delete this._elementsForTextBasedTypes; return (this._elementsForTextBasedTypes = [ document.getElementById("pageStyleMenu"), document.getElementById("context-viewpartialsource-selection"), ]); }, get _elementsForFind() { delete this._elementsForFind; return (this._elementsForFind = [ document.getElementById("cmd_find"), document.getElementById("cmd_findAgain"), document.getElementById("cmd_findPrevious"), ]); }, get _elementsForViewSource() { delete this._elementsForViewSource; return (this._elementsForViewSource = [ document.getElementById("context-viewsource"), document.getElementById("View:PageSource"), ]); }, forceInitialBrowserNonRemote(aOpener) { gBrowser.updateBrowserRemoteness(gBrowser.initialBrowser, { opener: aOpener, remoteType: E10SUtils.NOT_REMOTE, }); }, setDefaultStatus(status) { this.defaultStatus = status; StatusPanel.update(); }, setOverLink(url) { if (url) { url = Services.textToSubURI.unEscapeURIForUI(url); // 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 (UrlbarPrefs.get("trimURLs")) { url = BrowserUtils.trimURL(url); } } this.overLink = url; LinkTargetDisplay.update(); }, showTooltip(x, y, tooltip, direction, browser) { if ( Cc["@mozilla.org/widget/dragservice;1"] .getService(Ci.nsIDragService) .getCurrentSession() ) { return; } // The x,y coordinates are relative to the element using // the chrome zoom level. let elt = document.getElementById("remoteBrowserTooltip"); elt.label = tooltip; elt.style.direction = direction; let screenX; let screenY; if (browser instanceof XULElement) { // XUL element such as has the `screenX` and `screenY` fields. // https://searchfox.org/mozilla-central/source/dom/webidl/XULElement.webidl screenX = browser.screenX; screenY = browser.screenY; } else { // In case of HTML element such as