1 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
2 * This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 var { XPCOMUtils } = ChromeUtils.importESModule(
7 "resource://gre/modules/XPCOMUtils.sys.mjs"
9 var { AppConstants } = ChromeUtils.importESModule(
10 "resource://gre/modules/AppConstants.sys.mjs"
12 ChromeUtils.import("resource://gre/modules/NotificationDB.jsm");
14 // lazy module getters
16 ChromeUtils.defineESModuleGetters(this, {
17 AboutReaderParent: "resource:///actors/AboutReaderParent.sys.mjs",
18 BrowserSearchTelemetry: "resource:///modules/BrowserSearchTelemetry.sys.mjs",
19 BrowserTelemetryUtils: "resource://gre/modules/BrowserTelemetryUtils.sys.mjs",
20 Color: "resource://gre/modules/Color.sys.mjs",
22 "resource://devtools/shared/security/DevToolsSocketStatus.sys.mjs",
23 Deprecated: "resource://gre/modules/Deprecated.sys.mjs",
24 E10SUtils: "resource://gre/modules/E10SUtils.sys.mjs",
25 FirefoxViewNotificationManager:
26 "resource:///modules/firefox-view-notification-manager.sys.mjs",
27 LightweightThemeConsumer:
28 "resource://gre/modules/LightweightThemeConsumer.sys.mjs",
29 Log: "resource://gre/modules/Log.sys.mjs",
30 NewTabUtils: "resource://gre/modules/NewTabUtils.sys.mjs",
31 PictureInPicture: "resource://gre/modules/PictureInPicture.sys.mjs",
32 PlacesTransactions: "resource://gre/modules/PlacesTransactions.sys.mjs",
33 PlacesUIUtils: "resource:///modules/PlacesUIUtils.sys.mjs",
34 PlacesUtils: "resource://gre/modules/PlacesUtils.sys.mjs",
35 PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.sys.mjs",
36 PromiseUtils: "resource://gre/modules/PromiseUtils.sys.mjs",
37 ScreenshotsUtils: "resource:///modules/ScreenshotsUtils.sys.mjs",
38 ShortcutUtils: "resource://gre/modules/ShortcutUtils.sys.mjs",
39 SubDialog: "resource://gre/modules/SubDialog.sys.mjs",
40 SubDialogManager: "resource://gre/modules/SubDialog.sys.mjs",
42 "resource:///modules/firefox-view-tabs-setup-manager.sys.mjs",
43 UpdateUtils: "resource://gre/modules/UpdateUtils.sys.mjs",
44 UrlbarInput: "resource:///modules/UrlbarInput.sys.mjs",
45 UrlbarPrefs: "resource:///modules/UrlbarPrefs.sys.mjs",
46 UrlbarProviderSearchTips:
47 "resource:///modules/UrlbarProviderSearchTips.sys.mjs",
48 UrlbarTokenizer: "resource:///modules/UrlbarTokenizer.sys.mjs",
49 UrlbarUtils: "resource:///modules/UrlbarUtils.sys.mjs",
50 UrlbarValueFormatter: "resource:///modules/UrlbarValueFormatter.sys.mjs",
51 WebsiteFilter: "resource:///modules/policies/WebsiteFilter.sys.mjs",
54 XPCOMUtils.defineLazyModuleGetters(this, {
55 AboutNewTab: "resource:///modules/AboutNewTab.jsm",
56 AddonManager: "resource://gre/modules/AddonManager.jsm",
57 AMTelemetry: "resource://gre/modules/AddonManager.jsm",
58 NewTabPagePreloading: "resource:///modules/NewTabPagePreloading.jsm",
59 BrowserUsageTelemetry: "resource:///modules/BrowserUsageTelemetry.jsm",
60 BrowserUIUtils: "resource:///modules/BrowserUIUtils.jsm",
61 BrowserWindowTracker: "resource:///modules/BrowserWindowTracker.jsm",
62 CFRPageActions: "resource://activity-stream/lib/CFRPageActions.jsm",
63 ContextualIdentityService:
64 "resource://gre/modules/ContextualIdentityService.jsm",
65 CustomizableUI: "resource:///modules/CustomizableUI.jsm",
66 DownloadsCommon: "resource:///modules/DownloadsCommon.jsm",
67 DownloadUtils: "resource://gre/modules/DownloadUtils.jsm",
68 NimbusFeatures: "resource://nimbus/ExperimentAPI.jsm",
69 ExtensionsUI: "resource:///modules/ExtensionsUI.jsm",
70 HomePage: "resource:///modules/HomePage.jsm",
71 LoginHelper: "resource://gre/modules/LoginHelper.jsm",
72 LoginManagerParent: "resource://gre/modules/LoginManagerParent.jsm",
73 MigrationUtils: "resource:///modules/MigrationUtils.jsm",
74 NetUtil: "resource://gre/modules/NetUtil.jsm",
75 OpenInTabsUtils: "resource:///modules/OpenInTabsUtils.jsm",
76 PageActions: "resource:///modules/PageActions.jsm",
77 PageThumbs: "resource://gre/modules/PageThumbs.jsm",
78 PanelMultiView: "resource:///modules/PanelMultiView.jsm",
79 PanelView: "resource:///modules/PanelMultiView.jsm",
80 PluralForm: "resource://gre/modules/PluralForm.jsm",
81 Pocket: "chrome://pocket/content/Pocket.jsm",
82 ProcessHangMonitor: "resource:///modules/ProcessHangMonitor.jsm",
83 PromptUtils: "resource://gre/modules/SharedPromptUtils.jsm",
85 // TODO (Bug 1529552): Remove once old urlbar code goes away.
86 ReaderMode: "resource://gre/modules/ReaderMode.jsm",
88 RFPHelper: "resource://gre/modules/RFPHelper.jsm",
89 SafeBrowsing: "resource://gre/modules/SafeBrowsing.jsm",
90 Sanitizer: "resource:///modules/Sanitizer.jsm",
91 SaveToPocket: "chrome://pocket/content/SaveToPocket.jsm",
92 SessionStartup: "resource:///modules/sessionstore/SessionStartup.jsm",
93 SessionStore: "resource:///modules/sessionstore/SessionStore.jsm",
94 SiteDataManager: "resource:///modules/SiteDataManager.jsm",
95 SitePermissions: "resource:///modules/SitePermissions.jsm",
96 TabModalPrompt: "chrome://global/content/tabprompts.jsm",
97 TabCrashHandler: "resource:///modules/ContentCrashHandlers.jsm",
98 TelemetryEnvironment: "resource://gre/modules/TelemetryEnvironment.jsm",
99 Translation: "resource:///modules/translation/TranslationParent.jsm",
100 UITour: "resource:///modules/UITour.jsm",
101 Weave: "resource://services-sync/main.js",
102 WebNavigationFrames: "resource://gre/modules/WebNavigationFrames.jsm",
103 webrtcUI: "resource:///modules/webrtcUI.jsm",
104 ZoomUI: "resource:///modules/ZoomUI.jsm",
107 XPCOMUtils.defineLazyGetter(this, "fxAccounts", () => {
108 return ChromeUtils.import(
109 "resource://gre/modules/FxAccounts.jsm"
110 ).getFxAccountsSingleton();
113 if (AppConstants.MOZ_CRASHREPORTER) {
114 ChromeUtils.defineModuleGetter(
116 "PluginCrashReporter",
117 "resource:///modules/ContentCrashHandlers.jsm"
121 XPCOMUtils.defineLazyScriptGetter(
124 "chrome://browser/content/places/treeView.js"
126 XPCOMUtils.defineLazyScriptGetter(
128 ["PlacesInsertionPoint", "PlacesController", "PlacesControllerDragHelper"],
129 "chrome://browser/content/places/controller.js"
131 XPCOMUtils.defineLazyScriptGetter(
134 "chrome://global/content/printUtils.js"
136 XPCOMUtils.defineLazyScriptGetter(
139 "chrome://global/content/viewZoomOverlay.js"
141 XPCOMUtils.defineLazyScriptGetter(
144 "chrome://browser/content/browser-fullZoom.js"
146 XPCOMUtils.defineLazyScriptGetter(
149 "chrome://browser/content/customizableui/panelUI.js"
151 XPCOMUtils.defineLazyScriptGetter(
154 "chrome://global/content/viewSourceUtils.js"
156 XPCOMUtils.defineLazyScriptGetter(
159 "chrome://browser/content/browser-allTabsMenu.js"
161 XPCOMUtils.defineLazyScriptGetter(
165 "gExtensionsNotifications",
166 "gUnifiedExtensions",
167 "gXPInstallObserver",
169 "chrome://browser/content/browser-addons.js"
171 XPCOMUtils.defineLazyScriptGetter(
174 "chrome://browser/content/browser-ctrlTab.js"
176 XPCOMUtils.defineLazyScriptGetter(
178 ["CustomizationHandler", "AutoHideMenubar"],
179 "chrome://browser/content/browser-customization.js"
181 XPCOMUtils.defineLazyScriptGetter(
183 ["PointerLock", "FullScreen"],
184 "chrome://browser/content/browser-fullScreenAndPointerLock.js"
186 XPCOMUtils.defineLazyScriptGetter(
189 "chrome://browser/content/browser-siteIdentity.js"
191 XPCOMUtils.defineLazyScriptGetter(
194 "chrome://browser/content/browser-sitePermissionPanel.js"
196 XPCOMUtils.defineLazyScriptGetter(
198 "gProtectionsHandler",
199 "chrome://browser/content/browser-siteProtections.js"
201 XPCOMUtils.defineLazyScriptGetter(
203 ["gGestureSupport", "gHistorySwipeAnimation"],
204 "chrome://browser/content/browser-gestureSupport.js"
206 XPCOMUtils.defineLazyScriptGetter(
209 "chrome://browser/content/browser-safebrowsing.js"
211 XPCOMUtils.defineLazyScriptGetter(
214 "chrome://browser/content/browser-sync.js"
216 XPCOMUtils.defineLazyScriptGetter(
218 "gBrowserThumbnails",
219 "chrome://browser/content/browser-thumbnails.js"
221 XPCOMUtils.defineLazyScriptGetter(
223 ["openContextMenu", "nsContextMenu"],
224 "chrome://browser/content/nsContextMenu.js"
226 XPCOMUtils.defineLazyScriptGetter(
230 "DownloadsOverlayLoader",
233 "DownloadsViewController",
236 "DownloadsBlockedSubview",
238 "chrome://browser/content/downloads/downloads.js"
240 XPCOMUtils.defineLazyScriptGetter(
242 ["DownloadsButton", "DownloadsIndicatorView"],
243 "chrome://browser/content/downloads/indicator.js"
245 XPCOMUtils.defineLazyScriptGetter(
248 "chrome://browser/content/places/instantEditBookmark.js"
250 XPCOMUtils.defineLazyScriptGetter(
253 "chrome://browser/content/browser-graphics-utils.js"
255 XPCOMUtils.defineLazyScriptGetter(
258 "chrome://pocket/content/pktUI.js"
260 XPCOMUtils.defineLazyScriptGetter(
262 "ToolbarKeyboardNavigator",
263 "chrome://browser/content/browser-toolbarKeyNav.js"
265 XPCOMUtils.defineLazyScriptGetter(
268 "chrome://browser/content/browser-a11yUtils.js"
270 XPCOMUtils.defineLazyScriptGetter(
273 "chrome://browser/content/browser-webrtc.js"
276 // lazy service getters
278 XPCOMUtils.defineLazyServiceGetters(this, {
279 ContentPrefService2: [
280 "@mozilla.org/content-pref/service;1",
281 "nsIContentPrefService2",
284 "@mozilla.org/url-classifier/dbservice;1",
287 Favicons: ["@mozilla.org/browser/favicon-service;1", "nsIFaviconService"],
288 gDNSService: ["@mozilla.org/network/dns-service;1", "nsIDNSService"],
289 WindowsUIUtils: ["@mozilla.org/windows-ui-utils;1", "nsIWindowsUIUtils"],
290 BrowserHandler: ["@mozilla.org/browser/clh;1", "nsIBrowserHandler"],
293 if (AppConstants.ENABLE_WEBDRIVER) {
294 XPCOMUtils.defineLazyServiceGetter(
297 "@mozilla.org/remote/marionette;1",
301 XPCOMUtils.defineLazyServiceGetter(
304 "@mozilla.org/remote/agent;1",
308 this.Marionette = { running: false };
309 this.RemoteAgent = { running: false };
312 XPCOMUtils.defineLazyGetter(this, "RTL_UI", () => {
313 return Services.locale.isAppLocaleRTL;
316 XPCOMUtils.defineLazyGetter(this, "gBrandBundle", () => {
317 return Services.strings.createBundle(
318 "chrome://branding/locale/brand.properties"
322 XPCOMUtils.defineLazyGetter(this, "gBrowserBundle", () => {
323 return Services.strings.createBundle(
324 "chrome://browser/locale/browser.properties"
328 XPCOMUtils.defineLazyGetter(this, "gTabBrowserBundle", () => {
329 return Services.strings.createBundle(
330 "chrome://browser/locale/tabbrowser.properties"
334 XPCOMUtils.defineLazyGetter(this, "gCustomizeMode", () => {
335 let { CustomizeMode } = ChromeUtils.import(
336 "resource:///modules/CustomizeMode.jsm"
338 return new CustomizeMode(window);
341 XPCOMUtils.defineLazyGetter(this, "gNavToolbox", () => {
342 return document.getElementById("navigator-toolbox");
345 XPCOMUtils.defineLazyGetter(this, "gURLBar", () => {
346 let urlbar = new UrlbarInput({
347 textbox: document.getElementById("urlbar"),
348 eventTelemetryCategory: "urlbar",
351 let beforeFocusOrSelect = event => {
352 // In customize mode, the url bar is disabled. If a new tab is opened or the
353 // user switches to a different tab, this function gets called before we've
354 // finished leaving customize mode, and the url bar will still be disabled.
355 // We can't focus it when it's disabled, so we need to re-run ourselves when
356 // we've finished leaving customize mode.
358 CustomizationHandler.isCustomizing() ||
359 CustomizationHandler.isExitingCustomizeMode
361 gNavToolbox.addEventListener(
362 "aftercustomization",
364 if (event.type == "beforeselect") {
374 event.preventDefault();
378 if (window.fullScreen) {
379 FullScreen.showNavToolbox();
382 urlbar.addEventListener("beforefocus", beforeFocusOrSelect);
383 urlbar.addEventListener("beforeselect", beforeFocusOrSelect);
388 XPCOMUtils.defineLazyGetter(this, "ReferrerInfo", () =>
389 Components.Constructor(
390 "@mozilla.org/referrer-info;1",
396 // High priority notification bars shown at the top of the window.
397 XPCOMUtils.defineLazyGetter(this, "gNotificationBox", () => {
398 return new MozElements.NotificationBox(element => {
399 element.classList.add("global-notificationbox");
400 element.setAttribute("notificationside", "top");
401 element.setAttribute("prepend-notifications", true);
402 const tabNotifications = document.getElementById("tab-notification-deck");
403 gNavToolbox.insertBefore(element, tabNotifications);
407 XPCOMUtils.defineLazyGetter(this, "InlineSpellCheckerUI", () => {
408 let { InlineSpellChecker } = ChromeUtils.importESModule(
409 "resource://gre/modules/InlineSpellChecker.sys.mjs"
411 return new InlineSpellChecker();
414 XPCOMUtils.defineLazyGetter(this, "PopupNotifications", () => {
415 // eslint-disable-next-line no-shadow
416 let { PopupNotifications } = ChromeUtils.importESModule(
417 "resource://gre/modules/PopupNotifications.sys.mjs"
420 // Hide all PopupNotifications while the URL is being edited and the address
421 // bar has focus or while async tab switching, including the virtual focus in
422 // the results popup.
423 let shouldSuppress = () =>
424 (gURLBar.getAttribute("pageproxystate") != "valid" &&
425 (gURLBar.focused || gBrowser.selectedBrowser._awaitingSetURI)) ||
426 shouldSuppressPopupNotifications();
427 return new PopupNotifications(
429 document.getElementById("notification-popup"),
430 document.getElementById("notification-popup-box"),
439 XPCOMUtils.defineLazyGetter(this, "MacUserActivityUpdater", () => {
440 if (AppConstants.platform != "macosx") {
444 return Cc["@mozilla.org/widget/macuseractivityupdater;1"].getService(
445 Ci.nsIMacUserActivityUpdater
449 XPCOMUtils.defineLazyGetter(this, "Win7Features", () => {
450 if (AppConstants.platform != "win") {
454 const WINTASKBAR_CONTRACTID = "@mozilla.org/windows-taskbar;1";
456 WINTASKBAR_CONTRACTID in Cc &&
457 Cc[WINTASKBAR_CONTRACTID].getService(Ci.nsIWinTaskbar).available
459 let { AeroPeek } = ChromeUtils.import(
460 "resource:///modules/WindowsPreviewPerTab.jsm"
464 AeroPeek.onOpenWindow(window);
465 this.handledOpening = true;
468 if (this.handledOpening) {
469 AeroPeek.onCloseWindow(window);
472 handledOpening: false,
478 XPCOMUtils.defineLazyPreferenceGetter(
480 "gToolbarKeyNavEnabled",
481 "browser.toolbars.keyboard_navigation",
483 (aPref, aOldVal, aNewVal) => {
488 ToolbarKeyboardNavigator.init();
490 ToolbarKeyboardNavigator.uninit();
495 XPCOMUtils.defineLazyPreferenceGetter(
497 "gBookmarksToolbarVisibility",
498 "browser.toolbars.bookmarks.visibility",
502 XPCOMUtils.defineLazyPreferenceGetter(
504 "gFxaToolbarEnabled",
505 "identity.fxaccounts.toolbar.enabled",
507 (aPref, aOldVal, aNewVal) => {
508 updateFxaToolbarMenu(aNewVal);
512 XPCOMUtils.defineLazyPreferenceGetter(
514 "gFxaToolbarAccessed",
515 "identity.fxaccounts.toolbar.accessed",
517 (aPref, aOldVal, aNewVal) => {
518 updateFxaToolbarMenu(gFxaToolbarEnabled);
522 XPCOMUtils.defineLazyPreferenceGetter(
524 "gAddonAbuseReportEnabled",
525 "extensions.abuseReport.enabled",
529 XPCOMUtils.defineLazyPreferenceGetter(
532 "browser.download.alwaysOpenPanel",
536 XPCOMUtils.defineLazyPreferenceGetter(
538 "gScreenshotsDisabled",
539 "extensions.screenshots.disabled",
542 Services.obs.notifyObservers(
544 "toggle-screenshot-disable",
545 gScreenshots.shouldScreenshotsButtonBeDisabled()
550 XPCOMUtils.defineLazyPreferenceGetter(
552 "gScreenshotsComponentEnabled",
553 "screenshots.browser.component.enabled",
556 Services.obs.notifyObservers(
558 "toggle-screenshot-disable",
559 gScreenshots.shouldScreenshotsButtonBeDisabled()
564 customElements.setElementCreationCallback("translation-notification", () => {
565 Services.scriptloader.loadSubScript(
566 "chrome://browser/content/translation-notification.js",
571 customElements.setElementCreationCallback("screenshots-buttons", () => {
572 Services.scriptloader.loadSubScript(
573 "chrome://browser/content/screenshots/screenshots-buttons.js",
579 var gContextMenu = null; // nsContextMenu instance
580 var gMultiProcessBrowser = window.docShell.QueryInterface(Ci.nsILoadContext)
582 var gFissionBrowser = window.docShell.QueryInterface(Ci.nsILoadContext)
585 var gBrowserAllowScriptsToCloseInitialTabs = false;
587 if (AppConstants.platform != "macosx") {
588 var gEditUIVisible = true;
591 Object.defineProperty(this, "gReduceMotion", {
594 return typeof gReduceMotionOverride == "boolean"
595 ? gReduceMotionOverride
596 : gReduceMotionSetting;
599 // Reduce motion during startup. The setting will be reset later.
600 let gReduceMotionSetting = true;
601 // This is for tests to set.
602 var gReduceMotionOverride;
604 // Smart getter for the findbar. If you don't wish to force the creation of
605 // the findbar, check gFindBarInitialized first.
607 Object.defineProperty(this, "gFindBar", {
610 return gBrowser.getCachedFindBar();
614 Object.defineProperty(this, "gFindBarInitialized", {
617 return gBrowser.isFindBarInitialized();
621 Object.defineProperty(this, "gFindBarPromise", {
624 return gBrowser.getFindBar();
628 function shouldSuppressPopupNotifications() {
629 // We have to hide notifications explicitly when the window is
630 // minimized because of the effects of the "noautohide" attribute on Linux.
631 // This can be removed once bug 545265 and bug 1320361 are fixed.
632 // Hide popup notifications when system tab prompts are shown so they
633 // don't cover up the prompt.
635 window.windowState == window.STATE_MINIMIZED ||
636 gBrowser?.selectedBrowser.hasAttribute("tabmodalChromePromptShowing") ||
637 gBrowser?.selectedBrowser.hasAttribute("tabDialogShowing") ||
642 async function gLazyFindCommand(cmd, ...args) {
643 let fb = await gFindBarPromise;
644 // We could be closed by now, or the tab with XBL binding could have gone away:
646 fb[cmd].apply(fb, args);
651 "about:home": "chrome://branding/content/icon32.png",
652 "about:newtab": "chrome://branding/content/icon32.png",
653 "about:welcome": "chrome://branding/content/icon32.png",
654 "about:privatebrowsing": "chrome://browser/skin/privatebrowsing/favicon.svg",
657 var gInitialPages = [
662 "about:privatebrowsing",
663 "about:sessionrestore",
668 function isInitialPage(url) {
669 if (!(url instanceof Ci.nsIURI)) {
671 url = Services.io.newURI(url);
677 let nonQuery = url.prePath + url.filePath;
678 return gInitialPages.includes(nonQuery) || nonQuery == BROWSER_NEW_TAB_URL;
681 function browserWindows() {
682 return Services.wm.getEnumerator("navigator:browser");
685 // This is a stringbundle-like interface to gBrowserBundle, formerly a getter for
686 // the "bundle_browser" element.
687 var gNavigatorBundle = {
689 return gBrowserBundle.GetStringFromName(key);
691 getFormattedString(key, array) {
692 return gBrowserBundle.formatStringFromName(key, array);
697 shouldScreenshotsButtonBeDisabled() {
698 // About pages other than about:reader are not currently supported by
699 // the screenshots extension (see Bug 1620992).
700 let uri = gBrowser.currentURI;
701 let shouldBeDisabled =
702 gScreenshotsDisabled ||
703 (!gScreenshotsComponentEnabled &&
704 uri.scheme === "about" &&
705 !uri.spec.startsWith("about:reader"));
707 return shouldBeDisabled;
711 function updateFxaToolbarMenu(enable, isInitialUpdate = false) {
712 // We only show the Firefox Account toolbar menu if the feature is enabled and
713 // if sync is enabled.
714 const syncEnabled = Services.prefs.getBoolPref(
715 "identity.fxaccounts.enabled",
719 const mainWindowEl = document.documentElement;
720 const fxaPanelEl = PanelMultiView.getViewNode(document, "PanelUI-fxa");
722 // To minimize the toolbar button flickering or appearing/disappearing during startup,
723 // we use this pref to anticipate the likely FxA status.
724 const statusGuess = !!Services.prefs.getStringPref(
725 "identity.fxaccounts.account.device.name",
728 mainWindowEl.setAttribute(
730 statusGuess ? "signed_in" : "not_configured"
733 fxaPanelEl.addEventListener("ViewShowing", gSync.updateSendToDeviceTitle);
735 Services.telemetry.setEventRecordingEnabled("fxa_app_menu", true);
737 if (enable && syncEnabled) {
738 mainWindowEl.setAttribute("fxatoolbarmenu", "visible");
740 // We have to manually update the sync state UI when toggling the FxA toolbar
741 // because it could show an invalid icon if the user is logged in and no sync
742 // event was performed yet.
743 if (!isInitialUpdate) {
744 gSync.maybeUpdateUIState();
747 Services.telemetry.setEventRecordingEnabled("fxa_avatar_menu", true);
749 mainWindowEl.removeAttribute("fxatoolbarmenu");
753 function UpdateBackForwardCommands(aWebNavigation) {
754 var backCommand = document.getElementById("Browser:Back");
755 var forwardCommand = document.getElementById("Browser:Forward");
757 // Avoid setting attributes on commands if the value hasn't changed!
758 // Remember, guys, setting attributes on elements is expensive! They
759 // get inherited into anonymous content, broadcast to other widgets, etc.!
760 // Don't do it if the value hasn't changed! - dwh
762 var backDisabled = backCommand.hasAttribute("disabled");
763 var forwardDisabled = forwardCommand.hasAttribute("disabled");
764 if (backDisabled == aWebNavigation.canGoBack) {
766 backCommand.removeAttribute("disabled");
768 backCommand.setAttribute("disabled", true);
772 if (forwardDisabled == aWebNavigation.canGoForward) {
773 if (forwardDisabled) {
774 forwardCommand.removeAttribute("disabled");
776 forwardCommand.setAttribute("disabled", true);
782 * Click-and-Hold implementation for the Back and Forward buttons
783 * XXXmano: should this live in toolbarbutton.js?
785 function SetClickAndHoldHandlers() {
786 // Bug 414797: Clone the back/forward buttons' context menu into both buttons.
787 let popup = document.getElementById("backForwardMenu").cloneNode(true);
788 popup.removeAttribute("id");
789 // Prevent the back/forward buttons' context attributes from being inherited.
790 popup.setAttribute("context", "");
792 let backButton = document.getElementById("back-button");
793 backButton.setAttribute("type", "menu");
794 backButton.prepend(popup);
795 gClickAndHoldListenersOnElement.add(backButton);
797 let forwardButton = document.getElementById("forward-button");
798 popup = popup.cloneNode(true);
799 forwardButton.setAttribute("type", "menu");
800 forwardButton.prepend(popup);
801 gClickAndHoldListenersOnElement.add(forwardButton);
804 const gClickAndHoldListenersOnElement = {
807 _mousedownHandler(aEvent) {
809 aEvent.button != 0 ||
810 aEvent.currentTarget.open ||
811 aEvent.currentTarget.disabled
816 // Prevent the menupopup from opening immediately
817 aEvent.currentTarget.menupopup.hidden = true;
819 aEvent.currentTarget.addEventListener("mouseout", this);
820 aEvent.currentTarget.addEventListener("mouseup", this);
822 aEvent.currentTarget,
823 setTimeout(b => this._openMenu(b), 500, aEvent.currentTarget)
827 _clickHandler(aEvent) {
829 aEvent.button == 0 &&
830 aEvent.target == aEvent.currentTarget &&
831 !aEvent.currentTarget.open &&
832 !aEvent.currentTarget.disabled
834 let cmdEvent = document.createEvent("xulcommandevent");
835 cmdEvent.initCommandEvent(
847 aEvent.mozInputSource
849 aEvent.currentTarget.dispatchEvent(cmdEvent);
851 // This is here to cancel the XUL default event
852 // dom.click() triggers a command even if there is a click handler
853 // however this can now be prevented with preventDefault().
854 aEvent.preventDefault();
859 this._cancelHold(aButton);
860 aButton.firstElementChild.hidden = false;
864 _mouseoutHandler(aEvent) {
865 let buttonRect = aEvent.currentTarget.getBoundingClientRect();
867 aEvent.clientX >= buttonRect.left &&
868 aEvent.clientX <= buttonRect.right &&
869 aEvent.clientY >= buttonRect.bottom
871 this._openMenu(aEvent.currentTarget);
873 this._cancelHold(aEvent.currentTarget);
877 _mouseupHandler(aEvent) {
878 this._cancelHold(aEvent.currentTarget);
881 _cancelHold(aButton) {
882 clearTimeout(this._timers.get(aButton));
883 aButton.removeEventListener("mouseout", this);
884 aButton.removeEventListener("mouseup", this);
887 _keypressHandler(aEvent) {
888 if (aEvent.key == " " || aEvent.key == "Enter") {
889 // Normally, command events get fired for keyboard activation. However,
890 // we've set type="menu", so that doesn't happen. Handle this the same
891 // way we handle clicks.
892 aEvent.target.click();
899 this._mouseoutHandler(e);
902 this._mousedownHandler(e);
905 this._clickHandler(e);
908 this._mouseupHandler(e);
911 this._keypressHandler(e);
917 aButton.removeEventListener("mousedown", this, true);
918 aButton.removeEventListener("click", this, true);
919 aButton.removeEventListener("keypress", this, true);
923 this._timers.delete(aElm);
925 aElm.addEventListener("mousedown", this, true);
926 aElm.addEventListener("click", this, true);
927 aElm.addEventListener("keypress", this, true);
931 const gSessionHistoryObserver = {
932 observe(subject, topic, data) {
933 if (topic != "browser:purge-session-history") {
937 var backCommand = document.getElementById("Browser:Back");
938 backCommand.setAttribute("disabled", "true");
939 var fwdCommand = document.getElementById("Browser:Forward");
940 fwdCommand.setAttribute("disabled", "true");
942 // Clear undo history of the URL bar
943 gURLBar.editor.clearUndoRedo();
947 const gStoragePressureObserver = {
948 _lastNotificationTime: -1,
950 async observe(subject, topic, data) {
951 if (topic != "QuotaManager::StoragePressure") {
955 const NOTIFICATION_VALUE = "storage-pressure-notification";
956 if (gNotificationBox.getNotificationWithValue(NOTIFICATION_VALUE)) {
957 // Do not display the 2nd notification when there is already one
961 // Don't display notification twice within the given interval.
963 // - not to annoy user
964 // - give user some time to clean space.
965 // Even user sees notification and starts acting, it still takes some time.
966 const MIN_NOTIFICATION_INTERVAL_MS = Services.prefs.getIntPref(
967 "browser.storageManager.pressureNotification.minIntervalMS"
969 let duration = Date.now() - this._lastNotificationTime;
970 if (duration <= MIN_NOTIFICATION_INTERVAL_MS) {
973 this._lastNotificationTime = Date.now();
975 MozXULElement.insertFTLIfNeeded("branding/brand.ftl");
976 MozXULElement.insertFTLIfNeeded("browser/preferences/preferences.ftl");
978 const BYTES_IN_GIGABYTE = 1073741824;
979 const USAGE_THRESHOLD_BYTES =
981 Services.prefs.getIntPref(
982 "browser.storageManager.pressureNotification.usageThresholdGB"
984 let messageFragment = document.createDocumentFragment();
985 let message = document.createElement("span");
987 let buttons = [{ supportPage: "storage-permissions" }];
988 let usage = subject.QueryInterface(Ci.nsISupportsPRUint64).data;
989 if (usage < USAGE_THRESHOLD_BYTES) {
990 // The firefox-used space < 5GB, then warn user to free some disk space.
991 // This is because this usage is small and not the main cause for space issue.
992 // In order to avoid the bad and wrong impression among users that
993 // firefox eats disk space a lot, indicate users to clean up other disk space.
994 document.l10n.setAttributes(message, "space-alert-under-5gb-message2");
996 // The firefox-used space >= 5GB, then guide users to about:preferences
997 // to clear some data stored on firefox by websites.
998 document.l10n.setAttributes(message, "space-alert-over-5gb-message2");
1000 "l10n-id": "space-alert-over-5gb-settings-button",
1001 callback(notificationBar, button) {
1002 // The advanced subpanes are only supported in the old organization, which will
1003 // be removed by bug 1349689.
1004 openPreferences("privacy-sitedata");
1008 messageFragment.appendChild(message);
1010 gNotificationBox.appendNotification(
1013 label: messageFragment,
1014 priority: gNotificationBox.PRIORITY_WARNING_HIGH,
1019 // This seems to be necessary to get the buttons to display correctly
1020 // See: https://bugzilla.mozilla.org/show_bug.cgi?id=1504216
1021 document.l10n.translateFragment(gNotificationBox.currentNotification);
1025 var gPopupBlockerObserver = {
1026 handleEvent(aEvent) {
1027 if (aEvent.originalTarget != gBrowser.selectedBrowser) {
1031 gPermissionPanel.refreshPermissionIcons();
1033 let popupCount = gBrowser.selectedBrowser.popupBlocker.getBlockedPopupCount();
1036 // Hide the notification box (if it's visible).
1037 let notificationBox = gBrowser.getNotificationBox();
1038 let notification = notificationBox.getNotificationWithValue(
1042 notificationBox.removeNotification(notification, false);
1047 // Only show the notification again if we've not already shown it. Since
1048 // notifications are per-browser, we don't need to worry about re-adding
1050 if (gBrowser.selectedBrowser.popupBlocker.shouldShowNotification) {
1051 if (Services.prefs.getBoolPref("privacy.popups.showBrowserMessage")) {
1052 var brandBundle = document.getElementById("bundle_brand");
1053 var brandShortName = brandBundle.getString("brandShortName");
1056 AppConstants.platform == "win"
1057 ? "popupWarningButton"
1058 : "popupWarningButtonUnix";
1060 var popupButtonText = gNavigatorBundle.getString(stringKey);
1061 var popupButtonAccesskey = gNavigatorBundle.getString(
1062 stringKey + ".accesskey"
1066 if (popupCount < this.maxReportedPopups) {
1067 messageBase = gNavigatorBundle.getString("popupWarning.message");
1069 messageBase = gNavigatorBundle.getString(
1070 "popupWarning.exceeded.message"
1074 var message = PluralForm.get(popupCount, messageBase)
1075 .replace("#1", brandShortName)
1076 .replace("#2", popupCount);
1078 let notificationBox = gBrowser.getNotificationBox();
1079 let notification = notificationBox.getNotificationWithValue(
1083 notification.label = message;
1087 label: popupButtonText,
1088 accessKey: popupButtonAccesskey,
1089 popup: "blockedPopupOptions",
1094 const priority = notificationBox.PRIORITY_INFO_MEDIUM;
1095 notificationBox.appendNotification(
1099 image: "chrome://browser/skin/notification-icons/popup.svg",
1107 // Record the fact that we've reported this blocked popup, so we don't
1109 gBrowser.selectedBrowser.popupBlocker.didShowNotification();
1113 toggleAllowPopupsForSite(aEvent) {
1114 var pm = Services.perms;
1115 var shouldBlock = aEvent.target.getAttribute("block") == "true";
1116 var perm = shouldBlock ? pm.DENY_ACTION : pm.ALLOW_ACTION;
1117 pm.addFromPrincipal(gBrowser.contentPrincipal, "popup", perm);
1120 gBrowser.selectedBrowser.popupBlocker.unblockAllPopups();
1123 gBrowser.getNotificationBox().removeCurrentNotification();
1126 fillPopupList(aEvent) {
1127 // XXXben - rather than using |currentURI| here, which breaks down on multi-framed sites
1128 // we should really walk the blockedPopups and create a list of "allow for <host>"
1129 // menuitems for the common subset of hosts present in the report, this will
1130 // make us frame-safe.
1132 // XXXjst - Note that when this is fixed to work with multi-framed sites,
1133 // also back out the fix for bug 343772 where
1134 // nsGlobalWindow::CheckOpenAllow() was changed to also
1135 // check if the top window's location is allow-listed.
1136 let browser = gBrowser.selectedBrowser;
1137 var uriOrPrincipal = browser.contentPrincipal.isContentPrincipal
1138 ? browser.contentPrincipal
1139 : browser.currentURI;
1140 var blockedPopupAllowSite = document.getElementById(
1141 "blockedPopupAllowSite"
1144 blockedPopupAllowSite.removeAttribute("hidden");
1145 let uriHost = uriOrPrincipal.asciiHost
1146 ? uriOrPrincipal.host
1147 : uriOrPrincipal.spec;
1148 var pm = Services.perms;
1150 pm.testPermissionFromPrincipal(browser.contentPrincipal, "popup") ==
1153 // Offer an item to block popups for this site, if an allow-list entry exists
1155 document.l10n.setAttributes(
1156 blockedPopupAllowSite,
1157 "popups-infobar-block",
1160 blockedPopupAllowSite.setAttribute("block", "true");
1162 // Offer an item to allow popups for this site
1163 document.l10n.setAttributes(
1164 blockedPopupAllowSite,
1165 "popups-infobar-allow",
1168 blockedPopupAllowSite.removeAttribute("block");
1171 blockedPopupAllowSite.hidden = true;
1174 let blockedPopupDontShowMessage = document.getElementById(
1175 "blockedPopupDontShowMessage"
1177 let showMessage = Services.prefs.getBoolPref(
1178 "privacy.popups.showBrowserMessage"
1180 blockedPopupDontShowMessage.setAttribute("checked", !showMessage);
1182 let blockedPopupsSeparator = document.getElementById(
1183 "blockedPopupsSeparator"
1185 blockedPopupsSeparator.hidden = true;
1187 browser.popupBlocker.getBlockedPopups().then(blockedPopups => {
1188 let foundUsablePopupURI = false;
1189 if (blockedPopups) {
1190 for (let i = 0; i < blockedPopups.length; i++) {
1191 let blockedPopup = blockedPopups[i];
1193 // popupWindowURI will be null if the file picker popup is blocked.
1194 // xxxdz this should make the option say "Show file picker" and do it (Bug 590306)
1195 if (!blockedPopup.popupWindowURISpec) {
1199 var popupURIspec = blockedPopup.popupWindowURISpec;
1201 // Sometimes the popup URI that we get back from the blockedPopup
1202 // isn't useful (for instance, netscape.com's popup URI ends up
1203 // being "http://www.netscape.com", which isn't really the URI of
1204 // the popup they're trying to show). This isn't going to be
1205 // useful to the user, so we won't create a menu item for it.
1207 popupURIspec == "" ||
1208 popupURIspec == "about:blank" ||
1209 popupURIspec == "<self>" ||
1210 popupURIspec == uriOrPrincipal.spec
1215 // Because of the short-circuit above, we may end up in a situation
1216 // in which we don't have any usable popup addresses to show in
1217 // the menu, and therefore we shouldn't show the separator. However,
1218 // since we got past the short-circuit, we must've found at least
1219 // one usable popup URI and thus we'll turn on the separator later.
1220 foundUsablePopupURI = true;
1222 var menuitem = document.createXULElement("menuitem");
1223 var label = gNavigatorBundle.getFormattedString(
1224 "popupShowPopupPrefix",
1227 menuitem.setAttribute("label", label);
1228 menuitem.setAttribute(
1230 "gPopupBlockerObserver.showBlockedPopup(event);"
1232 menuitem.setAttribute("popupReportIndex", i);
1233 menuitem.setAttribute(
1234 "popupInnerWindowId",
1235 blockedPopup.innerWindowId
1237 menuitem.browsingContext = blockedPopup.browsingContext;
1238 menuitem.popupReportBrowser = browser;
1239 aEvent.target.appendChild(menuitem);
1243 // Show the separator if we added any
1244 // showable popup addresses to the menu.
1245 if (foundUsablePopupURI) {
1246 blockedPopupsSeparator.removeAttribute("hidden");
1251 onPopupHiding(aEvent) {
1252 let item = aEvent.target.lastElementChild;
1253 while (item && item.id != "blockedPopupsSeparator") {
1254 let next = item.previousElementSibling;
1260 showBlockedPopup(aEvent) {
1261 let target = aEvent.target;
1262 let browsingContext = target.browsingContext;
1263 let innerWindowId = target.getAttribute("popupInnerWindowId");
1264 let popupReportIndex = target.getAttribute("popupReportIndex");
1265 let browser = target.popupReportBrowser;
1266 browser.popupBlocker.unblockPopup(
1273 editPopupSettings() {
1274 openPreferences("privacy-permissions-block-popups");
1278 var showMessage = Services.prefs.getBoolPref(
1279 "privacy.popups.showBrowserMessage"
1281 Services.prefs.setBoolPref(
1282 "privacy.popups.showBrowserMessage",
1285 gBrowser.getNotificationBox().removeCurrentNotification();
1289 XPCOMUtils.defineLazyPreferenceGetter(
1290 gPopupBlockerObserver,
1291 "maxReportedPopups",
1292 "privacy.popups.maxReported"
1295 var gKeywordURIFixup = {
1296 check(browser, { fixedURI, keywordProviderName, preferredURI }) {
1297 // We get called irrespective of whether we did a keyword search, or
1298 // whether the original input would be vaguely interpretable as a URL,
1299 // so figure that out first.
1301 !keywordProviderName ||
1304 UrlbarPrefs.get("browser.fixup.dns_first_for_single_words") ||
1305 UrlbarPrefs.get("dnsResolveSingleWordsAfterSearch") == 0
1310 let contentPrincipal = browser.contentPrincipal;
1312 // At this point we're still only just about to load this URI.
1313 // When the async DNS lookup comes back, we may be in any of these states:
1314 // 1) still on the previous URI, waiting for the preferredURI (keyword
1315 // search) to respond;
1316 // 2) at the keyword search URI (preferredURI)
1317 // 3) at some other page because the user stopped navigation.
1318 // We keep track of the currentURI to detect case (1) in the DNS lookup
1320 let previousURI = browser.currentURI;
1322 // now swap for a weak ref so we don't hang on to browser needlessly
1323 // even if the DNS query takes forever
1324 let weakBrowser = Cu.getWeakReference(browser);
1327 // Additionally, we need the host of the parsed url
1328 let hostName = fixedURI.displayHost;
1329 // and the ascii-only host for the pref:
1330 let asciiHost = fixedURI.asciiHost;
1332 let onLookupCompleteListener = {
1333 onLookupComplete(request, record, status) {
1334 let browserRef = weakBrowser.get();
1335 if (!Components.isSuccessCode(status) || !browserRef) {
1339 let currentURI = browserRef.currentURI;
1340 // If we're in case (3) (see above), don't show an info bar.
1342 !currentURI.equals(previousURI) &&
1343 !currentURI.equals(preferredURI)
1348 // show infobar offering to visit the host
1349 let notificationBox = gBrowser.getNotificationBox(browserRef);
1350 if (notificationBox.getNotificationWithValue("keyword-uri-fixup")) {
1354 let displayHostName = "http://" + hostName + "/";
1355 let message = gNavigatorBundle.getFormattedString(
1356 "keywordURIFixup.message",
1359 let yesMessage = gNavigatorBundle.getFormattedString(
1360 "keywordURIFixup.goTo",
1367 accessKey: gNavigatorBundle.getString(
1368 "keywordURIFixup.goTo.accesskey"
1371 // Do not set this preference while in private browsing.
1372 if (!PrivateBrowsingUtils.isWindowPrivate(window)) {
1373 let prefHost = asciiHost;
1374 // Normalize out a single trailing dot - NB: not using endsWith/lastIndexOf
1375 // because we need to be sure this last dot is the *only* dot, too.
1376 // More generally, this is used for the pref and should stay in sync with
1377 // the code in URIFixup::KeywordURIFixup .
1378 if (prefHost.indexOf(".") == prefHost.length - 1) {
1379 prefHost = prefHost.slice(0, -1);
1381 let pref = "browser.fixup.domainwhitelist." + prefHost;
1382 Services.prefs.setBoolPref(pref, true);
1384 openTrustedLinkIn(fixedURI.spec, "current");
1388 let notification = notificationBox.appendNotification(
1389 "keyword-uri-fixup",
1392 priority: notificationBox.PRIORITY_INFO_HIGH,
1396 notification.persistence = 1;
1400 Services.uriFixup.checkHost(
1402 onLookupCompleteListener,
1403 contentPrincipal.originAttributes
1407 observe(fixupInfo, topic, data) {
1408 fixupInfo.QueryInterface(Ci.nsIURIFixupInfo);
1410 let browser = fixupInfo.consumer?.top?.embedderElement;
1411 if (!browser || browser.ownerGlobal != window) {
1415 this.check(browser, fixupInfo);
1419 function serializeInputStream(aStream) {
1421 content: NetUtil.readInputStreamToString(aStream, aStream.available()),
1424 if (aStream instanceof Ci.nsIMIMEInputStream) {
1425 data.headers = new Map();
1426 aStream.visitHeaders((name, value) => {
1427 data.headers.set(name, value);
1435 * Handles URIs when we want to deal with them in chrome code rather than pass
1436 * them down to a content browser. This can avoid unnecessary process switching
1438 * @param aBrowser the browser that is attempting to load the URI
1439 * @param aUri the nsIURI that is being loaded
1440 * @returns true if the URI is handled, otherwise false
1442 function handleUriInChrome(aBrowser, aUri) {
1443 if (aUri.scheme == "file") {
1445 let mimeType = Cc["@mozilla.org/mime;1"]
1446 .getService(Ci.nsIMIMEService)
1447 .getTypeFromURI(aUri);
1448 if (mimeType == "application/x-xpinstall") {
1449 let systemPrincipal = Services.scriptSecurityManager.getSystemPrincipal();
1450 AddonManager.getInstallForURL(aUri.spec, {
1451 telemetryInfo: { source: "file-url" },
1452 }).then(install => {
1453 AddonManager.installAddonFromWebpage(
1470 /* Creates a null principal using the userContextId
1471 from the current selected tab or a passed in tab argument */
1472 function _createNullPrincipalFromTabUserContextId(tab = gBrowser.selectedTab) {
1474 if (tab.hasAttribute("usercontextid")) {
1475 userContextId = tab.getAttribute("usercontextid");
1477 return Services.scriptSecurityManager.createNullPrincipal({
1482 // A shared function used by both remote and non-remote browser XBL bindings to
1483 // load a URI or redirect it to the correct process.
1484 function _loadURI(browser, uri, params = {}) {
1486 uri = "about:blank";
1490 triggeringPrincipal,
1496 hasValidUserGestureActivation,
1497 globalHistoryOptions,
1500 params.loadFlags || params.flags || Ci.nsIWebNavigation.LOAD_FLAGS_NONE;
1501 hasValidUserGestureActivation ??=
1502 document.hasValidTransientUserGestureActivation;
1504 if (!triggeringPrincipal) {
1505 throw new Error("Must load with a triggering Principal");
1508 if (userContextId && userContextId != browser.getAttribute("usercontextid")) {
1509 throw new Error("Cannot load with mismatched userContextId");
1512 // Attempt to perform URI fixup to see if we can handle this URI in chrome.
1513 let fixupFlags = Ci.nsIURIFixup.FIXUP_FLAG_NONE;
1514 if (loadFlags & Ci.nsIWebNavigation.LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP) {
1515 fixupFlags |= Ci.nsIURIFixup.FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP;
1517 if (loadFlags & Ci.nsIWebNavigation.LOAD_FLAGS_FIXUP_SCHEME_TYPOS) {
1518 fixupFlags |= Ci.nsIURIFixup.FIXUP_FLAG_FIX_SCHEME_TYPOS;
1520 if (PrivateBrowsingUtils.isBrowserPrivate(browser)) {
1521 fixupFlags |= Ci.nsIURIFixup.FIXUP_FLAG_PRIVATE_CONTEXT;
1525 let uriObject = Services.uriFixup.getFixupURIInfo(uri, fixupFlags)
1527 if (uriObject && handleUriInChrome(browser, uriObject)) {
1528 // If we've handled the URI in Chrome, then just return here.
1532 // getFixupURIInfo may throw. Gracefully recover and try to load the URI normally.
1535 // XXX(nika): Is `browser.isNavigating` necessary anymore?
1536 browser.isNavigating = true;
1538 if (globalHistoryOptions?.triggeringSearchEngine) {
1539 browser.setAttribute(
1540 "triggeringSearchEngine",
1541 globalHistoryOptions.triggeringSearchEngine
1543 browser.setAttribute("triggeringSearchEngineURL", uri);
1545 browser.removeAttribute("triggeringSearchEngine");
1546 browser.removeAttribute("triggeringSearchEngineURL");
1549 if (globalHistoryOptions?.triggeringSponsoredURL) {
1551 // Browser may access URL after fixing it up, then store the URL into DB.
1552 // To match with it, fix the link up explicitly.
1553 const triggeringSponsoredURL = Services.uriFixup.getFixupURIInfo(
1554 globalHistoryOptions.triggeringSponsoredURL,
1557 browser.setAttribute("triggeringSponsoredURL", triggeringSponsoredURL);
1559 globalHistoryOptions.triggeringSponsoredURLVisitTimeMS || Date.now();
1560 browser.setAttribute("triggeringSponsoredURLVisitTimeMS", time);
1564 let loadURIOptions = {
1565 triggeringPrincipal,
1570 hasValidUserGestureActivation,
1574 browser.webNavigation.loadURI(uri, loadURIOptions);
1576 browser.isNavigating = false;
1580 let _resolveDelayedStartup;
1581 var delayedStartupPromise = new Promise(resolve => {
1582 _resolveDelayedStartup = resolve;
1585 var gBrowserInit = {
1586 delayedStartupFinished: false,
1587 idleTasksFinishedPromise: null,
1588 idleTaskPromiseResolve: null,
1589 domContentLoaded: false,
1591 _tabToAdopt: undefined,
1594 if (this._tabToAdopt !== undefined) {
1595 return this._tabToAdopt;
1598 if (window.arguments && window.XULElement.isInstance(window.arguments[0])) {
1599 this._tabToAdopt = window.arguments[0];
1601 // Clear the reference of the tab being adopted from the arguments.
1602 window.arguments[0] = null;
1604 // There was no tab to adopt in the arguments, set _tabToAdopt to null
1605 // to avoid checking it again.
1606 this._tabToAdopt = null;
1609 return this._tabToAdopt;
1612 _clearTabToAdopt() {
1613 this._tabToAdopt = null;
1616 // Used to check if the new window is still adopting an existing tab as its first tab
1617 // (e.g. from the WebExtensions internals).
1619 return !!this.getTabToAdopt();
1622 onBeforeInitialXULLayout() {
1623 BookmarkingUI.updateEmptyToolbarMessage();
1624 setToolbarVisibility(
1625 BookmarkingUI.toolbar,
1626 gBookmarksToolbarVisibility,
1631 // Set a sane starting width/height for all resolutions on new profiles.
1632 if (Services.prefs.getBoolPref("privacy.resistFingerprinting")) {
1633 // When the fingerprinting resistance is enabled, making sure that we don't
1634 // have a maximum window to interfere with generating rounded window dimensions.
1635 document.documentElement.setAttribute("sizemode", "normal");
1636 } else if (!document.documentElement.hasAttribute("width")) {
1637 const TARGET_WIDTH = 1280;
1638 const TARGET_HEIGHT = 1040;
1639 let width = Math.min(screen.availWidth * 0.9, TARGET_WIDTH);
1640 let height = Math.min(screen.availHeight * 0.9, TARGET_HEIGHT);
1642 document.documentElement.setAttribute("width", width);
1643 document.documentElement.setAttribute("height", height);
1645 if (width < TARGET_WIDTH && height < TARGET_HEIGHT) {
1646 document.documentElement.setAttribute("sizemode", "maximized");
1649 if (AppConstants.MENUBAR_CAN_AUTOHIDE) {
1650 const toolbarMenubar = document.getElementById("toolbar-menubar");
1651 // set a default value
1652 if (!toolbarMenubar.hasAttribute("autohide")) {
1653 toolbarMenubar.setAttribute("autohide", true);
1655 toolbarMenubar.setAttribute(
1657 "toolbar-context-menu-menu-bar-cmd"
1659 toolbarMenubar.setAttribute("data-l10n-attrs", "toolbarname");
1662 // Run menubar initialization first, to avoid TabsInTitlebar code picking
1663 // up mutations from it and causing a reflow.
1664 AutoHideMenubar.init();
1665 // Update the chromemargin attribute so the window can be sized correctly.
1666 window.TabBarVisibility.update();
1667 TabsInTitlebar.init();
1669 new LightweightThemeConsumer(document);
1671 if (AppConstants.platform == "win") {
1673 window.matchMedia("(-moz-platform: windows-win8)").matches &&
1674 window.matchMedia("(-moz-windows-default-theme)").matches
1676 let windowFrameColor = new Color(
1677 ...ChromeUtils.importESModule(
1678 "resource:///modules/Windows8WindowFrameColor.sys.mjs"
1679 ).Windows8WindowFrameColor.get()
1681 // Default to black for foreground text.
1682 if (!windowFrameColor.isContrastRatioAcceptable(new Color(0, 0, 0))) {
1683 document.documentElement.setAttribute("darkwindowframe", "true");
1685 } else if (AppConstants.isPlatformAndVersionAtLeast("win", "10")) {
1686 TelemetryEnvironment.onInitialized().then(() => {
1687 // 17763 is the build number of Windows 10 version 1809
1689 TelemetryEnvironment.currentEnvironment.system.os
1690 .windowsBuildNumber < 17763
1692 document.documentElement.setAttribute(
1693 "always-use-accent-color-for-window-border",
1702 Services.prefs.getBoolPref(
1703 "toolkit.legacyUserProfileCustomizations.windowIcon",
1707 document.documentElement.setAttribute("icon", "main-window");
1710 // Call this after we set attributes that might change toolbars' computed
1712 ToolbarIconColor.init();
1715 onDOMContentLoaded() {
1716 // This needs setting up before we create the first remote browser.
1717 window.docShell.treeOwner
1718 .QueryInterface(Ci.nsIInterfaceRequestor)
1719 .getInterface(Ci.nsIAppWindow).XULBrowserWindow = window.XULBrowserWindow;
1720 window.browserDOMWindow = new nsBrowserAccess();
1722 gBrowser = window._gBrowser;
1723 delete window._gBrowser;
1726 BrowserWindowTracker.track(window);
1728 FirefoxViewHandler.init();
1729 gNavToolbox.palette = document.getElementById(
1730 "BrowserToolbarPalette"
1732 let areas = CustomizableUI.areas;
1733 areas.splice(areas.indexOf(CustomizableUI.AREA_FIXED_OVERFLOW_PANEL), 1);
1734 for (let area of areas) {
1735 let node = document.getElementById(area);
1736 CustomizableUI.registerToolbarNode(node);
1738 BrowserSearch.initPlaceHolder();
1740 // Hack to ensure that the various initial pages favicon is loaded
1741 // instantaneously, to avoid flickering and improve perceived performance.
1742 this._callWithURIToLoad(uriToLoad => {
1745 url = Services.io.newURI(uriToLoad);
1749 let nonQuery = url.prePath + url.filePath;
1750 if (nonQuery in gPageIcons) {
1751 gBrowser.setIcon(gBrowser.selectedTab, gPageIcons[nonQuery]);
1755 updateFxaToolbarMenu(gFxaToolbarEnabled, true);
1757 gUnifiedExtensions.init();
1759 // Setting the focus will cause a style flush, it's preferable to call anything
1760 // that will modify the DOM from within this function before this call.
1761 this._setInitialFocus();
1763 this.domContentLoaded = true;
1767 gBrowser.addEventListener("DOMUpdateBlockedPopups", gPopupBlockerObserver);
1769 window.addEventListener("AppCommand", HandleAppCommandEvent, true);
1771 // These routines add message listeners. They must run before
1772 // loading the frame script to ensure that we don't miss any
1773 // message sent between when the frame script is loaded and when
1774 // the listener is registered.
1775 CaptivePortalWatcher.init();
1776 ZoomUI.init(window);
1778 if (!gMultiProcessBrowser) {
1779 // There is a Content:Click message manually sent from content.
1780 Services.els.addSystemEventListener(
1788 // hook up UI through progress listener
1789 gBrowser.addProgressListener(window.XULBrowserWindow);
1790 gBrowser.addTabsProgressListener(window.TabsProgressListener);
1794 // We do this in onload because we want to ensure the button's state
1795 // doesn't flicker as the window is being shown.
1796 DownloadsButton.init();
1798 // Certain kinds of automigration rely on this notification to complete
1799 // their tasks BEFORE the browser window is shown. SessionStore uses it to
1800 // restore tabs into windows AFTER important parts like gMultiProcessBrowser
1801 // have been initialized.
1802 Services.obs.notifyObservers(window, "browser-window-before-show");
1804 if (!window.toolbar.visible) {
1805 // adjust browser UI for popups
1806 gURLBar.readOnly = true;
1811 TabletModeUpdater.init();
1812 CombinedStopReload.ensureInitialized();
1813 gPrivateBrowsingUI.init();
1814 BrowserSearch.init();
1815 BrowserPageActions.init();
1816 if (gToolbarKeyNavEnabled) {
1817 ToolbarKeyboardNavigator.init();
1820 // Update UI if browser is under remote control.
1821 gRemoteControl.updateVisualCue();
1823 // If we are given a tab to swap in, take care of it before first paint to
1824 // avoid an about:blank flash.
1825 let tabToAdopt = this.getTabToAdopt();
1827 let evt = new CustomEvent("before-initial-tab-adopted", {
1830 gBrowser.tabpanels.dispatchEvent(evt);
1832 // Stop the about:blank load
1834 // make sure it has a docshell
1837 // Remove the speculative focus from the urlbar to let the url be formatted.
1838 gURLBar.removeAttribute("focused");
1840 let swapBrowsers = () => {
1842 gBrowser.swapBrowsersAndCloseOther(gBrowser.selectedTab, tabToAdopt);
1847 // Clear the reference to the tab once its adoption has been completed.
1848 this._clearTabToAdopt();
1850 if (tabToAdopt.linkedBrowser.isRemoteBrowser) {
1851 // For remote browsers, wait for the paint event, otherwise the tabs
1852 // are not yet ready and focus gets confused because the browser swaps
1853 // out while tabs are switching.
1854 addEventListener("MozAfterPaint", swapBrowsers, { once: true });
1860 // Wait until chrome is painted before executing code not critical to making the window visible
1861 this._boundDelayedStartup = this._delayedStartup.bind(this);
1862 window.addEventListener("MozAfterPaint", this._boundDelayedStartup);
1864 if (!PrivateBrowsingUtils.enabled) {
1865 document.getElementById("Tools:PrivateBrowsing").hidden = true;
1866 // Setting disabled doesn't disable the shortcut, so we just remove
1868 document.getElementById("key_privatebrowsing").remove();
1871 if (BrowserUIUtils.quitShortcutDisabled) {
1872 document.getElementById("key_quitApplication").remove();
1873 document.getElementById("menu_FileQuitItem").removeAttribute("key");
1875 PanelMultiView.getViewNode(
1877 "appMenu-quit-button2"
1878 )?.removeAttribute("key");
1881 this._loadHandled = true;
1884 _cancelDelayedStartup() {
1885 window.removeEventListener("MozAfterPaint", this._boundDelayedStartup);
1886 this._boundDelayedStartup = null;
1890 let { TelemetryTimestamps } = ChromeUtils.import(
1891 "resource://gre/modules/TelemetryTimestamps.jsm"
1893 TelemetryTimestamps.add("delayedStartupStarted");
1895 this._cancelDelayedStartup();
1897 // Bug 1531854 - The hidden window is force-created here
1898 // until all of its dependencies are handled.
1899 Services.appShell.hiddenDOMWindow;
1901 gBrowser.addEventListener(
1902 "PermissionStateChange",
1904 gIdentityHandler.refreshIdentityBlock();
1905 gPermissionPanel.updateSharingIndicator();
1910 this._handleURIToLoad();
1912 Services.obs.addObserver(gIdentityHandler, "perm-changed");
1913 Services.obs.addObserver(gRemoteControl, "devtools-socket");
1914 Services.obs.addObserver(gRemoteControl, "marionette-listening");
1915 Services.obs.addObserver(gRemoteControl, "remote-listening");
1916 Services.obs.addObserver(
1917 gSessionHistoryObserver,
1918 "browser:purge-session-history"
1920 Services.obs.addObserver(
1921 gStoragePressureObserver,
1922 "QuotaManager::StoragePressure"
1924 Services.obs.addObserver(gXPInstallObserver, "addon-install-disabled");
1925 Services.obs.addObserver(gXPInstallObserver, "addon-install-started");
1926 Services.obs.addObserver(gXPInstallObserver, "addon-install-blocked");
1927 Services.obs.addObserver(
1929 "addon-install-fullscreen-blocked"
1931 Services.obs.addObserver(
1933 "addon-install-origin-blocked"
1935 Services.obs.addObserver(
1937 "addon-install-policy-blocked"
1939 Services.obs.addObserver(
1941 "addon-install-webapi-blocked"
1943 Services.obs.addObserver(gXPInstallObserver, "addon-install-failed");
1944 Services.obs.addObserver(gXPInstallObserver, "addon-install-confirmation");
1945 Services.obs.addObserver(gXPInstallObserver, "addon-install-complete");
1946 Services.obs.addObserver(gKeywordURIFixup, "keyword-uri-fixup");
1948 BrowserOffline.init();
1949 CanvasPermissionPromptHelper.init();
1950 WebAuthnPromptHelper.init();
1952 // Initialize the full zoom setting.
1953 // We do this before the session restore service gets initialized so we can
1954 // apply full zoom settings to tabs restored by the session restore service.
1956 PanelUI.init(shouldSuppressPopupNotifications);
1958 UpdateUrlbarSearchSplitterState();
1960 BookmarkingUI.init();
1961 BrowserSearch.delayedStartupInit();
1962 gProtectionsHandler.init();
1963 HomePage.delayedStartup().catch(Cu.reportError);
1965 let safeMode = document.getElementById("helpSafeMode");
1966 if (Services.appinfo.inSafeMode) {
1967 document.l10n.setAttributes(safeMode, "menu-help-exit-troubleshoot-mode");
1968 safeMode.setAttribute(
1969 "appmenu-data-l10n-id",
1970 "appmenu-help-exit-troubleshoot-mode"
1975 gBidiUI = isBidiEnabled();
1977 document.getElementById("documentDirection-separator").hidden = false;
1978 document.getElementById("documentDirection-swap").hidden = false;
1979 document.getElementById("textfieldDirection-separator").hidden = false;
1980 document.getElementById("textfieldDirection-swap").hidden = false;
1983 // Setup click-and-hold gestures access to the session history
1984 // menus if global click-and-hold isn't turned on
1985 if (!Services.prefs.getBoolPref("ui.click_hold_context_menus", false)) {
1986 SetClickAndHoldHandlers();
1989 function initBackForwardButtonTooltip(tooltipId, l10nId, shortcutId) {
1990 let shortcut = document.getElementById(shortcutId);
1991 shortcut = ShortcutUtils.prettifyShortcut(shortcut);
1993 let tooltip = document.getElementById(tooltipId);
1994 document.l10n.setAttributes(tooltip, l10nId, { shortcut });
1997 initBackForwardButtonTooltip(
1998 "back-button-tooltip-description",
1999 "navbar-tooltip-back-2",
2003 initBackForwardButtonTooltip(
2004 "forward-button-tooltip-description",
2005 "navbar-tooltip-forward-2",
2009 PlacesToolbarHelper.init();
2012 Services.prefs.addObserver(ctrlTab.prefName, ctrlTab);
2014 // The object handling the downloads indicator is initialized here in the
2015 // delayed startup function, but the actual indicator element is not loaded
2016 // unless there are downloads to be displayed.
2017 DownloadsButton.initializeIndicator();
2019 if (AppConstants.platform != "macosx") {
2020 updateEditUIVisibility();
2021 let placesContext = document.getElementById("placesContext");
2022 placesContext.addEventListener("popupshowing", updateEditUIVisibility);
2023 placesContext.addEventListener("popuphiding", updateEditUIVisibility);
2028 if (AppConstants.isPlatformAndVersionAtLeast("win", "10")) {
2029 MenuTouchModeObserver.init();
2032 if (AppConstants.MOZ_DATA_REPORTING) {
2033 gDataNotificationInfoBar.init();
2036 if (!AppConstants.MOZILLA_OFFICIAL) {
2037 DevelopmentHelpers.init();
2040 gExtensionsNotifications.init();
2042 let wasMinimized = window.windowState == window.STATE_MINIMIZED;
2043 window.addEventListener("sizemodechange", () => {
2044 let isMinimized = window.windowState == window.STATE_MINIMIZED;
2045 if (wasMinimized != isMinimized) {
2046 wasMinimized = isMinimized;
2047 UpdatePopupNotificationsVisibility();
2051 window.addEventListener("mousemove", MousePosTracker);
2052 window.addEventListener("dragover", MousePosTracker);
2054 gNavToolbox.addEventListener("customizationstarting", CustomizationHandler);
2055 gNavToolbox.addEventListener("aftercustomization", CustomizationHandler);
2057 SessionStore.promiseInitialized.then(() => {
2058 // Bail out if the window has been closed in the meantime.
2059 if (window.closed) {
2063 // Enable the Restore Last Session command if needed
2064 RestoreLastSessionObserver.init();
2066 SidebarUI.startDelayedLoad();
2068 PanicButtonNotifier.init();
2071 if (BrowserHandler.kiosk) {
2072 // We don't modify popup windows for kiosk mode
2073 if (!gURLBar.readOnly) {
2074 window.fullScreen = true;
2078 if (Services.policies.status === Services.policies.ACTIVE) {
2079 if (!Services.policies.isAllowed("hideShowMenuBar")) {
2081 .getElementById("toolbar-menubar")
2082 .removeAttribute("toolbarname");
2084 let policies = Services.policies.getActivePolicies();
2085 if ("ManagedBookmarks" in policies) {
2086 let managedBookmarks = policies.ManagedBookmarks;
2087 let children = managedBookmarks.filter(
2088 child => !("toplevel_name" in child)
2090 if (children.length) {
2091 let managedBookmarksButton = document.createXULElement(
2094 managedBookmarksButton.setAttribute("id", "managed-bookmarks");
2095 managedBookmarksButton.setAttribute("class", "bookmark-item");
2096 let toplevel = managedBookmarks.find(
2097 element => "toplevel_name" in element
2100 managedBookmarksButton.setAttribute(
2102 toplevel.toplevel_name
2105 managedBookmarksButton.setAttribute(
2110 managedBookmarksButton.setAttribute("context", "placesContext");
2111 managedBookmarksButton.setAttribute("container", "true");
2112 managedBookmarksButton.setAttribute("removable", "false");
2113 managedBookmarksButton.setAttribute("type", "menu");
2115 let managedBookmarksPopup = document.createXULElement("menupopup");
2116 managedBookmarksPopup.setAttribute("id", "managed-bookmarks-popup");
2117 managedBookmarksPopup.setAttribute(
2119 "PlacesToolbarHelper.openManagedBookmark(event);"
2121 managedBookmarksPopup.setAttribute(
2123 "event.dataTransfer.effectAllowed='none';"
2125 managedBookmarksPopup.setAttribute(
2127 "PlacesToolbarHelper.onDragStartManaged(event);"
2129 managedBookmarksPopup.setAttribute(
2131 "PlacesToolbarHelper.populateManagedBookmarks(this);"
2133 managedBookmarksPopup.setAttribute("placespopup", "true");
2134 managedBookmarksPopup.setAttribute("is", "places-popup");
2135 managedBookmarksButton.appendChild(managedBookmarksPopup);
2137 gNavToolbox.palette.appendChild(managedBookmarksButton);
2139 CustomizableUI.ensureWidgetPlacedInWindow(
2140 "managed-bookmarks",
2144 // Add button if it doesn't exist
2145 if (!CustomizableUI.getPlacementOfWidget("managed-bookmarks")) {
2146 CustomizableUI.addWidgetToArea(
2147 "managed-bookmarks",
2148 CustomizableUI.AREA_BOOKMARKS,
2156 CaptivePortalWatcher.delayedStartup();
2158 SessionStore.promiseAllWindowsRestored.then(() => {
2159 this._schedulePerWindowIdleTasks();
2160 document.documentElement.setAttribute("sessionrestored", "true");
2163 this.delayedStartupFinished = true;
2164 _resolveDelayedStartup();
2165 Services.obs.notifyObservers(window, "browser-delayed-startup-finished");
2166 TelemetryTimestamps.add("delayedStartupFinished");
2167 // We've announced that delayed startup has finished. Do not add code past this point.
2171 * Resolved on the first MozAfterPaint in the first content window.
2173 get firstContentWindowPaintPromise() {
2174 return this._firstContentWindowPaintDeferred.promise;
2177 _setInitialFocus() {
2178 let initiallyFocusedElement = document.commandDispatcher.focusedElement;
2180 // To prevent startup flicker, the urlbar has the 'focused' attribute set
2181 // by default. If we are not sure the urlbar will be focused in this
2182 // window, we need to remove the attribute before first paint.
2183 // TODO (bug 1629956): The urlbar having the 'focused' attribute by default
2184 // isn't a useful optimization anymore since UrlbarInput needs layout
2185 // information to focus the urlbar properly.
2186 let shouldRemoveFocusedAttribute = true;
2188 this._callWithURIToLoad(uriToLoad => {
2189 // Check if user is enrolled in an aboutWelcome experiment that has skipFocus
2190 // property set to true, if yes remove focus from urlbar for about:welcome
2191 const aboutWelcomeSkipUrlBarFocus =
2192 uriToLoad == "about:welcome" &&
2193 NimbusFeatures.aboutwelcome.getVariable("skipFocus");
2196 (isBlankPageURL(uriToLoad) && !aboutWelcomeSkipUrlBarFocus) ||
2197 uriToLoad == "about:privatebrowsing" ||
2198 this.getTabToAdopt()?.isEmpty
2201 shouldRemoveFocusedAttribute = false;
2205 // If the initial browser is remote, in order to optimize for first paint,
2206 // we'll defer switching focus to that browser until it has painted.
2207 // Otherwise use a regular promise to guarantee that mutationobserver
2208 // microtasks that could affect focusability have run.
2209 let promise = gBrowser.selectedBrowser.isRemoteBrowser
2210 ? this._firstContentWindowPaintDeferred.promise
2211 : Promise.resolve();
2213 promise.then(() => {
2214 // If focus didn't move while we were waiting, we're okay to move to
2217 document.commandDispatcher.focusedElement == initiallyFocusedElement
2219 gBrowser.selectedBrowser.focus();
2224 // Delay removing the attribute using requestAnimationFrame to avoid
2225 // invalidating styles multiple times in a row if uriToLoadPromise
2226 // resolves before first paint.
2227 if (shouldRemoveFocusedAttribute) {
2228 window.requestAnimationFrame(() => {
2229 if (shouldRemoveFocusedAttribute) {
2230 gURLBar.removeAttribute("focused");
2236 _handleURIToLoad() {
2237 this._callWithURIToLoad(uriToLoad => {
2239 // We don't check whether window.arguments[5] (userContextId) is set
2240 // because tabbrowser.js takes care of that for the initial tab.
2244 // We don't check if uriToLoad is a XULElement because this case has
2245 // already been handled before first paint, and the argument cleared.
2246 if (Array.isArray(uriToLoad)) {
2247 // This function throws for certain malformed URIs, so use exception handling
2248 // so that we don't disrupt startup
2250 gBrowser.loadTabs(uriToLoad, {
2251 inBackground: false,
2253 // See below for the semantics of window.arguments. Only the minimum is supported.
2254 userContextId: window.arguments[5],
2255 triggeringPrincipal:
2256 window.arguments[8] ||
2257 Services.scriptSecurityManager.getSystemPrincipal(),
2258 allowInheritPrincipal: window.arguments[9],
2259 csp: window.arguments[10],
2263 } else if (window.arguments.length >= 3) {
2264 // window.arguments[1]: extraOptions (nsIPropertyBag)
2265 // [2]: referrerInfo (nsIReferrerInfo)
2266 // [3]: postData (nsIInputStream)
2267 // [4]: allowThirdPartyFixup (bool)
2268 // [5]: userContextId (int)
2269 // [6]: originPrincipal (nsIPrincipal)
2270 // [7]: originStoragePrincipal (nsIPrincipal)
2271 // [8]: triggeringPrincipal (nsIPrincipal)
2272 // [9]: allowInheritPrincipal (bool)
2273 // [10]: csp (nsIContentSecurityPolicy)
2274 // [11]: nsOpenWindowInfo
2276 window.arguments[5] != undefined
2277 ? window.arguments[5]
2278 : Ci.nsIScriptSecurityManager.DEFAULT_USER_CONTEXT_ID;
2280 let hasValidUserGestureActivation = undefined;
2281 let fromExternal = undefined;
2282 let globalHistoryOptions = undefined;
2283 if (window.arguments[1]) {
2284 if (!(window.arguments[1] instanceof Ci.nsIPropertyBag2)) {
2286 "window.arguments[1] must be null or Ci.nsIPropertyBag2!"
2290 let extraOptions = window.arguments[1];
2291 if (extraOptions.hasKey("hasValidUserGestureActivation")) {
2292 hasValidUserGestureActivation = extraOptions.getPropertyAsBool(
2293 "hasValidUserGestureActivation"
2296 if (extraOptions.hasKey("fromExternal")) {
2297 fromExternal = extraOptions.getPropertyAsBool("fromExternal");
2299 if (extraOptions.hasKey("triggeringSponsoredURL")) {
2300 globalHistoryOptions = {
2301 triggeringSponsoredURL: extraOptions.getPropertyAsACString(
2302 "triggeringSponsoredURL"
2305 if (extraOptions.hasKey("triggeringSponsoredURLVisitTimeMS")) {
2306 globalHistoryOptions.triggeringSponsoredURLVisitTimeMS = extraOptions.getPropertyAsUint64(
2307 "triggeringSponsoredURLVisitTimeMS"
2314 openLinkIn(uriToLoad, "current", {
2315 referrerInfo: window.arguments[2] || null,
2316 postData: window.arguments[3] || null,
2317 allowThirdPartyFixup: window.arguments[4] || false,
2319 // pass the origin principal (if any) and force its use to create
2320 // an initial about:blank viewer if present:
2321 originPrincipal: window.arguments[6],
2322 originStoragePrincipal: window.arguments[7],
2323 triggeringPrincipal: window.arguments[8],
2324 // TODO fix allowInheritPrincipal to default to false.
2325 // Default to true unless explicitly set to false because of bug 1475201.
2326 allowInheritPrincipal: window.arguments[9] !== false,
2327 csp: window.arguments[10],
2328 forceAboutBlankViewerInCurrent: !!window.arguments[6],
2329 hasValidUserGestureActivation,
2331 globalHistoryOptions,
2339 // Note: loadOneOrMoreURIs *must not* be called if window.arguments.length >= 3.
2340 // Such callers expect that window.arguments[0] is handled as a single URI.
2343 Services.scriptSecurityManager.getSystemPrincipal(),
2351 * Use this function as an entry point to schedule tasks that
2352 * need to run once per window after startup, and can be scheduled
2353 * by using an idle callback.
2355 * The functions scheduled here will fire from idle callbacks
2356 * once every window has finished being restored by session
2357 * restore, and after the equivalent only-once tasks
2358 * have run (from _scheduleStartupIdleTasks in BrowserGlue.jsm).
2360 _schedulePerWindowIdleTasks() {
2361 // Bail out if the window has been closed in the meantime.
2362 if (window.closed) {
2366 function scheduleIdleTask(func, options) {
2367 requestIdleCallback(function idleTaskRunner() {
2368 if (!window.closed) {
2374 scheduleIdleTask(() => {
2375 // Initialize the Sync UI
2379 scheduleIdleTask(() => {
2380 // Read prefers-reduced-motion setting
2381 let reduceMotionQuery = window.matchMedia(
2382 "(prefers-reduced-motion: reduce)"
2384 function readSetting() {
2385 gReduceMotionSetting = reduceMotionQuery.matches;
2387 reduceMotionQuery.addListener(readSetting);
2391 scheduleIdleTask(() => {
2392 // setup simple gestures support
2393 gGestureSupport.init(true);
2395 // setup history swipe animation
2396 gHistorySwipeAnimation.init();
2399 scheduleIdleTask(() => {
2400 gBrowserThumbnails.init();
2405 // Initialize the download manager some time after the app starts so that
2406 // auto-resume downloads begin (such as after crashing or quitting with
2407 // active downloads) and speeds up the first-load of the download manager UI.
2408 // If the user manually opens the download manager before the timeout, the
2409 // downloads will start right away, and initializing again won't hurt.
2411 DownloadsCommon.initializeAllDataLinks();
2413 "resource:///modules/DownloadsTaskbar.jsm"
2414 ).DownloadsTaskbar.registerIndicator(window);
2415 if (AppConstants.platform == "macosx") {
2417 "resource:///modules/DownloadsMacFinderProgress.jsm"
2418 ).DownloadsMacFinderProgress.register();
2420 Services.telemetry.setEventRecordingEnabled("downloads", true);
2429 scheduleIdleTask(() => Win7Features.onOpenWindow());
2432 scheduleIdleTask(async () => {
2433 NewTabPagePreloading.maybeCreatePreloadedBrowser(window);
2436 scheduleIdleTask(() => {
2440 // This should always go last, since the idle tasks (except for the ones with
2441 // timeouts) should execute in order. Note that this observer notification is
2442 // not guaranteed to fire, since the window could close before we get here.
2443 scheduleIdleTask(() => {
2444 this.idleTaskPromiseResolve();
2445 Services.obs.notifyObservers(
2447 "browser-idle-startup-tasks-finished"
2452 // Returns the URI(s) to load at startup if it is immediately known, or a
2453 // promise resolving to the URI to load.
2454 get uriToLoadPromise() {
2455 delete this.uriToLoadPromise;
2456 return (this.uriToLoadPromise = (function() {
2457 // window.arguments[0]: URI to load (string), or an nsIArray of
2458 // nsISupportsStrings to load, or a xul:tab of
2459 // a tabbrowser, which will be replaced by this
2460 // window (for this case, all other arguments are
2462 let uri = window.arguments?.[0];
2463 if (!uri || window.XULElement.isInstance(uri)) {
2467 let defaultArgs = BrowserHandler.defaultArgs;
2469 // If the given URI is different from the homepage, we want to load it.
2470 if (uri != defaultArgs) {
2471 AboutNewTab.noteNonDefaultStartup();
2473 if (uri instanceof Ci.nsIArray) {
2474 // Transform the nsIArray of nsISupportsString's into a JS Array of
2477 uri.enumerate(Ci.nsISupportsString),
2478 supportStr => supportStr.data
2480 } else if (uri instanceof Ci.nsISupportsString) {
2486 // The URI appears to be the the homepage. We want to load it only if
2487 // session restore isn't about to override the homepage.
2488 let willOverride = SessionStartup.willOverrideHomepage;
2489 if (typeof willOverride == "boolean") {
2490 return willOverride ? null : uri;
2492 return willOverride.then(willOverrideHomepage =>
2493 willOverrideHomepage ? null : uri
2498 // Calls the given callback with the URI to load at startup.
2499 // Synchronously if possible, or after uriToLoadPromise resolves otherwise.
2500 _callWithURIToLoad(callback) {
2501 let uriToLoad = this.uriToLoadPromise;
2502 if (uriToLoad && uriToLoad.then) {
2503 uriToLoad.then(callback);
2505 callback(uriToLoad);
2510 gUIDensity.uninit();
2512 TabsInTitlebar.uninit();
2514 ToolbarIconColor.uninit();
2516 // In certain scenarios it's possible for unload to be fired before onload,
2517 // (e.g. if the window is being closed after browser.js loads but before the
2518 // load completes). In that case, there's nothing to do here.
2519 if (!this._loadHandled) {
2523 // First clean up services initialized in gBrowserInit.onLoad (or those whose
2524 // uninit methods don't depend on the services having been initialized).
2526 CombinedStopReload.uninit();
2528 gGestureSupport.init(false);
2530 gHistorySwipeAnimation.uninit();
2532 FullScreen.uninit();
2536 gExtensionsNotifications.uninit();
2537 gUnifiedExtensions.uninit();
2540 gBrowser.removeProgressListener(window.XULBrowserWindow);
2541 gBrowser.removeTabsProgressListener(window.TabsProgressListener);
2544 PlacesToolbarHelper.uninit();
2546 BookmarkingUI.uninit();
2548 TabletModeUpdater.uninit();
2550 gTabletModePageCounter.finish();
2552 CaptivePortalWatcher.uninit();
2556 DownloadsButton.uninit();
2558 if (gToolbarKeyNavEnabled) {
2559 ToolbarKeyboardNavigator.uninit();
2562 BrowserSearch.uninit();
2564 NewTabPagePreloading.removePreloadedBrowser(window);
2566 FirefoxViewHandler.uninit();
2568 // Now either cancel delayedStartup, or clean up the services initialized from
2570 if (this._boundDelayedStartup) {
2571 this._cancelDelayedStartup();
2574 Win7Features.onCloseWindow();
2576 Services.prefs.removeObserver(ctrlTab.prefName, ctrlTab);
2578 gBrowserThumbnails.uninit();
2579 gProtectionsHandler.uninit();
2582 Services.obs.removeObserver(gIdentityHandler, "perm-changed");
2583 Services.obs.removeObserver(gRemoteControl, "devtools-socket");
2584 Services.obs.removeObserver(gRemoteControl, "marionette-listening");
2585 Services.obs.removeObserver(gRemoteControl, "remote-listening");
2586 Services.obs.removeObserver(
2587 gSessionHistoryObserver,
2588 "browser:purge-session-history"
2590 Services.obs.removeObserver(
2591 gStoragePressureObserver,
2592 "QuotaManager::StoragePressure"
2594 Services.obs.removeObserver(gXPInstallObserver, "addon-install-disabled");
2595 Services.obs.removeObserver(gXPInstallObserver, "addon-install-started");
2596 Services.obs.removeObserver(gXPInstallObserver, "addon-install-blocked");
2597 Services.obs.removeObserver(
2599 "addon-install-fullscreen-blocked"
2601 Services.obs.removeObserver(
2603 "addon-install-origin-blocked"
2605 Services.obs.removeObserver(
2607 "addon-install-policy-blocked"
2609 Services.obs.removeObserver(
2611 "addon-install-webapi-blocked"
2613 Services.obs.removeObserver(gXPInstallObserver, "addon-install-failed");
2614 Services.obs.removeObserver(
2616 "addon-install-confirmation"
2618 Services.obs.removeObserver(gXPInstallObserver, "addon-install-complete");
2619 Services.obs.removeObserver(gKeywordURIFixup, "keyword-uri-fixup");
2621 if (AppConstants.isPlatformAndVersionAtLeast("win", "10")) {
2622 MenuTouchModeObserver.uninit();
2624 BrowserOffline.uninit();
2625 CanvasPermissionPromptHelper.uninit();
2626 WebAuthnPromptHelper.uninit();
2630 // Final window teardown, do this last.
2632 window.XULBrowserWindow = null;
2633 window.docShell.treeOwner
2634 .QueryInterface(Ci.nsIInterfaceRequestor)
2635 .getInterface(Ci.nsIAppWindow).XULBrowserWindow = null;
2636 window.browserDOMWindow = null;
2640 XPCOMUtils.defineLazyGetter(
2642 "_firstContentWindowPaintDeferred",
2643 () => PromiseUtils.defer()
2646 gBrowserInit.idleTasksFinishedPromise = new Promise(resolve => {
2647 gBrowserInit.idleTaskPromiseResolve = resolve;
2650 function HandleAppCommandEvent(evt) {
2651 switch (evt.command) {
2659 BrowserReloadSkipCache();
2662 if (XULBrowserWindow.stopCommand.getAttribute("disabled") != "true") {
2667 BrowserSearch.webSearch();
2670 SidebarUI.toggle("viewBookmarksSidebar");
2679 BrowserCloseTabOrWindow();
2682 gLazyFindCommand("onFindCommand");
2685 openHelpLink("firefox-help");
2688 BrowserOpenFileWindow();
2691 PrintUtils.startPrintWindow(gBrowser.selectedBrowser.browsingContext);
2694 saveBrowser(gBrowser.selectedBrowser);
2697 MailIntegration.sendLinkForBrowser(gBrowser.selectedBrowser);
2702 evt.stopPropagation();
2703 evt.preventDefault();
2706 function gotoHistoryIndex(aEvent) {
2707 aEvent = getRootEvent(aEvent);
2709 let index = aEvent.target.getAttribute("index");
2714 let where = whereToOpenLink(aEvent);
2716 if (where == "current") {
2717 // Normal click. Go there in the current tab and update session history.
2720 gBrowser.gotoIndex(index);
2726 // Modified click. Go there in a new tab/window.
2728 let historyindex = aEvent.target.getAttribute("historyindex");
2729 duplicateTabIn(gBrowser.selectedTab, where, Number(historyindex));
2733 function BrowserForward(aEvent) {
2734 let where = whereToOpenLink(aEvent, false, true);
2736 if (where == "current") {
2738 gBrowser.goForward();
2741 duplicateTabIn(gBrowser.selectedTab, where, 1);
2745 function BrowserBack(aEvent) {
2746 let where = whereToOpenLink(aEvent, false, true);
2748 if (where == "current") {
2753 duplicateTabIn(gBrowser.selectedTab, where, -1);
2757 function BrowserHandleBackspace() {
2758 switch (Services.prefs.getIntPref("browser.backspace_action")) {
2763 goDoCommand("cmd_scrollPageUp");
2768 function BrowserHandleShiftBackspace() {
2769 switch (Services.prefs.getIntPref("browser.backspace_action")) {
2774 goDoCommand("cmd_scrollPageDown");
2779 function BrowserStop() {
2780 gBrowser.webNavigation.stop(Ci.nsIWebNavigation.STOP_ALL);
2783 function BrowserReloadOrDuplicate(aEvent) {
2784 aEvent = getRootEvent(aEvent);
2785 let accelKeyPressed =
2786 AppConstants.platform == "macosx" ? aEvent.metaKey : aEvent.ctrlKey;
2787 var backgroundTabModifier = aEvent.button == 1 || accelKeyPressed;
2789 if (aEvent.shiftKey && !backgroundTabModifier) {
2790 BrowserReloadSkipCache();
2794 let where = whereToOpenLink(aEvent, false, true);
2795 if (where == "current") {
2798 duplicateTabIn(gBrowser.selectedTab, where);
2802 function BrowserReload() {
2803 if (gBrowser.currentURI.schemeIs("view-source")) {
2804 // Bug 1167797: For view source, we always skip the cache
2805 return BrowserReloadSkipCache();
2807 const reloadFlags = Ci.nsIWebNavigation.LOAD_FLAGS_NONE;
2808 BrowserReloadWithFlags(reloadFlags);
2811 const kSkipCacheFlags =
2812 Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_PROXY |
2813 Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_CACHE;
2814 function BrowserReloadSkipCache() {
2815 // Bypass proxy and cache.
2816 BrowserReloadWithFlags(kSkipCacheFlags);
2819 function BrowserHome(aEvent) {
2820 if (aEvent && "button" in aEvent && aEvent.button == 2) {
2821 // right-click: do nothing
2825 var homePage = HomePage.get(window);
2826 var where = whereToOpenLink(aEvent, false, true);
2828 var notifyObservers;
2830 // Don't load the home page in pinned or hidden tabs (e.g. Firefox View).
2832 where == "current" &&
2833 (gBrowser?.selectedTab.pinned || gBrowser?.selectedTab.hidden)
2838 // openTrustedLinkIn in utilityOverlay.js doesn't handle loading multiple pages
2841 // If we're going to load an initial page in the current tab as the
2842 // home page, we set initialPageLoadedFromURLBar so that the URL
2843 // bar is cleared properly (even during a remoteness flip).
2844 if (isInitialPage(homePage)) {
2845 gBrowser.selectedBrowser.initialPageLoadedFromUserAction = homePage;
2849 Services.scriptSecurityManager.getSystemPrincipal(),
2852 if (isBlankPageURL(homePage)) {
2855 gBrowser.selectedBrowser.focus();
2857 notifyObservers = true;
2858 aEvent?.preventDefault();
2862 urls = homePage.split("|");
2863 var loadInBackground = Services.prefs.getBoolPref(
2864 "browser.tabs.loadBookmarksInBackground",
2867 // The homepage observer event should only be triggered when the homepage opens
2868 // in the foreground. This is mostly to support the homepage changed by extension
2869 // doorhanger which doesn't currently support background pages. This may change in
2871 notifyObservers = !loadInBackground;
2872 gBrowser.loadTabs(urls, {
2873 inBackground: loadInBackground,
2874 triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
2877 if (!loadInBackground) {
2878 if (isBlankPageURL(homePage)) {
2881 gBrowser.selectedBrowser.focus();
2884 aEvent?.preventDefault();
2887 // OpenBrowserWindow will trigger the observer event, so no need to do so here.
2888 notifyObservers = false;
2889 OpenBrowserWindow();
2890 aEvent?.preventDefault();
2893 if (notifyObservers) {
2894 // A notification for when a user has triggered their homepage. This is used
2895 // to display a doorhanger explaining that an extension has modified the
2896 // homepage, if necessary. Observers are only notified if the homepage
2897 // becomes the active page.
2898 Services.obs.notifyObservers(null, "browser-open-homepage-start");
2902 function loadOneOrMoreURIs(aURIString, aTriggeringPrincipal, aCsp) {
2903 // we're not a browser window, pass the URI string to a new browser window
2904 if (window.location.href != AppConstants.BROWSER_CHROME_URL) {
2906 AppConstants.BROWSER_CHROME_URL,
2914 // This function throws for certain malformed URIs, so use exception handling
2915 // so that we don't disrupt startup
2917 gBrowser.loadTabs(aURIString.split("|"), {
2918 inBackground: false,
2920 triggeringPrincipal: aTriggeringPrincipal,
2926 function openLocation(event) {
2927 if (window.location.href == AppConstants.BROWSER_CHROME_URL) {
2929 gURLBar.view.autoOpen({ event });
2933 // If there's an open browser window, redirect the command there.
2934 let win = getTopWin();
2941 // There are no open browser windows; open a new one.
2943 AppConstants.BROWSER_CHROME_URL,
2945 "chrome,all,dialog=no",
2950 function BrowserOpenTab(event) {
2952 let relatedToCurrent = false;
2955 where = whereToOpenLink(event, false, true);
2960 // When accel-click or middle-click are used, open the new tab as
2961 // related to the current tab.
2962 relatedToCurrent = true;
2970 // A notification intended to be useful for modular peformance tracking
2971 // starting as close as is reasonably possible to the time when the user
2972 // expressed the intent to open a new tab. Since there are a lot of
2973 // entry points, this won't catch every single tab created, but most
2974 // initiated by the user should go through here.
2976 // Note 1: This notification gets notified with a promise that resolves
2977 // with the linked browser when the tab gets created
2978 // Note 2: This is also used to notify a user that an extension has changed
2979 // the New Tab page.
2980 Services.obs.notifyObservers(
2982 wrappedJSObject: new Promise(resolve => {
2983 openTrustedLinkIn(BROWSER_NEW_TAB_URL, where, {
2985 resolveOnNewTabCreated: resolve,
2989 "browser-open-newtab-start"
2993 var gLastOpenDirectory = {
2996 if (!this._lastDir || !this._lastDir.exists()) {
2998 this._lastDir = Services.prefs.getComplexValue(
2999 "browser.open.lastDir",
3002 if (!this._lastDir.exists()) {
3003 this._lastDir = null;
3007 return this._lastDir;
3011 if (!val || !val.isDirectory()) {
3017 this._lastDir = val.clone();
3019 // Don't save the last open directory pref inside the Private Browsing mode
3020 if (!PrivateBrowsingUtils.isWindowPrivate(window)) {
3021 Services.prefs.setComplexValue(
3022 "browser.open.lastDir",
3029 this._lastDir = null;
3033 function BrowserOpenFileWindow() {
3034 // Get filepicker component.
3036 const nsIFilePicker = Ci.nsIFilePicker;
3037 let fp = Cc["@mozilla.org/filepicker;1"].createInstance(nsIFilePicker);
3038 let fpCallback = function fpCallback_done(aResult) {
3039 if (aResult == nsIFilePicker.returnOK) {
3042 gLastOpenDirectory.path = fp.file.parent.QueryInterface(Ci.nsIFile);
3045 openTrustedLinkIn(fp.fileURL.spec, "current");
3051 gNavigatorBundle.getString("openFile"),
3052 nsIFilePicker.modeOpen
3055 nsIFilePicker.filterAll |
3056 nsIFilePicker.filterText |
3057 nsIFilePicker.filterImages |
3058 nsIFilePicker.filterXML |
3059 nsIFilePicker.filterHTML
3061 fp.displayDirectory = gLastOpenDirectory.path;
3062 fp.open(fpCallback);
3066 function BrowserCloseTabOrWindow(event) {
3067 // If we're not a browser window, just close the window.
3068 if (window.location.href != AppConstants.BROWSER_CHROME_URL) {
3073 // In a multi-select context, close all selected tabs
3074 if (gBrowser.multiSelectedTabsCount) {
3075 gBrowser.removeMultiSelectedTabs();
3079 // Keyboard shortcuts that would close a tab that is pinned select the first
3080 // unpinned tab instead.
3083 (event.ctrlKey || event.metaKey || event.altKey) &&
3084 gBrowser.selectedTab.pinned
3086 if (gBrowser.visibleTabs.length > gBrowser._numPinnedTabs) {
3087 gBrowser.tabContainer.selectedIndex = gBrowser._numPinnedTabs;
3092 // If the current tab is the last one, this will close the window.
3093 gBrowser.removeCurrentTab({ animate: true });
3096 function BrowserTryToCloseWindow(event) {
3097 if (WindowIsClosing(event)) {
3099 } // WindowIsClosing does all the necessary checks
3106 allowThirdPartyFixup,
3109 originStoragePrincipal,
3110 forceAboutBlankViewerInCurrent,
3111 triggeringPrincipal,
3112 allowInheritPrincipal = false,
3115 if (!triggeringPrincipal) {
3116 throw new Error("Must load with a triggering Principal");
3120 openLinkIn(uri, "current", {
3123 allowThirdPartyFixup,
3126 originStoragePrincipal,
3127 triggeringPrincipal,
3129 forceAboutBlankViewerInCurrent,
3130 allowInheritPrincipal,
3137 function getLoadContext() {
3138 return window.docShell.QueryInterface(Ci.nsILoadContext);
3141 function readFromClipboard() {
3145 // Create transferable that will transfer the text.
3146 var trans = Cc["@mozilla.org/widget/transferable;1"].createInstance(
3149 trans.init(getLoadContext());
3151 trans.addDataFlavor("text/unicode");
3153 // If available, use selection clipboard, otherwise global one
3154 if (Services.clipboard.supportsSelectionClipboard()) {
3155 Services.clipboard.getData(trans, Services.clipboard.kSelectionClipboard);
3157 Services.clipboard.getData(trans, Services.clipboard.kGlobalClipboard);
3161 trans.getTransferData("text/unicode", data);
3164 data = data.value.QueryInterface(Ci.nsISupportsString);
3173 * Open the View Source dialog.
3176 * An object with the following properties:
3179 * A string URL for the page we'd like to view the source of.
3180 * browser (optional):
3181 * The browser containing the document that we would like to view the
3182 * source of. This is required if outerWindowID is passed.
3183 * outerWindowID (optional):
3184 * The outerWindowID of the content window containing the document that
3185 * we want to view the source of. You only need to provide this if you
3186 * want to attempt to retrieve the document source from the network
3188 * lineNumber (optional):
3189 * The line number to focus on once the source is loaded.
3191 async function BrowserViewSourceOfDocument(args) {
3192 // Check if external view source is enabled. If so, try it. If it fails,
3193 // fallback to internal view source.
3194 if (Services.prefs.getBoolPref("view_source.editor.external")) {
3196 await top.gViewSourceUtils.openInExternalEditor(args);
3201 let tabBrowser = gBrowser;
3202 let preferredRemoteType;
3203 let initialBrowsingContextGroupId;
3205 preferredRemoteType = args.browser.remoteType;
3206 initialBrowsingContextGroupId = args.browser.browsingContext.group.id;
3210 "BrowserViewSourceOfDocument should be passed the " +
3211 "subject browser if called from a window without " +
3215 // Some internal URLs (such as specific chrome: and about: URLs that are
3216 // not yet remote ready) cannot be loaded in a remote browser. View
3217 // source in tab expects the new view source browser's remoteness to match
3218 // that of the original URL, so disable remoteness if necessary for this
3220 var oa = E10SUtils.predictOriginAttributes({ window });
3221 preferredRemoteType = E10SUtils.getRemoteTypeForURI(
3223 gMultiProcessBrowser,
3225 E10SUtils.DEFAULT_REMOTE_TYPE,
3231 // In the case of popups, we need to find a non-popup browser window.
3232 if (!tabBrowser || !window.toolbar.visible) {
3233 // This returns only non-popup browser windows by default.
3234 let browserWindow = BrowserWindowTracker.getTopWindow();
3235 tabBrowser = browserWindow.gBrowser;
3238 const inNewWindow = !Services.prefs.getBoolPref("view_source.tab");
3240 // `viewSourceInBrowser` will load the source content from the page
3241 // descriptor for the tab (when possible) or fallback to the network if
3242 // that fails. Either way, the view source module will manage the tab's
3243 // location, so use "about:blank" here to avoid unnecessary redundant
3245 let tab = tabBrowser.loadOneTab("about:blank", {
3246 relatedToCurrent: true,
3247 inBackground: inNewWindow,
3248 skipAnimation: inNewWindow,
3249 preferredRemoteType,
3250 initialBrowsingContextGroupId,
3251 triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
3254 args.viewSourceBrowser = tabBrowser.getBrowserForTab(tab);
3255 top.gViewSourceUtils.viewSourceInBrowser(args);
3258 tabBrowser.hideTab(tab);
3259 tabBrowser.replaceTabWithWindow(tab);
3264 * Opens the View Source dialog for the source loaded in the root
3265 * top-level document of the browser. This is really just a
3266 * convenience wrapper around BrowserViewSourceOfDocument.
3269 * The browser that we want to load the source of.
3271 function BrowserViewSource(browser) {
3272 BrowserViewSourceOfDocument({
3274 outerWindowID: browser.outerWindowID,
3275 URL: browser.currentURI.spec,
3279 // documentURL - URL of the document to view, or null for this window's document
3280 // initialTab - name of the initial tab to display, or null for the first tab
3281 // imageElement - image to load in the Media Tab of the Page Info window; can be null/omitted
3282 // browsingContext - the browsingContext of the frame that we want to view information about; can be null/omitted
3283 // browser - the browser containing the document we're interested in inspecting; can be null/omitted
3284 function BrowserPageInfo(
3291 if (HTMLDocument.isInstance(documentURL)) {
3293 "Please pass the location URL instead of the document " +
3294 "to BrowserPageInfo() as the first argument.",
3295 "https://bugzilla.mozilla.org/show_bug.cgi?id=1238180"
3297 documentURL = documentURL.location;
3300 let args = { initialTab, imageElement, browsingContext, browser };
3302 documentURL = documentURL || window.gBrowser.selectedBrowser.currentURI.spec;
3304 let isPrivate = PrivateBrowsingUtils.isWindowPrivate(window);
3306 // Check for windows matching the url
3307 for (let currentWindow of Services.wm.getEnumerator("Browser:page-info")) {
3308 if (currentWindow.closed) {
3312 currentWindow.document.documentElement.getAttribute("relatedUrl") ==
3314 PrivateBrowsingUtils.isWindowPrivate(currentWindow) == isPrivate
3316 currentWindow.focus();
3317 currentWindow.resetPageInfo(args);
3318 return currentWindow;
3322 // We didn't find a matching window, so open a new one.
3323 let options = "chrome,toolbar,dialog=no,resizable";
3325 // Ensure the window groups correctly in the Windows taskbar
3327 options += ",private";
3330 "chrome://browser/content/pageinfo/pageInfo.xhtml",
3337 function UpdateUrlbarSearchSplitterState() {
3338 var splitter = document.getElementById("urlbar-search-splitter");
3339 var urlbar = document.getElementById("urlbar-container");
3340 var searchbar = document.getElementById("search-container");
3342 if (document.documentElement.getAttribute("customizing") == "true") {
3349 // If the splitter is already in the right place, we don't need to do anything:
3352 ((splitter.nextElementSibling == searchbar &&
3353 splitter.previousElementSibling == urlbar) ||
3354 (splitter.nextElementSibling == urlbar &&
3355 splitter.previousElementSibling == searchbar))
3361 let resizebefore = "none";
3362 let resizeafter = "none";
3363 if (urlbar && searchbar) {
3364 if (urlbar.nextElementSibling == searchbar) {
3365 resizeafter = "sibling";
3366 ibefore = searchbar;
3367 } else if (searchbar.nextElementSibling == urlbar) {
3368 resizebefore = "sibling";
3375 splitter = document.createXULElement("splitter");
3376 splitter.id = "urlbar-search-splitter";
3377 splitter.setAttribute("resizebefore", resizebefore);
3378 splitter.setAttribute("resizeafter", resizeafter);
3379 splitter.setAttribute("skipintoolbarset", "true");
3380 splitter.setAttribute("overflows", "false");
3381 splitter.className = "chromeclass-toolbar-additional";
3383 urlbar.parentNode.insertBefore(splitter, ibefore);
3384 } else if (splitter) {
3389 function UpdatePopupNotificationsVisibility() {
3390 // Only need to update PopupNotifications if it has already been initialized
3391 // for this window (i.e. its getter no longer exists).
3392 if (!Object.getOwnPropertyDescriptor(window, "PopupNotifications").get) {
3393 // Notify PopupNotifications that the visible anchors may have changed. This
3394 // also checks the suppression state according to the "shouldSuppress"
3395 // function defined earlier in this file.
3396 PopupNotifications.anchorVisibilityChange();
3399 // This is similar to the above, but for notifications attached to the
3400 // hamburger menu icon (such as update notifications and add-on install
3402 PanelUI?.updateNotifications();
3405 function PageProxyClickHandler(aEvent) {
3406 if (aEvent.button == 1 && Services.prefs.getBoolPref("middlemouse.paste")) {
3407 middleMousePaste(aEvent);
3412 * Handle command events bubbling up from error page content
3413 * or from about:newtab or from remote error pages that invoke
3414 * us via async messaging.
3416 var BrowserOnClick = {
3417 ignoreWarningLink(reason, blockedInfo, browsingContext) {
3418 let triggeringPrincipal =
3419 blockedInfo.triggeringPrincipal ||
3420 _createNullPrincipalFromTabUserContextId();
3422 // Allow users to override and continue through to the site,
3423 // but add a notify bar as a reminder, so that they don't lose
3424 // track after, e.g., tab switching.
3425 browsingContext.loadURI(blockedInfo.uri, {
3426 triggeringPrincipal,
3427 flags: Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_CLASSIFIER,
3430 // We can't use browser.contentPrincipal which is principal of about:blocked
3431 // Create one from uri with current principal origin attributes
3432 let principal = Services.scriptSecurityManager.createContentPrincipal(
3433 Services.io.newURI(blockedInfo.uri),
3434 browsingContext.currentWindowGlobal.documentPrincipal.originAttributes
3436 Services.perms.addFromPrincipal(
3439 Ci.nsIPermissionManager.ALLOW_ACTION,
3440 Ci.nsIPermissionManager.EXPIRE_SESSION
3445 label: gNavigatorBundle.getString(
3446 "safebrowsing.getMeOutOfHereButton.label"
3448 accessKey: gNavigatorBundle.getString(
3449 "safebrowsing.getMeOutOfHereButton.accessKey"
3452 getMeOutOfHere(browsingContext);
3458 if (reason === "malware") {
3459 let reportUrl = gSafeBrowsing.getReportURL("MalwareMistake", blockedInfo);
3460 title = gNavigatorBundle.getString("safebrowsing.reportedAttackSite");
3461 // There's no button if we can not get report url, for example if the provider
3462 // of blockedInfo is not Google
3465 label: gNavigatorBundle.getString(
3466 "safebrowsing.notAnAttackButton.label"
3468 accessKey: gNavigatorBundle.getString(
3469 "safebrowsing.notAnAttackButton.accessKey"
3472 openTrustedLinkIn(reportUrl, "tab");
3476 } else if (reason === "phishing") {
3477 let reportUrl = gSafeBrowsing.getReportURL("PhishMistake", blockedInfo);
3478 title = gNavigatorBundle.getString("safebrowsing.deceptiveSite");
3479 // There's no button if we can not get report url, for example if the provider
3480 // of blockedInfo is not Google
3483 label: gNavigatorBundle.getString(
3484 "safebrowsing.notADeceptiveSiteButton.label"
3486 accessKey: gNavigatorBundle.getString(
3487 "safebrowsing.notADeceptiveSiteButton.accessKey"
3490 openTrustedLinkIn(reportUrl, "tab");
3494 } else if (reason === "unwanted") {
3495 title = gNavigatorBundle.getString("safebrowsing.reportedUnwantedSite");
3496 // There is no button for reporting errors since Google doesn't currently
3497 // provide a URL endpoint for these reports.
3498 } else if (reason === "harmful") {
3499 title = gNavigatorBundle.getString("safebrowsing.reportedHarmfulSite");
3500 // There is no button for reporting errors since Google doesn't currently
3501 // provide a URL endpoint for these reports.
3504 SafeBrowsingNotificationBox.show(title, buttons);
3509 * Re-direct the browser to a known-safe page. This function is
3510 * used when, for example, the user browses to a known malware page
3511 * and is presented with about:blocked. The "Get me out of here!"
3512 * button should take the user to the default start page so that even
3513 * when their own homepage is infected, we can get them somewhere safe.
3515 function getMeOutOfHere(browsingContext) {
3516 browsingContext.top.loadURI(getDefaultHomePage(), {
3517 triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(), // Also needs to load homepage
3522 * Return the default start page for the cases when the user's own homepage is
3523 * infected, so we can get them somewhere safe.
3525 function getDefaultHomePage() {
3526 let url = BROWSER_NEW_TAB_URL;
3527 if (PrivateBrowsingUtils.isWindowPrivate(window)) {
3530 url = HomePage.getDefault();
3531 // If url is a pipe-delimited set of pages, just take the first one.
3532 if (url.includes("|")) {
3533 url = url.split("|")[0];
3538 function BrowserFullScreen() {
3539 window.fullScreen = !window.fullScreen || BrowserHandler.kiosk;
3542 function BrowserReloadWithFlags(reloadFlags) {
3543 let unchangedRemoteness = [];
3545 for (let tab of gBrowser.selectedTabs) {
3546 let browser = tab.linkedBrowser;
3547 let url = browser.currentURI.spec;
3548 // We need to cache the content principal here because the browser will be
3549 // reconstructed when the remoteness changes and the content prinicpal will
3550 // be cleared after reconstruction.
3551 let principal = tab.linkedBrowser.contentPrincipal;
3552 if (gBrowser.updateBrowserRemotenessByURL(browser, url)) {
3553 // If the remoteness has changed, the new browser doesn't have any
3554 // information of what was loaded before, so we need to load the previous
3556 if (tab.linkedPanel) {
3557 loadBrowserURI(browser, url, principal);
3559 // Shift to fully loaded browser and make
3560 // sure load handler is instantiated.
3561 tab.addEventListener(
3563 () => loadBrowserURI(browser, url, principal),
3566 gBrowser._insertBrowser(tab);
3569 unchangedRemoteness.push(tab);
3573 if (!unchangedRemoteness.length) {
3577 // Reset temporary permissions on the remaining tabs to reload.
3578 // This is done here because we only want to reset
3579 // permissions on user reload.
3580 for (let tab of unchangedRemoteness) {
3581 SitePermissions.clearTemporaryBlockPermissions(tab.linkedBrowser);
3582 // Also reset DOS mitigations for the basic auth prompt on reload.
3583 delete tab.linkedBrowser.authPromptAbuseCounter;
3585 gIdentityHandler.hidePopup();
3586 gPermissionPanel.hidePopup();
3588 let handlingUserInput = document.hasValidTransientUserGestureActivation;
3590 for (let tab of unchangedRemoteness) {
3591 if (tab.linkedPanel) {
3592 sendReloadMessage(tab);
3594 // Shift to fully loaded browser and make
3595 // sure load handler is instantiated.
3596 tab.addEventListener("SSTabRestoring", () => sendReloadMessage(tab), {
3599 gBrowser._insertBrowser(tab);
3603 function loadBrowserURI(browser, url, principal) {
3604 browser.loadURI(url, {
3606 triggeringPrincipal: principal,
3610 function sendReloadMessage(tab) {
3611 tab.linkedBrowser.sendMessageToActor(
3613 { flags: reloadFlags, handlingUserInput },
3619 // TODO: can we pull getPEMString in from pippki.js instead of
3620 // duplicating them here?
3621 function getPEMString(cert) {
3622 var derb64 = cert.getBase64DERString();
3623 // Wrap the Base64 string into lines of 64 characters,
3624 // with CRLF line breaks (as specified in RFC 1421).
3625 var wrapped = derb64.replace(/(\S{64}(?!$))/g, "$1\r\n");
3627 "-----BEGIN CERTIFICATE-----\r\n" +
3629 "\r\n-----END CERTIFICATE-----\r\n"
3633 var browserDragAndDrop = {
3634 canDropLink: aEvent => Services.droppedLinkHandler.canDropLink(aEvent, true),
3637 if (this.canDropLink(aEvent)) {
3638 aEvent.preventDefault();
3642 getTriggeringPrincipal(aEvent) {
3643 return Services.droppedLinkHandler.getTriggeringPrincipal(aEvent);
3647 return Services.droppedLinkHandler.getCSP(aEvent);
3650 validateURIsForDrop(aEvent, aURIs) {
3651 return Services.droppedLinkHandler.validateURIsForDrop(aEvent, aURIs);
3654 dropLinks(aEvent, aDisallowInherit) {
3655 return Services.droppedLinkHandler.dropLinks(aEvent, aDisallowInherit);
3659 var homeButtonObserver = {
3661 // disallow setting home pages that inherit the principal
3662 let links = browserDragAndDrop.dropLinks(aEvent, true);
3665 for (let link of links) {
3666 if (link.url.includes("|")) {
3667 urls.push(...link.url.split("|"));
3669 urls.push(link.url);
3674 browserDragAndDrop.validateURIsForDrop(aEvent, urls);
3679 setTimeout(openHomeDialog, 0, urls.join("|"));
3683 onDragOver(aEvent) {
3684 if (HomePage.locked) {
3687 browserDragAndDrop.dragOver(aEvent);
3688 aEvent.dropEffect = "link";
3692 function openHomeDialog(aURL) {
3693 var promptTitle = gNavigatorBundle.getString("droponhometitle");
3695 if (aURL.includes("|")) {
3696 promptMsg = gNavigatorBundle.getString("droponhomemsgMultiple");
3698 promptMsg = gNavigatorBundle.getString("droponhomemsg");
3701 var pressedVal = Services.prompt.confirmEx(
3705 Services.prompt.STD_YES_NO_BUTTONS,
3713 if (pressedVal == 0) {
3714 HomePage.set(aURL).catch(Cu.reportError);
3718 var newTabButtonObserver = {
3719 onDragOver(aEvent) {
3720 browserDragAndDrop.dragOver(aEvent);
3722 async onDrop(aEvent) {
3723 let links = browserDragAndDrop.dropLinks(aEvent);
3726 Services.prefs.getIntPref("browser.tabs.maxOpenBeforeWarn")
3728 // Sync dialog cannot be used inside drop event handler.
3729 let answer = await OpenInTabsUtils.promiseConfirmOpenInTabs(
3738 let where = aEvent.shiftKey ? "tabshifted" : "tab";
3739 let triggeringPrincipal = browserDragAndDrop.getTriggeringPrincipal(aEvent);
3740 let csp = browserDragAndDrop.getCSP(aEvent);
3741 for (let link of links) {
3743 let data = await UrlbarUtils.getShortcutOrURIAndPostData(link.url);
3744 // Allow third-party services to fixup this URL.
3745 openLinkIn(data.url, where, {
3746 postData: data.postData,
3747 allowThirdPartyFixup: true,
3748 triggeringPrincipal,
3756 var newWindowButtonObserver = {
3757 onDragOver(aEvent) {
3758 browserDragAndDrop.dragOver(aEvent);
3760 async onDrop(aEvent) {
3761 let links = browserDragAndDrop.dropLinks(aEvent);
3764 Services.prefs.getIntPref("browser.tabs.maxOpenBeforeWarn")
3766 // Sync dialog cannot be used inside drop event handler.
3767 let answer = await OpenInTabsUtils.promiseConfirmOpenInTabs(
3776 let triggeringPrincipal = browserDragAndDrop.getTriggeringPrincipal(aEvent);
3777 let csp = browserDragAndDrop.getCSP(aEvent);
3778 for (let link of links) {
3780 let data = await UrlbarUtils.getShortcutOrURIAndPostData(link.url);
3781 // Allow third-party services to fixup this URL.
3782 openLinkIn(data.url, "window", {
3783 // TODO fix allowInheritPrincipal
3784 // (this is required by javascript: drop to the new window) Bug 1475201
3785 allowInheritPrincipal: true,
3786 postData: data.postData,
3787 allowThirdPartyFixup: true,
3788 triggeringPrincipal,
3796 const BrowserSearch = {
3797 _searchInitComplete: false,
3800 Services.obs.addObserver(this, "browser-search-engine-modified");
3803 delayedStartupInit() {
3804 // Asynchronously initialize the search service if necessary, to get the
3805 // current engine for working out the placeholder.
3806 this._updateURLBarPlaceholderFromDefaultEngine(
3807 PrivateBrowsingUtils.isWindowPrivate(window),
3808 // Delay the update for this until so that we don't change it while
3809 // the user is looking at it / isn't expecting it.
3812 this._searchInitComplete = true;
3817 Services.obs.removeObserver(this, "browser-search-engine-modified");
3820 observe(engine, topic, data) {
3821 // There are two kinds of search engine objects, nsISearchEngine objects and
3822 // plain { uri, title, icon } objects. `engine` in this method is the
3823 // former. The browser.engines and browser.hiddenEngines arrays are the
3824 // latter, and they're the engines offered by the the page in the browser.
3826 // The two types of engines are currently related by their titles/names,
3827 // although that may change; see bug 335102.
3828 let engineName = engine.wrappedJSObject.name;
3830 case "engine-removed":
3831 // An engine was removed from the search service. If a page is offering
3832 // the engine, then the engine needs to be added back to the corresponding
3833 // browser's offered engines.
3834 this._addMaybeOfferedEngine(engineName);
3836 case "engine-added":
3837 // An engine was added to the search service. If a page is offering the
3838 // engine, then the engine needs to be removed from the corresponding
3839 // browser's offered engines.
3840 this._removeMaybeOfferedEngine(engineName);
3842 case "engine-default":
3844 this._searchInitComplete &&
3845 !PrivateBrowsingUtils.isWindowPrivate(window)
3847 this._updateURLBarPlaceholder(engineName, false);
3850 case "engine-default-private":
3852 this._searchInitComplete &&
3853 PrivateBrowsingUtils.isWindowPrivate(window)
3855 this._updateURLBarPlaceholder(engineName, true);
3861 _addMaybeOfferedEngine(engineName) {
3862 let selectedBrowserOffersEngine = false;
3863 for (let browser of gBrowser.browsers) {
3864 for (let i = 0; i < (browser.hiddenEngines || []).length; i++) {
3865 if (browser.hiddenEngines[i].title == engineName) {
3866 if (!browser.engines) {
3867 browser.engines = [];
3869 browser.engines.push(browser.hiddenEngines[i]);
3870 browser.hiddenEngines.splice(i, 1);
3871 if (browser == gBrowser.selectedBrowser) {
3872 selectedBrowserOffersEngine = true;
3878 if (selectedBrowserOffersEngine) {
3879 this.updateOpenSearchBadge();
3883 _removeMaybeOfferedEngine(engineName) {
3884 let selectedBrowserOffersEngine = false;
3885 for (let browser of gBrowser.browsers) {
3886 for (let i = 0; i < (browser.engines || []).length; i++) {
3887 if (browser.engines[i].title == engineName) {
3888 if (!browser.hiddenEngines) {
3889 browser.hiddenEngines = [];
3891 browser.hiddenEngines.push(browser.engines[i]);
3892 browser.engines.splice(i, 1);
3893 if (browser == gBrowser.selectedBrowser) {
3894 selectedBrowserOffersEngine = true;
3900 if (selectedBrowserOffersEngine) {
3901 this.updateOpenSearchBadge();
3906 * Initializes the urlbar placeholder to the pre-saved engine name. We do this
3907 * via a preference, to avoid needing to synchronously init the search service.
3909 * This should be called around the time of DOMContentLoaded, so that it is
3910 * initialized quickly before the user sees anything.
3912 * Note: If the preference doesn't exist, we don't do anything as the default
3913 * placeholder is a string which doesn't have the engine name; however, this
3914 * can be overridden using the `force` parameter.
3916 * @param {Boolean} force If true and the preference doesn't exist, the
3917 * placeholder will be set to the default version
3918 * without an engine name ("Search or enter address").
3920 initPlaceHolder(force = false) {
3922 "browser.urlbar.placeholderName" +
3923 (PrivateBrowsingUtils.isWindowPrivate(window) ? ".private" : "");
3924 let engineName = Services.prefs.getStringPref(prefName, "");
3925 if (engineName || force) {
3926 // We can do this directly, since we know we're at DOMContentLoaded.
3927 this._setURLBarPlaceholder(engineName);
3932 * This is a wrapper around '_updateURLBarPlaceholder' that uses the
3933 * appropriate default engine to get the engine name.
3935 * @param {Boolean} isPrivate Set to true if this is a private window.
3936 * @param {Boolean} [delayUpdate] Set to true, to delay update until the
3937 * placeholder is not displayed.
3939 async _updateURLBarPlaceholderFromDefaultEngine(
3943 const getDefault = isPrivate
3944 ? Services.search.getDefaultPrivate
3945 : Services.search.getDefault;
3946 let defaultEngine = await getDefault();
3948 this._updateURLBarPlaceholder(defaultEngine.name, isPrivate, delayUpdate);
3952 * Updates the URLBar placeholder for the specified engine, delaying the
3953 * update if required. This also saves the current engine name in preferences
3954 * for the next restart.
3956 * Note: The engine name will only be displayed for built-in engines, as we
3957 * know they should have short names.
3959 * @param {String} engineName The search engine name to use for the update.
3960 * @param {Boolean} isPrivate Set to true if this is a private window.
3961 * @param {Boolean} [delayUpdate] Set to true, to delay update until the
3962 * placeholder is not displayed.
3964 _updateURLBarPlaceholder(engineName, isPrivate, delayUpdate = false) {
3966 throw new Error("Expected an engineName to be specified");
3969 const engine = Services.search.getEngineByName(engineName);
3971 "browser.urlbar.placeholderName" + (isPrivate ? ".private" : "");
3972 if (engine.isAppProvided) {
3973 Services.prefs.setStringPref(prefName, engineName);
3975 Services.prefs.clearUserPref(prefName);
3976 // Set the engine name to an empty string for non-default engines, which'll
3977 // make sure we display the default placeholder string.
3981 // Only delay if requested, and we're not displaying text in the URL bar
3983 if (delayUpdate && !gURLBar.value) {
3984 // Delays changing the URL Bar placeholder until the user is not going to be
3985 // seeing it, e.g. when there is a value entered in the bar, or if there is
3986 // a tab switch to a tab which has a url loaded. We delay the update until
3987 // the user is out of search mode since an alternative placeholder is used
3989 let placeholderUpdateListener = () => {
3990 if (gURLBar.value && !gURLBar.searchMode) {
3991 // By the time the user has switched, they may have changed the engine
3992 // again, so we need to call this function again but with the
3994 // No need to await for this to finish, we're in a listener here anyway.
3995 this._updateURLBarPlaceholderFromDefaultEngine(isPrivate, false);
3996 gURLBar.removeEventListener("input", placeholderUpdateListener);
3997 gBrowser.tabContainer.removeEventListener(
3999 placeholderUpdateListener
4004 gURLBar.addEventListener("input", placeholderUpdateListener);
4005 gBrowser.tabContainer.addEventListener(
4007 placeholderUpdateListener
4009 } else if (!gURLBar.searchMode) {
4010 this._setURLBarPlaceholder(engineName);
4015 * Sets the URLBar placeholder to either something based on the engine name,
4016 * or the default placeholder.
4018 * @param {String} name The name of the engine to use, an empty string if to
4019 * use the default placeholder.
4021 _setURLBarPlaceholder(name) {
4022 document.l10n.setAttributes(
4024 name ? "urlbar-placeholder-with-name" : "urlbar-placeholder",
4025 name ? { name } : undefined
4029 addEngine(browser, engine) {
4030 if (!this._searchInitComplete) {
4031 // We haven't finished initialising search yet. This means we can't
4032 // call getEngineByName here. Since this is only on start-up and unlikely
4033 // to happen in the normal case, we'll just return early rather than
4034 // trying to handle it asynchronously.
4037 // Check to see whether we've already added an engine with this title
4038 if (browser.engines) {
4039 if (browser.engines.some(e => e.title == engine.title)) {
4045 // If this engine (identified by title) is already in the list, add it
4046 // to the list of hidden engines rather than to the main list.
4047 if (Services.search.getEngineByName(engine.title)) {
4051 var engines = (hidden ? browser.hiddenEngines : browser.engines) || [];
4055 title: engine.title,
4057 return browser.mIconURL;
4062 browser.hiddenEngines = engines;
4064 browser.engines = engines;
4065 if (browser == gBrowser.selectedBrowser) {
4066 this.updateOpenSearchBadge();
4072 * Update the browser UI to show whether or not additional engines are
4073 * available when a page is loaded or the user switches tabs to a page that
4074 * has search engines.
4076 updateOpenSearchBadge() {
4077 gURLBar.addSearchEngineHelper.setEnginesFromBrowser(
4078 gBrowser.selectedBrowser
4081 var searchBar = this.searchBar;
4086 var engines = gBrowser.selectedBrowser.engines;
4087 if (engines && engines.length) {
4088 searchBar.setAttribute("addengines", "true");
4090 searchBar.removeAttribute("addengines");
4095 * Focuses the search bar if present on the toolbar, or the address bar,
4096 * putting it in search mode. Will do so in an existing non-popup browser
4097 * window or open a new one if necessary.
4099 webSearch: function BrowserSearch_webSearch() {
4101 window.location.href != AppConstants.BROWSER_CHROME_URL ||
4104 let win = getTopWin({ skipPopups: true });
4106 // If there's an open browser window, it should handle this command
4108 win.BrowserSearch.webSearch();
4110 // If there are no open browser windows, open a new one
4111 var observer = function(subject, topic, data) {
4112 if (subject == win) {
4113 BrowserSearch.webSearch();
4114 Services.obs.removeObserver(
4116 "browser-delayed-startup-finished"
4120 win = window.openDialog(
4121 AppConstants.BROWSER_CHROME_URL,
4123 "chrome,all,dialog=no",
4126 Services.obs.addObserver(observer, "browser-delayed-startup-finished");
4131 let focusUrlBarIfSearchFieldIsNotActive = function(aSearchBar) {
4132 if (!aSearchBar || document.activeElement != aSearchBar.textbox) {
4133 // Limit the results to search suggestions, like the search bar.
4134 gURLBar.searchModeShortcut();
4138 let searchBar = this.searchBar;
4139 let placement = CustomizableUI.getPlacementOfWidget("search-container");
4140 let focusSearchBar = () => {
4141 searchBar = this.searchBar;
4143 focusUrlBarIfSearchFieldIsNotActive(searchBar);
4148 ((searchBar.parentNode.getAttribute("overflowedItem") == "true" &&
4149 placement.area == CustomizableUI.AREA_NAVBAR) ||
4150 placement.area == CustomizableUI.AREA_FIXED_OVERFLOW_PANEL)
4152 let navBar = document.getElementById(CustomizableUI.AREA_NAVBAR);
4153 navBar.overflowable.show().then(focusSearchBar);
4157 if (window.fullScreen) {
4158 FullScreen.showNavToolbox();
4162 focusUrlBarIfSearchFieldIsNotActive(searchBar);
4166 * Loads a search results page, given a set of search terms. Uses the current
4167 * engine if the search bar is visible, or the default engine otherwise.
4170 * The search terms to use for the search.
4172 * String indicating where the search should load. Most commonly used
4173 * are 'tab' or 'window', defaults to 'current'.
4175 * Whether to use the Private Browsing mode default search engine.
4176 * Defaults to `false`.
4177 * @param purpose [optional]
4178 * A string meant to indicate the context of the search request. This
4179 * allows the search service to provide a different nsISearchSubmission
4180 * depending on e.g. where the search is triggered in the UI.
4181 * @param triggeringPrincipal
4182 * The principal to use for a new window or tab.
4184 * The content security policy to use for a new window or tab.
4185 * @param inBackground [optional]
4186 * Set to true for the tab to be loaded in the background, default false.
4187 * @param engine [optional]
4188 * The search engine to use for the search.
4189 * @param tab [optional]
4190 * The tab to show the search result.
4192 * @return engine The search engine used to perform a search, or null if no
4193 * search was performed.
4200 triggeringPrincipal,
4202 inBackground = false,
4206 if (!triggeringPrincipal) {
4208 "Required argument triggeringPrincipal missing within _loadSearch"
4214 ? await Services.search.getDefaultPrivate()
4215 : await Services.search.getDefault();
4218 let submission = engine.getSubmission(searchText, null, purpose); // HTML response
4220 // getSubmission can return null if the engine doesn't have a URL
4221 // with a text/html response type. This is unlikely (since
4222 // SearchService._addEngineToStore() should fail for such an engine),
4223 // but let's be on the safe side.
4228 openLinkIn(submission.uri.spec, where || "current", {
4229 private: usePrivate && !PrivateBrowsingUtils.isWindowPrivate(window),
4230 postData: submission.postData,
4232 relatedToCurrent: true,
4233 triggeringPrincipal,
4235 targetBrowser: tab?.linkedBrowser,
4236 globalHistoryOptions: {
4237 triggeringSearchEngine: engine.name,
4241 return { engine, url: submission.uri };
4245 * Perform a search initiated from the context menu.
4247 * This should only be called from the context menu. See
4248 * BrowserSearch.loadSearch for the preferred API.
4250 async loadSearchFromContext(
4253 triggeringPrincipal,
4257 event = getRootEvent(event);
4258 let where = whereToOpenLink(event);
4259 if (where == "current") {
4260 // override: historically search opens in new tab
4263 if (usePrivate && !PrivateBrowsingUtils.isWindowPrivate(window)) {
4266 let inBackground = Services.prefs.getBoolPref(
4267 "browser.search.context.loadInBackground"
4269 if (event.button == 1 || event.ctrlKey) {
4270 inBackground = !inBackground;
4273 let { engine, url } = await BrowserSearch._loadSearch(
4278 Services.scriptSecurityManager.createNullPrincipal(
4279 triggeringPrincipal.originAttributes
4286 BrowserSearchTelemetry.recordSearch(
4287 gBrowser.selectedBrowser,
4296 * Perform a search initiated from the command line.
4298 async loadSearchFromCommandLine(terms, usePrivate, triggeringPrincipal, csp) {
4299 let { engine, url } = await BrowserSearch._loadSearch(
4304 triggeringPrincipal,
4308 BrowserSearchTelemetry.recordSearch(
4309 gBrowser.selectedBrowser,
4318 * Perform a search initiated from an extension.
4320 async loadSearchFromExtension(terms, engine, tab, triggeringPrincipal) {
4321 const result = await BrowserSearch._loadSearch(
4323 tab ? "current" : "tab",
4324 PrivateBrowsingUtils.isWindowPrivate(window),
4326 triggeringPrincipal,
4333 BrowserSearchTelemetry.recordSearch(
4334 gBrowser.selectedBrowser,
4342 * Returns the search bar element if it is present in the toolbar, null otherwise.
4345 return document.getElementById("searchbar");
4349 * Infobar to notify the user's search engine has been removed
4350 * and replaced with an application default search engine.
4352 * @param {string} oldEngine
4353 * name of the engine to be moved and replaced.
4354 * @param {string} newEngine
4355 * name of the application default engine to replaced the removed engine.
4357 removalOfSearchEngineNotificationBox(oldEngine, newEngine) {
4358 let messageFragment = document.createDocumentFragment();
4359 let message = document.createElement("span");
4360 let link = document.createXULElement("label", {
4364 link.href = Services.urlFormatter.formatURLPref(
4365 "browser.search.searchEngineRemoval"
4367 link.setAttribute("data-l10n-name", "remove-search-engine-article");
4368 document.l10n.setAttributes(message, "removed-search-engine-message", {
4373 message.appendChild(link);
4374 messageFragment.appendChild(message);
4378 "l10n-id": "remove-search-engine-button",
4381 const notificationBox = gNotificationBox.getNotificationWithValue(
4382 "search-engine-removal"
4384 gNotificationBox.removeNotification(notificationBox);
4389 gNotificationBox.appendNotification(
4390 "search-engine-removal",
4392 label: messageFragment,
4393 priority: gNotificationBox.PRIORITY_SYSTEM,
4398 // Update engine name in the placeholder to the new default engine name.
4399 this._updateURLBarPlaceholderFromDefaultEngine(
4400 PrivateBrowsingUtils.isWindowPrivate(window),
4402 ).catch(Cu.reportError);
4406 XPCOMUtils.defineConstant(this, "BrowserSearch", BrowserSearch);
4408 function CreateContainerTabMenu(event) {
4409 createUserContextMenu(event, {
4410 useAccessKeys: false,
4411 showDefaultTab: true,
4415 function FillHistoryMenu(aParent) {
4416 // Lazily add the hover listeners on first showing and never remove them
4417 if (!aParent.hasStatusListener) {
4418 // Show history item's uri in the status bar when hovering, and clear on exit
4419 aParent.addEventListener("DOMMenuItemActive", function(aEvent) {
4420 // Only the current page should have the checked attribute, so skip it
4421 if (!aEvent.target.hasAttribute("checked")) {
4422 XULBrowserWindow.setOverLink(aEvent.target.getAttribute("uri"));
4425 aParent.addEventListener("DOMMenuItemInactive", function() {
4426 XULBrowserWindow.setOverLink("");
4429 aParent.hasStatusListener = true;
4432 // Remove old entries if any
4433 let children = aParent.children;
4434 for (var i = children.length - 1; i >= 0; --i) {
4435 if (children[i].hasAttribute("index")) {
4436 aParent.removeChild(children[i]);
4440 const MAX_HISTORY_MENU_ITEMS = 15;
4442 const tooltipBack = gNavigatorBundle.getString("tabHistory.goBack");
4443 const tooltipCurrent = gNavigatorBundle.getString("tabHistory.reloadCurrent");
4444 const tooltipForward = gNavigatorBundle.getString("tabHistory.goForward");
4446 function updateSessionHistory(sessionHistory, initial, ssInParent) {
4447 let count = ssInParent
4448 ? sessionHistory.count
4449 : sessionHistory.entries.length;
4453 // if there is only one entry now, close the popup.
4454 aParent.hidePopup();
4456 } else if (aParent.id != "backForwardMenu" && !aParent.parentNode.open) {
4457 // if the popup wasn't open before, but now needs to be, reopen the menu.
4458 // It should trigger FillHistoryMenu again. This might happen with the
4459 // delay from click-and-hold menus but skip this for the context menu
4460 // (backForwardMenu) rather than figuring out how the menu should be
4461 // positioned and opened as it is an extreme edgecase.
4462 aParent.parentNode.open = true;
4467 let index = sessionHistory.index;
4468 let half_length = Math.floor(MAX_HISTORY_MENU_ITEMS / 2);
4469 let start = Math.max(index - half_length, 0);
4471 start == 0 ? MAX_HISTORY_MENU_ITEMS : index + half_length + 1,
4475 start = Math.max(count - MAX_HISTORY_MENU_ITEMS, 0);
4478 let existingIndex = 0;
4480 for (let j = end - 1; j >= start; j--) {
4481 let entry = ssInParent
4482 ? sessionHistory.getEntryAtIndex(j)
4483 : sessionHistory.entries[j];
4484 // Explicitly check for "false" to stay backwards-compatible with session histories
4485 // from before the hasUserInteraction was implemented.
4487 BrowserUtils.navigationRequireUserInteraction &&
4488 entry.hasUserInteraction === false &&
4489 // Always allow going to the first and last navigation points.
4495 let uri = ssInParent ? entry.URI.spec : entry.url;
4498 existingIndex < children.length
4499 ? children[existingIndex]
4500 : document.createXULElement("menuitem");
4502 item.setAttribute("uri", uri);
4503 item.setAttribute("label", entry.title || uri);
4504 item.setAttribute("index", j);
4506 // Cache this so that gotoHistoryIndex doesn't need the original index
4507 item.setAttribute("historyindex", j - index);
4510 // Use list-style-image rather than the image attribute in order to
4511 // allow CSS to override this.
4512 item.style.listStyleImage = `url(page-icon:${uri})`;
4517 "unified-nav-back menuitem-iconic menuitem-with-favicon";
4518 item.setAttribute("tooltiptext", tooltipBack);
4519 } else if (j == index) {
4520 item.setAttribute("type", "radio");
4521 item.setAttribute("checked", "true");
4522 item.className = "unified-nav-current";
4523 item.setAttribute("tooltiptext", tooltipCurrent);
4526 "unified-nav-forward menuitem-iconic menuitem-with-favicon";
4527 item.setAttribute("tooltiptext", tooltipForward);
4530 if (!item.parentNode) {
4531 aParent.appendChild(item);
4538 let existingLength = children.length;
4539 while (existingIndex < existingLength) {
4540 aParent.removeChild(aParent.lastElementChild);
4546 // If session history in parent is available, use it. Otherwise, get the session history
4547 // from session store.
4548 let sessionHistory = gBrowser.selectedBrowser.browsingContext.sessionHistory;
4549 if (sessionHistory?.count) {
4550 // Don't show the context menu if there is only one item.
4551 if (sessionHistory.count <= 1) {
4555 updateSessionHistory(sessionHistory, true, true);
4557 sessionHistory = SessionStore.getSessionHistory(
4558 gBrowser.selectedTab,
4559 updateSessionHistory
4561 updateSessionHistory(sessionHistory, true, false);
4567 function BrowserDownloadsUI() {
4568 if (PrivateBrowsingUtils.isWindowPrivate(window)) {
4569 openTrustedLinkIn("about:downloads", "tab");
4571 PlacesCommandHook.showPlacesOrganizer("Downloads");
4575 function toOpenWindowByType(inType, uri, features) {
4576 var topWindow = Services.wm.getMostRecentWindow(inType);
4580 } else if (features) {
4581 window.open(uri, "_blank", features);
4586 "chrome,extrachrome,menubar,resizable,scrollbars,status,toolbar"
4592 * Open a new browser window.
4594 * @param {Object} options
4596 * private: A boolean indicating if the window should be
4598 * remote: A boolean indicating if the window should run
4599 * remote browser tabs or not. If omitted, the window
4600 * will choose the profile default state.
4601 * fission: A boolean indicating if the window should run
4602 * with fission enabled or not. If omitted, the window
4603 * will choose the profile default state.
4605 * @return a reference to the new window.
4607 function OpenBrowserWindow(options) {
4608 var telemetryObj = {};
4609 TelemetryStopwatch.start("FX_NEW_WINDOW_MS", telemetryObj);
4611 var defaultArgs = BrowserHandler.defaultArgs;
4612 var wintype = document.documentElement.getAttribute("windowtype");
4614 var extraFeatures = "";
4615 if (options && options.private && PrivateBrowsingUtils.enabled) {
4616 extraFeatures = ",private";
4617 if (!PrivateBrowsingUtils.permanentPrivateBrowsing) {
4618 // Force the new window to load about:privatebrowsing instead of the default home page
4619 defaultArgs = "about:privatebrowsing";
4622 extraFeatures = ",non-private";
4625 if (options && options.remote) {
4626 extraFeatures += ",remote";
4627 } else if (options && options.remote === false) {
4628 extraFeatures += ",non-remote";
4631 if (options && options.fission) {
4632 extraFeatures += ",fission";
4633 } else if (options && options.fission === false) {
4634 extraFeatures += ",non-fission";
4637 // If the window is maximized, we want to skip the animation, since we're
4638 // going to be taking up most of the screen anyways, and we want to optimize
4639 // for showing the user a useful window as soon as possible.
4640 if (window.windowState == window.STATE_MAXIMIZED) {
4641 extraFeatures += ",suppressanimation";
4644 // if and only if the current window is a browser window and it has a document with a character
4645 // set, then extract the current charset menu setting from the current document and use it to
4646 // initialize the new browser window...
4650 wintype == "navigator:browser" &&
4652 window.content.document
4654 var DocCharset = window.content.document.characterSet;
4655 let charsetArg = "charset=" + DocCharset;
4657 // we should "inherit" the charset menu setting in a new window
4658 win = window.openDialog(
4659 AppConstants.BROWSER_CHROME_URL,
4661 "chrome,all,dialog=no" + extraFeatures,
4666 // forget about the charset information.
4667 win = window.openDialog(
4668 AppConstants.BROWSER_CHROME_URL,
4670 "chrome,all,dialog=no" + extraFeatures,
4675 win.addEventListener(
4678 TelemetryStopwatch.finish("FX_NEW_WINDOW_MS", telemetryObj);
4680 Services.prefs.getIntPref("browser.startup.page") == 1 &&
4681 defaultArgs == HomePage.get()
4683 // A notification for when a user has triggered their homepage. This is used
4684 // to display a doorhanger explaining that an extension has modified the
4685 // homepage, if necessary.
4686 Services.obs.notifyObservers(win, "browser-open-homepage-start");
4696 * Update the global flag that tracks whether or not any edit UI (the Edit menu,
4697 * edit-related items in the context menu, and edit-related toolbar buttons
4698 * is visible, then update the edit commands' enabled state accordingly. We use
4699 * this flag to skip updating the edit commands on focus or selection changes
4700 * when no UI is visible to improve performance (including pageload performance,
4701 * since focus changes when you load a new page).
4703 * If UI is visible, we use goUpdateGlobalEditMenuItems to set the commands'
4704 * enabled state so the UI will reflect it appropriately.
4706 * If the UI isn't visible, we enable all edit commands so keyboard shortcuts
4707 * still work and just lazily disable them as needed when the user presses a
4710 * This doesn't work on Mac, since Mac menus flash when users press their
4711 * keyboard shortcuts, so edit UI is essentially always visible on the Mac,
4712 * and we need to always update the edit commands. Thus on Mac this function
4715 function updateEditUIVisibility() {
4716 if (AppConstants.platform == "macosx") {
4720 let editMenuPopupState = document.getElementById("menu_EditPopup").state;
4721 let contextMenuPopupState = document.getElementById("contentAreaContextMenu")
4723 let placesContextMenuPopupState = document.getElementById("placesContext")
4726 let oldVisible = gEditUIVisible;
4728 // The UI is visible if the Edit menu is opening or open, if the context menu
4729 // is open, or if the toolbar has been customized to include the Cut, Copy,
4730 // or Paste toolbar buttons.
4732 editMenuPopupState == "showing" ||
4733 editMenuPopupState == "open" ||
4734 contextMenuPopupState == "showing" ||
4735 contextMenuPopupState == "open" ||
4736 placesContextMenuPopupState == "showing" ||
4737 placesContextMenuPopupState == "open";
4738 const kOpenPopupStates = ["showing", "open"];
4739 if (!gEditUIVisible) {
4740 // Now check the edit-controls toolbar buttons.
4741 let placement = CustomizableUI.getPlacementOfWidget("edit-controls");
4742 let areaType = placement ? CustomizableUI.getAreaType(placement.area) : "";
4743 if (areaType == CustomizableUI.TYPE_MENU_PANEL) {
4744 let customizablePanel = PanelUI.overflowPanel;
4745 gEditUIVisible = kOpenPopupStates.includes(customizablePanel.state);
4747 areaType == CustomizableUI.TYPE_TOOLBAR &&
4748 window.toolbar.visible
4750 // The edit controls are on a toolbar, so they are visible,
4751 // unless they're in a panel that isn't visible...
4752 if (placement.area == "nav-bar") {
4753 let editControls = document.getElementById("edit-controls");
4755 !editControls.hasAttribute("overflowedItem") ||
4756 kOpenPopupStates.includes(
4757 document.getElementById("widget-overflow").state
4760 gEditUIVisible = true;
4765 // Now check the main menu panel
4766 if (!gEditUIVisible) {
4767 gEditUIVisible = kOpenPopupStates.includes(PanelUI.panel.state);
4770 // No need to update commands if the edit UI visibility has not changed.
4771 if (gEditUIVisible == oldVisible) {
4775 // If UI is visible, update the edit commands' enabled state to reflect
4776 // whether or not they are actually enabled for the current focus/selection.
4777 if (gEditUIVisible) {
4778 goUpdateGlobalEditMenuItems();
4780 // Otherwise, enable all commands, so that keyboard shortcuts still work,
4781 // then lazily determine their actual enabled state when the user presses
4782 // a keyboard shortcut.
4783 goSetCommandEnabled("cmd_undo", true);
4784 goSetCommandEnabled("cmd_redo", true);
4785 goSetCommandEnabled("cmd_cut", true);
4786 goSetCommandEnabled("cmd_copy", true);
4787 goSetCommandEnabled("cmd_paste", true);
4788 goSetCommandEnabled("cmd_selectAll", true);
4789 goSetCommandEnabled("cmd_delete", true);
4790 goSetCommandEnabled("cmd_switchTextDirection", true);
4796 * Updates User Context Menu Item UI visibility depending on
4797 * privacy.userContext.enabled pref state.
4799 updateUserContextUIVisibility() {
4800 let menu = document.getElementById("menu_newUserContext");
4801 menu.hidden = !Services.prefs.getBoolPref(
4802 "privacy.userContext.enabled",
4805 // Visibility of File menu item shouldn't change frequently.
4806 if (PrivateBrowsingUtils.isWindowPrivate(window)) {
4807 menu.setAttribute("disabled", "true");
4812 * Updates the enabled state of the "Import From Another Browser" command
4813 * depending on the DisableProfileImport policy.
4815 updateImportCommandEnabledState() {
4816 if (!Services.policies.isAllowed("profileImport")) {
4818 .getElementById("cmd_file_importFromAnotherBrowser")
4819 .setAttribute("disabled", "true");
4824 * Updates the "Close tab" command to reflect the number of selected tabs,
4827 updateTabCloseCountState() {
4828 document.l10n.setAttributes(
4829 document.getElementById("menu_close"),
4830 "menu-file-close-tab",
4831 { tabCount: gBrowser.selectedTabs.length }
4835 onPopupShowing(event) {
4836 // We don't care about submenus:
4837 if (event.target.id != "menu_FilePopup") {
4840 this.updateUserContextUIVisibility();
4841 this.updateImportCommandEnabledState();
4842 this.updateTabCloseCountState();
4843 if (AppConstants.platform == "macosx") {
4844 gShareUtils.updateShareURLMenuItem(
4845 gBrowser.selectedBrowser,
4846 document.getElementById("menu_savePage")
4849 PrintUtils.updatePrintSetupMenuHiddenState();
4855 * Updates a sharing item in a given menu, creating it if necessary.
4857 updateShareURLMenuItem(browser, insertAfterEl) {
4858 // We only support "share URL" on macOS and on Windows 10:
4860 AppConstants.platform != "macosx" &&
4861 // Windows 10's internal NT version number was initially 6.4
4862 !AppConstants.isPlatformAndVersionAtLeast("win", "6.4")
4867 let shareURL = insertAfterEl.nextElementSibling;
4868 if (!shareURL?.matches(".share-tab-url-item")) {
4869 shareURL = this._createShareURLMenuItem(insertAfterEl);
4872 shareURL.browserToShare = Cu.getWeakReference(browser);
4873 if (AppConstants.platform == "win") {
4874 // We disable the item on Windows, as there's no submenu.
4875 // On macOS, we handle this inside the menupopup.
4876 shareURL.hidden = !BrowserUtils.isShareableURL(browser.currentURI);
4881 * Creates and returns the "Share" menu item.
4883 _createShareURLMenuItem(insertAfterEl) {
4884 let menu = insertAfterEl.parentNode;
4885 let shareURL = null;
4886 if (AppConstants.platform == "win") {
4887 shareURL = this._buildShareURLItem(menu.id);
4888 } else if (AppConstants.platform == "macosx") {
4889 shareURL = this._buildShareURLMenu(menu.id);
4891 shareURL.className = "share-tab-url-item";
4894 menu.id == "tabContextMenu"
4895 ? "tab-context-share-url"
4896 : "menu-file-share-url";
4897 document.l10n.setAttributes(shareURL, l10nID);
4899 menu.insertBefore(shareURL, insertAfterEl.nextSibling);
4904 * Returns a menu item specifically for accessing Windows sharing services.
4906 _buildShareURLItem() {
4907 let shareURLMenuItem = document.createXULElement("menuitem");
4908 shareURLMenuItem.addEventListener("command", this);
4909 return shareURLMenuItem;
4913 * Returns a menu specifically for accessing macOSx sharing services .
4915 _buildShareURLMenu() {
4916 let menu = document.createXULElement("menu");
4917 let menuPopup = document.createXULElement("menupopup");
4918 menuPopup.addEventListener("popupshowing", this);
4919 menu.appendChild(menuPopup);
4924 * Get the sharing data for a given DOM node.
4926 getDataToShare(node) {
4927 let browser = node.browserToShare?.get();
4928 let urlToShare = null;
4929 let titleToShare = null;
4931 if (browser && BrowserUtils.isShareableURL(browser.currentURI)) {
4932 urlToShare = browser.currentURI;
4933 titleToShare = browser.contentTitle;
4935 return { urlToShare, titleToShare };
4939 * Populates the "Share" menupopup on macOSx.
4941 initializeShareURLPopup(menuPopup) {
4942 if (AppConstants.platform != "macosx") {
4947 while (menuPopup.firstChild) {
4948 menuPopup.firstChild.remove();
4951 let { urlToShare } = this.getDataToShare(menuPopup.parentNode);
4953 // If we can't share the current URL, we display the items disabled,
4954 // but enable the "more..." item at the bottom, to allow the user to
4955 // change sharing preferences in the system dialog.
4956 let shouldEnable = !!urlToShare;
4958 // Fake it so we can ask the sharing service for services:
4959 urlToShare = makeURI("https://mozilla.org/");
4962 let sharingService = gBrowser.MacSharingService;
4963 let currentURI = gURLBar.makeURIReadable(urlToShare).displaySpec;
4964 let services = sharingService.getSharingProviders(currentURI);
4966 services.forEach(share => {
4967 let item = document.createXULElement("menuitem");
4968 item.classList.add("menuitem-iconic");
4969 item.setAttribute("label", share.menuItemTitle);
4970 item.setAttribute("share-name", share.name);
4971 item.setAttribute("image", share.image);
4972 if (!shouldEnable) {
4973 item.setAttribute("disabled", "true");
4975 menuPopup.appendChild(item);
4977 menuPopup.appendChild(document.createXULElement("menuseparator"));
4978 let moreItem = document.createXULElement("menuitem");
4979 document.l10n.setAttributes(moreItem, "menu-share-more");
4980 moreItem.classList.add("menuitem-iconic", "share-more-button");
4981 menuPopup.appendChild(moreItem);
4983 menuPopup.addEventListener("command", this);
4984 menuPopup.parentNode
4985 .closest("menupopup")
4986 .addEventListener("popuphiding", this);
4987 menuPopup.setAttribute("data-initialized", true);
4990 onShareURLCommand(event) {
4991 // Only call sharing services for the "Share" menu item. These services
4992 // are accessed from a submenu popup for MacOS or the "Share" menu item
4993 // for Windows. Use .closest() as a hack to find either the item itself
4994 // or a parent with the right class.
4995 let target = event.target.closest(".share-tab-url-item");
5000 // urlToShare/titleToShare may be null, in which case only the "more"
5001 // item is enabled, so handle that case first:
5002 if (event.target.classList.contains("share-more-button")) {
5003 gBrowser.MacSharingService.openSharingPreferences();
5007 let { urlToShare, titleToShare } = this.getDataToShare(target);
5008 let currentURI = gURLBar.makeURIReadable(urlToShare).displaySpec;
5010 if (AppConstants.platform == "win") {
5011 WindowsUIUtils.shareUrl(currentURI, titleToShare);
5015 // On macOSX platforms
5016 let shareName = event.target.getAttribute("share-name");
5019 gBrowser.MacSharingService.shareUrl(shareName, currentURI, titleToShare);
5023 onPopupHiding(event) {
5024 // We don't want to rebuild the contents of the "Share" menupopup if only its submenu is
5025 // hidden. So bail if this isn't the top menupopup in the DOM tree:
5026 if (event.target.parentNode.closest("menupopup")) {
5029 // Otherwise, clear its "data-initialized" attribute.
5030 let menupopup = event.target.querySelector(".share-tab-url-item")
5032 menupopup?.removeAttribute("data-initialized");
5034 event.target.removeEventListener("popuphiding", this);
5037 onPopupShowing(event) {
5038 if (!event.target.hasAttribute("data-initialized")) {
5039 this.initializeShareURLPopup(event.target);
5043 handleEvent(aEvent) {
5044 switch (aEvent.type) {
5046 this.onShareURLCommand(aEvent);
5049 this.onPopupHiding(aEvent);
5051 case "popupshowing":
5052 this.onPopupShowing(aEvent);
5059 * Opens a new tab with the userContextId specified as an attribute of
5060 * sourceEvent. This attribute is propagated to the top level originAttributes
5061 * living on the tab's docShell.
5064 * A click event on a userContext File Menu option
5066 function openNewUserContextTab(event) {
5067 openTrustedLinkIn(BROWSER_NEW_TAB_URL, "tab", {
5068 userContextId: parseInt(event.target.getAttribute("data-usercontextid")),
5073 * Updates the User Context UI indicators if the browser is in a non-default context
5075 function updateUserContextUIIndicator() {
5076 function replaceContainerClass(classType, element, value) {
5077 let prefix = "identity-" + classType + "-";
5078 if (value && element.classList.contains(prefix + value)) {
5081 for (let className of element.classList) {
5082 if (className.startsWith(prefix)) {
5083 element.classList.remove(className);
5087 element.classList.add(prefix + value);
5091 let hbox = document.getElementById("userContext-icons");
5093 let userContextId = gBrowser.selectedBrowser.getAttribute("usercontextid");
5094 if (!userContextId) {
5095 replaceContainerClass("color", hbox, "");
5100 let identity = ContextualIdentityService.getPublicIdentityFromId(
5104 replaceContainerClass("color", hbox, "");
5109 replaceContainerClass("color", hbox, identity.color);
5111 let label = ContextualIdentityService.getUserContextLabel(userContextId);
5112 document.getElementById("userContext-label").setAttribute("value", label);
5113 // Also set the container label as the tooltip so we can only show the icon
5114 // in small windows.
5115 hbox.setAttribute("tooltiptext", label);
5117 let indicator = document.getElementById("userContext-indicator");
5118 replaceContainerClass("icon", indicator, identity.icon);
5120 hbox.hidden = false;
5123 var XULBrowserWindow = {
5124 // Stored Status, Link and Loading values
5132 QueryInterface: ChromeUtils.generateQI([
5133 "nsIWebProgressListener",
5134 "nsIWebProgressListener2",
5135 "nsISupportsWeakReference",
5136 "nsIXULBrowserWindow",
5140 delete this.stopCommand;
5141 return (this.stopCommand = document.getElementById("Browser:Stop"));
5143 get reloadCommand() {
5144 delete this.reloadCommand;
5145 return (this.reloadCommand = document.getElementById("Browser:Reload"));
5147 get _elementsForTextBasedTypes() {
5148 delete this._elementsForTextBasedTypes;
5149 return (this._elementsForTextBasedTypes = [
5150 document.getElementById("pageStyleMenu"),
5151 document.getElementById("context-viewpartialsource-selection"),
5152 document.getElementById("context-print-selection"),
5155 get _elementsForFind() {
5156 delete this._elementsForFind;
5157 return (this._elementsForFind = [
5158 document.getElementById("cmd_find"),
5159 document.getElementById("cmd_findAgain"),
5160 document.getElementById("cmd_findPrevious"),
5163 get _elementsForViewSource() {
5164 delete this._elementsForViewSource;
5165 return (this._elementsForViewSource = [
5166 document.getElementById("context-viewsource"),
5167 document.getElementById("View:PageSource"),
5170 get _menuItemForRepairTextEncoding() {
5171 delete this._menuItemForRepairTextEncoding;
5172 return (this._menuItemForRepairTextEncoding = document.getElementById(
5173 "repair-text-encoding"
5177 setDefaultStatus(status) {
5178 this.defaultStatus = status;
5179 StatusPanel.update();
5184 url = Services.textToSubURI.unEscapeURIForUI(url);
5186 // Encode bidirectional formatting characters.
5187 // (RFC 3987 sections 3.2 and 4.1 paragraph 6)
5189 /[\u200e\u200f\u202a\u202b\u202c\u202d\u202e]/g,
5193 if (UrlbarPrefs.get("trimURLs")) {
5194 url = BrowserUIUtils.trimURL(url);
5198 this.overLink = url;
5199 LinkTargetDisplay.update();
5202 showTooltip(xDevPix, yDevPix, tooltip, direction, browser) {
5204 Cc["@mozilla.org/widget/dragservice;1"]
5205 .getService(Ci.nsIDragService)
5206 .getCurrentSession()
5211 let elt = document.getElementById("remoteBrowserTooltip");
5212 elt.label = tooltip;
5213 elt.style.direction = direction;
5214 elt.openPopupAtScreen(
5215 xDevPix / window.devicePixelRatio,
5216 yDevPix / window.devicePixelRatio,
5223 let elt = document.getElementById("remoteBrowserTooltip");
5228 return gBrowser.tabs.length;
5250 return this.onProgressChange(
5260 // This function fires only for the currently selected tab.
5261 onStateChange(aWebProgress, aRequest, aStateFlags, aStatus) {
5262 const nsIWebProgressListener = Ci.nsIWebProgressListener;
5264 let browser = gBrowser.selectedBrowser;
5265 gProtectionsHandler.onStateChange(aWebProgress, aStateFlags);
5268 aStateFlags & nsIWebProgressListener.STATE_START &&
5269 aStateFlags & nsIWebProgressListener.STATE_IS_NETWORK
5271 if (aRequest && aWebProgress.isTopLevel) {
5272 // clear out search-engine data
5273 browser.engines = null;
5278 if (!(aStateFlags & nsIWebProgressListener.STATE_RESTORING)) {
5281 // XXX: This needs to be based on window activity...
5282 this.stopCommand.removeAttribute("disabled");
5283 CombinedStopReload.switchToStop(aRequest, aWebProgress);
5285 } else if (aStateFlags & nsIWebProgressListener.STATE_STOP) {
5286 // This (thanks to the filter) is a network stop or the last
5287 // request stop outside of loading the document, stop throbbers
5288 // and progress bars and such
5292 let canViewSource = true;
5293 // Get the URI either from a channel or a pseudo-object
5294 if (aRequest instanceof Ci.nsIChannel || "URI" in aRequest) {
5295 location = aRequest.URI;
5297 // For keyword URIs clear the user typed value since they will be changed into real URIs
5298 if (location.scheme == "keyword" && aWebProgress.isTopLevel) {
5299 gBrowser.userTypedValue = null;
5302 canViewSource = location.scheme != "view-source";
5304 if (location.spec != "about:blank") {
5306 case Cr.NS_ERROR_NET_TIMEOUT:
5307 msg = gNavigatorBundle.getString("nv_timeout");
5314 this.setDefaultStatus(msg);
5316 // Disable View Source menu entries for images, enable otherwise
5318 browser.documentContentType &&
5319 BrowserUtils.mimeTypeIsTextBased(browser.documentContentType);
5320 for (let element of this._elementsForViewSource) {
5321 if (canViewSource && isText) {
5322 element.removeAttribute("disabled");
5324 element.setAttribute("disabled", "true");
5328 this._updateElementsForContentType();
5330 // Update Override Text Encoding state.
5331 // Can't cache the button, because the presence of the element in the DOM
5332 // may change over time.
5333 let button = document.getElementById("characterencoding-button");
5334 if (browser.mayEnableCharacterEncodingMenu) {
5335 this._menuItemForRepairTextEncoding.removeAttribute("disabled");
5336 button?.removeAttribute("disabled");
5338 this._menuItemForRepairTextEncoding.setAttribute("disabled", "true");
5339 button?.setAttribute("disabled", "true");
5343 this.isBusy = false;
5346 this.busyUI = false;
5348 this.stopCommand.setAttribute("disabled", "true");
5349 CombinedStopReload.switchToReload(aRequest, aWebProgress);
5355 * An nsIWebProgressListener method called by tabbrowser. The `aIsSimulated`
5356 * parameter is extra and not declared in nsIWebProgressListener, however; see
5359 * @param {nsIWebProgress} aWebProgress
5360 * The nsIWebProgress instance that fired the notification.
5361 * @param {nsIRequest} aRequest
5362 * The associated nsIRequest. This may be null in some cases.
5363 * @param {nsIURI} aLocationURI
5364 * The URI of the location that is being loaded.
5365 * @param {integer} aFlags
5366 * Flags that indicate the reason the location changed. See the
5367 * nsIWebProgressListener.LOCATION_CHANGE_* values.
5368 * @param {boolean} aIsSimulated
5369 * True when this is called by tabbrowser due to switching tabs and
5370 * undefined otherwise. This parameter is not declared in
5371 * nsIWebProgressListener.onLocationChange; see bug 1478348.
5373 onLocationChange(aWebProgress, aRequest, aLocationURI, aFlags, aIsSimulated) {
5374 var location = aLocationURI ? aLocationURI.spec : "";
5376 UpdateBackForwardCommands(gBrowser.webNavigation);
5378 Services.obs.notifyObservers(
5380 "touchbar-location-change",
5384 // For most changes we only need to update the browser UI if the primary
5385 // content area was navigated or the selected tab was changed. We don't need
5386 // to do anything else if there was a subframe navigation.
5388 if (!aWebProgress.isTopLevel) {
5392 this.hideOverLinkImmediately = true;
5393 this.setOverLink("");
5394 this.hideOverLinkImmediately = false;
5396 let isSameDocument =
5397 aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT;
5399 (location == "about:blank" &&
5400 BrowserUIUtils.checkEmptyPageOrigin(gBrowser.selectedBrowser)) ||
5403 // Second condition is for new tabs, otherwise
5404 // reload function is enabled until tab is refreshed.
5405 this.reloadCommand.setAttribute("disabled", "true");
5407 this.reloadCommand.removeAttribute("disabled");
5410 let isSessionRestore = !!(
5411 aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SESSION_STORE
5414 // We want to update the popup visibility if we received this notification
5415 // via simulated locationchange events such as switching between tabs, however
5416 // if this is a document navigation then PopupNotifications will be updated
5417 // via TabsProgressListener.onLocationChange and we do not want it called twice
5418 gURLBar.setURI(aLocationURI, aIsSimulated, isSessionRestore);
5420 BookmarkingUI.onLocationChange();
5421 // If we've actually changed document, update the toolbar visibility.
5422 if (!isSameDocument) {
5423 let bookmarksToolbar = gNavToolbox.querySelector("#PersonalToolbar");
5424 setToolbarVisibility(
5426 gBookmarksToolbarVisibility,
5432 let closeOpenPanels = selector => {
5433 for (let panel of document.querySelectorAll(selector)) {
5434 if (panel.state != "closed") {
5440 // If the location is changed due to switching tabs,
5441 // ensure we close any open tabspecific panels.
5443 closeOpenPanels("panel[tabspecific='true']");
5446 // Ensure we close any remaining open locationspecific panels
5447 if (!isSameDocument) {
5448 closeOpenPanels("panel[locationspecific='true']");
5451 let screenshotsButtonsDisabled = gScreenshots.shouldScreenshotsButtonBeDisabled();
5452 Services.obs.notifyObservers(
5454 "toggle-screenshot-disable",
5455 screenshotsButtonsDisabled
5458 gPermissionPanel.onLocationChange();
5460 gProtectionsHandler.onLocationChange();
5462 BrowserPageActions.onLocationChange();
5464 SafeBrowsingNotificationBox.onLocationChange(aLocationURI);
5466 SaveToPocket.onLocationChange(window);
5468 UrlbarProviderSearchTips.onLocationChange(
5475 gTabletModePageCounter.inc();
5477 this._updateElementsForContentType();
5479 this._updateMacUserActivity(window, aLocationURI, aWebProgress);
5481 // Unconditionally disable the Text Encoding button during load to
5482 // keep the UI calm when navigating from one modern page to another and
5483 // the toolbar button is visible.
5484 // Can't cache the button, because the presence of the element in the DOM
5485 // may change over time.
5486 let button = document.getElementById("characterencoding-button");
5487 this._menuItemForRepairTextEncoding.setAttribute("disabled", "true");
5488 button?.setAttribute("disabled", "true");
5490 // Try not to instantiate gCustomizeMode as much as possible,
5491 // so don't use CustomizeMode.jsm to check for URI or customizing.
5493 location == "about:blank" &&
5494 gBrowser.selectedTab.hasAttribute("customizemode")
5496 gCustomizeMode.enter();
5498 CustomizationHandler.isEnteringCustomizeMode ||
5499 CustomizationHandler.isCustomizing()
5501 gCustomizeMode.exit();
5504 CFRPageActions.updatePageActions(gBrowser.selectedBrowser);
5506 AboutReaderParent.updateReaderButton(gBrowser.selectedBrowser);
5508 if (!gMultiProcessBrowser) {
5509 // Bug 1108553 - Cannot rotate images with e10s
5510 gGestureSupport.restoreRotationState();
5513 // See bug 358202, when tabs are switched during a drag operation,
5514 // timers don't fire on windows (bug 203573)
5516 setTimeout(function() {
5517 XULBrowserWindow.asyncUpdateUI();
5520 this.asyncUpdateUI();
5523 if (AppConstants.MOZ_CRASHREPORTER && aLocationURI) {
5524 let uri = aLocationURI;
5526 // If the current URI contains a username/password, remove it.
5532 /* Ignore failures on about: URIs. */
5536 Services.appinfo.annotateCrashReport("URL", uri.spec);
5538 // Don't make noise when the crash reporter is built but not enabled.
5539 if (ex.result != Cr.NS_ERROR_NOT_INITIALIZED) {
5546 _updateElementsForContentType() {
5547 let browser = gBrowser.selectedBrowser;
5550 browser.documentContentType &&
5551 BrowserUtils.mimeTypeIsTextBased(browser.documentContentType);
5552 for (let element of this._elementsForTextBasedTypes) {
5554 element.removeAttribute("disabled");
5556 element.setAttribute("disabled", "true");
5560 // Always enable find commands in PDF documents, otherwise do it only for
5561 // text documents whose location is not in the blacklist.
5563 browser.contentPrincipal?.spec == "resource://pdf.js/web/viewer.html" ||
5564 (isText && BrowserUtils.canFindInPage(gBrowser.currentURI.spec));
5565 for (let element of this._elementsForFind) {
5567 element.removeAttribute("disabled");
5569 element.setAttribute("disabled", "true");
5575 * Updates macOS platform code with the current URI and page title.
5576 * From there, we update the current NSUserActivity, enabling Handoff to other
5578 * @param {Window} window
5579 * The window in which the navigation occurred.
5580 * @param {nsIURI} uri
5581 * The URI pointing to the current page.
5582 * @param {nsIWebProgress} webProgress
5583 * The nsIWebProgress instance that fired a onLocationChange notification.
5585 _updateMacUserActivity(win, uri, webProgress) {
5586 if (!webProgress.isTopLevel || AppConstants.platform != "macosx") {
5591 if (PrivateBrowsingUtils.isWindowPrivate(win)) {
5592 // Passing an empty string to MacUserActivityUpdater will invalidate the
5593 // current user activity.
5596 let baseWin = win.docShell.treeOwner.QueryInterface(Ci.nsIBaseWindow);
5597 MacUserActivityUpdater.updateLocation(
5599 win.gBrowser.contentTitle,
5605 BrowserSearch.updateOpenSearchBadge();
5608 onStatusChange(aWebProgress, aRequest, aStatus, aMessage) {
5609 this.status = aMessage;
5610 StatusPanel.update();
5613 // Properties used to cache security state used to update the UI
5615 _lastLocation: null,
5617 _lastLocationForEvent: null,
5618 // _isSecureContext can change without the state/location changing, due to security
5619 // error pages that intercept certain loads. For example this happens sometimes
5620 // with the the HTTPS-Only Mode error page (more details in bug 1656027)
5621 _isSecureContext: null,
5623 // This is called in multiple ways:
5624 // 1. Due to the nsIWebProgressListener.onContentBlockingEvent notification.
5625 // 2. Called by tabbrowser.xml when updating the current browser.
5626 // 3. Called directly during this object's initializations.
5627 // 4. Due to the nsIWebProgressListener.onLocationChange notification.
5628 // aRequest will be null always in case 2 and 3, and sometimes in case 1 (for
5629 // instance, there won't be a request when STATE_BLOCKED_TRACKING_CONTENT or
5630 // other blocking events are observed).
5631 onContentBlockingEvent(aWebProgress, aRequest, aEvent, aIsSimulated) {
5632 // Don't need to do anything if the data we use to update the UI hasn't
5634 let uri = gBrowser.currentURI;
5635 let spec = uri.spec;
5636 if (this._event == aEvent && this._lastLocationForEvent == spec) {
5639 this._lastLocationForEvent = spec;
5642 typeof aIsSimulated != "boolean" &&
5643 typeof aIsSimulated != "undefined"
5646 "onContentBlockingEvent: aIsSimulated receieved an unexpected type"
5650 gProtectionsHandler.onContentBlockingEvent(
5654 this._event // previous content blocking event
5657 // We need the state of the previous content blocking event, so update
5658 // event after onContentBlockingEvent is called.
5659 this._event = aEvent;
5662 // This is called in multiple ways:
5663 // 1. Due to the nsIWebProgressListener.onSecurityChange notification.
5664 // 2. Called by tabbrowser.xml when updating the current browser.
5665 // 3. Called directly during this object's initializations.
5666 // aRequest will be null always in case 2 and 3, and sometimes in case 1.
5667 onSecurityChange(aWebProgress, aRequest, aState, aIsSimulated) {
5668 // Don't need to do anything if the data we use to update the UI hasn't
5670 let uri = gBrowser.currentURI;
5671 let spec = uri.spec;
5672 let isSecureContext = gBrowser.securityUI.isSecureContext;
5674 this._state == aState &&
5675 this._lastLocation == spec &&
5676 this._isSecureContext === isSecureContext
5678 // Switching to a tab of the same URL doesn't change most security
5679 // information, but tab specific permissions may be different.
5680 gIdentityHandler.refreshIdentityBlock();
5683 this._state = aState;
5684 this._lastLocation = spec;
5685 this._isSecureContext = isSecureContext;
5687 // Make sure the "https" part of the URL is striked out or not,
5688 // depending on the current mixed active content blocking state.
5689 gURLBar.formatValue();
5692 uri = Services.io.createExposableURI(uri);
5694 gIdentityHandler.updateIdentity(this._state, uri);
5697 // simulate all change notifications after switching tabs
5698 onUpdateCurrentBrowser: function XWB_onUpdateCurrentBrowser(
5704 if (FullZoom.updateBackgroundTabs) {
5705 FullZoom.onLocationChange(gBrowser.currentURI, true);
5708 CombinedStopReload.onTabSwitch();
5710 // Docshell should normally take care of hiding the tooltip, but we need to do it
5711 // ourselves for tabswitches.
5714 // Also hide tooltips for content loaded in the parent process:
5715 document.getElementById("aHTMLTooltip").hidePopup();
5717 var nsIWebProgressListener = Ci.nsIWebProgressListener;
5718 var loadingDone = aStateFlags & nsIWebProgressListener.STATE_STOP;
5719 // use a pseudo-object instead of a (potentially nonexistent) channel for getting
5720 // a correct error message - and make sure that the UI is always either in
5721 // loading (STATE_START) or done (STATE_STOP) mode
5723 gBrowser.webProgress,
5724 { URI: gBrowser.currentURI },
5726 ? nsIWebProgressListener.STATE_STOP
5727 : nsIWebProgressListener.STATE_START,
5730 // status message and progress value are undefined if we're done with loading
5734 this.onStatusChange(gBrowser.webProgress, null, 0, aMessage);
5738 var LinkTargetDisplay = {
5740 delete this.DELAY_SHOW;
5741 return (this.DELAY_SHOW = Services.prefs.getIntPref(
5742 "browser.overlink-delay"
5749 get _contextMenu() {
5750 delete this._contextMenu;
5751 return (this._contextMenu = document.getElementById(
5752 "contentAreaContextMenu"
5758 this._contextMenu.state == "open" ||
5759 this._contextMenu.state == "showing"
5761 this._contextMenu.addEventListener("popuphidden", () => this.update(), {
5767 clearTimeout(this._timer);
5768 window.removeEventListener("mousemove", this, true);
5770 if (!XULBrowserWindow.overLink) {
5771 if (XULBrowserWindow.hideOverLinkImmediately) {
5774 this._timer = setTimeout(this._hide.bind(this), this.DELAY_HIDE);
5779 if (StatusPanel.isVisible) {
5780 StatusPanel.update();
5782 // Let the display appear when the mouse doesn't move within the delay
5783 this._showDelayed();
5784 window.addEventListener("mousemove", this, true);
5788 handleEvent(event) {
5789 switch (event.type) {
5791 // Restart the delay since the mouse was moved
5792 clearTimeout(this._timer);
5793 this._showDelayed();
5799 this._timer = setTimeout(
5801 StatusPanel.update();
5802 window.removeEventListener("mousemove", self, true);
5810 clearTimeout(this._timer);
5812 StatusPanel.update();
5816 var CombinedStopReload = {
5817 // Try to initialize. Returns whether initialization was successful, which
5818 // may mean we had already initialized.
5819 ensureInitialized() {
5820 if (this._initialized) {
5823 if (this._destroyed) {
5827 let reload = document.getElementById("reload-button");
5828 let stop = document.getElementById("stop-button");
5829 // It's possible the stop/reload buttons have been moved to the palette.
5830 // They may be reinserted later, so we will retry initialization if/when
5831 // we get notified of document loads.
5832 if (!stop || !reload) {
5836 this._initialized = true;
5837 if (XULBrowserWindow.stopCommand.getAttribute("disabled") != "true") {
5838 reload.setAttribute("displaystop", "true");
5840 stop.addEventListener("click", this);
5842 // Removing attributes based on the observed command doesn't happen if the button
5843 // is in the palette when the command's attribute is removed (cf. bug 309953)
5844 for (let button of [stop, reload]) {
5845 if (button.hasAttribute("disabled")) {
5846 let command = document.getElementById(button.getAttribute("command"));
5847 if (!command.hasAttribute("disabled")) {
5848 button.removeAttribute("disabled");
5853 this.reload = reload;
5855 this.stopReloadContainer = this.reload.parentNode;
5856 this.timeWhenSwitchedToStop = 0;
5858 this.stopReloadContainer.addEventListener("animationend", this);
5859 this.stopReloadContainer.addEventListener("animationcancel", this);
5865 this._destroyed = true;
5867 if (!this._initialized) {
5871 this._cancelTransition();
5872 this.stop.removeEventListener("click", this);
5873 this.stopReloadContainer.removeEventListener("animationend", this);
5874 this.stopReloadContainer.removeEventListener("animationcancel", this);
5875 this.stopReloadContainer = null;
5880 handleEvent(event) {
5881 switch (event.type) {
5883 if (event.button == 0 && !this.stop.disabled) {
5884 this._stopClicked = true;
5887 case "animationcancel":
5888 case "animationend": {
5890 event.target.classList.contains("toolbarbutton-animatable-image") &&
5891 (event.animationName == "reload-to-stop" ||
5892 event.animationName == "stop-to-reload")
5894 this.stopReloadContainer.removeAttribute("animate");
5901 // Reset the time in the event of a tabswitch since the stored time
5902 // would have been associated with the previous tab, so the animation will
5903 // still run if the page has been loading until long after the tab switch.
5904 this.timeWhenSwitchedToStop = window.performance.now();
5907 switchToStop(aRequest, aWebProgress) {
5909 !this.ensureInitialized() ||
5910 !this._shouldSwitch(aRequest, aWebProgress)
5915 // Store the time that we switched to the stop button only if a request
5916 // is active. Requests are null if the switch is related to a tabswitch.
5917 // This is used to determine if we should show the stop->reload animation.
5918 if (aRequest instanceof Ci.nsIRequest) {
5919 this.timeWhenSwitchedToStop = window.performance.now();
5923 aRequest instanceof Ci.nsIRequest &&
5924 aWebProgress.isTopLevel &&
5925 aWebProgress.isLoadingDocument &&
5926 !gBrowser.tabAnimationsInProgress &&
5928 this.stopReloadContainer.closest("#nav-bar-customization-target");
5930 this._cancelTransition();
5931 if (shouldAnimate) {
5932 BrowserUIUtils.setToolbarButtonHeightProperty(this.stopReloadContainer);
5933 this.stopReloadContainer.setAttribute("animate", "true");
5935 this.stopReloadContainer.removeAttribute("animate");
5937 this.reload.setAttribute("displaystop", "true");
5940 switchToReload(aRequest, aWebProgress) {
5941 if (!this.ensureInitialized() || !this.reload.hasAttribute("displaystop")) {
5946 aRequest instanceof Ci.nsIRequest &&
5947 aWebProgress.isTopLevel &&
5948 !aWebProgress.isLoadingDocument &&
5949 !gBrowser.tabAnimationsInProgress &&
5951 this._loadTimeExceedsMinimumForAnimation() &&
5952 this.stopReloadContainer.closest("#nav-bar-customization-target");
5954 if (shouldAnimate) {
5955 BrowserUIUtils.setToolbarButtonHeightProperty(this.stopReloadContainer);
5956 this.stopReloadContainer.setAttribute("animate", "true");
5958 this.stopReloadContainer.removeAttribute("animate");
5961 this.reload.removeAttribute("displaystop");
5963 if (!shouldAnimate || this._stopClicked) {
5964 this._stopClicked = false;
5965 this._cancelTransition();
5966 this.reload.disabled =
5967 XULBrowserWindow.reloadCommand.getAttribute("disabled") == "true";
5975 // Temporarily disable the reload button to prevent the user from
5976 // accidentally reloading the page when intending to click the stop button
5977 this.reload.disabled = true;
5978 this._timer = setTimeout(
5981 self.reload.disabled =
5982 XULBrowserWindow.reloadCommand.getAttribute("disabled") == "true";
5989 _loadTimeExceedsMinimumForAnimation() {
5990 // If the time between switching to the stop button then switching to
5991 // the reload button exceeds 150ms, then we will show the animation.
5992 // If we don't know when we switched to stop (switchToStop is called
5993 // after init but before switchToReload), then we will prevent the
5994 // animation from occuring.
5996 this.timeWhenSwitchedToStop &&
5997 window.performance.now() - this.timeWhenSwitchedToStop > 150
6001 _shouldSwitch(aRequest, aWebProgress) {
6004 aRequest.originalURI &&
6005 (aRequest.originalURI.schemeIs("chrome") ||
6006 (aRequest.originalURI.schemeIs("about") &&
6007 aWebProgress.isTopLevel &&
6008 !aRequest.originalURI.spec.startsWith("about:reader")))
6016 _cancelTransition() {
6018 clearTimeout(this._timer);
6024 var TabsProgressListener = {
6025 onStateChange(aBrowser, aWebProgress, aRequest, aStateFlags, aStatus) {
6026 // Collect telemetry data about tab load times.
6028 aWebProgress.isTopLevel &&
6029 (!aRequest.originalURI || aRequest.originalURI.scheme != "about")
6031 let histogram = "FX_PAGE_LOAD_MS_2";
6032 let recordLoadTelemetry = true;
6034 if (aWebProgress.loadType & Ci.nsIDocShell.LOAD_CMD_RELOAD) {
6035 // loadType is constructed by shifting loadFlags, this is why we need to
6036 // do the same shifting here.
6037 // https://searchfox.org/mozilla-central/rev/11cfa0462a6b5d8c5e2111b8cfddcf78098f0141/docshell/base/nsDocShellLoadTypes.h#22
6038 if (aWebProgress.loadType & (kSkipCacheFlags << 16)) {
6039 histogram = "FX_PAGE_RELOAD_SKIP_CACHE_MS";
6040 } else if (aWebProgress.loadType == Ci.nsIDocShell.LOAD_CMD_RELOAD) {
6041 histogram = "FX_PAGE_RELOAD_NORMAL_MS";
6043 recordLoadTelemetry = false;
6047 let stopwatchRunning = TelemetryStopwatch.running(histogram, aBrowser);
6048 if (aStateFlags & Ci.nsIWebProgressListener.STATE_IS_WINDOW) {
6049 if (aStateFlags & Ci.nsIWebProgressListener.STATE_START) {
6050 if (stopwatchRunning) {
6051 // Oops, we're seeing another start without having noticed the previous stop.
6052 if (recordLoadTelemetry) {
6053 TelemetryStopwatch.cancel(histogram, aBrowser);
6056 if (recordLoadTelemetry) {
6057 TelemetryStopwatch.start(histogram, aBrowser);
6059 Services.telemetry.getHistogramById("FX_TOTAL_TOP_VISITS").add(true);
6061 aStateFlags & Ci.nsIWebProgressListener.STATE_STOP &&
6062 stopwatchRunning /* we won't see STATE_START events for pre-rendered tabs */
6064 if (recordLoadTelemetry) {
6065 TelemetryStopwatch.finish(histogram, aBrowser);
6066 BrowserTelemetryUtils.recordSiteOriginTelemetry(browserWindows());
6070 aStateFlags & Ci.nsIWebProgressListener.STATE_STOP &&
6071 aStatus == Cr.NS_BINDING_ABORTED &&
6072 stopwatchRunning /* we won't see STATE_START events for pre-rendered tabs */
6074 if (recordLoadTelemetry) {
6075 TelemetryStopwatch.cancel(histogram, aBrowser);
6081 onLocationChange(aBrowser, aWebProgress, aRequest, aLocationURI, aFlags) {
6082 // Filter out location changes caused by anchor navigation
6083 // or history.push/pop/replaceState.
6084 if (aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT) {
6085 // Reader mode cares about history.pushState and friends.
6086 // FIXME: The content process should manage this directly (bug 1445351).
6087 aBrowser.sendMessageToActor(
6090 isArticle: aBrowser.isArticle,
6097 // Filter out location changes in sub documents.
6098 if (!aWebProgress.isTopLevel) {
6102 // Only need to call locationChange if the PopupNotifications object
6103 // for this window has already been initialized (i.e. its getter no
6105 if (!Object.getOwnPropertyDescriptor(window, "PopupNotifications").get) {
6106 PopupNotifications.locationChange(aBrowser);
6109 let tab = gBrowser.getTabForBrowser(aBrowser);
6110 if (tab && tab._sharingState) {
6111 gBrowser.resetBrowserSharing(aBrowser);
6114 gBrowser.readNotificationBox(aBrowser)?.removeTransientNotifications();
6116 FullZoom.onLocationChange(aLocationURI, false, aBrowser);
6117 CaptivePortalWatcher.onLocationChange(aBrowser);
6120 onLinkIconAvailable(browser, dataURI, iconURI) {
6124 if (browser == gBrowser.selectedBrowser) {
6125 // If the "Add Search Engine" page action is in the urlbar, its image
6126 // needs to be set to the new icon, so call updateOpenSearchBadge.
6127 BrowserSearch.updateOpenSearchBadge();
6132 function nsBrowserAccess() {}
6134 nsBrowserAccess.prototype = {
6135 QueryInterface: ChromeUtils.generateQI(["nsIBrowserDOMWindow"]),
6142 aForceNotRemote = false,
6143 aUserContextId = Ci.nsIScriptSecurityManager.DEFAULT_USER_CONTEXT_ID,
6144 aOpenWindowInfo = null,
6145 aOpenerBrowser = null,
6146 aTriggeringPrincipal = null,
6151 let win, needToFocusWin;
6153 // try the current window. if we're in a popup, fall back on the most recent browser window
6154 if (window.toolbar.visible) {
6157 win = BrowserWindowTracker.getTopWindow({ private: aIsPrivate });
6158 needToFocusWin = true;
6162 // we couldn't find a suitable window, a new one needs to be opened.
6166 if (aIsExternal && (!aURI || aURI.spec == "about:blank")) {
6167 win.BrowserOpenTab(); // this also focuses the location bar
6169 return win.gBrowser.selectedBrowser;
6172 let loadInBackground = Services.prefs.getBoolPref(
6173 "browser.tabs.loadDivertedInBackground"
6176 let tab = win.gBrowser.loadOneTab(aURI ? aURI.spec : "about:blank", {
6177 triggeringPrincipal: aTriggeringPrincipal,
6178 referrerInfo: aReferrerInfo,
6179 userContextId: aUserContextId,
6180 fromExternal: aIsExternal,
6181 inBackground: loadInBackground,
6182 forceNotRemote: aForceNotRemote,
6183 openWindowInfo: aOpenWindowInfo,
6184 openerBrowser: aOpenerBrowser,
6187 skipLoad: aSkipLoad,
6189 let browser = win.gBrowser.getBrowserForTab(tab);
6191 if (needToFocusWin || (!loadInBackground && aIsExternal)) {
6198 createContentWindow(
6203 aTriggeringPrincipal,
6206 return this.getContentWindowOrOpenURI(
6211 aTriggeringPrincipal,
6217 openURI(aURI, aOpenWindowInfo, aWhere, aFlags, aTriggeringPrincipal, aCsp) {
6219 Cu.reportError("openURI should only be called with a valid URI");
6220 throw Components.Exception("", Cr.NS_ERROR_FAILURE);
6222 return this.getContentWindowOrOpenURI(
6227 aTriggeringPrincipal,
6233 getContentWindowOrOpenURI(
6238 aTriggeringPrincipal,
6242 var browsingContext = null;
6243 var isExternal = !!(aFlags & Ci.nsIBrowserDOMWindow.OPEN_EXTERNAL);
6245 if (aOpenWindowInfo && isExternal) {
6247 "nsBrowserAccess.openURI did not expect aOpenWindowInfo to be " +
6248 "passed if the context is OPEN_EXTERNAL."
6250 throw Components.Exception("", Cr.NS_ERROR_FAILURE);
6253 if (isExternal && aURI && aURI.schemeIs("chrome")) {
6254 dump("use --chrome command-line option to load external chrome urls\n");
6258 if (aWhere == Ci.nsIBrowserDOMWindow.OPEN_DEFAULTWINDOW) {
6261 Services.prefs.prefHasUserValue(
6262 "browser.link.open_newwindow.override.external"
6265 aWhere = Services.prefs.getIntPref(
6266 "browser.link.open_newwindow.override.external"
6269 aWhere = Services.prefs.getIntPref("browser.link.open_newwindow");
6274 if (aFlags & Ci.nsIBrowserDOMWindow.OPEN_NO_REFERRER) {
6275 referrerInfo = new ReferrerInfo(Ci.nsIReferrerInfo.EMPTY, false, null);
6278 aOpenWindowInfo.parent &&
6279 aOpenWindowInfo.parent.window
6281 referrerInfo = new ReferrerInfo(
6282 aOpenWindowInfo.parent.window.document.referrerInfo.referrerPolicy,
6284 makeURI(aOpenWindowInfo.parent.window.location.href)
6287 referrerInfo = new ReferrerInfo(Ci.nsIReferrerInfo.EMPTY, true, null);
6290 let isPrivate = aOpenWindowInfo
6291 ? aOpenWindowInfo.originAttributes.privateBrowsingId != 0
6292 : PrivateBrowsingUtils.isWindowPrivate(window);
6295 case Ci.nsIBrowserDOMWindow.OPEN_NEWWINDOW:
6296 // FIXME: Bug 408379. So how come this doesn't send the
6297 // referrer like the other loads do?
6298 var url = aURI && aURI.spec;
6299 let features = "all,dialog=no";
6301 features += ",private";
6303 // Pass all params to openDialog to ensure that "url" isn't passed through
6304 // loadOneOrMoreURIs, which splits based on "|"
6306 let extraOptions = Cc[
6307 "@mozilla.org/hash-property-bag;1"
6308 ].createInstance(Ci.nsIWritablePropertyBag2);
6309 extraOptions.setPropertyAsBool("fromExternal", isExternal);
6312 AppConstants.BROWSER_CHROME_URL,
6324 aTriggeringPrincipal,
6329 // At this point, the new browser window is just starting to load, and
6330 // hasn't created the content <browser> that we should return.
6331 // If the caller of this function is originating in C++, they can pass a
6332 // callback in nsOpenWindowInfo and it will be invoked when the browsing
6333 // context for a newly opened window is ready.
6334 browsingContext = null;
6339 case Ci.nsIBrowserDOMWindow.OPEN_NEWTAB: {
6340 // If we have an opener, that means that the caller is expecting access
6341 // to the nsIDOMWindow of the opened tab right away. For e10s windows,
6342 // this means forcing the newly opened browser to be non-remote so that
6343 // we can hand back the nsIDOMWindow. DocumentLoadListener will do the
6344 // job of shuttling off the newly opened browser to run in the right
6345 // process once it starts loading a URI.
6346 let forceNotRemote = aOpenWindowInfo && !aOpenWindowInfo.isRemote;
6347 let userContextId = aOpenWindowInfo
6348 ? aOpenWindowInfo.originAttributes.userContextId
6349 : Ci.nsIScriptSecurityManager.DEFAULT_USER_CONTEXT_ID;
6350 let browser = this._openURIInNewTab(
6358 aOpenWindowInfo?.parent?.top.embedderElement,
6359 aTriggeringPrincipal,
6365 browsingContext = browser.browsingContext;
6369 case Ci.nsIBrowserDOMWindow.OPEN_PRINT_BROWSER: {
6370 let browser = PrintUtils.handleStaticCloneCreatedForPrint(
6374 browsingContext = browser.browsingContext;
6379 // OPEN_CURRENTWINDOW or an illegal value
6380 browsingContext = window.gBrowser.selectedBrowser.browsingContext;
6382 let loadFlags = Ci.nsIWebNavigation.LOAD_FLAGS_NONE;
6384 loadFlags |= Ci.nsIWebNavigation.LOAD_FLAGS_FROM_EXTERNAL;
6385 } else if (!aTriggeringPrincipal.isSystemPrincipal) {
6386 // XXX this code must be reviewed and changed when bug 1616353
6388 loadFlags |= Ci.nsIWebNavigation.LOAD_FLAGS_FIRST_LOAD;
6390 gBrowser.loadURI(aURI.spec, {
6391 triggeringPrincipal: aTriggeringPrincipal,
6398 !Services.prefs.getBoolPref("browser.tabs.loadDivertedInBackground")
6403 return browsingContext;
6406 createContentWindowInFrame: function browser_createContentWindowInFrame(
6413 // Passing a null-URI to only create the content window,
6414 // and pass true for aSkipLoad to prevent loading of
6416 return this.getContentWindowOrOpenURIInFrame(
6426 openURIInFrame: function browser_openURIInFrame(
6433 return this.getContentWindowOrOpenURIInFrame(
6443 getContentWindowOrOpenURIInFrame: function browser_getContentWindowOrOpenURIInFrame(
6451 if (aWhere == Ci.nsIBrowserDOMWindow.OPEN_PRINT_BROWSER) {
6452 return PrintUtils.handleStaticCloneCreatedForPrint(
6453 aParams.openWindowInfo
6457 if (aWhere != Ci.nsIBrowserDOMWindow.OPEN_NEWTAB) {
6458 dump("Error: openURIInFrame can only open in new tabs or print");
6462 var isExternal = !!(aFlags & Ci.nsIBrowserDOMWindow.OPEN_EXTERNAL);
6465 aParams.openerOriginAttributes &&
6466 "userContextId" in aParams.openerOriginAttributes
6467 ? aParams.openerOriginAttributes.userContextId
6468 : Ci.nsIScriptSecurityManager.DEFAULT_USER_CONTEXT_ID;
6470 return this._openURIInNewTab(
6472 aParams.referrerInfo,
6477 aParams.openWindowInfo,
6478 aParams.openerBrowser,
6479 aParams.triggeringPrincipal,
6487 return CanCloseWindow();
6491 return gBrowser.tabs.length;
6495 function showFullScreenViewContextMenuItems(popup) {
6496 for (let node of popup.querySelectorAll('[contexttype="fullscreen"]')) {
6497 node.hidden = !window.fullScreen;
6499 let autoHide = popup.querySelector(".fullscreen-context-autohide");
6501 FullScreen.updateAutohideMenuitem(autoHide);
6505 function onViewToolbarsPopupShowing(aEvent, aInsertPoint) {
6506 var popup = aEvent.target;
6507 if (popup != aEvent.currentTarget) {
6512 for (var i = popup.children.length - 1; i >= 0; --i) {
6513 var deadItem = popup.children[i];
6514 if (deadItem.hasAttribute("toolbarId")) {
6515 popup.removeChild(deadItem);
6519 MozXULElement.insertFTLIfNeeded("browser/toolbarContextMenu.ftl");
6520 let firstMenuItem = aInsertPoint || popup.firstElementChild;
6521 let toolbarNodes = gNavToolbox.querySelectorAll("toolbar");
6522 for (let toolbar of toolbarNodes) {
6523 if (!toolbar.hasAttribute("toolbarname")) {
6527 if (toolbar.id == "PersonalToolbar") {
6528 let menu = BookmarkingUI.buildBookmarksToolbarSubmenu(toolbar);
6529 popup.insertBefore(menu, firstMenuItem);
6531 let menuItem = document.createXULElement("menuitem");
6532 menuItem.setAttribute("id", "toggle_" + toolbar.id);
6533 menuItem.setAttribute("toolbarId", toolbar.id);
6534 menuItem.setAttribute("type", "checkbox");
6535 menuItem.setAttribute("label", toolbar.getAttribute("toolbarname"));
6536 let hidingAttribute =
6537 toolbar.getAttribute("type") == "menubar" ? "autohide" : "collapsed";
6538 menuItem.setAttribute(
6540 toolbar.getAttribute(hidingAttribute) != "true"
6542 menuItem.setAttribute("accesskey", toolbar.getAttribute("accesskey"));
6543 if (popup.id != "toolbar-context-menu") {
6544 menuItem.setAttribute("key", toolbar.getAttribute("key"));
6547 popup.insertBefore(menuItem, firstMenuItem);
6548 menuItem.addEventListener("command", onViewToolbarCommand);
6552 let moveToPanel = popup.querySelector(".customize-context-moveToPanel");
6553 let removeFromToolbar = popup.querySelector(
6554 ".customize-context-removeFromToolbar"
6556 // Show/hide fullscreen context menu items and set the
6557 // autohide item's checked state to mirror the autohide pref.
6558 showFullScreenViewContextMenuItems(popup);
6559 // View -> Toolbars menu doesn't have the moveToPanel or removeFromToolbar items.
6560 if (!moveToPanel || !removeFromToolbar) {
6564 // triggerNode can be a nested child element of a toolbaritem.
6565 let toolbarItem = popup.triggerNode;
6567 if (toolbarItem && toolbarItem.localName == "toolbarpaletteitem") {
6568 toolbarItem = toolbarItem.firstElementChild;
6569 } else if (toolbarItem && toolbarItem.localName != "toolbar") {
6570 while (toolbarItem && toolbarItem.parentElement) {
6571 let parent = toolbarItem.parentElement;
6573 (parent.classList &&
6574 parent.classList.contains("customization-target")) ||
6575 parent.getAttribute("overflowfortoolbar") || // Needs to work in the overflow list as well.
6576 parent.localName == "toolbarpaletteitem" ||
6577 parent.localName == "toolbar"
6581 toolbarItem = parent;
6587 let showTabStripItems = toolbarItem && toolbarItem.id == "tabbrowser-tabs";
6588 for (let node of popup.querySelectorAll(
6589 'menuitem[contexttype="toolbaritem"]'
6591 node.hidden = showTabStripItems;
6594 for (let node of popup.querySelectorAll('menuitem[contexttype="tabbar"]')) {
6595 node.hidden = !showTabStripItems;
6599 .getElementById("toolbar-context-menu")
6600 .querySelectorAll("[data-lazy-l10n-id]")
6602 el.setAttribute("data-l10n-id", el.getAttribute("data-lazy-l10n-id"));
6603 el.removeAttribute("data-lazy-l10n-id");
6606 // The "normal" toolbar items menu separator is hidden because it's unused
6607 // when hiding the "moveToPanel" and "removeFromToolbar" items on flexible
6608 // space items. But we need to ensure its hidden state is reset in the case
6609 // the context menu is subsequently opened on a non-flexible space item.
6610 let menuSeparator = document.getElementById("toolbarItemsMenuSeparator");
6611 menuSeparator.hidden = false;
6613 document.getElementById(
6614 "toolbarNavigatorItemsMenuSeparator"
6615 ).hidden = !showTabStripItems;
6618 !CustomizationHandler.isCustomizing() &&
6619 CustomizableUI.isSpecialWidget(toolbarItem?.id || "")
6621 moveToPanel.hidden = true;
6622 removeFromToolbar.hidden = true;
6623 menuSeparator.hidden = !showTabStripItems;
6626 if (showTabStripItems) {
6627 let multipleTabsSelected = !!gBrowser.multiSelectedTabsCount;
6628 document.getElementById(
6629 "toolbar-context-bookmarkSelectedTabs"
6630 ).hidden = !multipleTabsSelected;
6631 document.getElementById(
6632 "toolbar-context-bookmarkSelectedTab"
6633 ).hidden = multipleTabsSelected;
6634 document.getElementById(
6635 "toolbar-context-reloadSelectedTabs"
6636 ).hidden = !multipleTabsSelected;
6637 document.getElementById(
6638 "toolbar-context-reloadSelectedTab"
6639 ).hidden = multipleTabsSelected;
6640 document.getElementById(
6641 "toolbar-context-selectAllTabs"
6642 ).disabled = gBrowser.allTabsSelected();
6643 document.getElementById("toolbar-context-undoCloseTab").disabled =
6644 SessionStore.getClosedTabCount(window) == 0;
6651 CustomizableUI.isWidgetRemovable(toolbarItem);
6653 if (CustomizableUI.isSpecialWidget(toolbarItem.id)) {
6654 moveToPanel.setAttribute("disabled", true);
6656 moveToPanel.removeAttribute("disabled");
6658 removeFromToolbar.removeAttribute("disabled");
6660 moveToPanel.setAttribute("disabled", true);
6661 removeFromToolbar.setAttribute("disabled", true);
6665 function onViewToolbarCommand(aEvent) {
6666 let node = aEvent.originalTarget;
6670 if (node.dataset.bookmarksToolbarVisibility) {
6671 isVisible = node.dataset.visibilityEnum;
6672 toolbarId = "PersonalToolbar";
6673 menuId = node.parentNode.parentNode.parentNode.id;
6674 Services.prefs.setCharPref(
6675 "browser.toolbars.bookmarks.visibility",
6679 menuId = node.parentNode.id;
6680 toolbarId = node.getAttribute("toolbarId");
6681 isVisible = node.getAttribute("checked") == "true";
6683 CustomizableUI.setToolbarVisibility(toolbarId, isVisible);
6684 BrowserUsageTelemetry.recordToolbarVisibility(toolbarId, isVisible, menuId);
6687 function setToolbarVisibility(
6693 let hidingAttribute;
6694 if (toolbar.getAttribute("type") == "menubar") {
6695 hidingAttribute = "autohide";
6696 if (AppConstants.platform == "linux") {
6697 Services.prefs.setBoolPref("ui.key.menuAccessKeyFocuses", !isVisible);
6700 hidingAttribute = "collapsed";
6703 // For the bookmarks toolbar, we need to persist state before toggling
6704 // the visibility in this window, because the state can be different
6705 // (newtab vs never or always) even when that won't change visibility
6707 if (persist && toolbar.id == "PersonalToolbar") {
6709 if (typeof isVisible == "string") {
6710 prefValue = isVisible;
6712 prefValue = isVisible ? "always" : "never";
6714 Services.prefs.setCharPref(
6715 "browser.toolbars.bookmarks.visibility",
6720 if (typeof isVisible == "string") {
6721 switch (isVisible) {
6729 let currentURI = gBrowser?.currentURI;
6730 if (!gBrowserInit.domContentLoaded) {
6731 let uriToLoad = gBrowserInit.uriToLoadPromise;
6733 if (Array.isArray(uriToLoad)) {
6734 // We only care about the first tab being loaded
6735 uriToLoad = uriToLoad[0];
6738 currentURI = Services.io.newURI(uriToLoad);
6743 !!currentURI && BookmarkingUI.isOnNewTabPage({ currentURI });
6748 if (toolbar.getAttribute(hidingAttribute) == (!isVisible).toString()) {
6749 // If this call will not result in a visibility change, return early
6750 // since dispatching toolbarvisibilitychange will cause views to get rebuilt.
6754 toolbar.classList.toggle("instant", !animated);
6755 toolbar.setAttribute(hidingAttribute, !isVisible);
6756 // For the bookmarks toolbar, we will have saved state above. For other
6757 // toolbars, we need to do it after setting the attribute, or we might
6758 // save the wrong state.
6759 if (persist && toolbar.id != "PersonalToolbar") {
6760 Services.xulStore.persist(toolbar, hidingAttribute);
6769 let event = new CustomEvent("toolbarvisibilitychange", eventParams);
6770 toolbar.dispatchEvent(event);
6773 function updateToggleControlLabel(control) {
6774 if (!control.hasAttribute("label-checked")) {
6778 if (!control.hasAttribute("label-unchecked")) {
6779 control.setAttribute("label-unchecked", control.getAttribute("label"));
6781 let prefix = control.getAttribute("checked") == "true" ? "" : "un";
6782 control.setAttribute("label", control.getAttribute(`label-${prefix}checked`));
6785 var TabletModeUpdater = {
6787 if (AppConstants.isPlatformAndVersionAtLeast("win", "10")) {
6788 this.update(WindowsUIUtils.inTabletMode);
6789 Services.obs.addObserver(this, "tablet-mode-change");
6794 if (AppConstants.isPlatformAndVersionAtLeast("win", "10")) {
6795 Services.obs.removeObserver(this, "tablet-mode-change");
6799 observe(subject, topic, data) {
6800 this.update(data == "tablet-mode");
6803 update(isInTabletMode) {
6804 let wasInTabletMode = document.documentElement.hasAttribute("tabletmode");
6805 if (isInTabletMode) {
6806 document.documentElement.setAttribute("tabletmode", "true");
6808 document.documentElement.removeAttribute("tabletmode");
6810 if (wasInTabletMode != isInTabletMode) {
6811 gUIDensity.update();
6816 var gTabletModePageCounter = {
6819 this.enabled = AppConstants.isPlatformAndVersionAtLeast("win", "10.0");
6820 if (!this.enabled) {
6821 this.inc = () => {};
6824 this.inc = this._realInc;
6831 let inTabletMode = document.documentElement.hasAttribute("tabletmode");
6832 this[inTabletMode ? "_tabletCount" : "_desktopCount"]++;
6837 let histogram = Services.telemetry.getKeyedHistogramById(
6838 "FX_TABLETMODE_PAGE_LOAD"
6840 histogram.add("tablet", this._tabletCount);
6841 histogram.add("desktop", this._desktopCount);
6846 function displaySecurityInfo() {
6847 BrowserPageInfo(null, "securityTab");
6850 // Updates the UI density (for touch and compact mode) based on the uidensity pref.
6855 uiDensityPref: "browser.uidensity",
6856 autoTouchModePref: "browser.touchmode.auto",
6860 Services.prefs.addObserver(this.uiDensityPref, this);
6861 Services.prefs.addObserver(this.autoTouchModePref, this);
6865 Services.prefs.removeObserver(this.uiDensityPref, this);
6866 Services.prefs.removeObserver(this.autoTouchModePref, this);
6869 observe(aSubject, aTopic, aPrefName) {
6871 aTopic != "nsPref:changed" ||
6872 (aPrefName != this.uiDensityPref && aPrefName != this.autoTouchModePref)
6880 getCurrentDensity() {
6881 // Automatically override the uidensity to touch in Windows tablet mode.
6883 AppConstants.isPlatformAndVersionAtLeast("win", "10") &&
6884 WindowsUIUtils.inTabletMode &&
6885 Services.prefs.getBoolPref(this.autoTouchModePref)
6887 return { mode: this.MODE_TOUCH, overridden: true };
6890 mode: Services.prefs.getIntPref(this.uiDensityPref),
6897 mode = this.getCurrentDensity().mode;
6900 let docs = [document.documentElement];
6901 let shouldUpdateSidebar = SidebarUI.initialized && SidebarUI.isOpen;
6902 if (shouldUpdateSidebar) {
6903 docs.push(SidebarUI.browser.contentDocument.documentElement);
6905 for (let doc of docs) {
6907 case this.MODE_COMPACT:
6908 doc.setAttribute("uidensity", "compact");
6910 case this.MODE_TOUCH:
6911 doc.setAttribute("uidensity", "touch");
6914 doc.removeAttribute("uidensity");
6918 if (shouldUpdateSidebar) {
6919 let tree = SidebarUI.browser.contentDocument.querySelector(
6920 ".sidebar-placesTree"
6923 // Tree items don't update their styles without changing some property on the
6924 // parent tree element, like background-color or border. See bug 1407399.
6925 tree.style.border = "1px";
6926 tree.style.border = "";
6930 gBrowser.tabContainer.uiDensityChanged();
6931 gURLBar.updateLayoutBreakout();
6935 const nodeToTooltipMap = {
6936 "bookmarks-menu-button": "bookmarksMenuButton.tooltip",
6937 "context-reload": "reloadButton.tooltip",
6938 "context-stop": "stopButton.tooltip",
6939 "downloads-button": "downloads.tooltip",
6940 "fullscreen-button": "fullscreenButton.tooltip",
6941 "appMenu-fullscreen-button2": "fullscreenButton.tooltip",
6942 "new-window-button": "newWindowButton.tooltip",
6943 "new-tab-button": "newTabButton.tooltip",
6944 "tabs-newtab-button": "newTabButton.tooltip",
6945 "reload-button": "reloadButton.tooltip",
6946 "stop-button": "stopButton.tooltip",
6947 "urlbar-zoom-button": "urlbar-zoom-button.tooltip",
6948 "appMenu-zoomEnlarge-button2": "zoomEnlarge-button.tooltip",
6949 "appMenu-zoomReset-button2": "zoomReset-button.tooltip",
6950 "appMenu-zoomReduce-button2": "zoomReduce-button.tooltip",
6951 "reader-mode-button": "reader-mode-button.tooltip",
6952 "reader-mode-button-icon": "reader-mode-button.tooltip",
6954 const nodeToShortcutMap = {
6955 "bookmarks-menu-button": "manBookmarkKb",
6956 "context-reload": "key_reload",
6957 "context-stop": "key_stop",
6958 "downloads-button": "key_openDownloads",
6959 "fullscreen-button": "key_fullScreen",
6960 "appMenu-fullscreen-button2": "key_fullScreen",
6961 "new-window-button": "key_newNavigator",
6962 "new-tab-button": "key_newNavigatorTab",
6963 "tabs-newtab-button": "key_newNavigatorTab",
6964 "reload-button": "key_reload",
6965 "stop-button": "key_stop",
6966 "urlbar-zoom-button": "key_fullZoomReset",
6967 "appMenu-zoomEnlarge-button2": "key_fullZoomEnlarge",
6968 "appMenu-zoomReset-button2": "key_fullZoomReset",
6969 "appMenu-zoomReduce-button2": "key_fullZoomReduce",
6970 "reader-mode-button": "key_toggleReaderMode",
6971 "reader-mode-button-icon": "key_toggleReaderMode",
6974 const gDynamicTooltipCache = new Map();
6975 function GetDynamicShortcutTooltipText(nodeId) {
6976 if (!gDynamicTooltipCache.has(nodeId) && nodeId in nodeToTooltipMap) {
6977 let strId = nodeToTooltipMap[nodeId];
6979 if (nodeId in nodeToShortcutMap) {
6980 let shortcutId = nodeToShortcutMap[nodeId];
6981 let shortcut = document.getElementById(shortcutId);
6983 args.push(ShortcutUtils.prettifyShortcut(shortcut));
6986 gDynamicTooltipCache.set(
6988 gNavigatorBundle.getFormattedString(strId, args)
6991 return gDynamicTooltipCache.get(nodeId);
6994 function UpdateDynamicShortcutTooltipText(aTooltip) {
6996 aTooltip.triggerNode.id || aTooltip.triggerNode.getAttribute("anonid");
6997 aTooltip.setAttribute("label", GetDynamicShortcutTooltipText(nodeId));
7001 * - [ Dependencies ] ---------------------------------------------------------
7002 * utilityOverlay.js:
7007 * Extracts linkNode and href for the current click target.
7011 * @return [href, linkNode].
7013 * @note linkNode will be null if the click wasn't on an anchor
7014 * element (or XLink).
7016 function hrefAndLinkNodeForClickEvent(event) {
7017 function isHTMLLink(aNode) {
7018 // Be consistent with what nsContextMenu.js does.
7020 (HTMLAnchorElement.isInstance(aNode) && aNode.href) ||
7021 (HTMLAreaElement.isInstance(aNode) && aNode.href) ||
7022 HTMLLinkElement.isInstance(aNode)
7026 let node = event.composedTarget;
7027 while (node && !isHTMLLink(node)) {
7028 node = node.flattenedTreeParentNode;
7032 return [node.href, node];
7035 // If there is no linkNode, try simple XLink.
7037 node = event.composedTarget;
7038 while (node && !href) {
7040 node.nodeType == Node.ELEMENT_NODE &&
7041 (node.localName == "a" ||
7042 node.namespaceURI == "http://www.w3.org/1998/Math/MathML")
7045 node.getAttribute("href") ||
7046 node.getAttributeNS("http://www.w3.org/1999/xlink", "href");
7049 baseURI = node.baseURI;
7053 node = node.flattenedTreeParentNode;
7056 // In case of XLink, we don't return the node we got href from since
7057 // callers expect <a>-like elements.
7058 return [href ? makeURLAbsolute(baseURI, href) : null, null];
7062 * Called whenever the user clicks in the content area.
7066 * @param isPanelClick
7067 * Whether the event comes from an extension panel.
7068 * @note default event is prevented if the click is handled.
7070 function contentAreaClick(event, isPanelClick) {
7071 if (!event.isTrusted || event.defaultPrevented || event.button != 0) {
7075 let [href, linkNode] = hrefAndLinkNodeForClickEvent(event);
7077 // Not a link, handle middle mouse navigation.
7079 event.button == 1 &&
7080 Services.prefs.getBoolPref("middlemouse.contentLoadURL") &&
7081 !Services.prefs.getBoolPref("general.autoScroll")
7083 middleMousePaste(event);
7084 event.preventDefault();
7089 // This code only applies if we have a linkNode (i.e. clicks on real anchor
7090 // elements, as opposed to XLink).
7093 event.button == 0 &&
7099 // An extension panel's links should target the main content area. Do this
7100 // if no modifier keys are down and if there's no target or the target
7101 // equals _main (the IE convention) or _content (the Mozilla convention).
7102 let target = linkNode.target;
7103 let mainTarget = !target || target == "_content" || target == "_main";
7104 if (isPanelClick && mainTarget) {
7105 // javascript and data links should be executed in the current browser.
7107 linkNode.getAttribute("onclick") ||
7108 href.startsWith("javascript:") ||
7109 href.startsWith("data:")
7115 urlSecurityCheck(href, linkNode.ownerDocument.nodePrincipal);
7117 // Prevent loading unsecure destinations.
7118 event.preventDefault();
7122 loadURI(href, null, null, false);
7123 event.preventDefault();
7128 handleLinkClick(event, href, linkNode);
7130 // Mark the page as a user followed link. This is done so that history can
7131 // distinguish automatic embed visits from user activated ones. For example
7132 // pages loaded in frames are embed visits and lost with the session, while
7133 // visits across frames should be preserved.
7135 if (!PrivateBrowsingUtils.isWindowPrivate(window)) {
7136 PlacesUIUtils.markPageAsFollowedLink(href);
7139 /* Skip invalid URIs. */
7144 * Handles clicks on links.
7146 * @return true if the click event was handled, false otherwise.
7148 function handleLinkClick(event, href, linkNode) {
7149 if (event.button == 2) {
7154 var where = whereToOpenLink(event);
7155 if (where == "current") {
7159 var doc = event.target.ownerDocument;
7160 let referrerInfo = Cc["@mozilla.org/referrer-info;1"].createInstance(
7164 referrerInfo.initWithElement(linkNode);
7166 referrerInfo.initWithDocument(doc);
7169 if (where == "save") {
7173 linkNode ? gatherTextUnder(linkNode) : "",
7178 doc.cookieJarSettings,
7181 event.preventDefault();
7185 let frameID = WebNavigationFrames.getFrameId(doc.defaultView);
7187 urlSecurityCheck(href, doc.nodePrincipal);
7189 charset: doc.characterSet,
7191 originPrincipal: doc.nodePrincipal,
7192 originStoragePrincipal: doc.effectiveStoragePrincipal,
7193 triggeringPrincipal: doc.nodePrincipal,
7198 // The new tab/window must use the same userContextId
7199 if (doc.nodePrincipal.originAttributes.userContextId) {
7200 params.userContextId = doc.nodePrincipal.originAttributes.userContextId;
7203 openLinkIn(href, where, params);
7204 event.preventDefault();
7209 * Handles paste on middle mouse clicks.
7211 * @param event {Event | Object} Event or JSON object.
7213 function middleMousePaste(event) {
7214 let clipboard = readFromClipboard();
7219 // Strip embedded newlines and surrounding whitespace, to match the URL
7220 // bar's behavior (stripsurroundingwhitespace)
7221 clipboard = clipboard.replace(/\s*\n\s*/g, "");
7223 clipboard = UrlbarUtils.stripUnsafeProtocolOnPaste(clipboard);
7225 // if it's not the current tab, we don't need to do anything because the
7226 // browser doesn't exist.
7227 let where = whereToOpenLink(event, true, false);
7228 let lastLocationChange;
7229 if (where == "current") {
7230 lastLocationChange = gBrowser.selectedBrowser.lastLocationChange;
7233 UrlbarUtils.getShortcutOrURIAndPostData(clipboard).then(data => {
7242 UrlbarUtils.addToUrlbarHistory(data.url, window);
7244 // Things may go wrong when adding url to session history,
7245 // but don't let that interfere with the loading of the url.
7250 where != "current" ||
7251 lastLocationChange == gBrowser.selectedBrowser.lastLocationChange
7253 openUILink(data.url, event, {
7255 allowInheritPrincipal: data.mayInheritPrincipal,
7256 triggeringPrincipal: gBrowser.selectedBrowser.contentPrincipal,
7257 csp: gBrowser.selectedBrowser.csp,
7262 if (Event.isInstance(event)) {
7263 event.stopPropagation();
7267 // handleDroppedLink has the following 2 overloads:
7268 // handleDroppedLink(event, url, name, triggeringPrincipal)
7269 // handleDroppedLink(event, links, triggeringPrincipal)
7270 function handleDroppedLink(
7273 nameOrTriggeringPrincipal,
7277 if (Array.isArray(urlOrLinks)) {
7279 triggeringPrincipal = nameOrTriggeringPrincipal;
7281 links = [{ url: urlOrLinks, nameOrTriggeringPrincipal, type: "" }];
7284 let lastLocationChange = gBrowser.selectedBrowser.lastLocationChange;
7286 let userContextId = gBrowser.selectedBrowser.getAttribute("usercontextid");
7288 // event is null if links are dropped in content process.
7289 // inBackground should be false, as it's loading into current browser.
7290 let inBackground = false;
7292 inBackground = Services.prefs.getBoolPref("browser.tabs.loadInBackground");
7293 if (event.shiftKey) {
7294 inBackground = !inBackground;
7301 Services.prefs.getIntPref("browser.tabs.maxOpenBeforeWarn")
7303 // Sync dialog cannot be used inside drop event handler.
7304 let answer = await OpenInTabsUtils.promiseConfirmOpenInTabs(
7315 for (let link of links) {
7316 let data = await UrlbarUtils.getShortcutOrURIAndPostData(link.url);
7317 urls.push(data.url);
7318 postDatas.push(data.postData);
7320 if (lastLocationChange == gBrowser.selectedBrowser.lastLocationChange) {
7321 gBrowser.loadTabs(urls, {
7324 allowThirdPartyFixup: false,
7327 triggeringPrincipal,
7332 // If links are dropped in content process, event.preventDefault() should be
7333 // called in content process.
7335 // Keep the event from being handled by the dragDrop listeners
7336 // built-in to gecko if they happen to be above us.
7337 event.preventDefault();
7341 function BrowserForceEncodingDetection() {
7342 gBrowser.selectedBrowser.forceEncodingDetection();
7343 BrowserReloadWithFlags(Ci.nsIWebNavigation.LOAD_FLAGS_CHARSET_CHANGE);
7346 var ToolbarContextMenu = {
7347 updateDownloadsAutoHide(popup) {
7348 let checkbox = document.getElementById(
7349 "toolbar-context-autohide-downloads-button"
7352 popup.triggerNode &&
7353 ["downloads-button", "wrapper-downloads-button"].includes(
7354 popup.triggerNode.id
7356 checkbox.hidden = !isDownloads;
7357 if (DownloadsButton.autoHideDownloadsButton) {
7358 checkbox.setAttribute("checked", "true");
7360 checkbox.removeAttribute("checked");
7364 onDownloadsAutoHideChange(event) {
7365 let autoHide = event.target.getAttribute("checked") == "true";
7366 Services.prefs.setBoolPref("browser.download.autohideButton", autoHide);
7369 updateDownloadsAlwaysOpenPanel(popup) {
7370 let separator = document.getElementById(
7371 "toolbarDownloadsAnchorMenuSeparator"
7373 let checkbox = document.getElementById(
7374 "toolbar-context-always-open-downloads-panel"
7377 popup.triggerNode &&
7378 ["downloads-button", "wrapper-downloads-button"].includes(
7379 popup.triggerNode.id
7381 separator.hidden = checkbox.hidden = !isDownloads;
7383 ? checkbox.setAttribute("checked", "true")
7384 : checkbox.removeAttribute("checked");
7387 onDownloadsAlwaysOpenPanelChange(event) {
7388 let alwaysOpen = event.target.getAttribute("checked") == "true";
7389 Services.prefs.setBoolPref("browser.download.alwaysOpenPanel", alwaysOpen);
7392 _getUnwrappedTriggerNode(popup) {
7393 // Toolbar buttons are wrapped in customize mode. Unwrap if necessary.
7394 let { triggerNode } = popup;
7395 if (triggerNode && gCustomizeMode.isWrappedToolbarItem(triggerNode)) {
7396 return triggerNode.firstElementChild;
7401 _getExtensionId(popup) {
7402 let node = this._getUnwrappedTriggerNode(popup);
7403 return node && node.getAttribute("data-extensionid");
7406 async updateExtension(popup) {
7407 let removeExtension = popup.querySelector(
7408 ".customize-context-removeExtension"
7410 let manageExtension = popup.querySelector(
7411 ".customize-context-manageExtension"
7413 let reportExtension = popup.querySelector(
7414 ".customize-context-reportExtension"
7416 let separator = reportExtension.nextElementSibling;
7417 let id = this._getExtensionId(popup);
7418 let addon = id && (await AddonManager.getAddonByID(id));
7420 for (let element of [removeExtension, manageExtension, separator]) {
7421 element.hidden = !addon;
7424 reportExtension.hidden = !addon || !gAddonAbuseReportEnabled;
7427 removeExtension.disabled = !(
7428 addon.permissions & AddonManager.PERM_CAN_UNINSTALL
7431 ExtensionsUI.originControlsMenu(popup, id);
7435 async removeExtensionForContextAction(popup) {
7436 let id = this._getExtensionId(popup);
7437 await BrowserAddonUI.removeAddon(id, "browserAction");
7440 async reportExtensionForContextAction(popup, reportEntryPoint) {
7441 let id = this._getExtensionId(popup);
7442 await BrowserAddonUI.reportAddon(id, reportEntryPoint);
7445 async openAboutAddonsForContextAction(popup) {
7446 let id = this._getExtensionId(popup);
7447 await BrowserAddonUI.manageAddon(id, "browserAction");
7451 var gPageStyleMenu = {
7452 // This maps from a <browser> element (or, more specifically, a
7453 // browser's permanentKey) to an Object that contains the most recent
7454 // information about the browser content's stylesheets. That Object
7455 // is populated via the PageStyle:StyleSheets message from the content
7456 // process. The Object should have the following structure:
7458 // filteredStyleSheets (Array):
7459 // An Array of objects with a filtered list representing all stylesheets
7460 // that the current page offers. Each object has the following members:
7463 // The title of the stylesheet
7466 // Whether or not the stylesheet is currently applied
7469 // The URL of the stylesheet. Stylesheets loaded via a data URL will
7470 // have this property set to null.
7472 // authorStyleDisabled (bool):
7473 // Whether or not the user currently has "No Style" selected for
7474 // the current page.
7476 // preferredStyleSheetSet (bool):
7477 // Whether or not the user currently has the "Default" style selected
7478 // for the current page.
7480 _pageStyleSheets: new WeakMap(),
7483 * Add/append styleSheets to the _pageStyleSheets weakmap.
7484 * @param styleSheets
7485 * The stylesheets to add, including the preferred
7486 * stylesheet set for this document.
7487 * @param permanentKey
7488 * The permanent key of the browser that
7489 * these stylesheets come from.
7491 addBrowserStyleSheets(styleSheets, permanentKey) {
7492 let sheetData = this._pageStyleSheets.get(permanentKey);
7494 this._pageStyleSheets.set(permanentKey, styleSheets);
7497 sheetData.filteredStyleSheets.push(...styleSheets.filteredStyleSheets);
7498 sheetData.preferredStyleSheetSet =
7499 sheetData.preferredStyleSheetSet || styleSheets.preferredStyleSheetSet;
7502 clearBrowserStyleSheets(permanentKey) {
7503 this._pageStyleSheets.delete(permanentKey);
7506 _getStyleSheetInfo(browser) {
7507 let data = this._pageStyleSheets.get(browser.permanentKey);
7510 filteredStyleSheets: [],
7511 authorStyleDisabled: false,
7512 preferredStyleSheetSet: true,
7519 fillPopup(menuPopup) {
7520 let styleSheetInfo = this._getStyleSheetInfo(gBrowser.selectedBrowser);
7521 var noStyle = menuPopup.firstElementChild;
7522 var persistentOnly = noStyle.nextElementSibling;
7523 var sep = persistentOnly.nextElementSibling;
7524 while (sep.nextElementSibling) {
7525 menuPopup.removeChild(sep.nextElementSibling);
7528 let styleSheets = styleSheetInfo.filteredStyleSheets;
7529 var currentStyleSheets = {};
7530 var styleDisabled = styleSheetInfo.authorStyleDisabled;
7531 var haveAltSheets = false;
7532 var altStyleSelected = false;
7534 for (let currentStyleSheet of styleSheets) {
7535 if (!currentStyleSheet.disabled) {
7536 altStyleSelected = true;
7539 haveAltSheets = true;
7541 let lastWithSameTitle = null;
7542 if (currentStyleSheet.title in currentStyleSheets) {
7543 lastWithSameTitle = currentStyleSheets[currentStyleSheet.title];
7546 if (!lastWithSameTitle) {
7547 let menuItem = document.createXULElement("menuitem");
7548 menuItem.setAttribute("type", "radio");
7549 menuItem.setAttribute("label", currentStyleSheet.title);
7550 menuItem.setAttribute("data", currentStyleSheet.title);
7551 menuItem.setAttribute(
7553 !currentStyleSheet.disabled && !styleDisabled
7555 menuItem.setAttribute(
7557 "gPageStyleMenu.switchStyleSheet(this.getAttribute('data'));"
7559 menuPopup.appendChild(menuItem);
7560 currentStyleSheets[currentStyleSheet.title] = menuItem;
7561 } else if (currentStyleSheet.disabled) {
7562 lastWithSameTitle.removeAttribute("checked");
7566 noStyle.setAttribute("checked", styleDisabled);
7567 persistentOnly.setAttribute("checked", !altStyleSelected && !styleDisabled);
7568 persistentOnly.hidden = styleSheetInfo.preferredStyleSheetSet
7571 sep.hidden = (noStyle.hidden && persistentOnly.hidden) || !haveAltSheets;
7575 * Send a message to all PageStyleParents by walking the BrowsingContext tree.
7577 * The string message to send to each PageStyleChild.
7579 * The data to send to each PageStyleChild within the message.
7581 _sendMessageToAll(message, data) {
7582 let contextsToVisit = [gBrowser.selectedBrowser.browsingContext];
7583 while (contextsToVisit.length) {
7584 let currentContext = contextsToVisit.pop();
7585 let global = currentContext.currentWindowGlobal;
7591 let actor = global.getActor("PageStyle");
7592 actor.sendAsyncMessage(message, data);
7594 contextsToVisit.push(...currentContext.children);
7599 * Switch the stylesheet of all documents in the current browser.
7600 * @param title The title of the stylesheet to switch to.
7602 switchStyleSheet(title) {
7603 let { permanentKey } = gBrowser.selectedBrowser;
7604 let sheetData = this._pageStyleSheets.get(permanentKey);
7605 if (sheetData && sheetData.filteredStyleSheets) {
7606 sheetData.authorStyleDisabled = false;
7607 for (let sheet of sheetData.filteredStyleSheets) {
7608 sheet.disabled = sheet.title !== title;
7611 this._sendMessageToAll("PageStyle:Switch", { title });
7615 * Disable all stylesheets. Called with View > Page Style > No Style.
7618 let { permanentKey } = gBrowser.selectedBrowser;
7619 let sheetData = this._pageStyleSheets.get(permanentKey);
7621 sheetData.authorStyleDisabled = true;
7623 this._sendMessageToAll("PageStyle:Disable", {});
7627 // Note that this is also called from non-browser windows on OSX, which do
7628 // share menu items but not much else. See nonbrowser-mac.js.
7629 var BrowserOffline = {
7632 // BrowserOffline Public Methods
7634 if (!this._uiElement) {
7635 this._uiElement = document.getElementById("cmd_toggleOfflineStatus");
7638 Services.obs.addObserver(this, "network:offline-status-changed");
7640 this._updateOfflineUI(Services.io.offline);
7642 this._inited = true;
7647 Services.obs.removeObserver(this, "network:offline-status-changed");
7651 toggleOfflineStatus() {
7652 var ioService = Services.io;
7654 if (!ioService.offline && !this._canGoOffline()) {
7655 this._updateOfflineUI(false);
7659 ioService.offline = !ioService.offline;
7663 observe(aSubject, aTopic, aState) {
7664 if (aTopic != "network:offline-status-changed") {
7668 // This notification is also received because of a loss in connectivity,
7669 // which we ignore by updating the UI to the current value of io.offline
7670 this._updateOfflineUI(Services.io.offline);
7673 // BrowserOffline Implementation Methods
7676 var cancelGoOffline = Cc["@mozilla.org/supports-PRBool;1"].createInstance(
7677 Ci.nsISupportsPRBool
7679 Services.obs.notifyObservers(cancelGoOffline, "offline-requested");
7681 // Something aborted the quit process.
7682 if (cancelGoOffline.data) {
7691 _updateOfflineUI(aOffline) {
7692 var offlineLocked = Services.prefs.prefIsLocked("network.online");
7693 if (offlineLocked) {
7694 this._uiElement.setAttribute("disabled", "true");
7697 this._uiElement.setAttribute("checked", aOffline);
7701 var CanvasPermissionPromptHelper = {
7702 _permissionsPrompt: "canvas-permissions-prompt",
7703 _permissionsPromptHideDoorHanger: "canvas-permissions-prompt-hide-doorhanger",
7704 _notificationIcon: "canvas-notification-icon",
7707 Services.obs.addObserver(this, this._permissionsPrompt);
7708 Services.obs.addObserver(this, this._permissionsPromptHideDoorHanger);
7712 Services.obs.removeObserver(this, this._permissionsPrompt);
7713 Services.obs.removeObserver(this, this._permissionsPromptHideDoorHanger);
7716 // aSubject is an nsIBrowser (e10s) or an nsIDOMWindow (non-e10s).
7717 // aData is an Origin string.
7718 observe(aSubject, aTopic, aData) {
7720 aTopic != this._permissionsPrompt &&
7721 aTopic != this._permissionsPromptHideDoorHanger
7727 if (aSubject instanceof Ci.nsIDOMWindow) {
7728 browser = aSubject.docShell.chromeEventHandler;
7733 if (gBrowser.selectedBrowser !== browser) {
7734 // Must belong to some other window.
7738 let message = gNavigatorBundle.getFormattedString(
7739 "canvas.siteprompt2",
7744 let principal = Services.scriptSecurityManager.createContentPrincipalFromOrigin(
7748 function setCanvasPermission(aPerm, aPersistent) {
7749 Services.perms.addFromPrincipal(
7754 ? Ci.nsIPermissionManager.EXPIRE_NEVER
7755 : Ci.nsIPermissionManager.EXPIRE_SESSION
7760 label: gNavigatorBundle.getString("canvas.allow2"),
7761 accessKey: gNavigatorBundle.getString("canvas.allow2.accesskey"),
7763 setCanvasPermission(
7764 Ci.nsIPermissionManager.ALLOW_ACTION,
7765 state && state.checkboxChecked
7770 let secondaryActions = [
7772 label: gNavigatorBundle.getString("canvas.block"),
7773 accessKey: gNavigatorBundle.getString("canvas.block.accesskey"),
7775 setCanvasPermission(
7776 Ci.nsIPermissionManager.DENY_ACTION,
7777 state && state.checkboxChecked
7784 // In PB mode, we don't want the "always remember" checkbox
7785 show: !PrivateBrowsingUtils.isWindowPrivate(window),
7787 if (checkbox.show) {
7788 checkbox.checked = true;
7789 checkbox.label = gBrowserBundle.GetStringFromName("canvas.remember2");
7794 name: principal.host,
7796 Services.urlFormatter.formatURLPref("app.support.baseURL") +
7797 "fingerprint-permission",
7798 dismissed: aTopic == this._permissionsPromptHideDoorHanger,
7800 if (e == "showing") {
7801 this.browser.ownerDocument.getElementById(
7802 "canvas-permissions-prompt-warning"
7803 ).textContent = gBrowserBundle.GetStringFromName(
7804 "canvas.siteprompt2.warning"
7809 PopupNotifications.show(
7811 this._permissionsPrompt,
7813 this._notificationIcon,
7821 var WebAuthnPromptHelper = {
7822 _icon: "webauthn-notification-icon",
7823 _topic: "webauthn-prompt",
7825 // The current notification, if any. The U2F manager is a singleton, we will
7826 // never allow more than one active request. And thus we'll never have more
7827 // than one notification either.
7830 // The current transaction ID. Will be checked when we're notified of the
7831 // cancellation of an ongoing WebAuthhn request.
7835 Services.obs.addObserver(this, this._topic);
7839 Services.obs.removeObserver(this, this._topic);
7842 observe(aSubject, aTopic, aData) {
7843 let mgr = aSubject.QueryInterface(Ci.nsIU2FTokenManager);
7844 let data = JSON.parse(aData);
7846 // If we receive a cancel, it might be a WebAuthn prompt starting in another
7847 // window, and the other window's browsing context will send out the
7848 // cancellations, so any cancel action we get should prompt us to cancel.
7849 if (data.action == "cancel") {
7854 data.browsingContextId !== gBrowser.selectedBrowser.browsingContext.id
7856 // Must belong to some other window.
7860 if (data.action == "register") {
7861 this.register(mgr, data);
7862 } else if (data.action == "register-direct") {
7863 this.registerDirect(mgr, data);
7864 } else if (data.action == "sign") {
7865 this.sign(mgr, data);
7869 register(mgr, { origin, tid }) {
7870 let mainAction = this.buildCancelAction(mgr, tid);
7871 this.show(tid, "register", "webauthn.registerPrompt2", origin, mainAction);
7874 registerDirect(mgr, { origin, tid }) {
7875 let mainAction = this.buildProceedAction(mgr, tid);
7876 let secondaryActions = [this.buildCancelAction(mgr, tid)];
7879 Services.urlFormatter.formatURLPref("app.support.baseURL") +
7880 "webauthn-direct-attestation";
7885 label: gNavigatorBundle.getString("webauthn.anonymize"),
7887 hintText: "webauthn.registerDirectPromptHint",
7892 "webauthn.registerDirectPrompt3",
7900 sign(mgr, { origin, tid }) {
7901 let mainAction = this.buildCancelAction(mgr, tid);
7902 this.show(tid, "sign", "webauthn.signPrompt2", origin, mainAction);
7911 secondaryActions = [],
7917 origin = Services.io.newURI(origin).asciiHost;
7919 /* Might fail for arbitrary U2F RP IDs. */
7922 let brandShortName = document
7923 .getElementById("bundle_brand")
7924 .getString("brandShortName");
7925 let message = gNavigatorBundle.getFormattedString(stringId, [
7929 if (options.hintText) {
7930 options.hintText = gNavigatorBundle.getFormattedString(options.hintText, [
7935 options.name = origin;
7936 options.hideClose = true;
7937 options.persistent = true;
7938 options.eventCallback = event => {
7939 if (event == "removed") {
7940 this._current = null;
7946 this._current = PopupNotifications.show(
7947 gBrowser.selectedBrowser,
7948 `webauthn-prompt-${id}`,
7958 if (this._tid == tid) {
7964 if (this._current) {
7965 this._current.remove();
7969 buildProceedAction(mgr, tid) {
7971 label: gNavigatorBundle.getString("webauthn.proceed"),
7972 accessKey: gNavigatorBundle.getString("webauthn.proceed.accesskey"),
7974 mgr.resumeRegister(tid, state.checkboxChecked);
7979 buildCancelAction(mgr, tid) {
7981 label: gNavigatorBundle.getString("webauthn.cancel"),
7982 accessKey: gNavigatorBundle.getString("webauthn.cancel.accesskey"),
7990 function CanCloseWindow() {
7991 // Avoid redundant calls to canClose from showing multiple
7992 // PermitUnload dialogs.
7993 if (Services.startup.shuttingDown || window.skipNextCanClose) {
7997 for (let browser of gBrowser.browsers) {
7998 // Don't instantiate lazy browsers.
7999 if (!browser.isConnected) {
8003 let { permitUnload } = browser.permitUnload();
8004 if (!permitUnload) {
8011 function WindowIsClosing(event) {
8014 let target = event.sourceEvent?.target;
8015 if (target?.id?.startsWith("menu_")) {
8016 source = "menuitem";
8017 } else if (target?.nodeName == "toolbarbutton") {
8018 source = "close-button";
8020 let key = AppConstants.platform == "macosx" ? "metaKey" : "ctrlKey";
8021 source = event[key] ? "shortcut" : "OS";
8024 if (!closeWindow(false, warnAboutClosingWindow, source)) {
8028 // In theory we should exit here and the Window's internal Close
8029 // method should trigger canClose on nsBrowserAccess. However, by
8030 // that point it's too late to be able to show a prompt for
8031 // PermitUnload. So we do it here, when we still can.
8032 if (CanCloseWindow()) {
8033 // This flag ensures that the later canClose call does nothing.
8034 // It's only needed to make tests pass, since they detect the
8035 // prompt even when it's not actually shown.
8036 window.skipNextCanClose = true;
8044 * Checks if this is the last full *browser* window around. If it is, this will
8045 * be communicated like quitting. Otherwise, we warn about closing multiple tabs.
8047 * @param source where the request to close came from (used for telemetry)
8048 * @returns true if closing can proceed, false if it got cancelled.
8050 function warnAboutClosingWindow(source) {
8051 // Popups aren't considered full browser windows; we also ignore private windows.
8053 PrivateBrowsingUtils.isWindowPrivate(window) &&
8054 !PrivateBrowsingUtils.permanentPrivateBrowsing;
8056 if (!isPBWindow && !toolbar.visible) {
8057 return gBrowser.warnAboutClosingTabs(
8058 gBrowser.visibleTabs.length,
8059 gBrowser.closingTabsEnum.ALL,
8064 // Figure out if there's at least one other browser window around.
8065 let otherPBWindowExists = false;
8066 let otherWindowExists = false;
8067 for (let win of browserWindows()) {
8068 if (!win.closed && win != window) {
8069 otherWindowExists = true;
8070 if (isPBWindow && PrivateBrowsingUtils.isWindowPrivate(win)) {
8071 otherPBWindowExists = true;
8073 // If the current window is not in private browsing mode we don't need to
8074 // look for other pb windows, we can leave the loop when finding the
8075 // first non-popup window. If however the current window is in private
8076 // browsing mode then we need at least one other pb and one non-popup
8077 // window to break out early.
8078 if (!isPBWindow || otherPBWindowExists) {
8084 if (isPBWindow && !otherPBWindowExists) {
8085 let exitingCanceled = Cc["@mozilla.org/supports-PRBool;1"].createInstance(
8086 Ci.nsISupportsPRBool
8088 exitingCanceled.data = false;
8089 Services.obs.notifyObservers(exitingCanceled, "last-pb-context-exiting");
8090 if (exitingCanceled.data) {
8095 if (otherWindowExists) {
8098 gBrowser.warnAboutClosingTabs(
8099 gBrowser.visibleTabs.length,
8100 gBrowser.closingTabsEnum.ALL,
8106 let os = Services.obs;
8108 let closingCanceled = Cc["@mozilla.org/supports-PRBool;1"].createInstance(
8109 Ci.nsISupportsPRBool
8111 os.notifyObservers(closingCanceled, "browser-lastwindow-close-requested");
8112 if (closingCanceled.data) {
8116 os.notifyObservers(null, "browser-lastwindow-close-granted");
8118 // OS X doesn't quit the application when the last window is closed, but keeps
8119 // the session alive. Hence don't prompt users to save tabs, but warn about
8120 // closing multiple tabs.
8122 AppConstants.platform != "macosx" ||
8124 gBrowser.warnAboutClosingTabs(
8125 gBrowser.visibleTabs.length,
8126 gBrowser.closingTabsEnum.ALL,
8132 var MailIntegration = {
8133 sendLinkForBrowser(aBrowser) {
8135 gURLBar.makeURIReadable(aBrowser.currentURI).displaySpec,
8136 aBrowser.contentTitle
8140 sendMessage(aBody, aSubject) {
8141 // generate a mailto url based on the url and the url's title
8142 var mailtoUrl = "mailto:";
8144 mailtoUrl += "?body=" + encodeURIComponent(aBody);
8145 mailtoUrl += "&subject=" + encodeURIComponent(aSubject);
8148 var uri = makeURI(mailtoUrl);
8150 // now pass this uri to the operating system
8151 this._launchExternalUrl(uri);
8154 // a generic method which can be used to pass arbitrary urls to the operating
8156 // aURL --> a nsIURI which represents the url to launch
8157 _launchExternalUrl(aURL) {
8158 var extProtocolSvc = Cc[
8159 "@mozilla.org/uriloader/external-protocol-service;1"
8160 ].getService(Ci.nsIExternalProtocolService);
8161 if (extProtocolSvc) {
8162 extProtocolSvc.loadURI(
8164 Services.scriptSecurityManager.getSystemPrincipal()
8171 * Open about:addons page by given view id.
8172 * @param {String} aView
8173 * View id of page that will open.
8174 * e.g. "addons://discover/"
8175 * @param {Object} options
8177 * selectTabByViewId: If true, if there is the tab opening page having
8178 * same view id, select the tab. Else if the current
8179 * page is blank, load on it. Otherwise, open a new
8180 * tab, then load on it.
8181 * If false, if there is the tab opening
8182 * about:addoons page, select the tab and load page
8183 * for view id on it. Otherwise, leave the loading
8184 * behavior to switchToTabHavingURI().
8185 * If no options, handles as false.
8187 * @returns {Promise} When the Promise resolves, returns window object loaded the
8190 function BrowserOpenAddonsMgr(aView, { selectTabByViewId = false } = {}) {
8191 return new Promise(resolve => {
8195 var receivePong = function(aSubject, aTopic, aData) {
8196 let browserWin = aSubject.browsingContext.topChromeWindow;
8197 if (!emWindow || browserWin == window /* favor the current window */) {
8199 selectTabByViewId &&
8200 aSubject.gViewController.currentViewId !== aView
8205 emWindow = aSubject;
8206 browserWindow = browserWin;
8209 Services.obs.addObserver(receivePong, "EM-pong");
8210 Services.obs.notifyObservers(null, "EM-ping");
8211 Services.obs.removeObserver(receivePong, "EM-pong");
8214 if (aView && !selectTabByViewId) {
8215 emWindow.loadView(aView);
8217 let tab = browserWindow.gBrowser.getTabForBrowser(
8218 emWindow.docShell.chromeEventHandler
8220 browserWindow.gBrowser.selectedTab = tab;
8226 if (selectTabByViewId) {
8227 const target = isBlankPageURL(gBrowser.currentURI.spec)
8230 openTrustedLinkIn("about:addons", target);
8232 // This must be a new load, else the ping/pong would have
8233 // found the window above.
8234 switchToTabHavingURI("about:addons", true);
8237 Services.obs.addObserver(function observer(aSubject, aTopic, aData) {
8238 Services.obs.removeObserver(observer, aTopic);
8240 aSubject.loadView(aView);
8248 function AddKeywordForSearchField() {
8249 if (!gContextMenu) {
8250 throw new Error("Context menu doesn't seem to be open.");
8253 gContextMenu.addKeywordForSearchField();
8257 * Re-open a closed tab.
8259 * The index of the tab (via SessionStore.getClosedTabData)
8260 * @returns a reference to the reopened tab.
8262 function undoCloseTab(aIndex) {
8263 // wallpaper patch to prevent an unnecessary blank tab (bug 343895)
8264 let blankTabToRemove = null;
8265 if (gBrowser.tabs.length == 1 && gBrowser.selectedTab.isEmpty) {
8266 blankTabToRemove = gBrowser.selectedTab;
8270 // aIndex is undefined if the function is called without a specific tab to restore.
8272 aIndex !== undefined
8274 : new Array(SessionStore.getLastClosedTabCount(window)).fill(0);
8275 let tabsRemoved = false;
8276 for (let index of tabsToRemove) {
8277 if (SessionStore.getClosedTabCount(window) > index) {
8278 tab = SessionStore.undoCloseTab(window, index);
8283 if (tabsRemoved && blankTabToRemove) {
8284 gBrowser.removeTab(blankTabToRemove);
8291 * Re-open a closed window.
8293 * The index of the window (via SessionStore.getClosedWindowData)
8294 * @returns a reference to the reopened window.
8296 function undoCloseWindow(aIndex) {
8298 if (SessionStore.getClosedWindowCount() > (aIndex || 0)) {
8299 window = SessionStore.undoCloseWindow(aIndex || 0);
8305 function ReportFalseDeceptiveSite() {
8306 let contextsToVisit = [gBrowser.selectedBrowser.browsingContext];
8307 while (contextsToVisit.length) {
8308 let currentContext = contextsToVisit.pop();
8309 let global = currentContext.currentWindowGlobal;
8314 let docURI = global.documentURI;
8315 // Ensure the page is an about:blocked pagae before handling.
8316 if (docURI && docURI.spec.startsWith("about:blocked?e=deceptiveBlocked")) {
8317 let actor = global.getActor("BlockedSite");
8318 actor.sendQuery("DeceptiveBlockedDetails").then(data => {
8319 let reportUrl = gSafeBrowsing.getReportURL(
8324 openTrustedLinkIn(reportUrl, "tab");
8326 let bundle = Services.strings.createBundle(
8327 "chrome://browser/locale/safebrowsing/safebrowsing.properties"
8329 Services.prompt.alert(
8331 bundle.GetStringFromName("errorReportFalseDeceptiveTitle"),
8332 bundle.formatStringFromName("errorReportFalseDeceptiveMessage", [
8333 data.blockedInfo.provider,
8340 contextsToVisit.push(...currentContext.children);
8345 * This is a temporary hack to connect a Help menu item for reporting
8346 * site issues to the WebCompat team's Site Compatability Reporter
8347 * WebExtension, which ships by default and is enabled on pre-release
8350 * Once we determine if Help is the right place for it, we'll do something
8351 * slightly better than this.
8355 function ReportSiteIssue() {
8356 let subject = { wrappedJSObject: gBrowser.selectedTab };
8357 Services.obs.notifyObservers(subject, "report-site-issue");
8361 * When the browser is being controlled from out-of-process,
8362 * e.g. when Marionette or the remote debugging protocol is used,
8363 * we add a visual hint to the browser UI to indicate to the user
8364 * that the browser session is under remote control.
8366 * This is called when the content browser initialises (from gBrowserInit.onLoad())
8367 * and when the "remote-listening" system notification fires.
8369 const gRemoteControl = {
8370 observe(subject, topic, data) {
8371 gRemoteControl.updateVisualCue();
8375 // Disable updating the remote control cue for performance tests,
8376 // because these could fail due to an early initialization of Marionette.
8377 const disableRemoteControlCue = Services.prefs.getBoolPref(
8378 "browser.chrome.disableRemoteControlCueForTests",
8381 if (disableRemoteControlCue && Cu.isInAutomation) {
8385 const mainWindow = document.documentElement;
8386 const remoteControlComponent = this.getRemoteControlComponent();
8387 if (remoteControlComponent) {
8388 mainWindow.setAttribute("remotecontrol", "true");
8389 const remoteControlIcon = document.getElementById("remote-control-icon");
8390 document.l10n.setAttributes(
8392 "urlbar-remote-control-notification-anchor2",
8393 { component: remoteControlComponent }
8396 mainWindow.removeAttribute("remotecontrol");
8400 getRemoteControlComponent() {
8401 // For DevTools sockets, only show the remote control cue if the socket is
8402 // not coming from a regular Browser Toolbox debugging session.
8404 DevToolsSocketStatus.hasSocketOpened({
8405 excludeBrowserToolboxSockets: true,
8411 if (Marionette.running) {
8412 return "Marionette";
8415 if (RemoteAgent.running) {
8416 return "RemoteAgent";
8423 // Note that this is also called from non-browser windows on OSX, which do
8424 // share menu items but not much else. See nonbrowser-mac.js.
8425 var gPrivateBrowsingUI = {
8426 init: function PBUI_init() {
8427 // Do nothing for normal windows
8428 if (!PrivateBrowsingUtils.isWindowPrivate(window)) {
8432 // Disable the Clear Recent History... menu item when in PB mode
8433 // temporary fix until bug 463607 is fixed
8434 document.getElementById("Tools:Sanitize").setAttribute("disabled", "true");
8436 if (window.location.href != AppConstants.BROWSER_CHROME_URL) {
8440 // Adjust the window's title
8441 let docElement = document.documentElement;
8442 docElement.setAttribute(
8443 "privatebrowsingmode",
8444 PrivateBrowsingUtils.permanentPrivateBrowsing ? "permanent" : "temporary"
8446 // If enabled, show the new private browsing indicator with label.
8447 // This will hide the old indicator.
8448 docElement.toggleAttribute(
8449 "privatebrowsingnewindicator",
8450 NimbusFeatures.majorRelease2022.getVariable("feltPrivacyPBMNewIndicator")
8453 gBrowser.updateTitlebar();
8455 if (PrivateBrowsingUtils.permanentPrivateBrowsing) {
8456 // Adjust the New Window menu entries
8457 let newWindow = document.getElementById("menu_newNavigator");
8458 let newPrivateWindow = document.getElementById("menu_newPrivateWindow");
8459 if (newWindow && newPrivateWindow) {
8460 newPrivateWindow.hidden = true;
8461 newWindow.label = newPrivateWindow.label;
8462 newWindow.accessKey = newPrivateWindow.accessKey;
8463 newWindow.command = newPrivateWindow.command;
8470 * Switch to a tab that has a given URI, and focuses its browser window.
8471 * If a matching tab is in this window, it will be switched to. Otherwise, other
8472 * windows will be searched.
8477 * True to open a new tab and switch to it, if no existing tab is found.
8478 * If no suitable window is found, a new one will be opened.
8479 * @param aOpenParams
8480 * If switching to this URI results in us opening a tab, aOpenParams
8481 * will be the parameter object that gets passed to openTrustedLinkIn. Please
8482 * see the documentation for openTrustedLinkIn to see what parameters can be
8483 * passed via this object.
8484 * This object also allows:
8485 * - 'ignoreFragment' property to be set to true to exclude fragment-portion
8486 * matching when comparing URIs.
8487 * If set to "whenComparing", the fragment will be unmodified.
8488 * If set to "whenComparingAndReplace", the fragment will be replaced.
8489 * - 'ignoreQueryString' boolean property to be set to true to exclude query string
8490 * matching when comparing URIs.
8491 * - 'replaceQueryString' boolean property to be set to true to exclude query string
8492 * matching when comparing URIs and overwrite the initial query string with
8493 * the one from the new URI.
8494 * - 'adoptIntoActiveWindow' boolean property to be set to true to adopt the tab
8495 * into the current window.
8496 * @return True if an existing tab was found, false otherwise
8498 function switchToTabHavingURI(aURI, aOpenNew, aOpenParams = {}) {
8499 // Certain URLs can be switched to irrespective of the source or destination
8500 // window being in private browsing mode:
8501 const kPrivateBrowsingWhitelist = new Set(["about:addons"]);
8503 let ignoreFragment = aOpenParams.ignoreFragment;
8504 let ignoreQueryString = aOpenParams.ignoreQueryString;
8505 let replaceQueryString = aOpenParams.replaceQueryString;
8506 let adoptIntoActiveWindow = aOpenParams.adoptIntoActiveWindow;
8508 // These properties are only used by switchToTabHavingURI and should
8509 // not be used as a parameter for the new load.
8510 delete aOpenParams.ignoreFragment;
8511 delete aOpenParams.ignoreQueryString;
8512 delete aOpenParams.replaceQueryString;
8513 delete aOpenParams.adoptIntoActiveWindow;
8515 let isBrowserWindow = !!window.gBrowser;
8517 // This will switch to the tab in aWindow having aURI, if present.
8518 function switchIfURIInWindow(aWindow) {
8519 // We can switch tab only if if both the source and destination windows have
8520 // the same private-browsing status.
8522 !kPrivateBrowsingWhitelist.has(aURI.spec) &&
8523 PrivateBrowsingUtils.isWindowPrivate(window) !==
8524 PrivateBrowsingUtils.isWindowPrivate(aWindow)
8529 // Remove the query string, fragment, both, or neither from a given url.
8530 function cleanURL(url, removeQuery, removeFragment) {
8532 if (removeFragment) {
8533 ret = ret.split("#")[0];
8535 // This removes a query, if present before the fragment.
8536 ret = ret.split("?")[0];
8538 } else if (removeQuery) {
8539 // This is needed in case there is a fragment after the query.
8540 let fragment = ret.split("#")[1];
8543 .concat(fragment != undefined ? "#".concat(fragment) : "");
8548 // Need to handle nsSimpleURIs here too (e.g. about:...), which don't
8549 // work correctly with URL objects - so treat them as strings
8550 let ignoreFragmentWhenComparing =
8551 typeof ignoreFragment == "string" &&
8552 ignoreFragment.startsWith("whenComparing");
8553 let requestedCompare = cleanURL(
8555 ignoreQueryString || replaceQueryString,
8556 ignoreFragmentWhenComparing
8558 let browsers = aWindow.gBrowser.browsers;
8559 for (let i = 0; i < browsers.length; i++) {
8560 let browser = browsers[i];
8561 let browserCompare = cleanURL(
8562 browser.currentURI.displaySpec,
8563 ignoreQueryString || replaceQueryString,
8564 ignoreFragmentWhenComparing
8566 if (requestedCompare == browserCompare) {
8567 // If adoptIntoActiveWindow is set, and this is a cross-window switch,
8568 // adopt the tab into the current window, after the active tab.
8570 adoptIntoActiveWindow && isBrowserWindow && aWindow != window;
8573 const newTab = window.gBrowser.adoptTab(
8574 aWindow.gBrowser.getTabForBrowser(browser),
8575 window.gBrowser.tabContainer.selectedIndex + 1,
8576 /* aSelectTab = */ true
8586 if (ignoreFragment == "whenComparingAndReplace" || replaceQueryString) {
8587 browser.loadURI(aURI.spec, {
8588 triggeringPrincipal:
8589 aOpenParams.triggeringPrincipal ||
8590 _createNullPrincipalFromTabUserContextId(),
8595 aWindow.gBrowser.tabContainer.selectedIndex = i;
8604 // This can be passed either nsIURI or a string.
8605 if (!(aURI instanceof Ci.nsIURI)) {
8606 aURI = Services.io.newURI(aURI);
8609 // Prioritise this window.
8610 if (isBrowserWindow && switchIfURIInWindow(window)) {
8614 for (let browserWin of browserWindows()) {
8615 // Skip closed (but not yet destroyed) windows,
8616 // and the current window (which was checked earlier).
8617 if (browserWin.closed || browserWin == window) {
8620 if (switchIfURIInWindow(browserWin)) {
8625 // No opened tab has that url.
8627 if (isBrowserWindow && gBrowser.selectedTab.isEmpty) {
8628 openTrustedLinkIn(aURI.spec, "current", aOpenParams);
8630 openTrustedLinkIn(aURI.spec, "tab", aOpenParams);
8637 var RestoreLastSessionObserver = {
8640 SessionStore.canRestoreLastSession &&
8641 !PrivateBrowsingUtils.isWindowPrivate(window)
8643 Services.obs.addObserver(this, "sessionstore-last-session-cleared", true);
8644 goSetCommandEnabled("Browser:RestoreLastSession", true);
8645 } else if (SessionStore.willAutoRestore) {
8646 document.getElementById("Browser:RestoreLastSession").hidden = true;
8651 // The last session can only be restored once so there's
8652 // no way we need to re-enable our menu item.
8653 Services.obs.removeObserver(this, "sessionstore-last-session-cleared");
8654 goSetCommandEnabled("Browser:RestoreLastSession", false);
8657 QueryInterface: ChromeUtils.generateQI([
8659 "nsISupportsWeakReference",
8663 /* Observes menus and adjusts their size for better
8664 * usability when opened via a touch screen. */
8665 var MenuTouchModeObserver = {
8667 window.addEventListener("popupshowing", this, true);
8670 handleEvent(event) {
8671 let target = event.originalTarget;
8672 if (event.mozInputSource == MouseEvent.MOZ_SOURCE_TOUCH) {
8673 target.setAttribute("touchmode", "true");
8675 target.removeAttribute("touchmode");
8680 window.removeEventListener("popupshowing", this, true);
8684 // Prompt user to restart the browser in safe mode
8685 function safeModeRestart() {
8686 if (Services.appinfo.inSafeMode) {
8687 let cancelQuit = Cc["@mozilla.org/supports-PRBool;1"].createInstance(
8688 Ci.nsISupportsPRBool
8690 Services.obs.notifyObservers(
8692 "quit-application-requested",
8696 if (cancelQuit.data) {
8700 Services.startup.quit(
8701 Ci.nsIAppStartup.eRestart | Ci.nsIAppStartup.eAttemptQuit
8706 Services.obs.notifyObservers(window, "restart-in-safe-mode");
8709 /* duplicateTabIn duplicates tab in a place specified by the parameter |where|.
8713 * "tabshifted" same as "tab" but in background if default is to select new
8714 * tabs, and vice versa
8715 * "window" new window
8717 * delta is the offset to the history entry that you want to load.
8719 function duplicateTabIn(aTab, where, delta) {
8722 let otherWin = OpenBrowserWindow({
8723 private: PrivateBrowsingUtils.isBrowserPrivate(aTab.linkedBrowser),
8725 let delayedStartupFinished = (subject, topic) => {
8727 topic == "browser-delayed-startup-finished" &&
8730 Services.obs.removeObserver(delayedStartupFinished, topic);
8731 let otherGBrowser = otherWin.gBrowser;
8732 let otherTab = otherGBrowser.selectedTab;
8733 SessionStore.duplicateTab(otherWin, aTab, delta);
8734 otherGBrowser.removeTab(otherTab, { animate: false });
8738 Services.obs.addObserver(
8739 delayedStartupFinished,
8740 "browser-delayed-startup-finished"
8744 SessionStore.duplicateTab(window, aTab, delta);
8745 // A background tab has been opened, nothing else to do here.
8748 SessionStore.duplicateTab(window, aTab, delta, true, {
8749 inBackground: false,
8755 var MousePosTracker = {
8756 _listeners: new Set(),
8761 * Registers a listener.
8763 * @param listener (object)
8764 * A listener is expected to expose the following properties:
8766 * getMouseTargetRect (function)
8767 * Returns the rect that the MousePosTracker needs to alert
8768 * the listener about if the mouse happens to be within it.
8770 * onMouseEnter (function, optional)
8771 * The function to be called if the mouse enters the rect
8772 * returned by getMouseTargetRect. MousePosTracker always
8773 * runs this inside of a requestAnimationFrame, since it
8774 * assumes that the notification is used to update the DOM.
8776 * onMouseLeave (function, optional)
8777 * The function to be called if the mouse exits the rect
8778 * returned by getMouseTargetRect. MousePosTracker always
8779 * runs this inside of a requestAnimationFrame, since it
8780 * assumes that the notification is used to update the DOM.
8782 addListener(listener) {
8783 if (this._listeners.has(listener)) {
8787 listener._hover = false;
8788 this._listeners.add(listener);
8790 this._callListener(listener);
8793 removeListener(listener) {
8794 this._listeners.delete(listener);
8797 handleEvent(event) {
8798 let fullZoom = window.windowUtils.fullZoom;
8799 this._x = event.screenX / fullZoom - window.mozInnerScreenX;
8800 this._y = event.screenY / fullZoom - window.mozInnerScreenY;
8802 this._listeners.forEach(listener => {
8804 this._callListener(listener);
8811 _callListener(listener) {
8812 let rect = listener.getMouseTargetRect();
8814 this._x >= rect.left &&
8815 this._x <= rect.right &&
8816 this._y >= rect.top &&
8817 this._y <= rect.bottom;
8819 if (hover == listener._hover) {
8823 listener._hover = hover;
8826 if (listener.onMouseEnter) {
8827 listener.onMouseEnter();
8829 } else if (listener.onMouseLeave) {
8830 listener.onMouseLeave();
8835 var ToolbarIconColor = {
8839 tabsintitlebar: false,
8842 this._initialized = true;
8844 window.addEventListener("nativethemechange", this);
8845 window.addEventListener("activate", this);
8846 window.addEventListener("deactivate", this);
8847 window.addEventListener("toolbarvisibilitychange", this);
8848 window.addEventListener("windowlwthemeupdate", this);
8850 // If the window isn't active now, we assume that it has never been active
8851 // before and will soon become active such that inferFromText will be
8852 // called from the initial activate event.
8853 if (Services.focus.activeWindow == window) {
8854 this.inferFromText("activate");
8859 this._initialized = false;
8861 window.removeEventListener("nativethemechange", this);
8862 window.removeEventListener("activate", this);
8863 window.removeEventListener("deactivate", this);
8864 window.removeEventListener("toolbarvisibilitychange", this);
8865 window.removeEventListener("windowlwthemeupdate", this);
8868 handleEvent(event) {
8869 switch (event.type) {
8872 case "nativethemechange":
8873 case "windowlwthemeupdate":
8874 this.inferFromText(event.type);
8876 case "toolbarvisibilitychange":
8877 this.inferFromText(event.type, event.visible);
8882 // a cache of luminance values for each toolbar
8883 // to avoid unnecessary calls to getComputedStyle
8884 _toolbarLuminanceCache: new Map(),
8886 inferFromText(reason, reasonValue) {
8887 if (!this._initialized) {
8890 function parseRGB(aColorString) {
8891 let rgb = aColorString.match(/^rgba?\((\d+), (\d+), (\d+)/);
8893 return rgb.map(x => parseInt(x));
8897 case "activate": // falls through
8899 this._windowState.active = reason === "activate";
8902 this._windowState.fullscreen = reasonValue;
8904 case "nativethemechange":
8905 case "windowlwthemeupdate":
8906 // theme change, we'll need to recalculate all color values
8907 this._toolbarLuminanceCache.clear();
8909 case "toolbarvisibilitychange":
8910 // toolbar changes dont require reset of the cached color values
8912 case "tabsintitlebar":
8913 this._windowState.tabsintitlebar = reasonValue;
8917 let toolbarSelector = ".browser-toolbar:not([collapsed=true])";
8918 if (AppConstants.platform == "macosx") {
8919 toolbarSelector += ":not([type=menubar])";
8922 // The getComputedStyle calls and setting the brighttext are separated in
8923 // two loops to avoid flushing layout and making it dirty repeatedly.
8924 let cachedLuminances = this._toolbarLuminanceCache;
8925 let luminances = new Map();
8926 for (let toolbar of document.querySelectorAll(toolbarSelector)) {
8927 // toolbars *should* all have ids, but guard anyway to avoid blowing up
8929 toolbar.id && toolbar.id + JSON.stringify(this._windowState);
8930 // lookup cached luminance value for this toolbar in this window state
8931 let luminance = cacheKey && cachedLuminances.get(cacheKey);
8932 if (isNaN(luminance)) {
8933 let [r, g, b] = parseRGB(getComputedStyle(toolbar).color);
8934 luminance = 0.2125 * r + 0.7154 * g + 0.0721 * b;
8936 cachedLuminances.set(cacheKey, luminance);
8939 luminances.set(toolbar, luminance);
8942 const luminanceThreshold = 127; // In between 0 and 255
8943 for (let [toolbar, luminance] of luminances) {
8944 if (luminance <= luminanceThreshold) {
8945 toolbar.removeAttribute("brighttext");
8947 toolbar.setAttribute("brighttext", "true");
8953 var PanicButtonNotifier = {
8955 this._initialized = true;
8956 if (window.PanicButtonNotifierShouldNotify) {
8957 delete window.PanicButtonNotifierShouldNotify;
8961 createPanelIfNeeded() {
8962 // Lazy load the panic-button-success-notification panel the first time we need to display it.
8963 if (!document.getElementById("panic-button-success-notification")) {
8964 let template = document.getElementById("panicButtonNotificationTemplate");
8965 template.replaceWith(template.content);
8969 if (!this._initialized) {
8970 window.PanicButtonNotifierShouldNotify = true;
8973 // Display notification panel here...
8975 this.createPanelIfNeeded();
8976 let popup = document.getElementById("panic-button-success-notification");
8977 popup.hidden = false;
8978 // To close the popup in 3 seconds after the popup is shown but left uninteracted.
8979 let onTimeout = () => {
8980 PanicButtonNotifier.close();
8983 popup.addEventListener("popupshown", function() {
8984 PanicButtonNotifier.timer = setTimeout(onTimeout, 3000);
8986 // To prevent the popup from closing when user tries to interact with the
8987 // popup using mouse or keyboard.
8988 let onUserInteractsWithPopup = () => {
8989 clearTimeout(PanicButtonNotifier.timer);
8992 popup.addEventListener("mouseover", onUserInteractsWithPopup);
8993 window.addEventListener("keydown", onUserInteractsWithPopup);
8994 let removeListeners = () => {
8995 popup.removeEventListener("mouseover", onUserInteractsWithPopup);
8996 window.removeEventListener("keydown", onUserInteractsWithPopup);
8997 popup.removeEventListener("popuphidden", removeListeners);
8999 popup.addEventListener("popuphidden", removeListeners);
9001 let widget = CustomizableUI.getWidget("panic-button").forWindow(window);
9002 let anchor = widget.anchor.icon;
9003 popup.openPopup(anchor, popup.getAttribute("position"));
9009 let popup = document.getElementById("panic-button-success-notification");
9014 const SafeBrowsingNotificationBox = {
9015 _currentURIBaseDomain: null,
9016 show(title, buttons) {
9017 let uri = gBrowser.currentURI;
9019 // start tracking host so that we know when we leave the domain
9021 this._currentURIBaseDomain = Services.eTLD.getBaseDomain(uri);
9023 // If we can't get the base domain, fallback to use host instead. However,
9024 // host is sometimes empty when the scheme is file. In this case, just use
9026 this._currentURIBaseDomain = uri.asciiHost || uri.asciiSpec;
9029 let notificationBox = gBrowser.getNotificationBox();
9030 let value = "blocked-badware-page";
9032 let previousNotification = notificationBox.getNotificationWithValue(value);
9033 if (previousNotification) {
9034 notificationBox.removeNotification(previousNotification);
9037 let notification = notificationBox.appendNotification(
9041 image: "chrome://global/skin/icons/blocked.svg",
9042 priority: notificationBox.PRIORITY_CRITICAL_HIGH,
9046 // Persist the notification until the user removes so it
9047 // doesn't get removed on redirects.
9048 notification.persistence = -1;
9050 onLocationChange(aLocationURI) {
9051 // take this to represent that you haven't visited a bad place
9052 if (!this._currentURIBaseDomain) {
9056 let newURIBaseDomain = Services.eTLD.getBaseDomain(aLocationURI);
9058 if (newURIBaseDomain !== this._currentURIBaseDomain) {
9059 let notificationBox = gBrowser.getNotificationBox();
9060 let notification = notificationBox.getNotificationWithValue(
9061 "blocked-badware-page"
9064 notificationBox.removeNotification(notification, false);
9067 this._currentURIBaseDomain = null;
9073 * The TabDialogBox supports opening window dialogs as SubDialogs on the tab and content
9074 * level. Both tab and content dialogs have their own separate managers.
9075 * Dialogs will be queued FIFO and cover the web content.
9076 * Dialogs are closed when the user reloads or leaves the page.
9077 * While a dialog is open PopupNotifications, such as permission prompts, are
9080 class TabDialogBox {
9081 static _containerFor(browser) {
9082 // Return the .browserContainer
9083 return browser.parentNode.parentNode;
9086 constructor(browser) {
9087 this._weakBrowserRef = Cu.getWeakReference(browser);
9089 // Create parent element for tab dialogs
9090 let template = document.getElementById("dialogStackTemplate");
9091 let dialogStack = template.content.cloneNode(true).firstElementChild;
9092 dialogStack.classList.add("tab-prompt-dialog");
9094 TabDialogBox._containerFor(browser).appendChild(dialogStack);
9096 // Initially the stack only contains the template
9097 let dialogTemplate = dialogStack.firstElementChild;
9099 // Create dialog manager for prompts at the tab level.
9100 this._tabDialogManager = new SubDialogManager({
9103 orderType: SubDialogManager.ORDER_QUEUE,
9104 allowDuplicateDialogs: true,
9106 consumeOutsideClicks: false,
9112 * Open a dialog on tab or content level.
9113 * @param {String} aURL - URL of the dialog to load in the tab box.
9114 * @param {Object} [aOptions]
9115 * @param {String} [aOptions.features] - Comma separated list of window
9117 * @param {Boolean} [aOptions.allowDuplicateDialogs] - Whether to allow
9118 * showing multiple dialogs with aURL at the same time. If false calls for
9119 * duplicate dialogs will be dropped.
9120 * @param {String} [aOptions.sizeTo] - Pass "available" to stretch dialog to
9121 * roughly content size. Any max-width or max-height style values on the document root
9122 * will also be applied to the dialog box.
9123 * @param {Boolean} [aOptions.keepOpenSameOriginNav] - By default dialogs are
9124 * aborted on any navigation.
9125 * Set to true to keep the dialog open for same origin navigation.
9126 * @param {Number} [aOptions.modalType] - The modal type to create the dialog for.
9127 * By default, we show the dialog for tab prompts.
9128 * @returns {Object} [result] Returns an object { closedPromise, dialog }.
9129 * @returns {Promise} [result.closedPromise] Resolves once the dialog has been closed.
9130 * @returns {SubDialog} [result.dialog] A reference to the opened SubDialog.
9136 allowDuplicateDialogs = true,
9138 keepOpenSameOriginNav,
9140 allowFocusCheckbox = false,
9145 let closedPromise = new Promise(resolve => (resolveClosed = resolve));
9146 // Get the dialog manager to open the prompt with.
9148 modalType === Ci.nsIPrompt.MODAL_TYPE_CONTENT
9149 ? this.getContentDialogManager()
9150 : this._tabDialogManager;
9152 let hasDialogs = () =>
9153 this._tabDialogManager.hasDialogs ||
9154 this._contentDialogManager?.hasDialogs;
9156 if (!hasDialogs()) {
9157 this._onFirstDialogOpen();
9160 let closingCallback = event => {
9161 if (!hasDialogs()) {
9162 this._onLastDialogClose();
9165 if (allowFocusCheckbox && !event.detail?.abort) {
9166 this.maybeSetAllowTabSwitchPermission(event.target);
9170 if (modalType == Ci.nsIPrompt.MODAL_TYPE_CONTENT) {
9171 sizeTo = "limitheight";
9174 // Open dialog and resolve once it has been closed
9175 let dialog = dialogManager.open(
9179 allowDuplicateDialogs,
9182 closedCallback: resolveClosed,
9187 // Marking the dialog externally, instead of passing it as an option.
9188 // The SubDialog(Manager) does not care about navigation.
9189 // dialog can be null here if allowDuplicateDialogs = false.
9191 dialog._keepOpenSameOriginNav = keepOpenSameOriginNav;
9193 return { closedPromise, dialog };
9196 _onFirstDialogOpen() {
9197 // Hide PopupNotifications to prevent them from covering up dialogs.
9198 this.browser.setAttribute("tabDialogShowing", true);
9199 UpdatePopupNotificationsVisibility();
9201 // Register listeners
9202 this._lastPrincipal = this.browser.contentPrincipal;
9203 this.browser.addProgressListener(this, Ci.nsIWebProgress.NOTIFY_LOCATION);
9205 this.tab?.addEventListener("TabClose", this);
9208 _onLastDialogClose() {
9209 // Show PopupNotifications again.
9210 this.browser.removeAttribute("tabDialogShowing");
9211 UpdatePopupNotificationsVisibility();
9213 // Clean up listeners
9214 this.browser.removeProgressListener(this);
9215 this._lastPrincipal = null;
9217 this.tab?.removeEventListener("TabClose", this);
9220 _buildContentPromptDialog() {
9221 let template = document.getElementById("dialogStackTemplate");
9222 let contentDialogStack = template.content.cloneNode(true).firstElementChild;
9223 contentDialogStack.classList.add("content-prompt-dialog");
9225 // Create a dialog manager for content prompts.
9226 let browserContainer = TabDialogBox._containerFor(this.browser);
9227 let tabPromptDialog = browserContainer.querySelector(".tab-prompt-dialog");
9228 browserContainer.insertBefore(contentDialogStack, tabPromptDialog);
9230 let contentDialogTemplate = contentDialogStack.firstElementChild;
9231 this._contentDialogManager = new SubDialogManager({
9232 dialogStack: contentDialogStack,
9233 dialogTemplate: contentDialogTemplate,
9234 orderType: SubDialogManager.ORDER_QUEUE,
9235 allowDuplicateDialogs: true,
9237 consumeOutsideClicks: false,
9242 handleEvent(event) {
9243 if (event.type !== "TabClose") {
9246 this.abortAllDialogs();
9250 this._tabDialogManager.abortDialogs();
9251 this._contentDialogManager?.abortDialogs();
9255 // Prioritize focusing the dialog manager for tab prompts
9256 if (this._tabDialogManager._dialogs.length) {
9257 this._tabDialogManager.focusTopDialog();
9260 this._contentDialogManager?.focusTopDialog();
9264 * If the user navigates away or refreshes the page, close all dialogs for
9265 * the current browser.
9267 onLocationChange(aWebProgress, aRequest, aLocation, aFlags) {
9269 !aWebProgress.isTopLevel ||
9270 aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT
9275 // Dialogs can be exempt from closing on same origin location change.
9278 // Test for same origin location change
9280 this._lastPrincipal?.isSameOrigin(
9282 this.browser.browsingContext.usePrivateBrowsing
9285 filterFn = dialog => !dialog._keepOpenSameOriginNav;
9288 this._lastPrincipal = this.browser.contentPrincipal;
9290 this._tabDialogManager.abortDialogs(filterFn);
9291 this._contentDialogManager?.abortDialogs(filterFn);
9295 return gBrowser.getTabForBrowser(this.browser);
9299 let browser = this._weakBrowserRef.get();
9301 throw new Error("Stale dialog box! The associated browser is gone.");
9306 getTabDialogManager() {
9307 return this._tabDialogManager;
9310 getContentDialogManager() {
9311 if (!this._contentDialogManager) {
9312 this._buildContentPromptDialog();
9314 return this._contentDialogManager;
9317 onNextPromptShowAllowFocusCheckboxFor(principal) {
9318 this._allowTabFocusByPromptPrincipal = principal;
9322 * Sets the "focus-tab-by-prompt" permission for the dialog.
9324 maybeSetAllowTabSwitchPermission(dialog) {
9325 let checkbox = dialog.querySelector("checkbox");
9327 if (checkbox.checked) {
9328 Services.perms.addFromPrincipal(
9329 this._allowTabFocusByPromptPrincipal,
9330 "focus-tab-by-prompt",
9331 Services.perms.ALLOW_ACTION
9335 // Don't show the "allow tab switch checkbox" for subsequent prompts.
9336 this._allowTabFocusByPromptPrincipal = null;
9340 TabDialogBox.prototype.QueryInterface = ChromeUtils.generateQI([
9341 "nsIWebProgressListener",
9342 "nsISupportsWeakReference",
9345 function TabModalPromptBox(browser) {
9346 this._weakBrowserRef = Cu.getWeakReference(browser);
9348 * These WeakMaps holds the TabModalPrompt instances, key to the <tabmodalprompt> prompt
9349 * in the DOM. We don't want to hold the instances directly to avoid leaking.
9351 * WeakMap also prevents us from reading back its insertion order.
9352 * Order of the elements in the DOM should be the only order to consider.
9354 this._contentPrompts = new WeakMap();
9355 this._tabPrompts = new WeakMap();
9358 TabModalPromptBox.prototype = {
9359 _promptCloseCallback(
9361 principalToAllowFocusFor,
9366 principalToAllowFocusFor &&
9367 allowFocusCheckbox &&
9368 allowFocusCheckbox.checked
9370 Services.perms.addFromPrincipal(
9371 principalToAllowFocusFor,
9372 "focus-tab-by-prompt",
9373 Services.perms.ALLOW_ACTION
9376 onCloseCallback.apply(this, args);
9379 getPrompt(promptEl) {
9380 if (promptEl.classList.contains("tab-prompt")) {
9381 return this._tabPrompts.get(promptEl);
9383 return this._contentPrompts.get(promptEl);
9386 appendPrompt(args, onCloseCallback) {
9387 let browser = this.browser;
9388 let newPrompt = new TabModalPrompt(browser.ownerGlobal);
9390 if (args.modalType === Ci.nsIPrompt.MODAL_TYPE_TAB) {
9391 newPrompt.element.classList.add("tab-prompt");
9392 this._tabPrompts.set(newPrompt.element, newPrompt);
9394 newPrompt.element.classList.add("content-prompt");
9395 this._contentPrompts.set(newPrompt.element, newPrompt);
9398 browser.parentNode.insertBefore(
9400 browser.nextElementSibling
9402 browser.setAttribute("tabmodalPromptShowing", true);
9404 // Indicate if a tab modal chrome prompt is being shown so that
9405 // PopupNotifications are suppressed.
9407 args.modalType === Ci.nsIPrompt.MODAL_TYPE_TAB &&
9408 !browser.hasAttribute("tabmodalChromePromptShowing")
9410 browser.setAttribute("tabmodalChromePromptShowing", true);
9411 // Notify popup notifications of the UI change so they hide their
9412 // notification panels.
9413 UpdatePopupNotificationsVisibility();
9416 let prompts = this.listPrompts(args.modalType);
9417 if (prompts.length > 1) {
9418 // Let's hide ourself behind the current prompt.
9419 newPrompt.element.hidden = true;
9422 let principalToAllowFocusFor = this._allowTabFocusByPromptPrincipal;
9423 delete this._allowTabFocusByPromptPrincipal;
9425 let allowFocusCheckbox; // Define outside the if block so we can bind it into the callback.
9426 let hostForAllowFocusCheckbox = "";
9428 hostForAllowFocusCheckbox = principalToAllowFocusFor.URI.host;
9430 /* Ignore exceptions for host-less URIs */
9432 if (hostForAllowFocusCheckbox) {
9433 let allowFocusRow = document.createElement("div");
9435 let spacer = document.createElement("div");
9436 allowFocusRow.appendChild(spacer);
9438 allowFocusCheckbox = document.createXULElement("checkbox");
9439 document.l10n.setAttributes(
9441 "tabbrowser-allow-dialogs-to-get-focus",
9442 { domain: hostForAllowFocusCheckbox }
9444 allowFocusRow.appendChild(allowFocusCheckbox);
9446 newPrompt.ui.rows.append(allowFocusRow);
9449 let tab = gBrowser.getTabForBrowser(browser);
9450 let closeCB = this._promptCloseCallback.bind(
9453 principalToAllowFocusFor,
9456 newPrompt.init(args, tab, closeCB);
9460 removePrompt(aPrompt) {
9461 let { modalType } = aPrompt.args;
9462 if (modalType === Ci.nsIPrompt.MODAL_TYPE_TAB) {
9463 this._tabPrompts.delete(aPrompt.element);
9465 this._contentPrompts.delete(aPrompt.element);
9468 let browser = this.browser;
9469 aPrompt.element.remove();
9471 let prompts = this.listPrompts(modalType);
9472 if (prompts.length) {
9473 let prompt = prompts[prompts.length - 1];
9474 prompt.element.hidden = false;
9475 // Because we were hidden before, this won't have been possible, so do it now:
9476 prompt.Dialog.setDefaultFocus();
9477 } else if (modalType === Ci.nsIPrompt.MODAL_TYPE_TAB) {
9478 // If we remove the last tab chrome prompt, also remove the browser
9480 browser.removeAttribute("tabmodalChromePromptShowing");
9481 // Notify popup notifications of the UI change so they show notification
9483 UpdatePopupNotificationsVisibility();
9485 // Check if all prompts are closed
9486 if (!this._hasPrompts()) {
9487 browser.removeAttribute("tabmodalPromptShowing");
9493 * Checks if the prompt box has prompt elements.
9494 * @returns {Boolean} - true if there are prompt elements.
9497 return !!this._getPromptElements().length;
9501 * Get list of current prompt elements.
9502 * @param {Number} [aModalType] - Optionally filter by
9503 * Ci.nsIPrompt.MODAL_TYPE_.
9504 * @returns {NodeList} - A list of tabmodalprompt elements.
9506 _getPromptElements(aModalType = null) {
9507 let selector = "tabmodalprompt";
9509 if (aModalType != null) {
9510 if (aModalType === Ci.nsIPrompt.MODAL_TYPE_TAB) {
9511 selector += ".tab-prompt";
9513 selector += ".content-prompt";
9516 return this.browser.parentNode.querySelectorAll(selector);
9520 * Get a list of all TabModalPrompt objects associated with the prompt box.
9521 * @param {Number} [aModalType] - Optionally filter by
9522 * Ci.nsIPrompt.MODAL_TYPE_.
9523 * @returns {TabModalPrompt[]} - An array of TabModalPrompt objects.
9525 listPrompts(aModalType = null) {
9526 // Get the nodelist, then return the TabModalPrompt instances as an array
9530 if (aModalType === Ci.nsIPrompt.MODAL_TYPE_TAB) {
9531 promptMap = this._tabPrompts;
9533 promptMap = this._contentPrompts;
9537 let elements = this._getPromptElements(aModalType);
9540 return [...elements].map(el => promptMap.get(el));
9542 return [...elements].map(
9543 el => this._contentPrompts.get(el) || this._tabPrompts.get(el)
9547 onNextPromptShowAllowFocusCheckboxFor(principal) {
9548 this._allowTabFocusByPromptPrincipal = principal;
9552 let browser = this._weakBrowserRef.get();
9554 throw new Error("Stale promptbox! The associated browser is gone.");
9560 // Handle window-modal prompts that we want to display with the same style as
9561 // tab-modal prompts.
9564 _nextOpenJumpsQueue: false,
9567 // Used to wait for a `close` event from the HTML
9568 // dialog. The event is fired asynchronously, which means
9569 // that if we open another dialog immediately after the
9570 // previous one, we might be confused into thinking a
9571 // `close` event for the old dialog is for the new one.
9572 // As they have the same event target, we have no way of
9573 // distinguishing them. So we wait for the `close` event
9574 // to have happened before allowing another dialog to open.
9575 _didCloseHTMLDialog: null,
9576 // Whether we managed to open the dialog we tried to open.
9577 // Used to avoid waiting for the above callback in case
9578 // of an error opening the dialog.
9579 _didOpenHTMLDialog: false,
9582 return this._dialog;
9586 return !!this._dialog;
9589 replaceDialogIfOpen() {
9590 this._dialog?.close();
9591 this._nextOpenJumpsQueue = true;
9594 async open(uri, args) {
9595 // If we need to queue, some callers indicate they should go first.
9596 const queueMethod = this._nextOpenJumpsQueue ? "unshift" : "push";
9597 this._nextOpenJumpsQueue = false;
9599 // If we already have a dialog opened and are trying to open another,
9600 // queue the next one to be opened later.
9602 return new Promise((resolve, reject) => {
9603 this._queued[queueMethod]({ resolve, reject, uri, args });
9607 // We're not open. If we're in a modal state though, we can't
9608 // show the dialog effectively. To avoid hanging by deadlock,
9609 // just return immediately for sync prompts:
9610 if (window.windowUtils.isInModalState() && !args.getProperty("async")) {
9611 throw Components.Exception(
9612 "Prompt could not be shown.",
9613 Cr.NS_ERROR_NOT_AVAILABLE
9617 // Indicate if we should wait for the dialog to close.
9618 this._didOpenHTMLDialog = false;
9619 let haveClosedPromise = new Promise(resolve => {
9620 this._didCloseHTMLDialog = resolve;
9623 // Bring the window to the front in case we're minimized or occluded:
9627 await this._open(uri, args);
9631 let dialog = document.getElementById("window-modal-dialog");
9635 // If the dialog was opened successfully, then we can wait for it
9636 // to close before trying to open any others.
9637 if (this._didOpenHTMLDialog) {
9638 await haveClosedPromise;
9640 dialog.style.visibility = "hidden";
9641 dialog.style.height = "0";
9642 dialog.style.width = "0";
9643 document.documentElement.removeAttribute("window-modal-open");
9644 dialog.removeEventListener("dialogopen", this);
9645 dialog.removeEventListener("close", this);
9646 this._updateMenuAndCommandState(true /* to enable */);
9647 this._dialog = null;
9648 UpdatePopupNotificationsVisibility();
9650 if (this._queued.length) {
9651 setTimeout(() => this._openNextDialog(), 0);
9658 let { resolve, reject, uri, args } = this._queued.shift();
9659 this.open(uri, args).then(resolve, reject);
9663 handleEvent(event) {
9664 switch (event.type) {
9666 this._dialog.focus(true);
9669 this._didCloseHTMLDialog();
9670 this._dialog.close();
9676 // Get this offset before we touch style below, as touching style seems
9677 // to reset the cached layout bounds.
9678 let offset = window.windowUtils.getBoundsWithoutFlushing(
9679 gBrowser.selectedBrowser
9681 let parentElement = document.getElementById("window-modal-dialog");
9682 parentElement.style.setProperty("--chrome-offset", offset + "px");
9683 parentElement.style.removeProperty("visibility");
9684 parentElement.style.removeProperty("width");
9685 parentElement.style.removeProperty("height");
9686 document.documentElement.setAttribute("window-modal-open", true);
9687 // Call this first so the contents show up and get layout, which is
9688 // required for SubDialog to work.
9689 parentElement.showModal();
9690 this._didOpenHTMLDialog = true;
9692 // Disable menus and shortcuts.
9693 this._updateMenuAndCommandState(false /* to disable */);
9695 // Now actually set up the dialog contents:
9696 let template = document.getElementById("window-modal-dialog-template")
9697 .content.firstElementChild;
9698 parentElement.addEventListener("dialogopen", this);
9699 parentElement.addEventListener("close", this);
9700 this._dialog = new SubDialog({
9703 id: "window-modal-dialog-subdialog",
9705 consumeOutsideClicks: false,
9708 let closedPromise = new Promise(resolve => {
9709 this._closedCallback = function() {
9710 PromptUtils.fireDialogEvent(window, "DOMModalDialogClosed");
9717 features: "resizable=no",
9718 modalType: Ci.nsIPrompt.MODAL_TYPE_INTERNAL_WINDOW,
9719 closedCallback: () => {
9720 this._closedCallback();
9725 UpdatePopupNotificationsVisibility();
9726 return closedPromise;
9729 _nonUpdatableElements: new Set([
9730 // Make an exception for debugging tools, for developer ease of use.
9731 "key_browserConsole",
9732 "key_browserToolbox",
9734 // Don't touch the editing keys/commands which we might want inside the dialog.
9745 _updateMenuAndCommandState(shouldBeEnabled) {
9746 let editorCommands = document.getElementById("editMenuCommands");
9747 // For the following items, set or clear disabled state:
9748 // - toplevel menubar items (will affect inner items on macOS)
9749 // - command elements
9750 // - key elements not connected to command elements.
9751 for (let element of document.querySelectorAll(
9752 "menubar > menu, command, key:not([command])"
9755 editorCommands?.contains(element) ||
9756 (element.id && this._nonUpdatableElements.has(element.id))
9760 if (element.nodeName == "key" && element.command) {
9763 if (!shouldBeEnabled) {
9764 if (element.getAttribute("disabled") != "true") {
9765 element.setAttribute("disabled", true);
9767 element.setAttribute("wasdisabled", true);
9769 } else if (element.getAttribute("wasdisabled") != "true") {
9770 element.removeAttribute("disabled");
9772 element.removeAttribute("wasdisabled");
9778 // browser.js loads in the library window, too, but we can only show prompts
9779 // in the main browser window:
9780 if (window.location.href != AppConstants.BROWSER_CHROME_URL) {
9784 var ConfirmationHint = {
9788 * Shows a transient, non-interactive confirmation hint anchored to an
9789 * element, usually used in response to a user action to reaffirm that it was
9790 * successful and potentially provide extra context. Examples for such hints:
9791 * - "Saved to bookmarks" after bookmarking a page
9792 * - "Sent!" after sending a tab to another device
9793 * - "Queued (offline)" when attempting to send a tab to another device
9796 * @param anchor (DOM node, required)
9797 * The anchor for the panel.
9798 * @param messageId (string, required)
9799 * For getting the message string from browser.properties:
9800 * confirmationHint.<messageId>.label
9801 * @param options (object, optional)
9802 * An object with the following optional properties:
9803 * - event (DOM event): The event that triggered the feedback.
9804 * - showDescription (boolean): show description text (confirmationHint.<messageId>.description)
9807 show(anchor, messageId, options = {}) {
9810 this._message.textContent = gBrowserBundle.GetStringFromName(
9811 `confirmationHint.${messageId}.label`
9814 if (options.showDescription) {
9815 this._description.textContent = gBrowserBundle.GetStringFromName(
9816 `confirmationHint.${messageId}.description`
9818 this._description.hidden = false;
9819 this._panel.classList.add("with-description");
9821 this._description.hidden = true;
9822 this._panel.classList.remove("with-description");
9825 this._panel.setAttribute("data-message-id", messageId);
9827 // The timeout value used here allows the panel to stay open for
9828 // 1.5s second after the text transition (duration=120ms) has finished.
9829 // If there is a description, we show for 4s after the text transition.
9830 const DURATION = options.showDescription ? 4000 : 1500;
9831 this._panel.addEventListener(
9834 this._animationBox.setAttribute("animate", "true");
9835 this._timerID = setTimeout(() => {
9836 this._panel.hidePopup(true);
9842 this._panel.addEventListener(
9845 // reset the timerId in case our timeout wasn't the cause of the popup being hidden
9851 this._panel.openPopup(anchor, {
9852 position: "bottomcenter topleft",
9853 triggerEvent: options.event,
9858 if (this._timerID) {
9859 clearTimeout(this._timerID);
9860 this._timerID = null;
9863 this._animationBox.removeAttribute("animate");
9864 this._panel.removeAttribute("data-message-id");
9869 this._ensurePanel();
9870 return this.__panel;
9873 get _animationBox() {
9874 this._ensurePanel();
9875 delete this._animationBox;
9876 return (this._animationBox = document.getElementById(
9877 "confirmation-hint-checkmark-animation-container"
9882 this._ensurePanel();
9883 delete this._message;
9884 return (this._message = document.getElementById(
9885 "confirmation-hint-message"
9889 get _description() {
9890 this._ensurePanel();
9891 delete this._description;
9892 return (this._description = document.getElementById(
9893 "confirmation-hint-description"
9898 if (!this.__panel) {
9899 let wrapper = document.getElementById("confirmation-hint-wrapper");
9900 wrapper.replaceWith(wrapper.content);
9901 this.__panel = document.getElementById("confirmation-hint");
9906 var FirefoxViewHandler = {
9908 BUTTON_ID: "firefox-view-button",
9911 return document.getElementById(this.BUTTON_ID);
9914 CustomizableUI.addListener(this);
9916 this._updateEnabledState = this._updateEnabledState.bind(this);
9917 this._updateEnabledState();
9918 NimbusFeatures.majorRelease2022.onUpdate(this._updateEnabledState);
9920 if (this._enabled) {
9921 this._toggleNotificationDot(
9922 FirefoxViewNotificationManager.shouldNotificationDotBeShowing()
9925 XPCOMUtils.defineLazyModuleGetters(this, {
9926 SyncedTabs: "resource://services-sync/SyncedTabs.jsm",
9928 Services.obs.addObserver(this, "firefoxview-notification-dot-update");
9931 CustomizableUI.removeListener(this);
9932 Services.obs.removeObserver(this, "firefoxview-notification-dot-update");
9933 NimbusFeatures.majorRelease2022.off(this._updateEnabledState);
9935 _updateEnabledState() {
9936 this._enabled = NimbusFeatures.majorRelease2022.getVariable("firefoxView");
9937 // We use a root attribute because there's no guarantee the button is in the
9938 // DOM, and visibility changes need to take effect even if it isn't in the DOM
9940 document.documentElement.toggleAttribute(
9941 "firefoxviewhidden",
9944 document.getElementById("menu_openFirefoxView").hidden = !this._enabled;
9946 onWidgetRemoved(aWidgetId) {
9947 if (aWidgetId == this.BUTTON_ID && this.tab) {
9948 gBrowser.removeTab(this.tab);
9951 onWidgetAdded(aWidgetId) {
9952 if (aWidgetId === this.BUTTON_ID) {
9953 this.button.removeAttribute("open");
9957 if (event?.type == "mousedown" && event?.button != 0) {
9960 if (!CustomizableUI.getPlacementOfWidget(this.BUTTON_ID)) {
9961 CustomizableUI.addWidgetToArea(
9963 CustomizableUI.AREA_TABSTRIP,
9964 CustomizableUI.getPlacementOfWidget("tabbrowser-tabs").position
9968 this.tab = gBrowser.addTrustedTab("about:firefoxview");
9969 this.tab.addEventListener("TabClose", this, { once: true });
9970 gBrowser.tabContainer.addEventListener("TabSelect", this);
9971 window.addEventListener("activate", this);
9972 gBrowser.hideTab(this.tab);
9973 this.button.setAttribute("aria-controls", this.tab.linkedPanel);
9975 // we put this here to avoid a race condition that would occur
9976 // if this was called in response to "TabSelect"
9977 this._closeDeviceConnectedTab();
9978 gBrowser.selectedTab = this.tab;
9983 this.button?.toggleAttribute("open", e.target == this.tab);
9984 this.button?.setAttribute("aria-selected", e.target == this.tab);
9985 this._recordViewIfTabSelected();
9986 this._onTabForegrounded();
9990 gBrowser.tabContainer.removeEventListener("TabSelect", this);
9991 this.button?.removeAttribute("aria-controls");
9994 this._onTabForegrounded();
9998 observe(sub, topic, data) {
10000 case "firefoxview-notification-dot-update":
10001 let shouldShow = data === "true";
10002 this._toggleNotificationDot(shouldShow);
10006 _closeDeviceConnectedTab() {
10007 if (!TabsSetupFlowManager.didFxaTabOpen) {
10010 // close the tab left behind after a user pairs a device and
10011 // is redirected back to the Firefox View tab
10012 const fxaRoot = Services.prefs.getCharPref(
10013 "identity.fxaccounts.remote.root"
10015 const fxDeviceConnectedTab = gBrowser.tabs.find(tab =>
10016 tab.linkedBrowser.currentURI.displaySpec.startsWith(
10017 `${fxaRoot}pair/auth/complete`
10021 if (!fxDeviceConnectedTab) {
10025 if (gBrowser.tabs.length <= 2) {
10026 // if its the only tab besides the Firefox View tab,
10027 // open a new tab first so the browser doesn't close
10028 gBrowser.addTrustedTab("about:newtab");
10030 gBrowser.removeTab(fxDeviceConnectedTab);
10031 TabsSetupFlowManager.didFxaTabOpen = false;
10033 _onTabForegrounded() {
10034 if (this.tab?.selected) {
10035 this.SyncedTabs.syncTabs();
10036 Services.obs.notifyObservers(
10038 "firefoxview-notification-dot-update",
10043 _recordViewIfTabSelected() {
10044 if (this.tab?.selected) {
10045 const PREF_NAME = "browser.firefox-view.view-count";
10046 const MAX_VIEW_COUNT = 10;
10047 let viewCount = Services.prefs.getIntPref(PREF_NAME, 0);
10048 if (viewCount < MAX_VIEW_COUNT) {
10049 Services.prefs.setIntPref(PREF_NAME, viewCount + 1);
10053 _toggleNotificationDot(shouldShow) {
10054 this.button?.toggleAttribute("attention", shouldShow);