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.importESModule("resource://gre/modules/NotificationDB.sys.mjs");
14 // lazy module getters
16 ChromeUtils.defineESModuleGetters(this, {
17 AMTelemetry: "resource://gre/modules/AddonManager.sys.mjs",
18 AboutNewTab: "resource:///modules/AboutNewTab.sys.mjs",
19 AboutReaderParent: "resource:///actors/AboutReaderParent.sys.mjs",
20 AddonManager: "resource://gre/modules/AddonManager.sys.mjs",
21 BrowserSearchTelemetry: "resource:///modules/BrowserSearchTelemetry.sys.mjs",
22 BrowserTelemetryUtils: "resource://gre/modules/BrowserTelemetryUtils.sys.mjs",
23 BrowserUIUtils: "resource:///modules/BrowserUIUtils.sys.mjs",
24 BrowserUsageTelemetry: "resource:///modules/BrowserUsageTelemetry.sys.mjs",
25 BrowserWindowTracker: "resource:///modules/BrowserWindowTracker.sys.mjs",
26 CFRPageActions: "resource:///modules/asrouter/CFRPageActions.sys.mjs",
27 Color: "resource://gre/modules/Color.sys.mjs",
28 ContentAnalysis: "resource:///modules/ContentAnalysis.sys.mjs",
29 ContextualIdentityService:
30 "resource://gre/modules/ContextualIdentityService.sys.mjs",
31 CustomizableUI: "resource:///modules/CustomizableUI.sys.mjs",
33 "resource://devtools/shared/security/DevToolsSocketStatus.sys.mjs",
34 DownloadUtils: "resource://gre/modules/DownloadUtils.sys.mjs",
35 DownloadsCommon: "resource:///modules/DownloadsCommon.sys.mjs",
36 E10SUtils: "resource://gre/modules/E10SUtils.sys.mjs",
37 ExtensionsUI: "resource:///modules/ExtensionsUI.sys.mjs",
38 FirefoxViewNotificationManager:
39 "resource:///modules/firefox-view-notification-manager.sys.mjs",
40 HomePage: "resource:///modules/HomePage.sys.mjs",
41 isProductURL: "chrome://global/content/shopping/ShoppingProduct.mjs",
42 LightweightThemeConsumer:
43 "resource://gre/modules/LightweightThemeConsumer.sys.mjs",
44 LoginHelper: "resource://gre/modules/LoginHelper.sys.mjs",
45 LoginManagerParent: "resource://gre/modules/LoginManagerParent.sys.mjs",
46 MigrationUtils: "resource:///modules/MigrationUtils.sys.mjs",
47 NetUtil: "resource://gre/modules/NetUtil.sys.mjs",
48 NewTabPagePreloading: "resource:///modules/NewTabPagePreloading.sys.mjs",
49 NewTabUtils: "resource://gre/modules/NewTabUtils.sys.mjs",
50 NimbusFeatures: "resource://nimbus/ExperimentAPI.sys.mjs",
51 OpenInTabsUtils: "resource:///modules/OpenInTabsUtils.sys.mjs",
52 PageActions: "resource:///modules/PageActions.sys.mjs",
53 PageThumbs: "resource://gre/modules/PageThumbs.sys.mjs",
54 PanelMultiView: "resource:///modules/PanelMultiView.sys.mjs",
55 PanelView: "resource:///modules/PanelMultiView.sys.mjs",
56 PictureInPicture: "resource://gre/modules/PictureInPicture.sys.mjs",
57 PlacesTransactions: "resource://gre/modules/PlacesTransactions.sys.mjs",
58 PlacesUIUtils: "resource:///modules/PlacesUIUtils.sys.mjs",
59 PlacesUtils: "resource://gre/modules/PlacesUtils.sys.mjs",
60 Pocket: "chrome://pocket/content/Pocket.sys.mjs",
61 PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.sys.mjs",
62 ProcessHangMonitor: "resource:///modules/ProcessHangMonitor.sys.mjs",
63 PromptUtils: "resource://gre/modules/PromptUtils.sys.mjs",
64 ReaderMode: "resource://gre/modules/ReaderMode.sys.mjs",
65 ResetPBMPanel: "resource:///modules/ResetPBMPanel.sys.mjs",
66 ReportBrokenSite: "resource:///modules/ReportBrokenSite.sys.mjs",
67 SafeBrowsing: "resource://gre/modules/SafeBrowsing.sys.mjs",
68 Sanitizer: "resource:///modules/Sanitizer.sys.mjs",
69 SaveToPocket: "chrome://pocket/content/SaveToPocket.sys.mjs",
70 ScreenshotsUtils: "resource:///modules/ScreenshotsUtils.sys.mjs",
71 SearchUIUtils: "resource:///modules/SearchUIUtils.sys.mjs",
72 SessionStartup: "resource:///modules/sessionstore/SessionStartup.sys.mjs",
73 SessionStore: "resource:///modules/sessionstore/SessionStore.sys.mjs",
74 ShoppingSidebarParent: "resource:///actors/ShoppingSidebarParent.sys.mjs",
75 ShoppingSidebarManager: "resource:///actors/ShoppingSidebarParent.sys.mjs",
76 ShortcutUtils: "resource://gre/modules/ShortcutUtils.sys.mjs",
77 SiteDataManager: "resource:///modules/SiteDataManager.sys.mjs",
78 SitePermissions: "resource:///modules/SitePermissions.sys.mjs",
79 SubDialog: "resource://gre/modules/SubDialog.sys.mjs",
80 SubDialogManager: "resource://gre/modules/SubDialog.sys.mjs",
81 TabCrashHandler: "resource:///modules/ContentCrashHandlers.sys.mjs",
83 "resource:///modules/firefox-view-tabs-setup-manager.sys.mjs",
84 TelemetryEnvironment: "resource://gre/modules/TelemetryEnvironment.sys.mjs",
85 TranslationsParent: "resource://gre/actors/TranslationsParent.sys.mjs",
86 UITour: "resource:///modules/UITour.sys.mjs",
87 UpdateUtils: "resource://gre/modules/UpdateUtils.sys.mjs",
88 URILoadingHelper: "resource:///modules/URILoadingHelper.sys.mjs",
89 UrlbarInput: "resource:///modules/UrlbarInput.sys.mjs",
90 UrlbarPrefs: "resource:///modules/UrlbarPrefs.sys.mjs",
91 UrlbarProviderSearchTips:
92 "resource:///modules/UrlbarProviderSearchTips.sys.mjs",
93 UrlbarTokenizer: "resource:///modules/UrlbarTokenizer.sys.mjs",
94 UrlbarUtils: "resource:///modules/UrlbarUtils.sys.mjs",
95 UrlbarValueFormatter: "resource:///modules/UrlbarValueFormatter.sys.mjs",
96 Weave: "resource://services-sync/main.sys.mjs",
97 WebNavigationFrames: "resource://gre/modules/WebNavigationFrames.sys.mjs",
98 webrtcUI: "resource:///modules/webrtcUI.sys.mjs",
99 WebsiteFilter: "resource:///modules/policies/WebsiteFilter.sys.mjs",
100 ZoomUI: "resource:///modules/ZoomUI.sys.mjs",
103 ChromeUtils.defineLazyGetter(this, "fxAccounts", () => {
104 return ChromeUtils.importESModule(
105 "resource://gre/modules/FxAccounts.sys.mjs"
106 ).getFxAccountsSingleton();
109 XPCOMUtils.defineLazyScriptGetter(
112 "chrome://browser/content/places/treeView.js"
114 XPCOMUtils.defineLazyScriptGetter(
116 ["PlacesInsertionPoint", "PlacesController", "PlacesControllerDragHelper"],
117 "chrome://browser/content/places/controller.js"
119 XPCOMUtils.defineLazyScriptGetter(
122 "chrome://global/content/printUtils.js"
124 XPCOMUtils.defineLazyScriptGetter(
127 "chrome://global/content/viewZoomOverlay.js"
129 XPCOMUtils.defineLazyScriptGetter(
132 "chrome://browser/content/browser-fullZoom.js"
134 XPCOMUtils.defineLazyScriptGetter(
137 "chrome://browser/content/customizableui/panelUI.js"
139 XPCOMUtils.defineLazyScriptGetter(
142 "chrome://global/content/viewSourceUtils.js"
144 XPCOMUtils.defineLazyScriptGetter(
147 "chrome://browser/content/browser-allTabsMenu.js"
149 XPCOMUtils.defineLazyScriptGetter(
153 "gExtensionsNotifications",
154 "gUnifiedExtensions",
155 "gXPInstallObserver",
157 "chrome://browser/content/browser-addons.js"
159 XPCOMUtils.defineLazyScriptGetter(
162 "chrome://browser/content/browser-ctrlTab.js"
164 XPCOMUtils.defineLazyScriptGetter(
166 ["CustomizationHandler", "AutoHideMenubar"],
167 "chrome://browser/content/browser-customization.js"
169 XPCOMUtils.defineLazyScriptGetter(
171 ["PointerLock", "FullScreen"],
172 "chrome://browser/content/browser-fullScreenAndPointerLock.js"
174 XPCOMUtils.defineLazyScriptGetter(
177 "chrome://browser/content/browser-siteIdentity.js"
179 XPCOMUtils.defineLazyScriptGetter(
182 "chrome://browser/content/browser-sitePermissionPanel.js"
184 XPCOMUtils.defineLazyScriptGetter(
186 "SelectTranslationsPanel",
187 "chrome://browser/content/translations/selectTranslationsPanel.js"
189 XPCOMUtils.defineLazyScriptGetter(
191 "FullPageTranslationsPanel",
192 "chrome://browser/content/translations/fullPageTranslationsPanel.js"
194 XPCOMUtils.defineLazyScriptGetter(
196 "gProtectionsHandler",
197 "chrome://browser/content/browser-siteProtections.js"
199 XPCOMUtils.defineLazyScriptGetter(
201 ["gGestureSupport", "gHistorySwipeAnimation"],
202 "chrome://browser/content/browser-gestureSupport.js"
204 XPCOMUtils.defineLazyScriptGetter(
207 "chrome://browser/content/browser-safebrowsing.js"
209 XPCOMUtils.defineLazyScriptGetter(
212 "chrome://browser/content/browser-sync.js"
214 XPCOMUtils.defineLazyScriptGetter(
216 "gBrowserThumbnails",
217 "chrome://browser/content/browser-thumbnails.js"
219 XPCOMUtils.defineLazyScriptGetter(
221 ["openContextMenu", "nsContextMenu"],
222 "chrome://browser/content/nsContextMenu.js"
224 XPCOMUtils.defineLazyScriptGetter(
228 "DownloadsOverlayLoader",
231 "DownloadsViewController",
234 "DownloadsBlockedSubview",
236 "chrome://browser/content/downloads/downloads.js"
238 XPCOMUtils.defineLazyScriptGetter(
240 ["DownloadsButton", "DownloadsIndicatorView"],
241 "chrome://browser/content/downloads/indicator.js"
243 XPCOMUtils.defineLazyScriptGetter(
246 "chrome://browser/content/places/editBookmark.js"
248 XPCOMUtils.defineLazyScriptGetter(
251 "chrome://browser/content/browser-graphics-utils.js"
253 XPCOMUtils.defineLazyScriptGetter(
256 "chrome://pocket/content/pktUI.js"
258 XPCOMUtils.defineLazyScriptGetter(
260 "ToolbarKeyboardNavigator",
261 "chrome://browser/content/browser-toolbarKeyNav.js"
263 XPCOMUtils.defineLazyScriptGetter(
266 "chrome://browser/content/browser-a11yUtils.js"
268 XPCOMUtils.defineLazyScriptGetter(
271 "chrome://browser/content/browser-webrtc.js"
273 XPCOMUtils.defineLazyScriptGetter(
276 "chrome://browser/content/browser-pagestyle.js"
278 XPCOMUtils.defineLazyScriptGetter(
281 "chrome://browser/content/browser-profiles.js"
284 // lazy service getters
286 XPCOMUtils.defineLazyServiceGetters(this, {
287 ContentPrefService2: [
288 "@mozilla.org/content-pref/service;1",
289 "nsIContentPrefService2",
292 "@mozilla.org/url-classifier/dbservice;1",
295 Favicons: ["@mozilla.org/browser/favicon-service;1", "nsIFaviconService"],
296 WindowsUIUtils: ["@mozilla.org/windows-ui-utils;1", "nsIWindowsUIUtils"],
297 BrowserHandler: ["@mozilla.org/browser/clh;1", "nsIBrowserHandler"],
300 if (AppConstants.ENABLE_WEBDRIVER) {
301 XPCOMUtils.defineLazyServiceGetter(
304 "@mozilla.org/remote/marionette;1",
308 XPCOMUtils.defineLazyServiceGetter(
311 "@mozilla.org/remote/agent;1",
315 this.Marionette = { running: false };
316 this.RemoteAgent = { running: false };
319 ChromeUtils.defineLazyGetter(this, "RTL_UI", () => {
320 return Services.locale.isAppLocaleRTL;
323 ChromeUtils.defineLazyGetter(this, "gBrandBundle", () => {
324 return Services.strings.createBundle(
325 "chrome://branding/locale/brand.properties"
329 ChromeUtils.defineLazyGetter(this, "gBrowserBundle", () => {
330 return Services.strings.createBundle(
331 "chrome://browser/locale/browser.properties"
335 ChromeUtils.defineLazyGetter(this, "gCustomizeMode", () => {
336 let { CustomizeMode } = ChromeUtils.importESModule(
337 "resource:///modules/CustomizeMode.sys.mjs"
339 return new CustomizeMode(window);
342 ChromeUtils.defineLazyGetter(this, "gNavToolbox", () => {
343 return document.getElementById("navigator-toolbox");
346 ChromeUtils.defineLazyGetter(this, "gURLBar", () => {
347 let urlbar = new UrlbarInput({
348 textbox: document.getElementById("urlbar"),
349 eventTelemetryCategory: "urlbar",
352 let beforeFocusOrSelect = event => {
353 // In customize mode, the url bar is disabled. If a new tab is opened or the
354 // user switches to a different tab, this function gets called before we've
355 // finished leaving customize mode, and the url bar will still be disabled.
356 // We can't focus it when it's disabled, so we need to re-run ourselves when
357 // we've finished leaving customize mode.
359 CustomizationHandler.isCustomizing() ||
360 CustomizationHandler.isExitingCustomizeMode
362 gNavToolbox.addEventListener(
363 "aftercustomization",
365 if (event.type == "beforeselect") {
375 event.preventDefault();
379 if (window.fullScreen) {
380 FullScreen.showNavToolbox();
383 urlbar.addEventListener("beforefocus", beforeFocusOrSelect);
384 urlbar.addEventListener("beforeselect", beforeFocusOrSelect);
389 ChromeUtils.defineLazyGetter(this, "ReferrerInfo", () =>
390 Components.Constructor(
391 "@mozilla.org/referrer-info;1",
397 // High priority notification bars shown at the top of the window.
398 ChromeUtils.defineLazyGetter(this, "gNotificationBox", () => {
399 return new MozElements.NotificationBox(element => {
400 element.classList.add("global-notificationbox");
401 element.setAttribute("notificationside", "top");
402 element.setAttribute("prepend-notifications", true);
403 const tabNotifications = document.getElementById("tab-notification-deck");
404 gNavToolbox.insertBefore(element, tabNotifications);
408 ChromeUtils.defineLazyGetter(this, "InlineSpellCheckerUI", () => {
409 let { InlineSpellChecker } = ChromeUtils.importESModule(
410 "resource://gre/modules/InlineSpellChecker.sys.mjs"
412 return new InlineSpellChecker();
415 ChromeUtils.defineLazyGetter(this, "PopupNotifications", () => {
416 // eslint-disable-next-line no-shadow
417 let { PopupNotifications } = ChromeUtils.importESModule(
418 "resource://gre/modules/PopupNotifications.sys.mjs"
421 // Hide all PopupNotifications while the the address bar has focus,
422 // including the virtual focus in the results popup, and the URL is being
423 // edited or the page proxy state is invalid while async tab switching.
424 let shouldSuppress = () => {
425 // "Blank" pages, like about:welcome, have a pageproxystate of "invalid", but
426 // popups like CFRs should not automatically be suppressed when the address
427 // bar has focus on these pages as it disrupts user navigation using FN+F6.
428 // See `UrlbarInput.setURI()` where pageproxystate is set to "invalid" for
429 // all pages that the "isBlankPageURL" method returns true for.
430 const urlBarEdited = isBlankPageURL(gBrowser.currentURI.spec)
431 ? gURLBar.hasAttribute("usertyping")
432 : gURLBar.getAttribute("pageproxystate") != "valid";
434 (urlBarEdited && gURLBar.focused) ||
435 (gURLBar.getAttribute("pageproxystate") != "valid" &&
436 gBrowser.selectedBrowser._awaitingSetURI) ||
437 shouldSuppressPopupNotifications()
441 // Before a Popup is shown, check that its anchor is visible.
442 // If the anchor is not visible, use one of the fallbacks.
443 // If no fallbacks are visible, return null.
444 const getVisibleAnchorElement = anchorElement => {
445 // If the anchor element is present in the Urlbar,
446 // ensure that both the anchor and page URL are visible.
447 gURLBar.maybeHandleRevertFromPopup(anchorElement);
448 if (anchorElement?.checkVisibility()) {
449 return anchorElement;
452 document.getElementById("identity-icon"),
453 document.getElementById("urlbar-search-button"),
455 return fallback.find(element => element?.checkVisibility()) ?? null;
458 return new PopupNotifications(
460 document.getElementById("notification-popup"),
461 document.getElementById("notification-popup-box"),
462 { shouldSuppress, getVisibleAnchorElement }
470 ChromeUtils.defineLazyGetter(this, "MacUserActivityUpdater", () => {
471 if (AppConstants.platform != "macosx") {
475 return Cc["@mozilla.org/widget/macuseractivityupdater;1"].getService(
476 Ci.nsIMacUserActivityUpdater
480 ChromeUtils.defineLazyGetter(this, "Win7Features", () => {
481 if (AppConstants.platform != "win") {
485 const WINTASKBAR_CONTRACTID = "@mozilla.org/windows-taskbar;1";
487 WINTASKBAR_CONTRACTID in Cc &&
488 Cc[WINTASKBAR_CONTRACTID].getService(Ci.nsIWinTaskbar).available
490 let { AeroPeek } = ChromeUtils.importESModule(
491 "resource:///modules/WindowsPreviewPerTab.sys.mjs"
495 AeroPeek.onOpenWindow(window);
496 this.handledOpening = true;
499 if (this.handledOpening) {
500 AeroPeek.onCloseWindow(window);
503 handledOpening: false,
509 XPCOMUtils.defineLazyPreferenceGetter(
511 "gToolbarKeyNavEnabled",
512 "browser.toolbars.keyboard_navigation",
514 (aPref, aOldVal, aNewVal) => {
519 ToolbarKeyboardNavigator.init();
521 ToolbarKeyboardNavigator.uninit();
526 XPCOMUtils.defineLazyPreferenceGetter(
528 "gBookmarksToolbarVisibility",
529 "browser.toolbars.bookmarks.visibility",
533 XPCOMUtils.defineLazyPreferenceGetter(
535 "gFxaToolbarEnabled",
536 "identity.fxaccounts.toolbar.enabled",
538 (aPref, aOldVal, aNewVal) => {
539 updateFxaToolbarMenu(aNewVal);
543 XPCOMUtils.defineLazyPreferenceGetter(
545 "gFxaToolbarAccessed",
546 "identity.fxaccounts.toolbar.accessed",
549 updateFxaToolbarMenu(gFxaToolbarEnabled);
553 XPCOMUtils.defineLazyPreferenceGetter(
555 "gAddonAbuseReportEnabled",
556 "extensions.abuseReport.enabled",
560 XPCOMUtils.defineLazyPreferenceGetter(
563 "browser.download.alwaysOpenPanel",
567 XPCOMUtils.defineLazyPreferenceGetter(
569 "gMiddleClickNewTabUsesPasteboard",
570 "browser.tabs.searchclipboardfor.middleclick",
574 XPCOMUtils.defineLazyPreferenceGetter(
576 "gScreenshotsDisabled",
577 "extensions.screenshots.disabled",
580 Services.obs.notifyObservers(
582 "toggle-screenshot-disable",
583 gScreenshots.shouldScreenshotsButtonBeDisabled()
588 XPCOMUtils.defineLazyPreferenceGetter(
593 (aPref, aOldVal, aNewVal) => {
594 updatePrintCommands(aNewVal);
598 XPCOMUtils.defineLazyPreferenceGetter(
600 "gScreenshotsComponentEnabled",
601 "screenshots.browser.component.enabled",
604 Services.obs.notifyObservers(
606 "toggle-screenshot-disable",
607 gScreenshots.shouldScreenshotsButtonBeDisabled()
612 XPCOMUtils.defineLazyPreferenceGetter(
614 "gTranslationsEnabled",
615 "browser.translations.enable",
619 XPCOMUtils.defineLazyPreferenceGetter(
622 "browser.privatebrowsing.felt-privacy-v1",
626 customElements.setElementCreationCallback("screenshots-buttons", () => {
627 Services.scriptloader.loadSubScript(
628 "chrome://browser/content/screenshots/screenshots-buttons.js",
634 var gContextMenu = null; // nsContextMenu instance
635 var gMultiProcessBrowser = window.docShell.QueryInterface(
638 var gFissionBrowser = window.docShell.QueryInterface(
640 ).useRemoteSubframes;
642 var gBrowserAllowScriptsToCloseInitialTabs = false;
644 if (AppConstants.platform != "macosx") {
645 var gEditUIVisible = true;
648 Object.defineProperty(this, "gReduceMotion", {
651 return typeof gReduceMotionOverride == "boolean"
652 ? gReduceMotionOverride
653 : gReduceMotionSetting;
656 // Reduce motion during startup. The setting will be reset later.
657 let gReduceMotionSetting = true;
658 // This is for tests to set.
659 var gReduceMotionOverride;
661 // Smart getter for the findbar. If you don't wish to force the creation of
662 // the findbar, check gFindBarInitialized first.
664 Object.defineProperty(this, "gFindBar", {
667 return gBrowser.getCachedFindBar();
671 Object.defineProperty(this, "gFindBarInitialized", {
674 return gBrowser.isFindBarInitialized();
678 Object.defineProperty(this, "gFindBarPromise", {
681 return gBrowser.getFindBar();
685 function shouldSuppressPopupNotifications() {
686 // We have to hide notifications explicitly when the window is
687 // minimized because of the effects of the "noautohide" attribute on Linux.
688 // This can be removed once bug 545265 and bug 1320361 are fixed.
689 // Hide popup notifications when system tab prompts are shown so they
690 // don't cover up the prompt.
692 window.windowState == window.STATE_MINIMIZED ||
693 gBrowser?.selectedBrowser.hasAttribute("tabDialogShowing") ||
698 async function gLazyFindCommand(cmd, ...args) {
699 let fb = await gFindBarPromise;
700 // We could be closed by now, or the tab with XBL binding could have gone away:
702 fb[cmd].apply(fb, args);
707 "about:home": "chrome://branding/content/icon32.png",
708 "about:newtab": "chrome://branding/content/icon32.png",
709 "about:welcome": "chrome://branding/content/icon32.png",
710 "about:privatebrowsing": "chrome://browser/skin/privatebrowsing/favicon.svg",
713 var gInitialPages = [
718 "about:privatebrowsing",
719 "about:sessionrestore",
722 "chrome://browser/content/blanktab.html",
723 "about:profilemanager",
726 function isInitialPage(url) {
727 if (!(url instanceof Ci.nsIURI)) {
729 url = Services.io.newURI(url);
735 let nonQuery = url.prePath + url.filePath;
736 return gInitialPages.includes(nonQuery) || nonQuery == BROWSER_NEW_TAB_URL;
739 function browserWindows() {
740 return Services.wm.getEnumerator("navigator:browser");
743 function updateBookmarkToolbarVisibility() {
744 BookmarkingUI.updateEmptyToolbarMessage();
745 setToolbarVisibility(
746 BookmarkingUI.toolbar,
747 gBookmarksToolbarVisibility,
753 // This is a stringbundle-like interface to gBrowserBundle, formerly a getter for
754 // the "bundle_browser" element.
755 var gNavigatorBundle = {
757 return gBrowserBundle.GetStringFromName(key);
759 getFormattedString(key, array) {
760 return gBrowserBundle.formatStringFromName(key, array);
765 shouldScreenshotsButtonBeDisabled() {
766 // About pages other than about:reader are not currently supported by
767 // the screenshots extension (see Bug 1620992).
768 let uri = gBrowser.selectedBrowser.currentURI;
769 let shouldBeDisabled =
770 gScreenshotsDisabled ||
771 (!gScreenshotsComponentEnabled &&
772 uri.scheme === "about" &&
773 !uri.spec.startsWith("about:reader"));
775 return shouldBeDisabled;
779 function updateFxaToolbarMenu(enable, isInitialUpdate = false) {
780 // We only show the Firefox Account toolbar menu if the feature is enabled and
781 // if sync is enabled.
782 const syncEnabled = Services.prefs.getBoolPref(
783 "identity.fxaccounts.enabled",
787 const mainWindowEl = document.documentElement;
788 const fxaPanelEl = PanelMultiView.getViewNode(document, "PanelUI-fxa");
790 // To minimize the toolbar button flickering or appearing/disappearing during startup,
791 // we use this pref to anticipate the likely FxA status.
792 const statusGuess = !!Services.prefs.getStringPref(
793 "identity.fxaccounts.account.device.name",
796 mainWindowEl.setAttribute(
798 statusGuess ? "signed_in" : "not_configured"
801 fxaPanelEl.addEventListener("ViewShowing", gSync.updateSendToDeviceTitle);
803 Services.telemetry.setEventRecordingEnabled("fxa_app_menu", true);
805 if (enable && syncEnabled) {
806 mainWindowEl.setAttribute("fxatoolbarmenu", "visible");
808 // We have to manually update the sync state UI when toggling the FxA toolbar
809 // because it could show an invalid icon if the user is logged in and no sync
810 // event was performed yet.
811 if (!isInitialUpdate) {
812 gSync.maybeUpdateUIState();
815 Services.telemetry.setEventRecordingEnabled("fxa_avatar_menu", true);
817 mainWindowEl.removeAttribute("fxatoolbarmenu");
821 function UpdateBackForwardCommands(aWebNavigation) {
822 var backCommand = document.getElementById("Browser:Back");
823 var forwardCommand = document.getElementById("Browser:Forward");
825 // Avoid setting attributes on commands if the value hasn't changed!
826 // Remember, guys, setting attributes on elements is expensive! They
827 // get inherited into anonymous content, broadcast to other widgets, etc.!
828 // Don't do it if the value hasn't changed! - dwh
830 var backDisabled = backCommand.hasAttribute("disabled");
831 var forwardDisabled = forwardCommand.hasAttribute("disabled");
832 if (backDisabled == aWebNavigation.canGoBack) {
834 backCommand.removeAttribute("disabled");
836 backCommand.setAttribute("disabled", true);
840 if (forwardDisabled == aWebNavigation.canGoForward) {
841 if (forwardDisabled) {
842 forwardCommand.removeAttribute("disabled");
844 forwardCommand.setAttribute("disabled", true);
849 function updatePrintCommands(enabled) {
850 var printCommand = document.getElementById("cmd_print");
851 var printPreviewCommand = document.getElementById("cmd_printPreviewToggle");
854 printCommand.removeAttribute("disabled");
855 printPreviewCommand.removeAttribute("disabled");
857 printCommand.setAttribute("disabled", "true");
858 printPreviewCommand.setAttribute("disabled", "true");
863 * Click-and-Hold implementation for the Back and Forward buttons
864 * XXXmano: should this live in toolbarbutton.js?
866 function SetClickAndHoldHandlers() {
867 // Bug 414797: Clone the back/forward buttons' context menu into both buttons.
868 let popup = document.getElementById("backForwardMenu").cloneNode(true);
869 popup.removeAttribute("id");
870 // Prevent the back/forward buttons' context attributes from being inherited.
871 popup.setAttribute("context", "");
873 let backButton = document.getElementById("back-button");
874 backButton.setAttribute("type", "menu");
875 backButton.prepend(popup);
876 gClickAndHoldListenersOnElement.add(backButton);
878 let forwardButton = document.getElementById("forward-button");
879 popup = popup.cloneNode(true);
880 forwardButton.setAttribute("type", "menu");
881 forwardButton.prepend(popup);
882 gClickAndHoldListenersOnElement.add(forwardButton);
885 const gClickAndHoldListenersOnElement = {
888 _mousedownHandler(aEvent) {
890 aEvent.button != 0 ||
891 aEvent.currentTarget.open ||
892 aEvent.currentTarget.disabled
897 // Prevent the menupopup from opening immediately
898 aEvent.currentTarget.menupopup.hidden = true;
900 aEvent.currentTarget.addEventListener("mouseout", this);
901 aEvent.currentTarget.addEventListener("mouseup", this);
903 aEvent.currentTarget,
904 setTimeout(b => this._openMenu(b), 500, aEvent.currentTarget)
908 _clickHandler(aEvent) {
910 aEvent.button == 0 &&
911 aEvent.target == aEvent.currentTarget &&
912 !aEvent.currentTarget.open &&
913 !aEvent.currentTarget.disabled &&
914 // When menupopup is not hidden and we receive
915 // a click event, it means the mousedown occurred
916 // on aEvent.currentTarget and mouseup occurred on
917 // aEvent.currentTarget.menupopup, we don't
918 // need to handle the click event as menupopup
919 // handled mouseup event already.
920 aEvent.currentTarget.menupopup.hidden
922 let cmdEvent = document.createEvent("xulcommandevent");
923 cmdEvent.initCommandEvent(
937 aEvent.currentTarget.dispatchEvent(cmdEvent);
939 // This is here to cancel the XUL default event
940 // dom.click() triggers a command even if there is a click handler
941 // however this can now be prevented with preventDefault().
942 aEvent.preventDefault();
947 this._cancelHold(aButton);
948 aButton.firstElementChild.hidden = false;
952 _mouseoutHandler(aEvent) {
953 let buttonRect = aEvent.currentTarget.getBoundingClientRect();
955 aEvent.clientX >= buttonRect.left &&
956 aEvent.clientX <= buttonRect.right &&
957 aEvent.clientY >= buttonRect.bottom
959 this._openMenu(aEvent.currentTarget);
961 this._cancelHold(aEvent.currentTarget);
965 _mouseupHandler(aEvent) {
966 this._cancelHold(aEvent.currentTarget);
969 _cancelHold(aButton) {
970 clearTimeout(this._timers.get(aButton));
971 aButton.removeEventListener("mouseout", this);
972 aButton.removeEventListener("mouseup", this);
975 _keypressHandler(aEvent) {
976 if (aEvent.key == " " || aEvent.key == "Enter") {
977 // Normally, command events get fired for keyboard activation. However,
978 // we've set type="menu", so that doesn't happen. Handle this the same
979 // way we handle clicks.
980 aEvent.target.click();
987 this._mouseoutHandler(e);
990 this._mousedownHandler(e);
993 this._clickHandler(e);
996 this._mouseupHandler(e);
999 this._keypressHandler(e);
1005 aButton.removeEventListener("mousedown", this, true);
1006 aButton.removeEventListener("click", this, true);
1007 aButton.removeEventListener("keypress", this, true);
1011 this._timers.delete(aElm);
1013 aElm.addEventListener("mousedown", this, true);
1014 aElm.addEventListener("click", this, true);
1015 aElm.addEventListener("keypress", this, true);
1019 const gSessionHistoryObserver = {
1020 observe(subject, topic) {
1021 if (topic != "browser:purge-session-history") {
1025 var backCommand = document.getElementById("Browser:Back");
1026 backCommand.setAttribute("disabled", "true");
1027 var fwdCommand = document.getElementById("Browser:Forward");
1028 fwdCommand.setAttribute("disabled", "true");
1030 // Clear undo history of the URL bar
1031 gURLBar.editor.clearUndoRedo();
1035 const gStoragePressureObserver = {
1036 _lastNotificationTime: -1,
1038 async observe(subject, topic) {
1039 if (topic != "QuotaManager::StoragePressure") {
1043 const NOTIFICATION_VALUE = "storage-pressure-notification";
1044 if (gNotificationBox.getNotificationWithValue(NOTIFICATION_VALUE)) {
1045 // Do not display the 2nd notification when there is already one
1049 // Don't display notification twice within the given interval.
1051 // - not to annoy user
1052 // - give user some time to clean space.
1053 // Even user sees notification and starts acting, it still takes some time.
1054 const MIN_NOTIFICATION_INTERVAL_MS = Services.prefs.getIntPref(
1055 "browser.storageManager.pressureNotification.minIntervalMS"
1057 let duration = Date.now() - this._lastNotificationTime;
1058 if (duration <= MIN_NOTIFICATION_INTERVAL_MS) {
1061 this._lastNotificationTime = Date.now();
1063 MozXULElement.insertFTLIfNeeded("branding/brand.ftl");
1064 MozXULElement.insertFTLIfNeeded("browser/preferences/preferences.ftl");
1066 const BYTES_IN_GIGABYTE = 1073741824;
1067 const USAGE_THRESHOLD_BYTES =
1069 Services.prefs.getIntPref(
1070 "browser.storageManager.pressureNotification.usageThresholdGB"
1072 let messageFragment = document.createDocumentFragment();
1073 let message = document.createElement("span");
1075 let buttons = [{ supportPage: "storage-permissions" }];
1076 let usage = subject.QueryInterface(Ci.nsISupportsPRUint64).data;
1077 if (usage < USAGE_THRESHOLD_BYTES) {
1078 // The firefox-used space < 5GB, then warn user to free some disk space.
1079 // This is because this usage is small and not the main cause for space issue.
1080 // In order to avoid the bad and wrong impression among users that
1081 // firefox eats disk space a lot, indicate users to clean up other disk space.
1082 document.l10n.setAttributes(message, "space-alert-under-5gb-message2");
1084 // The firefox-used space >= 5GB, then guide users to about:preferences
1085 // to clear some data stored on firefox by websites.
1086 document.l10n.setAttributes(message, "space-alert-over-5gb-message2");
1088 "l10n-id": "space-alert-over-5gb-settings-button",
1090 // The advanced subpanes are only supported in the old organization, which will
1091 // be removed by bug 1349689.
1092 openPreferences("privacy-sitedata");
1096 messageFragment.appendChild(message);
1098 await gNotificationBox.appendNotification(
1101 label: messageFragment,
1102 priority: gNotificationBox.PRIORITY_WARNING_HIGH,
1107 // This seems to be necessary to get the buttons to display correctly
1108 // See: https://bugzilla.mozilla.org/show_bug.cgi?id=1504216
1109 document.l10n.translateFragment(gNotificationBox.currentNotification);
1113 var gPopupBlockerObserver = {
1114 async handleEvent(aEvent) {
1115 if (aEvent.originalTarget != gBrowser.selectedBrowser) {
1119 gPermissionPanel.refreshPermissionIcons();
1122 gBrowser.selectedBrowser.popupBlocker.getBlockedPopupCount();
1125 // Hide the notification box (if it's visible).
1126 let notificationBox = gBrowser.getNotificationBox();
1128 notificationBox.getNotificationWithValue("popup-blocked");
1130 notificationBox.removeNotification(notification, false);
1135 // Only show the notification again if we've not already shown it. Since
1136 // notifications are per-browser, we don't need to worry about re-adding
1138 if (gBrowser.selectedBrowser.popupBlocker.shouldShowNotification) {
1139 if (Services.prefs.getBoolPref("privacy.popups.showBrowserMessage")) {
1142 popupCount < this.maxReportedPopups
1143 ? "popup-warning-message"
1144 : "popup-warning-exceeded-message",
1145 "l10n-args": { popupCount },
1148 let notificationBox = gBrowser.getNotificationBox();
1150 notificationBox.getNotificationWithValue("popup-blocked") ||
1151 (await this.notificationPromise);
1153 notification.label = label;
1155 const image = "chrome://browser/skin/notification-icons/popup.svg";
1156 const priority = notificationBox.PRIORITY_INFO_MEDIUM;
1158 this.notificationPromise = notificationBox.appendNotification(
1160 { label, image, priority },
1163 "l10n-id": "popup-warning-button",
1164 popup: "blockedPopupOptions",
1169 await this.notificationPromise;
1173 this.notificationPromise = null;
1178 // Record the fact that we've reported this blocked popup, so we don't
1180 gBrowser.selectedBrowser.popupBlocker.didShowNotification();
1184 toggleAllowPopupsForSite(aEvent) {
1185 var pm = Services.perms;
1186 var shouldBlock = aEvent.target.getAttribute("block") == "true";
1187 var perm = shouldBlock ? pm.DENY_ACTION : pm.ALLOW_ACTION;
1188 pm.addFromPrincipal(gBrowser.contentPrincipal, "popup", perm);
1191 gBrowser.selectedBrowser.popupBlocker.unblockAllPopups();
1194 gBrowser.getNotificationBox().removeCurrentNotification();
1197 fillPopupList(aEvent) {
1198 // XXXben - rather than using |currentURI| here, which breaks down on multi-framed sites
1199 // we should really walk the blockedPopups and create a list of "allow for <host>"
1200 // menuitems for the common subset of hosts present in the report, this will
1201 // make us frame-safe.
1203 // XXXjst - Note that when this is fixed to work with multi-framed sites,
1204 // also back out the fix for bug 343772 where
1205 // nsGlobalWindow::CheckOpenAllow() was changed to also
1206 // check if the top window's location is allow-listed.
1207 let browser = gBrowser.selectedBrowser;
1208 var uriOrPrincipal = browser.contentPrincipal.isContentPrincipal
1209 ? browser.contentPrincipal
1210 : browser.currentURI;
1211 var blockedPopupAllowSite = document.getElementById(
1212 "blockedPopupAllowSite"
1215 blockedPopupAllowSite.removeAttribute("hidden");
1216 let uriHost = uriOrPrincipal.asciiHost
1217 ? uriOrPrincipal.host
1218 : uriOrPrincipal.spec;
1219 var pm = Services.perms;
1221 pm.testPermissionFromPrincipal(browser.contentPrincipal, "popup") ==
1224 // Offer an item to block popups for this site, if an allow-list entry exists
1226 document.l10n.setAttributes(
1227 blockedPopupAllowSite,
1228 "popups-infobar-block",
1231 blockedPopupAllowSite.setAttribute("block", "true");
1233 // Offer an item to allow popups for this site
1234 document.l10n.setAttributes(
1235 blockedPopupAllowSite,
1236 "popups-infobar-allow",
1239 blockedPopupAllowSite.removeAttribute("block");
1242 blockedPopupAllowSite.hidden = true;
1245 let blockedPopupDontShowMessage = document.getElementById(
1246 "blockedPopupDontShowMessage"
1248 let showMessage = Services.prefs.getBoolPref(
1249 "privacy.popups.showBrowserMessage"
1251 blockedPopupDontShowMessage.setAttribute("checked", !showMessage);
1253 let blockedPopupsSeparator = document.getElementById(
1254 "blockedPopupsSeparator"
1256 blockedPopupsSeparator.hidden = true;
1258 browser.popupBlocker.getBlockedPopups().then(blockedPopups => {
1259 let foundUsablePopupURI = false;
1260 if (blockedPopups) {
1261 for (let i = 0; i < blockedPopups.length; i++) {
1262 let blockedPopup = blockedPopups[i];
1264 // popupWindowURI will be null if the file picker popup is blocked.
1265 // xxxdz this should make the option say "Show file picker" and do it (Bug 590306)
1266 if (!blockedPopup.popupWindowURISpec) {
1270 var popupURIspec = blockedPopup.popupWindowURISpec;
1272 // Sometimes the popup URI that we get back from the blockedPopup
1273 // isn't useful (for instance, netscape.com's popup URI ends up
1274 // being "http://www.netscape.com", which isn't really the URI of
1275 // the popup they're trying to show). This isn't going to be
1276 // useful to the user, so we won't create a menu item for it.
1278 popupURIspec == "" ||
1279 popupURIspec == "about:blank" ||
1280 popupURIspec == "<self>" ||
1281 popupURIspec == uriOrPrincipal.spec
1286 // Because of the short-circuit above, we may end up in a situation
1287 // in which we don't have any usable popup addresses to show in
1288 // the menu, and therefore we shouldn't show the separator. However,
1289 // since we got past the short-circuit, we must've found at least
1290 // one usable popup URI and thus we'll turn on the separator later.
1291 foundUsablePopupURI = true;
1293 var menuitem = document.createXULElement("menuitem");
1294 document.l10n.setAttributes(menuitem, "popup-show-popup-menuitem", {
1295 popupURI: popupURIspec,
1297 menuitem.setAttribute(
1299 "gPopupBlockerObserver.showBlockedPopup(event);"
1301 menuitem.setAttribute("popupReportIndex", i);
1302 menuitem.setAttribute(
1303 "popupInnerWindowId",
1304 blockedPopup.innerWindowId
1306 menuitem.browsingContext = blockedPopup.browsingContext;
1307 menuitem.popupReportBrowser = browser;
1308 aEvent.target.appendChild(menuitem);
1312 // Show the separator if we added any
1313 // showable popup addresses to the menu.
1314 if (foundUsablePopupURI) {
1315 blockedPopupsSeparator.removeAttribute("hidden");
1320 onPopupHiding(aEvent) {
1321 let item = aEvent.target.lastElementChild;
1322 while (item && item.id != "blockedPopupsSeparator") {
1323 let next = item.previousElementSibling;
1329 showBlockedPopup(aEvent) {
1330 let target = aEvent.target;
1331 let browsingContext = target.browsingContext;
1332 let innerWindowId = target.getAttribute("popupInnerWindowId");
1333 let popupReportIndex = target.getAttribute("popupReportIndex");
1334 let browser = target.popupReportBrowser;
1335 browser.popupBlocker.unblockPopup(
1342 editPopupSettings() {
1343 openPreferences("privacy-permissions-block-popups");
1347 var showMessage = Services.prefs.getBoolPref(
1348 "privacy.popups.showBrowserMessage"
1350 Services.prefs.setBoolPref(
1351 "privacy.popups.showBrowserMessage",
1354 gBrowser.getNotificationBox().removeCurrentNotification();
1358 XPCOMUtils.defineLazyPreferenceGetter(
1359 gPopupBlockerObserver,
1360 "maxReportedPopups",
1361 "privacy.popups.maxReported"
1364 var gKeywordURIFixup = {
1365 check(browser, { fixedURI, keywordProviderName, preferredURI }) {
1366 // We get called irrespective of whether we did a keyword search, or
1367 // whether the original input would be vaguely interpretable as a URL,
1368 // so figure that out first.
1370 !keywordProviderName ||
1373 UrlbarPrefs.get("browser.fixup.dns_first_for_single_words") ||
1374 UrlbarPrefs.get("dnsResolveSingleWordsAfterSearch") == 0
1379 let contentPrincipal = browser.contentPrincipal;
1381 // At this point we're still only just about to load this URI.
1382 // When the async DNS lookup comes back, we may be in any of these states:
1383 // 1) still on the previous URI, waiting for the preferredURI (keyword
1384 // search) to respond;
1385 // 2) at the keyword search URI (preferredURI)
1386 // 3) at some other page because the user stopped navigation.
1387 // We keep track of the currentURI to detect case (1) in the DNS lookup
1389 let previousURI = browser.currentURI;
1391 // now swap for a weak ref so we don't hang on to browser needlessly
1392 // even if the DNS query takes forever
1393 let weakBrowser = Cu.getWeakReference(browser);
1396 // Additionally, we need the host of the parsed url
1397 let hostName = fixedURI.displayHost;
1398 // and the ascii-only host for the pref:
1399 let asciiHost = fixedURI.asciiHost;
1401 let onLookupCompleteListener = {
1402 async onLookupComplete(request, record, status) {
1403 let browserRef = weakBrowser.get();
1404 if (!Components.isSuccessCode(status) || !browserRef) {
1408 let currentURI = browserRef.currentURI;
1409 // If we're in case (3) (see above), don't show an info bar.
1411 !currentURI.equals(previousURI) &&
1412 !currentURI.equals(preferredURI)
1417 // show infobar offering to visit the host
1418 let notificationBox = gBrowser.getNotificationBox(browserRef);
1419 if (notificationBox.getNotificationWithValue("keyword-uri-fixup")) {
1423 let displayHostName = "http://" + hostName + "/";
1424 let message = gNavigatorBundle.getFormattedString(
1425 "keywordURIFixup.message",
1428 let yesMessage = gNavigatorBundle.getFormattedString(
1429 "keywordURIFixup.goTo",
1436 accessKey: gNavigatorBundle.getString(
1437 "keywordURIFixup.goTo.accesskey"
1440 // Do not set this preference while in private browsing.
1441 if (!PrivateBrowsingUtils.isWindowPrivate(window)) {
1442 let prefHost = asciiHost;
1443 // Normalize out a single trailing dot - NB: not using endsWith/lastIndexOf
1444 // because we need to be sure this last dot is the *only* dot, too.
1445 // More generally, this is used for the pref and should stay in sync with
1446 // the code in URIFixup::KeywordURIFixup .
1447 if (prefHost.indexOf(".") == prefHost.length - 1) {
1448 prefHost = prefHost.slice(0, -1);
1450 let pref = "browser.fixup.domainwhitelist." + prefHost;
1451 Services.prefs.setBoolPref(pref, true);
1453 openTrustedLinkIn(fixedURI.spec, "current");
1457 let notification = await notificationBox.appendNotification(
1458 "keyword-uri-fixup",
1461 priority: notificationBox.PRIORITY_INFO_HIGH,
1465 notification.persistence = 1;
1469 Services.uriFixup.checkHost(
1471 onLookupCompleteListener,
1472 contentPrincipal.originAttributes
1476 observe(fixupInfo) {
1477 fixupInfo.QueryInterface(Ci.nsIURIFixupInfo);
1479 let browser = fixupInfo.consumer?.top?.embedderElement;
1480 if (!browser || browser.ownerGlobal != window) {
1484 this.check(browser, fixupInfo);
1488 /* Creates a null principal using the userContextId
1489 from the current selected tab or a passed in tab argument */
1490 function _createNullPrincipalFromTabUserContextId(tab = gBrowser.selectedTab) {
1492 if (tab.hasAttribute("usercontextid")) {
1493 userContextId = tab.getAttribute("usercontextid");
1495 return Services.scriptSecurityManager.createNullPrincipal({
1500 let _resolveDelayedStartup;
1501 var delayedStartupPromise = new Promise(resolve => {
1502 _resolveDelayedStartup = resolve;
1505 var gBrowserInit = {
1506 delayedStartupFinished: false,
1507 idleTasksFinishedPromise: null,
1508 idleTaskPromiseResolve: null,
1509 domContentLoaded: false,
1511 _tabToAdopt: undefined,
1513 _setupFirstContentWindowPaintPromise() {
1514 let lastTransactionId = window.windowUtils.lastTransactionId;
1515 let layerTreeListener = () => {
1516 if (this.getTabToAdopt()) {
1517 // Need to wait until we finish adopting the tab, or we might end
1518 // up focusing the initial browser and then losing focus when it
1519 // gets swapped out for the tab to adopt.
1522 removeEventListener("MozLayerTreeReady", layerTreeListener);
1523 let listener = e => {
1524 if (e.transactionId > lastTransactionId) {
1525 window.removeEventListener("MozAfterPaint", listener);
1526 this._firstContentWindowPaintDeferred.resolve();
1529 addEventListener("MozAfterPaint", listener);
1531 addEventListener("MozLayerTreeReady", layerTreeListener);
1535 if (this._tabToAdopt !== undefined) {
1536 return this._tabToAdopt;
1539 if (window.arguments && window.XULElement.isInstance(window.arguments[0])) {
1540 this._tabToAdopt = window.arguments[0];
1542 // Clear the reference of the tab being adopted from the arguments.
1543 window.arguments[0] = null;
1545 // There was no tab to adopt in the arguments, set _tabToAdopt to null
1546 // to avoid checking it again.
1547 this._tabToAdopt = null;
1550 return this._tabToAdopt;
1553 _clearTabToAdopt() {
1554 this._tabToAdopt = null;
1557 // Used to check if the new window is still adopting an existing tab as its first tab
1558 // (e.g. from the WebExtensions internals).
1560 return !!this.getTabToAdopt();
1563 onBeforeInitialXULLayout() {
1564 this._setupFirstContentWindowPaintPromise();
1566 updateBookmarkToolbarVisibility();
1568 // Set a sane starting width/height for all resolutions on new profiles.
1569 if (ChromeUtils.shouldResistFingerprinting("RoundWindowSize", null)) {
1570 // When the fingerprinting resistance is enabled, making sure that we don't
1571 // have a maximum window to interfere with generating rounded window dimensions.
1572 document.documentElement.setAttribute("sizemode", "normal");
1573 } else if (!document.documentElement.hasAttribute("width")) {
1574 const TARGET_WIDTH = 1280;
1575 const TARGET_HEIGHT = 1040;
1576 let width = Math.min(screen.availWidth * 0.9, TARGET_WIDTH);
1577 let height = Math.min(screen.availHeight * 0.9, TARGET_HEIGHT);
1579 document.documentElement.setAttribute("width", width);
1580 document.documentElement.setAttribute("height", height);
1582 if (width < TARGET_WIDTH && height < TARGET_HEIGHT) {
1583 document.documentElement.setAttribute("sizemode", "maximized");
1586 if (AppConstants.MENUBAR_CAN_AUTOHIDE) {
1587 const toolbarMenubar = document.getElementById("toolbar-menubar");
1588 // set a default value
1589 if (!toolbarMenubar.hasAttribute("autohide")) {
1590 toolbarMenubar.setAttribute("autohide", true);
1592 document.l10n.setAttributes(
1594 "toolbar-context-menu-menu-bar-cmd"
1596 toolbarMenubar.setAttribute("data-l10n-attrs", "toolbarname");
1599 // Run menubar initialization first, to avoid TabsInTitlebar code picking
1600 // up mutations from it and causing a reflow.
1601 AutoHideMenubar.init();
1602 // Update the chromemargin attribute so the window can be sized correctly.
1603 window.TabBarVisibility.update();
1604 TabsInTitlebar.init();
1606 new LightweightThemeConsumer(document);
1609 Services.prefs.getBoolPref(
1610 "toolkit.legacyUserProfileCustomizations.windowIcon",
1614 document.documentElement.setAttribute("icon", "main-window");
1617 // Call this after we set attributes that might change toolbars' computed
1619 ToolbarIconColor.init();
1622 onDOMContentLoaded() {
1623 // This needs setting up before we create the first remote browser.
1624 window.docShell.treeOwner
1625 .QueryInterface(Ci.nsIInterfaceRequestor)
1626 .getInterface(Ci.nsIAppWindow).XULBrowserWindow = window.XULBrowserWindow;
1627 window.browserDOMWindow = new nsBrowserAccess();
1629 gBrowser = window._gBrowser;
1630 delete window._gBrowser;
1633 BrowserWindowTracker.track(window);
1635 FirefoxViewHandler.init();
1637 gNavToolbox.palette = document.getElementById(
1638 "BrowserToolbarPalette"
1640 for (let area of CustomizableUI.areas) {
1641 let type = CustomizableUI.getAreaType(area);
1642 if (type == CustomizableUI.TYPE_TOOLBAR) {
1643 let node = document.getElementById(area);
1644 CustomizableUI.registerToolbarNode(node);
1647 BrowserSearch.initPlaceHolder();
1649 // Hack to ensure that the various initial pages favicon is loaded
1650 // instantaneously, to avoid flickering and improve perceived performance.
1651 this._callWithURIToLoad(uriToLoad => {
1654 url = Services.io.newURI(uriToLoad);
1658 let nonQuery = url.prePath + url.filePath;
1659 if (nonQuery in gPageIcons) {
1660 gBrowser.setIcon(gBrowser.selectedTab, gPageIcons[nonQuery]);
1664 updateFxaToolbarMenu(gFxaToolbarEnabled, true);
1666 updatePrintCommands(gPrintEnabled);
1668 gUnifiedExtensions.init();
1670 // Setting the focus will cause a style flush, it's preferable to call anything
1671 // that will modify the DOM from within this function before this call.
1672 this._setInitialFocus();
1674 this.domContentLoaded = true;
1678 gBrowser.addEventListener("DOMUpdateBlockedPopups", gPopupBlockerObserver);
1679 gBrowser.addEventListener(
1680 "TranslationsParent:LanguageState",
1681 FullPageTranslationsPanel
1683 gBrowser.addEventListener(
1684 "TranslationsParent:OfferTranslation",
1685 FullPageTranslationsPanel
1687 gBrowser.addTabsProgressListener(FullPageTranslationsPanel);
1689 window.addEventListener("AppCommand", HandleAppCommandEvent, true);
1691 // These routines add message listeners. They must run before
1692 // loading the frame script to ensure that we don't miss any
1693 // message sent between when the frame script is loaded and when
1694 // the listener is registered.
1695 CaptivePortalWatcher.init();
1696 ZoomUI.init(window);
1698 if (!gMultiProcessBrowser) {
1699 // There is a Content:Click message manually sent from content.
1700 gBrowser.tabpanels.addEventListener("click", contentAreaClick, {
1702 mozSystemGroup: true,
1706 // hook up UI through progress listener
1707 gBrowser.addProgressListener(window.XULBrowserWindow);
1708 gBrowser.addTabsProgressListener(window.TabsProgressListener);
1712 // We do this in onload because we want to ensure the button's state
1713 // doesn't flicker as the window is being shown.
1714 DownloadsButton.init();
1716 // Certain kinds of automigration rely on this notification to complete
1717 // their tasks BEFORE the browser window is shown. SessionStore uses it to
1718 // restore tabs into windows AFTER important parts like gMultiProcessBrowser
1719 // have been initialized.
1720 Services.obs.notifyObservers(window, "browser-window-before-show");
1722 if (!window.toolbar.visible) {
1723 // adjust browser UI for popups
1724 gURLBar.readOnly = true;
1729 TabletModeUpdater.init();
1730 CombinedStopReload.ensureInitialized();
1731 gPrivateBrowsingUI.init();
1732 BrowserSearch.init();
1733 BrowserPageActions.init();
1734 if (gToolbarKeyNavEnabled) {
1735 ToolbarKeyboardNavigator.init();
1738 // Update UI if browser is under remote control.
1739 gRemoteControl.updateVisualCue();
1741 // If we are given a tab to swap in, take care of it before first paint to
1742 // avoid an about:blank flash.
1743 let tabToAdopt = this.getTabToAdopt();
1745 let evt = new CustomEvent("before-initial-tab-adopted", {
1748 gBrowser.tabpanels.dispatchEvent(evt);
1750 // Stop the about:blank load
1753 // Remove the speculative focus from the urlbar to let the url be formatted.
1754 gURLBar.removeAttribute("focused");
1756 let swapBrowsers = () => {
1758 gBrowser.swapBrowsersAndCloseOther(gBrowser.selectedTab, tabToAdopt);
1763 // Clear the reference to the tab once its adoption has been completed.
1764 this._clearTabToAdopt();
1766 if (tabToAdopt.linkedBrowser.isRemoteBrowser) {
1767 // For remote browsers, wait for the paint event, otherwise the tabs
1768 // are not yet ready and focus gets confused because the browser swaps
1769 // out while tabs are switching.
1770 addEventListener("MozAfterPaint", swapBrowsers, { once: true });
1776 // Wait until chrome is painted before executing code not critical to making the window visible
1777 this._boundDelayedStartup = this._delayedStartup.bind(this);
1778 window.addEventListener("MozAfterPaint", this._boundDelayedStartup);
1780 if (!PrivateBrowsingUtils.enabled) {
1781 document.getElementById("Tools:PrivateBrowsing").hidden = true;
1782 // Setting disabled doesn't disable the shortcut, so we just remove
1784 document.getElementById("key_privatebrowsing").remove();
1787 if (BrowserUIUtils.quitShortcutDisabled) {
1788 document.getElementById("key_quitApplication").remove();
1789 document.getElementById("menu_FileQuitItem").removeAttribute("key");
1791 PanelMultiView.getViewNode(
1793 "appMenu-quit-button2"
1794 )?.removeAttribute("key");
1797 this._loadHandled = true;
1800 _cancelDelayedStartup() {
1801 window.removeEventListener("MozAfterPaint", this._boundDelayedStartup);
1802 this._boundDelayedStartup = null;
1806 let { TelemetryTimestamps } = ChromeUtils.importESModule(
1807 "resource://gre/modules/TelemetryTimestamps.sys.mjs"
1809 TelemetryTimestamps.add("delayedStartupStarted");
1811 this._cancelDelayedStartup();
1813 // Bug 1531854 - The hidden window is force-created here
1814 // until all of its dependencies are handled.
1815 Services.appShell.hiddenDOMWindow;
1817 gBrowser.addEventListener(
1818 "PermissionStateChange",
1820 gIdentityHandler.refreshIdentityBlock();
1821 gPermissionPanel.updateSharingIndicator();
1826 this._handleURIToLoad();
1828 Services.obs.addObserver(gIdentityHandler, "perm-changed");
1829 Services.obs.addObserver(gRemoteControl, "devtools-socket");
1830 Services.obs.addObserver(gRemoteControl, "marionette-listening");
1831 Services.obs.addObserver(gRemoteControl, "remote-listening");
1832 Services.obs.addObserver(
1833 gSessionHistoryObserver,
1834 "browser:purge-session-history"
1836 Services.obs.addObserver(
1837 gStoragePressureObserver,
1838 "QuotaManager::StoragePressure"
1840 Services.obs.addObserver(gXPInstallObserver, "addon-install-disabled");
1841 Services.obs.addObserver(gXPInstallObserver, "addon-install-started");
1842 Services.obs.addObserver(gXPInstallObserver, "addon-install-blocked");
1843 Services.obs.addObserver(
1845 "addon-install-fullscreen-blocked"
1847 Services.obs.addObserver(
1849 "addon-install-origin-blocked"
1851 Services.obs.addObserver(
1853 "addon-install-policy-blocked"
1855 Services.obs.addObserver(
1857 "addon-install-webapi-blocked"
1859 Services.obs.addObserver(gXPInstallObserver, "addon-install-failed");
1860 Services.obs.addObserver(gXPInstallObserver, "addon-install-confirmation");
1861 Services.obs.addObserver(gKeywordURIFixup, "keyword-uri-fixup");
1863 BrowserOffline.init();
1864 CanvasPermissionPromptHelper.init();
1865 WebAuthnPromptHelper.init();
1866 ContentAnalysis.initialize();
1868 // Initialize the full zoom setting.
1869 // We do this before the session restore service gets initialized so we can
1870 // apply full zoom settings to tabs restored by the session restore service.
1872 PanelUI.init(shouldSuppressPopupNotifications);
1873 ReportBrokenSite.init(gBrowser);
1875 UpdateUrlbarSearchSplitterState();
1877 BookmarkingUI.init();
1878 BrowserSearch.delayedStartupInit();
1879 SearchUIUtils.init();
1880 gProtectionsHandler.init();
1881 HomePage.delayedStartup().catch(console.error);
1883 let safeMode = document.getElementById("helpSafeMode");
1884 if (Services.appinfo.inSafeMode) {
1885 document.l10n.setAttributes(safeMode, "menu-help-exit-troubleshoot-mode");
1886 safeMode.setAttribute(
1887 "appmenu-data-l10n-id",
1888 "appmenu-help-exit-troubleshoot-mode"
1893 gBidiUI = isBidiEnabled();
1895 document.getElementById("documentDirection-separator").hidden = false;
1896 document.getElementById("documentDirection-swap").hidden = false;
1897 document.getElementById("textfieldDirection-separator").hidden = false;
1898 document.getElementById("textfieldDirection-swap").hidden = false;
1901 // Setup click-and-hold gestures access to the session history
1902 // menus if global click-and-hold isn't turned on
1903 if (!Services.prefs.getBoolPref("ui.click_hold_context_menus", false)) {
1904 SetClickAndHoldHandlers();
1907 function initBackForwardButtonTooltip(tooltipId, l10nId, shortcutId) {
1908 let shortcut = document.getElementById(shortcutId);
1909 shortcut = ShortcutUtils.prettifyShortcut(shortcut);
1911 let tooltip = document.getElementById(tooltipId);
1912 document.l10n.setAttributes(tooltip, l10nId, { shortcut });
1915 initBackForwardButtonTooltip(
1916 "back-button-tooltip-description",
1917 "navbar-tooltip-back-2",
1921 initBackForwardButtonTooltip(
1922 "forward-button-tooltip-description",
1923 "navbar-tooltip-forward-2",
1927 PlacesToolbarHelper.init();
1930 Services.prefs.addObserver(ctrlTab.prefName, ctrlTab);
1932 // The object handling the downloads indicator is initialized here in the
1933 // delayed startup function, but the actual indicator element is not loaded
1934 // unless there are downloads to be displayed.
1935 DownloadsButton.initializeIndicator();
1937 if (AppConstants.platform != "macosx") {
1938 updateEditUIVisibility();
1939 let placesContext = document.getElementById("placesContext");
1940 placesContext.addEventListener("popupshowing", updateEditUIVisibility);
1941 placesContext.addEventListener("popuphiding", updateEditUIVisibility);
1945 MenuTouchModeObserver.init();
1947 if (AppConstants.MOZ_DATA_REPORTING) {
1948 gDataNotificationInfoBar.init();
1951 if (!AppConstants.MOZILLA_OFFICIAL) {
1952 DevelopmentHelpers.init();
1955 gExtensionsNotifications.init();
1957 let wasMinimized = window.windowState == window.STATE_MINIMIZED;
1958 window.addEventListener("sizemodechange", () => {
1959 let isMinimized = window.windowState == window.STATE_MINIMIZED;
1960 if (wasMinimized != isMinimized) {
1961 wasMinimized = isMinimized;
1962 UpdatePopupNotificationsVisibility();
1966 window.addEventListener("mousemove", MousePosTracker);
1967 window.addEventListener("dragover", MousePosTracker);
1969 gNavToolbox.addEventListener("customizationstarting", CustomizationHandler);
1970 gNavToolbox.addEventListener("aftercustomization", CustomizationHandler);
1972 SessionStore.promiseInitialized.then(() => {
1973 // Bail out if the window has been closed in the meantime.
1974 if (window.closed) {
1978 // Enable the Restore Last Session command if needed
1979 RestoreLastSessionObserver.init();
1981 SidebarUI.startDelayedLoad();
1983 PanicButtonNotifier.init();
1986 if (BrowserHandler.kiosk) {
1987 // We don't modify popup windows for kiosk mode
1988 if (!gURLBar.readOnly) {
1989 window.fullScreen = true;
1993 if (Services.policies.status === Services.policies.ACTIVE) {
1994 if (!Services.policies.isAllowed("hideShowMenuBar")) {
1996 .getElementById("toolbar-menubar")
1997 .removeAttribute("toolbarname");
1999 if (!Services.policies.isAllowed("filepickers")) {
2000 let savePageCommand = document.getElementById("Browser:SavePage");
2001 let openFileCommand = document.getElementById("Browser:OpenFile");
2003 savePageCommand.setAttribute("disabled", "true");
2004 openFileCommand.setAttribute("disabled", "true");
2006 document.addEventListener("FilePickerBlocked", function (event) {
2007 let browser = event.target;
2009 let notificationBox = browser
2011 ?.getNotificationBox(browser);
2013 // Prevent duplicate notifications
2016 !notificationBox.getNotificationWithValue("filepicker-blocked")
2018 notificationBox.appendNotification("filepicker-blocked", {
2020 "l10n-id": "filepicker-blocked-infobar",
2022 priority: notificationBox.PRIORITY_INFO_LOW,
2027 let policies = Services.policies.getActivePolicies();
2028 if ("ManagedBookmarks" in policies) {
2029 let managedBookmarks = policies.ManagedBookmarks;
2030 let children = managedBookmarks.filter(
2031 child => !("toplevel_name" in child)
2033 if (children.length) {
2034 let managedBookmarksButton =
2035 document.createXULElement("toolbarbutton");
2036 managedBookmarksButton.setAttribute("id", "managed-bookmarks");
2037 managedBookmarksButton.setAttribute("class", "bookmark-item");
2038 let toplevel = managedBookmarks.find(
2039 element => "toplevel_name" in element
2042 managedBookmarksButton.setAttribute(
2044 toplevel.toplevel_name
2047 document.l10n.setAttributes(
2048 managedBookmarksButton,
2052 managedBookmarksButton.setAttribute("context", "placesContext");
2053 managedBookmarksButton.setAttribute("container", "true");
2054 managedBookmarksButton.setAttribute("removable", "false");
2055 managedBookmarksButton.setAttribute("type", "menu");
2057 let managedBookmarksPopup = document.createXULElement("menupopup");
2058 managedBookmarksPopup.setAttribute("id", "managed-bookmarks-popup");
2059 managedBookmarksPopup.setAttribute(
2061 "PlacesToolbarHelper.openManagedBookmark(event);"
2063 managedBookmarksPopup.setAttribute(
2065 "event.dataTransfer.effectAllowed='none';"
2067 managedBookmarksPopup.setAttribute(
2069 "PlacesToolbarHelper.onDragStartManaged(event);"
2071 managedBookmarksPopup.setAttribute(
2073 "PlacesToolbarHelper.populateManagedBookmarks(this);"
2075 managedBookmarksPopup.setAttribute("placespopup", "true");
2076 managedBookmarksPopup.setAttribute("is", "places-popup");
2077 managedBookmarksPopup.classList.add("toolbar-menupopup");
2078 managedBookmarksButton.appendChild(managedBookmarksPopup);
2080 gNavToolbox.palette.appendChild(managedBookmarksButton);
2082 CustomizableUI.ensureWidgetPlacedInWindow(
2083 "managed-bookmarks",
2087 // Add button if it doesn't exist
2088 if (!CustomizableUI.getPlacementOfWidget("managed-bookmarks")) {
2089 CustomizableUI.addWidgetToArea(
2090 "managed-bookmarks",
2091 CustomizableUI.AREA_BOOKMARKS,
2099 CaptivePortalWatcher.delayedStartup();
2101 ShoppingSidebarManager.ensureInitialized();
2103 SessionStore.promiseAllWindowsRestored.then(() => {
2104 this._schedulePerWindowIdleTasks();
2105 document.documentElement.setAttribute("sessionrestored", "true");
2108 this.delayedStartupFinished = true;
2109 _resolveDelayedStartup();
2110 Services.obs.notifyObservers(window, "browser-delayed-startup-finished");
2111 TelemetryTimestamps.add("delayedStartupFinished");
2112 // We've announced that delayed startup has finished. Do not add code past this point.
2116 * Resolved on the first MozLayerTreeReady and next MozAfterPaint in the
2119 get firstContentWindowPaintPromise() {
2120 return this._firstContentWindowPaintDeferred.promise;
2123 _setInitialFocus() {
2124 let initiallyFocusedElement = document.commandDispatcher.focusedElement;
2126 // To prevent startup flicker, the urlbar has the 'focused' attribute set
2127 // by default. If we are not sure the urlbar will be focused in this
2128 // window, we need to remove the attribute before first paint.
2129 // TODO (bug 1629956): The urlbar having the 'focused' attribute by default
2130 // isn't a useful optimization anymore since UrlbarInput needs layout
2131 // information to focus the urlbar properly.
2132 let shouldRemoveFocusedAttribute = true;
2134 this._callWithURIToLoad(uriToLoad => {
2136 isBlankPageURL(uriToLoad) ||
2137 uriToLoad == "about:privatebrowsing" ||
2138 this.getTabToAdopt()?.isEmpty
2141 shouldRemoveFocusedAttribute = false;
2145 // If the initial browser is remote, in order to optimize for first paint,
2146 // we'll defer switching focus to that browser until it has painted.
2147 // Otherwise use a regular promise to guarantee that mutationobserver
2148 // microtasks that could affect focusability have run.
2149 let promise = gBrowser.selectedBrowser.isRemoteBrowser
2150 ? this.firstContentWindowPaintPromise
2151 : Promise.resolve();
2153 promise.then(() => {
2154 // If focus didn't move while we were waiting, we're okay to move to
2157 document.commandDispatcher.focusedElement == initiallyFocusedElement
2159 gBrowser.selectedBrowser.focus();
2164 // Delay removing the attribute using requestAnimationFrame to avoid
2165 // invalidating styles multiple times in a row if uriToLoadPromise
2166 // resolves before first paint.
2167 if (shouldRemoveFocusedAttribute) {
2168 window.requestAnimationFrame(() => {
2169 if (shouldRemoveFocusedAttribute) {
2170 gURLBar.removeAttribute("focused");
2176 _handleURIToLoad() {
2177 this._callWithURIToLoad(uriToLoad => {
2179 // We don't check whether window.arguments[5] (userContextId) is set
2180 // because tabbrowser.js takes care of that for the initial tab.
2184 // We don't check if uriToLoad is a XULElement because this case has
2185 // already been handled before first paint, and the argument cleared.
2186 if (Array.isArray(uriToLoad)) {
2187 // This function throws for certain malformed URIs, so use exception handling
2188 // so that we don't disrupt startup
2190 gBrowser.loadTabs(uriToLoad, {
2191 inBackground: false,
2193 // See below for the semantics of window.arguments. Only the minimum is supported.
2194 userContextId: window.arguments[5],
2195 triggeringPrincipal:
2196 window.arguments[8] ||
2197 Services.scriptSecurityManager.getSystemPrincipal(),
2198 allowInheritPrincipal: window.arguments[9],
2199 csp: window.arguments[10],
2203 } else if (window.arguments.length >= 3) {
2204 // window.arguments[1]: extraOptions (nsIPropertyBag)
2205 // [2]: referrerInfo (nsIReferrerInfo)
2206 // [3]: postData (nsIInputStream)
2207 // [4]: allowThirdPartyFixup (bool)
2208 // [5]: userContextId (int)
2209 // [6]: originPrincipal (nsIPrincipal)
2210 // [7]: originStoragePrincipal (nsIPrincipal)
2211 // [8]: triggeringPrincipal (nsIPrincipal)
2212 // [9]: allowInheritPrincipal (bool)
2213 // [10]: csp (nsIContentSecurityPolicy)
2214 // [11]: nsOpenWindowInfo
2216 window.arguments[5] != undefined
2217 ? window.arguments[5]
2218 : Ci.nsIScriptSecurityManager.DEFAULT_USER_CONTEXT_ID;
2220 let hasValidUserGestureActivation = undefined;
2221 let fromExternal = undefined;
2222 let globalHistoryOptions = undefined;
2223 let triggeringRemoteType = undefined;
2224 let forceAllowDataURI = false;
2225 let wasSchemelessInput = false;
2226 if (window.arguments[1]) {
2227 if (!(window.arguments[1] instanceof Ci.nsIPropertyBag2)) {
2229 "window.arguments[1] must be null or Ci.nsIPropertyBag2!"
2233 let extraOptions = window.arguments[1];
2234 if (extraOptions.hasKey("hasValidUserGestureActivation")) {
2235 hasValidUserGestureActivation = extraOptions.getPropertyAsBool(
2236 "hasValidUserGestureActivation"
2239 if (extraOptions.hasKey("fromExternal")) {
2240 fromExternal = extraOptions.getPropertyAsBool("fromExternal");
2242 if (extraOptions.hasKey("triggeringSponsoredURL")) {
2243 globalHistoryOptions = {
2244 triggeringSponsoredURL: extraOptions.getPropertyAsACString(
2245 "triggeringSponsoredURL"
2248 if (extraOptions.hasKey("triggeringSponsoredURLVisitTimeMS")) {
2249 globalHistoryOptions.triggeringSponsoredURLVisitTimeMS =
2250 extraOptions.getPropertyAsUint64(
2251 "triggeringSponsoredURLVisitTimeMS"
2255 if (extraOptions.hasKey("triggeringRemoteType")) {
2256 triggeringRemoteType = extraOptions.getPropertyAsACString(
2257 "triggeringRemoteType"
2260 if (extraOptions.hasKey("forceAllowDataURI")) {
2262 extraOptions.getPropertyAsBool("forceAllowDataURI");
2264 if (extraOptions.hasKey("wasSchemelessInput")) {
2265 wasSchemelessInput =
2266 extraOptions.getPropertyAsBool("wasSchemelessInput");
2271 openLinkIn(uriToLoad, "current", {
2272 referrerInfo: window.arguments[2] || null,
2273 postData: window.arguments[3] || null,
2274 allowThirdPartyFixup: window.arguments[4] || false,
2276 // pass the origin principal (if any) and force its use to create
2277 // an initial about:blank viewer if present:
2278 originPrincipal: window.arguments[6],
2279 originStoragePrincipal: window.arguments[7],
2280 triggeringPrincipal: window.arguments[8],
2281 // TODO fix allowInheritPrincipal to default to false.
2282 // Default to true unless explicitly set to false because of bug 1475201.
2283 allowInheritPrincipal: window.arguments[9] !== false,
2284 csp: window.arguments[10],
2285 forceAboutBlankViewerInCurrent: !!window.arguments[6],
2287 hasValidUserGestureActivation,
2289 globalHistoryOptions,
2290 triggeringRemoteType,
2299 // Note: loadOneOrMoreURIs *must not* be called if window.arguments.length >= 3.
2300 // Such callers expect that window.arguments[0] is handled as a single URI.
2303 Services.scriptSecurityManager.getSystemPrincipal(),
2311 * Use this function as an entry point to schedule tasks that
2312 * need to run once per window after startup, and can be scheduled
2313 * by using an idle callback.
2315 * The functions scheduled here will fire from idle callbacks
2316 * once every window has finished being restored by session
2317 * restore, and after the equivalent only-once tasks
2318 * have run (from _scheduleStartupIdleTasks in BrowserGlue.sys.mjs).
2320 _schedulePerWindowIdleTasks() {
2321 // Bail out if the window has been closed in the meantime.
2322 if (window.closed) {
2326 function scheduleIdleTask(func, options) {
2327 requestIdleCallback(function idleTaskRunner() {
2328 if (!window.closed) {
2334 scheduleIdleTask(() => {
2335 // Initialize the Sync UI
2339 scheduleIdleTask(() => {
2340 // Read prefers-reduced-motion setting
2341 let reduceMotionQuery = window.matchMedia(
2342 "(prefers-reduced-motion: reduce)"
2344 function readSetting() {
2345 gReduceMotionSetting = reduceMotionQuery.matches;
2347 reduceMotionQuery.addListener(readSetting);
2351 scheduleIdleTask(() => {
2352 // load the tab preview component
2353 import("chrome://browser/content/tabpreview/tabpreview.mjs").catch(
2358 scheduleIdleTask(() => {
2359 // setup simple gestures support
2360 gGestureSupport.init(true);
2362 // setup history swipe animation
2363 gHistorySwipeAnimation.init();
2366 scheduleIdleTask(() => {
2367 gBrowserThumbnails.init();
2372 // Initialize the download manager some time after the app starts so that
2373 // auto-resume downloads begin (such as after crashing or quitting with
2374 // active downloads) and speeds up the first-load of the download manager UI.
2375 // If the user manually opens the download manager before the timeout, the
2376 // downloads will start right away, and initializing again won't hurt.
2378 DownloadsCommon.initializeAllDataLinks();
2379 ChromeUtils.importESModule(
2380 "resource:///modules/DownloadsTaskbar.sys.mjs"
2381 ).DownloadsTaskbar.registerIndicator(window);
2382 if (AppConstants.platform == "macosx") {
2383 ChromeUtils.importESModule(
2384 "resource:///modules/DownloadsMacFinderProgress.sys.mjs"
2385 ).DownloadsMacFinderProgress.register();
2387 Services.telemetry.setEventRecordingEnabled("downloads", true);
2396 scheduleIdleTask(() => Win7Features.onOpenWindow());
2399 scheduleIdleTask(async () => {
2400 NewTabPagePreloading.maybeCreatePreloadedBrowser(window);
2403 scheduleIdleTask(() => {
2407 // This should always go last, since the idle tasks (except for the ones with
2408 // timeouts) should execute in order. Note that this observer notification is
2409 // not guaranteed to fire, since the window could close before we get here.
2410 scheduleIdleTask(() => {
2411 this.idleTaskPromiseResolve();
2412 Services.obs.notifyObservers(
2414 "browser-idle-startup-tasks-finished"
2418 scheduleIdleTask(() => {
2423 // Returns the URI(s) to load at startup if it is immediately known, or a
2424 // promise resolving to the URI to load.
2425 get uriToLoadPromise() {
2426 delete this.uriToLoadPromise;
2427 return (this.uriToLoadPromise = (function () {
2428 // window.arguments[0]: URI to load (string), or an nsIArray of
2429 // nsISupportsStrings to load, or a xul:tab of
2430 // a tabbrowser, which will be replaced by this
2431 // window (for this case, all other arguments are
2433 let uri = window.arguments?.[0];
2434 if (!uri || window.XULElement.isInstance(uri)) {
2438 let defaultArgs = BrowserHandler.defaultArgs;
2440 // If the given URI is different from the homepage, we want to load it.
2441 if (uri != defaultArgs) {
2442 AboutNewTab.noteNonDefaultStartup();
2444 if (uri instanceof Ci.nsIArray) {
2445 // Transform the nsIArray of nsISupportsString's into a JS Array of
2448 uri.enumerate(Ci.nsISupportsString),
2449 supportStr => supportStr.data
2451 } else if (uri instanceof Ci.nsISupportsString) {
2457 // The URI appears to be the the homepage. We want to load it only if
2458 // session restore isn't about to override the homepage.
2459 let willOverride = SessionStartup.willOverrideHomepage;
2460 if (typeof willOverride == "boolean") {
2461 return willOverride ? null : uri;
2463 return willOverride.then(willOverrideHomepage =>
2464 willOverrideHomepage ? null : uri
2469 // Calls the given callback with the URI to load at startup.
2470 // Synchronously if possible, or after uriToLoadPromise resolves otherwise.
2471 _callWithURIToLoad(callback) {
2472 let uriToLoad = this.uriToLoadPromise;
2473 if (uriToLoad && uriToLoad.then) {
2474 uriToLoad.then(callback);
2476 callback(uriToLoad);
2481 gUIDensity.uninit();
2483 TabsInTitlebar.uninit();
2485 ToolbarIconColor.uninit();
2487 // In certain scenarios it's possible for unload to be fired before onload,
2488 // (e.g. if the window is being closed after browser.js loads but before the
2489 // load completes). In that case, there's nothing to do here.
2490 if (!this._loadHandled) {
2494 // First clean up services initialized in gBrowserInit.onLoad (or those whose
2495 // uninit methods don't depend on the services having been initialized).
2497 CombinedStopReload.uninit();
2499 gGestureSupport.init(false);
2501 gHistorySwipeAnimation.uninit();
2503 FullScreen.uninit();
2507 gExtensionsNotifications.uninit();
2508 gUnifiedExtensions.uninit();
2511 gBrowser.removeProgressListener(window.XULBrowserWindow);
2512 gBrowser.removeTabsProgressListener(window.TabsProgressListener);
2515 PlacesToolbarHelper.uninit();
2517 BookmarkingUI.uninit();
2519 TabletModeUpdater.uninit();
2521 gTabletModePageCounter.finish();
2523 CaptivePortalWatcher.uninit();
2527 DownloadsButton.uninit();
2529 if (gToolbarKeyNavEnabled) {
2530 ToolbarKeyboardNavigator.uninit();
2533 BrowserSearch.uninit();
2535 NewTabPagePreloading.removePreloadedBrowser(window);
2537 FirefoxViewHandler.uninit();
2539 // Now either cancel delayedStartup, or clean up the services initialized from
2541 if (this._boundDelayedStartup) {
2542 this._cancelDelayedStartup();
2545 Win7Features.onCloseWindow();
2547 Services.prefs.removeObserver(ctrlTab.prefName, ctrlTab);
2549 gBrowserThumbnails.uninit();
2550 gProtectionsHandler.uninit();
2553 Services.obs.removeObserver(gIdentityHandler, "perm-changed");
2554 Services.obs.removeObserver(gRemoteControl, "devtools-socket");
2555 Services.obs.removeObserver(gRemoteControl, "marionette-listening");
2556 Services.obs.removeObserver(gRemoteControl, "remote-listening");
2557 Services.obs.removeObserver(
2558 gSessionHistoryObserver,
2559 "browser:purge-session-history"
2561 Services.obs.removeObserver(
2562 gStoragePressureObserver,
2563 "QuotaManager::StoragePressure"
2565 Services.obs.removeObserver(gXPInstallObserver, "addon-install-disabled");
2566 Services.obs.removeObserver(gXPInstallObserver, "addon-install-started");
2567 Services.obs.removeObserver(gXPInstallObserver, "addon-install-blocked");
2568 Services.obs.removeObserver(
2570 "addon-install-fullscreen-blocked"
2572 Services.obs.removeObserver(
2574 "addon-install-origin-blocked"
2576 Services.obs.removeObserver(
2578 "addon-install-policy-blocked"
2580 Services.obs.removeObserver(
2582 "addon-install-webapi-blocked"
2584 Services.obs.removeObserver(gXPInstallObserver, "addon-install-failed");
2585 Services.obs.removeObserver(
2587 "addon-install-confirmation"
2589 Services.obs.removeObserver(gKeywordURIFixup, "keyword-uri-fixup");
2591 MenuTouchModeObserver.uninit();
2592 BrowserOffline.uninit();
2593 CanvasPermissionPromptHelper.uninit();
2594 WebAuthnPromptHelper.uninit();
2598 // Final window teardown, do this last.
2600 window.XULBrowserWindow = null;
2601 window.docShell.treeOwner
2602 .QueryInterface(Ci.nsIInterfaceRequestor)
2603 .getInterface(Ci.nsIAppWindow).XULBrowserWindow = null;
2604 window.browserDOMWindow = null;
2608 ChromeUtils.defineLazyGetter(
2610 "_firstContentWindowPaintDeferred",
2611 () => Promise.withResolvers()
2614 gBrowserInit.idleTasksFinishedPromise = new Promise(resolve => {
2615 gBrowserInit.idleTaskPromiseResolve = resolve;
2618 function HandleAppCommandEvent(evt) {
2619 switch (evt.command) {
2627 BrowserReloadSkipCache();
2630 if (XULBrowserWindow.stopCommand.getAttribute("disabled") != "true") {
2635 BrowserSearch.webSearch();
2638 SidebarUI.toggle("viewBookmarksSidebar");
2647 BrowserCloseTabOrWindow();
2650 gLazyFindCommand("onFindCommand");
2653 openHelpLink("firefox-help");
2656 BrowserOpenFileWindow();
2659 PrintUtils.startPrintWindow(gBrowser.selectedBrowser.browsingContext);
2662 saveBrowser(gBrowser.selectedBrowser);
2665 MailIntegration.sendLinkForBrowser(gBrowser.selectedBrowser);
2670 evt.stopPropagation();
2671 evt.preventDefault();
2674 function gotoHistoryIndex(aEvent) {
2675 aEvent = getRootEvent(aEvent);
2677 let index = aEvent.target.getAttribute("index");
2682 let where = whereToOpenLink(aEvent);
2684 if (where == "current") {
2685 // Normal click. Go there in the current tab and update session history.
2688 gBrowser.gotoIndex(index);
2694 // Modified click. Go there in a new tab/window.
2696 let historyindex = aEvent.target.getAttribute("historyindex");
2697 duplicateTabIn(gBrowser.selectedTab, where, Number(historyindex));
2701 function BrowserForward(aEvent) {
2702 let where = whereToOpenLink(aEvent, false, true);
2704 if (where == "current") {
2706 gBrowser.goForward();
2709 duplicateTabIn(gBrowser.selectedTab, where, 1);
2713 function BrowserBack(aEvent) {
2714 let where = whereToOpenLink(aEvent, false, true);
2716 if (where == "current") {
2721 duplicateTabIn(gBrowser.selectedTab, where, -1);
2725 function BrowserHandleBackspace() {
2726 switch (Services.prefs.getIntPref("browser.backspace_action")) {
2731 goDoCommand("cmd_scrollPageUp");
2736 function BrowserHandleShiftBackspace() {
2737 switch (Services.prefs.getIntPref("browser.backspace_action")) {
2742 goDoCommand("cmd_scrollPageDown");
2747 function BrowserStop() {
2748 gBrowser.webNavigation.stop(Ci.nsIWebNavigation.STOP_ALL);
2751 function BrowserReloadOrDuplicate(aEvent) {
2752 aEvent = getRootEvent(aEvent);
2753 let accelKeyPressed =
2754 AppConstants.platform == "macosx" ? aEvent.metaKey : aEvent.ctrlKey;
2755 var backgroundTabModifier = aEvent.button == 1 || accelKeyPressed;
2757 if (aEvent.shiftKey && !backgroundTabModifier) {
2758 BrowserReloadSkipCache();
2762 let where = whereToOpenLink(aEvent, false, true);
2763 if (where == "current") {
2766 duplicateTabIn(gBrowser.selectedTab, where);
2770 function BrowserReload() {
2771 if (gBrowser.currentURI.schemeIs("view-source")) {
2772 // Bug 1167797: For view source, we always skip the cache
2773 return BrowserReloadSkipCache();
2775 const reloadFlags = Ci.nsIWebNavigation.LOAD_FLAGS_NONE;
2776 BrowserReloadWithFlags(reloadFlags);
2779 const kSkipCacheFlags =
2780 Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_PROXY |
2781 Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_CACHE;
2782 function BrowserReloadSkipCache() {
2783 // Bypass proxy and cache.
2784 BrowserReloadWithFlags(kSkipCacheFlags);
2787 function BrowserHome(aEvent) {
2788 if (aEvent && "button" in aEvent && aEvent.button == 2) {
2789 // right-click: do nothing
2793 var homePage = HomePage.get(window);
2794 var where = whereToOpenLink(aEvent, false, true);
2796 var notifyObservers;
2798 // Don't load the home page in pinned or hidden tabs (e.g. Firefox View).
2800 where == "current" &&
2801 (gBrowser?.selectedTab.pinned || gBrowser?.selectedTab.hidden)
2806 // openTrustedLinkIn in utilityOverlay.js doesn't handle loading multiple pages
2809 // If we're going to load an initial page in the current tab as the
2810 // home page, we set initialPageLoadedFromURLBar so that the URL
2811 // bar is cleared properly (even during a remoteness flip).
2812 if (isInitialPage(homePage)) {
2813 gBrowser.selectedBrowser.initialPageLoadedFromUserAction = homePage;
2817 Services.scriptSecurityManager.getSystemPrincipal(),
2820 if (isBlankPageURL(homePage)) {
2823 gBrowser.selectedBrowser.focus();
2825 notifyObservers = true;
2826 aEvent?.preventDefault();
2830 urls = homePage.split("|");
2831 var loadInBackground = Services.prefs.getBoolPref(
2832 "browser.tabs.loadBookmarksInBackground",
2835 // The homepage observer event should only be triggered when the homepage opens
2836 // in the foreground. This is mostly to support the homepage changed by extension
2837 // doorhanger which doesn't currently support background pages. This may change in
2839 notifyObservers = !loadInBackground;
2840 gBrowser.loadTabs(urls, {
2841 inBackground: loadInBackground,
2842 triggeringPrincipal:
2843 Services.scriptSecurityManager.getSystemPrincipal(),
2846 if (!loadInBackground) {
2847 if (isBlankPageURL(homePage)) {
2850 gBrowser.selectedBrowser.focus();
2853 aEvent?.preventDefault();
2856 // OpenBrowserWindow will trigger the observer event, so no need to do so here.
2857 notifyObservers = false;
2858 OpenBrowserWindow();
2859 aEvent?.preventDefault();
2862 if (notifyObservers) {
2863 // A notification for when a user has triggered their homepage. This is used
2864 // to display a doorhanger explaining that an extension has modified the
2865 // homepage, if necessary. Observers are only notified if the homepage
2866 // becomes the active page.
2867 Services.obs.notifyObservers(null, "browser-open-homepage-start");
2871 function loadOneOrMoreURIs(aURIString, aTriggeringPrincipal, aCsp) {
2872 // we're not a browser window, pass the URI string to a new browser window
2873 if (window.location.href != AppConstants.BROWSER_CHROME_URL) {
2875 AppConstants.BROWSER_CHROME_URL,
2883 // This function throws for certain malformed URIs, so use exception handling
2884 // so that we don't disrupt startup
2886 gBrowser.loadTabs(aURIString.split("|"), {
2887 inBackground: false,
2889 triggeringPrincipal: aTriggeringPrincipal,
2895 function openLocation(event) {
2896 if (window.location.href == AppConstants.BROWSER_CHROME_URL) {
2898 gURLBar.view.autoOpen({ event });
2902 // If there's an open browser window, redirect the command there.
2903 let win = URILoadingHelper.getTargetWindow(window);
2910 // There are no open browser windows; open a new one.
2912 AppConstants.BROWSER_CHROME_URL,
2914 "chrome,all,dialog=no",
2919 function BrowserOpenTab({ event, url } = {}) {
2920 let werePassedURL = !!url;
2921 url ??= BROWSER_NEW_TAB_URL;
2922 let searchClipboard = gMiddleClickNewTabUsesPasteboard && event?.button == 1;
2924 let relatedToCurrent = false;
2928 where = whereToOpenLink(event, false, true);
2933 // When accel-click or middle-click are used, open the new tab as
2934 // related to the current tab.
2935 relatedToCurrent = true;
2943 // A notification intended to be useful for modular peformance tracking
2944 // starting as close as is reasonably possible to the time when the user
2945 // expressed the intent to open a new tab. Since there are a lot of
2946 // entry points, this won't catch every single tab created, but most
2947 // initiated by the user should go through here.
2949 // Note 1: This notification gets notified with a promise that resolves
2950 // with the linked browser when the tab gets created
2951 // Note 2: This is also used to notify a user that an extension has changed
2952 // the New Tab page.
2953 Services.obs.notifyObservers(
2955 wrappedJSObject: new Promise(resolve => {
2958 resolveOnNewTabCreated: resolve,
2960 if (!werePassedURL && searchClipboard) {
2961 let clipboard = readFromClipboard();
2962 clipboard = UrlbarUtils.stripUnsafeProtocolOnPaste(clipboard).trim();
2965 options.allowThirdPartyFixup = true;
2968 openTrustedLinkIn(url, where, options);
2971 "browser-open-newtab-start"
2975 var gLastOpenDirectory = {
2978 if (!this._lastDir || !this._lastDir.exists()) {
2980 this._lastDir = Services.prefs.getComplexValue(
2981 "browser.open.lastDir",
2984 if (!this._lastDir.exists()) {
2985 this._lastDir = null;
2989 return this._lastDir;
2993 if (!val || !val.isDirectory()) {
2999 this._lastDir = val.clone();
3001 // Don't save the last open directory pref inside the Private Browsing mode
3002 if (!PrivateBrowsingUtils.isWindowPrivate(window)) {
3003 Services.prefs.setComplexValue(
3004 "browser.open.lastDir",
3011 this._lastDir = null;
3015 function BrowserOpenFileWindow() {
3016 // Get filepicker component.
3018 const nsIFilePicker = Ci.nsIFilePicker;
3019 let fp = Cc["@mozilla.org/filepicker;1"].createInstance(nsIFilePicker);
3020 let fpCallback = function fpCallback_done(aResult) {
3021 if (aResult == nsIFilePicker.returnOK) {
3024 gLastOpenDirectory.path = fp.file.parent.QueryInterface(Ci.nsIFile);
3027 openTrustedLinkIn(fp.fileURL.spec, "current");
3032 window.browsingContext,
3033 gNavigatorBundle.getString("openFile"),
3034 nsIFilePicker.modeOpen
3037 nsIFilePicker.filterAll |
3038 nsIFilePicker.filterText |
3039 nsIFilePicker.filterImages |
3040 nsIFilePicker.filterXML |
3041 nsIFilePicker.filterHTML |
3042 nsIFilePicker.filterPDF
3044 fp.displayDirectory = gLastOpenDirectory.path;
3045 fp.open(fpCallback);
3049 function BrowserCloseTabOrWindow(event) {
3050 // If we're not a browser window, just close the window.
3051 if (window.location.href != AppConstants.BROWSER_CHROME_URL) {
3056 // In a multi-select context, close all selected tabs
3057 if (gBrowser.multiSelectedTabsCount) {
3058 gBrowser.removeMultiSelectedTabs();
3062 // Keyboard shortcuts that would close a tab that is pinned select the first
3063 // unpinned tab instead.
3066 (event.ctrlKey || event.metaKey || event.altKey) &&
3067 gBrowser.selectedTab.pinned
3069 if (gBrowser.visibleTabs.length > gBrowser._numPinnedTabs) {
3070 gBrowser.tabContainer.selectedIndex = gBrowser._numPinnedTabs;
3075 // If the current tab is the last one, this will close the window.
3076 gBrowser.removeCurrentTab({ animate: true });
3079 function BrowserTryToCloseWindow(event) {
3080 if (WindowIsClosing(event)) {
3082 } // WindowIsClosing does all the necessary checks
3085 function getLoadContext() {
3086 return window.docShell.QueryInterface(Ci.nsILoadContext);
3089 function readFromClipboard() {
3093 // Create transferable that will transfer the text.
3094 var trans = Cc["@mozilla.org/widget/transferable;1"].createInstance(
3097 trans.init(getLoadContext());
3099 trans.addDataFlavor("text/plain");
3101 // If available, use selection clipboard, otherwise global one
3102 let clipboard = Services.clipboard;
3103 if (clipboard.isClipboardTypeSupported(clipboard.kSelectionClipboard)) {
3104 clipboard.getData(trans, clipboard.kSelectionClipboard);
3106 clipboard.getData(trans, clipboard.kGlobalClipboard);
3110 trans.getTransferData("text/plain", data);
3113 data = data.value.QueryInterface(Ci.nsISupportsString);
3122 * Open the View Source dialog.
3125 * An object with the following properties:
3128 * A string URL for the page we'd like to view the source of.
3129 * browser (optional):
3130 * The browser containing the document that we would like to view the
3131 * source of. This is required if outerWindowID is passed.
3132 * outerWindowID (optional):
3133 * The outerWindowID of the content window containing the document that
3134 * we want to view the source of. You only need to provide this if you
3135 * want to attempt to retrieve the document source from the network
3137 * lineNumber (optional):
3138 * The line number to focus on once the source is loaded.
3140 async function BrowserViewSourceOfDocument(args) {
3141 // Check if external view source is enabled. If so, try it. If it fails,
3142 // fallback to internal view source.
3143 if (Services.prefs.getBoolPref("view_source.editor.external")) {
3145 await top.gViewSourceUtils.openInExternalEditor(args);
3150 let tabBrowser = gBrowser;
3151 let preferredRemoteType;
3152 let initialBrowsingContextGroupId;
3154 preferredRemoteType = args.browser.remoteType;
3155 initialBrowsingContextGroupId = args.browser.browsingContext.group.id;
3159 "BrowserViewSourceOfDocument should be passed the " +
3160 "subject browser if called from a window without " +
3164 // Some internal URLs (such as specific chrome: and about: URLs that are
3165 // not yet remote ready) cannot be loaded in a remote browser. View
3166 // source in tab expects the new view source browser's remoteness to match
3167 // that of the original URL, so disable remoteness if necessary for this
3169 var oa = E10SUtils.predictOriginAttributes({ window });
3170 preferredRemoteType = E10SUtils.getRemoteTypeForURI(
3172 gMultiProcessBrowser,
3174 E10SUtils.DEFAULT_REMOTE_TYPE,
3180 // In the case of popups, we need to find a non-popup browser window.
3181 if (!tabBrowser || !window.toolbar.visible) {
3182 // This returns only non-popup browser windows by default.
3183 let browserWindow = BrowserWindowTracker.getTopWindow();
3184 tabBrowser = browserWindow.gBrowser;
3187 const inNewWindow = !Services.prefs.getBoolPref("view_source.tab");
3189 // `viewSourceInBrowser` will load the source content from the page
3190 // descriptor for the tab (when possible) or fallback to the network if
3191 // that fails. Either way, the view source module will manage the tab's
3192 // location, so use "about:blank" here to avoid unnecessary redundant
3194 let tab = tabBrowser.addTab("about:blank", {
3195 relatedToCurrent: true,
3196 inBackground: inNewWindow,
3197 skipAnimation: inNewWindow,
3198 preferredRemoteType,
3199 initialBrowsingContextGroupId,
3200 triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
3203 args.viewSourceBrowser = tabBrowser.getBrowserForTab(tab);
3204 top.gViewSourceUtils.viewSourceInBrowser(args);
3207 tabBrowser.hideTab(tab);
3208 tabBrowser.replaceTabWithWindow(tab);
3213 * Opens the View Source dialog for the source loaded in the root
3214 * top-level document of the browser. This is really just a
3215 * convenience wrapper around BrowserViewSourceOfDocument.
3218 * The browser that we want to load the source of.
3220 function BrowserViewSource(browser) {
3221 BrowserViewSourceOfDocument({
3223 outerWindowID: browser.outerWindowID,
3224 URL: browser.currentURI.spec,
3228 // documentURL - URL of the document to view, or null for this window's document
3229 // initialTab - name of the initial tab to display, or null for the first tab
3230 // imageElement - image to load in the Media Tab of the Page Info window; can be null/omitted
3231 // browsingContext - the browsingContext of the frame that we want to view information about; can be null/omitted
3232 // browser - the browser containing the document we're interested in inspecting; can be null/omitted
3233 function BrowserPageInfo(
3240 let args = { initialTab, imageElement, browsingContext, browser };
3242 documentURL = documentURL || window.gBrowser.selectedBrowser.currentURI.spec;
3244 let isPrivate = PrivateBrowsingUtils.isWindowPrivate(window);
3246 // Check for windows matching the url
3247 for (let currentWindow of Services.wm.getEnumerator("Browser:page-info")) {
3248 if (currentWindow.closed) {
3252 currentWindow.document.documentElement.getAttribute("relatedUrl") ==
3254 PrivateBrowsingUtils.isWindowPrivate(currentWindow) == isPrivate
3256 currentWindow.focus();
3257 currentWindow.resetPageInfo(args);
3258 return currentWindow;
3262 // We didn't find a matching window, so open a new one.
3263 let options = "chrome,toolbar,dialog=no,resizable";
3265 // Ensure the window groups correctly in the Windows taskbar
3267 options += ",private";
3270 "chrome://browser/content/pageinfo/pageInfo.xhtml",
3277 function UpdateUrlbarSearchSplitterState() {
3278 var splitter = document.getElementById("urlbar-search-splitter");
3279 var urlbar = document.getElementById("urlbar-container");
3280 var searchbar = document.getElementById("search-container");
3282 if (document.documentElement.getAttribute("customizing") == "true") {
3289 // If the splitter is already in the right place, we don't need to do anything:
3292 ((splitter.nextElementSibling == searchbar &&
3293 splitter.previousElementSibling == urlbar) ||
3294 (splitter.nextElementSibling == urlbar &&
3295 splitter.previousElementSibling == searchbar))
3301 let resizebefore = "none";
3302 let resizeafter = "none";
3303 if (urlbar && searchbar) {
3304 if (urlbar.nextElementSibling == searchbar) {
3305 resizeafter = "sibling";
3306 ibefore = searchbar;
3307 } else if (searchbar.nextElementSibling == urlbar) {
3308 resizebefore = "sibling";
3315 splitter = document.createXULElement("splitter");
3316 splitter.id = "urlbar-search-splitter";
3317 splitter.setAttribute("resizebefore", resizebefore);
3318 splitter.setAttribute("resizeafter", resizeafter);
3319 splitter.setAttribute("skipintoolbarset", "true");
3320 splitter.setAttribute("overflows", "false");
3321 splitter.className = "chromeclass-toolbar-additional";
3323 urlbar.parentNode.insertBefore(splitter, ibefore);
3324 } else if (splitter) {
3329 function UpdatePopupNotificationsVisibility() {
3330 // Only need to update PopupNotifications if it has already been initialized
3331 // for this window (i.e. its getter no longer exists).
3332 if (!Object.getOwnPropertyDescriptor(window, "PopupNotifications").get) {
3333 // Notify PopupNotifications that the visible anchors may have changed. This
3334 // also checks the suppression state according to the "shouldSuppress"
3335 // function defined earlier in this file.
3336 PopupNotifications.anchorVisibilityChange();
3339 // This is similar to the above, but for notifications attached to the
3340 // hamburger menu icon (such as update notifications and add-on install
3342 PanelUI?.updateNotifications();
3345 function PageProxyClickHandler(aEvent) {
3346 if (aEvent.button == 1 && Services.prefs.getBoolPref("middlemouse.paste")) {
3347 middleMousePaste(aEvent);
3352 * Handle command events bubbling up from error page content
3353 * or from about:newtab or from remote error pages that invoke
3354 * us via async messaging.
3356 var BrowserOnClick = {
3357 async ignoreWarningLink(reason, blockedInfo, browsingContext) {
3358 // Add a notify bar before allowing the user to continue through to the
3359 // site, so that they don't lose track after, e.g., tab switching.
3360 // We can't use browser.contentPrincipal which is principal of about:blocked
3361 // Create one from uri with current principal origin attributes
3362 let principal = Services.scriptSecurityManager.createContentPrincipal(
3363 Services.io.newURI(blockedInfo.uri),
3364 browsingContext.currentWindowGlobal.documentPrincipal.originAttributes
3366 Services.perms.addFromPrincipal(
3369 Ci.nsIPermissionManager.ALLOW_ACTION,
3370 Ci.nsIPermissionManager.EXPIRE_SESSION
3375 label: gNavigatorBundle.getString(
3376 "safebrowsing.getMeOutOfHereButton.label"
3378 accessKey: gNavigatorBundle.getString(
3379 "safebrowsing.getMeOutOfHereButton.accessKey"
3382 getMeOutOfHere(browsingContext);
3388 if (reason === "malware") {
3389 let reportUrl = gSafeBrowsing.getReportURL("MalwareMistake", blockedInfo);
3390 title = gNavigatorBundle.getString("safebrowsing.reportedAttackSite");
3391 // There's no button if we can not get report url, for example if the provider
3392 // of blockedInfo is not Google
3395 label: gNavigatorBundle.getString(
3396 "safebrowsing.notAnAttackButton.label"
3398 accessKey: gNavigatorBundle.getString(
3399 "safebrowsing.notAnAttackButton.accessKey"
3402 openTrustedLinkIn(reportUrl, "tab");
3406 } else if (reason === "phishing") {
3407 let reportUrl = gSafeBrowsing.getReportURL("PhishMistake", blockedInfo);
3408 title = gNavigatorBundle.getString("safebrowsing.deceptiveSite");
3409 // There's no button if we can not get report url, for example if the provider
3410 // of blockedInfo is not Google
3413 label: gNavigatorBundle.getString(
3414 "safebrowsing.notADeceptiveSiteButton.label"
3416 accessKey: gNavigatorBundle.getString(
3417 "safebrowsing.notADeceptiveSiteButton.accessKey"
3420 openTrustedLinkIn(reportUrl, "tab");
3424 } else if (reason === "unwanted") {
3425 title = gNavigatorBundle.getString("safebrowsing.reportedUnwantedSite");
3426 // There is no button for reporting errors since Google doesn't currently
3427 // provide a URL endpoint for these reports.
3428 } else if (reason === "harmful") {
3429 title = gNavigatorBundle.getString("safebrowsing.reportedHarmfulSite");
3430 // There is no button for reporting errors since Google doesn't currently
3431 // provide a URL endpoint for these reports.
3434 await SafeBrowsingNotificationBox.show(title, buttons);
3436 // Allow users to override and continue through to the site.
3437 // Note that we have to use the passed URI info and can't just
3438 // rely on the document URI, because the latter contains
3439 // additional query parameters that should be stripped.
3440 let triggeringPrincipal =
3441 blockedInfo.triggeringPrincipal ||
3442 _createNullPrincipalFromTabUserContextId();
3444 browsingContext.fixupAndLoadURIString(blockedInfo.uri, {
3445 triggeringPrincipal,
3446 flags: Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_CLASSIFIER,
3452 * Re-direct the browser to a known-safe page. This function is
3453 * used when, for example, the user browses to a known malware page
3454 * and is presented with about:blocked. The "Get me out of here!"
3455 * button should take the user to the default start page so that even
3456 * when their own homepage is infected, we can get them somewhere safe.
3458 function getMeOutOfHere(browsingContext) {
3459 browsingContext.top.fixupAndLoadURIString(getDefaultHomePage(), {
3460 triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(), // Also needs to load homepage
3465 * Return the default start page for the cases when the user's own homepage is
3466 * infected, so we can get them somewhere safe.
3468 function getDefaultHomePage() {
3469 let url = BROWSER_NEW_TAB_URL;
3470 if (PrivateBrowsingUtils.isWindowPrivate(window)) {
3473 url = HomePage.getDefault();
3474 // If url is a pipe-delimited set of pages, just take the first one.
3475 if (url.includes("|")) {
3476 url = url.split("|")[0];
3481 function BrowserFullScreen() {
3482 window.fullScreen = !window.fullScreen || BrowserHandler.kiosk;
3485 function BrowserReloadWithFlags(reloadFlags) {
3486 let unchangedRemoteness = [];
3488 for (let tab of gBrowser.selectedTabs) {
3489 let browser = tab.linkedBrowser;
3490 let url = browser.currentURI;
3491 let urlSpec = url.spec;
3492 // We need to cache the content principal here because the browser will be
3493 // reconstructed when the remoteness changes and the content prinicpal will
3494 // be cleared after reconstruction.
3495 let principal = tab.linkedBrowser.contentPrincipal;
3496 if (gBrowser.updateBrowserRemotenessByURL(browser, urlSpec)) {
3497 // If the remoteness has changed, the new browser doesn't have any
3498 // information of what was loaded before, so we need to load the previous
3500 if (tab.linkedPanel) {
3501 loadBrowserURI(browser, url, principal);
3503 // Shift to fully loaded browser and make
3504 // sure load handler is instantiated.
3505 tab.addEventListener(
3507 () => loadBrowserURI(browser, url, principal),
3510 gBrowser._insertBrowser(tab);
3513 unchangedRemoteness.push(tab);
3517 if (!unchangedRemoteness.length) {
3521 // Reset temporary permissions on the remaining tabs to reload.
3522 // This is done here because we only want to reset
3523 // permissions on user reload.
3524 for (let tab of unchangedRemoteness) {
3525 SitePermissions.clearTemporaryBlockPermissions(tab.linkedBrowser);
3526 // Also reset DOS mitigations for the basic auth prompt on reload.
3527 delete tab.linkedBrowser.authPromptAbuseCounter;
3529 gIdentityHandler.hidePopup();
3530 gPermissionPanel.hidePopup();
3532 let handlingUserInput = document.hasValidTransientUserGestureActivation;
3534 for (let tab of unchangedRemoteness) {
3535 if (tab.linkedPanel) {
3536 sendReloadMessage(tab);
3538 // Shift to fully loaded browser and make
3539 // sure load handler is instantiated.
3540 tab.addEventListener("SSTabRestoring", () => sendReloadMessage(tab), {
3543 gBrowser._insertBrowser(tab);
3547 function loadBrowserURI(browser, url, principal) {
3548 browser.loadURI(url, {
3550 triggeringPrincipal: principal,
3554 function sendReloadMessage(tab) {
3555 tab.linkedBrowser.sendMessageToActor(
3557 { flags: reloadFlags, handlingUserInput },
3563 // TODO: can we pull getPEMString in from pippki.js instead of
3564 // duplicating them here?
3565 function getPEMString(cert) {
3566 var derb64 = cert.getBase64DERString();
3567 // Wrap the Base64 string into lines of 64 characters,
3568 // with CRLF line breaks (as specified in RFC 1421).
3569 var wrapped = derb64.replace(/(\S{64}(?!$))/g, "$1\r\n");
3571 "-----BEGIN CERTIFICATE-----\r\n" +
3573 "\r\n-----END CERTIFICATE-----\r\n"
3577 var browserDragAndDrop = {
3578 canDropLink: aEvent => Services.droppedLinkHandler.canDropLink(aEvent, true),
3581 if (this.canDropLink(aEvent)) {
3582 aEvent.preventDefault();
3586 getTriggeringPrincipal(aEvent) {
3587 return Services.droppedLinkHandler.getTriggeringPrincipal(aEvent);
3591 return Services.droppedLinkHandler.getCsp(aEvent);
3594 validateURIsForDrop(aEvent, aURIs) {
3595 return Services.droppedLinkHandler.validateURIsForDrop(aEvent, aURIs);
3598 dropLinks(aEvent, aDisallowInherit) {
3599 return Services.droppedLinkHandler.dropLinks(aEvent, aDisallowInherit);
3603 var homeButtonObserver = {
3605 // disallow setting home pages that inherit the principal
3606 let links = browserDragAndDrop.dropLinks(aEvent, true);
3609 for (let link of links) {
3610 if (link.url.includes("|")) {
3611 urls.push(...link.url.split("|"));
3613 urls.push(link.url);
3618 browserDragAndDrop.validateURIsForDrop(aEvent, urls);
3623 setTimeout(openHomeDialog, 0, urls.join("|"));
3627 onDragOver(aEvent) {
3628 if (HomePage.locked) {
3631 browserDragAndDrop.dragOver(aEvent);
3632 aEvent.dropEffect = "link";
3636 function openHomeDialog(aURL) {
3637 var promptTitle = gNavigatorBundle.getString("droponhometitle");
3639 if (aURL.includes("|")) {
3640 promptMsg = gNavigatorBundle.getString("droponhomemsgMultiple");
3642 promptMsg = gNavigatorBundle.getString("droponhomemsg");
3645 var pressedVal = Services.prompt.confirmEx(
3649 Services.prompt.STD_YES_NO_BUTTONS,
3657 if (pressedVal == 0) {
3658 HomePage.set(aURL).catch(console.error);
3662 var newTabButtonObserver = {
3663 onDragOver(aEvent) {
3664 browserDragAndDrop.dragOver(aEvent);
3666 async onDrop(aEvent) {
3667 let links = browserDragAndDrop.dropLinks(aEvent);
3670 Services.prefs.getIntPref("browser.tabs.maxOpenBeforeWarn")
3672 // Sync dialog cannot be used inside drop event handler.
3673 let answer = await OpenInTabsUtils.promiseConfirmOpenInTabs(
3682 let where = aEvent.shiftKey ? "tabshifted" : "tab";
3683 let triggeringPrincipal = browserDragAndDrop.getTriggeringPrincipal(aEvent);
3684 let csp = browserDragAndDrop.getCsp(aEvent);
3685 for (let link of links) {
3687 let data = await UrlbarUtils.getShortcutOrURIAndPostData(link.url);
3688 // Allow third-party services to fixup this URL.
3689 openLinkIn(data.url, where, {
3690 postData: data.postData,
3691 allowThirdPartyFixup: true,
3692 triggeringPrincipal,
3700 var newWindowButtonObserver = {
3701 onDragOver(aEvent) {
3702 browserDragAndDrop.dragOver(aEvent);
3704 async onDrop(aEvent) {
3705 let links = browserDragAndDrop.dropLinks(aEvent);
3708 Services.prefs.getIntPref("browser.tabs.maxOpenBeforeWarn")
3710 // Sync dialog cannot be used inside drop event handler.
3711 let answer = await OpenInTabsUtils.promiseConfirmOpenInTabs(
3720 let triggeringPrincipal = browserDragAndDrop.getTriggeringPrincipal(aEvent);
3721 let csp = browserDragAndDrop.getCsp(aEvent);
3722 for (let link of links) {
3724 let data = await UrlbarUtils.getShortcutOrURIAndPostData(link.url);
3725 // Allow third-party services to fixup this URL.
3726 openLinkIn(data.url, "window", {
3727 // TODO fix allowInheritPrincipal
3728 // (this is required by javascript: drop to the new window) Bug 1475201
3729 allowInheritPrincipal: true,
3730 postData: data.postData,
3731 allowThirdPartyFixup: true,
3732 triggeringPrincipal,
3740 const BrowserSearch = {
3741 _searchInitComplete: false,
3744 Services.obs.addObserver(this, "browser-search-engine-modified");
3747 delayedStartupInit() {
3748 // Asynchronously initialize the search service if necessary, to get the
3749 // current engine for working out the placeholder.
3750 this._updateURLBarPlaceholderFromDefaultEngine(
3751 PrivateBrowsingUtils.isWindowPrivate(window),
3752 // Delay the update for this until so that we don't change it while
3753 // the user is looking at it / isn't expecting it.
3756 this._searchInitComplete = true;
3761 Services.obs.removeObserver(this, "browser-search-engine-modified");
3764 observe(engine, topic, data) {
3765 // There are two kinds of search engine objects, nsISearchEngine objects and
3766 // plain { uri, title, icon } objects. `engine` in this method is the
3767 // former. The browser.engines and browser.hiddenEngines arrays are the
3768 // latter, and they're the engines offered by the the page in the browser.
3770 // The two types of engines are currently related by their titles/names,
3771 // although that may change; see bug 335102.
3772 let engineName = engine.wrappedJSObject.name;
3774 case "engine-removed":
3775 // An engine was removed from the search service. If a page is offering
3776 // the engine, then the engine needs to be added back to the corresponding
3777 // browser's offered engines.
3778 this._addMaybeOfferedEngine(engineName);
3780 case "engine-added":
3781 // An engine was added to the search service. If a page is offering the
3782 // engine, then the engine needs to be removed from the corresponding
3783 // browser's offered engines.
3784 this._removeMaybeOfferedEngine(engineName);
3786 case "engine-default":
3788 this._searchInitComplete &&
3789 !PrivateBrowsingUtils.isWindowPrivate(window)
3791 this._updateURLBarPlaceholder(engineName, false);
3794 case "engine-default-private":
3796 this._searchInitComplete &&
3797 PrivateBrowsingUtils.isWindowPrivate(window)
3799 this._updateURLBarPlaceholder(engineName, true);
3805 _addMaybeOfferedEngine(engineName) {
3806 let selectedBrowserOffersEngine = false;
3807 for (let browser of gBrowser.browsers) {
3808 for (let i = 0; i < (browser.hiddenEngines || []).length; i++) {
3809 if (browser.hiddenEngines[i].title == engineName) {
3810 if (!browser.engines) {
3811 browser.engines = [];
3813 browser.engines.push(browser.hiddenEngines[i]);
3814 browser.hiddenEngines.splice(i, 1);
3815 if (browser == gBrowser.selectedBrowser) {
3816 selectedBrowserOffersEngine = true;
3822 if (selectedBrowserOffersEngine) {
3823 this.updateOpenSearchBadge();
3827 _removeMaybeOfferedEngine(engineName) {
3828 let selectedBrowserOffersEngine = false;
3829 for (let browser of gBrowser.browsers) {
3830 for (let i = 0; i < (browser.engines || []).length; i++) {
3831 if (browser.engines[i].title == engineName) {
3832 if (!browser.hiddenEngines) {
3833 browser.hiddenEngines = [];
3835 browser.hiddenEngines.push(browser.engines[i]);
3836 browser.engines.splice(i, 1);
3837 if (browser == gBrowser.selectedBrowser) {
3838 selectedBrowserOffersEngine = true;
3844 if (selectedBrowserOffersEngine) {
3845 this.updateOpenSearchBadge();
3850 * Initializes the urlbar placeholder to the pre-saved engine name. We do this
3851 * via a preference, to avoid needing to synchronously init the search service.
3853 * This should be called around the time of DOMContentLoaded, so that it is
3854 * initialized quickly before the user sees anything.
3856 * Note: If the preference doesn't exist, we don't do anything as the default
3857 * placeholder is a string which doesn't have the engine name; however, this
3858 * can be overridden using the `force` parameter.
3860 * @param {Boolean} force If true and the preference doesn't exist, the
3861 * placeholder will be set to the default version
3862 * without an engine name ("Search or enter address").
3864 initPlaceHolder(force = false) {
3866 "browser.urlbar.placeholderName" +
3867 (PrivateBrowsingUtils.isWindowPrivate(window) ? ".private" : "");
3868 let engineName = Services.prefs.getStringPref(prefName, "");
3869 if (engineName || force) {
3870 // We can do this directly, since we know we're at DOMContentLoaded.
3871 this._setURLBarPlaceholder(engineName);
3876 * This is a wrapper around '_updateURLBarPlaceholder' that uses the
3877 * appropriate default engine to get the engine name.
3879 * @param {Boolean} isPrivate Set to true if this is a private window.
3880 * @param {Boolean} [delayUpdate] Set to true, to delay update until the
3881 * placeholder is not displayed.
3883 async _updateURLBarPlaceholderFromDefaultEngine(
3887 const getDefault = isPrivate
3888 ? Services.search.getDefaultPrivate
3889 : Services.search.getDefault;
3890 let defaultEngine = await getDefault();
3891 if (!this._searchInitComplete) {
3892 // If we haven't finished initialising, ensure the placeholder
3893 // preference is set for the next startup.
3894 SearchUIUtils.updatePlaceholderNamePreference(defaultEngine, isPrivate);
3896 this._updateURLBarPlaceholder(defaultEngine.name, isPrivate, delayUpdate);
3900 * Updates the URLBar placeholder for the specified engine, delaying the
3901 * update if required. This also saves the current engine name in preferences
3902 * for the next restart.
3904 * Note: The engine name will only be displayed for built-in engines, as we
3905 * know they should have short names.
3907 * @param {String} engineName The search engine name to use for the update.
3908 * @param {Boolean} isPrivate Set to true if this is a private window.
3909 * @param {Boolean} [delayUpdate] Set to true, to delay update until the
3910 * placeholder is not displayed.
3912 _updateURLBarPlaceholder(engineName, isPrivate, delayUpdate = false) {
3914 throw new Error("Expected an engineName to be specified");
3917 const engine = Services.search.getEngineByName(engineName);
3918 if (!engine.isAppProvided) {
3919 // Set the engine name to an empty string for non-default engines, which'll
3920 // make sure we display the default placeholder string.
3924 // Only delay if requested, and we're not displaying text in the URL bar
3926 if (delayUpdate && !gURLBar.value) {
3927 // Delays changing the URL Bar placeholder until the user is not going to be
3928 // seeing it, e.g. when there is a value entered in the bar, or if there is
3929 // a tab switch to a tab which has a url loaded. We delay the update until
3930 // the user is out of search mode since an alternative placeholder is used
3932 let placeholderUpdateListener = () => {
3933 if (gURLBar.value && !gURLBar.searchMode) {
3934 // By the time the user has switched, they may have changed the engine
3935 // again, so we need to call this function again but with the
3937 // No need to await for this to finish, we're in a listener here anyway.
3938 this._updateURLBarPlaceholderFromDefaultEngine(isPrivate, false);
3939 gURLBar.removeEventListener("input", placeholderUpdateListener);
3940 gBrowser.tabContainer.removeEventListener(
3942 placeholderUpdateListener
3947 gURLBar.addEventListener("input", placeholderUpdateListener);
3948 gBrowser.tabContainer.addEventListener(
3950 placeholderUpdateListener
3952 } else if (!gURLBar.searchMode) {
3953 this._setURLBarPlaceholder(engineName);
3958 * Sets the URLBar placeholder to either something based on the engine name,
3959 * or the default placeholder.
3961 * @param {String} name The name of the engine to use, an empty string if to
3962 * use the default placeholder.
3964 _setURLBarPlaceholder(name) {
3965 document.l10n.setAttributes(
3967 name ? "urlbar-placeholder-with-name" : "urlbar-placeholder",
3968 name ? { name } : undefined
3972 addEngine(browser, engine) {
3973 if (!this._searchInitComplete) {
3974 // We haven't finished initialising search yet. This means we can't
3975 // call getEngineByName here. Since this is only on start-up and unlikely
3976 // to happen in the normal case, we'll just return early rather than
3977 // trying to handle it asynchronously.
3980 // Check to see whether we've already added an engine with this title
3981 if (browser.engines) {
3982 if (browser.engines.some(e => e.title == engine.title)) {
3988 // If this engine (identified by title) is already in the list, add it
3989 // to the list of hidden engines rather than to the main list.
3990 if (Services.search.getEngineByName(engine.title)) {
3994 var engines = (hidden ? browser.hiddenEngines : browser.engines) || [];
3998 title: engine.title,
4000 return browser.mIconURL;
4005 browser.hiddenEngines = engines;
4007 browser.engines = engines;
4008 if (browser == gBrowser.selectedBrowser) {
4009 this.updateOpenSearchBadge();
4015 * Update the browser UI to show whether or not additional engines are
4016 * available when a page is loaded or the user switches tabs to a page that
4017 * has search engines.
4019 updateOpenSearchBadge() {
4020 gURLBar.addSearchEngineHelper.setEnginesFromBrowser(
4021 gBrowser.selectedBrowser
4024 var searchBar = this.searchBar;
4029 var engines = gBrowser.selectedBrowser.engines;
4030 if (engines && engines.length) {
4031 searchBar.setAttribute("addengines", "true");
4033 searchBar.removeAttribute("addengines");
4038 * Focuses the search bar if present on the toolbar, or the address bar,
4039 * putting it in search mode. Will do so in an existing non-popup browser
4040 * window or open a new one if necessary.
4042 webSearch: function BrowserSearch_webSearch() {
4044 window.location.href != AppConstants.BROWSER_CHROME_URL ||
4047 let win = URILoadingHelper.getTopWin(window, { skipPopups: true });
4049 // If there's an open browser window, it should handle this command
4051 win.BrowserSearch.webSearch();
4053 // If there are no open browser windows, open a new one
4054 var observer = function (subject) {
4055 if (subject == win) {
4056 BrowserSearch.webSearch();
4057 Services.obs.removeObserver(
4059 "browser-delayed-startup-finished"
4063 win = window.openDialog(
4064 AppConstants.BROWSER_CHROME_URL,
4066 "chrome,all,dialog=no",
4069 Services.obs.addObserver(observer, "browser-delayed-startup-finished");
4074 let focusUrlBarIfSearchFieldIsNotActive = function (aSearchBar) {
4075 if (!aSearchBar || document.activeElement != aSearchBar.textbox) {
4076 // Limit the results to search suggestions, like the search bar.
4077 gURLBar.searchModeShortcut();
4081 let searchBar = this.searchBar;
4082 let placement = CustomizableUI.getPlacementOfWidget("search-container");
4083 let focusSearchBar = () => {
4084 searchBar = this.searchBar;
4086 focusUrlBarIfSearchFieldIsNotActive(searchBar);
4091 ((searchBar.parentNode.getAttribute("overflowedItem") == "true" &&
4092 placement.area == CustomizableUI.AREA_NAVBAR) ||
4093 placement.area == CustomizableUI.AREA_FIXED_OVERFLOW_PANEL)
4095 let navBar = document.getElementById(CustomizableUI.AREA_NAVBAR);
4096 navBar.overflowable.show().then(focusSearchBar);
4100 if (window.fullScreen) {
4101 FullScreen.showNavToolbox();
4105 focusUrlBarIfSearchFieldIsNotActive(searchBar);
4109 * Loads a search results page, given a set of search terms. Uses the current
4110 * engine if the search bar is visible, or the default engine otherwise.
4113 * The search terms to use for the search.
4115 * String indicating where the search should load. Most commonly used
4116 * are 'tab' or 'window', defaults to 'current'.
4118 * Whether to use the Private Browsing mode default search engine.
4119 * Defaults to `false`.
4120 * @param purpose [optional]
4121 * A string meant to indicate the context of the search request. This
4122 * allows the search service to provide a different nsISearchSubmission
4123 * depending on e.g. where the search is triggered in the UI.
4124 * @param triggeringPrincipal
4125 * The principal to use for a new window or tab.
4127 * The content security policy to use for a new window or tab.
4128 * @param inBackground [optional]
4129 * Set to true for the tab to be loaded in the background, default false.
4130 * @param engine [optional]
4131 * The search engine to use for the search.
4132 * @param tab [optional]
4133 * The tab to show the search result.
4135 * @return engine The search engine used to perform a search, or null if no
4136 * search was performed.
4143 triggeringPrincipal,
4145 inBackground = false,
4149 if (!triggeringPrincipal) {
4151 "Required argument triggeringPrincipal missing within _loadSearch"
4157 ? await Services.search.getDefaultPrivate()
4158 : await Services.search.getDefault();
4161 let submission = engine.getSubmission(searchText, null, purpose); // HTML response
4163 // getSubmission can return null if the engine doesn't have a URL
4164 // with a text/html response type. This is unlikely (since
4165 // SearchService._addEngineToStore() should fail for such an engine),
4166 // but let's be on the safe side.
4171 openLinkIn(submission.uri.spec, where || "current", {
4172 private: usePrivate && !PrivateBrowsingUtils.isWindowPrivate(window),
4173 postData: submission.postData,
4175 relatedToCurrent: true,
4176 triggeringPrincipal,
4178 targetBrowser: tab?.linkedBrowser,
4179 globalHistoryOptions: {
4180 triggeringSearchEngine: engine.name,
4184 return { engine, url: submission.uri };
4188 * Perform a search initiated from the context menu.
4190 * This should only be called from the context menu. See
4191 * BrowserSearch.loadSearch for the preferred API.
4193 async loadSearchFromContext(
4196 triggeringPrincipal,
4200 event = getRootEvent(event);
4201 let where = whereToOpenLink(event);
4202 if (where == "current") {
4203 // override: historically search opens in new tab
4206 if (usePrivate && !PrivateBrowsingUtils.isWindowPrivate(window)) {
4209 let inBackground = Services.prefs.getBoolPref(
4210 "browser.search.context.loadInBackground"
4212 if (event.button == 1 || event.ctrlKey) {
4213 inBackground = !inBackground;
4216 let { engine } = await BrowserSearch._loadSearch(
4221 Services.scriptSecurityManager.createNullPrincipal(
4222 triggeringPrincipal.originAttributes
4229 BrowserSearchTelemetry.recordSearch(
4230 gBrowser.selectedBrowser,
4238 * Perform a search initiated from the command line.
4240 async loadSearchFromCommandLine(terms, usePrivate, triggeringPrincipal, csp) {
4241 let { engine } = await BrowserSearch._loadSearch(
4246 triggeringPrincipal,
4250 BrowserSearchTelemetry.recordSearch(
4251 gBrowser.selectedBrowser,
4259 * Perform a search initiated from an extension.
4261 async loadSearchFromExtension({
4266 triggeringPrincipal,
4268 const result = await BrowserSearch._loadSearch(
4271 PrivateBrowsingUtils.isWindowPrivate(window),
4273 triggeringPrincipal,
4280 BrowserSearchTelemetry.recordSearch(
4281 gBrowser.selectedBrowser,
4288 * Returns the search bar element if it is present in the toolbar, null otherwise.
4291 return document.getElementById("searchbar");
4295 * Infobar to notify the user's search engine has been removed
4296 * and replaced with an application default search engine.
4298 * @param {string} oldEngine
4299 * name of the engine to be moved and replaced.
4300 * @param {string} newEngine
4301 * name of the application default engine to replaced the removed engine.
4303 async removalOfSearchEngineNotificationBox(oldEngine, newEngine) {
4306 "l10n-id": "remove-search-engine-button",
4309 const notificationBox = gNotificationBox.getNotificationWithValue(
4310 "search-engine-removal"
4312 gNotificationBox.removeNotification(notificationBox);
4316 supportPage: "search-engine-removal",
4320 await gNotificationBox.appendNotification(
4321 "search-engine-removal",
4324 "l10n-id": "removed-search-engine-message2",
4325 "l10n-args": { oldEngine, newEngine },
4327 priority: gNotificationBox.PRIORITY_SYSTEM,
4332 // Update engine name in the placeholder to the new default engine name.
4333 this._updateURLBarPlaceholderFromDefaultEngine(
4334 PrivateBrowsingUtils.isWindowPrivate(window),
4336 ).catch(console.error);
4340 XPCOMUtils.defineConstant(this, "BrowserSearch", BrowserSearch);
4342 function CreateContainerTabMenu(event) {
4343 // Do not open context menus within menus.
4344 // Note that triggerNode is null if we're opened by long press.
4345 if (event.target.triggerNode?.closest("menupopup")) {
4348 createUserContextMenu(event, {
4349 useAccessKeys: false,
4350 showDefaultTab: true,
4354 function FillHistoryMenu(aParent) {
4355 // Lazily add the hover listeners on first showing and never remove them
4356 if (!aParent.hasStatusListener) {
4357 // Show history item's uri in the status bar when hovering, and clear on exit
4358 aParent.addEventListener("DOMMenuItemActive", function (aEvent) {
4359 // Only the current page should have the checked attribute, so skip it
4360 if (!aEvent.target.hasAttribute("checked")) {
4361 XULBrowserWindow.setOverLink(aEvent.target.getAttribute("uri"));
4364 aParent.addEventListener("DOMMenuItemInactive", function () {
4365 XULBrowserWindow.setOverLink("");
4368 aParent.hasStatusListener = true;
4371 // Remove old entries if any
4372 let children = aParent.children;
4373 for (var i = children.length - 1; i >= 0; --i) {
4374 if (children[i].hasAttribute("index")) {
4375 aParent.removeChild(children[i]);
4379 const MAX_HISTORY_MENU_ITEMS = 15;
4381 const tooltipBack = gNavigatorBundle.getString("tabHistory.goBack");
4382 const tooltipCurrent = gNavigatorBundle.getString("tabHistory.reloadCurrent");
4383 const tooltipForward = gNavigatorBundle.getString("tabHistory.goForward");
4385 function updateSessionHistory(sessionHistory, initial, ssInParent) {
4386 let count = ssInParent
4387 ? sessionHistory.count
4388 : sessionHistory.entries.length;
4392 // if there is only one entry now, close the popup.
4393 aParent.hidePopup();
4395 } else if (aParent.id != "backForwardMenu" && !aParent.parentNode.open) {
4396 // if the popup wasn't open before, but now needs to be, reopen the menu.
4397 // It should trigger FillHistoryMenu again. This might happen with the
4398 // delay from click-and-hold menus but skip this for the context menu
4399 // (backForwardMenu) rather than figuring out how the menu should be
4400 // positioned and opened as it is an extreme edgecase.
4401 aParent.parentNode.open = true;
4406 let index = sessionHistory.index;
4407 let half_length = Math.floor(MAX_HISTORY_MENU_ITEMS / 2);
4408 let start = Math.max(index - half_length, 0);
4410 start == 0 ? MAX_HISTORY_MENU_ITEMS : index + half_length + 1,
4414 start = Math.max(count - MAX_HISTORY_MENU_ITEMS, 0);
4417 let existingIndex = 0;
4419 for (let j = end - 1; j >= start; j--) {
4420 let entry = ssInParent
4421 ? sessionHistory.getEntryAtIndex(j)
4422 : sessionHistory.entries[j];
4423 // Explicitly check for "false" to stay backwards-compatible with session histories
4424 // from before the hasUserInteraction was implemented.
4426 BrowserUtils.navigationRequireUserInteraction &&
4427 entry.hasUserInteraction === false &&
4428 // Always allow going to the first and last navigation points.
4434 let uri = ssInParent ? entry.URI.spec : entry.url;
4437 existingIndex < children.length
4438 ? children[existingIndex]
4439 : document.createXULElement("menuitem");
4441 item.setAttribute("uri", uri);
4442 item.setAttribute("label", entry.title || uri);
4443 item.setAttribute("index", j);
4445 // Cache this so that gotoHistoryIndex doesn't need the original index
4446 item.setAttribute("historyindex", j - index);
4449 // Use list-style-image rather than the image attribute in order to
4450 // allow CSS to override this.
4451 item.style.listStyleImage = `url(page-icon:${uri})`;
4456 "unified-nav-back menuitem-iconic menuitem-with-favicon";
4457 item.setAttribute("tooltiptext", tooltipBack);
4458 } else if (j == index) {
4459 item.setAttribute("type", "radio");
4460 item.setAttribute("checked", "true");
4461 item.className = "unified-nav-current";
4462 item.setAttribute("tooltiptext", tooltipCurrent);
4465 "unified-nav-forward menuitem-iconic menuitem-with-favicon";
4466 item.setAttribute("tooltiptext", tooltipForward);
4469 if (!item.parentNode) {
4470 aParent.appendChild(item);
4477 let existingLength = children.length;
4478 while (existingIndex < existingLength) {
4479 aParent.removeChild(aParent.lastElementChild);
4485 // If session history in parent is available, use it. Otherwise, get the session history
4486 // from session store.
4487 let sessionHistory = gBrowser.selectedBrowser.browsingContext.sessionHistory;
4488 if (sessionHistory?.count) {
4489 // Don't show the context menu if there is only one item.
4490 if (sessionHistory.count <= 1) {
4494 updateSessionHistory(sessionHistory, true, true);
4496 sessionHistory = SessionStore.getSessionHistory(
4497 gBrowser.selectedTab,
4498 updateSessionHistory
4500 updateSessionHistory(sessionHistory, true, false);
4506 function BrowserDownloadsUI() {
4507 if (PrivateBrowsingUtils.isWindowPrivate(window)) {
4508 openTrustedLinkIn("about:downloads", "tab");
4510 PlacesCommandHook.showPlacesOrganizer("Downloads");
4514 function toOpenWindowByType(inType, uri, features) {
4515 var topWindow = Services.wm.getMostRecentWindow(inType);
4519 } else if (features) {
4520 window.open(uri, "_blank", features);
4525 "chrome,extrachrome,menubar,resizable,scrollbars,status,toolbar"
4531 * Open a new browser window. See `BrowserWindowTracker.openWindow` for
4534 * @return a reference to the new window.
4536 function OpenBrowserWindow(options = {}) {
4537 let telemetryObj = {};
4538 TelemetryStopwatch.start("FX_NEW_WINDOW_MS", telemetryObj);
4540 let win = BrowserWindowTracker.openWindow({
4541 openerWindow: window,
4545 win.addEventListener(
4548 TelemetryStopwatch.finish("FX_NEW_WINDOW_MS", telemetryObj);
4557 * Update the global flag that tracks whether or not any edit UI (the Edit menu,
4558 * edit-related items in the context menu, and edit-related toolbar buttons
4559 * is visible, then update the edit commands' enabled state accordingly. We use
4560 * this flag to skip updating the edit commands on focus or selection changes
4561 * when no UI is visible to improve performance (including pageload performance,
4562 * since focus changes when you load a new page).
4564 * If UI is visible, we use goUpdateGlobalEditMenuItems to set the commands'
4565 * enabled state so the UI will reflect it appropriately.
4567 * If the UI isn't visible, we enable all edit commands so keyboard shortcuts
4568 * still work and just lazily disable them as needed when the user presses a
4571 * This doesn't work on Mac, since Mac menus flash when users press their
4572 * keyboard shortcuts, so edit UI is essentially always visible on the Mac,
4573 * and we need to always update the edit commands. Thus on Mac this function
4576 function updateEditUIVisibility() {
4577 if (AppConstants.platform == "macosx") {
4581 let editMenuPopupState = document.getElementById("menu_EditPopup").state;
4582 let contextMenuPopupState = document.getElementById(
4583 "contentAreaContextMenu"
4585 let placesContextMenuPopupState =
4586 document.getElementById("placesContext").state;
4588 let oldVisible = gEditUIVisible;
4590 // The UI is visible if the Edit menu is opening or open, if the context menu
4591 // is open, or if the toolbar has been customized to include the Cut, Copy,
4592 // or Paste toolbar buttons.
4594 editMenuPopupState == "showing" ||
4595 editMenuPopupState == "open" ||
4596 contextMenuPopupState == "showing" ||
4597 contextMenuPopupState == "open" ||
4598 placesContextMenuPopupState == "showing" ||
4599 placesContextMenuPopupState == "open";
4600 const kOpenPopupStates = ["showing", "open"];
4601 if (!gEditUIVisible) {
4602 // Now check the edit-controls toolbar buttons.
4603 let placement = CustomizableUI.getPlacementOfWidget("edit-controls");
4604 let areaType = placement ? CustomizableUI.getAreaType(placement.area) : "";
4605 if (areaType == CustomizableUI.TYPE_PANEL) {
4606 let customizablePanel = PanelUI.overflowPanel;
4607 gEditUIVisible = kOpenPopupStates.includes(customizablePanel.state);
4609 areaType == CustomizableUI.TYPE_TOOLBAR &&
4610 window.toolbar.visible
4612 // The edit controls are on a toolbar, so they are visible,
4613 // unless they're in a panel that isn't visible...
4614 if (placement.area == "nav-bar") {
4615 let editControls = document.getElementById("edit-controls");
4617 !editControls.hasAttribute("overflowedItem") ||
4618 kOpenPopupStates.includes(
4619 document.getElementById("widget-overflow").state
4622 gEditUIVisible = true;
4627 // Now check the main menu panel
4628 if (!gEditUIVisible) {
4629 gEditUIVisible = kOpenPopupStates.includes(PanelUI.panel.state);
4632 // No need to update commands if the edit UI visibility has not changed.
4633 if (gEditUIVisible == oldVisible) {
4637 // If UI is visible, update the edit commands' enabled state to reflect
4638 // whether or not they are actually enabled for the current focus/selection.
4639 if (gEditUIVisible) {
4640 goUpdateGlobalEditMenuItems();
4642 // Otherwise, enable all commands, so that keyboard shortcuts still work,
4643 // then lazily determine their actual enabled state when the user presses
4644 // a keyboard shortcut.
4645 goSetCommandEnabled("cmd_undo", true);
4646 goSetCommandEnabled("cmd_redo", true);
4647 goSetCommandEnabled("cmd_cut", true);
4648 goSetCommandEnabled("cmd_copy", true);
4649 goSetCommandEnabled("cmd_paste", true);
4650 goSetCommandEnabled("cmd_selectAll", true);
4651 goSetCommandEnabled("cmd_delete", true);
4652 goSetCommandEnabled("cmd_switchTextDirection", true);
4658 * Updates User Context Menu Item UI visibility depending on
4659 * privacy.userContext.enabled pref state.
4661 updateUserContextUIVisibility() {
4662 let menu = document.getElementById("menu_newUserContext");
4663 menu.hidden = !Services.prefs.getBoolPref(
4664 "privacy.userContext.enabled",
4667 // Visibility of File menu item shouldn't change frequently.
4668 if (PrivateBrowsingUtils.isWindowPrivate(window)) {
4669 menu.setAttribute("disabled", "true");
4674 * Updates the enabled state of the "Import From Another Browser" command
4675 * depending on the DisableProfileImport policy.
4677 updateImportCommandEnabledState() {
4678 if (!Services.policies.isAllowed("profileImport")) {
4680 .getElementById("cmd_file_importFromAnotherBrowser")
4681 .setAttribute("disabled", "true");
4686 * Updates the "Close tab" command to reflect the number of selected tabs,
4689 updateTabCloseCountState() {
4690 document.l10n.setAttributes(
4691 document.getElementById("menu_close"),
4692 "menu-file-close-tab",
4693 { tabCount: gBrowser.selectedTabs.length }
4697 onPopupShowing(event) {
4698 // We don't care about submenus:
4699 if (event.target.id != "menu_FilePopup") {
4702 this.updateUserContextUIVisibility();
4703 this.updateImportCommandEnabledState();
4704 this.updateTabCloseCountState();
4705 if (AppConstants.platform == "macosx") {
4706 gShareUtils.updateShareURLMenuItem(
4707 gBrowser.selectedBrowser,
4708 document.getElementById("menu_savePage")
4711 PrintUtils.updatePrintSetupMenuHiddenState();
4717 * Updates a sharing item in a given menu, creating it if necessary.
4719 updateShareURLMenuItem(browser, insertAfterEl) {
4720 if (!Services.prefs.getBoolPref("browser.menu.share_url.allow", true)) {
4724 // We only support "share URL" on macOS and on Windows:
4725 if (AppConstants.platform != "macosx" && AppConstants.platform != "win") {
4729 let shareURL = insertAfterEl.nextElementSibling;
4730 if (!shareURL?.matches(".share-tab-url-item")) {
4731 shareURL = this._createShareURLMenuItem(insertAfterEl);
4734 shareURL.browserToShare = Cu.getWeakReference(browser);
4735 if (AppConstants.platform == "win") {
4736 // We disable the item on Windows, as there's no submenu.
4737 // On macOS, we handle this inside the menupopup.
4738 shareURL.hidden = !BrowserUtils.getShareableURL(browser.currentURI);
4743 * Creates and returns the "Share" menu item.
4745 _createShareURLMenuItem(insertAfterEl) {
4746 let menu = insertAfterEl.parentNode;
4747 let shareURL = null;
4748 if (AppConstants.platform == "win") {
4749 shareURL = this._buildShareURLItem(menu.id);
4750 } else if (AppConstants.platform == "macosx") {
4751 shareURL = this._buildShareURLMenu(menu.id);
4753 shareURL.className = "share-tab-url-item";
4756 menu.id == "tabContextMenu"
4757 ? "tab-context-share-url"
4758 : "menu-file-share-url";
4759 document.l10n.setAttributes(shareURL, l10nID);
4761 menu.insertBefore(shareURL, insertAfterEl.nextSibling);
4766 * Returns a menu item specifically for accessing Windows sharing services.
4768 _buildShareURLItem() {
4769 let shareURLMenuItem = document.createXULElement("menuitem");
4770 shareURLMenuItem.addEventListener("command", this);
4771 return shareURLMenuItem;
4775 * Returns a menu specifically for accessing macOSx sharing services .
4777 _buildShareURLMenu() {
4778 let menu = document.createXULElement("menu");
4779 let menuPopup = document.createXULElement("menupopup");
4780 menuPopup.addEventListener("popupshowing", this);
4781 menu.appendChild(menuPopup);
4786 * Get the sharing data for a given DOM node.
4788 getDataToShare(node) {
4789 let browser = node.browserToShare?.get();
4790 let urlToShare = null;
4791 let titleToShare = null;
4794 let maybeToShare = BrowserUtils.getShareableURL(browser.currentURI);
4796 urlToShare = maybeToShare;
4797 titleToShare = browser.contentTitle;
4800 return { urlToShare, titleToShare };
4804 * Populates the "Share" menupopup on macOSx.
4806 initializeShareURLPopup(menuPopup) {
4807 if (AppConstants.platform != "macosx") {
4812 while (menuPopup.firstChild) {
4813 menuPopup.firstChild.remove();
4816 let { urlToShare } = this.getDataToShare(menuPopup.parentNode);
4818 // If we can't share the current URL, we display the items disabled,
4819 // but enable the "more..." item at the bottom, to allow the user to
4820 // change sharing preferences in the system dialog.
4821 let shouldEnable = !!urlToShare;
4823 // Fake it so we can ask the sharing service for services:
4824 urlToShare = makeURI("https://mozilla.org/");
4827 let sharingService = gBrowser.MacSharingService;
4828 let currentURI = gURLBar.makeURIReadable(urlToShare).displaySpec;
4829 let services = sharingService.getSharingProviders(currentURI);
4831 services.forEach(share => {
4832 let item = document.createXULElement("menuitem");
4833 item.classList.add("menuitem-iconic");
4834 item.setAttribute("label", share.menuItemTitle);
4835 item.setAttribute("share-name", share.name);
4836 item.setAttribute("image", share.image);
4837 if (!shouldEnable) {
4838 item.setAttribute("disabled", "true");
4840 menuPopup.appendChild(item);
4842 menuPopup.appendChild(document.createXULElement("menuseparator"));
4843 let moreItem = document.createXULElement("menuitem");
4844 document.l10n.setAttributes(moreItem, "menu-share-more");
4845 moreItem.classList.add("menuitem-iconic", "share-more-button");
4846 menuPopup.appendChild(moreItem);
4848 menuPopup.addEventListener("command", this);
4849 menuPopup.parentNode
4850 .closest("menupopup")
4851 .addEventListener("popuphiding", this);
4852 menuPopup.setAttribute("data-initialized", true);
4855 onShareURLCommand(event) {
4856 // Only call sharing services for the "Share" menu item. These services
4857 // are accessed from a submenu popup for MacOS or the "Share" menu item
4858 // for Windows. Use .closest() as a hack to find either the item itself
4859 // or a parent with the right class.
4860 let target = event.target.closest(".share-tab-url-item");
4865 // urlToShare/titleToShare may be null, in which case only the "more"
4866 // item is enabled, so handle that case first:
4867 if (event.target.classList.contains("share-more-button")) {
4868 gBrowser.MacSharingService.openSharingPreferences();
4872 let { urlToShare, titleToShare } = this.getDataToShare(target);
4873 let currentURI = gURLBar.makeURIReadable(urlToShare).displaySpec;
4875 if (AppConstants.platform == "win") {
4876 WindowsUIUtils.shareUrl(currentURI, titleToShare);
4880 // On macOSX platforms
4881 let shareName = event.target.getAttribute("share-name");
4884 gBrowser.MacSharingService.shareUrl(shareName, currentURI, titleToShare);
4888 onPopupHiding(event) {
4889 // We don't want to rebuild the contents of the "Share" menupopup if only its submenu is
4890 // hidden. So bail if this isn't the top menupopup in the DOM tree:
4891 if (event.target.parentNode.closest("menupopup")) {
4894 // Otherwise, clear its "data-initialized" attribute.
4895 let menupopup = event.target.querySelector(
4896 ".share-tab-url-item"
4898 menupopup?.removeAttribute("data-initialized");
4900 event.target.removeEventListener("popuphiding", this);
4903 onPopupShowing(event) {
4904 if (!event.target.hasAttribute("data-initialized")) {
4905 this.initializeShareURLPopup(event.target);
4909 handleEvent(aEvent) {
4910 switch (aEvent.type) {
4912 this.onShareURLCommand(aEvent);
4915 this.onPopupHiding(aEvent);
4917 case "popupshowing":
4918 this.onPopupShowing(aEvent);
4925 * Opens a new tab with the userContextId specified as an attribute of
4926 * sourceEvent. This attribute is propagated to the top level originAttributes
4927 * living on the tab's docShell.
4930 * A click event on a userContext File Menu option
4932 function openNewUserContextTab(event) {
4933 openTrustedLinkIn(BROWSER_NEW_TAB_URL, "tab", {
4934 userContextId: parseInt(event.target.getAttribute("data-usercontextid")),
4938 var XULBrowserWindow = {
4939 // Stored Status, Link and Loading values
4947 QueryInterface: ChromeUtils.generateQI([
4948 "nsIWebProgressListener",
4949 "nsIWebProgressListener2",
4950 "nsISupportsWeakReference",
4951 "nsIXULBrowserWindow",
4955 delete this.stopCommand;
4956 return (this.stopCommand = document.getElementById("Browser:Stop"));
4958 get reloadCommand() {
4959 delete this.reloadCommand;
4960 return (this.reloadCommand = document.getElementById("Browser:Reload"));
4962 get _elementsForTextBasedTypes() {
4963 delete this._elementsForTextBasedTypes;
4964 return (this._elementsForTextBasedTypes = [
4965 document.getElementById("pageStyleMenu"),
4966 document.getElementById("context-viewpartialsource-selection"),
4967 document.getElementById("context-print-selection"),
4970 get _elementsForFind() {
4971 delete this._elementsForFind;
4972 return (this._elementsForFind = [
4973 document.getElementById("cmd_find"),
4974 document.getElementById("cmd_findAgain"),
4975 document.getElementById("cmd_findPrevious"),
4978 get _elementsForViewSource() {
4979 delete this._elementsForViewSource;
4980 return (this._elementsForViewSource = [
4981 document.getElementById("context-viewsource"),
4982 document.getElementById("View:PageSource"),
4985 get _menuItemForRepairTextEncoding() {
4986 delete this._menuItemForRepairTextEncoding;
4987 return (this._menuItemForRepairTextEncoding = document.getElementById(
4988 "repair-text-encoding"
4991 get _menuItemForTranslations() {
4992 delete this._menuItemForTranslations;
4993 return (this._menuItemForTranslations =
4994 document.getElementById("cmd_translate"));
4997 setDefaultStatus(status) {
4998 this.defaultStatus = status;
4999 StatusPanel.update();
5003 * Tells the UI what link we are currently over.
5005 * @param {String} url
5006 * The URL of the link.
5007 * @param {Object} [options]
5008 * This is an extension of nsIXULBrowserWindow for JS callers, will be
5009 * passed on to LinkTargetDisplay.
5011 setOverLink(url, options = undefined) {
5013 url = Services.textToSubURI.unEscapeURIForUI(url);
5015 // Encode bidirectional formatting characters.
5016 // (RFC 3987 sections 3.2 and 4.1 paragraph 6)
5018 /[\u200e\u200f\u202a\u202b\u202c\u202d\u202e]/g,
5022 if (UrlbarPrefs.get("trimURLs")) {
5023 url = BrowserUIUtils.trimURL(url);
5027 this.overLink = url;
5028 LinkTargetDisplay.update(options);
5031 onEnterDOMFullscreen() {
5032 // Clear the status panel.
5034 this.setDefaultStatus("");
5035 this.setOverLink("", { hideStatusPanelImmediately: true });
5038 showTooltip(xDevPix, yDevPix, tooltip, direction, _browser) {
5040 Cc["@mozilla.org/widget/dragservice;1"]
5041 .getService(Ci.nsIDragService)
5042 .getCurrentSession()
5047 if (!document.hasFocus()) {
5051 let elt = document.getElementById("remoteBrowserTooltip");
5052 elt.label = tooltip;
5053 elt.style.direction = direction;
5054 elt.openPopupAtScreen(
5055 xDevPix / window.devicePixelRatio,
5056 yDevPix / window.devicePixelRatio,
5063 let elt = document.getElementById("remoteBrowserTooltip");
5068 return gBrowser.tabs.length;
5071 onProgressChange() {
5083 return this.onProgressChange(
5093 // This function fires only for the currently selected tab.
5094 onStateChange(aWebProgress, aRequest, aStateFlags, aStatus) {
5095 const nsIWebProgressListener = Ci.nsIWebProgressListener;
5097 let browser = gBrowser.selectedBrowser;
5098 gProtectionsHandler.onStateChange(aWebProgress, aStateFlags);
5101 aStateFlags & nsIWebProgressListener.STATE_START &&
5102 aStateFlags & nsIWebProgressListener.STATE_IS_NETWORK
5104 if (aRequest && aWebProgress.isTopLevel) {
5105 // clear out search-engine data
5106 browser.engines = null;
5112 !(aStateFlags & nsIWebProgressListener.STATE_RESTORING) &&
5113 aWebProgress.isTopLevel
5117 // XXX: This needs to be based on window activity...
5118 this.stopCommand.removeAttribute("disabled");
5119 CombinedStopReload.switchToStop(aRequest, aWebProgress);
5121 } else if (aStateFlags & nsIWebProgressListener.STATE_STOP) {
5122 // This (thanks to the filter) is a network stop or the last
5123 // request stop outside of loading the document, stop throbbers
5124 // and progress bars and such
5128 let canViewSource = true;
5129 // Get the URI either from a channel or a pseudo-object
5130 if (aRequest instanceof Ci.nsIChannel || "URI" in aRequest) {
5131 location = aRequest.URI;
5133 // For keyword URIs clear the user typed value since they will be changed into real URIs
5134 if (location.scheme == "keyword" && aWebProgress.isTopLevel) {
5135 gBrowser.userTypedValue = null;
5138 canViewSource = location.scheme != "view-source";
5140 if (location.spec != "about:blank") {
5142 case Cr.NS_ERROR_NET_TIMEOUT:
5143 msg = gNavigatorBundle.getString("nv_timeout");
5150 this.setDefaultStatus(msg);
5152 // Disable View Source menu entries for images, enable otherwise
5154 browser.documentContentType &&
5155 BrowserUtils.mimeTypeIsTextBased(browser.documentContentType);
5156 for (let element of this._elementsForViewSource) {
5157 if (canViewSource && isText) {
5158 element.removeAttribute("disabled");
5160 element.setAttribute("disabled", "true");
5164 this._updateElementsForContentType();
5166 // Update Override Text Encoding state.
5167 // Can't cache the button, because the presence of the element in the DOM
5168 // may change over time.
5169 let button = document.getElementById("characterencoding-button");
5170 if (browser.mayEnableCharacterEncodingMenu) {
5171 this._menuItemForRepairTextEncoding.removeAttribute("disabled");
5172 button?.removeAttribute("disabled");
5174 this._menuItemForRepairTextEncoding.setAttribute("disabled", "true");
5175 button?.setAttribute("disabled", "true");
5179 this.isBusy = false;
5181 if (this.busyUI && aWebProgress.isTopLevel) {
5182 this.busyUI = false;
5184 this.stopCommand.setAttribute("disabled", "true");
5185 CombinedStopReload.switchToReload(aRequest, aWebProgress);
5191 * An nsIWebProgressListener method called by tabbrowser. The `aIsSimulated`
5192 * parameter is extra and not declared in nsIWebProgressListener, however; see
5195 * @param {nsIWebProgress} aWebProgress
5196 * The nsIWebProgress instance that fired the notification.
5197 * @param {nsIRequest} aRequest
5198 * The associated nsIRequest. This may be null in some cases.
5199 * @param {nsIURI} aLocationURI
5200 * The URI of the location that is being loaded.
5201 * @param {integer} aFlags
5202 * Flags that indicate the reason the location changed. See the
5203 * nsIWebProgressListener.LOCATION_CHANGE_* values.
5204 * @param {boolean} aIsSimulated
5205 * True when this is called by tabbrowser due to switching tabs and
5206 * undefined otherwise. This parameter is not declared in
5207 * nsIWebProgressListener.onLocationChange; see bug 1478348.
5209 onLocationChange(aWebProgress, aRequest, aLocationURI, aFlags, aIsSimulated) {
5210 var location = aLocationURI ? aLocationURI.spec : "";
5212 UpdateBackForwardCommands(gBrowser.webNavigation);
5214 Services.obs.notifyObservers(
5216 "touchbar-location-change",
5220 // For most changes we only need to update the browser UI if the primary
5221 // content area was navigated or the selected tab was changed. We don't need
5222 // to do anything else if there was a subframe navigation.
5224 if (!aWebProgress.isTopLevel) {
5228 this.setOverLink("", { hideStatusPanelImmediately: true });
5230 let isSameDocument =
5231 aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT;
5233 (location == "about:blank" &&
5234 BrowserUIUtils.checkEmptyPageOrigin(gBrowser.selectedBrowser)) ||
5237 // Second condition is for new tabs, otherwise
5238 // reload function is enabled until tab is refreshed.
5239 this.reloadCommand.setAttribute("disabled", "true");
5241 this.reloadCommand.removeAttribute("disabled");
5244 let isSessionRestore = !!(
5245 aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SESSION_STORE
5248 // We want to update the popup visibility if we received this notification
5249 // via simulated locationchange events such as switching between tabs, however
5250 // if this is a document navigation then PopupNotifications will be updated
5251 // via TabsProgressListener.onLocationChange and we do not want it called twice
5260 BookmarkingUI.onLocationChange();
5261 // If we've actually changed document, update the toolbar visibility.
5262 if (!isSameDocument) {
5263 updateBookmarkToolbarVisibility();
5266 let closeOpenPanels = selector => {
5267 for (let panel of document.querySelectorAll(selector)) {
5268 if (panel.state != "closed") {
5274 // If the location is changed due to switching tabs,
5275 // ensure we close any open tabspecific panels.
5277 closeOpenPanels("panel[tabspecific='true']");
5280 // Ensure we close any remaining open locationspecific panels
5281 if (!isSameDocument) {
5282 closeOpenPanels("panel[locationspecific='true']");
5285 let screenshotsButtonsDisabled =
5286 gScreenshots.shouldScreenshotsButtonBeDisabled();
5287 Services.obs.notifyObservers(
5289 "toggle-screenshot-disable",
5290 screenshotsButtonsDisabled
5293 gPermissionPanel.onLocationChange();
5295 gProtectionsHandler.onLocationChange();
5297 BrowserPageActions.onLocationChange();
5299 SafeBrowsingNotificationBox.onLocationChange(aLocationURI);
5301 SaveToPocket.onLocationChange(window);
5304 if (aRequest instanceof Ci.nsIChannel) {
5305 originalURI = aRequest.originalURI;
5308 UrlbarProviderSearchTips.onLocationChange(
5316 gTabletModePageCounter.inc();
5318 this._updateElementsForContentType();
5320 this._updateMacUserActivity(window, aLocationURI, aWebProgress);
5322 // Unconditionally disable the Text Encoding button during load to
5323 // keep the UI calm when navigating from one modern page to another and
5324 // the toolbar button is visible.
5325 // Can't cache the button, because the presence of the element in the DOM
5326 // may change over time.
5327 let button = document.getElementById("characterencoding-button");
5328 this._menuItemForRepairTextEncoding.setAttribute("disabled", "true");
5329 button?.setAttribute("disabled", "true");
5331 // Try not to instantiate gCustomizeMode as much as possible,
5332 // so don't use CustomizeMode.sys.mjs to check for URI or customizing.
5334 location == "about:blank" &&
5335 gBrowser.selectedTab.hasAttribute("customizemode")
5337 gCustomizeMode.enter();
5339 CustomizationHandler.isEnteringCustomizeMode ||
5340 CustomizationHandler.isCustomizing()
5342 gCustomizeMode.exit();
5345 CFRPageActions.updatePageActions(gBrowser.selectedBrowser);
5347 AboutReaderParent.updateReaderButton(gBrowser.selectedBrowser);
5348 TranslationsParent.onLocationChange(gBrowser.selectedBrowser);
5350 PictureInPicture.updateUrlbarToggle(gBrowser.selectedBrowser);
5352 if (!gMultiProcessBrowser) {
5353 // Bug 1108553 - Cannot rotate images with e10s
5354 gGestureSupport.restoreRotationState();
5357 // See bug 358202, when tabs are switched during a drag operation,
5358 // timers don't fire on windows (bug 203573)
5360 setTimeout(function () {
5361 XULBrowserWindow.asyncUpdateUI();
5364 this.asyncUpdateUI();
5367 if (AppConstants.MOZ_CRASHREPORTER && aLocationURI) {
5368 let uri = aLocationURI;
5370 // If the current URI contains a username/password, remove it.
5371 uri = aLocationURI.mutate().setUserPass("").finalize();
5373 /* Ignore failures on about: URIs. */
5377 Services.appinfo.annotateCrashReport("URL", uri.spec);
5379 // Don't make noise when the crash reporter is built but not enabled.
5380 if (ex.result != Cr.NS_ERROR_NOT_INITIALIZED) {
5387 _updateElementsForContentType() {
5388 let browser = gBrowser.selectedBrowser;
5391 browser.documentContentType &&
5392 BrowserUtils.mimeTypeIsTextBased(browser.documentContentType);
5393 for (let element of this._elementsForTextBasedTypes) {
5395 element.removeAttribute("disabled");
5397 element.setAttribute("disabled", "true");
5401 // Always enable find commands in PDF documents, otherwise do it only for
5402 // text documents whose location is not in the blacklist.
5404 browser.contentPrincipal?.spec == "resource://pdf.js/web/viewer.html" ||
5405 (isText && BrowserUtils.canFindInPage(gBrowser.currentURI.spec));
5406 for (let element of this._elementsForFind) {
5408 element.removeAttribute("disabled");
5410 element.setAttribute("disabled", "true");
5414 if (TranslationsParent.isRestrictedPage(gBrowser)) {
5415 this._menuItemForTranslations.setAttribute("disabled", "true");
5417 this._menuItemForTranslations.removeAttribute("disabled");
5419 if (gTranslationsEnabled) {
5420 if (TranslationsParent.getIsTranslationsEngineSupported()) {
5421 this._menuItemForTranslations.removeAttribute("hidden");
5423 this._menuItemForTranslations.setAttribute("hidden", "true");
5426 this._menuItemForTranslations.setAttribute("hidden", "true");
5431 * Updates macOS platform code with the current URI and page title.
5432 * From there, we update the current NSUserActivity, enabling Handoff to other
5434 * @param {Window} window
5435 * The window in which the navigation occurred.
5436 * @param {nsIURI} uri
5437 * The URI pointing to the current page.
5438 * @param {nsIWebProgress} webProgress
5439 * The nsIWebProgress instance that fired a onLocationChange notification.
5441 _updateMacUserActivity(win, uri, webProgress) {
5442 if (!webProgress.isTopLevel || AppConstants.platform != "macosx") {
5447 if (PrivateBrowsingUtils.isWindowPrivate(win)) {
5448 // Passing an empty string to MacUserActivityUpdater will invalidate the
5449 // current user activity.
5452 let baseWin = win.docShell.treeOwner.QueryInterface(Ci.nsIBaseWindow);
5453 MacUserActivityUpdater.updateLocation(
5455 win.gBrowser.contentTitle,
5461 * Potentially gets a URI for a MozBrowser to be shown to the user in the
5462 * identity panel. For browsers whose content does not have a principal,
5463 * this tries the precursor. If this is null, we should not override the
5464 * browser's currentURI.
5465 * @param {MozBrowser} browser
5466 * The browser that we need a URI to show the user in the
5468 * @return nsIURI of the principal for the browser's content if
5469 * the browser's currentURI should not be used, null otherwise.
5471 _securityURIOverride(browser) {
5472 let uri = browser.currentURI;
5477 // If the browser's currentURI is sufficiently good that we
5478 // do not require an override, bail out here.
5479 // browser.currentURI should be used.
5480 let { URI_INHERITS_SECURITY_CONTEXT } = Ci.nsIProtocolHandler;
5482 !(doGetProtocolFlags(uri) & URI_INHERITS_SECURITY_CONTEXT) &&
5483 !(uri.scheme == "about" && uri.filePath == "srcdoc") &&
5484 !(uri.scheme == "about" && uri.filePath == "blank")
5489 let principal = browser.contentPrincipal;
5491 if (principal.isNullPrincipal) {
5492 principal = principal.precursorPrincipal;
5499 // Can't get the original URI for a PDF viewer principal yet.
5500 if (principal.originNoSuffix == "resource://pdf.js") {
5504 return principal.URI;
5508 BrowserSearch.updateOpenSearchBadge();
5511 onStatusChange(aWebProgress, aRequest, aStatus, aMessage) {
5512 this.status = aMessage;
5513 StatusPanel.update();
5516 // Properties used to cache security state used to update the UI
5518 _lastLocation: null,
5520 _lastLocationForEvent: null,
5521 // _isSecureContext can change without the state/location changing, due to security
5522 // error pages that intercept certain loads. For example this happens sometimes
5523 // with the the HTTPS-Only Mode error page (more details in bug 1656027)
5524 _isSecureContext: null,
5526 // This is called in multiple ways:
5527 // 1. Due to the nsIWebProgressListener.onContentBlockingEvent notification.
5528 // 2. Called by tabbrowser.xml when updating the current browser.
5529 // 3. Called directly during this object's initializations.
5530 // 4. Due to the nsIWebProgressListener.onLocationChange notification.
5531 // aRequest will be null always in case 2 and 3, and sometimes in case 1 (for
5532 // instance, there won't be a request when STATE_BLOCKED_TRACKING_CONTENT or
5533 // other blocking events are observed).
5534 onContentBlockingEvent(aWebProgress, aRequest, aEvent, aIsSimulated) {
5535 // Don't need to do anything if the data we use to update the UI hasn't
5537 let uri = gBrowser.currentURI;
5538 let spec = uri.spec;
5539 if (this._event == aEvent && this._lastLocationForEvent == spec) {
5542 this._lastLocationForEvent = spec;
5545 typeof aIsSimulated != "boolean" &&
5546 typeof aIsSimulated != "undefined"
5549 "onContentBlockingEvent: aIsSimulated receieved an unexpected type"
5553 gProtectionsHandler.onContentBlockingEvent(
5557 this._event // previous content blocking event
5560 // We need the state of the previous content blocking event, so update
5561 // event after onContentBlockingEvent is called.
5562 this._event = aEvent;
5565 // This is called in multiple ways:
5566 // 1. Due to the nsIWebProgressListener.onSecurityChange notification.
5567 // 2. Called by tabbrowser.xml when updating the current browser.
5568 // 3. Called directly during this object's initializations.
5569 // aRequest will be null always in case 2 and 3, and sometimes in case 1.
5570 onSecurityChange(aWebProgress, aRequest, aState, _aIsSimulated) {
5571 // Don't need to do anything if the data we use to update the UI hasn't
5573 let uri = gBrowser.currentURI;
5574 let spec = uri.spec;
5575 let isSecureContext = gBrowser.securityUI.isSecureContext;
5577 this._state == aState &&
5578 this._lastLocation == spec &&
5579 this._isSecureContext === isSecureContext
5581 // Switching to a tab of the same URL doesn't change most security
5582 // information, but tab specific permissions may be different.
5583 gIdentityHandler.refreshIdentityBlock();
5586 this._state = aState;
5587 this._lastLocation = spec;
5588 this._isSecureContext = isSecureContext;
5590 // Make sure the "https" part of the URL is striked out or not,
5591 // depending on the current mixed active content blocking state.
5592 gURLBar.formatValue();
5594 // Update the identity panel, making sure we use the precursorPrincipal's
5595 // URI where appropriate, for example about:blank windows.
5596 let uriOverride = this._securityURIOverride(gBrowser.selectedBrowser);
5599 this._state |= Ci.nsIWebProgressListener.STATE_IDENTITY_ASSOCIATED;
5603 uri = Services.io.createExposableURI(uri);
5605 gIdentityHandler.updateIdentity(this._state, uri);
5608 // simulate all change notifications after switching tabs
5609 onUpdateCurrentBrowser: function XWB_onUpdateCurrentBrowser(
5615 if (FullZoom.updateBackgroundTabs) {
5616 FullZoom.onLocationChange(gBrowser.currentURI, true);
5619 CombinedStopReload.onTabSwitch();
5621 // Docshell should normally take care of hiding the tooltip, but we need to do it
5622 // ourselves for tabswitches.
5625 // Also hide tooltips for content loaded in the parent process:
5626 document.getElementById("aHTMLTooltip").hidePopup();
5628 var nsIWebProgressListener = Ci.nsIWebProgressListener;
5629 var loadingDone = aStateFlags & nsIWebProgressListener.STATE_STOP;
5630 // use a pseudo-object instead of a (potentially nonexistent) channel for getting
5631 // a correct error message - and make sure that the UI is always either in
5632 // loading (STATE_START) or done (STATE_STOP) mode
5634 gBrowser.webProgress,
5635 { URI: gBrowser.currentURI },
5637 ? nsIWebProgressListener.STATE_STOP
5638 : nsIWebProgressListener.STATE_START,
5641 // status message and progress value are undefined if we're done with loading
5645 this.onStatusChange(gBrowser.webProgress, null, 0, aMessage);
5649 var LinkTargetDisplay = {
5651 delete this.DELAY_SHOW;
5652 return (this.DELAY_SHOW = Services.prefs.getIntPref(
5653 "browser.overlink-delay"
5660 get _contextMenu() {
5661 delete this._contextMenu;
5662 return (this._contextMenu = document.getElementById(
5663 "contentAreaContextMenu"
5667 update({ hideStatusPanelImmediately = false } = {}) {
5669 this._contextMenu.state == "open" ||
5670 this._contextMenu.state == "showing"
5672 this._contextMenu.addEventListener("popuphidden", () => this.update(), {
5678 clearTimeout(this._timer);
5679 window.removeEventListener("mousemove", this, true);
5681 if (!XULBrowserWindow.overLink) {
5682 if (hideStatusPanelImmediately) {
5685 this._timer = setTimeout(this._hide.bind(this), this.DELAY_HIDE);
5690 if (StatusPanel.isVisible) {
5691 StatusPanel.update();
5693 // Let the display appear when the mouse doesn't move within the delay
5694 this._showDelayed();
5695 window.addEventListener("mousemove", this, true);
5699 handleEvent(event) {
5700 switch (event.type) {
5702 // Restart the delay since the mouse was moved
5703 clearTimeout(this._timer);
5704 this._showDelayed();
5710 this._timer = setTimeout(
5712 StatusPanel.update();
5713 window.removeEventListener("mousemove", self, true);
5721 clearTimeout(this._timer);
5723 StatusPanel.update();
5727 var CombinedStopReload = {
5728 // Try to initialize. Returns whether initialization was successful, which
5729 // may mean we had already initialized.
5730 ensureInitialized() {
5731 if (this._initialized) {
5734 if (this._destroyed) {
5738 let reload = document.getElementById("reload-button");
5739 let stop = document.getElementById("stop-button");
5740 // It's possible the stop/reload buttons have been moved to the palette.
5741 // They may be reinserted later, so we will retry initialization if/when
5742 // we get notified of document loads.
5743 if (!stop || !reload) {
5747 this._initialized = true;
5748 if (XULBrowserWindow.stopCommand.getAttribute("disabled") != "true") {
5749 reload.setAttribute("displaystop", "true");
5751 stop.addEventListener("click", this);
5753 // Removing attributes based on the observed command doesn't happen if the button
5754 // is in the palette when the command's attribute is removed (cf. bug 309953)
5755 for (let button of [stop, reload]) {
5756 if (button.hasAttribute("disabled")) {
5757 let command = document.getElementById(button.getAttribute("command"));
5758 if (!command.hasAttribute("disabled")) {
5759 button.removeAttribute("disabled");
5764 this.reload = reload;
5766 this.stopReloadContainer = this.reload.parentNode;
5767 this.timeWhenSwitchedToStop = 0;
5769 this.stopReloadContainer.addEventListener("animationend", this);
5770 this.stopReloadContainer.addEventListener("animationcancel", this);
5776 this._destroyed = true;
5778 if (!this._initialized) {
5782 this._cancelTransition();
5783 this.stop.removeEventListener("click", this);
5784 this.stopReloadContainer.removeEventListener("animationend", this);
5785 this.stopReloadContainer.removeEventListener("animationcancel", this);
5786 this.stopReloadContainer = null;
5791 handleEvent(event) {
5792 switch (event.type) {
5794 if (event.button == 0 && !this.stop.disabled) {
5795 this._stopClicked = true;
5798 case "animationcancel":
5799 case "animationend": {
5801 event.target.classList.contains("toolbarbutton-animatable-image") &&
5802 (event.animationName == "reload-to-stop" ||
5803 event.animationName == "stop-to-reload")
5805 this.stopReloadContainer.removeAttribute("animate");
5812 // Reset the time in the event of a tabswitch since the stored time
5813 // would have been associated with the previous tab, so the animation will
5814 // still run if the page has been loading until long after the tab switch.
5815 this.timeWhenSwitchedToStop = window.performance.now();
5818 switchToStop(aRequest, aWebProgress) {
5820 !this.ensureInitialized() ||
5821 !this._shouldSwitch(aRequest, aWebProgress)
5826 // Store the time that we switched to the stop button only if a request
5827 // is active. Requests are null if the switch is related to a tabswitch.
5828 // This is used to determine if we should show the stop->reload animation.
5829 if (aRequest instanceof Ci.nsIRequest) {
5830 this.timeWhenSwitchedToStop = window.performance.now();
5834 aRequest instanceof Ci.nsIRequest &&
5835 aWebProgress.isTopLevel &&
5836 aWebProgress.isLoadingDocument &&
5837 !gBrowser.tabAnimationsInProgress &&
5839 this.stopReloadContainer.closest("#nav-bar-customization-target");
5841 this._cancelTransition();
5842 if (shouldAnimate) {
5843 this.stopReloadContainer.setAttribute("animate", "true");
5845 this.stopReloadContainer.removeAttribute("animate");
5847 this.reload.setAttribute("displaystop", "true");
5850 switchToReload(aRequest, aWebProgress) {
5851 if (!this.ensureInitialized() || !this.reload.hasAttribute("displaystop")) {
5856 aRequest instanceof Ci.nsIRequest &&
5857 aWebProgress.isTopLevel &&
5858 !aWebProgress.isLoadingDocument &&
5859 !gBrowser.tabAnimationsInProgress &&
5861 this._loadTimeExceedsMinimumForAnimation() &&
5862 this.stopReloadContainer.closest("#nav-bar-customization-target");
5864 if (shouldAnimate) {
5865 this.stopReloadContainer.setAttribute("animate", "true");
5867 this.stopReloadContainer.removeAttribute("animate");
5870 this.reload.removeAttribute("displaystop");
5872 if (!shouldAnimate || this._stopClicked) {
5873 this._stopClicked = false;
5874 this._cancelTransition();
5875 this.reload.disabled =
5876 XULBrowserWindow.reloadCommand.getAttribute("disabled") == "true";
5884 // Temporarily disable the reload button to prevent the user from
5885 // accidentally reloading the page when intending to click the stop button
5886 this.reload.disabled = true;
5887 this._timer = setTimeout(
5890 self.reload.disabled =
5891 XULBrowserWindow.reloadCommand.getAttribute("disabled") == "true";
5898 _loadTimeExceedsMinimumForAnimation() {
5899 // If the time between switching to the stop button then switching to
5900 // the reload button exceeds 150ms, then we will show the animation.
5901 // If we don't know when we switched to stop (switchToStop is called
5902 // after init but before switchToReload), then we will prevent the
5903 // animation from occuring.
5905 this.timeWhenSwitchedToStop &&
5906 window.performance.now() - this.timeWhenSwitchedToStop > 150
5910 _shouldSwitch(aRequest, aWebProgress) {
5913 aRequest.originalURI &&
5914 (aRequest.originalURI.schemeIs("chrome") ||
5915 (aRequest.originalURI.schemeIs("about") &&
5916 aWebProgress.isTopLevel &&
5917 !aRequest.originalURI.spec.startsWith("about:reader")))
5925 _cancelTransition() {
5927 clearTimeout(this._timer);
5933 var TabsProgressListener = {
5934 onStateChange(aBrowser, aWebProgress, aRequest, aStateFlags, aStatus) {
5935 // Collect telemetry data about tab load times.
5937 aWebProgress.isTopLevel &&
5938 (!aRequest.originalURI || aRequest.originalURI.scheme != "about")
5940 let histogram = "FX_PAGE_LOAD_MS_2";
5941 let recordLoadTelemetry = true;
5943 if (aWebProgress.loadType & Ci.nsIDocShell.LOAD_CMD_RELOAD) {
5944 // loadType is constructed by shifting loadFlags, this is why we need to
5945 // do the same shifting here.
5946 // https://searchfox.org/mozilla-central/rev/11cfa0462a6b5d8c5e2111b8cfddcf78098f0141/docshell/base/nsDocShellLoadTypes.h#22
5947 if (aWebProgress.loadType & (kSkipCacheFlags << 16)) {
5948 histogram = "FX_PAGE_RELOAD_SKIP_CACHE_MS";
5949 } else if (aWebProgress.loadType == Ci.nsIDocShell.LOAD_CMD_RELOAD) {
5950 histogram = "FX_PAGE_RELOAD_NORMAL_MS";
5952 recordLoadTelemetry = false;
5956 let stopwatchRunning = TelemetryStopwatch.running(histogram, aBrowser);
5957 if (aStateFlags & Ci.nsIWebProgressListener.STATE_IS_WINDOW) {
5958 if (aStateFlags & Ci.nsIWebProgressListener.STATE_START) {
5959 if (stopwatchRunning) {
5960 // Oops, we're seeing another start without having noticed the previous stop.
5961 if (recordLoadTelemetry) {
5962 TelemetryStopwatch.cancel(histogram, aBrowser);
5965 if (recordLoadTelemetry) {
5966 TelemetryStopwatch.start(histogram, aBrowser);
5968 Services.telemetry.getHistogramById("FX_TOTAL_TOP_VISITS").add(true);
5970 aStateFlags & Ci.nsIWebProgressListener.STATE_STOP &&
5971 stopwatchRunning /* we won't see STATE_START events for pre-rendered tabs */
5973 if (recordLoadTelemetry) {
5974 TelemetryStopwatch.finish(histogram, aBrowser);
5975 BrowserTelemetryUtils.recordSiteOriginTelemetry(browserWindows());
5979 aStateFlags & Ci.nsIWebProgressListener.STATE_STOP &&
5980 aStatus == Cr.NS_BINDING_ABORTED &&
5981 stopwatchRunning /* we won't see STATE_START events for pre-rendered tabs */
5983 if (recordLoadTelemetry) {
5984 TelemetryStopwatch.cancel(histogram, aBrowser);
5990 onLocationChange(aBrowser, aWebProgress, aRequest, aLocationURI, aFlags) {
5991 // Filter out location changes in sub documents.
5992 if (!aWebProgress.isTopLevel) {
5996 // Some shops use pushState to move between individual products, so
5997 // the shopping code needs to be told about all of these.
5998 ShoppingSidebarManager.onLocationChange(aBrowser, aLocationURI, aFlags);
6000 // Filter out location changes caused by anchor navigation
6001 // or history.push/pop/replaceState.
6002 if (aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT) {
6003 // Reader mode cares about history.pushState and friends.
6004 // FIXME: The content process should manage this directly (bug 1445351).
6005 aBrowser.sendMessageToActor(
6008 isArticle: aBrowser.isArticle,
6015 // Only need to call locationChange if the PopupNotifications object
6016 // for this window has already been initialized (i.e. its getter no
6018 if (!Object.getOwnPropertyDescriptor(window, "PopupNotifications").get) {
6019 PopupNotifications.locationChange(aBrowser);
6022 let tab = gBrowser.getTabForBrowser(aBrowser);
6023 if (tab && tab._sharingState) {
6024 gBrowser.resetBrowserSharing(aBrowser);
6027 gBrowser.readNotificationBox(aBrowser)?.removeTransientNotifications();
6029 FullZoom.onLocationChange(aLocationURI, false, aBrowser);
6030 CaptivePortalWatcher.onLocationChange(aBrowser);
6033 onLinkIconAvailable(browser, dataURI, iconURI) {
6037 if (browser == gBrowser.selectedBrowser) {
6038 // If the "Add Search Engine" page action is in the urlbar, its image
6039 // needs to be set to the new icon, so call updateOpenSearchBadge.
6040 BrowserSearch.updateOpenSearchBadge();
6045 function nsBrowserAccess() {}
6047 nsBrowserAccess.prototype = {
6048 QueryInterface: ChromeUtils.generateQI(["nsIBrowserDOMWindow"]),
6055 aForceNotRemote = false,
6056 aUserContextId = Ci.nsIScriptSecurityManager.DEFAULT_USER_CONTEXT_ID,
6057 aOpenWindowInfo = null,
6058 aOpenerBrowser = null,
6059 aTriggeringPrincipal = null,
6063 aForceLoadInBackground = false
6065 let win, needToFocusWin;
6067 // try the current window. if we're in a popup, fall back on the most recent browser window
6068 if (window.toolbar.visible) {
6071 win = BrowserWindowTracker.getTopWindow({ private: aIsPrivate });
6072 needToFocusWin = true;
6076 // we couldn't find a suitable window, a new one needs to be opened.
6080 if (aIsExternal && (!aURI || aURI.spec == "about:blank")) {
6081 win.BrowserOpenTab(); // this also focuses the location bar
6083 return win.gBrowser.selectedBrowser;
6086 let loadInBackground = Services.prefs.getBoolPref(
6087 "browser.tabs.loadDivertedInBackground"
6089 if (aForceLoadInBackground) {
6090 loadInBackground = true;
6093 let tab = win.gBrowser.addTab(aURI ? aURI.spec : "about:blank", {
6094 triggeringPrincipal: aTriggeringPrincipal,
6095 referrerInfo: aReferrerInfo,
6096 userContextId: aUserContextId,
6097 fromExternal: aIsExternal,
6098 inBackground: loadInBackground,
6099 forceNotRemote: aForceNotRemote,
6100 openWindowInfo: aOpenWindowInfo,
6101 openerBrowser: aOpenerBrowser,
6104 skipLoad: aSkipLoad,
6106 let browser = win.gBrowser.getBrowserForTab(tab);
6108 if (needToFocusWin || (!loadInBackground && aIsExternal)) {
6115 createContentWindow(
6120 aTriggeringPrincipal,
6123 return this.getContentWindowOrOpenURI(
6128 aTriggeringPrincipal,
6134 openURI(aURI, aOpenWindowInfo, aWhere, aFlags, aTriggeringPrincipal, aCsp) {
6136 console.error("openURI should only be called with a valid URI");
6137 throw Components.Exception("", Cr.NS_ERROR_FAILURE);
6139 return this.getContentWindowOrOpenURI(
6144 aTriggeringPrincipal,
6150 getContentWindowOrOpenURI(
6155 aTriggeringPrincipal,
6159 var browsingContext = null;
6160 var isExternal = !!(aFlags & Ci.nsIBrowserDOMWindow.OPEN_EXTERNAL);
6161 var guessUserContextIdEnabled =
6163 !Services.prefs.getBoolPref(
6164 "browser.link.force_default_user_context_id_for_external_opens",
6167 var openingUserContextId =
6168 (guessUserContextIdEnabled &&
6169 URILoadingHelper.guessUserContextId(aURI)) ||
6170 Ci.nsIScriptSecurityManager.DEFAULT_USER_CONTEXT_ID;
6172 if (aOpenWindowInfo && isExternal) {
6174 "nsBrowserAccess.openURI did not expect aOpenWindowInfo to be " +
6175 "passed if the context is OPEN_EXTERNAL."
6177 throw Components.Exception("", Cr.NS_ERROR_FAILURE);
6180 if (isExternal && aURI && aURI.schemeIs("chrome")) {
6181 dump("use --chrome command-line option to load external chrome urls\n");
6185 if (aWhere == Ci.nsIBrowserDOMWindow.OPEN_DEFAULTWINDOW) {
6188 Services.prefs.prefHasUserValue(
6189 "browser.link.open_newwindow.override.external"
6192 aWhere = Services.prefs.getIntPref(
6193 "browser.link.open_newwindow.override.external"
6196 aWhere = Services.prefs.getIntPref("browser.link.open_newwindow");
6201 if (aFlags & Ci.nsIBrowserDOMWindow.OPEN_NO_REFERRER) {
6202 referrerInfo = new ReferrerInfo(Ci.nsIReferrerInfo.EMPTY, false, null);
6205 aOpenWindowInfo.parent &&
6206 aOpenWindowInfo.parent.window
6208 referrerInfo = new ReferrerInfo(
6209 aOpenWindowInfo.parent.window.document.referrerInfo.referrerPolicy,
6211 makeURI(aOpenWindowInfo.parent.window.location.href)
6214 referrerInfo = new ReferrerInfo(Ci.nsIReferrerInfo.EMPTY, true, null);
6217 let isPrivate = aOpenWindowInfo
6218 ? aOpenWindowInfo.originAttributes.privateBrowsingId != 0
6219 : PrivateBrowsingUtils.isWindowPrivate(window);
6222 case Ci.nsIBrowserDOMWindow.OPEN_NEWWINDOW:
6223 // FIXME: Bug 408379. So how come this doesn't send the
6224 // referrer like the other loads do?
6225 var url = aURI && aURI.spec;
6226 let features = "all,dialog=no";
6228 features += ",private";
6230 // Pass all params to openDialog to ensure that "url" isn't passed through
6231 // loadOneOrMoreURIs, which splits based on "|"
6233 let extraOptions = Cc[
6234 "@mozilla.org/hash-property-bag;1"
6235 ].createInstance(Ci.nsIWritablePropertyBag2);
6236 extraOptions.setPropertyAsBool("fromExternal", isExternal);
6239 AppConstants.BROWSER_CHROME_URL,
6251 aTriggeringPrincipal,
6256 // At this point, the new browser window is just starting to load, and
6257 // hasn't created the content <browser> that we should return.
6258 // If the caller of this function is originating in C++, they can pass a
6259 // callback in nsOpenWindowInfo and it will be invoked when the browsing
6260 // context for a newly opened window is ready.
6261 browsingContext = null;
6266 case Ci.nsIBrowserDOMWindow.OPEN_NEWTAB:
6267 case Ci.nsIBrowserDOMWindow.OPEN_NEWTAB_BACKGROUND: {
6268 // If we have an opener, that means that the caller is expecting access
6269 // to the nsIDOMWindow of the opened tab right away. For e10s windows,
6270 // this means forcing the newly opened browser to be non-remote so that
6271 // we can hand back the nsIDOMWindow. DocumentLoadListener will do the
6272 // job of shuttling off the newly opened browser to run in the right
6273 // process once it starts loading a URI.
6274 let forceNotRemote = aOpenWindowInfo && !aOpenWindowInfo.isRemote;
6275 let userContextId = aOpenWindowInfo
6276 ? aOpenWindowInfo.originAttributes.userContextId
6277 : openingUserContextId;
6278 let forceLoadInBackground =
6279 aWhere == Ci.nsIBrowserDOMWindow.OPEN_NEWTAB_BACKGROUND;
6280 let browser = this._openURIInNewTab(
6288 aOpenWindowInfo?.parent?.top.embedderElement,
6289 aTriggeringPrincipal,
6293 forceLoadInBackground
6296 browsingContext = browser.browsingContext;
6300 case Ci.nsIBrowserDOMWindow.OPEN_PRINT_BROWSER: {
6302 PrintUtils.handleStaticCloneCreatedForPrint(aOpenWindowInfo);
6304 browsingContext = browser.browsingContext;
6309 // OPEN_CURRENTWINDOW or an illegal value
6310 browsingContext = window.gBrowser.selectedBrowser.browsingContext;
6312 let loadFlags = Ci.nsIWebNavigation.LOAD_FLAGS_NONE;
6314 loadFlags |= Ci.nsIWebNavigation.LOAD_FLAGS_FROM_EXTERNAL;
6315 } else if (!aTriggeringPrincipal.isSystemPrincipal) {
6316 // XXX this code must be reviewed and changed when bug 1616353
6318 loadFlags |= Ci.nsIWebNavigation.LOAD_FLAGS_FIRST_LOAD;
6320 // This should ideally be able to call loadURI with the actual URI.
6321 // However, that would bypass some styles of fixup (notably Windows
6322 // paths passed as "URI"s), so this needs some further thought. It
6323 // should be addressed in bug 1815509.
6324 gBrowser.fixupAndLoadURIString(aURI.spec, {
6325 triggeringPrincipal: aTriggeringPrincipal,
6332 !Services.prefs.getBoolPref("browser.tabs.loadDivertedInBackground")
6337 return browsingContext;
6340 createContentWindowInFrame: function browser_createContentWindowInFrame(
6347 // Passing a null-URI to only create the content window,
6348 // and pass true for aSkipLoad to prevent loading of
6350 return this.getContentWindowOrOpenURIInFrame(
6360 openURIInFrame: function browser_openURIInFrame(
6367 return this.getContentWindowOrOpenURIInFrame(
6377 getContentWindowOrOpenURIInFrame:
6378 function browser_getContentWindowOrOpenURIInFrame(
6386 if (aWhere == Ci.nsIBrowserDOMWindow.OPEN_PRINT_BROWSER) {
6387 return PrintUtils.handleStaticCloneCreatedForPrint(
6388 aParams.openWindowInfo
6393 aWhere != Ci.nsIBrowserDOMWindow.OPEN_NEWTAB &&
6394 aWhere != Ci.nsIBrowserDOMWindow.OPEN_NEWTAB_BACKGROUND
6396 dump("Error: openURIInFrame can only open in new tabs or print");
6400 var isExternal = !!(aFlags & Ci.nsIBrowserDOMWindow.OPEN_EXTERNAL);
6403 aParams.openerOriginAttributes &&
6404 "userContextId" in aParams.openerOriginAttributes
6405 ? aParams.openerOriginAttributes.userContextId
6406 : Ci.nsIScriptSecurityManager.DEFAULT_USER_CONTEXT_ID;
6408 var forceLoadInBackground =
6409 aWhere == Ci.nsIBrowserDOMWindow.OPEN_NEWTAB_BACKGROUND;
6411 return this._openURIInNewTab(
6413 aParams.referrerInfo,
6418 aParams.openWindowInfo,
6419 aParams.openerBrowser,
6420 aParams.triggeringPrincipal,
6424 forceLoadInBackground
6429 return CanCloseWindow();
6433 return gBrowser.tabs.length;
6437 function showFullScreenViewContextMenuItems(popup) {
6438 for (let node of popup.querySelectorAll('[contexttype="fullscreen"]')) {
6439 node.hidden = !window.fullScreen;
6441 let autoHide = popup.querySelector(".fullscreen-context-autohide");
6443 FullScreen.updateAutohideMenuitem(autoHide);
6447 function onViewToolbarsPopupShowing(aEvent, aInsertPoint) {
6448 var popup = aEvent.target;
6449 if (popup != aEvent.currentTarget) {
6453 // triggerNode can be a nested child element of a toolbaritem.
6454 let toolbarItem = popup.triggerNode;
6455 while (toolbarItem) {
6456 let localName = toolbarItem.localName;
6457 if (localName == "toolbar") {
6461 if (localName == "toolbarpaletteitem") {
6462 toolbarItem = toolbarItem.firstElementChild;
6465 if (localName == "menupopup") {
6466 aEvent.preventDefault();
6467 aEvent.stopPropagation();
6470 let parent = toolbarItem.parentElement;
6473 parent.classList.contains("customization-target") ||
6474 parent.getAttribute("overflowfortoolbar") || // Needs to work in the overflow list as well.
6475 parent.localName == "toolbarpaletteitem" ||
6476 parent.localName == "toolbar"
6481 toolbarItem = parent;
6485 for (var i = popup.children.length - 1; i >= 0; --i) {
6486 var deadItem = popup.children[i];
6487 if (deadItem.hasAttribute("toolbarId")) {
6488 popup.removeChild(deadItem);
6492 MozXULElement.insertFTLIfNeeded("browser/toolbarContextMenu.ftl");
6493 let firstMenuItem = aInsertPoint || popup.firstElementChild;
6494 let toolbarNodes = gNavToolbox.querySelectorAll("toolbar");
6495 for (let toolbar of toolbarNodes) {
6496 if (!toolbar.hasAttribute("toolbarname")) {
6500 if (toolbar.id == "PersonalToolbar") {
6501 let menu = BookmarkingUI.buildBookmarksToolbarSubmenu(toolbar);
6502 popup.insertBefore(menu, firstMenuItem);
6504 let menuItem = document.createXULElement("menuitem");
6505 menuItem.setAttribute("id", "toggle_" + toolbar.id);
6506 menuItem.setAttribute("toolbarId", toolbar.id);
6507 menuItem.setAttribute("type", "checkbox");
6508 menuItem.setAttribute("label", toolbar.getAttribute("toolbarname"));
6509 let hidingAttribute =
6510 toolbar.getAttribute("type") == "menubar" ? "autohide" : "collapsed";
6511 menuItem.setAttribute(
6513 toolbar.getAttribute(hidingAttribute) != "true"
6515 menuItem.setAttribute("accesskey", toolbar.getAttribute("accesskey"));
6516 if (popup.id != "toolbar-context-menu") {
6517 menuItem.setAttribute("key", toolbar.getAttribute("key"));
6520 popup.insertBefore(menuItem, firstMenuItem);
6521 menuItem.addEventListener("command", onViewToolbarCommand);
6525 let moveToPanel = popup.querySelector(".customize-context-moveToPanel");
6526 let removeFromToolbar = popup.querySelector(
6527 ".customize-context-removeFromToolbar"
6529 // Show/hide fullscreen context menu items and set the
6530 // autohide item's checked state to mirror the autohide pref.
6531 showFullScreenViewContextMenuItems(popup);
6532 // View -> Toolbars menu doesn't have the moveToPanel or removeFromToolbar items.
6533 if (!moveToPanel || !removeFromToolbar) {
6537 let showTabStripItems = toolbarItem?.id == "tabbrowser-tabs";
6538 for (let node of popup.querySelectorAll(
6539 'menuitem[contexttype="toolbaritem"]'
6541 node.hidden = showTabStripItems;
6544 for (let node of popup.querySelectorAll('menuitem[contexttype="tabbar"]')) {
6545 node.hidden = !showTabStripItems;
6549 .getElementById("toolbar-context-menu")
6550 .querySelectorAll("[data-lazy-l10n-id]")
6552 el.setAttribute("data-l10n-id", el.getAttribute("data-lazy-l10n-id"));
6553 el.removeAttribute("data-lazy-l10n-id");
6556 // The "normal" toolbar items menu separator is hidden because it's unused
6557 // when hiding the "moveToPanel" and "removeFromToolbar" items on flexible
6558 // space items. But we need to ensure its hidden state is reset in the case
6559 // the context menu is subsequently opened on a non-flexible space item.
6560 let menuSeparator = document.getElementById("toolbarItemsMenuSeparator");
6561 menuSeparator.hidden = false;
6563 document.getElementById("toolbarNavigatorItemsMenuSeparator").hidden =
6567 !CustomizationHandler.isCustomizing() &&
6568 CustomizableUI.isSpecialWidget(toolbarItem?.id || "")
6570 moveToPanel.hidden = true;
6571 removeFromToolbar.hidden = true;
6572 menuSeparator.hidden = !showTabStripItems;
6575 if (showTabStripItems) {
6576 let multipleTabsSelected = !!gBrowser.multiSelectedTabsCount;
6577 document.getElementById("toolbar-context-bookmarkSelectedTabs").hidden =
6578 !multipleTabsSelected;
6579 document.getElementById("toolbar-context-bookmarkSelectedTab").hidden =
6580 multipleTabsSelected;
6581 document.getElementById("toolbar-context-reloadSelectedTabs").hidden =
6582 !multipleTabsSelected;
6583 document.getElementById("toolbar-context-reloadSelectedTab").hidden =
6584 multipleTabsSelected;
6585 document.getElementById("toolbar-context-selectAllTabs").disabled =
6586 gBrowser.allTabsSelected();
6587 document.getElementById("toolbar-context-undoCloseTab").disabled =
6588 SessionStore.getClosedTabCount() == 0;
6593 toolbarItem?.id && CustomizableUI.isWidgetRemovable(toolbarItem);
6595 if (CustomizableUI.isSpecialWidget(toolbarItem.id)) {
6596 moveToPanel.setAttribute("disabled", true);
6598 moveToPanel.removeAttribute("disabled");
6600 removeFromToolbar.removeAttribute("disabled");
6602 moveToPanel.setAttribute("disabled", true);
6603 removeFromToolbar.setAttribute("disabled", true);
6607 function onViewToolbarCommand(aEvent) {
6608 let node = aEvent.originalTarget;
6612 if (node.dataset.bookmarksToolbarVisibility) {
6613 isVisible = node.dataset.visibilityEnum;
6614 toolbarId = "PersonalToolbar";
6615 menuId = node.parentNode.parentNode.parentNode.id;
6616 Services.prefs.setCharPref(
6617 "browser.toolbars.bookmarks.visibility",
6621 menuId = node.parentNode.id;
6622 toolbarId = node.getAttribute("toolbarId");
6623 isVisible = node.getAttribute("checked") == "true";
6625 CustomizableUI.setToolbarVisibility(toolbarId, isVisible);
6626 BrowserUsageTelemetry.recordToolbarVisibility(toolbarId, isVisible, menuId);
6629 function setToolbarVisibility(
6635 let hidingAttribute;
6636 if (toolbar.getAttribute("type") == "menubar") {
6637 hidingAttribute = "autohide";
6638 if (AppConstants.platform == "linux") {
6639 Services.prefs.setBoolPref("ui.key.menuAccessKeyFocuses", !isVisible);
6642 hidingAttribute = "collapsed";
6645 if (toolbar == BookmarkingUI.toolbar) {
6646 // For the bookmarks toolbar, we need to persist state before toggling
6647 // the visibility in this window, because the state can be different
6648 // (newtab vs never or always) even when that won't change visibility
6652 if (typeof isVisible == "string") {
6653 prefValue = isVisible;
6655 prefValue = isVisible ? "always" : "never";
6657 Services.prefs.setCharPref(
6658 "browser.toolbars.bookmarks.visibility",
6663 const overlapAttr = "BookmarksToolbarOverlapsBrowser";
6664 switch (isVisible) {
6668 document.documentElement.toggleAttribute(overlapAttr, false);
6673 document.documentElement.toggleAttribute(overlapAttr, false);
6677 let currentURI = gBrowser?.currentURI;
6678 if (!gBrowserInit.domContentLoaded) {
6679 let uriToLoad = gBrowserInit.uriToLoadPromise;
6681 if (Array.isArray(uriToLoad)) {
6682 // We only care about the first tab being loaded
6683 uriToLoad = uriToLoad[0];
6686 currentURI = Services.io.newURI(uriToLoad);
6690 isVisible = BookmarkingUI.isOnNewTabPage(currentURI);
6691 document.documentElement.toggleAttribute(overlapAttr, isVisible);
6696 if (toolbar.getAttribute(hidingAttribute) == (!isVisible).toString()) {
6697 // If this call will not result in a visibility change, return early
6698 // since dispatching toolbarvisibilitychange will cause views to get rebuilt.
6702 toolbar.classList.toggle("instant", !animated);
6703 toolbar.setAttribute(hidingAttribute, !isVisible);
6704 // For the bookmarks toolbar, we will have saved state above. For other
6705 // toolbars, we need to do it after setting the attribute, or we might
6706 // save the wrong state.
6707 if (persist && toolbar.id != "PersonalToolbar") {
6708 Services.xulStore.persist(toolbar, hidingAttribute);
6717 let event = new CustomEvent("toolbarvisibilitychange", eventParams);
6718 toolbar.dispatchEvent(event);
6721 function updateToggleControlLabel(control) {
6722 if (!control.hasAttribute("label-checked")) {
6726 if (!control.hasAttribute("label-unchecked")) {
6727 control.setAttribute("label-unchecked", control.getAttribute("label"));
6729 let prefix = control.getAttribute("checked") == "true" ? "" : "un";
6730 control.setAttribute("label", control.getAttribute(`label-${prefix}checked`));
6733 var TabletModeUpdater = {
6735 if (AppConstants.platform == "win") {
6736 this.update(WindowsUIUtils.inTabletMode);
6737 Services.obs.addObserver(this, "tablet-mode-change");
6742 if (AppConstants.platform == "win") {
6743 Services.obs.removeObserver(this, "tablet-mode-change");
6747 observe(subject, topic, data) {
6748 this.update(data == "tablet-mode");
6751 update(isInTabletMode) {
6752 let wasInTabletMode = document.documentElement.hasAttribute("tabletmode");
6753 if (isInTabletMode) {
6754 document.documentElement.setAttribute("tabletmode", "true");
6756 document.documentElement.removeAttribute("tabletmode");
6758 if (wasInTabletMode != isInTabletMode) {
6759 gUIDensity.update();
6764 var gTabletModePageCounter = {
6767 this.enabled = AppConstants.platform == "win";
6768 if (!this.enabled) {
6769 this.inc = () => {};
6772 this.inc = this._realInc;
6779 let inTabletMode = document.documentElement.hasAttribute("tabletmode");
6780 this[inTabletMode ? "_tabletCount" : "_desktopCount"]++;
6785 let histogram = Services.telemetry.getKeyedHistogramById(
6786 "FX_TABLETMODE_PAGE_LOAD"
6788 histogram.add("tablet", this._tabletCount);
6789 histogram.add("desktop", this._desktopCount);
6794 function displaySecurityInfo() {
6795 BrowserPageInfo(null, "securityTab");
6798 // Updates the UI density (for touch and compact mode) based on the uidensity pref.
6803 uiDensityPref: "browser.uidensity",
6804 autoTouchModePref: "browser.touchmode.auto",
6808 Services.prefs.addObserver(this.uiDensityPref, this);
6809 Services.prefs.addObserver(this.autoTouchModePref, this);
6813 Services.prefs.removeObserver(this.uiDensityPref, this);
6814 Services.prefs.removeObserver(this.autoTouchModePref, this);
6817 observe(aSubject, aTopic, aPrefName) {
6819 aTopic != "nsPref:changed" ||
6820 (aPrefName != this.uiDensityPref && aPrefName != this.autoTouchModePref)
6828 getCurrentDensity() {
6829 // Automatically override the uidensity to touch in Windows tablet mode.
6831 AppConstants.platform == "win" &&
6832 WindowsUIUtils.inTabletMode &&
6833 Services.prefs.getBoolPref(this.autoTouchModePref)
6835 return { mode: this.MODE_TOUCH, overridden: true };
6838 mode: Services.prefs.getIntPref(this.uiDensityPref),
6845 mode = this.getCurrentDensity().mode;
6848 let docs = [document.documentElement];
6849 let shouldUpdateSidebar = SidebarUI.initialized && SidebarUI.isOpen;
6850 if (shouldUpdateSidebar) {
6851 docs.push(SidebarUI.browser.contentDocument.documentElement);
6853 for (let doc of docs) {
6855 case this.MODE_COMPACT:
6856 doc.setAttribute("uidensity", "compact");
6858 case this.MODE_TOUCH:
6859 doc.setAttribute("uidensity", "touch");
6862 doc.removeAttribute("uidensity");
6866 if (shouldUpdateSidebar) {
6867 let tree = SidebarUI.browser.contentDocument.querySelector(
6868 ".sidebar-placesTree"
6871 // Tree items don't update their styles without changing some property on the
6872 // parent tree element, like background-color or border. See bug 1407399.
6873 tree.style.border = "1px";
6874 tree.style.border = "";
6878 gBrowser.tabContainer.uiDensityChanged();
6879 gURLBar.updateLayoutBreakout();
6883 const nodeToTooltipMap = {
6884 "bookmarks-menu-button": "bookmarksMenuButton.tooltip",
6885 "context-reload": "reloadButton.tooltip",
6886 "context-stop": "stopButton.tooltip",
6887 "downloads-button": "downloads.tooltip",
6888 "fullscreen-button": "fullscreenButton.tooltip",
6889 "appMenu-fullscreen-button2": "fullscreenButton.tooltip",
6890 "new-window-button": "newWindowButton.tooltip",
6891 "new-tab-button": "newTabButton.tooltip",
6892 "tabs-newtab-button": "newTabButton.tooltip",
6893 "reload-button": "reloadButton.tooltip",
6894 "stop-button": "stopButton.tooltip",
6895 "urlbar-zoom-button": "urlbar-zoom-button.tooltip",
6896 "appMenu-zoomEnlarge-button2": "zoomEnlarge-button.tooltip",
6897 "appMenu-zoomReset-button2": "zoomReset-button.tooltip",
6898 "appMenu-zoomReduce-button2": "zoomReduce-button.tooltip",
6899 "reader-mode-button": "reader-mode-button.tooltip",
6900 "reader-mode-button-icon": "reader-mode-button.tooltip",
6902 const nodeToShortcutMap = {
6903 "bookmarks-menu-button": "manBookmarkKb",
6904 "context-reload": "key_reload",
6905 "context-stop": "key_stop",
6906 "downloads-button": "key_openDownloads",
6907 "fullscreen-button": "key_enterFullScreen",
6908 "appMenu-fullscreen-button2": "key_enterFullScreen",
6909 "new-window-button": "key_newNavigator",
6910 "new-tab-button": "key_newNavigatorTab",
6911 "tabs-newtab-button": "key_newNavigatorTab",
6912 "reload-button": "key_reload",
6913 "stop-button": "key_stop",
6914 "urlbar-zoom-button": "key_fullZoomReset",
6915 "appMenu-zoomEnlarge-button2": "key_fullZoomEnlarge",
6916 "appMenu-zoomReset-button2": "key_fullZoomReset",
6917 "appMenu-zoomReduce-button2": "key_fullZoomReduce",
6918 "reader-mode-button": "key_toggleReaderMode",
6919 "reader-mode-button-icon": "key_toggleReaderMode",
6922 const gDynamicTooltipCache = new Map();
6923 function GetDynamicShortcutTooltipText(nodeId) {
6924 if (!gDynamicTooltipCache.has(nodeId) && nodeId in nodeToTooltipMap) {
6925 let strId = nodeToTooltipMap[nodeId];
6927 if (nodeId in nodeToShortcutMap) {
6928 let shortcutId = nodeToShortcutMap[nodeId];
6929 let shortcut = document.getElementById(shortcutId);
6931 args.push(ShortcutUtils.prettifyShortcut(shortcut));
6934 gDynamicTooltipCache.set(
6936 gNavigatorBundle.getFormattedString(strId, args)
6939 return gDynamicTooltipCache.get(nodeId);
6942 function UpdateDynamicShortcutTooltipText(aTooltip) {
6944 aTooltip.triggerNode.id || aTooltip.triggerNode.getAttribute("anonid");
6945 aTooltip.setAttribute("label", GetDynamicShortcutTooltipText(nodeId));
6949 * - [ Dependencies ] ---------------------------------------------------------
6950 * utilityOverlay.js:
6955 * Extracts linkNode and href for the current click target.
6959 * @return [href, linkNode].
6961 * @note linkNode will be null if the click wasn't on an anchor
6962 * element (or XLink).
6964 function hrefAndLinkNodeForClickEvent(event) {
6965 function isHTMLLink(aNode) {
6966 // Be consistent with what nsContextMenu.js does.
6968 (HTMLAnchorElement.isInstance(aNode) && aNode.href) ||
6969 (HTMLAreaElement.isInstance(aNode) && aNode.href) ||
6970 HTMLLinkElement.isInstance(aNode)
6974 let node = event.composedTarget;
6975 while (node && !isHTMLLink(node)) {
6976 node = node.flattenedTreeParentNode;
6980 return [node.href, node];
6983 // If there is no linkNode, try simple XLink.
6985 node = event.composedTarget;
6986 while (node && !href) {
6988 node.nodeType == Node.ELEMENT_NODE &&
6989 (node.localName == "a" ||
6990 node.namespaceURI == "http://www.w3.org/1998/Math/MathML")
6993 node.getAttribute("href") ||
6994 node.getAttributeNS("http://www.w3.org/1999/xlink", "href");
6997 baseURI = node.baseURI;
7001 node = node.flattenedTreeParentNode;
7004 // In case of XLink, we don't return the node we got href from since
7005 // callers expect <a>-like elements.
7006 return [href ? makeURLAbsolute(baseURI, href) : null, null];
7010 * Called whenever the user clicks in the content area.
7014 * @param isPanelClick
7015 * Whether the event comes from an extension panel.
7016 * @note default event is prevented if the click is handled.
7018 function contentAreaClick(event, isPanelClick) {
7019 if (!event.isTrusted || event.defaultPrevented || event.button != 0) {
7023 let [href, linkNode] = hrefAndLinkNodeForClickEvent(event);
7025 // Not a link, handle middle mouse navigation.
7027 event.button == 1 &&
7028 Services.prefs.getBoolPref("middlemouse.contentLoadURL") &&
7029 !Services.prefs.getBoolPref("general.autoScroll")
7031 middleMousePaste(event);
7032 event.preventDefault();
7037 // This code only applies if we have a linkNode (i.e. clicks on real anchor
7038 // elements, as opposed to XLink).
7041 event.button == 0 &&
7047 // An extension panel's links should target the main content area. Do this
7048 // if no modifier keys are down and if there's no target or the target
7049 // equals _main (the IE convention) or _content (the Mozilla convention).
7050 let target = linkNode.target;
7051 let mainTarget = !target || target == "_content" || target == "_main";
7052 if (isPanelClick && mainTarget) {
7053 // javascript and data links should be executed in the current browser.
7055 linkNode.getAttribute("onclick") ||
7056 href.startsWith("javascript:") ||
7057 href.startsWith("data:")
7063 urlSecurityCheck(href, linkNode.ownerDocument.nodePrincipal);
7065 // Prevent loading unsecure destinations.
7066 event.preventDefault();
7070 openLinkIn(href, "current", {
7071 allowThirdPartyFixup: false,
7073 event.preventDefault();
7078 handleLinkClick(event, href, linkNode);
7080 // Mark the page as a user followed link. This is done so that history can
7081 // distinguish automatic embed visits from user activated ones. For example
7082 // pages loaded in frames are embed visits and lost with the session, while
7083 // visits across frames should be preserved.
7085 if (!PrivateBrowsingUtils.isWindowPrivate(window)) {
7086 PlacesUIUtils.markPageAsFollowedLink(href);
7089 /* Skip invalid URIs. */
7094 * Handles clicks on links.
7096 * @return true if the click event was handled, false otherwise.
7098 function handleLinkClick(event, href, linkNode) {
7099 if (event.button == 2) {
7104 var where = whereToOpenLink(event);
7105 if (where == "current") {
7109 var doc = event.target.ownerDocument;
7110 let referrerInfo = Cc["@mozilla.org/referrer-info;1"].createInstance(
7114 referrerInfo.initWithElement(linkNode);
7116 referrerInfo.initWithDocument(doc);
7119 if (where == "save") {
7123 linkNode ? gatherTextUnder(linkNode) : "",
7128 doc.cookieJarSettings,
7131 event.preventDefault();
7135 let frameID = WebNavigationFrames.getFrameId(doc.defaultView);
7137 urlSecurityCheck(href, doc.nodePrincipal);
7139 charset: doc.characterSet,
7141 originPrincipal: doc.nodePrincipal,
7142 originStoragePrincipal: doc.effectiveStoragePrincipal,
7143 triggeringPrincipal: doc.nodePrincipal,
7148 // The new tab/window must use the same userContextId
7149 if (doc.nodePrincipal.originAttributes.userContextId) {
7150 params.userContextId = doc.nodePrincipal.originAttributes.userContextId;
7153 openLinkIn(href, where, params);
7154 event.preventDefault();
7159 * Handles paste on middle mouse clicks.
7161 * @param event {Event | Object} Event or JSON object.
7163 function middleMousePaste(event) {
7164 let clipboard = readFromClipboard();
7169 // Strip embedded newlines and surrounding whitespace, to match the URL
7170 // bar's behavior (stripsurroundingwhitespace)
7171 clipboard = clipboard.replace(/\s*\n\s*/g, "");
7173 clipboard = UrlbarUtils.stripUnsafeProtocolOnPaste(clipboard);
7175 // if it's not the current tab, we don't need to do anything because the
7176 // browser doesn't exist.
7177 let where = whereToOpenLink(event, true, false);
7178 let lastLocationChange;
7179 if (where == "current") {
7180 lastLocationChange = gBrowser.selectedBrowser.lastLocationChange;
7183 UrlbarUtils.getShortcutOrURIAndPostData(clipboard).then(data => {
7192 UrlbarUtils.addToUrlbarHistory(data.url, window);
7194 // Things may go wrong when adding url to session history,
7195 // but don't let that interfere with the loading of the url.
7200 where != "current" ||
7201 lastLocationChange == gBrowser.selectedBrowser.lastLocationChange
7203 openUILink(data.url, event, {
7205 allowInheritPrincipal: data.mayInheritPrincipal,
7206 triggeringPrincipal: gBrowser.selectedBrowser.contentPrincipal,
7207 csp: gBrowser.selectedBrowser.csp,
7212 if (Event.isInstance(event)) {
7213 event.stopPropagation();
7217 // handleDroppedLink has the following 2 overloads:
7218 // handleDroppedLink(event, url, name, triggeringPrincipal)
7219 // handleDroppedLink(event, links, triggeringPrincipal)
7220 function handleDroppedLink(
7223 nameOrTriggeringPrincipal,
7227 if (Array.isArray(urlOrLinks)) {
7229 triggeringPrincipal = nameOrTriggeringPrincipal;
7231 links = [{ url: urlOrLinks, nameOrTriggeringPrincipal, type: "" }];
7234 let lastLocationChange = gBrowser.selectedBrowser.lastLocationChange;
7236 let userContextId = gBrowser.selectedBrowser.getAttribute("usercontextid");
7238 // event is null if links are dropped in content process.
7239 // inBackground should be false, as it's loading into current browser.
7240 let inBackground = false;
7242 inBackground = Services.prefs.getBoolPref("browser.tabs.loadInBackground");
7243 if (event.shiftKey) {
7244 inBackground = !inBackground;
7248 (async function () {
7251 Services.prefs.getIntPref("browser.tabs.maxOpenBeforeWarn")
7253 // Sync dialog cannot be used inside drop event handler.
7254 let answer = await OpenInTabsUtils.promiseConfirmOpenInTabs(
7265 for (let link of links) {
7266 let data = await UrlbarUtils.getShortcutOrURIAndPostData(link.url);
7267 urls.push(data.url);
7268 postDatas.push(data.postData);
7270 if (lastLocationChange == gBrowser.selectedBrowser.lastLocationChange) {
7271 gBrowser.loadTabs(urls, {
7274 allowThirdPartyFixup: false,
7277 triggeringPrincipal,
7282 // If links are dropped in content process, event.preventDefault() should be
7283 // called in content process.
7285 // Keep the event from being handled by the dragDrop listeners
7286 // built-in to gecko if they happen to be above us.
7287 event.preventDefault();
7291 function BrowserForceEncodingDetection() {
7292 gBrowser.selectedBrowser.forceEncodingDetection();
7293 BrowserReloadWithFlags(Ci.nsIWebNavigation.LOAD_FLAGS_CHARSET_CHANGE);
7296 var ToolbarContextMenu = {
7297 updateDownloadsAutoHide(popup) {
7298 let checkbox = document.getElementById(
7299 "toolbar-context-autohide-downloads-button"
7302 popup.triggerNode &&
7303 ["downloads-button", "wrapper-downloads-button"].includes(
7304 popup.triggerNode.id
7306 checkbox.hidden = !isDownloads;
7307 if (DownloadsButton.autoHideDownloadsButton) {
7308 checkbox.setAttribute("checked", "true");
7310 checkbox.removeAttribute("checked");
7314 onDownloadsAutoHideChange(event) {
7315 let autoHide = event.target.getAttribute("checked") == "true";
7316 Services.prefs.setBoolPref("browser.download.autohideButton", autoHide);
7319 updateDownloadsAlwaysOpenPanel(popup) {
7320 let separator = document.getElementById(
7321 "toolbarDownloadsAnchorMenuSeparator"
7323 let checkbox = document.getElementById(
7324 "toolbar-context-always-open-downloads-panel"
7327 popup.triggerNode &&
7328 ["downloads-button", "wrapper-downloads-button"].includes(
7329 popup.triggerNode.id
7331 separator.hidden = checkbox.hidden = !isDownloads;
7333 ? checkbox.setAttribute("checked", "true")
7334 : checkbox.removeAttribute("checked");
7337 onDownloadsAlwaysOpenPanelChange(event) {
7338 let alwaysOpen = event.target.getAttribute("checked") == "true";
7339 Services.prefs.setBoolPref("browser.download.alwaysOpenPanel", alwaysOpen);
7342 _getUnwrappedTriggerNode(popup) {
7343 // Toolbar buttons are wrapped in customize mode. Unwrap if necessary.
7344 let { triggerNode } = popup;
7345 if (triggerNode && gCustomizeMode.isWrappedToolbarItem(triggerNode)) {
7346 return triggerNode.firstElementChild;
7351 _getExtensionId(popup) {
7352 let node = this._getUnwrappedTriggerNode(popup);
7353 return node && node.getAttribute("data-extensionid");
7356 _getWidgetId(popup) {
7357 let node = this._getUnwrappedTriggerNode(popup);
7358 return node?.closest(".unified-extensions-item")?.id;
7361 async updateExtension(popup, event) {
7362 let removeExtension = popup.querySelector(
7363 ".customize-context-removeExtension"
7365 let manageExtension = popup.querySelector(
7366 ".customize-context-manageExtension"
7368 let reportExtension = popup.querySelector(
7369 ".customize-context-reportExtension"
7371 let pinToToolbar = popup.querySelector(".customize-context-pinToToolbar");
7372 let separator = reportExtension.nextElementSibling;
7373 let id = this._getExtensionId(popup);
7374 let addon = id && (await AddonManager.getAddonByID(id));
7376 for (let element of [removeExtension, manageExtension, separator]) {
7377 element.hidden = !addon;
7381 pinToToolbar.hidden = !addon;
7384 reportExtension.hidden = !addon || !gAddonAbuseReportEnabled;
7387 popup.querySelector(".customize-context-moveToPanel").hidden = true;
7388 popup.querySelector(".customize-context-removeFromToolbar").hidden = true;
7391 let widgetId = this._getWidgetId(popup);
7393 let area = CustomizableUI.getPlacementOfWidget(widgetId).area;
7394 let inToolbar = area != CustomizableUI.AREA_ADDONS;
7395 pinToToolbar.setAttribute("checked", inToolbar);
7399 removeExtension.disabled = !(
7400 addon.permissions & AddonManager.PERM_CAN_UNINSTALL
7403 if (event?.target?.id === "toolbar-context-menu") {
7404 ExtensionsUI.originControlsMenu(popup, id);
7409 async removeExtensionForContextAction(popup) {
7410 let id = this._getExtensionId(popup);
7411 await BrowserAddonUI.removeAddon(id, "browserAction");
7414 async reportExtensionForContextAction(popup, reportEntryPoint) {
7415 let id = this._getExtensionId(popup);
7416 await BrowserAddonUI.reportAddon(id, reportEntryPoint);
7419 async openAboutAddonsForContextAction(popup) {
7420 let id = this._getExtensionId(popup);
7421 await BrowserAddonUI.manageAddon(id, "browserAction");
7425 // Note that this is also called from non-browser windows on OSX, which do
7426 // share menu items but not much else. See nonbrowser-mac.js.
7427 var BrowserOffline = {
7430 // BrowserOffline Public Methods
7432 if (!this._uiElement) {
7433 this._uiElement = document.getElementById("cmd_toggleOfflineStatus");
7436 Services.obs.addObserver(this, "network:offline-status-changed");
7438 this._updateOfflineUI(Services.io.offline);
7440 this._inited = true;
7445 Services.obs.removeObserver(this, "network:offline-status-changed");
7449 toggleOfflineStatus() {
7450 var ioService = Services.io;
7452 if (!ioService.offline && !this._canGoOffline()) {
7453 this._updateOfflineUI(false);
7457 ioService.offline = !ioService.offline;
7461 observe(aSubject, aTopic) {
7462 if (aTopic != "network:offline-status-changed") {
7466 // This notification is also received because of a loss in connectivity,
7467 // which we ignore by updating the UI to the current value of io.offline
7468 this._updateOfflineUI(Services.io.offline);
7471 // BrowserOffline Implementation Methods
7474 var cancelGoOffline = Cc["@mozilla.org/supports-PRBool;1"].createInstance(
7475 Ci.nsISupportsPRBool
7477 Services.obs.notifyObservers(cancelGoOffline, "offline-requested");
7479 // Something aborted the quit process.
7480 if (cancelGoOffline.data) {
7489 _updateOfflineUI(aOffline) {
7490 var offlineLocked = Services.prefs.prefIsLocked("network.online");
7491 if (offlineLocked) {
7492 this._uiElement.setAttribute("disabled", "true");
7495 this._uiElement.setAttribute("checked", aOffline);
7499 var CanvasPermissionPromptHelper = {
7500 _permissionsPrompt: "canvas-permissions-prompt",
7501 _permissionsPromptHideDoorHanger: "canvas-permissions-prompt-hide-doorhanger",
7502 _notificationIcon: "canvas-notification-icon",
7505 Services.obs.addObserver(this, this._permissionsPrompt);
7506 Services.obs.addObserver(this, this._permissionsPromptHideDoorHanger);
7510 Services.obs.removeObserver(this, this._permissionsPrompt);
7511 Services.obs.removeObserver(this, this._permissionsPromptHideDoorHanger);
7514 // aSubject is an nsIBrowser (e10s) or an nsIDOMWindow (non-e10s).
7515 // aData is an Origin string.
7516 observe(aSubject, aTopic, aData) {
7518 aTopic != this._permissionsPrompt &&
7519 aTopic != this._permissionsPromptHideDoorHanger
7525 if (aSubject instanceof Ci.nsIDOMWindow) {
7526 browser = aSubject.docShell.chromeEventHandler;
7531 if (gBrowser.selectedBrowser !== browser) {
7532 // Must belong to some other window.
7536 let message = gNavigatorBundle.getFormattedString(
7537 "canvas.siteprompt2",
7543 Services.scriptSecurityManager.createContentPrincipalFromOrigin(aData);
7545 function setCanvasPermission(aPerm, aPersistent) {
7546 Services.perms.addFromPrincipal(
7551 ? Ci.nsIPermissionManager.EXPIRE_NEVER
7552 : Ci.nsIPermissionManager.EXPIRE_SESSION
7557 label: gNavigatorBundle.getString("canvas.allow2"),
7558 accessKey: gNavigatorBundle.getString("canvas.allow2.accesskey"),
7560 setCanvasPermission(
7561 Ci.nsIPermissionManager.ALLOW_ACTION,
7562 state && state.checkboxChecked
7567 let secondaryActions = [
7569 label: gNavigatorBundle.getString("canvas.block"),
7570 accessKey: gNavigatorBundle.getString("canvas.block.accesskey"),
7572 setCanvasPermission(
7573 Ci.nsIPermissionManager.DENY_ACTION,
7574 state && state.checkboxChecked
7581 // In PB mode, we don't want the "always remember" checkbox
7582 show: !PrivateBrowsingUtils.isWindowPrivate(window),
7584 if (checkbox.show) {
7585 checkbox.checked = true;
7586 checkbox.label = gBrowserBundle.GetStringFromName("canvas.remember2");
7591 name: principal.host,
7593 Services.urlFormatter.formatURLPref("app.support.baseURL") +
7594 "fingerprint-permission",
7595 dismissed: aTopic == this._permissionsPromptHideDoorHanger,
7597 if (e == "showing") {
7598 this.browser.ownerDocument.getElementById(
7599 "canvas-permissions-prompt-warning"
7600 ).textContent = gBrowserBundle.GetStringFromName(
7601 "canvas.siteprompt2.warning"
7606 PopupNotifications.show(
7608 this._permissionsPrompt,
7610 this._notificationIcon,
7618 var WebAuthnPromptHelper = {
7619 _icon: "webauthn-notification-icon",
7620 _topic: "webauthn-prompt",
7622 // The current notification, if any. The U2F manager is a singleton, we will
7623 // never allow more than one active request. And thus we'll never have more
7624 // than one notification either.
7627 // The current transaction ID. Will be checked when we're notified of the
7628 // cancellation of an ongoing WebAuthhn request.
7631 // Translation object
7635 this._l10n = new Localization(["browser/webauthnDialog.ftl"], true);
7636 Services.obs.addObserver(this, this._topic);
7640 Services.obs.removeObserver(this, this._topic);
7643 observe(aSubject, aTopic, aData) {
7645 case "fullscreen-nav-toolbox":
7646 // Prevent the navigation toolbox from being hidden while a WebAuthn
7647 // prompt is visible.
7648 if (aData == "hidden" && this._tid != 0) {
7649 FullScreen.showNavToolbox();
7652 case "fullscreen-painted":
7653 // Prevent DOM elements from going fullscreen while a WebAuthn
7655 if (this._tid != 0) {
7656 FullScreen.exitDomFullScreen();
7664 // aTopic is equal to this._topic
7666 let data = JSON.parse(aData);
7668 // If we receive a cancel, it might be a WebAuthn prompt starting in another
7669 // window, and the other window's browsing context will send out the
7670 // cancellations, so any cancel action we get should prompt us to cancel.
7671 if (data.prompt.type == "cancel") {
7677 data.browsingContextId !== gBrowser.selectedBrowser.browsingContext.id
7679 // Must belong to some other window.
7683 let mgr = Cc["@mozilla.org/webauthn/service;1"].getService(
7684 Ci.nsIWebAuthnService
7687 if (data.prompt.type == "presence") {
7688 this.presence_required(mgr, data);
7689 } else if (data.prompt.type == "register-direct") {
7690 this.registerDirect(mgr, data);
7691 } else if (data.prompt.type == "pin-required") {
7692 this.pin_required(mgr, false, data);
7693 } else if (data.prompt.type == "pin-invalid") {
7694 this.pin_required(mgr, true, data);
7695 } else if (data.prompt.type == "select-sign-result") {
7696 this.select_sign_result(mgr, data);
7697 } else if (data.prompt.type == "already-registered") {
7702 "alreadyRegistered",
7703 "webauthn.alreadyRegisteredPrompt"
7705 } else if (data.prompt.type == "select-device") {
7711 "webauthn.selectDevicePrompt"
7713 } else if (data.prompt.type == "pin-auth-blocked") {
7719 "webauthn.pinAuthBlockedPrompt"
7721 } else if (data.prompt.type == "uv-blocked") {
7727 "webauthn.uvBlockedPrompt"
7729 } else if (data.prompt.type == "uv-invalid") {
7730 let retriesLeft = data.prompt.retries;
7732 if (retriesLeft == 0) {
7733 // We can skip that because it will either be replaced
7734 // by uv-blocked or by PIN-prompt
7736 } else if (retriesLeft < 0) {
7737 dialogText = this._l10n.formatValueSync(
7738 "webauthn-uv-invalid-short-prompt"
7741 dialogText = this._l10n.formatValueSync(
7742 "webauthn-uv-invalid-long-prompt",
7746 let mainAction = this.buildCancelAction(mgr, data.tid);
7747 this.show_formatted_msg(data.tid, "uvInvalid", dialogText, mainAction);
7748 } else if (data.prompt.type == "device-blocked") {
7754 "webauthn.deviceBlockedPrompt"
7756 } else if (data.prompt.type == "pin-not-set") {
7762 "webauthn.pinNotSetPrompt"
7767 prompt_for_password(origin, wasInvalid, retriesLeft, aPassword) {
7771 dialogText = this._l10n.formatValueSync("webauthn-pin-required-prompt");
7772 } else if (retriesLeft < 0 || retriesLeft > 3) {
7773 // The token will need to be power cycled after three incorrect attempts,
7774 // so we show a short error message that does not include retriesLeft. It
7775 // would be confusing to display retriesLeft at this point, as the user
7776 // will feel that they only get three attempts.
7777 dialogText = this._l10n.formatValueSync(
7778 "webauthn-pin-invalid-short-prompt"
7781 // The user is close to having their PIN permanently blocked. Show a more
7782 // severe warning that includes the retriesLeft counter.
7783 dialogText = this._l10n.formatValueSync(
7784 "webauthn-pin-invalid-long-prompt",
7789 let res = Services.prompt.promptPasswordBC(
7790 gBrowser.selectedBrowser.browsingContext,
7791 Services.prompt.MODAL_TYPE_TAB,
7799 select_sign_result(mgr, { origin, tid, prompt: { entities } }) {
7800 let unknownAccount = this._l10n.formatValueSync(
7801 "webauthn-select-sign-result-unknown-account"
7803 let secondaryActions = [];
7804 for (let i = 0; i < entities.length; i++) {
7805 let label = entities[i].name ?? unknownAccount;
7806 secondaryActions.push({
7808 accessKey: i.toString(),
7810 mgr.selectionCallback(tid, i);
7814 let mainAction = this.buildCancelAction(mgr, tid);
7815 let options = { escAction: "buttoncommand" };
7818 "select-sign-result",
7819 "webauthn.selectSignResultPrompt",
7827 pin_required(mgr, wasInvalid, { origin, tid, prompt: { retries } }) {
7828 let aPassword = Object.create(null); // create a "null" object
7829 let res = this.prompt_for_password(origin, wasInvalid, retries, aPassword);
7831 mgr.pinCallback(tid, aPassword.value);
7837 presence_required(mgr, { origin, tid }) {
7838 let mainAction = this.buildCancelAction(mgr, tid);
7839 let options = { escAction: "buttoncommand" };
7840 let secondaryActions = [];
7841 let message = "webauthn.userPresencePrompt";
7853 registerDirect(mgr, { origin, tid }) {
7854 let mainAction = this.buildProceedAction(mgr, tid);
7855 let secondaryActions = [this.buildCancelAction(mgr, tid)];
7858 Services.urlFormatter.formatURLPref("app.support.baseURL") +
7859 "webauthn-direct-attestation";
7864 label: gNavigatorBundle.getString("webauthn.anonymize"),
7866 hintText: "webauthn.registerDirectPromptHint",
7871 "webauthn.registerDirectPrompt3",
7879 show_info(mgr, origin, tid, id, stringId) {
7880 let mainAction = this.buildCancelAction(mgr, tid);
7881 this.show(tid, id, stringId, origin, mainAction);
7890 secondaryActions = [],
7893 let brandShortName = document
7894 .getElementById("bundle_brand")
7895 .getString("brandShortName");
7896 let message = gNavigatorBundle.getFormattedString(stringId, [
7902 origin = Services.io.newURI(origin).asciiHost;
7904 /* Might fail for arbitrary U2F RP IDs. */
7906 options.name = origin;
7907 this.show_formatted_msg(
7922 secondaryActions = [],
7928 // We need to prevent some fullscreen transitions while WebAuthn prompts
7929 // are shown. The `fullscreen-painted` topic is notified when DOM elements
7931 Services.obs.addObserver(this, "fullscreen-painted");
7933 // The `fullscreen-nav-toolbox` topic is notified when the nav toolbox is
7935 Services.obs.addObserver(this, "fullscreen-nav-toolbox");
7937 // Ensure that no DOM elements are already fullscreen.
7938 FullScreen.exitDomFullScreen();
7940 // Ensure that the nav toolbox is being shown.
7941 if (window.fullScreen) {
7942 FullScreen.showNavToolbox();
7945 let brandShortName = document
7946 .getElementById("bundle_brand")
7947 .getString("brandShortName");
7948 if (options.hintText) {
7949 options.hintText = gNavigatorBundle.getFormattedString(options.hintText, [
7954 options.hideClose = true;
7955 options.persistent = true;
7956 options.eventCallback = event => {
7957 if (event == "removed") {
7958 Services.obs.removeObserver(this, "fullscreen-painted");
7959 Services.obs.removeObserver(this, "fullscreen-nav-toolbox");
7960 this._current = null;
7965 this._current = PopupNotifications.show(
7966 gBrowser.selectedBrowser,
7967 `webauthn-prompt-${id}`,
7977 if (this._tid == tid) {
7983 if (this._current) {
7984 this._current.remove();
7988 buildProceedAction(mgr, tid) {
7990 label: gNavigatorBundle.getString("webauthn.proceed"),
7991 accessKey: gNavigatorBundle.getString("webauthn.proceed.accesskey"),
7993 mgr.resumeMakeCredential(tid, state.checkboxChecked);
7998 buildCancelAction(mgr, tid) {
8000 label: gNavigatorBundle.getString("webauthn.cancel"),
8001 accessKey: gNavigatorBundle.getString("webauthn.cancel.accesskey"),
8009 function CanCloseWindow() {
8010 // Avoid redundant calls to canClose from showing multiple
8011 // PermitUnload dialogs.
8012 if (Services.startup.shuttingDown || window.skipNextCanClose) {
8016 for (let browser of gBrowser.browsers) {
8017 // Don't instantiate lazy browsers.
8018 if (!browser.isConnected) {
8022 let { permitUnload } = browser.permitUnload();
8023 if (!permitUnload) {
8030 function WindowIsClosing(event) {
8033 let target = event.sourceEvent?.target;
8034 if (target?.id?.startsWith("menu_")) {
8035 source = "menuitem";
8036 } else if (target?.nodeName == "toolbarbutton") {
8037 source = "close-button";
8039 let key = AppConstants.platform == "macosx" ? "metaKey" : "ctrlKey";
8040 source = event[key] ? "shortcut" : "OS";
8043 if (!closeWindow(false, warnAboutClosingWindow, source)) {
8047 // In theory we should exit here and the Window's internal Close
8048 // method should trigger canClose on nsBrowserAccess. However, by
8049 // that point it's too late to be able to show a prompt for
8050 // PermitUnload. So we do it here, when we still can.
8051 if (CanCloseWindow()) {
8052 // This flag ensures that the later canClose call does nothing.
8053 // It's only needed to make tests pass, since they detect the
8054 // prompt even when it's not actually shown.
8055 window.skipNextCanClose = true;
8063 * Checks if this is the last full *browser* window around. If it is, this will
8064 * be communicated like quitting. Otherwise, we warn about closing multiple tabs.
8066 * @param source where the request to close came from (used for telemetry)
8067 * @returns true if closing can proceed, false if it got cancelled.
8069 function warnAboutClosingWindow(source) {
8070 // Popups aren't considered full browser windows; we also ignore private windows.
8072 PrivateBrowsingUtils.isWindowPrivate(window) &&
8073 !PrivateBrowsingUtils.permanentPrivateBrowsing;
8075 if (!isPBWindow && !toolbar.visible) {
8076 return gBrowser.warnAboutClosingTabs(
8077 gBrowser.visibleTabs.length,
8078 gBrowser.closingTabsEnum.ALL,
8083 // Figure out if there's at least one other browser window around.
8084 let otherPBWindowExists = false;
8085 let otherWindowExists = false;
8086 for (let win of browserWindows()) {
8087 if (!win.closed && win != window) {
8088 otherWindowExists = true;
8089 if (isPBWindow && PrivateBrowsingUtils.isWindowPrivate(win)) {
8090 otherPBWindowExists = true;
8092 // If the current window is not in private browsing mode we don't need to
8093 // look for other pb windows, we can leave the loop when finding the
8094 // first non-popup window. If however the current window is in private
8095 // browsing mode then we need at least one other pb and one non-popup
8096 // window to break out early.
8097 if (!isPBWindow || otherPBWindowExists) {
8103 if (isPBWindow && !otherPBWindowExists) {
8104 let exitingCanceled = Cc["@mozilla.org/supports-PRBool;1"].createInstance(
8105 Ci.nsISupportsPRBool
8107 exitingCanceled.data = false;
8108 Services.obs.notifyObservers(exitingCanceled, "last-pb-context-exiting");
8109 if (exitingCanceled.data) {
8114 if (otherWindowExists) {
8117 gBrowser.warnAboutClosingTabs(
8118 gBrowser.visibleTabs.length,
8119 gBrowser.closingTabsEnum.ALL,
8125 let os = Services.obs;
8127 let closingCanceled = Cc["@mozilla.org/supports-PRBool;1"].createInstance(
8128 Ci.nsISupportsPRBool
8130 os.notifyObservers(closingCanceled, "browser-lastwindow-close-requested");
8131 if (closingCanceled.data) {
8135 os.notifyObservers(null, "browser-lastwindow-close-granted");
8137 // OS X doesn't quit the application when the last window is closed, but keeps
8138 // the session alive. Hence don't prompt users to save tabs, but warn about
8139 // closing multiple tabs.
8141 AppConstants.platform != "macosx" ||
8143 gBrowser.warnAboutClosingTabs(
8144 gBrowser.visibleTabs.length,
8145 gBrowser.closingTabsEnum.ALL,
8151 var MailIntegration = {
8152 sendLinkForBrowser(aBrowser) {
8154 gURLBar.makeURIReadable(aBrowser.currentURI).displaySpec,
8155 aBrowser.contentTitle
8159 sendMessage(aBody, aSubject) {
8160 // generate a mailto url based on the url and the url's title
8161 var mailtoUrl = "mailto:";
8163 mailtoUrl += "?body=" + encodeURIComponent(aBody);
8164 mailtoUrl += "&subject=" + encodeURIComponent(aSubject);
8167 var uri = makeURI(mailtoUrl);
8169 // now pass this uri to the operating system
8170 this._launchExternalUrl(uri);
8173 // a generic method which can be used to pass arbitrary urls to the operating
8175 // aURL --> a nsIURI which represents the url to launch
8176 _launchExternalUrl(aURL) {
8177 var extProtocolSvc = Cc[
8178 "@mozilla.org/uriloader/external-protocol-service;1"
8179 ].getService(Ci.nsIExternalProtocolService);
8180 if (extProtocolSvc) {
8181 extProtocolSvc.loadURI(
8183 Services.scriptSecurityManager.getSystemPrincipal()
8190 * Open about:addons page by given view id.
8191 * @param {String} aView
8192 * View id of page that will open.
8193 * e.g. "addons://discover/"
8194 * @param {Object} options
8196 * selectTabByViewId: If true, if there is the tab opening page having
8197 * same view id, select the tab. Else if the current
8198 * page is blank, load on it. Otherwise, open a new
8199 * tab, then load on it.
8200 * If false, if there is the tab opening
8201 * about:addoons page, select the tab and load page
8202 * for view id on it. Otherwise, leave the loading
8203 * behavior to switchToTabHavingURI().
8204 * If no options, handles as false.
8206 * @returns {Promise} When the Promise resolves, returns window object loaded the
8209 function BrowserOpenAddonsMgr(aView, { selectTabByViewId = false } = {}) {
8210 return new Promise(resolve => {
8214 var receivePong = function (aSubject) {
8215 let browserWin = aSubject.browsingContext.topChromeWindow;
8216 if (!emWindow || browserWin == window /* favor the current window */) {
8218 selectTabByViewId &&
8219 aSubject.gViewController.currentViewId !== aView
8224 emWindow = aSubject;
8225 browserWindow = browserWin;
8228 Services.obs.addObserver(receivePong, "EM-pong");
8229 Services.obs.notifyObservers(null, "EM-ping");
8230 Services.obs.removeObserver(receivePong, "EM-pong");
8233 if (aView && !selectTabByViewId) {
8234 emWindow.loadView(aView);
8236 let tab = browserWindow.gBrowser.getTabForBrowser(
8237 emWindow.docShell.chromeEventHandler
8239 browserWindow.gBrowser.selectedTab = tab;
8245 if (selectTabByViewId) {
8246 const target = isBlankPageURL(gBrowser.currentURI.spec)
8249 openTrustedLinkIn("about:addons", target);
8251 // This must be a new load, else the ping/pong would have
8252 // found the window above.
8253 switchToTabHavingURI("about:addons", true);
8256 Services.obs.addObserver(function observer(aSubject, aTopic) {
8257 Services.obs.removeObserver(observer, aTopic);
8259 aSubject.loadView(aView);
8267 function AddKeywordForSearchField() {
8268 if (!gContextMenu) {
8269 throw new Error("Context menu doesn't seem to be open.");
8272 gContextMenu.addKeywordForSearchField();
8276 * Applies only to the cmd|ctrl + shift + T keyboard shortcut
8277 * Undo the last action that was taken - either closing the last tab or closing the last window;
8278 * If none of those were the last actions, restore the last session if possible.
8280 function restoreLastClosedTabOrWindowOrSession() {
8281 let lastActionTaken = SessionStore.popLastClosedAction();
8283 if (lastActionTaken) {
8284 switch (lastActionTaken.type) {
8285 case SessionStore.LAST_ACTION_CLOSED_TAB: {
8289 case SessionStore.LAST_ACTION_CLOSED_WINDOW: {
8295 let closedTabCount = SessionStore.getLastClosedTabCount(window);
8296 if (SessionStore.canRestoreLastSession) {
8297 SessionStore.restoreLastSession();
8298 } else if (closedTabCount) {
8299 // we need to support users who have automatic session restore enabled
8306 * Re-open a closed tab into the current window.
8308 * The index of the tab (via SessionStore.getClosedTabData).
8309 * When undefined, the first n closed tabs will be re-opened, where n is provided by getLastClosedTabCount.
8310 * @param {string} [sourceWindowSSId]
8311 * An optional sessionstore id to identify the source window for the tab.
8312 * I.e. the window the tab belonged to when closed.
8313 * When undefined we'll use the current window
8314 * @returns a reference to the reopened tab.
8316 function undoCloseTab(aIndex, sourceWindowSSId) {
8317 // the window we'll open the tab into
8318 let targetWindow = window;
8319 // the window the tab was closed from
8321 if (sourceWindowSSId) {
8322 sourceWindow = SessionStore.getWindowById(sourceWindowSSId);
8323 if (!sourceWindow) {
8325 "sourceWindowSSId argument to undoCloseTab didn't resolve to a window"
8329 sourceWindow = window;
8332 // wallpaper patch to prevent an unnecessary blank tab (bug 343895)
8333 let blankTabToRemove = null;
8335 targetWindow.gBrowser.visibleTabs.length == 1 &&
8336 targetWindow.gBrowser.selectedTab.isEmpty
8338 blankTabToRemove = targetWindow.gBrowser.selectedTab;
8341 // We are specifically interested in the lastClosedTabCount for the source window.
8342 // When aIndex is undefined, we restore all the lastClosedTabCount tabs.
8343 let lastClosedTabCount = SessionStore.getLastClosedTabCount(sourceWindow);
8345 // aIndex is undefined if the function is called without a specific tab to restore.
8347 aIndex !== undefined ? [aIndex] : new Array(lastClosedTabCount).fill(0);
8348 let tabsRemoved = false;
8349 for (let index of tabsToRemove) {
8350 if (SessionStore.getClosedTabCountForWindow(sourceWindow) > index) {
8351 tab = SessionStore.undoCloseTab(sourceWindow, index, targetWindow);
8356 if (tabsRemoved && blankTabToRemove) {
8357 targetWindow.gBrowser.removeTab(blankTabToRemove);
8364 * Re-open a closed window.
8366 * The index of the window (via SessionStore.getClosedWindowData)
8367 * @returns a reference to the reopened window.
8369 function undoCloseWindow(aIndex) {
8371 if (SessionStore.getClosedWindowCount() > (aIndex || 0)) {
8372 window = SessionStore.undoCloseWindow(aIndex || 0);
8378 function ReportFalseDeceptiveSite() {
8379 let contextsToVisit = [gBrowser.selectedBrowser.browsingContext];
8380 while (contextsToVisit.length) {
8381 let currentContext = contextsToVisit.pop();
8382 let global = currentContext.currentWindowGlobal;
8387 let docURI = global.documentURI;
8388 // Ensure the page is an about:blocked pagae before handling.
8389 if (docURI && docURI.spec.startsWith("about:blocked?e=deceptiveBlocked")) {
8390 let actor = global.getActor("BlockedSite");
8391 actor.sendQuery("DeceptiveBlockedDetails").then(data => {
8392 let reportUrl = gSafeBrowsing.getReportURL(
8397 openTrustedLinkIn(reportUrl, "tab");
8399 let bundle = Services.strings.createBundle(
8400 "chrome://browser/locale/safebrowsing/safebrowsing.properties"
8402 Services.prompt.alert(
8404 bundle.GetStringFromName("errorReportFalseDeceptiveTitle"),
8405 bundle.formatStringFromName("errorReportFalseDeceptiveMessage", [
8406 data.blockedInfo.provider,
8413 contextsToVisit.push(...currentContext.children);
8418 * This is a temporary hack to connect a Help menu item for reporting
8419 * site issues to the WebCompat team's Site Compatability Reporter
8420 * WebExtension, which ships by default and is enabled on pre-release
8423 * Once we determine if Help is the right place for it, we'll do something
8424 * slightly better than this.
8428 function ReportSiteIssue() {
8429 let subject = { wrappedJSObject: gBrowser.selectedTab };
8430 Services.obs.notifyObservers(subject, "report-site-issue");
8434 * When the browser is being controlled from out-of-process,
8435 * e.g. when Marionette or the remote debugging protocol is used,
8436 * we add a visual hint to the browser UI to indicate to the user
8437 * that the browser session is under remote control.
8439 * This is called when the content browser initialises (from gBrowserInit.onLoad())
8440 * and when the "remote-listening" system notification fires.
8442 const gRemoteControl = {
8444 gRemoteControl.updateVisualCue();
8448 // Disable updating the remote control cue for performance tests,
8449 // because these could fail due to an early initialization of Marionette.
8450 const disableRemoteControlCue = Services.prefs.getBoolPref(
8451 "browser.chrome.disableRemoteControlCueForTests",
8454 if (disableRemoteControlCue && Cu.isInAutomation) {
8458 const mainWindow = document.documentElement;
8459 const remoteControlComponent = this.getRemoteControlComponent();
8460 if (remoteControlComponent) {
8461 mainWindow.setAttribute("remotecontrol", "true");
8462 const remoteControlIcon = document.getElementById("remote-control-icon");
8463 document.l10n.setAttributes(
8465 "urlbar-remote-control-notification-anchor2",
8466 { component: remoteControlComponent }
8469 mainWindow.removeAttribute("remotecontrol");
8473 getRemoteControlComponent() {
8474 // For DevTools sockets, only show the remote control cue if the socket is
8475 // not coming from a regular Browser Toolbox debugging session.
8477 DevToolsSocketStatus.hasSocketOpened({
8478 excludeBrowserToolboxSockets: true,
8484 if (Marionette.running) {
8485 return "Marionette";
8488 if (RemoteAgent.running) {
8489 return "RemoteAgent";
8496 // Note that this is also called from non-browser windows on OSX, which do
8497 // share menu items but not much else. See nonbrowser-mac.js.
8498 var gPrivateBrowsingUI = {
8499 init: function PBUI_init() {
8500 // Do nothing for normal windows
8501 if (!PrivateBrowsingUtils.isWindowPrivate(window)) {
8505 // Disable the Clear Recent History... menu item when in PB mode
8506 // temporary fix until bug 463607 is fixed
8507 document.getElementById("Tools:Sanitize").setAttribute("disabled", "true");
8509 if (window.location.href != AppConstants.BROWSER_CHROME_URL) {
8513 // Adjust the window's title
8514 let docElement = document.documentElement;
8515 docElement.setAttribute(
8516 "privatebrowsingmode",
8517 PrivateBrowsingUtils.permanentPrivateBrowsing ? "permanent" : "temporary"
8520 gBrowser.updateTitlebar();
8522 // Bug 1846583 - hide pocket button in PBM
8523 if (gUseFeltPrivacyUI) {
8524 const saveToPocketButton = document.getElementById(
8525 "save-to-pocket-button"
8527 if (saveToPocketButton) {
8528 saveToPocketButton.remove();
8529 document.documentElement.setAttribute("pocketdisabled", "true");
8533 if (PrivateBrowsingUtils.permanentPrivateBrowsing) {
8534 // Adjust the New Window menu entries
8535 let newWindow = document.getElementById("menu_newNavigator");
8536 let newPrivateWindow = document.getElementById("menu_newPrivateWindow");
8537 if (newWindow && newPrivateWindow) {
8538 newPrivateWindow.hidden = true;
8539 newWindow.label = newPrivateWindow.label;
8540 newWindow.accessKey = newPrivateWindow.accessKey;
8541 newWindow.command = newPrivateWindow.command;
8548 * Switch to a tab that has a given URI, and focuses its browser window.
8549 * If a matching tab is in this window, it will be switched to. Otherwise, other
8550 * windows will be searched.
8555 * True to open a new tab and switch to it, if no existing tab is found.
8556 * If no suitable window is found, a new one will be opened.
8557 * @param aOpenParams
8558 * If switching to this URI results in us opening a tab, aOpenParams
8559 * will be the parameter object that gets passed to openTrustedLinkIn. Please
8560 * see the documentation for openTrustedLinkIn to see what parameters can be
8561 * passed via this object.
8562 * This object also allows:
8563 * - 'ignoreFragment' property to be set to true to exclude fragment-portion
8564 * matching when comparing URIs.
8565 * If set to "whenComparing", the fragment will be unmodified.
8566 * If set to "whenComparingAndReplace", the fragment will be replaced.
8567 * - 'ignoreQueryString' boolean property to be set to true to exclude query string
8568 * matching when comparing URIs.
8569 * - 'replaceQueryString' boolean property to be set to true to exclude query string
8570 * matching when comparing URIs and overwrite the initial query string with
8571 * the one from the new URI.
8572 * - 'adoptIntoActiveWindow' boolean property to be set to true to adopt the tab
8573 * into the current window.
8574 * @param aUserContextId
8575 * If not null, will switch to the first found tab having the provided
8577 * @return True if an existing tab was found, false otherwise
8579 function switchToTabHavingURI(
8583 aUserContextId = null
8585 // Certain URLs can be switched to irrespective of the source or destination
8586 // window being in private browsing mode:
8587 const kPrivateBrowsingWhitelist = new Set(["about:addons"]);
8589 let ignoreFragment = aOpenParams.ignoreFragment;
8590 let ignoreQueryString = aOpenParams.ignoreQueryString;
8591 let replaceQueryString = aOpenParams.replaceQueryString;
8592 let adoptIntoActiveWindow = aOpenParams.adoptIntoActiveWindow;
8594 // These properties are only used by switchToTabHavingURI and should
8595 // not be used as a parameter for the new load.
8596 delete aOpenParams.ignoreFragment;
8597 delete aOpenParams.ignoreQueryString;
8598 delete aOpenParams.replaceQueryString;
8599 delete aOpenParams.adoptIntoActiveWindow;
8601 let isBrowserWindow = !!window.gBrowser;
8603 // This will switch to the tab in aWindow having aURI, if present.
8604 function switchIfURIInWindow(aWindow) {
8605 // We can switch tab only if if both the source and destination windows have
8606 // the same private-browsing status.
8608 !kPrivateBrowsingWhitelist.has(aURI.spec) &&
8609 PrivateBrowsingUtils.isWindowPrivate(window) !==
8610 PrivateBrowsingUtils.isWindowPrivate(aWindow)
8615 // Remove the query string, fragment, both, or neither from a given url.
8616 function cleanURL(url, removeQuery, removeFragment) {
8618 if (removeFragment) {
8619 ret = ret.split("#")[0];
8621 // This removes a query, if present before the fragment.
8622 ret = ret.split("?")[0];
8624 } else if (removeQuery) {
8625 // This is needed in case there is a fragment after the query.
8626 let fragment = ret.split("#")[1];
8629 .concat(fragment != undefined ? "#".concat(fragment) : "");
8634 // Need to handle nsSimpleURIs here too (e.g. about:...), which don't
8635 // work correctly with URL objects - so treat them as strings
8636 let ignoreFragmentWhenComparing =
8637 typeof ignoreFragment == "string" &&
8638 ignoreFragment.startsWith("whenComparing");
8639 let requestedCompare = cleanURL(
8641 ignoreQueryString || replaceQueryString,
8642 ignoreFragmentWhenComparing
8644 let browsers = aWindow.gBrowser.browsers;
8645 for (let i = 0; i < browsers.length; i++) {
8646 let browser = browsers[i];
8647 let browserCompare = cleanURL(
8648 browser.currentURI.displaySpec,
8649 ignoreQueryString || replaceQueryString,
8650 ignoreFragmentWhenComparing
8652 let browserUserContextId = browser.getAttribute("usercontextid") || "";
8653 if (aUserContextId != null && aUserContextId != browserUserContextId) {
8656 if (requestedCompare == browserCompare) {
8657 // If adoptIntoActiveWindow is set, and this is a cross-window switch,
8658 // adopt the tab into the current window, after the active tab.
8660 adoptIntoActiveWindow && isBrowserWindow && aWindow != window;
8663 const newTab = window.gBrowser.adoptTab(
8664 aWindow.gBrowser.getTabForBrowser(browser),
8665 window.gBrowser.tabContainer.selectedIndex + 1,
8666 /* aSelectTab = */ true
8676 if (ignoreFragment == "whenComparingAndReplace" || replaceQueryString) {
8677 browser.loadURI(aURI, {
8678 triggeringPrincipal:
8679 aOpenParams.triggeringPrincipal ||
8680 _createNullPrincipalFromTabUserContextId(),
8685 aWindow.gBrowser.tabContainer.selectedIndex = i;
8694 // This can be passed either nsIURI or a string.
8695 if (!(aURI instanceof Ci.nsIURI)) {
8696 aURI = Services.io.newURI(aURI);
8699 // Prioritise this window.
8700 if (isBrowserWindow && switchIfURIInWindow(window)) {
8704 for (let browserWin of browserWindows()) {
8705 // Skip closed (but not yet destroyed) windows,
8706 // and the current window (which was checked earlier).
8707 if (browserWin.closed || browserWin == window) {
8710 if (switchIfURIInWindow(browserWin)) {
8715 // No opened tab has that url.
8718 UrlbarPrefs.get("switchTabs.searchAllContainers") &&
8719 aUserContextId != null
8721 aOpenParams.userContextId = aUserContextId;
8723 if (isBrowserWindow && gBrowser.selectedTab.isEmpty) {
8724 openTrustedLinkIn(aURI.spec, "current", aOpenParams);
8726 openTrustedLinkIn(aURI.spec, "tab", aOpenParams);
8733 var RestoreLastSessionObserver = {
8736 SessionStore.canRestoreLastSession &&
8737 !PrivateBrowsingUtils.isWindowPrivate(window)
8739 Services.obs.addObserver(this, "sessionstore-last-session-cleared", true);
8740 goSetCommandEnabled("Browser:RestoreLastSession", true);
8741 } else if (SessionStore.willAutoRestore) {
8742 document.getElementById("Browser:RestoreLastSession").hidden = true;
8747 // The last session can only be restored once so there's
8748 // no way we need to re-enable our menu item.
8749 Services.obs.removeObserver(this, "sessionstore-last-session-cleared");
8750 goSetCommandEnabled("Browser:RestoreLastSession", false);
8753 QueryInterface: ChromeUtils.generateQI([
8755 "nsISupportsWeakReference",
8759 /* Observes menus and adjusts their size for better
8760 * usability when opened via a touch screen. */
8761 var MenuTouchModeObserver = {
8763 window.addEventListener("popupshowing", this, true);
8766 handleEvent(event) {
8767 let target = event.originalTarget;
8768 if (event.inputSource == MouseEvent.MOZ_SOURCE_TOUCH) {
8769 target.setAttribute("touchmode", "true");
8771 target.removeAttribute("touchmode");
8776 window.removeEventListener("popupshowing", this, true);
8780 // Prompt user to restart the browser in safe mode
8781 function safeModeRestart() {
8782 if (Services.appinfo.inSafeMode) {
8783 let cancelQuit = Cc["@mozilla.org/supports-PRBool;1"].createInstance(
8784 Ci.nsISupportsPRBool
8786 Services.obs.notifyObservers(
8788 "quit-application-requested",
8792 if (cancelQuit.data) {
8796 Services.startup.quit(
8797 Ci.nsIAppStartup.eRestart | Ci.nsIAppStartup.eAttemptQuit
8802 Services.obs.notifyObservers(window, "restart-in-safe-mode");
8805 /* duplicateTabIn duplicates tab in a place specified by the parameter |where|.
8809 * "tabshifted" same as "tab" but in background if default is to select new
8810 * tabs, and vice versa
8811 * "window" new window
8813 * delta is the offset to the history entry that you want to load.
8815 function duplicateTabIn(aTab, where, delta) {
8818 let otherWin = OpenBrowserWindow({
8819 private: PrivateBrowsingUtils.isBrowserPrivate(aTab.linkedBrowser),
8821 let delayedStartupFinished = (subject, topic) => {
8823 topic == "browser-delayed-startup-finished" &&
8826 Services.obs.removeObserver(delayedStartupFinished, topic);
8827 let otherGBrowser = otherWin.gBrowser;
8828 let otherTab = otherGBrowser.selectedTab;
8829 SessionStore.duplicateTab(otherWin, aTab, delta);
8830 otherGBrowser.removeTab(otherTab, { animate: false });
8834 Services.obs.addObserver(
8835 delayedStartupFinished,
8836 "browser-delayed-startup-finished"
8840 SessionStore.duplicateTab(window, aTab, delta);
8841 // A background tab has been opened, nothing else to do here.
8844 SessionStore.duplicateTab(window, aTab, delta, true, {
8845 inBackground: false,
8851 var MousePosTracker = {
8852 _listeners: new Set(),
8857 * Registers a listener.
8859 * @param listener (object)
8860 * A listener is expected to expose the following properties:
8862 * getMouseTargetRect (function)
8863 * Returns the rect that the MousePosTracker needs to alert
8864 * the listener about if the mouse happens to be within it.
8866 * onMouseEnter (function, optional)
8867 * The function to be called if the mouse enters the rect
8868 * returned by getMouseTargetRect. MousePosTracker always
8869 * runs this inside of a requestAnimationFrame, since it
8870 * assumes that the notification is used to update the DOM.
8872 * onMouseLeave (function, optional)
8873 * The function to be called if the mouse exits the rect
8874 * returned by getMouseTargetRect. MousePosTracker always
8875 * runs this inside of a requestAnimationFrame, since it
8876 * assumes that the notification is used to update the DOM.
8878 addListener(listener) {
8879 if (this._listeners.has(listener)) {
8883 listener._hover = false;
8884 this._listeners.add(listener);
8886 this._callListener(listener);
8889 removeListener(listener) {
8890 this._listeners.delete(listener);
8893 handleEvent(event) {
8894 this._x = event.screenX - window.mozInnerScreenX;
8895 this._y = event.screenY - window.mozInnerScreenY;
8897 this._listeners.forEach(listener => {
8899 this._callListener(listener);
8906 _callListener(listener) {
8907 let rect = listener.getMouseTargetRect();
8909 this._x >= rect.left &&
8910 this._x <= rect.right &&
8911 this._y >= rect.top &&
8912 this._y <= rect.bottom;
8914 if (hover == listener._hover) {
8918 listener._hover = hover;
8921 if (listener.onMouseEnter) {
8922 listener.onMouseEnter();
8924 } else if (listener.onMouseLeave) {
8925 listener.onMouseLeave();
8930 var ToolbarIconColor = {
8934 tabsintitlebar: false,
8937 this._initialized = true;
8939 window.addEventListener("nativethemechange", this);
8940 window.addEventListener("activate", this);
8941 window.addEventListener("deactivate", this);
8942 window.addEventListener("toolbarvisibilitychange", this);
8943 window.addEventListener("windowlwthemeupdate", this);
8945 // If the window isn't active now, we assume that it has never been active
8946 // before and will soon become active such that inferFromText will be
8947 // called from the initial activate event.
8948 if (Services.focus.activeWindow == window) {
8949 this.inferFromText("activate");
8954 this._initialized = false;
8956 window.removeEventListener("nativethemechange", this);
8957 window.removeEventListener("activate", this);
8958 window.removeEventListener("deactivate", this);
8959 window.removeEventListener("toolbarvisibilitychange", this);
8960 window.removeEventListener("windowlwthemeupdate", this);
8963 handleEvent(event) {
8964 switch (event.type) {
8967 case "nativethemechange":
8968 case "windowlwthemeupdate":
8969 this.inferFromText(event.type);
8971 case "toolbarvisibilitychange":
8972 this.inferFromText(event.type, event.visible);
8977 // a cache of luminance values for each toolbar
8978 // to avoid unnecessary calls to getComputedStyle
8979 _toolbarLuminanceCache: new Map(),
8981 inferFromText(reason, reasonValue) {
8982 if (!this._initialized) {
8986 case "activate": // falls through
8988 this._windowState.active = reason === "activate";
8991 this._windowState.fullscreen = reasonValue;
8993 case "nativethemechange":
8994 case "windowlwthemeupdate":
8995 // theme change, we'll need to recalculate all color values
8996 this._toolbarLuminanceCache.clear();
8998 case "toolbarvisibilitychange":
8999 // toolbar changes dont require reset of the cached color values
9001 case "tabsintitlebar":
9002 this._windowState.tabsintitlebar = reasonValue;
9006 let toolbarSelector = ".browser-toolbar:not([collapsed=true])";
9007 if (AppConstants.platform == "macosx") {
9008 toolbarSelector += ":not([type=menubar])";
9011 // The getComputedStyle calls and setting the brighttext are separated in
9012 // two loops to avoid flushing layout and making it dirty repeatedly.
9013 let cachedLuminances = this._toolbarLuminanceCache;
9014 let luminances = new Map();
9015 for (let toolbar of document.querySelectorAll(toolbarSelector)) {
9016 // toolbars *should* all have ids, but guard anyway to avoid blowing up
9018 toolbar.id && toolbar.id + JSON.stringify(this._windowState);
9019 // lookup cached luminance value for this toolbar in this window state
9020 let luminance = cacheKey && cachedLuminances.get(cacheKey);
9021 if (isNaN(luminance)) {
9022 let { r, g, b } = InspectorUtils.colorToRGBA(
9023 getComputedStyle(toolbar).color
9025 luminance = 0.2125 * r + 0.7154 * g + 0.0721 * b;
9027 cachedLuminances.set(cacheKey, luminance);
9030 luminances.set(toolbar, luminance);
9033 const luminanceThreshold = 127; // In between 0 and 255
9034 for (let [toolbar, luminance] of luminances) {
9035 if (luminance <= luminanceThreshold) {
9036 toolbar.removeAttribute("brighttext");
9038 toolbar.setAttribute("brighttext", "true");
9044 var PanicButtonNotifier = {
9046 this._initialized = true;
9047 if (window.PanicButtonNotifierShouldNotify) {
9048 delete window.PanicButtonNotifierShouldNotify;
9052 createPanelIfNeeded() {
9053 // Lazy load the panic-button-success-notification panel the first time we need to display it.
9054 if (!document.getElementById("panic-button-success-notification")) {
9055 let template = document.getElementById("panicButtonNotificationTemplate");
9056 template.replaceWith(template.content);
9060 if (!this._initialized) {
9061 window.PanicButtonNotifierShouldNotify = true;
9064 // Display notification panel here...
9066 this.createPanelIfNeeded();
9067 let popup = document.getElementById("panic-button-success-notification");
9068 popup.hidden = false;
9069 // To close the popup in 3 seconds after the popup is shown but left uninteracted.
9070 let onTimeout = () => {
9071 PanicButtonNotifier.close();
9074 popup.addEventListener("popupshown", function () {
9075 PanicButtonNotifier.timer = setTimeout(onTimeout, 3000);
9077 // To prevent the popup from closing when user tries to interact with the
9078 // popup using mouse or keyboard.
9079 let onUserInteractsWithPopup = () => {
9080 clearTimeout(PanicButtonNotifier.timer);
9083 popup.addEventListener("mouseover", onUserInteractsWithPopup);
9084 window.addEventListener("keydown", onUserInteractsWithPopup);
9085 let removeListeners = () => {
9086 popup.removeEventListener("mouseover", onUserInteractsWithPopup);
9087 window.removeEventListener("keydown", onUserInteractsWithPopup);
9088 popup.removeEventListener("popuphidden", removeListeners);
9090 popup.addEventListener("popuphidden", removeListeners);
9092 let widget = CustomizableUI.getWidget("panic-button").forWindow(window);
9093 let anchor = widget.anchor.icon;
9094 popup.openPopup(anchor, popup.getAttribute("position"));
9100 let popup = document.getElementById("panic-button-success-notification");
9105 const SafeBrowsingNotificationBox = {
9106 _currentURIBaseDomain: null,
9107 async show(title, buttons) {
9108 let uri = gBrowser.currentURI;
9110 // start tracking host so that we know when we leave the domain
9112 this._currentURIBaseDomain = Services.eTLD.getBaseDomain(uri);
9114 // If we can't get the base domain, fallback to use host instead. However,
9115 // host is sometimes empty when the scheme is file. In this case, just use
9117 this._currentURIBaseDomain = uri.asciiHost || uri.asciiSpec;
9120 let notificationBox = gBrowser.getNotificationBox();
9121 let value = "blocked-badware-page";
9123 let previousNotification = notificationBox.getNotificationWithValue(value);
9124 if (previousNotification) {
9125 notificationBox.removeNotification(previousNotification);
9128 let notification = await notificationBox.appendNotification(
9132 image: "chrome://global/skin/icons/blocked.svg",
9133 priority: notificationBox.PRIORITY_CRITICAL_HIGH,
9137 // Persist the notification until the user removes so it
9138 // doesn't get removed on redirects.
9139 notification.persistence = -1;
9141 onLocationChange(aLocationURI) {
9142 // take this to represent that you haven't visited a bad place
9143 if (!this._currentURIBaseDomain) {
9147 let newURIBaseDomain = Services.eTLD.getBaseDomain(aLocationURI);
9149 if (newURIBaseDomain !== this._currentURIBaseDomain) {
9150 let notificationBox = gBrowser.getNotificationBox();
9151 let notification = notificationBox.getNotificationWithValue(
9152 "blocked-badware-page"
9155 notificationBox.removeNotification(notification, false);
9158 this._currentURIBaseDomain = null;
9164 * The TabDialogBox supports opening window dialogs as SubDialogs on the tab and content
9165 * level. Both tab and content dialogs have their own separate managers.
9166 * Dialogs will be queued FIFO and cover the web content.
9167 * Dialogs are closed when the user reloads or leaves the page.
9168 * While a dialog is open PopupNotifications, such as permission prompts, are
9171 class TabDialogBox {
9172 static _containerFor(browser) {
9173 return browser.closest(".browserStack, .webextension-popup-stack");
9176 constructor(browser) {
9177 this._weakBrowserRef = Cu.getWeakReference(browser);
9179 // Create parent element for tab dialogs
9180 let template = document.getElementById("dialogStackTemplate");
9181 let dialogStack = template.content.cloneNode(true).firstElementChild;
9182 dialogStack.classList.add("tab-prompt-dialog");
9184 TabDialogBox._containerFor(browser).appendChild(dialogStack);
9186 // Initially the stack only contains the template
9187 let dialogTemplate = dialogStack.firstElementChild;
9189 // Create dialog manager for prompts at the tab level.
9190 this._tabDialogManager = new SubDialogManager({
9193 orderType: SubDialogManager.ORDER_QUEUE,
9194 allowDuplicateDialogs: true,
9196 consumeOutsideClicks: false,
9202 * Open a dialog on tab or content level.
9203 * @param {String} aURL - URL of the dialog to load in the tab box.
9204 * @param {Object} [aOptions]
9205 * @param {String} [aOptions.features] - Comma separated list of window
9207 * @param {Boolean} [aOptions.allowDuplicateDialogs] - Whether to allow
9208 * showing multiple dialogs with aURL at the same time. If false calls for
9209 * duplicate dialogs will be dropped.
9210 * @param {String} [aOptions.sizeTo] - Pass "available" to stretch dialog to
9211 * roughly content size. Any max-width or max-height style values on the document root
9212 * will also be applied to the dialog box.
9213 * @param {Boolean} [aOptions.keepOpenSameOriginNav] - By default dialogs are
9214 * aborted on any navigation.
9215 * Set to true to keep the dialog open for same origin navigation.
9216 * @param {Number} [aOptions.modalType] - The modal type to create the dialog for.
9217 * By default, we show the dialog for tab prompts.
9218 * @param {Boolean} [aOptions.hideContent] - When true, we are about to show a prompt that is requesting the
9219 * users credentials for a toplevel load of a resource from a base domain different from the base domain of the currently loaded page.
9220 * To avoid auth prompt spoofing (see bug 791594) we hide the current sites content
9221 * (among other protection mechanisms, that are not handled here, see the bug for reference).
9222 * @returns {Object} [result] Returns an object { closedPromise, dialog }.
9223 * @returns {Promise} [result.closedPromise] Resolves once the dialog has been closed.
9224 * @returns {SubDialog} [result.dialog] A reference to the opened SubDialog.
9230 allowDuplicateDialogs = true,
9232 keepOpenSameOriginNav,
9234 allowFocusCheckbox = false,
9235 hideContent = false,
9240 let closedPromise = new Promise(resolve => (resolveClosed = resolve));
9241 // Get the dialog manager to open the prompt with.
9243 modalType === Ci.nsIPrompt.MODAL_TYPE_CONTENT
9244 ? this.getContentDialogManager()
9245 : this._tabDialogManager;
9247 let hasDialogs = () =>
9248 this._tabDialogManager.hasDialogs ||
9249 this._contentDialogManager?.hasDialogs;
9251 if (!hasDialogs()) {
9252 this._onFirstDialogOpen();
9255 let closingCallback = event => {
9256 if (!hasDialogs()) {
9257 this._onLastDialogClose();
9260 if (allowFocusCheckbox && !event.detail?.abort) {
9261 this.maybeSetAllowTabSwitchPermission(event.target);
9265 if (modalType == Ci.nsIPrompt.MODAL_TYPE_CONTENT) {
9266 sizeTo = "limitheight";
9269 // Open dialog and resolve once it has been closed
9270 let dialog = dialogManager.open(
9274 allowDuplicateDialogs,
9277 closedCallback: resolveClosed,
9283 // Marking the dialog externally, instead of passing it as an option.
9284 // The SubDialog(Manager) does not care about navigation.
9285 // dialog can be null here if allowDuplicateDialogs = false.
9287 dialog._keepOpenSameOriginNav = keepOpenSameOriginNav;
9289 return { closedPromise, dialog };
9292 _onFirstDialogOpen() {
9293 // Hide PopupNotifications to prevent them from covering up dialogs.
9294 this.browser.setAttribute("tabDialogShowing", true);
9295 UpdatePopupNotificationsVisibility();
9297 // Register listeners
9298 this._lastPrincipal = this.browser.contentPrincipal;
9299 this.browser.addProgressListener(this, Ci.nsIWebProgress.NOTIFY_LOCATION);
9301 this.tab?.addEventListener("TabClose", this);
9304 _onLastDialogClose() {
9305 // Show PopupNotifications again.
9306 this.browser.removeAttribute("tabDialogShowing");
9307 UpdatePopupNotificationsVisibility();
9309 // Clean up listeners
9310 this.browser.removeProgressListener(this);
9311 this._lastPrincipal = null;
9313 this.tab?.removeEventListener("TabClose", this);
9316 _buildContentPromptDialog() {
9317 let template = document.getElementById("dialogStackTemplate");
9318 let contentDialogStack = template.content.cloneNode(true).firstElementChild;
9319 contentDialogStack.classList.add("content-prompt-dialog");
9321 // Create a dialog manager for content prompts.
9322 let browserContainer = TabDialogBox._containerFor(this.browser);
9323 let tabPromptDialog = browserContainer.querySelector(".tab-prompt-dialog");
9324 browserContainer.insertBefore(contentDialogStack, tabPromptDialog);
9326 let contentDialogTemplate = contentDialogStack.firstElementChild;
9327 this._contentDialogManager = new SubDialogManager({
9328 dialogStack: contentDialogStack,
9329 dialogTemplate: contentDialogTemplate,
9330 orderType: SubDialogManager.ORDER_QUEUE,
9331 allowDuplicateDialogs: true,
9333 consumeOutsideClicks: false,
9338 handleEvent(event) {
9339 if (event.type !== "TabClose") {
9342 this.abortAllDialogs();
9346 this._tabDialogManager.abortDialogs();
9347 this._contentDialogManager?.abortDialogs();
9351 // Prioritize focusing the dialog manager for tab prompts
9352 if (this._tabDialogManager._dialogs.length) {
9353 this._tabDialogManager.focusTopDialog();
9356 this._contentDialogManager?.focusTopDialog();
9360 * If the user navigates away or refreshes the page, close all dialogs for
9361 * the current browser.
9363 onLocationChange(aWebProgress, aRequest, aLocation, aFlags) {
9365 !aWebProgress.isTopLevel ||
9366 aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT
9371 // Dialogs can be exempt from closing on same origin location change.
9374 // Test for same origin location change
9376 this._lastPrincipal?.isSameOrigin(
9378 this.browser.browsingContext.usePrivateBrowsing
9381 filterFn = dialog => !dialog._keepOpenSameOriginNav;
9384 this._lastPrincipal = this.browser.contentPrincipal;
9386 this._tabDialogManager.abortDialogs(filterFn);
9387 this._contentDialogManager?.abortDialogs(filterFn);
9391 return gBrowser.getTabForBrowser(this.browser);
9395 let browser = this._weakBrowserRef.get();
9397 throw new Error("Stale dialog box! The associated browser is gone.");
9402 getTabDialogManager() {
9403 return this._tabDialogManager;
9406 getContentDialogManager() {
9407 if (!this._contentDialogManager) {
9408 this._buildContentPromptDialog();
9410 return this._contentDialogManager;
9413 onNextPromptShowAllowFocusCheckboxFor(principal) {
9414 this._allowTabFocusByPromptPrincipal = principal;
9418 * Sets the "focus-tab-by-prompt" permission for the dialog.
9420 maybeSetAllowTabSwitchPermission(dialog) {
9421 let checkbox = dialog.querySelector("checkbox");
9423 if (checkbox.checked) {
9424 Services.perms.addFromPrincipal(
9425 this._allowTabFocusByPromptPrincipal,
9426 "focus-tab-by-prompt",
9427 Services.perms.ALLOW_ACTION
9431 // Don't show the "allow tab switch checkbox" for subsequent prompts.
9432 this._allowTabFocusByPromptPrincipal = null;
9436 TabDialogBox.prototype.QueryInterface = ChromeUtils.generateQI([
9437 "nsIWebProgressListener",
9438 "nsISupportsWeakReference",
9441 // Handle window-modal prompts that we want to display with the same style as
9442 // tab-modal prompts.
9445 _nextOpenJumpsQueue: false,
9448 // Used to wait for a `close` event from the HTML
9449 // dialog. The event is fired asynchronously, which means
9450 // that if we open another dialog immediately after the
9451 // previous one, we might be confused into thinking a
9452 // `close` event for the old dialog is for the new one.
9453 // As they have the same event target, we have no way of
9454 // distinguishing them. So we wait for the `close` event
9455 // to have happened before allowing another dialog to open.
9456 _didCloseHTMLDialog: null,
9457 // Whether we managed to open the dialog we tried to open.
9458 // Used to avoid waiting for the above callback in case
9459 // of an error opening the dialog.
9460 _didOpenHTMLDialog: false,
9463 return this._dialog;
9467 return !!this._dialog;
9470 replaceDialogIfOpen() {
9471 this._dialog?.close();
9472 this._nextOpenJumpsQueue = true;
9475 async open(uri, args) {
9476 // If we need to queue, some callers indicate they should go first.
9477 const queueMethod = this._nextOpenJumpsQueue ? "unshift" : "push";
9478 this._nextOpenJumpsQueue = false;
9480 // If we already have a dialog opened and are trying to open another,
9481 // queue the next one to be opened later.
9483 return new Promise((resolve, reject) => {
9484 this._queued[queueMethod]({ resolve, reject, uri, args });
9488 // We're not open. If we're in a modal state though, we can't
9489 // show the dialog effectively. To avoid hanging by deadlock,
9490 // just return immediately for sync prompts:
9491 if (window.windowUtils.isInModalState() && !args.getProperty("async")) {
9492 throw Components.Exception(
9493 "Prompt could not be shown.",
9494 Cr.NS_ERROR_NOT_AVAILABLE
9498 // Indicate if we should wait for the dialog to close.
9499 this._didOpenHTMLDialog = false;
9500 let haveClosedPromise = new Promise(resolve => {
9501 this._didCloseHTMLDialog = resolve;
9504 // Bring the window to the front in case we're minimized or occluded:
9508 await this._open(uri, args);
9512 let dialog = document.getElementById("window-modal-dialog");
9516 // If the dialog was opened successfully, then we can wait for it
9517 // to close before trying to open any others.
9518 if (this._didOpenHTMLDialog) {
9519 await haveClosedPromise;
9521 dialog.style.visibility = "hidden";
9522 dialog.style.height = "0";
9523 dialog.style.width = "0";
9524 document.documentElement.removeAttribute("window-modal-open");
9525 dialog.removeEventListener("dialogopen", this);
9526 dialog.removeEventListener("close", this);
9527 this._updateMenuAndCommandState(true /* to enable */);
9528 this._dialog = null;
9529 UpdatePopupNotificationsVisibility();
9531 if (this._queued.length) {
9532 setTimeout(() => this._openNextDialog(), 0);
9539 let { resolve, reject, uri, args } = this._queued.shift();
9540 this.open(uri, args).then(resolve, reject);
9544 handleEvent(event) {
9545 switch (event.type) {
9547 this._dialog.focus(true);
9550 this._didCloseHTMLDialog();
9551 this._dialog.close();
9557 // Get this offset before we touch style below, as touching style seems
9558 // to reset the cached layout bounds.
9559 let offset = window.windowUtils.getBoundsWithoutFlushing(
9560 gBrowser.selectedBrowser
9562 let parentElement = document.getElementById("window-modal-dialog");
9563 parentElement.style.setProperty("--chrome-offset", offset + "px");
9564 parentElement.style.removeProperty("visibility");
9565 parentElement.style.removeProperty("width");
9566 parentElement.style.removeProperty("height");
9567 document.documentElement.setAttribute("window-modal-open", true);
9568 // Call this first so the contents show up and get layout, which is
9569 // required for SubDialog to work.
9570 parentElement.showModal();
9571 this._didOpenHTMLDialog = true;
9573 // Disable menus and shortcuts.
9574 this._updateMenuAndCommandState(false /* to disable */);
9576 // Now actually set up the dialog contents:
9577 let template = document.getElementById("window-modal-dialog-template")
9578 .content.firstElementChild;
9579 parentElement.addEventListener("dialogopen", this);
9580 parentElement.addEventListener("close", this);
9581 this._dialog = new SubDialog({
9584 id: "window-modal-dialog-subdialog",
9586 consumeOutsideClicks: false,
9589 let closedPromise = new Promise(resolve => {
9590 this._closedCallback = function () {
9591 PromptUtils.fireDialogEvent(window, "DOMModalDialogClosed");
9598 features: "resizable=no",
9599 modalType: Ci.nsIPrompt.MODAL_TYPE_INTERNAL_WINDOW,
9600 closedCallback: () => {
9601 this._closedCallback();
9606 UpdatePopupNotificationsVisibility();
9607 return closedPromise;
9610 _nonUpdatableElements: new Set([
9611 // Make an exception for debugging tools, for developer ease of use.
9612 "key_browserConsole",
9613 "key_browserToolbox",
9615 // Don't touch the editing keys/commands which we might want inside the dialog.
9626 _updateMenuAndCommandState(shouldBeEnabled) {
9627 let editorCommands = document.getElementById("editMenuCommands");
9628 // For the following items, set or clear disabled state:
9629 // - toplevel menubar items (will affect inner items on macOS)
9630 // - command elements
9631 // - key elements not connected to command elements.
9632 for (let element of document.querySelectorAll(
9633 "menubar > menu, command, key:not([command])"
9636 editorCommands?.contains(element) ||
9637 (element.id && this._nonUpdatableElements.has(element.id))
9641 if (element.nodeName == "key" && element.command) {
9644 if (!shouldBeEnabled) {
9645 if (element.getAttribute("disabled") != "true") {
9646 element.setAttribute("disabled", true);
9648 element.setAttribute("wasdisabled", true);
9650 } else if (element.getAttribute("wasdisabled") != "true") {
9651 element.removeAttribute("disabled");
9653 element.removeAttribute("wasdisabled");
9659 // browser.js loads in the library window, too, but we can only show prompts
9660 // in the main browser window:
9661 if (window.location.href != AppConstants.BROWSER_CHROME_URL) {
9665 var ConfirmationHint = {
9669 * Shows a transient, non-interactive confirmation hint anchored to an
9670 * element, usually used in response to a user action to reaffirm that it was
9671 * successful and potentially provide extra context. Examples for such hints:
9672 * - "Saved to bookmarks" after bookmarking a page
9673 * - "Sent!" after sending a tab to another device
9674 * - "Queued (offline)" when attempting to send a tab to another device
9677 * @param anchor (DOM node, required)
9678 * The anchor for the panel.
9679 * @param messageId (string, required)
9680 * For getting the message string from confirmationHints.ftl
9681 * @param options (object, optional)
9682 * An object with the following optional properties:
9683 * - event (DOM event): The event that triggered the feedback
9684 * - descriptionId (string): message ID of the description text
9685 * - position (string): position of the panel relative to the anchor.
9688 show(anchor, messageId, options = {}) {
9691 MozXULElement.insertFTLIfNeeded("toolkit/branding/brandings.ftl");
9692 MozXULElement.insertFTLIfNeeded("browser/confirmationHints.ftl");
9693 document.l10n.setAttributes(this._message, messageId);
9695 if (options.descriptionId) {
9696 document.l10n.setAttributes(this._description, options.descriptionId);
9697 this._description.hidden = false;
9698 this._panel.classList.add("with-description");
9700 this._description.hidden = true;
9701 this._panel.classList.remove("with-description");
9704 this._panel.setAttribute("data-message-id", messageId);
9706 // The timeout value used here allows the panel to stay open for
9707 // 3s after the text transition (duration=120ms) has finished.
9708 // If there is a description, we show for 6s after the text transition.
9709 const DURATION = options.showDescription ? 6000 : 3000;
9710 this._panel.addEventListener(
9713 this._animationBox.setAttribute("animate", "true");
9714 this._timerID = setTimeout(() => {
9715 this._panel.hidePopup(true);
9721 this._panel.addEventListener(
9724 // reset the timerId in case our timeout wasn't the cause of the popup being hidden
9730 this._panel.openPopup(anchor, {
9731 position: options.position ?? "bottomleft topleft",
9732 triggerEvent: options.event,
9737 if (this._timerID) {
9738 clearTimeout(this._timerID);
9739 this._timerID = null;
9742 this._animationBox.removeAttribute("animate");
9743 this._panel.removeAttribute("data-message-id");
9748 this._ensurePanel();
9749 return this.__panel;
9752 get _animationBox() {
9753 this._ensurePanel();
9754 delete this._animationBox;
9755 return (this._animationBox = document.getElementById(
9756 "confirmation-hint-checkmark-animation-container"
9761 this._ensurePanel();
9762 delete this._message;
9763 return (this._message = document.getElementById(
9764 "confirmation-hint-message"
9768 get _description() {
9769 this._ensurePanel();
9770 delete this._description;
9771 return (this._description = document.getElementById(
9772 "confirmation-hint-description"
9777 if (!this.__panel) {
9778 let wrapper = document.getElementById("confirmation-hint-wrapper");
9779 wrapper.replaceWith(wrapper.content);
9780 this.__panel = document.getElementById("confirmation-hint");
9785 var FirefoxViewHandler = {
9787 BUTTON_ID: "firefox-view-button",
9789 return document.getElementById(this.BUTTON_ID);
9792 CustomizableUI.addListener(this);
9794 ChromeUtils.defineESModuleGetters(this, {
9795 SyncedTabs: "resource://services-sync/SyncedTabs.sys.mjs",
9797 Services.obs.addObserver(this, "firefoxview-notification-dot-update");
9800 CustomizableUI.removeListener(this);
9801 Services.obs.removeObserver(this, "firefoxview-notification-dot-update");
9803 onWidgetRemoved(aWidgetId) {
9804 if (aWidgetId == this.BUTTON_ID && this.tab) {
9805 gBrowser.removeTab(this.tab);
9808 onWidgetAdded(aWidgetId) {
9809 if (aWidgetId === this.BUTTON_ID) {
9810 this.button.removeAttribute("open");
9814 if (!CustomizableUI.getPlacementOfWidget(this.BUTTON_ID)) {
9815 CustomizableUI.addWidgetToArea(
9817 CustomizableUI.AREA_TABSTRIP,
9818 CustomizableUI.getPlacementOfWidget("tabbrowser-tabs").position
9821 let viewURL = "about:firefoxview";
9823 viewURL = `${viewURL}#${section}`;
9825 // Need to account for navigation to Firefox View pages
9828 this.tab.linkedBrowser.currentURI.spec.split("#")[0] != viewURL
9830 gBrowser.removeTab(this.tab);
9834 this.tab = gBrowser.addTrustedTab(viewURL);
9835 this.tab.addEventListener("TabClose", this, { once: true });
9836 gBrowser.tabContainer.addEventListener("TabSelect", this);
9837 window.addEventListener("activate", this);
9838 gBrowser.hideTab(this.tab);
9839 this.button.setAttribute("aria-controls", this.tab.linkedPanel);
9841 // we put this here to avoid a race condition that would occur
9842 // if this was called in response to "TabSelect"
9843 this._closeDeviceConnectedTab();
9844 gBrowser.selectedTab = this.tab;
9846 openToolbarMouseEvent(event, section) {
9847 if (event?.type == "mousedown" && event?.button != 0) {
9850 this.openTab(section);
9855 const selected = e.target == this.tab;
9856 this.button?.toggleAttribute("open", selected);
9857 this.button?.setAttribute("aria-pressed", selected);
9858 this._recordViewIfTabSelected();
9859 this._onTabForegrounded();
9860 // If Fx View is opened, add temporary style to make first available tab focusable
9861 // When Fx View is closed, remove temporary -moz-user-focus style from first available tab
9862 gBrowser.visibleTabs[0].style.MozUserFocus =
9863 e.target == this.tab ? "normal" : "";
9867 gBrowser.tabContainer.removeEventListener("TabSelect", this);
9868 this.button?.removeAttribute("aria-controls");
9871 this._onTabForegrounded();
9875 observe(sub, topic, data) {
9877 case "firefoxview-notification-dot-update":
9878 let shouldShow = data === "true";
9879 this._toggleNotificationDot(shouldShow);
9883 _closeDeviceConnectedTab() {
9884 if (!TabsSetupFlowManager.didFxaTabOpen) {
9887 // close the tab left behind after a user pairs a device and
9888 // is redirected back to the Firefox View tab
9889 const fxaRoot = Services.prefs.getCharPref(
9890 "identity.fxaccounts.remote.root"
9892 const fxDeviceConnectedTab = gBrowser.tabs.find(tab =>
9893 tab.linkedBrowser.currentURI.displaySpec.startsWith(
9894 `${fxaRoot}pair/auth/complete`
9898 if (!fxDeviceConnectedTab) {
9902 if (gBrowser.tabs.length <= 2) {
9903 // if its the only tab besides the Firefox View tab,
9904 // open a new tab first so the browser doesn't close
9905 gBrowser.addTrustedTab("about:newtab");
9907 gBrowser.removeTab(fxDeviceConnectedTab);
9908 TabsSetupFlowManager.didFxaTabOpen = false;
9910 _onTabForegrounded() {
9911 if (this.tab?.selected) {
9912 this.SyncedTabs.syncTabs();
9913 Services.obs.notifyObservers(
9915 "firefoxview-notification-dot-update",
9920 _recordViewIfTabSelected() {
9921 if (this.tab?.selected) {
9922 const PREF_NAME = "browser.firefox-view.view-count";
9923 const MAX_VIEW_COUNT = 10;
9924 let viewCount = Services.prefs.getIntPref(PREF_NAME, 0);
9927 Services.telemetry.setEventRecordingEnabled("firefoxview_next", true);
9928 Services.telemetry.recordEvent(
9936 if (viewCount < MAX_VIEW_COUNT) {
9937 Services.prefs.setIntPref(PREF_NAME, viewCount + 1);
9941 _toggleNotificationDot(shouldShow) {
9942 this.button?.toggleAttribute("attention", shouldShow);