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 Color: "resource://gre/modules/Color.sys.mjs",
27 ContextualIdentityService:
28 "resource://gre/modules/ContextualIdentityService.sys.mjs",
29 CustomizableUI: "resource:///modules/CustomizableUI.sys.mjs",
30 Deprecated: "resource://gre/modules/Deprecated.sys.mjs",
32 "resource://devtools/shared/security/DevToolsSocketStatus.sys.mjs",
33 DownloadUtils: "resource://gre/modules/DownloadUtils.sys.mjs",
34 DownloadsCommon: "resource:///modules/DownloadsCommon.sys.mjs",
35 E10SUtils: "resource://gre/modules/E10SUtils.sys.mjs",
36 ExtensionsUI: "resource:///modules/ExtensionsUI.sys.mjs",
37 FirefoxViewNotificationManager:
38 "resource:///modules/firefox-view-notification-manager.sys.mjs",
39 HomePage: "resource:///modules/HomePage.sys.mjs",
40 isProductURL: "chrome://global/content/shopping/ShoppingProduct.mjs",
41 LightweightThemeConsumer:
42 "resource://gre/modules/LightweightThemeConsumer.sys.mjs",
43 Log: "resource://gre/modules/Log.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 PromiseUtils: "resource://gre/modules/PromiseUtils.sys.mjs",
64 PromptUtils: "resource://gre/modules/PromptUtils.sys.mjs",
65 ReaderMode: "resource://gre/modules/ReaderMode.sys.mjs",
66 SafeBrowsing: "resource://gre/modules/SafeBrowsing.sys.mjs",
67 Sanitizer: "resource:///modules/Sanitizer.sys.mjs",
68 SaveToPocket: "chrome://pocket/content/SaveToPocket.sys.mjs",
69 ScreenshotsUtils: "resource:///modules/ScreenshotsUtils.sys.mjs",
70 SearchUIUtils: "resource:///modules/SearchUIUtils.sys.mjs",
71 SessionStartup: "resource:///modules/sessionstore/SessionStartup.sys.mjs",
72 SessionStore: "resource:///modules/sessionstore/SessionStore.sys.mjs",
73 ShoppingSidebarParent: "resource:///actors/ShoppingSidebarParent.sys.mjs",
74 ShoppingUtils: "resource:///modules/ShoppingUtils.sys.mjs",
75 ShortcutUtils: "resource://gre/modules/ShortcutUtils.sys.mjs",
76 SiteDataManager: "resource:///modules/SiteDataManager.sys.mjs",
77 SitePermissions: "resource:///modules/SitePermissions.sys.mjs",
78 SubDialog: "resource://gre/modules/SubDialog.sys.mjs",
79 SubDialogManager: "resource://gre/modules/SubDialog.sys.mjs",
80 TabCrashHandler: "resource:///modules/ContentCrashHandlers.sys.mjs",
81 TabModalPrompt: "chrome://global/content/tabprompts.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 UrlbarInput: "resource:///modules/UrlbarInput.sys.mjs",
89 UrlbarPrefs: "resource:///modules/UrlbarPrefs.sys.mjs",
90 UrlbarProviderSearchTips:
91 "resource:///modules/UrlbarProviderSearchTips.sys.mjs",
92 UrlbarTokenizer: "resource:///modules/UrlbarTokenizer.sys.mjs",
93 UrlbarUtils: "resource:///modules/UrlbarUtils.sys.mjs",
94 UrlbarValueFormatter: "resource:///modules/UrlbarValueFormatter.sys.mjs",
95 Weave: "resource://services-sync/main.sys.mjs",
96 WebNavigationFrames: "resource://gre/modules/WebNavigationFrames.sys.mjs",
97 webrtcUI: "resource:///modules/webrtcUI.sys.mjs",
98 WebsiteFilter: "resource:///modules/policies/WebsiteFilter.sys.mjs",
99 ZoomUI: "resource:///modules/ZoomUI.sys.mjs",
102 XPCOMUtils.defineLazyModuleGetters(this, {
103 CFRPageActions: "resource://activity-stream/lib/CFRPageActions.jsm",
106 ChromeUtils.defineLazyGetter(this, "fxAccounts", () => {
107 return ChromeUtils.importESModule(
108 "resource://gre/modules/FxAccounts.sys.mjs"
109 ).getFxAccountsSingleton();
112 XPCOMUtils.defineLazyScriptGetter(
115 "chrome://browser/content/places/treeView.js"
117 XPCOMUtils.defineLazyScriptGetter(
119 ["PlacesInsertionPoint", "PlacesController", "PlacesControllerDragHelper"],
120 "chrome://browser/content/places/controller.js"
122 XPCOMUtils.defineLazyScriptGetter(
125 "chrome://global/content/printUtils.js"
127 XPCOMUtils.defineLazyScriptGetter(
130 "chrome://global/content/viewZoomOverlay.js"
132 XPCOMUtils.defineLazyScriptGetter(
135 "chrome://browser/content/browser-fullZoom.js"
137 XPCOMUtils.defineLazyScriptGetter(
140 "chrome://browser/content/customizableui/panelUI.js"
142 XPCOMUtils.defineLazyScriptGetter(
145 "chrome://global/content/viewSourceUtils.js"
147 XPCOMUtils.defineLazyScriptGetter(
150 "chrome://browser/content/browser-allTabsMenu.js"
152 XPCOMUtils.defineLazyScriptGetter(
156 "gExtensionsNotifications",
157 "gUnifiedExtensions",
158 "gXPInstallObserver",
160 "chrome://browser/content/browser-addons.js"
162 XPCOMUtils.defineLazyScriptGetter(
165 "chrome://browser/content/browser-ctrlTab.js"
167 XPCOMUtils.defineLazyScriptGetter(
169 ["CustomizationHandler", "AutoHideMenubar"],
170 "chrome://browser/content/browser-customization.js"
172 XPCOMUtils.defineLazyScriptGetter(
174 ["PointerLock", "FullScreen"],
175 "chrome://browser/content/browser-fullScreenAndPointerLock.js"
177 XPCOMUtils.defineLazyScriptGetter(
180 "chrome://browser/content/browser-siteIdentity.js"
182 XPCOMUtils.defineLazyScriptGetter(
185 "chrome://browser/content/browser-sitePermissionPanel.js"
187 XPCOMUtils.defineLazyScriptGetter(
190 "chrome://browser/content/translations/translationsPanel.js"
192 XPCOMUtils.defineLazyScriptGetter(
194 "gProtectionsHandler",
195 "chrome://browser/content/browser-siteProtections.js"
197 XPCOMUtils.defineLazyScriptGetter(
199 ["gGestureSupport", "gHistorySwipeAnimation"],
200 "chrome://browser/content/browser-gestureSupport.js"
202 XPCOMUtils.defineLazyScriptGetter(
205 "chrome://browser/content/browser-safebrowsing.js"
207 XPCOMUtils.defineLazyScriptGetter(
210 "chrome://browser/content/browser-sync.js"
212 XPCOMUtils.defineLazyScriptGetter(
214 "gBrowserThumbnails",
215 "chrome://browser/content/browser-thumbnails.js"
217 XPCOMUtils.defineLazyScriptGetter(
219 ["openContextMenu", "nsContextMenu"],
220 "chrome://browser/content/nsContextMenu.js"
222 XPCOMUtils.defineLazyScriptGetter(
226 "DownloadsOverlayLoader",
229 "DownloadsViewController",
232 "DownloadsBlockedSubview",
234 "chrome://browser/content/downloads/downloads.js"
236 XPCOMUtils.defineLazyScriptGetter(
238 ["DownloadsButton", "DownloadsIndicatorView"],
239 "chrome://browser/content/downloads/indicator.js"
241 XPCOMUtils.defineLazyScriptGetter(
244 "chrome://browser/content/places/editBookmark.js"
246 XPCOMUtils.defineLazyScriptGetter(
249 "chrome://browser/content/browser-graphics-utils.js"
251 XPCOMUtils.defineLazyScriptGetter(
254 "chrome://pocket/content/pktUI.js"
256 XPCOMUtils.defineLazyScriptGetter(
258 "ToolbarKeyboardNavigator",
259 "chrome://browser/content/browser-toolbarKeyNav.js"
261 XPCOMUtils.defineLazyScriptGetter(
264 "chrome://browser/content/browser-a11yUtils.js"
266 XPCOMUtils.defineLazyScriptGetter(
269 "chrome://browser/content/browser-webrtc.js"
271 XPCOMUtils.defineLazyScriptGetter(
274 "chrome://browser/content/browser-pagestyle.js"
277 // lazy service getters
279 XPCOMUtils.defineLazyServiceGetters(this, {
280 ContentPrefService2: [
281 "@mozilla.org/content-pref/service;1",
282 "nsIContentPrefService2",
285 "@mozilla.org/url-classifier/dbservice;1",
288 Favicons: ["@mozilla.org/browser/favicon-service;1", "nsIFaviconService"],
289 WindowsUIUtils: ["@mozilla.org/windows-ui-utils;1", "nsIWindowsUIUtils"],
290 BrowserHandler: ["@mozilla.org/browser/clh;1", "nsIBrowserHandler"],
293 if (AppConstants.ENABLE_WEBDRIVER) {
294 XPCOMUtils.defineLazyServiceGetter(
297 "@mozilla.org/remote/marionette;1",
301 XPCOMUtils.defineLazyServiceGetter(
304 "@mozilla.org/remote/agent;1",
308 this.Marionette = { running: false };
309 this.RemoteAgent = { running: false };
312 ChromeUtils.defineLazyGetter(this, "RTL_UI", () => {
313 return Services.locale.isAppLocaleRTL;
316 ChromeUtils.defineLazyGetter(this, "gBrandBundle", () => {
317 return Services.strings.createBundle(
318 "chrome://branding/locale/brand.properties"
322 ChromeUtils.defineLazyGetter(this, "gBrowserBundle", () => {
323 return Services.strings.createBundle(
324 "chrome://browser/locale/browser.properties"
328 ChromeUtils.defineLazyGetter(this, "gCustomizeMode", () => {
329 let { CustomizeMode } = ChromeUtils.importESModule(
330 "resource:///modules/CustomizeMode.sys.mjs"
332 return new CustomizeMode(window);
335 ChromeUtils.defineLazyGetter(this, "gNavToolbox", () => {
336 return document.getElementById("navigator-toolbox");
339 ChromeUtils.defineLazyGetter(this, "gURLBar", () => {
340 let urlbar = new UrlbarInput({
341 textbox: document.getElementById("urlbar"),
342 eventTelemetryCategory: "urlbar",
345 let beforeFocusOrSelect = event => {
346 // In customize mode, the url bar is disabled. If a new tab is opened or the
347 // user switches to a different tab, this function gets called before we've
348 // finished leaving customize mode, and the url bar will still be disabled.
349 // We can't focus it when it's disabled, so we need to re-run ourselves when
350 // we've finished leaving customize mode.
352 CustomizationHandler.isCustomizing() ||
353 CustomizationHandler.isExitingCustomizeMode
355 gNavToolbox.addEventListener(
356 "aftercustomization",
358 if (event.type == "beforeselect") {
368 event.preventDefault();
372 if (window.fullScreen) {
373 FullScreen.showNavToolbox();
376 urlbar.addEventListener("beforefocus", beforeFocusOrSelect);
377 urlbar.addEventListener("beforeselect", beforeFocusOrSelect);
382 ChromeUtils.defineLazyGetter(this, "ReferrerInfo", () =>
383 Components.Constructor(
384 "@mozilla.org/referrer-info;1",
390 // High priority notification bars shown at the top of the window.
391 ChromeUtils.defineLazyGetter(this, "gNotificationBox", () => {
392 return new MozElements.NotificationBox(element => {
393 element.classList.add("global-notificationbox");
394 element.setAttribute("notificationside", "top");
395 element.setAttribute("prepend-notifications", true);
396 const tabNotifications = document.getElementById("tab-notification-deck");
397 gNavToolbox.insertBefore(element, tabNotifications);
401 ChromeUtils.defineLazyGetter(this, "InlineSpellCheckerUI", () => {
402 let { InlineSpellChecker } = ChromeUtils.importESModule(
403 "resource://gre/modules/InlineSpellChecker.sys.mjs"
405 return new InlineSpellChecker();
408 ChromeUtils.defineLazyGetter(this, "PopupNotifications", () => {
409 // eslint-disable-next-line no-shadow
410 let { PopupNotifications } = ChromeUtils.importESModule(
411 "resource://gre/modules/PopupNotifications.sys.mjs"
414 // Hide all PopupNotifications while the the address bar has focus,
415 // including the virtual focus in the results popup, and the URL is being
416 // edited or the page proxy state is invalid while async tab switching.
417 let shouldSuppress = () => {
418 // "Blank" pages, like about:welcome, have a pageproxystate of "invalid", but
419 // popups like CFRs should not automatically be suppressed when the address
420 // bar has focus on these pages as it disrupts user navigation using FN+F6.
421 // See `UrlbarInput.setURI()` where pageproxystate is set to "invalid" for
422 // all pages that the "isBlankPageURL" method returns true for.
423 const urlBarEdited = isBlankPageURL(gBrowser.currentURI.spec)
424 ? gURLBar.hasAttribute("usertyping")
425 : gURLBar.getAttribute("pageproxystate") != "valid";
427 (urlBarEdited && gURLBar.focused) ||
428 (gURLBar.getAttribute("pageproxystate") != "valid" &&
429 gBrowser.selectedBrowser._awaitingSetURI) ||
430 shouldSuppressPopupNotifications()
434 // Before a Popup is shown, check that its anchor is visible.
435 // If the anchor is not visible, use one of the fallbacks.
436 // If no fallbacks are visible, return null.
437 const getVisibleAnchorElement = anchorElement => {
438 // If the anchor element is present in the Urlbar,
439 // ensure that both the anchor and page URL are visible.
440 gURLBar.maybeHandleRevertFromPopup(anchorElement);
441 if (anchorElement?.checkVisibility()) {
442 return anchorElement;
445 document.getElementById("identity-icon"),
446 document.getElementById("urlbar-search-button"),
448 return fallback.find(element => element?.checkVisibility()) ?? null;
451 return new PopupNotifications(
453 document.getElementById("notification-popup"),
454 document.getElementById("notification-popup-box"),
455 { shouldSuppress, getVisibleAnchorElement }
463 ChromeUtils.defineLazyGetter(this, "MacUserActivityUpdater", () => {
464 if (AppConstants.platform != "macosx") {
468 return Cc["@mozilla.org/widget/macuseractivityupdater;1"].getService(
469 Ci.nsIMacUserActivityUpdater
473 ChromeUtils.defineLazyGetter(this, "Win7Features", () => {
474 if (AppConstants.platform != "win") {
478 const WINTASKBAR_CONTRACTID = "@mozilla.org/windows-taskbar;1";
480 WINTASKBAR_CONTRACTID in Cc &&
481 Cc[WINTASKBAR_CONTRACTID].getService(Ci.nsIWinTaskbar).available
483 let { AeroPeek } = ChromeUtils.importESModule(
484 "resource:///modules/WindowsPreviewPerTab.sys.mjs"
488 AeroPeek.onOpenWindow(window);
489 this.handledOpening = true;
492 if (this.handledOpening) {
493 AeroPeek.onCloseWindow(window);
496 handledOpening: false,
502 XPCOMUtils.defineLazyPreferenceGetter(
504 "gToolbarKeyNavEnabled",
505 "browser.toolbars.keyboard_navigation",
507 (aPref, aOldVal, aNewVal) => {
512 ToolbarKeyboardNavigator.init();
514 ToolbarKeyboardNavigator.uninit();
519 XPCOMUtils.defineLazyPreferenceGetter(
521 "gBookmarksToolbarVisibility",
522 "browser.toolbars.bookmarks.visibility",
526 XPCOMUtils.defineLazyPreferenceGetter(
528 "gBookmarksToolbarShowInPrivate",
529 "browser.toolbars.bookmarks.showInPrivateBrowsing",
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",
548 (aPref, aOldVal, aNewVal) => {
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(
590 "gScreenshotsComponentEnabled",
591 "screenshots.browser.component.enabled",
594 Services.obs.notifyObservers(
596 "toggle-screenshot-disable",
597 gScreenshots.shouldScreenshotsButtonBeDisabled()
602 XPCOMUtils.defineLazyPreferenceGetter(
604 "gTranslationsEnabled",
605 "browser.translations.enable",
609 XPCOMUtils.defineLazyPreferenceGetter(
612 "browser.privatebrowsing.felt-privacy-v1",
616 customElements.setElementCreationCallback("screenshots-buttons", () => {
617 Services.scriptloader.loadSubScript(
618 "chrome://browser/content/screenshots/screenshots-buttons.js",
624 var gContextMenu = null; // nsContextMenu instance
625 var gMultiProcessBrowser = window.docShell.QueryInterface(
628 var gFissionBrowser = window.docShell.QueryInterface(
630 ).useRemoteSubframes;
632 var gBrowserAllowScriptsToCloseInitialTabs = false;
634 if (AppConstants.platform != "macosx") {
635 var gEditUIVisible = true;
638 Object.defineProperty(this, "gReduceMotion", {
641 return typeof gReduceMotionOverride == "boolean"
642 ? gReduceMotionOverride
643 : gReduceMotionSetting;
646 // Reduce motion during startup. The setting will be reset later.
647 let gReduceMotionSetting = true;
648 // This is for tests to set.
649 var gReduceMotionOverride;
651 // Smart getter for the findbar. If you don't wish to force the creation of
652 // the findbar, check gFindBarInitialized first.
654 Object.defineProperty(this, "gFindBar", {
657 return gBrowser.getCachedFindBar();
661 Object.defineProperty(this, "gFindBarInitialized", {
664 return gBrowser.isFindBarInitialized();
668 Object.defineProperty(this, "gFindBarPromise", {
671 return gBrowser.getFindBar();
675 function shouldSuppressPopupNotifications() {
676 // We have to hide notifications explicitly when the window is
677 // minimized because of the effects of the "noautohide" attribute on Linux.
678 // This can be removed once bug 545265 and bug 1320361 are fixed.
679 // Hide popup notifications when system tab prompts are shown so they
680 // don't cover up the prompt.
682 window.windowState == window.STATE_MINIMIZED ||
683 gBrowser?.selectedBrowser.hasAttribute("tabmodalChromePromptShowing") ||
684 gBrowser?.selectedBrowser.hasAttribute("tabDialogShowing") ||
689 async function gLazyFindCommand(cmd, ...args) {
690 let fb = await gFindBarPromise;
691 // We could be closed by now, or the tab with XBL binding could have gone away:
693 fb[cmd].apply(fb, args);
698 "about:home": "chrome://branding/content/icon32.png",
699 "about:newtab": "chrome://branding/content/icon32.png",
700 "about:welcome": "chrome://branding/content/icon32.png",
701 "about:privatebrowsing": "chrome://browser/skin/privatebrowsing/favicon.svg",
704 var gInitialPages = [
708 "about:firefoxview-next",
710 "about:privatebrowsing",
711 "about:sessionrestore",
714 "chrome://browser/content/blanktab.html",
717 function isInitialPage(url) {
718 if (!(url instanceof Ci.nsIURI)) {
720 url = Services.io.newURI(url);
726 let nonQuery = url.prePath + url.filePath;
727 return gInitialPages.includes(nonQuery) || nonQuery == BROWSER_NEW_TAB_URL;
730 function browserWindows() {
731 return Services.wm.getEnumerator("navigator:browser");
734 function updateBookmarkToolbarVisibility() {
735 // Bug 1846583 - hide bookmarks toolbar in PBM
738 !gBookmarksToolbarShowInPrivate &&
739 PrivateBrowsingUtils.isWindowPrivate(window)
741 setToolbarVisibility(BookmarkingUI.toolbar, false, false, false);
743 BookmarkingUI.updateEmptyToolbarMessage();
744 setToolbarVisibility(
745 BookmarkingUI.toolbar,
746 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);
850 * Click-and-Hold implementation for the Back and Forward buttons
851 * XXXmano: should this live in toolbarbutton.js?
853 function SetClickAndHoldHandlers() {
854 // Bug 414797: Clone the back/forward buttons' context menu into both buttons.
855 let popup = document.getElementById("backForwardMenu").cloneNode(true);
856 popup.removeAttribute("id");
857 // Prevent the back/forward buttons' context attributes from being inherited.
858 popup.setAttribute("context", "");
860 let backButton = document.getElementById("back-button");
861 backButton.setAttribute("type", "menu");
862 backButton.prepend(popup);
863 gClickAndHoldListenersOnElement.add(backButton);
865 let forwardButton = document.getElementById("forward-button");
866 popup = popup.cloneNode(true);
867 forwardButton.setAttribute("type", "menu");
868 forwardButton.prepend(popup);
869 gClickAndHoldListenersOnElement.add(forwardButton);
872 const gClickAndHoldListenersOnElement = {
875 _mousedownHandler(aEvent) {
877 aEvent.button != 0 ||
878 aEvent.currentTarget.open ||
879 aEvent.currentTarget.disabled
884 // Prevent the menupopup from opening immediately
885 aEvent.currentTarget.menupopup.hidden = true;
887 aEvent.currentTarget.addEventListener("mouseout", this);
888 aEvent.currentTarget.addEventListener("mouseup", this);
890 aEvent.currentTarget,
891 setTimeout(b => this._openMenu(b), 500, aEvent.currentTarget)
895 _clickHandler(aEvent) {
897 aEvent.button == 0 &&
898 aEvent.target == aEvent.currentTarget &&
899 !aEvent.currentTarget.open &&
900 !aEvent.currentTarget.disabled &&
901 // When menupopup is not hidden and we receive
902 // a click event, it means the mousedown occurred
903 // on aEvent.currentTarget and mouseup occurred on
904 // aEvent.currentTarget.menupopup, we don't
905 // need to handle the click event as menupopup
906 // handled mouseup event already.
907 aEvent.currentTarget.menupopup.hidden
909 let cmdEvent = document.createEvent("xulcommandevent");
910 cmdEvent.initCommandEvent(
924 aEvent.currentTarget.dispatchEvent(cmdEvent);
926 // This is here to cancel the XUL default event
927 // dom.click() triggers a command even if there is a click handler
928 // however this can now be prevented with preventDefault().
929 aEvent.preventDefault();
934 this._cancelHold(aButton);
935 aButton.firstElementChild.hidden = false;
939 _mouseoutHandler(aEvent) {
940 let buttonRect = aEvent.currentTarget.getBoundingClientRect();
942 aEvent.clientX >= buttonRect.left &&
943 aEvent.clientX <= buttonRect.right &&
944 aEvent.clientY >= buttonRect.bottom
946 this._openMenu(aEvent.currentTarget);
948 this._cancelHold(aEvent.currentTarget);
952 _mouseupHandler(aEvent) {
953 this._cancelHold(aEvent.currentTarget);
956 _cancelHold(aButton) {
957 clearTimeout(this._timers.get(aButton));
958 aButton.removeEventListener("mouseout", this);
959 aButton.removeEventListener("mouseup", this);
962 _keypressHandler(aEvent) {
963 if (aEvent.key == " " || aEvent.key == "Enter") {
964 // Normally, command events get fired for keyboard activation. However,
965 // we've set type="menu", so that doesn't happen. Handle this the same
966 // way we handle clicks.
967 aEvent.target.click();
974 this._mouseoutHandler(e);
977 this._mousedownHandler(e);
980 this._clickHandler(e);
983 this._mouseupHandler(e);
986 this._keypressHandler(e);
992 aButton.removeEventListener("mousedown", this, true);
993 aButton.removeEventListener("click", this, true);
994 aButton.removeEventListener("keypress", this, true);
998 this._timers.delete(aElm);
1000 aElm.addEventListener("mousedown", this, true);
1001 aElm.addEventListener("click", this, true);
1002 aElm.addEventListener("keypress", this, true);
1006 const gSessionHistoryObserver = {
1007 observe(subject, topic, data) {
1008 if (topic != "browser:purge-session-history") {
1012 var backCommand = document.getElementById("Browser:Back");
1013 backCommand.setAttribute("disabled", "true");
1014 var fwdCommand = document.getElementById("Browser:Forward");
1015 fwdCommand.setAttribute("disabled", "true");
1017 // Clear undo history of the URL bar
1018 gURLBar.editor.clearUndoRedo();
1022 const gStoragePressureObserver = {
1023 _lastNotificationTime: -1,
1025 async observe(subject, topic, data) {
1026 if (topic != "QuotaManager::StoragePressure") {
1030 const NOTIFICATION_VALUE = "storage-pressure-notification";
1031 if (gNotificationBox.getNotificationWithValue(NOTIFICATION_VALUE)) {
1032 // Do not display the 2nd notification when there is already one
1036 // Don't display notification twice within the given interval.
1038 // - not to annoy user
1039 // - give user some time to clean space.
1040 // Even user sees notification and starts acting, it still takes some time.
1041 const MIN_NOTIFICATION_INTERVAL_MS = Services.prefs.getIntPref(
1042 "browser.storageManager.pressureNotification.minIntervalMS"
1044 let duration = Date.now() - this._lastNotificationTime;
1045 if (duration <= MIN_NOTIFICATION_INTERVAL_MS) {
1048 this._lastNotificationTime = Date.now();
1050 MozXULElement.insertFTLIfNeeded("branding/brand.ftl");
1051 MozXULElement.insertFTLIfNeeded("browser/preferences/preferences.ftl");
1053 const BYTES_IN_GIGABYTE = 1073741824;
1054 const USAGE_THRESHOLD_BYTES =
1056 Services.prefs.getIntPref(
1057 "browser.storageManager.pressureNotification.usageThresholdGB"
1059 let messageFragment = document.createDocumentFragment();
1060 let message = document.createElement("span");
1062 let buttons = [{ supportPage: "storage-permissions" }];
1063 let usage = subject.QueryInterface(Ci.nsISupportsPRUint64).data;
1064 if (usage < USAGE_THRESHOLD_BYTES) {
1065 // The firefox-used space < 5GB, then warn user to free some disk space.
1066 // This is because this usage is small and not the main cause for space issue.
1067 // In order to avoid the bad and wrong impression among users that
1068 // firefox eats disk space a lot, indicate users to clean up other disk space.
1069 document.l10n.setAttributes(message, "space-alert-under-5gb-message2");
1071 // The firefox-used space >= 5GB, then guide users to about:preferences
1072 // to clear some data stored on firefox by websites.
1073 document.l10n.setAttributes(message, "space-alert-over-5gb-message2");
1075 "l10n-id": "space-alert-over-5gb-settings-button",
1076 callback(notificationBar, button) {
1077 // The advanced subpanes are only supported in the old organization, which will
1078 // be removed by bug 1349689.
1079 openPreferences("privacy-sitedata");
1083 messageFragment.appendChild(message);
1085 gNotificationBox.appendNotification(
1088 label: messageFragment,
1089 priority: gNotificationBox.PRIORITY_WARNING_HIGH,
1094 // This seems to be necessary to get the buttons to display correctly
1095 // See: https://bugzilla.mozilla.org/show_bug.cgi?id=1504216
1096 document.l10n.translateFragment(gNotificationBox.currentNotification);
1100 var gPopupBlockerObserver = {
1101 handleEvent(aEvent) {
1102 if (aEvent.originalTarget != gBrowser.selectedBrowser) {
1106 gPermissionPanel.refreshPermissionIcons();
1109 gBrowser.selectedBrowser.popupBlocker.getBlockedPopupCount();
1112 // Hide the notification box (if it's visible).
1113 let notificationBox = gBrowser.getNotificationBox();
1115 notificationBox.getNotificationWithValue("popup-blocked");
1117 notificationBox.removeNotification(notification, false);
1122 // Only show the notification again if we've not already shown it. Since
1123 // notifications are per-browser, we don't need to worry about re-adding
1125 if (gBrowser.selectedBrowser.popupBlocker.shouldShowNotification) {
1126 if (Services.prefs.getBoolPref("privacy.popups.showBrowserMessage")) {
1129 popupCount < this.maxReportedPopups
1130 ? "popup-warning-message"
1131 : "popup-warning-exceeded-message",
1132 "l10n-args": { popupCount },
1135 let notificationBox = gBrowser.getNotificationBox();
1137 notificationBox.getNotificationWithValue("popup-blocked");
1139 notification.label = label;
1141 const image = "chrome://browser/skin/notification-icons/popup.svg";
1142 const priority = notificationBox.PRIORITY_INFO_MEDIUM;
1143 notificationBox.appendNotification(
1145 { label, image, priority },
1148 "l10n-id": "popup-warning-button",
1149 popup: "blockedPopupOptions",
1157 // Record the fact that we've reported this blocked popup, so we don't
1159 gBrowser.selectedBrowser.popupBlocker.didShowNotification();
1163 toggleAllowPopupsForSite(aEvent) {
1164 var pm = Services.perms;
1165 var shouldBlock = aEvent.target.getAttribute("block") == "true";
1166 var perm = shouldBlock ? pm.DENY_ACTION : pm.ALLOW_ACTION;
1167 pm.addFromPrincipal(gBrowser.contentPrincipal, "popup", perm);
1170 gBrowser.selectedBrowser.popupBlocker.unblockAllPopups();
1173 gBrowser.getNotificationBox().removeCurrentNotification();
1176 fillPopupList(aEvent) {
1177 // XXXben - rather than using |currentURI| here, which breaks down on multi-framed sites
1178 // we should really walk the blockedPopups and create a list of "allow for <host>"
1179 // menuitems for the common subset of hosts present in the report, this will
1180 // make us frame-safe.
1182 // XXXjst - Note that when this is fixed to work with multi-framed sites,
1183 // also back out the fix for bug 343772 where
1184 // nsGlobalWindow::CheckOpenAllow() was changed to also
1185 // check if the top window's location is allow-listed.
1186 let browser = gBrowser.selectedBrowser;
1187 var uriOrPrincipal = browser.contentPrincipal.isContentPrincipal
1188 ? browser.contentPrincipal
1189 : browser.currentURI;
1190 var blockedPopupAllowSite = document.getElementById(
1191 "blockedPopupAllowSite"
1194 blockedPopupAllowSite.removeAttribute("hidden");
1195 let uriHost = uriOrPrincipal.asciiHost
1196 ? uriOrPrincipal.host
1197 : uriOrPrincipal.spec;
1198 var pm = Services.perms;
1200 pm.testPermissionFromPrincipal(browser.contentPrincipal, "popup") ==
1203 // Offer an item to block popups for this site, if an allow-list entry exists
1205 document.l10n.setAttributes(
1206 blockedPopupAllowSite,
1207 "popups-infobar-block",
1210 blockedPopupAllowSite.setAttribute("block", "true");
1212 // Offer an item to allow popups for this site
1213 document.l10n.setAttributes(
1214 blockedPopupAllowSite,
1215 "popups-infobar-allow",
1218 blockedPopupAllowSite.removeAttribute("block");
1221 blockedPopupAllowSite.hidden = true;
1224 let blockedPopupDontShowMessage = document.getElementById(
1225 "blockedPopupDontShowMessage"
1227 let showMessage = Services.prefs.getBoolPref(
1228 "privacy.popups.showBrowserMessage"
1230 blockedPopupDontShowMessage.setAttribute("checked", !showMessage);
1232 let blockedPopupsSeparator = document.getElementById(
1233 "blockedPopupsSeparator"
1235 blockedPopupsSeparator.hidden = true;
1237 browser.popupBlocker.getBlockedPopups().then(blockedPopups => {
1238 let foundUsablePopupURI = false;
1239 if (blockedPopups) {
1240 for (let i = 0; i < blockedPopups.length; i++) {
1241 let blockedPopup = blockedPopups[i];
1243 // popupWindowURI will be null if the file picker popup is blocked.
1244 // xxxdz this should make the option say "Show file picker" and do it (Bug 590306)
1245 if (!blockedPopup.popupWindowURISpec) {
1249 var popupURIspec = blockedPopup.popupWindowURISpec;
1251 // Sometimes the popup URI that we get back from the blockedPopup
1252 // isn't useful (for instance, netscape.com's popup URI ends up
1253 // being "http://www.netscape.com", which isn't really the URI of
1254 // the popup they're trying to show). This isn't going to be
1255 // useful to the user, so we won't create a menu item for it.
1257 popupURIspec == "" ||
1258 popupURIspec == "about:blank" ||
1259 popupURIspec == "<self>" ||
1260 popupURIspec == uriOrPrincipal.spec
1265 // Because of the short-circuit above, we may end up in a situation
1266 // in which we don't have any usable popup addresses to show in
1267 // the menu, and therefore we shouldn't show the separator. However,
1268 // since we got past the short-circuit, we must've found at least
1269 // one usable popup URI and thus we'll turn on the separator later.
1270 foundUsablePopupURI = true;
1272 var menuitem = document.createXULElement("menuitem");
1273 document.l10n.setAttributes(menuitem, "popup-show-popup-menuitem", {
1274 popupURI: popupURIspec,
1276 menuitem.setAttribute(
1278 "gPopupBlockerObserver.showBlockedPopup(event);"
1280 menuitem.setAttribute("popupReportIndex", i);
1281 menuitem.setAttribute(
1282 "popupInnerWindowId",
1283 blockedPopup.innerWindowId
1285 menuitem.browsingContext = blockedPopup.browsingContext;
1286 menuitem.popupReportBrowser = browser;
1287 aEvent.target.appendChild(menuitem);
1291 // Show the separator if we added any
1292 // showable popup addresses to the menu.
1293 if (foundUsablePopupURI) {
1294 blockedPopupsSeparator.removeAttribute("hidden");
1299 onPopupHiding(aEvent) {
1300 let item = aEvent.target.lastElementChild;
1301 while (item && item.id != "blockedPopupsSeparator") {
1302 let next = item.previousElementSibling;
1308 showBlockedPopup(aEvent) {
1309 let target = aEvent.target;
1310 let browsingContext = target.browsingContext;
1311 let innerWindowId = target.getAttribute("popupInnerWindowId");
1312 let popupReportIndex = target.getAttribute("popupReportIndex");
1313 let browser = target.popupReportBrowser;
1314 browser.popupBlocker.unblockPopup(
1321 editPopupSettings() {
1322 openPreferences("privacy-permissions-block-popups");
1326 var showMessage = Services.prefs.getBoolPref(
1327 "privacy.popups.showBrowserMessage"
1329 Services.prefs.setBoolPref(
1330 "privacy.popups.showBrowserMessage",
1333 gBrowser.getNotificationBox().removeCurrentNotification();
1337 XPCOMUtils.defineLazyPreferenceGetter(
1338 gPopupBlockerObserver,
1339 "maxReportedPopups",
1340 "privacy.popups.maxReported"
1343 var gKeywordURIFixup = {
1344 check(browser, { fixedURI, keywordProviderName, preferredURI }) {
1345 // We get called irrespective of whether we did a keyword search, or
1346 // whether the original input would be vaguely interpretable as a URL,
1347 // so figure that out first.
1349 !keywordProviderName ||
1352 UrlbarPrefs.get("browser.fixup.dns_first_for_single_words") ||
1353 UrlbarPrefs.get("dnsResolveSingleWordsAfterSearch") == 0
1358 let contentPrincipal = browser.contentPrincipal;
1360 // At this point we're still only just about to load this URI.
1361 // When the async DNS lookup comes back, we may be in any of these states:
1362 // 1) still on the previous URI, waiting for the preferredURI (keyword
1363 // search) to respond;
1364 // 2) at the keyword search URI (preferredURI)
1365 // 3) at some other page because the user stopped navigation.
1366 // We keep track of the currentURI to detect case (1) in the DNS lookup
1368 let previousURI = browser.currentURI;
1370 // now swap for a weak ref so we don't hang on to browser needlessly
1371 // even if the DNS query takes forever
1372 let weakBrowser = Cu.getWeakReference(browser);
1375 // Additionally, we need the host of the parsed url
1376 let hostName = fixedURI.displayHost;
1377 // and the ascii-only host for the pref:
1378 let asciiHost = fixedURI.asciiHost;
1380 let onLookupCompleteListener = {
1381 onLookupComplete(request, record, status) {
1382 let browserRef = weakBrowser.get();
1383 if (!Components.isSuccessCode(status) || !browserRef) {
1387 let currentURI = browserRef.currentURI;
1388 // If we're in case (3) (see above), don't show an info bar.
1390 !currentURI.equals(previousURI) &&
1391 !currentURI.equals(preferredURI)
1396 // show infobar offering to visit the host
1397 let notificationBox = gBrowser.getNotificationBox(browserRef);
1398 if (notificationBox.getNotificationWithValue("keyword-uri-fixup")) {
1402 let displayHostName = "http://" + hostName + "/";
1403 let message = gNavigatorBundle.getFormattedString(
1404 "keywordURIFixup.message",
1407 let yesMessage = gNavigatorBundle.getFormattedString(
1408 "keywordURIFixup.goTo",
1415 accessKey: gNavigatorBundle.getString(
1416 "keywordURIFixup.goTo.accesskey"
1419 // Do not set this preference while in private browsing.
1420 if (!PrivateBrowsingUtils.isWindowPrivate(window)) {
1421 let prefHost = asciiHost;
1422 // Normalize out a single trailing dot - NB: not using endsWith/lastIndexOf
1423 // because we need to be sure this last dot is the *only* dot, too.
1424 // More generally, this is used for the pref and should stay in sync with
1425 // the code in URIFixup::KeywordURIFixup .
1426 if (prefHost.indexOf(".") == prefHost.length - 1) {
1427 prefHost = prefHost.slice(0, -1);
1429 let pref = "browser.fixup.domainwhitelist." + prefHost;
1430 Services.prefs.setBoolPref(pref, true);
1432 openTrustedLinkIn(fixedURI.spec, "current");
1436 let notification = notificationBox.appendNotification(
1437 "keyword-uri-fixup",
1440 priority: notificationBox.PRIORITY_INFO_HIGH,
1444 notification.persistence = 1;
1448 Services.uriFixup.checkHost(
1450 onLookupCompleteListener,
1451 contentPrincipal.originAttributes
1455 observe(fixupInfo, topic, data) {
1456 fixupInfo.QueryInterface(Ci.nsIURIFixupInfo);
1458 let browser = fixupInfo.consumer?.top?.embedderElement;
1459 if (!browser || browser.ownerGlobal != window) {
1463 this.check(browser, fixupInfo);
1467 /* Creates a null principal using the userContextId
1468 from the current selected tab or a passed in tab argument */
1469 function _createNullPrincipalFromTabUserContextId(tab = gBrowser.selectedTab) {
1471 if (tab.hasAttribute("usercontextid")) {
1472 userContextId = tab.getAttribute("usercontextid");
1474 return Services.scriptSecurityManager.createNullPrincipal({
1479 let _resolveDelayedStartup;
1480 var delayedStartupPromise = new Promise(resolve => {
1481 _resolveDelayedStartup = resolve;
1484 var gBrowserInit = {
1485 delayedStartupFinished: false,
1486 idleTasksFinishedPromise: null,
1487 idleTaskPromiseResolve: null,
1488 domContentLoaded: false,
1490 _tabToAdopt: undefined,
1492 _setupFirstContentWindowPaintPromise() {
1493 let lastTransactionId = window.windowUtils.lastTransactionId;
1494 let layerTreeListener = () => {
1495 if (this.getTabToAdopt()) {
1496 // Need to wait until we finish adopting the tab, or we might end
1497 // up focusing the initial browser and then losing focus when it
1498 // gets swapped out for the tab to adopt.
1501 removeEventListener("MozLayerTreeReady", layerTreeListener);
1502 let listener = e => {
1503 if (e.transactionId > lastTransactionId) {
1504 window.removeEventListener("MozAfterPaint", listener);
1505 this._firstContentWindowPaintDeferred.resolve();
1508 addEventListener("MozAfterPaint", listener);
1510 addEventListener("MozLayerTreeReady", layerTreeListener);
1514 if (this._tabToAdopt !== undefined) {
1515 return this._tabToAdopt;
1518 if (window.arguments && window.XULElement.isInstance(window.arguments[0])) {
1519 this._tabToAdopt = window.arguments[0];
1521 // Clear the reference of the tab being adopted from the arguments.
1522 window.arguments[0] = null;
1524 // There was no tab to adopt in the arguments, set _tabToAdopt to null
1525 // to avoid checking it again.
1526 this._tabToAdopt = null;
1529 return this._tabToAdopt;
1532 _clearTabToAdopt() {
1533 this._tabToAdopt = null;
1536 // Used to check if the new window is still adopting an existing tab as its first tab
1537 // (e.g. from the WebExtensions internals).
1539 return !!this.getTabToAdopt();
1542 onBeforeInitialXULLayout() {
1543 this._setupFirstContentWindowPaintPromise();
1545 updateBookmarkToolbarVisibility();
1547 // Set a sane starting width/height for all resolutions on new profiles.
1548 if (ChromeUtils.shouldResistFingerprinting("RoundWindowSize")) {
1549 // When the fingerprinting resistance is enabled, making sure that we don't
1550 // have a maximum window to interfere with generating rounded window dimensions.
1551 document.documentElement.setAttribute("sizemode", "normal");
1552 } else if (!document.documentElement.hasAttribute("width")) {
1553 const TARGET_WIDTH = 1280;
1554 const TARGET_HEIGHT = 1040;
1555 let width = Math.min(screen.availWidth * 0.9, TARGET_WIDTH);
1556 let height = Math.min(screen.availHeight * 0.9, TARGET_HEIGHT);
1558 document.documentElement.setAttribute("width", width);
1559 document.documentElement.setAttribute("height", height);
1561 if (width < TARGET_WIDTH && height < TARGET_HEIGHT) {
1562 document.documentElement.setAttribute("sizemode", "maximized");
1565 if (AppConstants.MENUBAR_CAN_AUTOHIDE) {
1566 const toolbarMenubar = document.getElementById("toolbar-menubar");
1567 // set a default value
1568 if (!toolbarMenubar.hasAttribute("autohide")) {
1569 toolbarMenubar.setAttribute("autohide", true);
1571 document.l10n.setAttributes(
1573 "toolbar-context-menu-menu-bar-cmd"
1575 toolbarMenubar.setAttribute("data-l10n-attrs", "toolbarname");
1578 // Run menubar initialization first, to avoid TabsInTitlebar code picking
1579 // up mutations from it and causing a reflow.
1580 AutoHideMenubar.init();
1581 // Update the chromemargin attribute so the window can be sized correctly.
1582 window.TabBarVisibility.update();
1583 TabsInTitlebar.init();
1585 new LightweightThemeConsumer(document);
1588 Services.prefs.getBoolPref(
1589 "toolkit.legacyUserProfileCustomizations.windowIcon",
1593 document.documentElement.setAttribute("icon", "main-window");
1596 // Call this after we set attributes that might change toolbars' computed
1598 ToolbarIconColor.init();
1601 onDOMContentLoaded() {
1602 // This needs setting up before we create the first remote browser.
1603 window.docShell.treeOwner
1604 .QueryInterface(Ci.nsIInterfaceRequestor)
1605 .getInterface(Ci.nsIAppWindow).XULBrowserWindow = window.XULBrowserWindow;
1606 window.browserDOMWindow = new nsBrowserAccess();
1608 gBrowser = window._gBrowser;
1609 delete window._gBrowser;
1612 BrowserWindowTracker.track(window);
1614 FirefoxViewHandler.init();
1616 gNavToolbox.palette = document.getElementById(
1617 "BrowserToolbarPalette"
1619 for (let area of CustomizableUI.areas) {
1620 let type = CustomizableUI.getAreaType(area);
1621 if (type == CustomizableUI.TYPE_TOOLBAR) {
1622 let node = document.getElementById(area);
1623 CustomizableUI.registerToolbarNode(node);
1626 BrowserSearch.initPlaceHolder();
1628 // Hack to ensure that the various initial pages favicon is loaded
1629 // instantaneously, to avoid flickering and improve perceived performance.
1630 this._callWithURIToLoad(uriToLoad => {
1633 url = Services.io.newURI(uriToLoad);
1637 let nonQuery = url.prePath + url.filePath;
1638 if (nonQuery in gPageIcons) {
1639 gBrowser.setIcon(gBrowser.selectedTab, gPageIcons[nonQuery]);
1643 updateFxaToolbarMenu(gFxaToolbarEnabled, true);
1645 gUnifiedExtensions.init();
1647 // Setting the focus will cause a style flush, it's preferable to call anything
1648 // that will modify the DOM from within this function before this call.
1649 this._setInitialFocus();
1651 this.domContentLoaded = true;
1655 gBrowser.addEventListener("DOMUpdateBlockedPopups", gPopupBlockerObserver);
1656 gBrowser.addEventListener(
1657 "TranslationsParent:LanguageState",
1660 gBrowser.addEventListener(
1661 "TranslationsParent:OfferTranslation",
1664 gBrowser.addTabsProgressListener(TranslationsPanel);
1666 window.addEventListener("AppCommand", HandleAppCommandEvent, true);
1668 // These routines add message listeners. They must run before
1669 // loading the frame script to ensure that we don't miss any
1670 // message sent between when the frame script is loaded and when
1671 // the listener is registered.
1672 CaptivePortalWatcher.init();
1673 ZoomUI.init(window);
1675 if (!gMultiProcessBrowser) {
1676 // There is a Content:Click message manually sent from content.
1677 Services.els.addSystemEventListener(
1685 // hook up UI through progress listener
1686 gBrowser.addProgressListener(window.XULBrowserWindow);
1687 gBrowser.addTabsProgressListener(window.TabsProgressListener);
1691 // We do this in onload because we want to ensure the button's state
1692 // doesn't flicker as the window is being shown.
1693 DownloadsButton.init();
1695 // Certain kinds of automigration rely on this notification to complete
1696 // their tasks BEFORE the browser window is shown. SessionStore uses it to
1697 // restore tabs into windows AFTER important parts like gMultiProcessBrowser
1698 // have been initialized.
1699 Services.obs.notifyObservers(window, "browser-window-before-show");
1701 if (!window.toolbar.visible) {
1702 // adjust browser UI for popups
1703 gURLBar.readOnly = true;
1708 TabletModeUpdater.init();
1709 CombinedStopReload.ensureInitialized();
1710 gPrivateBrowsingUI.init();
1711 BrowserSearch.init();
1712 BrowserPageActions.init();
1713 if (gToolbarKeyNavEnabled) {
1714 ToolbarKeyboardNavigator.init();
1717 // Update UI if browser is under remote control.
1718 gRemoteControl.updateVisualCue();
1720 // If we are given a tab to swap in, take care of it before first paint to
1721 // avoid an about:blank flash.
1722 let tabToAdopt = this.getTabToAdopt();
1724 let evt = new CustomEvent("before-initial-tab-adopted", {
1727 gBrowser.tabpanels.dispatchEvent(evt);
1729 // Stop the about:blank load
1731 // make sure it has a docshell
1734 // Remove the speculative focus from the urlbar to let the url be formatted.
1735 gURLBar.removeAttribute("focused");
1737 let swapBrowsers = () => {
1739 gBrowser.swapBrowsersAndCloseOther(gBrowser.selectedTab, tabToAdopt);
1744 // Clear the reference to the tab once its adoption has been completed.
1745 this._clearTabToAdopt();
1747 if (tabToAdopt.linkedBrowser.isRemoteBrowser) {
1748 // For remote browsers, wait for the paint event, otherwise the tabs
1749 // are not yet ready and focus gets confused because the browser swaps
1750 // out while tabs are switching.
1751 addEventListener("MozAfterPaint", swapBrowsers, { once: true });
1757 // Wait until chrome is painted before executing code not critical to making the window visible
1758 this._boundDelayedStartup = this._delayedStartup.bind(this);
1759 window.addEventListener("MozAfterPaint", this._boundDelayedStartup);
1761 if (!PrivateBrowsingUtils.enabled) {
1762 document.getElementById("Tools:PrivateBrowsing").hidden = true;
1763 // Setting disabled doesn't disable the shortcut, so we just remove
1765 document.getElementById("key_privatebrowsing").remove();
1768 if (BrowserUIUtils.quitShortcutDisabled) {
1769 document.getElementById("key_quitApplication").remove();
1770 document.getElementById("menu_FileQuitItem").removeAttribute("key");
1772 PanelMultiView.getViewNode(
1774 "appMenu-quit-button2"
1775 )?.removeAttribute("key");
1778 this._loadHandled = true;
1781 _cancelDelayedStartup() {
1782 window.removeEventListener("MozAfterPaint", this._boundDelayedStartup);
1783 this._boundDelayedStartup = null;
1787 let { TelemetryTimestamps } = ChromeUtils.importESModule(
1788 "resource://gre/modules/TelemetryTimestamps.sys.mjs"
1790 TelemetryTimestamps.add("delayedStartupStarted");
1792 this._cancelDelayedStartup();
1794 // Bug 1531854 - The hidden window is force-created here
1795 // until all of its dependencies are handled.
1796 Services.appShell.hiddenDOMWindow;
1798 gBrowser.addEventListener(
1799 "PermissionStateChange",
1801 gIdentityHandler.refreshIdentityBlock();
1802 gPermissionPanel.updateSharingIndicator();
1807 this._handleURIToLoad();
1809 Services.obs.addObserver(gIdentityHandler, "perm-changed");
1810 Services.obs.addObserver(gRemoteControl, "devtools-socket");
1811 Services.obs.addObserver(gRemoteControl, "marionette-listening");
1812 Services.obs.addObserver(gRemoteControl, "remote-listening");
1813 Services.obs.addObserver(
1814 gSessionHistoryObserver,
1815 "browser:purge-session-history"
1817 Services.obs.addObserver(
1818 gStoragePressureObserver,
1819 "QuotaManager::StoragePressure"
1821 Services.obs.addObserver(gXPInstallObserver, "addon-install-disabled");
1822 Services.obs.addObserver(gXPInstallObserver, "addon-install-started");
1823 Services.obs.addObserver(gXPInstallObserver, "addon-install-blocked");
1824 Services.obs.addObserver(
1826 "addon-install-fullscreen-blocked"
1828 Services.obs.addObserver(
1830 "addon-install-origin-blocked"
1832 Services.obs.addObserver(
1834 "addon-install-policy-blocked"
1836 Services.obs.addObserver(
1838 "addon-install-webapi-blocked"
1840 Services.obs.addObserver(gXPInstallObserver, "addon-install-failed");
1841 Services.obs.addObserver(gXPInstallObserver, "addon-install-confirmation");
1842 Services.obs.addObserver(gKeywordURIFixup, "keyword-uri-fixup");
1844 BrowserOffline.init();
1845 CanvasPermissionPromptHelper.init();
1846 WebAuthnPromptHelper.init();
1848 // Initialize the full zoom setting.
1849 // We do this before the session restore service gets initialized so we can
1850 // apply full zoom settings to tabs restored by the session restore service.
1852 PanelUI.init(shouldSuppressPopupNotifications);
1854 UpdateUrlbarSearchSplitterState();
1856 BookmarkingUI.init();
1857 BrowserSearch.delayedStartupInit();
1858 SearchUIUtils.init();
1859 gProtectionsHandler.init();
1860 HomePage.delayedStartup().catch(console.error);
1862 let safeMode = document.getElementById("helpSafeMode");
1863 if (Services.appinfo.inSafeMode) {
1864 document.l10n.setAttributes(safeMode, "menu-help-exit-troubleshoot-mode");
1865 safeMode.setAttribute(
1866 "appmenu-data-l10n-id",
1867 "appmenu-help-exit-troubleshoot-mode"
1872 gBidiUI = isBidiEnabled();
1874 document.getElementById("documentDirection-separator").hidden = false;
1875 document.getElementById("documentDirection-swap").hidden = false;
1876 document.getElementById("textfieldDirection-separator").hidden = false;
1877 document.getElementById("textfieldDirection-swap").hidden = false;
1880 // Setup click-and-hold gestures access to the session history
1881 // menus if global click-and-hold isn't turned on
1882 if (!Services.prefs.getBoolPref("ui.click_hold_context_menus", false)) {
1883 SetClickAndHoldHandlers();
1886 function initBackForwardButtonTooltip(tooltipId, l10nId, shortcutId) {
1887 let shortcut = document.getElementById(shortcutId);
1888 shortcut = ShortcutUtils.prettifyShortcut(shortcut);
1890 let tooltip = document.getElementById(tooltipId);
1891 document.l10n.setAttributes(tooltip, l10nId, { shortcut });
1894 initBackForwardButtonTooltip(
1895 "back-button-tooltip-description",
1896 "navbar-tooltip-back-2",
1900 initBackForwardButtonTooltip(
1901 "forward-button-tooltip-description",
1902 "navbar-tooltip-forward-2",
1906 PlacesToolbarHelper.init();
1909 Services.prefs.addObserver(ctrlTab.prefName, ctrlTab);
1911 // The object handling the downloads indicator is initialized here in the
1912 // delayed startup function, but the actual indicator element is not loaded
1913 // unless there are downloads to be displayed.
1914 DownloadsButton.initializeIndicator();
1916 if (AppConstants.platform != "macosx") {
1917 updateEditUIVisibility();
1918 let placesContext = document.getElementById("placesContext");
1919 placesContext.addEventListener("popupshowing", updateEditUIVisibility);
1920 placesContext.addEventListener("popuphiding", updateEditUIVisibility);
1925 if (AppConstants.platform == "win") {
1926 MenuTouchModeObserver.init();
1929 if (AppConstants.MOZ_DATA_REPORTING) {
1930 gDataNotificationInfoBar.init();
1933 if (!AppConstants.MOZILLA_OFFICIAL) {
1934 DevelopmentHelpers.init();
1937 gExtensionsNotifications.init();
1939 let wasMinimized = window.windowState == window.STATE_MINIMIZED;
1940 window.addEventListener("sizemodechange", () => {
1941 let isMinimized = window.windowState == window.STATE_MINIMIZED;
1942 if (wasMinimized != isMinimized) {
1943 wasMinimized = isMinimized;
1944 UpdatePopupNotificationsVisibility();
1948 window.addEventListener("mousemove", MousePosTracker);
1949 window.addEventListener("dragover", MousePosTracker);
1951 gNavToolbox.addEventListener("customizationstarting", CustomizationHandler);
1952 gNavToolbox.addEventListener("aftercustomization", CustomizationHandler);
1954 SessionStore.promiseInitialized.then(() => {
1955 // Bail out if the window has been closed in the meantime.
1956 if (window.closed) {
1960 // Enable the Restore Last Session command if needed
1961 RestoreLastSessionObserver.init();
1963 SidebarUI.startDelayedLoad();
1965 PanicButtonNotifier.init();
1968 if (BrowserHandler.kiosk) {
1969 // We don't modify popup windows for kiosk mode
1970 if (!gURLBar.readOnly) {
1971 window.fullScreen = true;
1975 if (Services.policies.status === Services.policies.ACTIVE) {
1976 if (!Services.policies.isAllowed("hideShowMenuBar")) {
1978 .getElementById("toolbar-menubar")
1979 .removeAttribute("toolbarname");
1981 let policies = Services.policies.getActivePolicies();
1982 if ("ManagedBookmarks" in policies) {
1983 let managedBookmarks = policies.ManagedBookmarks;
1984 let children = managedBookmarks.filter(
1985 child => !("toplevel_name" in child)
1987 if (children.length) {
1988 let managedBookmarksButton =
1989 document.createXULElement("toolbarbutton");
1990 managedBookmarksButton.setAttribute("id", "managed-bookmarks");
1991 managedBookmarksButton.setAttribute("class", "bookmark-item");
1992 let toplevel = managedBookmarks.find(
1993 element => "toplevel_name" in element
1996 managedBookmarksButton.setAttribute(
1998 toplevel.toplevel_name
2001 document.l10n.setAttributes(
2002 managedBookmarksButton,
2006 managedBookmarksButton.setAttribute("context", "placesContext");
2007 managedBookmarksButton.setAttribute("container", "true");
2008 managedBookmarksButton.setAttribute("removable", "false");
2009 managedBookmarksButton.setAttribute("type", "menu");
2011 let managedBookmarksPopup = document.createXULElement("menupopup");
2012 managedBookmarksPopup.setAttribute("id", "managed-bookmarks-popup");
2013 managedBookmarksPopup.setAttribute(
2015 "PlacesToolbarHelper.openManagedBookmark(event);"
2017 managedBookmarksPopup.setAttribute(
2019 "event.dataTransfer.effectAllowed='none';"
2021 managedBookmarksPopup.setAttribute(
2023 "PlacesToolbarHelper.onDragStartManaged(event);"
2025 managedBookmarksPopup.setAttribute(
2027 "PlacesToolbarHelper.populateManagedBookmarks(this);"
2029 managedBookmarksPopup.setAttribute("placespopup", "true");
2030 managedBookmarksPopup.setAttribute("is", "places-popup");
2031 managedBookmarksPopup.classList.add("toolbar-menupopup");
2032 managedBookmarksButton.appendChild(managedBookmarksPopup);
2034 gNavToolbox.palette.appendChild(managedBookmarksButton);
2036 CustomizableUI.ensureWidgetPlacedInWindow(
2037 "managed-bookmarks",
2041 // Add button if it doesn't exist
2042 if (!CustomizableUI.getPlacementOfWidget("managed-bookmarks")) {
2043 CustomizableUI.addWidgetToArea(
2044 "managed-bookmarks",
2045 CustomizableUI.AREA_BOOKMARKS,
2053 CaptivePortalWatcher.delayedStartup();
2055 ShoppingSidebarManager.init();
2057 SessionStore.promiseAllWindowsRestored.then(() => {
2058 this._schedulePerWindowIdleTasks();
2059 document.documentElement.setAttribute("sessionrestored", "true");
2062 this.delayedStartupFinished = true;
2063 _resolveDelayedStartup();
2064 Services.obs.notifyObservers(window, "browser-delayed-startup-finished");
2065 TelemetryTimestamps.add("delayedStartupFinished");
2066 // We've announced that delayed startup has finished. Do not add code past this point.
2070 * Resolved on the first MozLayerTreeReady and next MozAfterPaint in the
2073 get firstContentWindowPaintPromise() {
2074 return this._firstContentWindowPaintDeferred.promise;
2077 _setInitialFocus() {
2078 let initiallyFocusedElement = document.commandDispatcher.focusedElement;
2080 // To prevent startup flicker, the urlbar has the 'focused' attribute set
2081 // by default. If we are not sure the urlbar will be focused in this
2082 // window, we need to remove the attribute before first paint.
2083 // TODO (bug 1629956): The urlbar having the 'focused' attribute by default
2084 // isn't a useful optimization anymore since UrlbarInput needs layout
2085 // information to focus the urlbar properly.
2086 let shouldRemoveFocusedAttribute = true;
2088 this._callWithURIToLoad(uriToLoad => {
2090 isBlankPageURL(uriToLoad) ||
2091 uriToLoad == "about:privatebrowsing" ||
2092 this.getTabToAdopt()?.isEmpty
2095 shouldRemoveFocusedAttribute = false;
2099 // If the initial browser is remote, in order to optimize for first paint,
2100 // we'll defer switching focus to that browser until it has painted.
2101 // Otherwise use a regular promise to guarantee that mutationobserver
2102 // microtasks that could affect focusability have run.
2103 let promise = gBrowser.selectedBrowser.isRemoteBrowser
2104 ? this.firstContentWindowPaintPromise
2105 : Promise.resolve();
2107 promise.then(() => {
2108 // If focus didn't move while we were waiting, we're okay to move to
2111 document.commandDispatcher.focusedElement == initiallyFocusedElement
2113 gBrowser.selectedBrowser.focus();
2118 // Delay removing the attribute using requestAnimationFrame to avoid
2119 // invalidating styles multiple times in a row if uriToLoadPromise
2120 // resolves before first paint.
2121 if (shouldRemoveFocusedAttribute) {
2122 window.requestAnimationFrame(() => {
2123 if (shouldRemoveFocusedAttribute) {
2124 gURLBar.removeAttribute("focused");
2130 _handleURIToLoad() {
2131 this._callWithURIToLoad(uriToLoad => {
2133 // We don't check whether window.arguments[5] (userContextId) is set
2134 // because tabbrowser.js takes care of that for the initial tab.
2138 // We don't check if uriToLoad is a XULElement because this case has
2139 // already been handled before first paint, and the argument cleared.
2140 if (Array.isArray(uriToLoad)) {
2141 // This function throws for certain malformed URIs, so use exception handling
2142 // so that we don't disrupt startup
2144 gBrowser.loadTabs(uriToLoad, {
2145 inBackground: false,
2147 // See below for the semantics of window.arguments. Only the minimum is supported.
2148 userContextId: window.arguments[5],
2149 triggeringPrincipal:
2150 window.arguments[8] ||
2151 Services.scriptSecurityManager.getSystemPrincipal(),
2152 allowInheritPrincipal: window.arguments[9],
2153 csp: window.arguments[10],
2157 } else if (window.arguments.length >= 3) {
2158 // window.arguments[1]: extraOptions (nsIPropertyBag)
2159 // [2]: referrerInfo (nsIReferrerInfo)
2160 // [3]: postData (nsIInputStream)
2161 // [4]: allowThirdPartyFixup (bool)
2162 // [5]: userContextId (int)
2163 // [6]: originPrincipal (nsIPrincipal)
2164 // [7]: originStoragePrincipal (nsIPrincipal)
2165 // [8]: triggeringPrincipal (nsIPrincipal)
2166 // [9]: allowInheritPrincipal (bool)
2167 // [10]: csp (nsIContentSecurityPolicy)
2168 // [11]: nsOpenWindowInfo
2170 window.arguments[5] != undefined
2171 ? window.arguments[5]
2172 : Ci.nsIScriptSecurityManager.DEFAULT_USER_CONTEXT_ID;
2174 let hasValidUserGestureActivation = undefined;
2175 let fromExternal = undefined;
2176 let globalHistoryOptions = undefined;
2177 let triggeringRemoteType = undefined;
2178 let forceAllowDataURI = false;
2179 if (window.arguments[1]) {
2180 if (!(window.arguments[1] instanceof Ci.nsIPropertyBag2)) {
2182 "window.arguments[1] must be null or Ci.nsIPropertyBag2!"
2186 let extraOptions = window.arguments[1];
2187 if (extraOptions.hasKey("hasValidUserGestureActivation")) {
2188 hasValidUserGestureActivation = extraOptions.getPropertyAsBool(
2189 "hasValidUserGestureActivation"
2192 if (extraOptions.hasKey("fromExternal")) {
2193 fromExternal = extraOptions.getPropertyAsBool("fromExternal");
2195 if (extraOptions.hasKey("triggeringSponsoredURL")) {
2196 globalHistoryOptions = {
2197 triggeringSponsoredURL: extraOptions.getPropertyAsACString(
2198 "triggeringSponsoredURL"
2201 if (extraOptions.hasKey("triggeringSponsoredURLVisitTimeMS")) {
2202 globalHistoryOptions.triggeringSponsoredURLVisitTimeMS =
2203 extraOptions.getPropertyAsUint64(
2204 "triggeringSponsoredURLVisitTimeMS"
2208 if (extraOptions.hasKey("triggeringRemoteType")) {
2209 triggeringRemoteType = extraOptions.getPropertyAsACString(
2210 "triggeringRemoteType"
2213 if (extraOptions.hasKey("forceAllowDataURI")) {
2215 extraOptions.getPropertyAsBool("forceAllowDataURI");
2220 openLinkIn(uriToLoad, "current", {
2221 referrerInfo: window.arguments[2] || null,
2222 postData: window.arguments[3] || null,
2223 allowThirdPartyFixup: window.arguments[4] || false,
2225 // pass the origin principal (if any) and force its use to create
2226 // an initial about:blank viewer if present:
2227 originPrincipal: window.arguments[6],
2228 originStoragePrincipal: window.arguments[7],
2229 triggeringPrincipal: window.arguments[8],
2230 // TODO fix allowInheritPrincipal to default to false.
2231 // Default to true unless explicitly set to false because of bug 1475201.
2232 allowInheritPrincipal: window.arguments[9] !== false,
2233 csp: window.arguments[10],
2234 forceAboutBlankViewerInCurrent: !!window.arguments[6],
2236 hasValidUserGestureActivation,
2238 globalHistoryOptions,
2239 triggeringRemoteType,
2247 // Note: loadOneOrMoreURIs *must not* be called if window.arguments.length >= 3.
2248 // Such callers expect that window.arguments[0] is handled as a single URI.
2251 Services.scriptSecurityManager.getSystemPrincipal(),
2259 * Use this function as an entry point to schedule tasks that
2260 * need to run once per window after startup, and can be scheduled
2261 * by using an idle callback.
2263 * The functions scheduled here will fire from idle callbacks
2264 * once every window has finished being restored by session
2265 * restore, and after the equivalent only-once tasks
2266 * have run (from _scheduleStartupIdleTasks in BrowserGlue.sys.mjs).
2268 _schedulePerWindowIdleTasks() {
2269 // Bail out if the window has been closed in the meantime.
2270 if (window.closed) {
2274 function scheduleIdleTask(func, options) {
2275 requestIdleCallback(function idleTaskRunner() {
2276 if (!window.closed) {
2282 scheduleIdleTask(() => {
2283 // Initialize the Sync UI
2287 scheduleIdleTask(() => {
2288 // Read prefers-reduced-motion setting
2289 let reduceMotionQuery = window.matchMedia(
2290 "(prefers-reduced-motion: reduce)"
2292 function readSetting() {
2293 gReduceMotionSetting = reduceMotionQuery.matches;
2295 reduceMotionQuery.addListener(readSetting);
2299 scheduleIdleTask(() => {
2300 // setup simple gestures support
2301 gGestureSupport.init(true);
2303 // setup history swipe animation
2304 gHistorySwipeAnimation.init();
2307 scheduleIdleTask(() => {
2308 gBrowserThumbnails.init();
2313 // Initialize the download manager some time after the app starts so that
2314 // auto-resume downloads begin (such as after crashing or quitting with
2315 // active downloads) and speeds up the first-load of the download manager UI.
2316 // If the user manually opens the download manager before the timeout, the
2317 // downloads will start right away, and initializing again won't hurt.
2319 DownloadsCommon.initializeAllDataLinks();
2320 ChromeUtils.importESModule(
2321 "resource:///modules/DownloadsTaskbar.sys.mjs"
2322 ).DownloadsTaskbar.registerIndicator(window);
2323 if (AppConstants.platform == "macosx") {
2324 ChromeUtils.importESModule(
2325 "resource:///modules/DownloadsMacFinderProgress.sys.mjs"
2326 ).DownloadsMacFinderProgress.register();
2328 Services.telemetry.setEventRecordingEnabled("downloads", true);
2337 scheduleIdleTask(() => Win7Features.onOpenWindow());
2340 scheduleIdleTask(async () => {
2341 NewTabPagePreloading.maybeCreatePreloadedBrowser(window);
2344 scheduleIdleTask(() => {
2348 // This should always go last, since the idle tasks (except for the ones with
2349 // timeouts) should execute in order. Note that this observer notification is
2350 // not guaranteed to fire, since the window could close before we get here.
2351 scheduleIdleTask(() => {
2352 this.idleTaskPromiseResolve();
2353 Services.obs.notifyObservers(
2355 "browser-idle-startup-tasks-finished"
2360 // Returns the URI(s) to load at startup if it is immediately known, or a
2361 // promise resolving to the URI to load.
2362 get uriToLoadPromise() {
2363 delete this.uriToLoadPromise;
2364 return (this.uriToLoadPromise = (function () {
2365 // window.arguments[0]: URI to load (string), or an nsIArray of
2366 // nsISupportsStrings to load, or a xul:tab of
2367 // a tabbrowser, which will be replaced by this
2368 // window (for this case, all other arguments are
2370 let uri = window.arguments?.[0];
2371 if (!uri || window.XULElement.isInstance(uri)) {
2375 let defaultArgs = BrowserHandler.defaultArgs;
2377 // If the given URI is different from the homepage, we want to load it.
2378 if (uri != defaultArgs) {
2379 AboutNewTab.noteNonDefaultStartup();
2381 if (uri instanceof Ci.nsIArray) {
2382 // Transform the nsIArray of nsISupportsString's into a JS Array of
2385 uri.enumerate(Ci.nsISupportsString),
2386 supportStr => supportStr.data
2388 } else if (uri instanceof Ci.nsISupportsString) {
2394 // The URI appears to be the the homepage. We want to load it only if
2395 // session restore isn't about to override the homepage.
2396 let willOverride = SessionStartup.willOverrideHomepage;
2397 if (typeof willOverride == "boolean") {
2398 return willOverride ? null : uri;
2400 return willOverride.then(willOverrideHomepage =>
2401 willOverrideHomepage ? null : uri
2406 // Calls the given callback with the URI to load at startup.
2407 // Synchronously if possible, or after uriToLoadPromise resolves otherwise.
2408 _callWithURIToLoad(callback) {
2409 let uriToLoad = this.uriToLoadPromise;
2410 if (uriToLoad && uriToLoad.then) {
2411 uriToLoad.then(callback);
2413 callback(uriToLoad);
2418 gUIDensity.uninit();
2420 TabsInTitlebar.uninit();
2422 ToolbarIconColor.uninit();
2424 // In certain scenarios it's possible for unload to be fired before onload,
2425 // (e.g. if the window is being closed after browser.js loads but before the
2426 // load completes). In that case, there's nothing to do here.
2427 if (!this._loadHandled) {
2431 // First clean up services initialized in gBrowserInit.onLoad (or those whose
2432 // uninit methods don't depend on the services having been initialized).
2434 CombinedStopReload.uninit();
2436 gGestureSupport.init(false);
2438 gHistorySwipeAnimation.uninit();
2440 FullScreen.uninit();
2444 gExtensionsNotifications.uninit();
2445 gUnifiedExtensions.uninit();
2448 gBrowser.removeProgressListener(window.XULBrowserWindow);
2449 gBrowser.removeTabsProgressListener(window.TabsProgressListener);
2452 PlacesToolbarHelper.uninit();
2454 BookmarkingUI.uninit();
2456 TabletModeUpdater.uninit();
2458 gTabletModePageCounter.finish();
2460 CaptivePortalWatcher.uninit();
2464 DownloadsButton.uninit();
2466 if (gToolbarKeyNavEnabled) {
2467 ToolbarKeyboardNavigator.uninit();
2470 BrowserSearch.uninit();
2472 NewTabPagePreloading.removePreloadedBrowser(window);
2474 FirefoxViewHandler.uninit();
2476 ShoppingSidebarManager.uninit();
2478 // Now either cancel delayedStartup, or clean up the services initialized from
2480 if (this._boundDelayedStartup) {
2481 this._cancelDelayedStartup();
2484 Win7Features.onCloseWindow();
2486 Services.prefs.removeObserver(ctrlTab.prefName, ctrlTab);
2488 gBrowserThumbnails.uninit();
2489 gProtectionsHandler.uninit();
2492 Services.obs.removeObserver(gIdentityHandler, "perm-changed");
2493 Services.obs.removeObserver(gRemoteControl, "devtools-socket");
2494 Services.obs.removeObserver(gRemoteControl, "marionette-listening");
2495 Services.obs.removeObserver(gRemoteControl, "remote-listening");
2496 Services.obs.removeObserver(
2497 gSessionHistoryObserver,
2498 "browser:purge-session-history"
2500 Services.obs.removeObserver(
2501 gStoragePressureObserver,
2502 "QuotaManager::StoragePressure"
2504 Services.obs.removeObserver(gXPInstallObserver, "addon-install-disabled");
2505 Services.obs.removeObserver(gXPInstallObserver, "addon-install-started");
2506 Services.obs.removeObserver(gXPInstallObserver, "addon-install-blocked");
2507 Services.obs.removeObserver(
2509 "addon-install-fullscreen-blocked"
2511 Services.obs.removeObserver(
2513 "addon-install-origin-blocked"
2515 Services.obs.removeObserver(
2517 "addon-install-policy-blocked"
2519 Services.obs.removeObserver(
2521 "addon-install-webapi-blocked"
2523 Services.obs.removeObserver(gXPInstallObserver, "addon-install-failed");
2524 Services.obs.removeObserver(
2526 "addon-install-confirmation"
2528 Services.obs.removeObserver(gKeywordURIFixup, "keyword-uri-fixup");
2530 if (AppConstants.platform == "win") {
2531 MenuTouchModeObserver.uninit();
2533 BrowserOffline.uninit();
2534 CanvasPermissionPromptHelper.uninit();
2535 WebAuthnPromptHelper.uninit();
2539 // Final window teardown, do this last.
2541 window.XULBrowserWindow = null;
2542 window.docShell.treeOwner
2543 .QueryInterface(Ci.nsIInterfaceRequestor)
2544 .getInterface(Ci.nsIAppWindow).XULBrowserWindow = null;
2545 window.browserDOMWindow = null;
2549 ChromeUtils.defineLazyGetter(
2551 "_firstContentWindowPaintDeferred",
2552 () => PromiseUtils.defer()
2555 gBrowserInit.idleTasksFinishedPromise = new Promise(resolve => {
2556 gBrowserInit.idleTaskPromiseResolve = resolve;
2559 function HandleAppCommandEvent(evt) {
2560 switch (evt.command) {
2568 BrowserReloadSkipCache();
2571 if (XULBrowserWindow.stopCommand.getAttribute("disabled") != "true") {
2576 BrowserSearch.webSearch();
2579 SidebarUI.toggle("viewBookmarksSidebar");
2588 BrowserCloseTabOrWindow();
2591 gLazyFindCommand("onFindCommand");
2594 openHelpLink("firefox-help");
2597 BrowserOpenFileWindow();
2600 PrintUtils.startPrintWindow(gBrowser.selectedBrowser.browsingContext);
2603 saveBrowser(gBrowser.selectedBrowser);
2606 MailIntegration.sendLinkForBrowser(gBrowser.selectedBrowser);
2611 evt.stopPropagation();
2612 evt.preventDefault();
2615 function gotoHistoryIndex(aEvent) {
2616 aEvent = getRootEvent(aEvent);
2618 let index = aEvent.target.getAttribute("index");
2623 let where = whereToOpenLink(aEvent);
2625 if (where == "current") {
2626 // Normal click. Go there in the current tab and update session history.
2629 gBrowser.gotoIndex(index);
2635 // Modified click. Go there in a new tab/window.
2637 let historyindex = aEvent.target.getAttribute("historyindex");
2638 duplicateTabIn(gBrowser.selectedTab, where, Number(historyindex));
2642 function BrowserForward(aEvent) {
2643 let where = whereToOpenLink(aEvent, false, true);
2645 if (where == "current") {
2647 gBrowser.goForward();
2650 duplicateTabIn(gBrowser.selectedTab, where, 1);
2654 function BrowserBack(aEvent) {
2655 let where = whereToOpenLink(aEvent, false, true);
2657 if (where == "current") {
2662 duplicateTabIn(gBrowser.selectedTab, where, -1);
2666 function BrowserHandleBackspace() {
2667 switch (Services.prefs.getIntPref("browser.backspace_action")) {
2672 goDoCommand("cmd_scrollPageUp");
2677 function BrowserHandleShiftBackspace() {
2678 switch (Services.prefs.getIntPref("browser.backspace_action")) {
2683 goDoCommand("cmd_scrollPageDown");
2688 function BrowserStop() {
2689 gBrowser.webNavigation.stop(Ci.nsIWebNavigation.STOP_ALL);
2692 function BrowserReloadOrDuplicate(aEvent) {
2693 aEvent = getRootEvent(aEvent);
2694 let accelKeyPressed =
2695 AppConstants.platform == "macosx" ? aEvent.metaKey : aEvent.ctrlKey;
2696 var backgroundTabModifier = aEvent.button == 1 || accelKeyPressed;
2698 if (aEvent.shiftKey && !backgroundTabModifier) {
2699 BrowserReloadSkipCache();
2703 let where = whereToOpenLink(aEvent, false, true);
2704 if (where == "current") {
2707 duplicateTabIn(gBrowser.selectedTab, where);
2711 function BrowserReload() {
2712 if (gBrowser.currentURI.schemeIs("view-source")) {
2713 // Bug 1167797: For view source, we always skip the cache
2714 return BrowserReloadSkipCache();
2716 const reloadFlags = Ci.nsIWebNavigation.LOAD_FLAGS_NONE;
2717 BrowserReloadWithFlags(reloadFlags);
2720 const kSkipCacheFlags =
2721 Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_PROXY |
2722 Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_CACHE;
2723 function BrowserReloadSkipCache() {
2724 // Bypass proxy and cache.
2725 BrowserReloadWithFlags(kSkipCacheFlags);
2728 function BrowserHome(aEvent) {
2729 if (aEvent && "button" in aEvent && aEvent.button == 2) {
2730 // right-click: do nothing
2734 var homePage = HomePage.get(window);
2735 var where = whereToOpenLink(aEvent, false, true);
2737 var notifyObservers;
2739 // Don't load the home page in pinned or hidden tabs (e.g. Firefox View).
2741 where == "current" &&
2742 (gBrowser?.selectedTab.pinned || gBrowser?.selectedTab.hidden)
2747 // openTrustedLinkIn in utilityOverlay.js doesn't handle loading multiple pages
2750 // If we're going to load an initial page in the current tab as the
2751 // home page, we set initialPageLoadedFromURLBar so that the URL
2752 // bar is cleared properly (even during a remoteness flip).
2753 if (isInitialPage(homePage)) {
2754 gBrowser.selectedBrowser.initialPageLoadedFromUserAction = homePage;
2758 Services.scriptSecurityManager.getSystemPrincipal(),
2761 if (isBlankPageURL(homePage)) {
2764 gBrowser.selectedBrowser.focus();
2766 notifyObservers = true;
2767 aEvent?.preventDefault();
2771 urls = homePage.split("|");
2772 var loadInBackground = Services.prefs.getBoolPref(
2773 "browser.tabs.loadBookmarksInBackground",
2776 // The homepage observer event should only be triggered when the homepage opens
2777 // in the foreground. This is mostly to support the homepage changed by extension
2778 // doorhanger which doesn't currently support background pages. This may change in
2780 notifyObservers = !loadInBackground;
2781 gBrowser.loadTabs(urls, {
2782 inBackground: loadInBackground,
2783 triggeringPrincipal:
2784 Services.scriptSecurityManager.getSystemPrincipal(),
2787 if (!loadInBackground) {
2788 if (isBlankPageURL(homePage)) {
2791 gBrowser.selectedBrowser.focus();
2794 aEvent?.preventDefault();
2797 // OpenBrowserWindow will trigger the observer event, so no need to do so here.
2798 notifyObservers = false;
2799 OpenBrowserWindow();
2800 aEvent?.preventDefault();
2803 if (notifyObservers) {
2804 // A notification for when a user has triggered their homepage. This is used
2805 // to display a doorhanger explaining that an extension has modified the
2806 // homepage, if necessary. Observers are only notified if the homepage
2807 // becomes the active page.
2808 Services.obs.notifyObservers(null, "browser-open-homepage-start");
2812 function loadOneOrMoreURIs(aURIString, aTriggeringPrincipal, aCsp) {
2813 // we're not a browser window, pass the URI string to a new browser window
2814 if (window.location.href != AppConstants.BROWSER_CHROME_URL) {
2816 AppConstants.BROWSER_CHROME_URL,
2824 // This function throws for certain malformed URIs, so use exception handling
2825 // so that we don't disrupt startup
2827 gBrowser.loadTabs(aURIString.split("|"), {
2828 inBackground: false,
2830 triggeringPrincipal: aTriggeringPrincipal,
2836 function openLocation(event) {
2837 if (window.location.href == AppConstants.BROWSER_CHROME_URL) {
2839 gURLBar.view.autoOpen({ event });
2843 // If there's an open browser window, redirect the command there.
2844 let win = URILoadingHelper.getTargetWindow(window);
2851 // There are no open browser windows; open a new one.
2853 AppConstants.BROWSER_CHROME_URL,
2855 "chrome,all,dialog=no",
2860 function BrowserOpenTab({ event, url } = {}) {
2861 let werePassedURL = !!url;
2862 url ??= BROWSER_NEW_TAB_URL;
2863 let searchClipboard = gMiddleClickNewTabUsesPasteboard && event?.button == 1;
2865 let relatedToCurrent = false;
2869 where = whereToOpenLink(event, false, true);
2874 // When accel-click or middle-click are used, open the new tab as
2875 // related to the current tab.
2876 relatedToCurrent = true;
2884 // A notification intended to be useful for modular peformance tracking
2885 // starting as close as is reasonably possible to the time when the user
2886 // expressed the intent to open a new tab. Since there are a lot of
2887 // entry points, this won't catch every single tab created, but most
2888 // initiated by the user should go through here.
2890 // Note 1: This notification gets notified with a promise that resolves
2891 // with the linked browser when the tab gets created
2892 // Note 2: This is also used to notify a user that an extension has changed
2893 // the New Tab page.
2894 Services.obs.notifyObservers(
2896 wrappedJSObject: new Promise(resolve => {
2899 resolveOnNewTabCreated: resolve,
2901 if (!werePassedURL && searchClipboard) {
2902 let clipboard = readFromClipboard();
2903 clipboard = UrlbarUtils.stripUnsafeProtocolOnPaste(clipboard).trim();
2906 options.allowThirdPartyFixup = true;
2909 openTrustedLinkIn(url, where, options);
2912 "browser-open-newtab-start"
2916 var gLastOpenDirectory = {
2919 if (!this._lastDir || !this._lastDir.exists()) {
2921 this._lastDir = Services.prefs.getComplexValue(
2922 "browser.open.lastDir",
2925 if (!this._lastDir.exists()) {
2926 this._lastDir = null;
2930 return this._lastDir;
2934 if (!val || !val.isDirectory()) {
2940 this._lastDir = val.clone();
2942 // Don't save the last open directory pref inside the Private Browsing mode
2943 if (!PrivateBrowsingUtils.isWindowPrivate(window)) {
2944 Services.prefs.setComplexValue(
2945 "browser.open.lastDir",
2952 this._lastDir = null;
2956 function BrowserOpenFileWindow() {
2957 // Get filepicker component.
2959 const nsIFilePicker = Ci.nsIFilePicker;
2960 let fp = Cc["@mozilla.org/filepicker;1"].createInstance(nsIFilePicker);
2961 let fpCallback = function fpCallback_done(aResult) {
2962 if (aResult == nsIFilePicker.returnOK) {
2965 gLastOpenDirectory.path = fp.file.parent.QueryInterface(Ci.nsIFile);
2968 openTrustedLinkIn(fp.fileURL.spec, "current");
2974 gNavigatorBundle.getString("openFile"),
2975 nsIFilePicker.modeOpen
2978 nsIFilePicker.filterAll |
2979 nsIFilePicker.filterText |
2980 nsIFilePicker.filterImages |
2981 nsIFilePicker.filterXML |
2982 nsIFilePicker.filterHTML |
2983 nsIFilePicker.filterPDF
2985 fp.displayDirectory = gLastOpenDirectory.path;
2986 fp.open(fpCallback);
2990 function BrowserCloseTabOrWindow(event) {
2991 // If we're not a browser window, just close the window.
2992 if (window.location.href != AppConstants.BROWSER_CHROME_URL) {
2997 // In a multi-select context, close all selected tabs
2998 if (gBrowser.multiSelectedTabsCount) {
2999 gBrowser.removeMultiSelectedTabs();
3003 // Keyboard shortcuts that would close a tab that is pinned select the first
3004 // unpinned tab instead.
3007 (event.ctrlKey || event.metaKey || event.altKey) &&
3008 gBrowser.selectedTab.pinned
3010 if (gBrowser.visibleTabs.length > gBrowser._numPinnedTabs) {
3011 gBrowser.tabContainer.selectedIndex = gBrowser._numPinnedTabs;
3016 // If the current tab is the last one, this will close the window.
3017 gBrowser.removeCurrentTab({ animate: true });
3020 function BrowserTryToCloseWindow(event) {
3021 if (WindowIsClosing(event)) {
3023 } // WindowIsClosing does all the necessary checks
3026 function getLoadContext() {
3027 return window.docShell.QueryInterface(Ci.nsILoadContext);
3030 function readFromClipboard() {
3034 // Create transferable that will transfer the text.
3035 var trans = Cc["@mozilla.org/widget/transferable;1"].createInstance(
3038 trans.init(getLoadContext());
3040 trans.addDataFlavor("text/plain");
3042 // If available, use selection clipboard, otherwise global one
3043 let clipboard = Services.clipboard;
3044 if (clipboard.isClipboardTypeSupported(clipboard.kSelectionClipboard)) {
3045 clipboard.getData(trans, clipboard.kSelectionClipboard);
3047 clipboard.getData(trans, clipboard.kGlobalClipboard);
3051 trans.getTransferData("text/plain", data);
3054 data = data.value.QueryInterface(Ci.nsISupportsString);
3063 * Open the View Source dialog.
3066 * An object with the following properties:
3069 * A string URL for the page we'd like to view the source of.
3070 * browser (optional):
3071 * The browser containing the document that we would like to view the
3072 * source of. This is required if outerWindowID is passed.
3073 * outerWindowID (optional):
3074 * The outerWindowID of the content window containing the document that
3075 * we want to view the source of. You only need to provide this if you
3076 * want to attempt to retrieve the document source from the network
3078 * lineNumber (optional):
3079 * The line number to focus on once the source is loaded.
3081 async function BrowserViewSourceOfDocument(args) {
3082 // Check if external view source is enabled. If so, try it. If it fails,
3083 // fallback to internal view source.
3084 if (Services.prefs.getBoolPref("view_source.editor.external")) {
3086 await top.gViewSourceUtils.openInExternalEditor(args);
3091 let tabBrowser = gBrowser;
3092 let preferredRemoteType;
3093 let initialBrowsingContextGroupId;
3095 preferredRemoteType = args.browser.remoteType;
3096 initialBrowsingContextGroupId = args.browser.browsingContext.group.id;
3100 "BrowserViewSourceOfDocument should be passed the " +
3101 "subject browser if called from a window without " +
3105 // Some internal URLs (such as specific chrome: and about: URLs that are
3106 // not yet remote ready) cannot be loaded in a remote browser. View
3107 // source in tab expects the new view source browser's remoteness to match
3108 // that of the original URL, so disable remoteness if necessary for this
3110 var oa = E10SUtils.predictOriginAttributes({ window });
3111 preferredRemoteType = E10SUtils.getRemoteTypeForURI(
3113 gMultiProcessBrowser,
3115 E10SUtils.DEFAULT_REMOTE_TYPE,
3121 // In the case of popups, we need to find a non-popup browser window.
3122 if (!tabBrowser || !window.toolbar.visible) {
3123 // This returns only non-popup browser windows by default.
3124 let browserWindow = BrowserWindowTracker.getTopWindow();
3125 tabBrowser = browserWindow.gBrowser;
3128 const inNewWindow = !Services.prefs.getBoolPref("view_source.tab");
3130 // `viewSourceInBrowser` will load the source content from the page
3131 // descriptor for the tab (when possible) or fallback to the network if
3132 // that fails. Either way, the view source module will manage the tab's
3133 // location, so use "about:blank" here to avoid unnecessary redundant
3135 let tab = tabBrowser.addTab("about:blank", {
3136 relatedToCurrent: true,
3137 inBackground: inNewWindow,
3138 skipAnimation: inNewWindow,
3139 preferredRemoteType,
3140 initialBrowsingContextGroupId,
3141 triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
3144 args.viewSourceBrowser = tabBrowser.getBrowserForTab(tab);
3145 top.gViewSourceUtils.viewSourceInBrowser(args);
3148 tabBrowser.hideTab(tab);
3149 tabBrowser.replaceTabWithWindow(tab);
3154 * Opens the View Source dialog for the source loaded in the root
3155 * top-level document of the browser. This is really just a
3156 * convenience wrapper around BrowserViewSourceOfDocument.
3159 * The browser that we want to load the source of.
3161 function BrowserViewSource(browser) {
3162 BrowserViewSourceOfDocument({
3164 outerWindowID: browser.outerWindowID,
3165 URL: browser.currentURI.spec,
3169 // documentURL - URL of the document to view, or null for this window's document
3170 // initialTab - name of the initial tab to display, or null for the first tab
3171 // imageElement - image to load in the Media Tab of the Page Info window; can be null/omitted
3172 // browsingContext - the browsingContext of the frame that we want to view information about; can be null/omitted
3173 // browser - the browser containing the document we're interested in inspecting; can be null/omitted
3174 function BrowserPageInfo(
3181 if (HTMLDocument.isInstance(documentURL)) {
3183 "Please pass the location URL instead of the document " +
3184 "to BrowserPageInfo() as the first argument.",
3185 "https://bugzilla.mozilla.org/show_bug.cgi?id=1238180"
3187 documentURL = documentURL.location;
3190 let args = { initialTab, imageElement, browsingContext, browser };
3192 documentURL = documentURL || window.gBrowser.selectedBrowser.currentURI.spec;
3194 let isPrivate = PrivateBrowsingUtils.isWindowPrivate(window);
3196 // Check for windows matching the url
3197 for (let currentWindow of Services.wm.getEnumerator("Browser:page-info")) {
3198 if (currentWindow.closed) {
3202 currentWindow.document.documentElement.getAttribute("relatedUrl") ==
3204 PrivateBrowsingUtils.isWindowPrivate(currentWindow) == isPrivate
3206 currentWindow.focus();
3207 currentWindow.resetPageInfo(args);
3208 return currentWindow;
3212 // We didn't find a matching window, so open a new one.
3213 let options = "chrome,toolbar,dialog=no,resizable";
3215 // Ensure the window groups correctly in the Windows taskbar
3217 options += ",private";
3220 "chrome://browser/content/pageinfo/pageInfo.xhtml",
3227 function UpdateUrlbarSearchSplitterState() {
3228 var splitter = document.getElementById("urlbar-search-splitter");
3229 var urlbar = document.getElementById("urlbar-container");
3230 var searchbar = document.getElementById("search-container");
3232 if (document.documentElement.getAttribute("customizing") == "true") {
3239 // If the splitter is already in the right place, we don't need to do anything:
3242 ((splitter.nextElementSibling == searchbar &&
3243 splitter.previousElementSibling == urlbar) ||
3244 (splitter.nextElementSibling == urlbar &&
3245 splitter.previousElementSibling == searchbar))
3251 let resizebefore = "none";
3252 let resizeafter = "none";
3253 if (urlbar && searchbar) {
3254 if (urlbar.nextElementSibling == searchbar) {
3255 resizeafter = "sibling";
3256 ibefore = searchbar;
3257 } else if (searchbar.nextElementSibling == urlbar) {
3258 resizebefore = "sibling";
3265 splitter = document.createXULElement("splitter");
3266 splitter.id = "urlbar-search-splitter";
3267 splitter.setAttribute("resizebefore", resizebefore);
3268 splitter.setAttribute("resizeafter", resizeafter);
3269 splitter.setAttribute("skipintoolbarset", "true");
3270 splitter.setAttribute("overflows", "false");
3271 splitter.className = "chromeclass-toolbar-additional";
3273 urlbar.parentNode.insertBefore(splitter, ibefore);
3274 } else if (splitter) {
3279 function UpdatePopupNotificationsVisibility() {
3280 // Only need to update PopupNotifications if it has already been initialized
3281 // for this window (i.e. its getter no longer exists).
3282 if (!Object.getOwnPropertyDescriptor(window, "PopupNotifications").get) {
3283 // Notify PopupNotifications that the visible anchors may have changed. This
3284 // also checks the suppression state according to the "shouldSuppress"
3285 // function defined earlier in this file.
3286 PopupNotifications.anchorVisibilityChange();
3289 // This is similar to the above, but for notifications attached to the
3290 // hamburger menu icon (such as update notifications and add-on install
3292 PanelUI?.updateNotifications();
3295 function PageProxyClickHandler(aEvent) {
3296 if (aEvent.button == 1 && Services.prefs.getBoolPref("middlemouse.paste")) {
3297 middleMousePaste(aEvent);
3302 * Handle command events bubbling up from error page content
3303 * or from about:newtab or from remote error pages that invoke
3304 * us via async messaging.
3306 var BrowserOnClick = {
3307 ignoreWarningLink(reason, blockedInfo, browsingContext) {
3308 let triggeringPrincipal =
3309 blockedInfo.triggeringPrincipal ||
3310 _createNullPrincipalFromTabUserContextId();
3312 // Allow users to override and continue through to the site,
3313 // but add a notify bar as a reminder, so that they don't lose
3314 // track after, e.g., tab switching.
3315 // Note that we have to use the passed URI info and can't just
3316 // rely on the document URI, because the latter contains
3317 // additional query parameters that should be stripped.
3318 browsingContext.fixupAndLoadURIString(blockedInfo.uri, {
3319 triggeringPrincipal,
3320 flags: Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_CLASSIFIER,
3323 // We can't use browser.contentPrincipal which is principal of about:blocked
3324 // Create one from uri with current principal origin attributes
3325 let principal = Services.scriptSecurityManager.createContentPrincipal(
3326 Services.io.newURI(blockedInfo.uri),
3327 browsingContext.currentWindowGlobal.documentPrincipal.originAttributes
3329 Services.perms.addFromPrincipal(
3332 Ci.nsIPermissionManager.ALLOW_ACTION,
3333 Ci.nsIPermissionManager.EXPIRE_SESSION
3338 label: gNavigatorBundle.getString(
3339 "safebrowsing.getMeOutOfHereButton.label"
3341 accessKey: gNavigatorBundle.getString(
3342 "safebrowsing.getMeOutOfHereButton.accessKey"
3345 getMeOutOfHere(browsingContext);
3351 if (reason === "malware") {
3352 let reportUrl = gSafeBrowsing.getReportURL("MalwareMistake", blockedInfo);
3353 title = gNavigatorBundle.getString("safebrowsing.reportedAttackSite");
3354 // There's no button if we can not get report url, for example if the provider
3355 // of blockedInfo is not Google
3358 label: gNavigatorBundle.getString(
3359 "safebrowsing.notAnAttackButton.label"
3361 accessKey: gNavigatorBundle.getString(
3362 "safebrowsing.notAnAttackButton.accessKey"
3365 openTrustedLinkIn(reportUrl, "tab");
3369 } else if (reason === "phishing") {
3370 let reportUrl = gSafeBrowsing.getReportURL("PhishMistake", blockedInfo);
3371 title = gNavigatorBundle.getString("safebrowsing.deceptiveSite");
3372 // There's no button if we can not get report url, for example if the provider
3373 // of blockedInfo is not Google
3376 label: gNavigatorBundle.getString(
3377 "safebrowsing.notADeceptiveSiteButton.label"
3379 accessKey: gNavigatorBundle.getString(
3380 "safebrowsing.notADeceptiveSiteButton.accessKey"
3383 openTrustedLinkIn(reportUrl, "tab");
3387 } else if (reason === "unwanted") {
3388 title = gNavigatorBundle.getString("safebrowsing.reportedUnwantedSite");
3389 // There is no button for reporting errors since Google doesn't currently
3390 // provide a URL endpoint for these reports.
3391 } else if (reason === "harmful") {
3392 title = gNavigatorBundle.getString("safebrowsing.reportedHarmfulSite");
3393 // There is no button for reporting errors since Google doesn't currently
3394 // provide a URL endpoint for these reports.
3397 SafeBrowsingNotificationBox.show(title, buttons);
3402 * Re-direct the browser to a known-safe page. This function is
3403 * used when, for example, the user browses to a known malware page
3404 * and is presented with about:blocked. The "Get me out of here!"
3405 * button should take the user to the default start page so that even
3406 * when their own homepage is infected, we can get them somewhere safe.
3408 function getMeOutOfHere(browsingContext) {
3409 browsingContext.top.fixupAndLoadURIString(getDefaultHomePage(), {
3410 triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(), // Also needs to load homepage
3415 * Return the default start page for the cases when the user's own homepage is
3416 * infected, so we can get them somewhere safe.
3418 function getDefaultHomePage() {
3419 let url = BROWSER_NEW_TAB_URL;
3420 if (PrivateBrowsingUtils.isWindowPrivate(window)) {
3423 url = HomePage.getDefault();
3424 // If url is a pipe-delimited set of pages, just take the first one.
3425 if (url.includes("|")) {
3426 url = url.split("|")[0];
3431 function BrowserFullScreen() {
3432 window.fullScreen = !window.fullScreen || BrowserHandler.kiosk;
3435 function BrowserReloadWithFlags(reloadFlags) {
3436 let unchangedRemoteness = [];
3438 for (let tab of gBrowser.selectedTabs) {
3439 let browser = tab.linkedBrowser;
3440 let url = browser.currentURI;
3441 let urlSpec = url.spec;
3442 // We need to cache the content principal here because the browser will be
3443 // reconstructed when the remoteness changes and the content prinicpal will
3444 // be cleared after reconstruction.
3445 let principal = tab.linkedBrowser.contentPrincipal;
3446 if (gBrowser.updateBrowserRemotenessByURL(browser, urlSpec)) {
3447 // If the remoteness has changed, the new browser doesn't have any
3448 // information of what was loaded before, so we need to load the previous
3450 if (tab.linkedPanel) {
3451 loadBrowserURI(browser, url, principal);
3453 // Shift to fully loaded browser and make
3454 // sure load handler is instantiated.
3455 tab.addEventListener(
3457 () => loadBrowserURI(browser, url, principal),
3460 gBrowser._insertBrowser(tab);
3463 unchangedRemoteness.push(tab);
3467 if (!unchangedRemoteness.length) {
3471 // Reset temporary permissions on the remaining tabs to reload.
3472 // This is done here because we only want to reset
3473 // permissions on user reload.
3474 for (let tab of unchangedRemoteness) {
3475 SitePermissions.clearTemporaryBlockPermissions(tab.linkedBrowser);
3476 // Also reset DOS mitigations for the basic auth prompt on reload.
3477 delete tab.linkedBrowser.authPromptAbuseCounter;
3479 gIdentityHandler.hidePopup();
3480 gPermissionPanel.hidePopup();
3482 let handlingUserInput = document.hasValidTransientUserGestureActivation;
3484 for (let tab of unchangedRemoteness) {
3485 if (tab.linkedPanel) {
3486 sendReloadMessage(tab);
3488 // Shift to fully loaded browser and make
3489 // sure load handler is instantiated.
3490 tab.addEventListener("SSTabRestoring", () => sendReloadMessage(tab), {
3493 gBrowser._insertBrowser(tab);
3497 function loadBrowserURI(browser, url, principal) {
3498 browser.loadURI(url, {
3500 triggeringPrincipal: principal,
3504 function sendReloadMessage(tab) {
3505 tab.linkedBrowser.sendMessageToActor(
3507 { flags: reloadFlags, handlingUserInput },
3513 // TODO: can we pull getPEMString in from pippki.js instead of
3514 // duplicating them here?
3515 function getPEMString(cert) {
3516 var derb64 = cert.getBase64DERString();
3517 // Wrap the Base64 string into lines of 64 characters,
3518 // with CRLF line breaks (as specified in RFC 1421).
3519 var wrapped = derb64.replace(/(\S{64}(?!$))/g, "$1\r\n");
3521 "-----BEGIN CERTIFICATE-----\r\n" +
3523 "\r\n-----END CERTIFICATE-----\r\n"
3527 var browserDragAndDrop = {
3528 canDropLink: aEvent => Services.droppedLinkHandler.canDropLink(aEvent, true),
3531 if (this.canDropLink(aEvent)) {
3532 aEvent.preventDefault();
3536 getTriggeringPrincipal(aEvent) {
3537 return Services.droppedLinkHandler.getTriggeringPrincipal(aEvent);
3541 return Services.droppedLinkHandler.getCsp(aEvent);
3544 validateURIsForDrop(aEvent, aURIs) {
3545 return Services.droppedLinkHandler.validateURIsForDrop(aEvent, aURIs);
3548 dropLinks(aEvent, aDisallowInherit) {
3549 return Services.droppedLinkHandler.dropLinks(aEvent, aDisallowInherit);
3553 var homeButtonObserver = {
3555 // disallow setting home pages that inherit the principal
3556 let links = browserDragAndDrop.dropLinks(aEvent, true);
3559 for (let link of links) {
3560 if (link.url.includes("|")) {
3561 urls.push(...link.url.split("|"));
3563 urls.push(link.url);
3568 browserDragAndDrop.validateURIsForDrop(aEvent, urls);
3573 setTimeout(openHomeDialog, 0, urls.join("|"));
3577 onDragOver(aEvent) {
3578 if (HomePage.locked) {
3581 browserDragAndDrop.dragOver(aEvent);
3582 aEvent.dropEffect = "link";
3586 function openHomeDialog(aURL) {
3587 var promptTitle = gNavigatorBundle.getString("droponhometitle");
3589 if (aURL.includes("|")) {
3590 promptMsg = gNavigatorBundle.getString("droponhomemsgMultiple");
3592 promptMsg = gNavigatorBundle.getString("droponhomemsg");
3595 var pressedVal = Services.prompt.confirmEx(
3599 Services.prompt.STD_YES_NO_BUTTONS,
3607 if (pressedVal == 0) {
3608 HomePage.set(aURL).catch(console.error);
3612 var newTabButtonObserver = {
3613 onDragOver(aEvent) {
3614 browserDragAndDrop.dragOver(aEvent);
3616 async onDrop(aEvent) {
3617 let links = browserDragAndDrop.dropLinks(aEvent);
3620 Services.prefs.getIntPref("browser.tabs.maxOpenBeforeWarn")
3622 // Sync dialog cannot be used inside drop event handler.
3623 let answer = await OpenInTabsUtils.promiseConfirmOpenInTabs(
3632 let where = aEvent.shiftKey ? "tabshifted" : "tab";
3633 let triggeringPrincipal = browserDragAndDrop.getTriggeringPrincipal(aEvent);
3634 let csp = browserDragAndDrop.getCsp(aEvent);
3635 for (let link of links) {
3637 let data = await UrlbarUtils.getShortcutOrURIAndPostData(link.url);
3638 // Allow third-party services to fixup this URL.
3639 openLinkIn(data.url, where, {
3640 postData: data.postData,
3641 allowThirdPartyFixup: true,
3642 triggeringPrincipal,
3650 var newWindowButtonObserver = {
3651 onDragOver(aEvent) {
3652 browserDragAndDrop.dragOver(aEvent);
3654 async onDrop(aEvent) {
3655 let links = browserDragAndDrop.dropLinks(aEvent);
3658 Services.prefs.getIntPref("browser.tabs.maxOpenBeforeWarn")
3660 // Sync dialog cannot be used inside drop event handler.
3661 let answer = await OpenInTabsUtils.promiseConfirmOpenInTabs(
3670 let triggeringPrincipal = browserDragAndDrop.getTriggeringPrincipal(aEvent);
3671 let csp = browserDragAndDrop.getCsp(aEvent);
3672 for (let link of links) {
3674 let data = await UrlbarUtils.getShortcutOrURIAndPostData(link.url);
3675 // Allow third-party services to fixup this URL.
3676 openLinkIn(data.url, "window", {
3677 // TODO fix allowInheritPrincipal
3678 // (this is required by javascript: drop to the new window) Bug 1475201
3679 allowInheritPrincipal: true,
3680 postData: data.postData,
3681 allowThirdPartyFixup: true,
3682 triggeringPrincipal,
3690 const BrowserSearch = {
3691 _searchInitComplete: false,
3694 Services.obs.addObserver(this, "browser-search-engine-modified");
3697 delayedStartupInit() {
3698 // Asynchronously initialize the search service if necessary, to get the
3699 // current engine for working out the placeholder.
3700 this._updateURLBarPlaceholderFromDefaultEngine(
3701 PrivateBrowsingUtils.isWindowPrivate(window),
3702 // Delay the update for this until so that we don't change it while
3703 // the user is looking at it / isn't expecting it.
3706 this._searchInitComplete = true;
3711 Services.obs.removeObserver(this, "browser-search-engine-modified");
3714 observe(engine, topic, data) {
3715 // There are two kinds of search engine objects, nsISearchEngine objects and
3716 // plain { uri, title, icon } objects. `engine` in this method is the
3717 // former. The browser.engines and browser.hiddenEngines arrays are the
3718 // latter, and they're the engines offered by the the page in the browser.
3720 // The two types of engines are currently related by their titles/names,
3721 // although that may change; see bug 335102.
3722 let engineName = engine.wrappedJSObject.name;
3724 case "engine-removed":
3725 // An engine was removed from the search service. If a page is offering
3726 // the engine, then the engine needs to be added back to the corresponding
3727 // browser's offered engines.
3728 this._addMaybeOfferedEngine(engineName);
3730 case "engine-added":
3731 // An engine was added to the search service. If a page is offering the
3732 // engine, then the engine needs to be removed from the corresponding
3733 // browser's offered engines.
3734 this._removeMaybeOfferedEngine(engineName);
3736 case "engine-default":
3738 this._searchInitComplete &&
3739 !PrivateBrowsingUtils.isWindowPrivate(window)
3741 this._updateURLBarPlaceholder(engineName, false);
3744 case "engine-default-private":
3746 this._searchInitComplete &&
3747 PrivateBrowsingUtils.isWindowPrivate(window)
3749 this._updateURLBarPlaceholder(engineName, true);
3755 _addMaybeOfferedEngine(engineName) {
3756 let selectedBrowserOffersEngine = false;
3757 for (let browser of gBrowser.browsers) {
3758 for (let i = 0; i < (browser.hiddenEngines || []).length; i++) {
3759 if (browser.hiddenEngines[i].title == engineName) {
3760 if (!browser.engines) {
3761 browser.engines = [];
3763 browser.engines.push(browser.hiddenEngines[i]);
3764 browser.hiddenEngines.splice(i, 1);
3765 if (browser == gBrowser.selectedBrowser) {
3766 selectedBrowserOffersEngine = true;
3772 if (selectedBrowserOffersEngine) {
3773 this.updateOpenSearchBadge();
3777 _removeMaybeOfferedEngine(engineName) {
3778 let selectedBrowserOffersEngine = false;
3779 for (let browser of gBrowser.browsers) {
3780 for (let i = 0; i < (browser.engines || []).length; i++) {
3781 if (browser.engines[i].title == engineName) {
3782 if (!browser.hiddenEngines) {
3783 browser.hiddenEngines = [];
3785 browser.hiddenEngines.push(browser.engines[i]);
3786 browser.engines.splice(i, 1);
3787 if (browser == gBrowser.selectedBrowser) {
3788 selectedBrowserOffersEngine = true;
3794 if (selectedBrowserOffersEngine) {
3795 this.updateOpenSearchBadge();
3800 * Initializes the urlbar placeholder to the pre-saved engine name. We do this
3801 * via a preference, to avoid needing to synchronously init the search service.
3803 * This should be called around the time of DOMContentLoaded, so that it is
3804 * initialized quickly before the user sees anything.
3806 * Note: If the preference doesn't exist, we don't do anything as the default
3807 * placeholder is a string which doesn't have the engine name; however, this
3808 * can be overridden using the `force` parameter.
3810 * @param {Boolean} force If true and the preference doesn't exist, the
3811 * placeholder will be set to the default version
3812 * without an engine name ("Search or enter address").
3814 initPlaceHolder(force = false) {
3816 "browser.urlbar.placeholderName" +
3817 (PrivateBrowsingUtils.isWindowPrivate(window) ? ".private" : "");
3818 let engineName = Services.prefs.getStringPref(prefName, "");
3819 if (engineName || force) {
3820 // We can do this directly, since we know we're at DOMContentLoaded.
3821 this._setURLBarPlaceholder(engineName);
3826 * This is a wrapper around '_updateURLBarPlaceholder' that uses the
3827 * appropriate default engine to get the engine name.
3829 * @param {Boolean} isPrivate Set to true if this is a private window.
3830 * @param {Boolean} [delayUpdate] Set to true, to delay update until the
3831 * placeholder is not displayed.
3833 async _updateURLBarPlaceholderFromDefaultEngine(
3837 const getDefault = isPrivate
3838 ? Services.search.getDefaultPrivate
3839 : Services.search.getDefault;
3840 let defaultEngine = await getDefault();
3841 if (!this._searchInitComplete) {
3842 // If we haven't finished initialising, ensure the placeholder
3843 // preference is set for the next startup.
3844 SearchUIUtils.updatePlaceholderNamePreference(defaultEngine, isPrivate);
3846 this._updateURLBarPlaceholder(defaultEngine.name, isPrivate, delayUpdate);
3850 * Updates the URLBar placeholder for the specified engine, delaying the
3851 * update if required. This also saves the current engine name in preferences
3852 * for the next restart.
3854 * Note: The engine name will only be displayed for built-in engines, as we
3855 * know they should have short names.
3857 * @param {String} engineName The search engine name to use for the update.
3858 * @param {Boolean} isPrivate Set to true if this is a private window.
3859 * @param {Boolean} [delayUpdate] Set to true, to delay update until the
3860 * placeholder is not displayed.
3862 _updateURLBarPlaceholder(engineName, isPrivate, delayUpdate = false) {
3864 throw new Error("Expected an engineName to be specified");
3867 const engine = Services.search.getEngineByName(engineName);
3868 if (!engine.isAppProvided) {
3869 // Set the engine name to an empty string for non-default engines, which'll
3870 // make sure we display the default placeholder string.
3874 // Only delay if requested, and we're not displaying text in the URL bar
3876 if (delayUpdate && !gURLBar.value) {
3877 // Delays changing the URL Bar placeholder until the user is not going to be
3878 // seeing it, e.g. when there is a value entered in the bar, or if there is
3879 // a tab switch to a tab which has a url loaded. We delay the update until
3880 // the user is out of search mode since an alternative placeholder is used
3882 let placeholderUpdateListener = () => {
3883 if (gURLBar.value && !gURLBar.searchMode) {
3884 // By the time the user has switched, they may have changed the engine
3885 // again, so we need to call this function again but with the
3887 // No need to await for this to finish, we're in a listener here anyway.
3888 this._updateURLBarPlaceholderFromDefaultEngine(isPrivate, false);
3889 gURLBar.removeEventListener("input", placeholderUpdateListener);
3890 gBrowser.tabContainer.removeEventListener(
3892 placeholderUpdateListener
3897 gURLBar.addEventListener("input", placeholderUpdateListener);
3898 gBrowser.tabContainer.addEventListener(
3900 placeholderUpdateListener
3902 } else if (!gURLBar.searchMode) {
3903 this._setURLBarPlaceholder(engineName);
3908 * Sets the URLBar placeholder to either something based on the engine name,
3909 * or the default placeholder.
3911 * @param {String} name The name of the engine to use, an empty string if to
3912 * use the default placeholder.
3914 _setURLBarPlaceholder(name) {
3915 document.l10n.setAttributes(
3917 name ? "urlbar-placeholder-with-name" : "urlbar-placeholder",
3918 name ? { name } : undefined
3922 addEngine(browser, engine) {
3923 if (!this._searchInitComplete) {
3924 // We haven't finished initialising search yet. This means we can't
3925 // call getEngineByName here. Since this is only on start-up and unlikely
3926 // to happen in the normal case, we'll just return early rather than
3927 // trying to handle it asynchronously.
3930 // Check to see whether we've already added an engine with this title
3931 if (browser.engines) {
3932 if (browser.engines.some(e => e.title == engine.title)) {
3938 // If this engine (identified by title) is already in the list, add it
3939 // to the list of hidden engines rather than to the main list.
3940 if (Services.search.getEngineByName(engine.title)) {
3944 var engines = (hidden ? browser.hiddenEngines : browser.engines) || [];
3948 title: engine.title,
3950 return browser.mIconURL;
3955 browser.hiddenEngines = engines;
3957 browser.engines = engines;
3958 if (browser == gBrowser.selectedBrowser) {
3959 this.updateOpenSearchBadge();
3965 * Update the browser UI to show whether or not additional engines are
3966 * available when a page is loaded or the user switches tabs to a page that
3967 * has search engines.
3969 updateOpenSearchBadge() {
3970 gURLBar.addSearchEngineHelper.setEnginesFromBrowser(
3971 gBrowser.selectedBrowser
3974 var searchBar = this.searchBar;
3979 var engines = gBrowser.selectedBrowser.engines;
3980 if (engines && engines.length) {
3981 searchBar.setAttribute("addengines", "true");
3983 searchBar.removeAttribute("addengines");
3988 * Focuses the search bar if present on the toolbar, or the address bar,
3989 * putting it in search mode. Will do so in an existing non-popup browser
3990 * window or open a new one if necessary.
3992 webSearch: function BrowserSearch_webSearch() {
3994 window.location.href != AppConstants.BROWSER_CHROME_URL ||
3997 let win = URILoadingHelper.getTopWin(window, { skipPopups: true });
3999 // If there's an open browser window, it should handle this command
4001 win.BrowserSearch.webSearch();
4003 // If there are no open browser windows, open a new one
4004 var observer = function (subject, topic, data) {
4005 if (subject == win) {
4006 BrowserSearch.webSearch();
4007 Services.obs.removeObserver(
4009 "browser-delayed-startup-finished"
4013 win = window.openDialog(
4014 AppConstants.BROWSER_CHROME_URL,
4016 "chrome,all,dialog=no",
4019 Services.obs.addObserver(observer, "browser-delayed-startup-finished");
4024 let focusUrlBarIfSearchFieldIsNotActive = function (aSearchBar) {
4025 if (!aSearchBar || document.activeElement != aSearchBar.textbox) {
4026 // Limit the results to search suggestions, like the search bar.
4027 gURLBar.searchModeShortcut();
4031 let searchBar = this.searchBar;
4032 let placement = CustomizableUI.getPlacementOfWidget("search-container");
4033 let focusSearchBar = () => {
4034 searchBar = this.searchBar;
4036 focusUrlBarIfSearchFieldIsNotActive(searchBar);
4041 ((searchBar.parentNode.getAttribute("overflowedItem") == "true" &&
4042 placement.area == CustomizableUI.AREA_NAVBAR) ||
4043 placement.area == CustomizableUI.AREA_FIXED_OVERFLOW_PANEL)
4045 let navBar = document.getElementById(CustomizableUI.AREA_NAVBAR);
4046 navBar.overflowable.show().then(focusSearchBar);
4050 if (window.fullScreen) {
4051 FullScreen.showNavToolbox();
4055 focusUrlBarIfSearchFieldIsNotActive(searchBar);
4059 * Loads a search results page, given a set of search terms. Uses the current
4060 * engine if the search bar is visible, or the default engine otherwise.
4063 * The search terms to use for the search.
4065 * String indicating where the search should load. Most commonly used
4066 * are 'tab' or 'window', defaults to 'current'.
4068 * Whether to use the Private Browsing mode default search engine.
4069 * Defaults to `false`.
4070 * @param purpose [optional]
4071 * A string meant to indicate the context of the search request. This
4072 * allows the search service to provide a different nsISearchSubmission
4073 * depending on e.g. where the search is triggered in the UI.
4074 * @param triggeringPrincipal
4075 * The principal to use for a new window or tab.
4077 * The content security policy to use for a new window or tab.
4078 * @param inBackground [optional]
4079 * Set to true for the tab to be loaded in the background, default false.
4080 * @param engine [optional]
4081 * The search engine to use for the search.
4082 * @param tab [optional]
4083 * The tab to show the search result.
4085 * @return engine The search engine used to perform a search, or null if no
4086 * search was performed.
4093 triggeringPrincipal,
4095 inBackground = false,
4099 if (!triggeringPrincipal) {
4101 "Required argument triggeringPrincipal missing within _loadSearch"
4107 ? await Services.search.getDefaultPrivate()
4108 : await Services.search.getDefault();
4111 let submission = engine.getSubmission(searchText, null, purpose); // HTML response
4113 // getSubmission can return null if the engine doesn't have a URL
4114 // with a text/html response type. This is unlikely (since
4115 // SearchService._addEngineToStore() should fail for such an engine),
4116 // but let's be on the safe side.
4121 openLinkIn(submission.uri.spec, where || "current", {
4122 private: usePrivate && !PrivateBrowsingUtils.isWindowPrivate(window),
4123 postData: submission.postData,
4125 relatedToCurrent: true,
4126 triggeringPrincipal,
4128 targetBrowser: tab?.linkedBrowser,
4129 globalHistoryOptions: {
4130 triggeringSearchEngine: engine.name,
4134 return { engine, url: submission.uri };
4138 * Perform a search initiated from the context menu.
4140 * This should only be called from the context menu. See
4141 * BrowserSearch.loadSearch for the preferred API.
4143 async loadSearchFromContext(
4146 triggeringPrincipal,
4150 event = getRootEvent(event);
4151 let where = whereToOpenLink(event);
4152 if (where == "current") {
4153 // override: historically search opens in new tab
4156 if (usePrivate && !PrivateBrowsingUtils.isWindowPrivate(window)) {
4159 let inBackground = Services.prefs.getBoolPref(
4160 "browser.search.context.loadInBackground"
4162 if (event.button == 1 || event.ctrlKey) {
4163 inBackground = !inBackground;
4166 let { engine, url } = await BrowserSearch._loadSearch(
4171 Services.scriptSecurityManager.createNullPrincipal(
4172 triggeringPrincipal.originAttributes
4179 BrowserSearchTelemetry.recordSearch(
4180 gBrowser.selectedBrowser,
4189 * Perform a search initiated from the command line.
4191 async loadSearchFromCommandLine(terms, usePrivate, triggeringPrincipal, csp) {
4192 let { engine, url } = await BrowserSearch._loadSearch(
4197 triggeringPrincipal,
4201 BrowserSearchTelemetry.recordSearch(
4202 gBrowser.selectedBrowser,
4211 * Perform a search initiated from an extension.
4213 async loadSearchFromExtension({
4218 triggeringPrincipal,
4220 const result = await BrowserSearch._loadSearch(
4223 PrivateBrowsingUtils.isWindowPrivate(window),
4225 triggeringPrincipal,
4232 BrowserSearchTelemetry.recordSearch(
4233 gBrowser.selectedBrowser,
4241 * Returns the search bar element if it is present in the toolbar, null otherwise.
4244 return document.getElementById("searchbar");
4248 * Infobar to notify the user's search engine has been removed
4249 * and replaced with an application default search engine.
4251 * @param {string} oldEngine
4252 * name of the engine to be moved and replaced.
4253 * @param {string} newEngine
4254 * name of the application default engine to replaced the removed engine.
4256 removalOfSearchEngineNotificationBox(oldEngine, newEngine) {
4257 let messageFragment = document.createDocumentFragment();
4258 let message = document.createElement("span");
4259 let link = document.createXULElement("label", {
4263 link.href = Services.urlFormatter.formatURLPref(
4264 "browser.search.searchEngineRemoval"
4266 link.setAttribute("data-l10n-name", "remove-search-engine-article");
4267 document.l10n.setAttributes(message, "removed-search-engine-message", {
4272 message.appendChild(link);
4273 messageFragment.appendChild(message);
4277 "l10n-id": "remove-search-engine-button",
4280 const notificationBox = gNotificationBox.getNotificationWithValue(
4281 "search-engine-removal"
4283 gNotificationBox.removeNotification(notificationBox);
4288 gNotificationBox.appendNotification(
4289 "search-engine-removal",
4291 label: messageFragment,
4292 priority: gNotificationBox.PRIORITY_SYSTEM,
4297 // Update engine name in the placeholder to the new default engine name.
4298 this._updateURLBarPlaceholderFromDefaultEngine(
4299 PrivateBrowsingUtils.isWindowPrivate(window),
4301 ).catch(console.error);
4305 XPCOMUtils.defineConstant(this, "BrowserSearch", BrowserSearch);
4307 function CreateContainerTabMenu(event) {
4308 // Do not open context menus within menus.
4309 // Note that triggerNode is null if we're opened by long press.
4310 if (event.target.triggerNode?.closest("menupopup")) {
4313 createUserContextMenu(event, {
4314 useAccessKeys: false,
4315 showDefaultTab: true,
4319 function FillHistoryMenu(aParent) {
4320 // Lazily add the hover listeners on first showing and never remove them
4321 if (!aParent.hasStatusListener) {
4322 // Show history item's uri in the status bar when hovering, and clear on exit
4323 aParent.addEventListener("DOMMenuItemActive", function (aEvent) {
4324 // Only the current page should have the checked attribute, so skip it
4325 if (!aEvent.target.hasAttribute("checked")) {
4326 XULBrowserWindow.setOverLink(aEvent.target.getAttribute("uri"));
4329 aParent.addEventListener("DOMMenuItemInactive", function () {
4330 XULBrowserWindow.setOverLink("");
4333 aParent.hasStatusListener = true;
4336 // Remove old entries if any
4337 let children = aParent.children;
4338 for (var i = children.length - 1; i >= 0; --i) {
4339 if (children[i].hasAttribute("index")) {
4340 aParent.removeChild(children[i]);
4344 const MAX_HISTORY_MENU_ITEMS = 15;
4346 const tooltipBack = gNavigatorBundle.getString("tabHistory.goBack");
4347 const tooltipCurrent = gNavigatorBundle.getString("tabHistory.reloadCurrent");
4348 const tooltipForward = gNavigatorBundle.getString("tabHistory.goForward");
4350 function updateSessionHistory(sessionHistory, initial, ssInParent) {
4351 let count = ssInParent
4352 ? sessionHistory.count
4353 : sessionHistory.entries.length;
4357 // if there is only one entry now, close the popup.
4358 aParent.hidePopup();
4360 } else if (aParent.id != "backForwardMenu" && !aParent.parentNode.open) {
4361 // if the popup wasn't open before, but now needs to be, reopen the menu.
4362 // It should trigger FillHistoryMenu again. This might happen with the
4363 // delay from click-and-hold menus but skip this for the context menu
4364 // (backForwardMenu) rather than figuring out how the menu should be
4365 // positioned and opened as it is an extreme edgecase.
4366 aParent.parentNode.open = true;
4371 let index = sessionHistory.index;
4372 let half_length = Math.floor(MAX_HISTORY_MENU_ITEMS / 2);
4373 let start = Math.max(index - half_length, 0);
4375 start == 0 ? MAX_HISTORY_MENU_ITEMS : index + half_length + 1,
4379 start = Math.max(count - MAX_HISTORY_MENU_ITEMS, 0);
4382 let existingIndex = 0;
4384 for (let j = end - 1; j >= start; j--) {
4385 let entry = ssInParent
4386 ? sessionHistory.getEntryAtIndex(j)
4387 : sessionHistory.entries[j];
4388 // Explicitly check for "false" to stay backwards-compatible with session histories
4389 // from before the hasUserInteraction was implemented.
4391 BrowserUtils.navigationRequireUserInteraction &&
4392 entry.hasUserInteraction === false &&
4393 // Always allow going to the first and last navigation points.
4399 let uri = ssInParent ? entry.URI.spec : entry.url;
4402 existingIndex < children.length
4403 ? children[existingIndex]
4404 : document.createXULElement("menuitem");
4406 item.setAttribute("uri", uri);
4407 item.setAttribute("label", entry.title || uri);
4408 item.setAttribute("index", j);
4410 // Cache this so that gotoHistoryIndex doesn't need the original index
4411 item.setAttribute("historyindex", j - index);
4414 // Use list-style-image rather than the image attribute in order to
4415 // allow CSS to override this.
4416 item.style.listStyleImage = `url(page-icon:${uri})`;
4421 "unified-nav-back menuitem-iconic menuitem-with-favicon";
4422 item.setAttribute("tooltiptext", tooltipBack);
4423 } else if (j == index) {
4424 item.setAttribute("type", "radio");
4425 item.setAttribute("checked", "true");
4426 item.className = "unified-nav-current";
4427 item.setAttribute("tooltiptext", tooltipCurrent);
4430 "unified-nav-forward menuitem-iconic menuitem-with-favicon";
4431 item.setAttribute("tooltiptext", tooltipForward);
4434 if (!item.parentNode) {
4435 aParent.appendChild(item);
4442 let existingLength = children.length;
4443 while (existingIndex < existingLength) {
4444 aParent.removeChild(aParent.lastElementChild);
4450 // If session history in parent is available, use it. Otherwise, get the session history
4451 // from session store.
4452 let sessionHistory = gBrowser.selectedBrowser.browsingContext.sessionHistory;
4453 if (sessionHistory?.count) {
4454 // Don't show the context menu if there is only one item.
4455 if (sessionHistory.count <= 1) {
4459 updateSessionHistory(sessionHistory, true, true);
4461 sessionHistory = SessionStore.getSessionHistory(
4462 gBrowser.selectedTab,
4463 updateSessionHistory
4465 updateSessionHistory(sessionHistory, true, false);
4471 function BrowserDownloadsUI() {
4472 if (PrivateBrowsingUtils.isWindowPrivate(window)) {
4473 openTrustedLinkIn("about:downloads", "tab");
4475 PlacesCommandHook.showPlacesOrganizer("Downloads");
4479 function toOpenWindowByType(inType, uri, features) {
4480 var topWindow = Services.wm.getMostRecentWindow(inType);
4484 } else if (features) {
4485 window.open(uri, "_blank", features);
4490 "chrome,extrachrome,menubar,resizable,scrollbars,status,toolbar"
4496 * Open a new browser window. See `BrowserWindowTracker.openWindow` for
4499 * @return a reference to the new window.
4501 function OpenBrowserWindow(options = {}) {
4502 return BrowserWindowTracker.openWindow({ openerWindow: window, ...options });
4506 * Update the global flag that tracks whether or not any edit UI (the Edit menu,
4507 * edit-related items in the context menu, and edit-related toolbar buttons
4508 * is visible, then update the edit commands' enabled state accordingly. We use
4509 * this flag to skip updating the edit commands on focus or selection changes
4510 * when no UI is visible to improve performance (including pageload performance,
4511 * since focus changes when you load a new page).
4513 * If UI is visible, we use goUpdateGlobalEditMenuItems to set the commands'
4514 * enabled state so the UI will reflect it appropriately.
4516 * If the UI isn't visible, we enable all edit commands so keyboard shortcuts
4517 * still work and just lazily disable them as needed when the user presses a
4520 * This doesn't work on Mac, since Mac menus flash when users press their
4521 * keyboard shortcuts, so edit UI is essentially always visible on the Mac,
4522 * and we need to always update the edit commands. Thus on Mac this function
4525 function updateEditUIVisibility() {
4526 if (AppConstants.platform == "macosx") {
4530 let editMenuPopupState = document.getElementById("menu_EditPopup").state;
4531 let contextMenuPopupState = document.getElementById(
4532 "contentAreaContextMenu"
4534 let placesContextMenuPopupState =
4535 document.getElementById("placesContext").state;
4537 let oldVisible = gEditUIVisible;
4539 // The UI is visible if the Edit menu is opening or open, if the context menu
4540 // is open, or if the toolbar has been customized to include the Cut, Copy,
4541 // or Paste toolbar buttons.
4543 editMenuPopupState == "showing" ||
4544 editMenuPopupState == "open" ||
4545 contextMenuPopupState == "showing" ||
4546 contextMenuPopupState == "open" ||
4547 placesContextMenuPopupState == "showing" ||
4548 placesContextMenuPopupState == "open";
4549 const kOpenPopupStates = ["showing", "open"];
4550 if (!gEditUIVisible) {
4551 // Now check the edit-controls toolbar buttons.
4552 let placement = CustomizableUI.getPlacementOfWidget("edit-controls");
4553 let areaType = placement ? CustomizableUI.getAreaType(placement.area) : "";
4554 if (areaType == CustomizableUI.TYPE_PANEL) {
4555 let customizablePanel = PanelUI.overflowPanel;
4556 gEditUIVisible = kOpenPopupStates.includes(customizablePanel.state);
4558 areaType == CustomizableUI.TYPE_TOOLBAR &&
4559 window.toolbar.visible
4561 // The edit controls are on a toolbar, so they are visible,
4562 // unless they're in a panel that isn't visible...
4563 if (placement.area == "nav-bar") {
4564 let editControls = document.getElementById("edit-controls");
4566 !editControls.hasAttribute("overflowedItem") ||
4567 kOpenPopupStates.includes(
4568 document.getElementById("widget-overflow").state
4571 gEditUIVisible = true;
4576 // Now check the main menu panel
4577 if (!gEditUIVisible) {
4578 gEditUIVisible = kOpenPopupStates.includes(PanelUI.panel.state);
4581 // No need to update commands if the edit UI visibility has not changed.
4582 if (gEditUIVisible == oldVisible) {
4586 // If UI is visible, update the edit commands' enabled state to reflect
4587 // whether or not they are actually enabled for the current focus/selection.
4588 if (gEditUIVisible) {
4589 goUpdateGlobalEditMenuItems();
4591 // Otherwise, enable all commands, so that keyboard shortcuts still work,
4592 // then lazily determine their actual enabled state when the user presses
4593 // a keyboard shortcut.
4594 goSetCommandEnabled("cmd_undo", true);
4595 goSetCommandEnabled("cmd_redo", true);
4596 goSetCommandEnabled("cmd_cut", true);
4597 goSetCommandEnabled("cmd_copy", true);
4598 goSetCommandEnabled("cmd_paste", true);
4599 goSetCommandEnabled("cmd_selectAll", true);
4600 goSetCommandEnabled("cmd_delete", true);
4601 goSetCommandEnabled("cmd_switchTextDirection", true);
4607 * Updates User Context Menu Item UI visibility depending on
4608 * privacy.userContext.enabled pref state.
4610 updateUserContextUIVisibility() {
4611 let menu = document.getElementById("menu_newUserContext");
4612 menu.hidden = !Services.prefs.getBoolPref(
4613 "privacy.userContext.enabled",
4616 // Visibility of File menu item shouldn't change frequently.
4617 if (PrivateBrowsingUtils.isWindowPrivate(window)) {
4618 menu.setAttribute("disabled", "true");
4623 * Updates the enabled state of the "Import From Another Browser" command
4624 * depending on the DisableProfileImport policy.
4626 updateImportCommandEnabledState() {
4627 if (!Services.policies.isAllowed("profileImport")) {
4629 .getElementById("cmd_file_importFromAnotherBrowser")
4630 .setAttribute("disabled", "true");
4635 * Updates the "Close tab" command to reflect the number of selected tabs,
4638 updateTabCloseCountState() {
4639 document.l10n.setAttributes(
4640 document.getElementById("menu_close"),
4641 "menu-file-close-tab",
4642 { tabCount: gBrowser.selectedTabs.length }
4646 onPopupShowing(event) {
4647 // We don't care about submenus:
4648 if (event.target.id != "menu_FilePopup") {
4651 this.updateUserContextUIVisibility();
4652 this.updateImportCommandEnabledState();
4653 this.updateTabCloseCountState();
4654 if (AppConstants.platform == "macosx") {
4655 gShareUtils.updateShareURLMenuItem(
4656 gBrowser.selectedBrowser,
4657 document.getElementById("menu_savePage")
4660 PrintUtils.updatePrintSetupMenuHiddenState();
4666 * Updates a sharing item in a given menu, creating it if necessary.
4668 updateShareURLMenuItem(browser, insertAfterEl) {
4669 if (!Services.prefs.getBoolPref("browser.menu.share_url.allow", true)) {
4673 // We only support "share URL" on macOS and on Windows 10:
4675 AppConstants.platform != "macosx" &&
4676 // Windows 10's internal NT version number was initially 6.4
4677 !AppConstants.isPlatformAndVersionAtLeast("win", "6.4")
4682 let shareURL = insertAfterEl.nextElementSibling;
4683 if (!shareURL?.matches(".share-tab-url-item")) {
4684 shareURL = this._createShareURLMenuItem(insertAfterEl);
4687 shareURL.browserToShare = Cu.getWeakReference(browser);
4688 if (AppConstants.platform == "win") {
4689 // We disable the item on Windows, as there's no submenu.
4690 // On macOS, we handle this inside the menupopup.
4691 shareURL.hidden = !BrowserUtils.getShareableURL(browser.currentURI);
4696 * Creates and returns the "Share" menu item.
4698 _createShareURLMenuItem(insertAfterEl) {
4699 let menu = insertAfterEl.parentNode;
4700 let shareURL = null;
4701 if (AppConstants.platform == "win") {
4702 shareURL = this._buildShareURLItem(menu.id);
4703 } else if (AppConstants.platform == "macosx") {
4704 shareURL = this._buildShareURLMenu(menu.id);
4706 shareURL.className = "share-tab-url-item";
4709 menu.id == "tabContextMenu"
4710 ? "tab-context-share-url"
4711 : "menu-file-share-url";
4712 document.l10n.setAttributes(shareURL, l10nID);
4714 menu.insertBefore(shareURL, insertAfterEl.nextSibling);
4719 * Returns a menu item specifically for accessing Windows sharing services.
4721 _buildShareURLItem() {
4722 let shareURLMenuItem = document.createXULElement("menuitem");
4723 shareURLMenuItem.addEventListener("command", this);
4724 return shareURLMenuItem;
4728 * Returns a menu specifically for accessing macOSx sharing services .
4730 _buildShareURLMenu() {
4731 let menu = document.createXULElement("menu");
4732 let menuPopup = document.createXULElement("menupopup");
4733 menuPopup.addEventListener("popupshowing", this);
4734 menu.appendChild(menuPopup);
4739 * Get the sharing data for a given DOM node.
4741 getDataToShare(node) {
4742 let browser = node.browserToShare?.get();
4743 let urlToShare = null;
4744 let titleToShare = null;
4747 let maybeToShare = BrowserUtils.getShareableURL(browser.currentURI);
4749 urlToShare = maybeToShare;
4750 titleToShare = browser.contentTitle;
4753 return { urlToShare, titleToShare };
4757 * Populates the "Share" menupopup on macOSx.
4759 initializeShareURLPopup(menuPopup) {
4760 if (AppConstants.platform != "macosx") {
4765 while (menuPopup.firstChild) {
4766 menuPopup.firstChild.remove();
4769 let { urlToShare } = this.getDataToShare(menuPopup.parentNode);
4771 // If we can't share the current URL, we display the items disabled,
4772 // but enable the "more..." item at the bottom, to allow the user to
4773 // change sharing preferences in the system dialog.
4774 let shouldEnable = !!urlToShare;
4776 // Fake it so we can ask the sharing service for services:
4777 urlToShare = makeURI("https://mozilla.org/");
4780 let sharingService = gBrowser.MacSharingService;
4781 let currentURI = gURLBar.makeURIReadable(urlToShare).displaySpec;
4782 let services = sharingService.getSharingProviders(currentURI);
4784 services.forEach(share => {
4785 let item = document.createXULElement("menuitem");
4786 item.classList.add("menuitem-iconic");
4787 item.setAttribute("label", share.menuItemTitle);
4788 item.setAttribute("share-name", share.name);
4789 item.setAttribute("image", share.image);
4790 if (!shouldEnable) {
4791 item.setAttribute("disabled", "true");
4793 menuPopup.appendChild(item);
4795 menuPopup.appendChild(document.createXULElement("menuseparator"));
4796 let moreItem = document.createXULElement("menuitem");
4797 document.l10n.setAttributes(moreItem, "menu-share-more");
4798 moreItem.classList.add("menuitem-iconic", "share-more-button");
4799 menuPopup.appendChild(moreItem);
4801 menuPopup.addEventListener("command", this);
4802 menuPopup.parentNode
4803 .closest("menupopup")
4804 .addEventListener("popuphiding", this);
4805 menuPopup.setAttribute("data-initialized", true);
4808 onShareURLCommand(event) {
4809 // Only call sharing services for the "Share" menu item. These services
4810 // are accessed from a submenu popup for MacOS or the "Share" menu item
4811 // for Windows. Use .closest() as a hack to find either the item itself
4812 // or a parent with the right class.
4813 let target = event.target.closest(".share-tab-url-item");
4818 // urlToShare/titleToShare may be null, in which case only the "more"
4819 // item is enabled, so handle that case first:
4820 if (event.target.classList.contains("share-more-button")) {
4821 gBrowser.MacSharingService.openSharingPreferences();
4825 let { urlToShare, titleToShare } = this.getDataToShare(target);
4826 let currentURI = gURLBar.makeURIReadable(urlToShare).displaySpec;
4828 if (AppConstants.platform == "win") {
4829 WindowsUIUtils.shareUrl(currentURI, titleToShare);
4833 // On macOSX platforms
4834 let shareName = event.target.getAttribute("share-name");
4837 gBrowser.MacSharingService.shareUrl(shareName, currentURI, titleToShare);
4841 onPopupHiding(event) {
4842 // We don't want to rebuild the contents of the "Share" menupopup if only its submenu is
4843 // hidden. So bail if this isn't the top menupopup in the DOM tree:
4844 if (event.target.parentNode.closest("menupopup")) {
4847 // Otherwise, clear its "data-initialized" attribute.
4848 let menupopup = event.target.querySelector(
4849 ".share-tab-url-item"
4851 menupopup?.removeAttribute("data-initialized");
4853 event.target.removeEventListener("popuphiding", this);
4856 onPopupShowing(event) {
4857 if (!event.target.hasAttribute("data-initialized")) {
4858 this.initializeShareURLPopup(event.target);
4862 handleEvent(aEvent) {
4863 switch (aEvent.type) {
4865 this.onShareURLCommand(aEvent);
4868 this.onPopupHiding(aEvent);
4870 case "popupshowing":
4871 this.onPopupShowing(aEvent);
4878 * Opens a new tab with the userContextId specified as an attribute of
4879 * sourceEvent. This attribute is propagated to the top level originAttributes
4880 * living on the tab's docShell.
4883 * A click event on a userContext File Menu option
4885 function openNewUserContextTab(event) {
4886 openTrustedLinkIn(BROWSER_NEW_TAB_URL, "tab", {
4887 userContextId: parseInt(event.target.getAttribute("data-usercontextid")),
4891 var XULBrowserWindow = {
4892 // Stored Status, Link and Loading values
4900 QueryInterface: ChromeUtils.generateQI([
4901 "nsIWebProgressListener",
4902 "nsIWebProgressListener2",
4903 "nsISupportsWeakReference",
4904 "nsIXULBrowserWindow",
4908 delete this.stopCommand;
4909 return (this.stopCommand = document.getElementById("Browser:Stop"));
4911 get reloadCommand() {
4912 delete this.reloadCommand;
4913 return (this.reloadCommand = document.getElementById("Browser:Reload"));
4915 get _elementsForTextBasedTypes() {
4916 delete this._elementsForTextBasedTypes;
4917 return (this._elementsForTextBasedTypes = [
4918 document.getElementById("pageStyleMenu"),
4919 document.getElementById("context-viewpartialsource-selection"),
4920 document.getElementById("context-print-selection"),
4923 get _elementsForFind() {
4924 delete this._elementsForFind;
4925 return (this._elementsForFind = [
4926 document.getElementById("cmd_find"),
4927 document.getElementById("cmd_findAgain"),
4928 document.getElementById("cmd_findPrevious"),
4931 get _elementsForViewSource() {
4932 delete this._elementsForViewSource;
4933 return (this._elementsForViewSource = [
4934 document.getElementById("context-viewsource"),
4935 document.getElementById("View:PageSource"),
4938 get _menuItemForRepairTextEncoding() {
4939 delete this._menuItemForRepairTextEncoding;
4940 return (this._menuItemForRepairTextEncoding = document.getElementById(
4941 "repair-text-encoding"
4944 get _menuItemForTranslations() {
4945 delete this._menuItemForTranslations;
4946 return (this._menuItemForTranslations =
4947 document.getElementById("cmd_translate"));
4950 setDefaultStatus(status) {
4951 this.defaultStatus = status;
4952 StatusPanel.update();
4957 url = Services.textToSubURI.unEscapeURIForUI(url);
4959 // Encode bidirectional formatting characters.
4960 // (RFC 3987 sections 3.2 and 4.1 paragraph 6)
4962 /[\u200e\u200f\u202a\u202b\u202c\u202d\u202e]/g,
4966 if (UrlbarPrefs.get("trimURLs")) {
4967 url = BrowserUIUtils.trimURL(url);
4971 this.overLink = url;
4972 LinkTargetDisplay.update();
4975 showTooltip(xDevPix, yDevPix, tooltip, direction, browser) {
4977 Cc["@mozilla.org/widget/dragservice;1"]
4978 .getService(Ci.nsIDragService)
4979 .getCurrentSession()
4984 if (!document.hasFocus()) {
4988 let elt = document.getElementById("remoteBrowserTooltip");
4989 elt.label = tooltip;
4990 elt.style.direction = direction;
4991 elt.openPopupAtScreen(
4992 xDevPix / window.devicePixelRatio,
4993 yDevPix / window.devicePixelRatio,
5000 let elt = document.getElementById("remoteBrowserTooltip");
5005 return gBrowser.tabs.length;
5027 return this.onProgressChange(
5037 // This function fires only for the currently selected tab.
5038 onStateChange(aWebProgress, aRequest, aStateFlags, aStatus) {
5039 const nsIWebProgressListener = Ci.nsIWebProgressListener;
5041 let browser = gBrowser.selectedBrowser;
5042 gProtectionsHandler.onStateChange(aWebProgress, aStateFlags);
5045 aStateFlags & nsIWebProgressListener.STATE_START &&
5046 aStateFlags & nsIWebProgressListener.STATE_IS_NETWORK
5048 if (aRequest && aWebProgress.isTopLevel) {
5049 // clear out search-engine data
5050 browser.engines = null;
5055 if (!(aStateFlags & nsIWebProgressListener.STATE_RESTORING)) {
5058 // XXX: This needs to be based on window activity...
5059 this.stopCommand.removeAttribute("disabled");
5060 CombinedStopReload.switchToStop(aRequest, aWebProgress);
5062 } else if (aStateFlags & nsIWebProgressListener.STATE_STOP) {
5063 // This (thanks to the filter) is a network stop or the last
5064 // request stop outside of loading the document, stop throbbers
5065 // and progress bars and such
5069 let canViewSource = true;
5070 // Get the URI either from a channel or a pseudo-object
5071 if (aRequest instanceof Ci.nsIChannel || "URI" in aRequest) {
5072 location = aRequest.URI;
5074 // For keyword URIs clear the user typed value since they will be changed into real URIs
5075 if (location.scheme == "keyword" && aWebProgress.isTopLevel) {
5076 gBrowser.userTypedValue = null;
5079 canViewSource = location.scheme != "view-source";
5081 if (location.spec != "about:blank") {
5083 case Cr.NS_ERROR_NET_TIMEOUT:
5084 msg = gNavigatorBundle.getString("nv_timeout");
5091 this.setDefaultStatus(msg);
5093 // Disable View Source menu entries for images, enable otherwise
5095 browser.documentContentType &&
5096 BrowserUtils.mimeTypeIsTextBased(browser.documentContentType);
5097 for (let element of this._elementsForViewSource) {
5098 if (canViewSource && isText) {
5099 element.removeAttribute("disabled");
5101 element.setAttribute("disabled", "true");
5105 this._updateElementsForContentType();
5107 // Update Override Text Encoding state.
5108 // Can't cache the button, because the presence of the element in the DOM
5109 // may change over time.
5110 let button = document.getElementById("characterencoding-button");
5111 if (browser.mayEnableCharacterEncodingMenu) {
5112 this._menuItemForRepairTextEncoding.removeAttribute("disabled");
5113 button?.removeAttribute("disabled");
5115 this._menuItemForRepairTextEncoding.setAttribute("disabled", "true");
5116 button?.setAttribute("disabled", "true");
5120 this.isBusy = false;
5123 this.busyUI = false;
5125 this.stopCommand.setAttribute("disabled", "true");
5126 CombinedStopReload.switchToReload(aRequest, aWebProgress);
5132 * An nsIWebProgressListener method called by tabbrowser. The `aIsSimulated`
5133 * parameter is extra and not declared in nsIWebProgressListener, however; see
5136 * @param {nsIWebProgress} aWebProgress
5137 * The nsIWebProgress instance that fired the notification.
5138 * @param {nsIRequest} aRequest
5139 * The associated nsIRequest. This may be null in some cases.
5140 * @param {nsIURI} aLocationURI
5141 * The URI of the location that is being loaded.
5142 * @param {integer} aFlags
5143 * Flags that indicate the reason the location changed. See the
5144 * nsIWebProgressListener.LOCATION_CHANGE_* values.
5145 * @param {boolean} aIsSimulated
5146 * True when this is called by tabbrowser due to switching tabs and
5147 * undefined otherwise. This parameter is not declared in
5148 * nsIWebProgressListener.onLocationChange; see bug 1478348.
5150 onLocationChange(aWebProgress, aRequest, aLocationURI, aFlags, aIsSimulated) {
5151 var location = aLocationURI ? aLocationURI.spec : "";
5153 UpdateBackForwardCommands(gBrowser.webNavigation);
5155 Services.obs.notifyObservers(
5157 "touchbar-location-change",
5161 // For most changes we only need to update the browser UI if the primary
5162 // content area was navigated or the selected tab was changed. We don't need
5163 // to do anything else if there was a subframe navigation.
5165 if (!aWebProgress.isTopLevel) {
5169 this.hideOverLinkImmediately = true;
5170 this.setOverLink("");
5171 this.hideOverLinkImmediately = false;
5173 let isSameDocument =
5174 aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT;
5176 (location == "about:blank" &&
5177 BrowserUIUtils.checkEmptyPageOrigin(gBrowser.selectedBrowser)) ||
5180 // Second condition is for new tabs, otherwise
5181 // reload function is enabled until tab is refreshed.
5182 this.reloadCommand.setAttribute("disabled", "true");
5184 this.reloadCommand.removeAttribute("disabled");
5187 let isSessionRestore = !!(
5188 aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SESSION_STORE
5191 // We want to update the popup visibility if we received this notification
5192 // via simulated locationchange events such as switching between tabs, however
5193 // if this is a document navigation then PopupNotifications will be updated
5194 // via TabsProgressListener.onLocationChange and we do not want it called twice
5203 BookmarkingUI.onLocationChange();
5204 // If we've actually changed document, update the toolbar visibility.
5205 if (!isSameDocument) {
5206 updateBookmarkToolbarVisibility();
5209 let closeOpenPanels = selector => {
5210 for (let panel of document.querySelectorAll(selector)) {
5211 if (panel.state != "closed") {
5217 // If the location is changed due to switching tabs,
5218 // ensure we close any open tabspecific panels.
5220 closeOpenPanels("panel[tabspecific='true']");
5223 // Ensure we close any remaining open locationspecific panels
5224 if (!isSameDocument) {
5225 closeOpenPanels("panel[locationspecific='true']");
5228 let screenshotsButtonsDisabled =
5229 gScreenshots.shouldScreenshotsButtonBeDisabled();
5230 Services.obs.notifyObservers(
5232 "toggle-screenshot-disable",
5233 screenshotsButtonsDisabled
5236 gPermissionPanel.onLocationChange();
5238 gProtectionsHandler.onLocationChange();
5240 BrowserPageActions.onLocationChange();
5242 SafeBrowsingNotificationBox.onLocationChange(aLocationURI);
5244 SaveToPocket.onLocationChange(window);
5247 if (aRequest instanceof Ci.nsIChannel) {
5248 originalURI = aRequest.originalURI;
5251 UrlbarProviderSearchTips.onLocationChange(
5259 gTabletModePageCounter.inc();
5261 this._updateElementsForContentType();
5263 this._updateMacUserActivity(window, aLocationURI, aWebProgress);
5265 // Unconditionally disable the Text Encoding button during load to
5266 // keep the UI calm when navigating from one modern page to another and
5267 // the toolbar button is visible.
5268 // Can't cache the button, because the presence of the element in the DOM
5269 // may change over time.
5270 let button = document.getElementById("characterencoding-button");
5271 this._menuItemForRepairTextEncoding.setAttribute("disabled", "true");
5272 button?.setAttribute("disabled", "true");
5274 // Try not to instantiate gCustomizeMode as much as possible,
5275 // so don't use CustomizeMode.sys.mjs to check for URI or customizing.
5277 location == "about:blank" &&
5278 gBrowser.selectedTab.hasAttribute("customizemode")
5280 gCustomizeMode.enter();
5282 CustomizationHandler.isEnteringCustomizeMode ||
5283 CustomizationHandler.isCustomizing()
5285 gCustomizeMode.exit();
5288 CFRPageActions.updatePageActions(gBrowser.selectedBrowser);
5290 AboutReaderParent.updateReaderButton(gBrowser.selectedBrowser);
5291 TranslationsParent.onLocationChange(gBrowser.selectedBrowser);
5293 PictureInPicture.updateUrlbarToggle(gBrowser.selectedBrowser);
5295 if (!gMultiProcessBrowser) {
5296 // Bug 1108553 - Cannot rotate images with e10s
5297 gGestureSupport.restoreRotationState();
5300 // See bug 358202, when tabs are switched during a drag operation,
5301 // timers don't fire on windows (bug 203573)
5303 setTimeout(function () {
5304 XULBrowserWindow.asyncUpdateUI();
5307 this.asyncUpdateUI();
5310 if (AppConstants.MOZ_CRASHREPORTER && aLocationURI) {
5311 let uri = aLocationURI;
5313 // If the current URI contains a username/password, remove it.
5314 uri = aLocationURI.mutate().setUserPass("").finalize();
5316 /* Ignore failures on about: URIs. */
5320 Services.appinfo.annotateCrashReport("URL", uri.spec);
5322 // Don't make noise when the crash reporter is built but not enabled.
5323 if (ex.result != Cr.NS_ERROR_NOT_INITIALIZED) {
5330 _updateElementsForContentType() {
5331 let browser = gBrowser.selectedBrowser;
5334 browser.documentContentType &&
5335 BrowserUtils.mimeTypeIsTextBased(browser.documentContentType);
5336 for (let element of this._elementsForTextBasedTypes) {
5338 element.removeAttribute("disabled");
5340 element.setAttribute("disabled", "true");
5344 // Always enable find commands in PDF documents, otherwise do it only for
5345 // text documents whose location is not in the blacklist.
5347 browser.contentPrincipal?.spec == "resource://pdf.js/web/viewer.html" ||
5348 (isText && BrowserUtils.canFindInPage(gBrowser.currentURI.spec));
5349 for (let element of this._elementsForFind) {
5351 element.removeAttribute("disabled");
5353 element.setAttribute("disabled", "true");
5357 if (TranslationsParent.isRestrictedPage(gBrowser.currentURI.scheme)) {
5358 this._menuItemForTranslations.setAttribute("disabled", "true");
5360 this._menuItemForTranslations.removeAttribute("disabled");
5362 if (gTranslationsEnabled) {
5363 TranslationsParent.onIsTranslationsEngineSupported(isSupported => {
5365 this._menuItemForTranslations.removeAttribute("hidden");
5367 this._menuItemForTranslations.setAttribute("hidden", "true");
5371 this._menuItemForTranslations.setAttribute("hidden", "true");
5376 * Updates macOS platform code with the current URI and page title.
5377 * From there, we update the current NSUserActivity, enabling Handoff to other
5379 * @param {Window} window
5380 * The window in which the navigation occurred.
5381 * @param {nsIURI} uri
5382 * The URI pointing to the current page.
5383 * @param {nsIWebProgress} webProgress
5384 * The nsIWebProgress instance that fired a onLocationChange notification.
5386 _updateMacUserActivity(win, uri, webProgress) {
5387 if (!webProgress.isTopLevel || AppConstants.platform != "macosx") {
5392 if (PrivateBrowsingUtils.isWindowPrivate(win)) {
5393 // Passing an empty string to MacUserActivityUpdater will invalidate the
5394 // current user activity.
5397 let baseWin = win.docShell.treeOwner.QueryInterface(Ci.nsIBaseWindow);
5398 MacUserActivityUpdater.updateLocation(
5400 win.gBrowser.contentTitle,
5406 BrowserSearch.updateOpenSearchBadge();
5409 onStatusChange(aWebProgress, aRequest, aStatus, aMessage) {
5410 this.status = aMessage;
5411 StatusPanel.update();
5414 // Properties used to cache security state used to update the UI
5416 _lastLocation: null,
5418 _lastLocationForEvent: null,
5419 // _isSecureContext can change without the state/location changing, due to security
5420 // error pages that intercept certain loads. For example this happens sometimes
5421 // with the the HTTPS-Only Mode error page (more details in bug 1656027)
5422 _isSecureContext: null,
5424 // This is called in multiple ways:
5425 // 1. Due to the nsIWebProgressListener.onContentBlockingEvent notification.
5426 // 2. Called by tabbrowser.xml when updating the current browser.
5427 // 3. Called directly during this object's initializations.
5428 // 4. Due to the nsIWebProgressListener.onLocationChange notification.
5429 // aRequest will be null always in case 2 and 3, and sometimes in case 1 (for
5430 // instance, there won't be a request when STATE_BLOCKED_TRACKING_CONTENT or
5431 // other blocking events are observed).
5432 onContentBlockingEvent(aWebProgress, aRequest, aEvent, aIsSimulated) {
5433 // Don't need to do anything if the data we use to update the UI hasn't
5435 let uri = gBrowser.currentURI;
5436 let spec = uri.spec;
5437 if (this._event == aEvent && this._lastLocationForEvent == spec) {
5440 this._lastLocationForEvent = spec;
5443 typeof aIsSimulated != "boolean" &&
5444 typeof aIsSimulated != "undefined"
5447 "onContentBlockingEvent: aIsSimulated receieved an unexpected type"
5451 gProtectionsHandler.onContentBlockingEvent(
5455 this._event // previous content blocking event
5458 // We need the state of the previous content blocking event, so update
5459 // event after onContentBlockingEvent is called.
5460 this._event = aEvent;
5463 // This is called in multiple ways:
5464 // 1. Due to the nsIWebProgressListener.onSecurityChange notification.
5465 // 2. Called by tabbrowser.xml when updating the current browser.
5466 // 3. Called directly during this object's initializations.
5467 // aRequest will be null always in case 2 and 3, and sometimes in case 1.
5468 onSecurityChange(aWebProgress, aRequest, aState, aIsSimulated) {
5469 // Don't need to do anything if the data we use to update the UI hasn't
5471 let uri = gBrowser.currentURI;
5472 let spec = uri.spec;
5473 let isSecureContext = gBrowser.securityUI.isSecureContext;
5475 this._state == aState &&
5476 this._lastLocation == spec &&
5477 this._isSecureContext === isSecureContext
5479 // Switching to a tab of the same URL doesn't change most security
5480 // information, but tab specific permissions may be different.
5481 gIdentityHandler.refreshIdentityBlock();
5484 this._state = aState;
5485 this._lastLocation = spec;
5486 this._isSecureContext = isSecureContext;
5488 // Make sure the "https" part of the URL is striked out or not,
5489 // depending on the current mixed active content blocking state.
5490 gURLBar.formatValue();
5493 uri = Services.io.createExposableURI(uri);
5495 gIdentityHandler.updateIdentity(this._state, uri);
5498 // simulate all change notifications after switching tabs
5499 onUpdateCurrentBrowser: function XWB_onUpdateCurrentBrowser(
5505 if (FullZoom.updateBackgroundTabs) {
5506 FullZoom.onLocationChange(gBrowser.currentURI, true);
5509 CombinedStopReload.onTabSwitch();
5511 // Docshell should normally take care of hiding the tooltip, but we need to do it
5512 // ourselves for tabswitches.
5515 // Also hide tooltips for content loaded in the parent process:
5516 document.getElementById("aHTMLTooltip").hidePopup();
5518 var nsIWebProgressListener = Ci.nsIWebProgressListener;
5519 var loadingDone = aStateFlags & nsIWebProgressListener.STATE_STOP;
5520 // use a pseudo-object instead of a (potentially nonexistent) channel for getting
5521 // a correct error message - and make sure that the UI is always either in
5522 // loading (STATE_START) or done (STATE_STOP) mode
5524 gBrowser.webProgress,
5525 { URI: gBrowser.currentURI },
5527 ? nsIWebProgressListener.STATE_STOP
5528 : nsIWebProgressListener.STATE_START,
5531 // status message and progress value are undefined if we're done with loading
5535 this.onStatusChange(gBrowser.webProgress, null, 0, aMessage);
5539 var LinkTargetDisplay = {
5541 delete this.DELAY_SHOW;
5542 return (this.DELAY_SHOW = Services.prefs.getIntPref(
5543 "browser.overlink-delay"
5550 get _contextMenu() {
5551 delete this._contextMenu;
5552 return (this._contextMenu = document.getElementById(
5553 "contentAreaContextMenu"
5559 this._contextMenu.state == "open" ||
5560 this._contextMenu.state == "showing"
5562 this._contextMenu.addEventListener("popuphidden", () => this.update(), {
5568 clearTimeout(this._timer);
5569 window.removeEventListener("mousemove", this, true);
5571 if (!XULBrowserWindow.overLink) {
5572 if (XULBrowserWindow.hideOverLinkImmediately) {
5575 this._timer = setTimeout(this._hide.bind(this), this.DELAY_HIDE);
5580 if (StatusPanel.isVisible) {
5581 StatusPanel.update();
5583 // Let the display appear when the mouse doesn't move within the delay
5584 this._showDelayed();
5585 window.addEventListener("mousemove", this, true);
5589 handleEvent(event) {
5590 switch (event.type) {
5592 // Restart the delay since the mouse was moved
5593 clearTimeout(this._timer);
5594 this._showDelayed();
5600 this._timer = setTimeout(
5602 StatusPanel.update();
5603 window.removeEventListener("mousemove", self, true);
5611 clearTimeout(this._timer);
5613 StatusPanel.update();
5617 var CombinedStopReload = {
5618 // Try to initialize. Returns whether initialization was successful, which
5619 // may mean we had already initialized.
5620 ensureInitialized() {
5621 if (this._initialized) {
5624 if (this._destroyed) {
5628 let reload = document.getElementById("reload-button");
5629 let stop = document.getElementById("stop-button");
5630 // It's possible the stop/reload buttons have been moved to the palette.
5631 // They may be reinserted later, so we will retry initialization if/when
5632 // we get notified of document loads.
5633 if (!stop || !reload) {
5637 this._initialized = true;
5638 if (XULBrowserWindow.stopCommand.getAttribute("disabled") != "true") {
5639 reload.setAttribute("displaystop", "true");
5641 stop.addEventListener("click", this);
5643 // Removing attributes based on the observed command doesn't happen if the button
5644 // is in the palette when the command's attribute is removed (cf. bug 309953)
5645 for (let button of [stop, reload]) {
5646 if (button.hasAttribute("disabled")) {
5647 let command = document.getElementById(button.getAttribute("command"));
5648 if (!command.hasAttribute("disabled")) {
5649 button.removeAttribute("disabled");
5654 this.reload = reload;
5656 this.stopReloadContainer = this.reload.parentNode;
5657 this.timeWhenSwitchedToStop = 0;
5659 this.stopReloadContainer.addEventListener("animationend", this);
5660 this.stopReloadContainer.addEventListener("animationcancel", this);
5666 this._destroyed = true;
5668 if (!this._initialized) {
5672 this._cancelTransition();
5673 this.stop.removeEventListener("click", this);
5674 this.stopReloadContainer.removeEventListener("animationend", this);
5675 this.stopReloadContainer.removeEventListener("animationcancel", this);
5676 this.stopReloadContainer = null;
5681 handleEvent(event) {
5682 switch (event.type) {
5684 if (event.button == 0 && !this.stop.disabled) {
5685 this._stopClicked = true;
5688 case "animationcancel":
5689 case "animationend": {
5691 event.target.classList.contains("toolbarbutton-animatable-image") &&
5692 (event.animationName == "reload-to-stop" ||
5693 event.animationName == "stop-to-reload")
5695 this.stopReloadContainer.removeAttribute("animate");
5702 // Reset the time in the event of a tabswitch since the stored time
5703 // would have been associated with the previous tab, so the animation will
5704 // still run if the page has been loading until long after the tab switch.
5705 this.timeWhenSwitchedToStop = window.performance.now();
5708 switchToStop(aRequest, aWebProgress) {
5710 !this.ensureInitialized() ||
5711 !this._shouldSwitch(aRequest, aWebProgress)
5716 // Store the time that we switched to the stop button only if a request
5717 // is active. Requests are null if the switch is related to a tabswitch.
5718 // This is used to determine if we should show the stop->reload animation.
5719 if (aRequest instanceof Ci.nsIRequest) {
5720 this.timeWhenSwitchedToStop = window.performance.now();
5724 aRequest instanceof Ci.nsIRequest &&
5725 aWebProgress.isTopLevel &&
5726 aWebProgress.isLoadingDocument &&
5727 !gBrowser.tabAnimationsInProgress &&
5729 this.stopReloadContainer.closest("#nav-bar-customization-target");
5731 this._cancelTransition();
5732 if (shouldAnimate) {
5733 this.stopReloadContainer.setAttribute("animate", "true");
5735 this.stopReloadContainer.removeAttribute("animate");
5737 this.reload.setAttribute("displaystop", "true");
5740 switchToReload(aRequest, aWebProgress) {
5741 if (!this.ensureInitialized() || !this.reload.hasAttribute("displaystop")) {
5746 aRequest instanceof Ci.nsIRequest &&
5747 aWebProgress.isTopLevel &&
5748 !aWebProgress.isLoadingDocument &&
5749 !gBrowser.tabAnimationsInProgress &&
5751 this._loadTimeExceedsMinimumForAnimation() &&
5752 this.stopReloadContainer.closest("#nav-bar-customization-target");
5754 if (shouldAnimate) {
5755 this.stopReloadContainer.setAttribute("animate", "true");
5757 this.stopReloadContainer.removeAttribute("animate");
5760 this.reload.removeAttribute("displaystop");
5762 if (!shouldAnimate || this._stopClicked) {
5763 this._stopClicked = false;
5764 this._cancelTransition();
5765 this.reload.disabled =
5766 XULBrowserWindow.reloadCommand.getAttribute("disabled") == "true";
5774 // Temporarily disable the reload button to prevent the user from
5775 // accidentally reloading the page when intending to click the stop button
5776 this.reload.disabled = true;
5777 this._timer = setTimeout(
5780 self.reload.disabled =
5781 XULBrowserWindow.reloadCommand.getAttribute("disabled") == "true";
5788 _loadTimeExceedsMinimumForAnimation() {
5789 // If the time between switching to the stop button then switching to
5790 // the reload button exceeds 150ms, then we will show the animation.
5791 // If we don't know when we switched to stop (switchToStop is called
5792 // after init but before switchToReload), then we will prevent the
5793 // animation from occuring.
5795 this.timeWhenSwitchedToStop &&
5796 window.performance.now() - this.timeWhenSwitchedToStop > 150
5800 _shouldSwitch(aRequest, aWebProgress) {
5803 aRequest.originalURI &&
5804 (aRequest.originalURI.schemeIs("chrome") ||
5805 (aRequest.originalURI.schemeIs("about") &&
5806 aWebProgress.isTopLevel &&
5807 !aRequest.originalURI.spec.startsWith("about:reader")))
5815 _cancelTransition() {
5817 clearTimeout(this._timer);
5823 var TabsProgressListener = {
5824 onStateChange(aBrowser, aWebProgress, aRequest, aStateFlags, aStatus) {
5825 // Collect telemetry data about tab load times.
5827 aWebProgress.isTopLevel &&
5828 (!aRequest.originalURI || aRequest.originalURI.scheme != "about")
5830 let histogram = "FX_PAGE_LOAD_MS_2";
5831 let recordLoadTelemetry = true;
5833 if (aWebProgress.loadType & Ci.nsIDocShell.LOAD_CMD_RELOAD) {
5834 // loadType is constructed by shifting loadFlags, this is why we need to
5835 // do the same shifting here.
5836 // https://searchfox.org/mozilla-central/rev/11cfa0462a6b5d8c5e2111b8cfddcf78098f0141/docshell/base/nsDocShellLoadTypes.h#22
5837 if (aWebProgress.loadType & (kSkipCacheFlags << 16)) {
5838 histogram = "FX_PAGE_RELOAD_SKIP_CACHE_MS";
5839 } else if (aWebProgress.loadType == Ci.nsIDocShell.LOAD_CMD_RELOAD) {
5840 histogram = "FX_PAGE_RELOAD_NORMAL_MS";
5842 recordLoadTelemetry = false;
5846 let stopwatchRunning = TelemetryStopwatch.running(histogram, aBrowser);
5847 if (aStateFlags & Ci.nsIWebProgressListener.STATE_IS_WINDOW) {
5848 if (aStateFlags & Ci.nsIWebProgressListener.STATE_START) {
5849 if (stopwatchRunning) {
5850 // Oops, we're seeing another start without having noticed the previous stop.
5851 if (recordLoadTelemetry) {
5852 TelemetryStopwatch.cancel(histogram, aBrowser);
5855 if (recordLoadTelemetry) {
5856 TelemetryStopwatch.start(histogram, aBrowser);
5858 Services.telemetry.getHistogramById("FX_TOTAL_TOP_VISITS").add(true);
5860 aStateFlags & Ci.nsIWebProgressListener.STATE_STOP &&
5861 stopwatchRunning /* we won't see STATE_START events for pre-rendered tabs */
5863 if (recordLoadTelemetry) {
5864 TelemetryStopwatch.finish(histogram, aBrowser);
5865 BrowserTelemetryUtils.recordSiteOriginTelemetry(browserWindows());
5869 aStateFlags & Ci.nsIWebProgressListener.STATE_STOP &&
5870 aStatus == Cr.NS_BINDING_ABORTED &&
5871 stopwatchRunning /* we won't see STATE_START events for pre-rendered tabs */
5873 if (recordLoadTelemetry) {
5874 TelemetryStopwatch.cancel(histogram, aBrowser);
5880 onLocationChange(aBrowser, aWebProgress, aRequest, aLocationURI, aFlags) {
5881 // Filter out location changes in sub documents.
5882 if (!aWebProgress.isTopLevel) {
5886 // Some shops use pushState to move between individual products, so
5887 // the shopping code needs to be told about all of these.
5888 ShoppingSidebarManager.onLocationChange(aBrowser, aLocationURI, aFlags);
5890 // Filter out location changes caused by anchor navigation
5891 // or history.push/pop/replaceState.
5892 if (aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT) {
5893 // Reader mode cares about history.pushState and friends.
5894 // FIXME: The content process should manage this directly (bug 1445351).
5895 aBrowser.sendMessageToActor(
5898 isArticle: aBrowser.isArticle,
5905 // Only need to call locationChange if the PopupNotifications object
5906 // for this window has already been initialized (i.e. its getter no
5908 if (!Object.getOwnPropertyDescriptor(window, "PopupNotifications").get) {
5909 PopupNotifications.locationChange(aBrowser);
5912 let tab = gBrowser.getTabForBrowser(aBrowser);
5913 if (tab && tab._sharingState) {
5914 gBrowser.resetBrowserSharing(aBrowser);
5917 gBrowser.readNotificationBox(aBrowser)?.removeTransientNotifications();
5919 FullZoom.onLocationChange(aLocationURI, false, aBrowser);
5920 CaptivePortalWatcher.onLocationChange(aBrowser);
5923 onLinkIconAvailable(browser, dataURI, iconURI) {
5927 if (browser == gBrowser.selectedBrowser) {
5928 // If the "Add Search Engine" page action is in the urlbar, its image
5929 // needs to be set to the new icon, so call updateOpenSearchBadge.
5930 BrowserSearch.updateOpenSearchBadge();
5935 function nsBrowserAccess() {}
5937 nsBrowserAccess.prototype = {
5938 QueryInterface: ChromeUtils.generateQI(["nsIBrowserDOMWindow"]),
5945 aForceNotRemote = false,
5946 aUserContextId = Ci.nsIScriptSecurityManager.DEFAULT_USER_CONTEXT_ID,
5947 aOpenWindowInfo = null,
5948 aOpenerBrowser = null,
5949 aTriggeringPrincipal = null,
5954 let win, needToFocusWin;
5956 // try the current window. if we're in a popup, fall back on the most recent browser window
5957 if (window.toolbar.visible) {
5960 win = BrowserWindowTracker.getTopWindow({ private: aIsPrivate });
5961 needToFocusWin = true;
5965 // we couldn't find a suitable window, a new one needs to be opened.
5969 if (aIsExternal && (!aURI || aURI.spec == "about:blank")) {
5970 win.BrowserOpenTab(); // this also focuses the location bar
5972 return win.gBrowser.selectedBrowser;
5975 let loadInBackground = Services.prefs.getBoolPref(
5976 "browser.tabs.loadDivertedInBackground"
5979 let tab = win.gBrowser.addTab(aURI ? aURI.spec : "about:blank", {
5980 triggeringPrincipal: aTriggeringPrincipal,
5981 referrerInfo: aReferrerInfo,
5982 userContextId: aUserContextId,
5983 fromExternal: aIsExternal,
5984 inBackground: loadInBackground,
5985 forceNotRemote: aForceNotRemote,
5986 openWindowInfo: aOpenWindowInfo,
5987 openerBrowser: aOpenerBrowser,
5990 skipLoad: aSkipLoad,
5992 let browser = win.gBrowser.getBrowserForTab(tab);
5994 if (needToFocusWin || (!loadInBackground && aIsExternal)) {
6001 createContentWindow(
6006 aTriggeringPrincipal,
6009 return this.getContentWindowOrOpenURI(
6014 aTriggeringPrincipal,
6020 openURI(aURI, aOpenWindowInfo, aWhere, aFlags, aTriggeringPrincipal, aCsp) {
6022 console.error("openURI should only be called with a valid URI");
6023 throw Components.Exception("", Cr.NS_ERROR_FAILURE);
6025 return this.getContentWindowOrOpenURI(
6030 aTriggeringPrincipal,
6036 getContentWindowOrOpenURI(
6041 aTriggeringPrincipal,
6045 var browsingContext = null;
6046 var isExternal = !!(aFlags & Ci.nsIBrowserDOMWindow.OPEN_EXTERNAL);
6048 if (aOpenWindowInfo && isExternal) {
6050 "nsBrowserAccess.openURI did not expect aOpenWindowInfo to be " +
6051 "passed if the context is OPEN_EXTERNAL."
6053 throw Components.Exception("", Cr.NS_ERROR_FAILURE);
6056 if (isExternal && aURI && aURI.schemeIs("chrome")) {
6057 dump("use --chrome command-line option to load external chrome urls\n");
6061 if (aWhere == Ci.nsIBrowserDOMWindow.OPEN_DEFAULTWINDOW) {
6064 Services.prefs.prefHasUserValue(
6065 "browser.link.open_newwindow.override.external"
6068 aWhere = Services.prefs.getIntPref(
6069 "browser.link.open_newwindow.override.external"
6072 aWhere = Services.prefs.getIntPref("browser.link.open_newwindow");
6077 if (aFlags & Ci.nsIBrowserDOMWindow.OPEN_NO_REFERRER) {
6078 referrerInfo = new ReferrerInfo(Ci.nsIReferrerInfo.EMPTY, false, null);
6081 aOpenWindowInfo.parent &&
6082 aOpenWindowInfo.parent.window
6084 referrerInfo = new ReferrerInfo(
6085 aOpenWindowInfo.parent.window.document.referrerInfo.referrerPolicy,
6087 makeURI(aOpenWindowInfo.parent.window.location.href)
6090 referrerInfo = new ReferrerInfo(Ci.nsIReferrerInfo.EMPTY, true, null);
6093 let isPrivate = aOpenWindowInfo
6094 ? aOpenWindowInfo.originAttributes.privateBrowsingId != 0
6095 : PrivateBrowsingUtils.isWindowPrivate(window);
6098 case Ci.nsIBrowserDOMWindow.OPEN_NEWWINDOW:
6099 // FIXME: Bug 408379. So how come this doesn't send the
6100 // referrer like the other loads do?
6101 var url = aURI && aURI.spec;
6102 let features = "all,dialog=no";
6104 features += ",private";
6106 // Pass all params to openDialog to ensure that "url" isn't passed through
6107 // loadOneOrMoreURIs, which splits based on "|"
6109 let extraOptions = Cc[
6110 "@mozilla.org/hash-property-bag;1"
6111 ].createInstance(Ci.nsIWritablePropertyBag2);
6112 extraOptions.setPropertyAsBool("fromExternal", isExternal);
6115 AppConstants.BROWSER_CHROME_URL,
6127 aTriggeringPrincipal,
6132 // At this point, the new browser window is just starting to load, and
6133 // hasn't created the content <browser> that we should return.
6134 // If the caller of this function is originating in C++, they can pass a
6135 // callback in nsOpenWindowInfo and it will be invoked when the browsing
6136 // context for a newly opened window is ready.
6137 browsingContext = null;
6142 case Ci.nsIBrowserDOMWindow.OPEN_NEWTAB: {
6143 // If we have an opener, that means that the caller is expecting access
6144 // to the nsIDOMWindow of the opened tab right away. For e10s windows,
6145 // this means forcing the newly opened browser to be non-remote so that
6146 // we can hand back the nsIDOMWindow. DocumentLoadListener will do the
6147 // job of shuttling off the newly opened browser to run in the right
6148 // process once it starts loading a URI.
6149 let forceNotRemote = aOpenWindowInfo && !aOpenWindowInfo.isRemote;
6150 let userContextId = aOpenWindowInfo
6151 ? aOpenWindowInfo.originAttributes.userContextId
6152 : Ci.nsIScriptSecurityManager.DEFAULT_USER_CONTEXT_ID;
6153 let browser = this._openURIInNewTab(
6161 aOpenWindowInfo?.parent?.top.embedderElement,
6162 aTriggeringPrincipal,
6168 browsingContext = browser.browsingContext;
6172 case Ci.nsIBrowserDOMWindow.OPEN_PRINT_BROWSER: {
6174 PrintUtils.handleStaticCloneCreatedForPrint(aOpenWindowInfo);
6176 browsingContext = browser.browsingContext;
6181 // OPEN_CURRENTWINDOW or an illegal value
6182 browsingContext = window.gBrowser.selectedBrowser.browsingContext;
6184 let loadFlags = Ci.nsIWebNavigation.LOAD_FLAGS_NONE;
6186 loadFlags |= Ci.nsIWebNavigation.LOAD_FLAGS_FROM_EXTERNAL;
6187 } else if (!aTriggeringPrincipal.isSystemPrincipal) {
6188 // XXX this code must be reviewed and changed when bug 1616353
6190 loadFlags |= Ci.nsIWebNavigation.LOAD_FLAGS_FIRST_LOAD;
6192 // This should ideally be able to call loadURI with the actual URI.
6193 // However, that would bypass some styles of fixup (notably Windows
6194 // paths passed as "URI"s), so this needs some further thought. It
6195 // should be addressed in bug 1815509.
6196 gBrowser.fixupAndLoadURIString(aURI.spec, {
6197 triggeringPrincipal: aTriggeringPrincipal,
6204 !Services.prefs.getBoolPref("browser.tabs.loadDivertedInBackground")
6209 return browsingContext;
6212 createContentWindowInFrame: function browser_createContentWindowInFrame(
6219 // Passing a null-URI to only create the content window,
6220 // and pass true for aSkipLoad to prevent loading of
6222 return this.getContentWindowOrOpenURIInFrame(
6232 openURIInFrame: function browser_openURIInFrame(
6239 return this.getContentWindowOrOpenURIInFrame(
6249 getContentWindowOrOpenURIInFrame:
6250 function browser_getContentWindowOrOpenURIInFrame(
6258 if (aWhere == Ci.nsIBrowserDOMWindow.OPEN_PRINT_BROWSER) {
6259 return PrintUtils.handleStaticCloneCreatedForPrint(
6260 aParams.openWindowInfo
6264 if (aWhere != Ci.nsIBrowserDOMWindow.OPEN_NEWTAB) {
6265 dump("Error: openURIInFrame can only open in new tabs or print");
6269 var isExternal = !!(aFlags & Ci.nsIBrowserDOMWindow.OPEN_EXTERNAL);
6272 aParams.openerOriginAttributes &&
6273 "userContextId" in aParams.openerOriginAttributes
6274 ? aParams.openerOriginAttributes.userContextId
6275 : Ci.nsIScriptSecurityManager.DEFAULT_USER_CONTEXT_ID;
6277 return this._openURIInNewTab(
6279 aParams.referrerInfo,
6284 aParams.openWindowInfo,
6285 aParams.openerBrowser,
6286 aParams.triggeringPrincipal,
6294 return CanCloseWindow();
6298 return gBrowser.tabs.length;
6302 function showFullScreenViewContextMenuItems(popup) {
6303 for (let node of popup.querySelectorAll('[contexttype="fullscreen"]')) {
6304 node.hidden = !window.fullScreen;
6306 let autoHide = popup.querySelector(".fullscreen-context-autohide");
6308 FullScreen.updateAutohideMenuitem(autoHide);
6312 function onViewToolbarsPopupShowing(aEvent, aInsertPoint) {
6313 var popup = aEvent.target;
6314 if (popup != aEvent.currentTarget) {
6319 for (var i = popup.children.length - 1; i >= 0; --i) {
6320 var deadItem = popup.children[i];
6321 if (deadItem.hasAttribute("toolbarId")) {
6322 popup.removeChild(deadItem);
6326 MozXULElement.insertFTLIfNeeded("browser/toolbarContextMenu.ftl");
6327 let firstMenuItem = aInsertPoint || popup.firstElementChild;
6328 let toolbarNodes = gNavToolbox.querySelectorAll("toolbar");
6329 for (let toolbar of toolbarNodes) {
6330 if (!toolbar.hasAttribute("toolbarname")) {
6334 if (toolbar.id == "PersonalToolbar") {
6335 let menu = BookmarkingUI.buildBookmarksToolbarSubmenu(toolbar);
6336 popup.insertBefore(menu, firstMenuItem);
6338 let menuItem = document.createXULElement("menuitem");
6339 menuItem.setAttribute("id", "toggle_" + toolbar.id);
6340 menuItem.setAttribute("toolbarId", toolbar.id);
6341 menuItem.setAttribute("type", "checkbox");
6342 menuItem.setAttribute("label", toolbar.getAttribute("toolbarname"));
6343 let hidingAttribute =
6344 toolbar.getAttribute("type") == "menubar" ? "autohide" : "collapsed";
6345 menuItem.setAttribute(
6347 toolbar.getAttribute(hidingAttribute) != "true"
6349 menuItem.setAttribute("accesskey", toolbar.getAttribute("accesskey"));
6350 if (popup.id != "toolbar-context-menu") {
6351 menuItem.setAttribute("key", toolbar.getAttribute("key"));
6354 popup.insertBefore(menuItem, firstMenuItem);
6355 menuItem.addEventListener("command", onViewToolbarCommand);
6359 let moveToPanel = popup.querySelector(".customize-context-moveToPanel");
6360 let removeFromToolbar = popup.querySelector(
6361 ".customize-context-removeFromToolbar"
6363 // Show/hide fullscreen context menu items and set the
6364 // autohide item's checked state to mirror the autohide pref.
6365 showFullScreenViewContextMenuItems(popup);
6366 // View -> Toolbars menu doesn't have the moveToPanel or removeFromToolbar items.
6367 if (!moveToPanel || !removeFromToolbar) {
6371 // triggerNode can be a nested child element of a toolbaritem.
6372 let toolbarItem = popup.triggerNode;
6374 if (toolbarItem && toolbarItem.localName == "toolbarpaletteitem") {
6375 toolbarItem = toolbarItem.firstElementChild;
6376 } else if (toolbarItem && toolbarItem.localName != "toolbar") {
6377 while (toolbarItem && toolbarItem.parentElement) {
6378 let parent = toolbarItem.parentElement;
6380 (parent.classList &&
6381 parent.classList.contains("customization-target")) ||
6382 parent.getAttribute("overflowfortoolbar") || // Needs to work in the overflow list as well.
6383 parent.localName == "toolbarpaletteitem" ||
6384 parent.localName == "toolbar"
6388 toolbarItem = parent;
6394 let showTabStripItems = toolbarItem && toolbarItem.id == "tabbrowser-tabs";
6395 for (let node of popup.querySelectorAll(
6396 'menuitem[contexttype="toolbaritem"]'
6398 node.hidden = showTabStripItems;
6401 for (let node of popup.querySelectorAll('menuitem[contexttype="tabbar"]')) {
6402 node.hidden = !showTabStripItems;
6406 .getElementById("toolbar-context-menu")
6407 .querySelectorAll("[data-lazy-l10n-id]")
6409 el.setAttribute("data-l10n-id", el.getAttribute("data-lazy-l10n-id"));
6410 el.removeAttribute("data-lazy-l10n-id");
6413 // The "normal" toolbar items menu separator is hidden because it's unused
6414 // when hiding the "moveToPanel" and "removeFromToolbar" items on flexible
6415 // space items. But we need to ensure its hidden state is reset in the case
6416 // the context menu is subsequently opened on a non-flexible space item.
6417 let menuSeparator = document.getElementById("toolbarItemsMenuSeparator");
6418 menuSeparator.hidden = false;
6420 document.getElementById("toolbarNavigatorItemsMenuSeparator").hidden =
6424 !CustomizationHandler.isCustomizing() &&
6425 CustomizableUI.isSpecialWidget(toolbarItem?.id || "")
6427 moveToPanel.hidden = true;
6428 removeFromToolbar.hidden = true;
6429 menuSeparator.hidden = !showTabStripItems;
6432 if (showTabStripItems) {
6433 let multipleTabsSelected = !!gBrowser.multiSelectedTabsCount;
6434 document.getElementById("toolbar-context-bookmarkSelectedTabs").hidden =
6435 !multipleTabsSelected;
6436 document.getElementById("toolbar-context-bookmarkSelectedTab").hidden =
6437 multipleTabsSelected;
6438 document.getElementById("toolbar-context-reloadSelectedTabs").hidden =
6439 !multipleTabsSelected;
6440 document.getElementById("toolbar-context-reloadSelectedTab").hidden =
6441 multipleTabsSelected;
6442 document.getElementById("toolbar-context-selectAllTabs").disabled =
6443 gBrowser.allTabsSelected();
6444 document.getElementById("toolbar-context-undoCloseTab").disabled =
6445 SessionStore.getClosedTabCount() == 0;
6452 CustomizableUI.isWidgetRemovable(toolbarItem);
6454 if (CustomizableUI.isSpecialWidget(toolbarItem.id)) {
6455 moveToPanel.setAttribute("disabled", true);
6457 moveToPanel.removeAttribute("disabled");
6459 removeFromToolbar.removeAttribute("disabled");
6461 moveToPanel.setAttribute("disabled", true);
6462 removeFromToolbar.setAttribute("disabled", true);
6466 function onViewToolbarCommand(aEvent) {
6467 let node = aEvent.originalTarget;
6471 if (node.dataset.bookmarksToolbarVisibility) {
6472 isVisible = node.dataset.visibilityEnum;
6473 toolbarId = "PersonalToolbar";
6474 menuId = node.parentNode.parentNode.parentNode.id;
6475 Services.prefs.setCharPref(
6476 "browser.toolbars.bookmarks.visibility",
6480 menuId = node.parentNode.id;
6481 toolbarId = node.getAttribute("toolbarId");
6482 isVisible = node.getAttribute("checked") == "true";
6484 CustomizableUI.setToolbarVisibility(toolbarId, isVisible);
6485 BrowserUsageTelemetry.recordToolbarVisibility(toolbarId, isVisible, menuId);
6488 function setToolbarVisibility(
6494 let hidingAttribute;
6495 if (toolbar.getAttribute("type") == "menubar") {
6496 hidingAttribute = "autohide";
6497 if (AppConstants.platform == "linux") {
6498 Services.prefs.setBoolPref("ui.key.menuAccessKeyFocuses", !isVisible);
6501 hidingAttribute = "collapsed";
6504 if (toolbar == BookmarkingUI.toolbar) {
6505 // For the bookmarks toolbar, we need to persist state before toggling
6506 // the visibility in this window, because the state can be different
6507 // (newtab vs never or always) even when that won't change visibility
6511 if (typeof isVisible == "string") {
6512 prefValue = isVisible;
6514 prefValue = isVisible ? "always" : "never";
6516 Services.prefs.setCharPref(
6517 "browser.toolbars.bookmarks.visibility",
6522 const overlapAttr = "BookmarksToolbarOverlapsBrowser";
6523 switch (isVisible) {
6527 document.documentElement.toggleAttribute(overlapAttr, false);
6532 document.documentElement.toggleAttribute(overlapAttr, false);
6536 let currentURI = gBrowser?.currentURI;
6537 if (!gBrowserInit.domContentLoaded) {
6538 let uriToLoad = gBrowserInit.uriToLoadPromise;
6540 if (Array.isArray(uriToLoad)) {
6541 // We only care about the first tab being loaded
6542 uriToLoad = uriToLoad[0];
6545 currentURI = Services.io.newURI(uriToLoad);
6549 isVisible = BookmarkingUI.isOnNewTabPage(currentURI);
6550 document.documentElement.toggleAttribute(overlapAttr, isVisible);
6555 if (toolbar.getAttribute(hidingAttribute) == (!isVisible).toString()) {
6556 // If this call will not result in a visibility change, return early
6557 // since dispatching toolbarvisibilitychange will cause views to get rebuilt.
6561 toolbar.classList.toggle("instant", !animated);
6562 toolbar.setAttribute(hidingAttribute, !isVisible);
6563 // For the bookmarks toolbar, we will have saved state above. For other
6564 // toolbars, we need to do it after setting the attribute, or we might
6565 // save the wrong state.
6566 if (persist && toolbar.id != "PersonalToolbar") {
6567 Services.xulStore.persist(toolbar, hidingAttribute);
6576 let event = new CustomEvent("toolbarvisibilitychange", eventParams);
6577 toolbar.dispatchEvent(event);
6580 function updateToggleControlLabel(control) {
6581 if (!control.hasAttribute("label-checked")) {
6585 if (!control.hasAttribute("label-unchecked")) {
6586 control.setAttribute("label-unchecked", control.getAttribute("label"));
6588 let prefix = control.getAttribute("checked") == "true" ? "" : "un";
6589 control.setAttribute("label", control.getAttribute(`label-${prefix}checked`));
6592 var TabletModeUpdater = {
6594 if (AppConstants.platform == "win") {
6595 this.update(WindowsUIUtils.inTabletMode);
6596 Services.obs.addObserver(this, "tablet-mode-change");
6601 if (AppConstants.platform == "win") {
6602 Services.obs.removeObserver(this, "tablet-mode-change");
6606 observe(subject, topic, data) {
6607 this.update(data == "tablet-mode");
6610 update(isInTabletMode) {
6611 let wasInTabletMode = document.documentElement.hasAttribute("tabletmode");
6612 if (isInTabletMode) {
6613 document.documentElement.setAttribute("tabletmode", "true");
6615 document.documentElement.removeAttribute("tabletmode");
6617 if (wasInTabletMode != isInTabletMode) {
6618 gUIDensity.update();
6623 var gTabletModePageCounter = {
6626 this.enabled = AppConstants.isPlatformAndVersionAtLeast("win", "10.0");
6627 if (!this.enabled) {
6628 this.inc = () => {};
6631 this.inc = this._realInc;
6638 let inTabletMode = document.documentElement.hasAttribute("tabletmode");
6639 this[inTabletMode ? "_tabletCount" : "_desktopCount"]++;
6644 let histogram = Services.telemetry.getKeyedHistogramById(
6645 "FX_TABLETMODE_PAGE_LOAD"
6647 histogram.add("tablet", this._tabletCount);
6648 histogram.add("desktop", this._desktopCount);
6653 function displaySecurityInfo() {
6654 BrowserPageInfo(null, "securityTab");
6657 // Updates the UI density (for touch and compact mode) based on the uidensity pref.
6662 uiDensityPref: "browser.uidensity",
6663 autoTouchModePref: "browser.touchmode.auto",
6667 Services.prefs.addObserver(this.uiDensityPref, this);
6668 Services.prefs.addObserver(this.autoTouchModePref, this);
6672 Services.prefs.removeObserver(this.uiDensityPref, this);
6673 Services.prefs.removeObserver(this.autoTouchModePref, this);
6676 observe(aSubject, aTopic, aPrefName) {
6678 aTopic != "nsPref:changed" ||
6679 (aPrefName != this.uiDensityPref && aPrefName != this.autoTouchModePref)
6687 getCurrentDensity() {
6688 // Automatically override the uidensity to touch in Windows tablet mode.
6690 AppConstants.platform == "win" &&
6691 WindowsUIUtils.inTabletMode &&
6692 Services.prefs.getBoolPref(this.autoTouchModePref)
6694 return { mode: this.MODE_TOUCH, overridden: true };
6697 mode: Services.prefs.getIntPref(this.uiDensityPref),
6704 mode = this.getCurrentDensity().mode;
6707 let docs = [document.documentElement];
6708 let shouldUpdateSidebar = SidebarUI.initialized && SidebarUI.isOpen;
6709 if (shouldUpdateSidebar) {
6710 docs.push(SidebarUI.browser.contentDocument.documentElement);
6712 for (let doc of docs) {
6714 case this.MODE_COMPACT:
6715 doc.setAttribute("uidensity", "compact");
6717 case this.MODE_TOUCH:
6718 doc.setAttribute("uidensity", "touch");
6721 doc.removeAttribute("uidensity");
6725 if (shouldUpdateSidebar) {
6726 let tree = SidebarUI.browser.contentDocument.querySelector(
6727 ".sidebar-placesTree"
6730 // Tree items don't update their styles without changing some property on the
6731 // parent tree element, like background-color or border. See bug 1407399.
6732 tree.style.border = "1px";
6733 tree.style.border = "";
6737 gBrowser.tabContainer.uiDensityChanged();
6738 gURLBar.updateLayoutBreakout();
6742 const nodeToTooltipMap = {
6743 "bookmarks-menu-button": "bookmarksMenuButton.tooltip",
6744 "context-reload": "reloadButton.tooltip",
6745 "context-stop": "stopButton.tooltip",
6746 "downloads-button": "downloads.tooltip",
6747 "fullscreen-button": "fullscreenButton.tooltip",
6748 "appMenu-fullscreen-button2": "fullscreenButton.tooltip",
6749 "new-window-button": "newWindowButton.tooltip",
6750 "new-tab-button": "newTabButton.tooltip",
6751 "tabs-newtab-button": "newTabButton.tooltip",
6752 "reload-button": "reloadButton.tooltip",
6753 "stop-button": "stopButton.tooltip",
6754 "urlbar-zoom-button": "urlbar-zoom-button.tooltip",
6755 "appMenu-zoomEnlarge-button2": "zoomEnlarge-button.tooltip",
6756 "appMenu-zoomReset-button2": "zoomReset-button.tooltip",
6757 "appMenu-zoomReduce-button2": "zoomReduce-button.tooltip",
6758 "reader-mode-button": "reader-mode-button.tooltip",
6759 "reader-mode-button-icon": "reader-mode-button.tooltip",
6761 const nodeToShortcutMap = {
6762 "bookmarks-menu-button": "manBookmarkKb",
6763 "context-reload": "key_reload",
6764 "context-stop": "key_stop",
6765 "downloads-button": "key_openDownloads",
6766 "fullscreen-button": "key_enterFullScreen",
6767 "appMenu-fullscreen-button2": "key_enterFullScreen",
6768 "new-window-button": "key_newNavigator",
6769 "new-tab-button": "key_newNavigatorTab",
6770 "tabs-newtab-button": "key_newNavigatorTab",
6771 "reload-button": "key_reload",
6772 "stop-button": "key_stop",
6773 "urlbar-zoom-button": "key_fullZoomReset",
6774 "appMenu-zoomEnlarge-button2": "key_fullZoomEnlarge",
6775 "appMenu-zoomReset-button2": "key_fullZoomReset",
6776 "appMenu-zoomReduce-button2": "key_fullZoomReduce",
6777 "reader-mode-button": "key_toggleReaderMode",
6778 "reader-mode-button-icon": "key_toggleReaderMode",
6781 const gDynamicTooltipCache = new Map();
6782 function GetDynamicShortcutTooltipText(nodeId) {
6783 if (!gDynamicTooltipCache.has(nodeId) && nodeId in nodeToTooltipMap) {
6784 let strId = nodeToTooltipMap[nodeId];
6786 if (nodeId in nodeToShortcutMap) {
6787 let shortcutId = nodeToShortcutMap[nodeId];
6788 let shortcut = document.getElementById(shortcutId);
6790 args.push(ShortcutUtils.prettifyShortcut(shortcut));
6793 gDynamicTooltipCache.set(
6795 gNavigatorBundle.getFormattedString(strId, args)
6798 return gDynamicTooltipCache.get(nodeId);
6801 function UpdateDynamicShortcutTooltipText(aTooltip) {
6803 aTooltip.triggerNode.id || aTooltip.triggerNode.getAttribute("anonid");
6804 aTooltip.setAttribute("label", GetDynamicShortcutTooltipText(nodeId));
6808 * - [ Dependencies ] ---------------------------------------------------------
6809 * utilityOverlay.js:
6814 * Extracts linkNode and href for the current click target.
6818 * @return [href, linkNode].
6820 * @note linkNode will be null if the click wasn't on an anchor
6821 * element (or XLink).
6823 function hrefAndLinkNodeForClickEvent(event) {
6824 function isHTMLLink(aNode) {
6825 // Be consistent with what nsContextMenu.js does.
6827 (HTMLAnchorElement.isInstance(aNode) && aNode.href) ||
6828 (HTMLAreaElement.isInstance(aNode) && aNode.href) ||
6829 HTMLLinkElement.isInstance(aNode)
6833 let node = event.composedTarget;
6834 while (node && !isHTMLLink(node)) {
6835 node = node.flattenedTreeParentNode;
6839 return [node.href, node];
6842 // If there is no linkNode, try simple XLink.
6844 node = event.composedTarget;
6845 while (node && !href) {
6847 node.nodeType == Node.ELEMENT_NODE &&
6848 (node.localName == "a" ||
6849 node.namespaceURI == "http://www.w3.org/1998/Math/MathML")
6852 node.getAttribute("href") ||
6853 node.getAttributeNS("http://www.w3.org/1999/xlink", "href");
6856 baseURI = node.baseURI;
6860 node = node.flattenedTreeParentNode;
6863 // In case of XLink, we don't return the node we got href from since
6864 // callers expect <a>-like elements.
6865 return [href ? makeURLAbsolute(baseURI, href) : null, null];
6869 * Called whenever the user clicks in the content area.
6873 * @param isPanelClick
6874 * Whether the event comes from an extension panel.
6875 * @note default event is prevented if the click is handled.
6877 function contentAreaClick(event, isPanelClick) {
6878 if (!event.isTrusted || event.defaultPrevented || event.button != 0) {
6882 let [href, linkNode] = hrefAndLinkNodeForClickEvent(event);
6884 // Not a link, handle middle mouse navigation.
6886 event.button == 1 &&
6887 Services.prefs.getBoolPref("middlemouse.contentLoadURL") &&
6888 !Services.prefs.getBoolPref("general.autoScroll")
6890 middleMousePaste(event);
6891 event.preventDefault();
6896 // This code only applies if we have a linkNode (i.e. clicks on real anchor
6897 // elements, as opposed to XLink).
6900 event.button == 0 &&
6906 // An extension panel's links should target the main content area. Do this
6907 // if no modifier keys are down and if there's no target or the target
6908 // equals _main (the IE convention) or _content (the Mozilla convention).
6909 let target = linkNode.target;
6910 let mainTarget = !target || target == "_content" || target == "_main";
6911 if (isPanelClick && mainTarget) {
6912 // javascript and data links should be executed in the current browser.
6914 linkNode.getAttribute("onclick") ||
6915 href.startsWith("javascript:") ||
6916 href.startsWith("data:")
6922 urlSecurityCheck(href, linkNode.ownerDocument.nodePrincipal);
6924 // Prevent loading unsecure destinations.
6925 event.preventDefault();
6929 openLinkIn(href, "current", {
6930 allowThirdPartyFixup: false,
6932 event.preventDefault();
6937 handleLinkClick(event, href, linkNode);
6939 // Mark the page as a user followed link. This is done so that history can
6940 // distinguish automatic embed visits from user activated ones. For example
6941 // pages loaded in frames are embed visits and lost with the session, while
6942 // visits across frames should be preserved.
6944 if (!PrivateBrowsingUtils.isWindowPrivate(window)) {
6945 PlacesUIUtils.markPageAsFollowedLink(href);
6948 /* Skip invalid URIs. */
6953 * Handles clicks on links.
6955 * @return true if the click event was handled, false otherwise.
6957 function handleLinkClick(event, href, linkNode) {
6958 if (event.button == 2) {
6963 var where = whereToOpenLink(event);
6964 if (where == "current") {
6968 var doc = event.target.ownerDocument;
6969 let referrerInfo = Cc["@mozilla.org/referrer-info;1"].createInstance(
6973 referrerInfo.initWithElement(linkNode);
6975 referrerInfo.initWithDocument(doc);
6978 if (where == "save") {
6982 linkNode ? gatherTextUnder(linkNode) : "",
6987 doc.cookieJarSettings,
6990 event.preventDefault();
6994 let frameID = WebNavigationFrames.getFrameId(doc.defaultView);
6996 urlSecurityCheck(href, doc.nodePrincipal);
6998 charset: doc.characterSet,
7000 originPrincipal: doc.nodePrincipal,
7001 originStoragePrincipal: doc.effectiveStoragePrincipal,
7002 triggeringPrincipal: doc.nodePrincipal,
7007 // The new tab/window must use the same userContextId
7008 if (doc.nodePrincipal.originAttributes.userContextId) {
7009 params.userContextId = doc.nodePrincipal.originAttributes.userContextId;
7012 openLinkIn(href, where, params);
7013 event.preventDefault();
7018 * Handles paste on middle mouse clicks.
7020 * @param event {Event | Object} Event or JSON object.
7022 function middleMousePaste(event) {
7023 let clipboard = readFromClipboard();
7028 // Strip embedded newlines and surrounding whitespace, to match the URL
7029 // bar's behavior (stripsurroundingwhitespace)
7030 clipboard = clipboard.replace(/\s*\n\s*/g, "");
7032 clipboard = UrlbarUtils.stripUnsafeProtocolOnPaste(clipboard);
7034 // if it's not the current tab, we don't need to do anything because the
7035 // browser doesn't exist.
7036 let where = whereToOpenLink(event, true, false);
7037 let lastLocationChange;
7038 if (where == "current") {
7039 lastLocationChange = gBrowser.selectedBrowser.lastLocationChange;
7042 UrlbarUtils.getShortcutOrURIAndPostData(clipboard).then(data => {
7051 UrlbarUtils.addToUrlbarHistory(data.url, window);
7053 // Things may go wrong when adding url to session history,
7054 // but don't let that interfere with the loading of the url.
7059 where != "current" ||
7060 lastLocationChange == gBrowser.selectedBrowser.lastLocationChange
7062 openUILink(data.url, event, {
7064 allowInheritPrincipal: data.mayInheritPrincipal,
7065 triggeringPrincipal: gBrowser.selectedBrowser.contentPrincipal,
7066 csp: gBrowser.selectedBrowser.csp,
7071 if (Event.isInstance(event)) {
7072 event.stopPropagation();
7076 // handleDroppedLink has the following 2 overloads:
7077 // handleDroppedLink(event, url, name, triggeringPrincipal)
7078 // handleDroppedLink(event, links, triggeringPrincipal)
7079 function handleDroppedLink(
7082 nameOrTriggeringPrincipal,
7086 if (Array.isArray(urlOrLinks)) {
7088 triggeringPrincipal = nameOrTriggeringPrincipal;
7090 links = [{ url: urlOrLinks, nameOrTriggeringPrincipal, type: "" }];
7093 let lastLocationChange = gBrowser.selectedBrowser.lastLocationChange;
7095 let userContextId = gBrowser.selectedBrowser.getAttribute("usercontextid");
7097 // event is null if links are dropped in content process.
7098 // inBackground should be false, as it's loading into current browser.
7099 let inBackground = false;
7101 inBackground = Services.prefs.getBoolPref("browser.tabs.loadInBackground");
7102 if (event.shiftKey) {
7103 inBackground = !inBackground;
7107 (async function () {
7110 Services.prefs.getIntPref("browser.tabs.maxOpenBeforeWarn")
7112 // Sync dialog cannot be used inside drop event handler.
7113 let answer = await OpenInTabsUtils.promiseConfirmOpenInTabs(
7124 for (let link of links) {
7125 let data = await UrlbarUtils.getShortcutOrURIAndPostData(link.url);
7126 urls.push(data.url);
7127 postDatas.push(data.postData);
7129 if (lastLocationChange == gBrowser.selectedBrowser.lastLocationChange) {
7130 gBrowser.loadTabs(urls, {
7133 allowThirdPartyFixup: false,
7136 triggeringPrincipal,
7141 // If links are dropped in content process, event.preventDefault() should be
7142 // called in content process.
7144 // Keep the event from being handled by the dragDrop listeners
7145 // built-in to gecko if they happen to be above us.
7146 event.preventDefault();
7150 function BrowserForceEncodingDetection() {
7151 gBrowser.selectedBrowser.forceEncodingDetection();
7152 BrowserReloadWithFlags(Ci.nsIWebNavigation.LOAD_FLAGS_CHARSET_CHANGE);
7155 var ToolbarContextMenu = {
7156 updateDownloadsAutoHide(popup) {
7157 let checkbox = document.getElementById(
7158 "toolbar-context-autohide-downloads-button"
7161 popup.triggerNode &&
7162 ["downloads-button", "wrapper-downloads-button"].includes(
7163 popup.triggerNode.id
7165 checkbox.hidden = !isDownloads;
7166 if (DownloadsButton.autoHideDownloadsButton) {
7167 checkbox.setAttribute("checked", "true");
7169 checkbox.removeAttribute("checked");
7173 onDownloadsAutoHideChange(event) {
7174 let autoHide = event.target.getAttribute("checked") == "true";
7175 Services.prefs.setBoolPref("browser.download.autohideButton", autoHide);
7178 updateDownloadsAlwaysOpenPanel(popup) {
7179 let separator = document.getElementById(
7180 "toolbarDownloadsAnchorMenuSeparator"
7182 let checkbox = document.getElementById(
7183 "toolbar-context-always-open-downloads-panel"
7186 popup.triggerNode &&
7187 ["downloads-button", "wrapper-downloads-button"].includes(
7188 popup.triggerNode.id
7190 separator.hidden = checkbox.hidden = !isDownloads;
7192 ? checkbox.setAttribute("checked", "true")
7193 : checkbox.removeAttribute("checked");
7196 onDownloadsAlwaysOpenPanelChange(event) {
7197 let alwaysOpen = event.target.getAttribute("checked") == "true";
7198 Services.prefs.setBoolPref("browser.download.alwaysOpenPanel", alwaysOpen);
7201 _getUnwrappedTriggerNode(popup) {
7202 // Toolbar buttons are wrapped in customize mode. Unwrap if necessary.
7203 let { triggerNode } = popup;
7204 if (triggerNode && gCustomizeMode.isWrappedToolbarItem(triggerNode)) {
7205 return triggerNode.firstElementChild;
7210 _getExtensionId(popup) {
7211 let node = this._getUnwrappedTriggerNode(popup);
7212 return node && node.getAttribute("data-extensionid");
7215 _getWidgetId(popup) {
7216 let node = this._getUnwrappedTriggerNode(popup);
7217 return node?.closest(".unified-extensions-item")?.id;
7220 async updateExtension(popup, event) {
7221 let removeExtension = popup.querySelector(
7222 ".customize-context-removeExtension"
7224 let manageExtension = popup.querySelector(
7225 ".customize-context-manageExtension"
7227 let reportExtension = popup.querySelector(
7228 ".customize-context-reportExtension"
7230 let pinToToolbar = popup.querySelector(".customize-context-pinToToolbar");
7231 let separator = reportExtension.nextElementSibling;
7232 let id = this._getExtensionId(popup);
7233 let addon = id && (await AddonManager.getAddonByID(id));
7235 for (let element of [removeExtension, manageExtension, separator]) {
7236 element.hidden = !addon;
7240 pinToToolbar.hidden = !addon;
7243 reportExtension.hidden = !addon || !gAddonAbuseReportEnabled;
7246 popup.querySelector(".customize-context-moveToPanel").hidden = true;
7247 popup.querySelector(".customize-context-removeFromToolbar").hidden = true;
7250 let widgetId = this._getWidgetId(popup);
7252 let area = CustomizableUI.getPlacementOfWidget(widgetId).area;
7253 let inToolbar = area != CustomizableUI.AREA_ADDONS;
7254 pinToToolbar.setAttribute("checked", inToolbar);
7258 removeExtension.disabled = !(
7259 addon.permissions & AddonManager.PERM_CAN_UNINSTALL
7262 if (event?.target?.id === "toolbar-context-menu") {
7263 ExtensionsUI.originControlsMenu(popup, id);
7268 async removeExtensionForContextAction(popup) {
7269 let id = this._getExtensionId(popup);
7270 await BrowserAddonUI.removeAddon(id, "browserAction");
7273 async reportExtensionForContextAction(popup, reportEntryPoint) {
7274 let id = this._getExtensionId(popup);
7275 await BrowserAddonUI.reportAddon(id, reportEntryPoint);
7278 async openAboutAddonsForContextAction(popup) {
7279 let id = this._getExtensionId(popup);
7280 await BrowserAddonUI.manageAddon(id, "browserAction");
7284 // Note that this is also called from non-browser windows on OSX, which do
7285 // share menu items but not much else. See nonbrowser-mac.js.
7286 var BrowserOffline = {
7289 // BrowserOffline Public Methods
7291 if (!this._uiElement) {
7292 this._uiElement = document.getElementById("cmd_toggleOfflineStatus");
7295 Services.obs.addObserver(this, "network:offline-status-changed");
7297 this._updateOfflineUI(Services.io.offline);
7299 this._inited = true;
7304 Services.obs.removeObserver(this, "network:offline-status-changed");
7308 toggleOfflineStatus() {
7309 var ioService = Services.io;
7311 if (!ioService.offline && !this._canGoOffline()) {
7312 this._updateOfflineUI(false);
7316 ioService.offline = !ioService.offline;
7320 observe(aSubject, aTopic, aState) {
7321 if (aTopic != "network:offline-status-changed") {
7325 // This notification is also received because of a loss in connectivity,
7326 // which we ignore by updating the UI to the current value of io.offline
7327 this._updateOfflineUI(Services.io.offline);
7330 // BrowserOffline Implementation Methods
7333 var cancelGoOffline = Cc["@mozilla.org/supports-PRBool;1"].createInstance(
7334 Ci.nsISupportsPRBool
7336 Services.obs.notifyObservers(cancelGoOffline, "offline-requested");
7338 // Something aborted the quit process.
7339 if (cancelGoOffline.data) {
7348 _updateOfflineUI(aOffline) {
7349 var offlineLocked = Services.prefs.prefIsLocked("network.online");
7350 if (offlineLocked) {
7351 this._uiElement.setAttribute("disabled", "true");
7354 this._uiElement.setAttribute("checked", aOffline);
7358 var CanvasPermissionPromptHelper = {
7359 _permissionsPrompt: "canvas-permissions-prompt",
7360 _permissionsPromptHideDoorHanger: "canvas-permissions-prompt-hide-doorhanger",
7361 _notificationIcon: "canvas-notification-icon",
7364 Services.obs.addObserver(this, this._permissionsPrompt);
7365 Services.obs.addObserver(this, this._permissionsPromptHideDoorHanger);
7369 Services.obs.removeObserver(this, this._permissionsPrompt);
7370 Services.obs.removeObserver(this, this._permissionsPromptHideDoorHanger);
7373 // aSubject is an nsIBrowser (e10s) or an nsIDOMWindow (non-e10s).
7374 // aData is an Origin string.
7375 observe(aSubject, aTopic, aData) {
7377 aTopic != this._permissionsPrompt &&
7378 aTopic != this._permissionsPromptHideDoorHanger
7384 if (aSubject instanceof Ci.nsIDOMWindow) {
7385 browser = aSubject.docShell.chromeEventHandler;
7390 if (gBrowser.selectedBrowser !== browser) {
7391 // Must belong to some other window.
7395 let message = gNavigatorBundle.getFormattedString(
7396 "canvas.siteprompt2",
7402 Services.scriptSecurityManager.createContentPrincipalFromOrigin(aData);
7404 function setCanvasPermission(aPerm, aPersistent) {
7405 Services.perms.addFromPrincipal(
7410 ? Ci.nsIPermissionManager.EXPIRE_NEVER
7411 : Ci.nsIPermissionManager.EXPIRE_SESSION
7416 label: gNavigatorBundle.getString("canvas.allow2"),
7417 accessKey: gNavigatorBundle.getString("canvas.allow2.accesskey"),
7419 setCanvasPermission(
7420 Ci.nsIPermissionManager.ALLOW_ACTION,
7421 state && state.checkboxChecked
7426 let secondaryActions = [
7428 label: gNavigatorBundle.getString("canvas.block"),
7429 accessKey: gNavigatorBundle.getString("canvas.block.accesskey"),
7431 setCanvasPermission(
7432 Ci.nsIPermissionManager.DENY_ACTION,
7433 state && state.checkboxChecked
7440 // In PB mode, we don't want the "always remember" checkbox
7441 show: !PrivateBrowsingUtils.isWindowPrivate(window),
7443 if (checkbox.show) {
7444 checkbox.checked = true;
7445 checkbox.label = gBrowserBundle.GetStringFromName("canvas.remember2");
7450 name: principal.host,
7452 Services.urlFormatter.formatURLPref("app.support.baseURL") +
7453 "fingerprint-permission",
7454 dismissed: aTopic == this._permissionsPromptHideDoorHanger,
7456 if (e == "showing") {
7457 this.browser.ownerDocument.getElementById(
7458 "canvas-permissions-prompt-warning"
7459 ).textContent = gBrowserBundle.GetStringFromName(
7460 "canvas.siteprompt2.warning"
7465 PopupNotifications.show(
7467 this._permissionsPrompt,
7469 this._notificationIcon,
7477 var WebAuthnPromptHelper = {
7478 _icon: "webauthn-notification-icon",
7479 _topic: "webauthn-prompt",
7481 // The current notification, if any. The U2F manager is a singleton, we will
7482 // never allow more than one active request. And thus we'll never have more
7483 // than one notification either.
7486 // The current transaction ID. Will be checked when we're notified of the
7487 // cancellation of an ongoing WebAuthhn request.
7490 // Translation object
7494 this._l10n = new Localization(["browser/webauthnDialog.ftl"], true);
7495 Services.obs.addObserver(this, this._topic);
7499 Services.obs.removeObserver(this, this._topic);
7502 observe(aSubject, aTopic, aData) {
7504 case "fullscreen-nav-toolbox":
7505 // Prevent the navigation toolbox from being hidden while a WebAuthn
7506 // prompt is visible.
7507 if (aData == "hidden" && this._tid != 0) {
7508 FullScreen.showNavToolbox();
7511 case "fullscreen-painted":
7512 // Prevent DOM elements from going fullscreen while a WebAuthn
7514 if (this._tid != 0) {
7515 FullScreen.exitDomFullScreen();
7523 // aTopic is equal to this._topic
7525 let data = JSON.parse(aData);
7527 // If we receive a cancel, it might be a WebAuthn prompt starting in another
7528 // window, and the other window's browsing context will send out the
7529 // cancellations, so any cancel action we get should prompt us to cancel.
7530 if (data.action == "cancel") {
7536 data.browsingContextId !== gBrowser.selectedBrowser.browsingContext.id
7538 // Must belong to some other window.
7542 let mgr = aSubject.QueryInterface(
7543 data.is_ctap2 ? Ci.nsIWebAuthnController : Ci.nsIU2FTokenManager
7546 if (data.action == "presence") {
7547 this.presence_required(mgr, data);
7548 } else if (data.action == "register-direct") {
7549 this.registerDirect(mgr, data);
7550 } else if (data.action == "pin-required") {
7551 this.pin_required(mgr, data);
7552 } else if (data.action == "select-sign-result") {
7553 this.select_sign_result(mgr, data);
7554 } else if (data.action == "already-registered") {
7559 "alreadyRegistered",
7560 "webauthn.alreadyRegisteredPrompt"
7562 } else if (data.action == "select-device") {
7568 "webauthn.selectDevicePrompt"
7570 } else if (data.action == "pin-auth-blocked") {
7576 "webauthn.pinAuthBlockedPrompt"
7578 } else if (data.action == "uv-blocked") {
7584 "webauthn.uvBlockedPrompt"
7586 } else if (data.action == "uv-invalid") {
7587 let retriesLeft = data.retriesLeft;
7589 if (retriesLeft == 0) {
7590 // We can skip that because it will either be replaced
7591 // by uv-blocked or by PIN-prompt
7593 } else if (retriesLeft < 0) {
7594 dialogText = this._l10n.formatValueSync(
7595 "webauthn-uv-invalid-short-prompt"
7598 dialogText = this._l10n.formatValueSync(
7599 "webauthn-uv-invalid-long-prompt",
7603 let mainAction = this.buildCancelAction(mgr, data.tid);
7604 this.show_formatted_msg(data.tid, "uvInvalid", dialogText, mainAction);
7605 } else if (data.action == "device-blocked") {
7611 "webauthn.deviceBlockedPrompt"
7613 } else if (data.action == "pin-not-set") {
7619 "webauthn.pinNotSetPrompt"
7624 prompt_for_password(origin, wasInvalid, retriesLeft, aPassword) {
7628 dialogText = this._l10n.formatValueSync("webauthn-pin-required-prompt");
7629 } else if (retriesLeft < 0 || retriesLeft > 3) {
7630 // The token will need to be power cycled after three incorrect attempts,
7631 // so we show a short error message that does not include retriesLeft. It
7632 // would be confusing to display retriesLeft at this point, as the user
7633 // will feel that they only get three attempts.
7634 dialogText = this._l10n.formatValueSync(
7635 "webauthn-pin-invalid-short-prompt"
7638 // The user is close to having their PIN permanently blocked. Show a more
7639 // severe warning that includes the retriesLeft counter.
7640 dialogText = this._l10n.formatValueSync(
7641 "webauthn-pin-invalid-long-prompt",
7646 let res = Services.prompt.promptPasswordBC(
7647 gBrowser.selectedBrowser.browsingContext,
7648 Services.prompt.MODAL_TYPE_TAB,
7656 select_sign_result(mgr, { origin, tid, usernames }) {
7657 let secondaryActions = [];
7658 for (let i = 0; i < usernames.length; i++) {
7659 secondaryActions.push({
7660 label: unescape(decodeURIComponent(usernames[i])),
7661 accessKey: i.toString(),
7663 mgr.signatureSelectionCallback(tid, i);
7667 let mainAction = this.buildCancelAction(mgr, tid);
7668 let options = { escAction: "buttoncommand" };
7671 "select-sign-result",
7672 "webauthn.selectSignResultPrompt",
7680 pin_required(mgr, { origin, tid, wasInvalid, retriesLeft }) {
7681 let aPassword = Object.create(null); // create a "null" object
7682 let res = this.prompt_for_password(
7689 mgr.pinCallback(tid, aPassword.value);
7695 presence_required(mgr, { origin, tid }) {
7696 let mainAction = this.buildCancelAction(mgr, tid);
7697 let options = { escAction: "buttoncommand" };
7698 let secondaryActions = [];
7699 let message = "webauthn.userPresencePrompt";
7711 registerDirect(mgr, { origin, tid }) {
7712 let mainAction = this.buildProceedAction(mgr, tid);
7713 let secondaryActions = [this.buildCancelAction(mgr, tid)];
7716 Services.urlFormatter.formatURLPref("app.support.baseURL") +
7717 "webauthn-direct-attestation";
7722 label: gNavigatorBundle.getString("webauthn.anonymize"),
7724 hintText: "webauthn.registerDirectPromptHint",
7729 "webauthn.registerDirectPrompt3",
7737 show_info(mgr, origin, tid, id, stringId) {
7738 let mainAction = this.buildCancelAction(mgr, tid);
7739 this.show(tid, id, stringId, origin, mainAction);
7748 secondaryActions = [],
7751 let brandShortName = document
7752 .getElementById("bundle_brand")
7753 .getString("brandShortName");
7754 let message = gNavigatorBundle.getFormattedString(stringId, [
7760 origin = Services.io.newURI(origin).asciiHost;
7762 /* Might fail for arbitrary U2F RP IDs. */
7764 options.name = origin;
7765 this.show_formatted_msg(
7780 secondaryActions = [],
7786 // We need to prevent some fullscreen transitions while WebAuthn prompts
7787 // are shown. The `fullscreen-painted` topic is notified when DOM elements
7789 Services.obs.addObserver(this, "fullscreen-painted");
7791 // The `fullscreen-nav-toolbox` topic is notified when the nav toolbox is
7793 Services.obs.addObserver(this, "fullscreen-nav-toolbox");
7795 // Ensure that no DOM elements are already fullscreen.
7796 FullScreen.exitDomFullScreen();
7798 // Ensure that the nav toolbox is being shown.
7799 if (window.fullScreen) {
7800 FullScreen.showNavToolbox();
7803 let brandShortName = document
7804 .getElementById("bundle_brand")
7805 .getString("brandShortName");
7806 if (options.hintText) {
7807 options.hintText = gNavigatorBundle.getFormattedString(options.hintText, [
7812 options.hideClose = true;
7813 options.persistent = true;
7814 options.eventCallback = event => {
7815 if (event == "removed") {
7816 Services.obs.removeObserver(this, "fullscreen-painted");
7817 Services.obs.removeObserver(this, "fullscreen-nav-toolbox");
7818 this._current = null;
7823 this._current = PopupNotifications.show(
7824 gBrowser.selectedBrowser,
7825 `webauthn-prompt-${id}`,
7835 if (this._tid == tid) {
7841 if (this._current) {
7842 this._current.remove();
7846 buildProceedAction(mgr, tid) {
7848 label: gNavigatorBundle.getString("webauthn.proceed"),
7849 accessKey: gNavigatorBundle.getString("webauthn.proceed.accesskey"),
7851 mgr.resumeRegister(tid, state.checkboxChecked);
7856 buildCancelAction(mgr, tid) {
7858 label: gNavigatorBundle.getString("webauthn.cancel"),
7859 accessKey: gNavigatorBundle.getString("webauthn.cancel.accesskey"),
7867 function CanCloseWindow() {
7868 // Avoid redundant calls to canClose from showing multiple
7869 // PermitUnload dialogs.
7870 if (Services.startup.shuttingDown || window.skipNextCanClose) {
7874 for (let browser of gBrowser.browsers) {
7875 // Don't instantiate lazy browsers.
7876 if (!browser.isConnected) {
7880 let { permitUnload } = browser.permitUnload();
7881 if (!permitUnload) {
7888 function WindowIsClosing(event) {
7891 let target = event.sourceEvent?.target;
7892 if (target?.id?.startsWith("menu_")) {
7893 source = "menuitem";
7894 } else if (target?.nodeName == "toolbarbutton") {
7895 source = "close-button";
7897 let key = AppConstants.platform == "macosx" ? "metaKey" : "ctrlKey";
7898 source = event[key] ? "shortcut" : "OS";
7901 if (!closeWindow(false, warnAboutClosingWindow, source)) {
7905 // In theory we should exit here and the Window's internal Close
7906 // method should trigger canClose on nsBrowserAccess. However, by
7907 // that point it's too late to be able to show a prompt for
7908 // PermitUnload. So we do it here, when we still can.
7909 if (CanCloseWindow()) {
7910 // This flag ensures that the later canClose call does nothing.
7911 // It's only needed to make tests pass, since they detect the
7912 // prompt even when it's not actually shown.
7913 window.skipNextCanClose = true;
7921 * Checks if this is the last full *browser* window around. If it is, this will
7922 * be communicated like quitting. Otherwise, we warn about closing multiple tabs.
7924 * @param source where the request to close came from (used for telemetry)
7925 * @returns true if closing can proceed, false if it got cancelled.
7927 function warnAboutClosingWindow(source) {
7928 // Popups aren't considered full browser windows; we also ignore private windows.
7930 PrivateBrowsingUtils.isWindowPrivate(window) &&
7931 !PrivateBrowsingUtils.permanentPrivateBrowsing;
7933 if (!isPBWindow && !toolbar.visible) {
7934 return gBrowser.warnAboutClosingTabs(
7935 gBrowser.visibleTabs.length,
7936 gBrowser.closingTabsEnum.ALL,
7941 // Figure out if there's at least one other browser window around.
7942 let otherPBWindowExists = false;
7943 let otherWindowExists = false;
7944 for (let win of browserWindows()) {
7945 if (!win.closed && win != window) {
7946 otherWindowExists = true;
7947 if (isPBWindow && PrivateBrowsingUtils.isWindowPrivate(win)) {
7948 otherPBWindowExists = true;
7950 // If the current window is not in private browsing mode we don't need to
7951 // look for other pb windows, we can leave the loop when finding the
7952 // first non-popup window. If however the current window is in private
7953 // browsing mode then we need at least one other pb and one non-popup
7954 // window to break out early.
7955 if (!isPBWindow || otherPBWindowExists) {
7961 if (isPBWindow && !otherPBWindowExists) {
7962 let exitingCanceled = Cc["@mozilla.org/supports-PRBool;1"].createInstance(
7963 Ci.nsISupportsPRBool
7965 exitingCanceled.data = false;
7966 Services.obs.notifyObservers(exitingCanceled, "last-pb-context-exiting");
7967 if (exitingCanceled.data) {
7972 if (otherWindowExists) {
7975 gBrowser.warnAboutClosingTabs(
7976 gBrowser.visibleTabs.length,
7977 gBrowser.closingTabsEnum.ALL,
7983 let os = Services.obs;
7985 let closingCanceled = Cc["@mozilla.org/supports-PRBool;1"].createInstance(
7986 Ci.nsISupportsPRBool
7988 os.notifyObservers(closingCanceled, "browser-lastwindow-close-requested");
7989 if (closingCanceled.data) {
7993 os.notifyObservers(null, "browser-lastwindow-close-granted");
7995 // OS X doesn't quit the application when the last window is closed, but keeps
7996 // the session alive. Hence don't prompt users to save tabs, but warn about
7997 // closing multiple tabs.
7999 AppConstants.platform != "macosx" ||
8001 gBrowser.warnAboutClosingTabs(
8002 gBrowser.visibleTabs.length,
8003 gBrowser.closingTabsEnum.ALL,
8009 var MailIntegration = {
8010 sendLinkForBrowser(aBrowser) {
8012 gURLBar.makeURIReadable(aBrowser.currentURI).displaySpec,
8013 aBrowser.contentTitle
8017 sendMessage(aBody, aSubject) {
8018 // generate a mailto url based on the url and the url's title
8019 var mailtoUrl = "mailto:";
8021 mailtoUrl += "?body=" + encodeURIComponent(aBody);
8022 mailtoUrl += "&subject=" + encodeURIComponent(aSubject);
8025 var uri = makeURI(mailtoUrl);
8027 // now pass this uri to the operating system
8028 this._launchExternalUrl(uri);
8031 // a generic method which can be used to pass arbitrary urls to the operating
8033 // aURL --> a nsIURI which represents the url to launch
8034 _launchExternalUrl(aURL) {
8035 var extProtocolSvc = Cc[
8036 "@mozilla.org/uriloader/external-protocol-service;1"
8037 ].getService(Ci.nsIExternalProtocolService);
8038 if (extProtocolSvc) {
8039 extProtocolSvc.loadURI(
8041 Services.scriptSecurityManager.getSystemPrincipal()
8048 * Open about:addons page by given view id.
8049 * @param {String} aView
8050 * View id of page that will open.
8051 * e.g. "addons://discover/"
8052 * @param {Object} options
8054 * selectTabByViewId: If true, if there is the tab opening page having
8055 * same view id, select the tab. Else if the current
8056 * page is blank, load on it. Otherwise, open a new
8057 * tab, then load on it.
8058 * If false, if there is the tab opening
8059 * about:addoons page, select the tab and load page
8060 * for view id on it. Otherwise, leave the loading
8061 * behavior to switchToTabHavingURI().
8062 * If no options, handles as false.
8064 * @returns {Promise} When the Promise resolves, returns window object loaded the
8067 function BrowserOpenAddonsMgr(aView, { selectTabByViewId = false } = {}) {
8068 return new Promise(resolve => {
8072 var receivePong = function (aSubject, aTopic, aData) {
8073 let browserWin = aSubject.browsingContext.topChromeWindow;
8074 if (!emWindow || browserWin == window /* favor the current window */) {
8076 selectTabByViewId &&
8077 aSubject.gViewController.currentViewId !== aView
8082 emWindow = aSubject;
8083 browserWindow = browserWin;
8086 Services.obs.addObserver(receivePong, "EM-pong");
8087 Services.obs.notifyObservers(null, "EM-ping");
8088 Services.obs.removeObserver(receivePong, "EM-pong");
8091 if (aView && !selectTabByViewId) {
8092 emWindow.loadView(aView);
8094 let tab = browserWindow.gBrowser.getTabForBrowser(
8095 emWindow.docShell.chromeEventHandler
8097 browserWindow.gBrowser.selectedTab = tab;
8103 if (selectTabByViewId) {
8104 const target = isBlankPageURL(gBrowser.currentURI.spec)
8107 openTrustedLinkIn("about:addons", target);
8109 // This must be a new load, else the ping/pong would have
8110 // found the window above.
8111 switchToTabHavingURI("about:addons", true);
8114 Services.obs.addObserver(function observer(aSubject, aTopic, aData) {
8115 Services.obs.removeObserver(observer, aTopic);
8117 aSubject.loadView(aView);
8125 function AddKeywordForSearchField() {
8126 if (!gContextMenu) {
8127 throw new Error("Context menu doesn't seem to be open.");
8130 gContextMenu.addKeywordForSearchField();
8134 * Applies only to the cmd|ctrl + shift + T keyboard shortcut
8135 * Undo the last action that was taken - either closing the last tab or closing the last window;
8136 * If none of those were the last actions, restore the last session if possible.
8138 function restoreLastClosedTabOrWindowOrSession() {
8139 let lastActionTaken = SessionStore.popLastClosedAction();
8141 if (lastActionTaken) {
8142 switch (lastActionTaken.type) {
8143 case SessionStore.LAST_ACTION_CLOSED_TAB: {
8147 case SessionStore.LAST_ACTION_CLOSED_WINDOW: {
8153 let closedTabCount = SessionStore.getLastClosedTabCount(window);
8154 if (SessionStore.canRestoreLastSession) {
8155 SessionStore.restoreLastSession();
8156 } else if (closedTabCount) {
8157 // we need to support users who have automatic session restore enabled
8164 * Re-open a closed tab into the current window.
8166 * The index of the tab (via SessionStore.getClosedTabData).
8167 * When undefined, the first n closed tabs will be re-opened, where n is provided by getLastClosedTabCount.
8168 * @param {string} [sourceWindowSSId]
8169 * An optional sessionstore id to identify the source window for the tab.
8170 * I.e. the window the tab belonged to when closed.
8171 * When undefined we'll use the current window
8172 * @returns a reference to the reopened tab.
8174 function undoCloseTab(aIndex, sourceWindowSSId) {
8175 // the window we'll open the tab into
8176 let targetWindow = window;
8177 // the window the tab was closed from
8179 if (sourceWindowSSId) {
8180 sourceWindow = SessionStore.getWindowById(sourceWindowSSId);
8181 if (!sourceWindow) {
8183 "sourceWindowSSId argument to undoCloseTab didn't resolve to a window"
8187 sourceWindow = window;
8190 // wallpaper patch to prevent an unnecessary blank tab (bug 343895)
8191 let blankTabToRemove = null;
8193 targetWindow.gBrowser.visibleTabs.length == 1 &&
8194 targetWindow.gBrowser.selectedTab.isEmpty
8196 blankTabToRemove = targetWindow.gBrowser.selectedTab;
8199 // We are specifically interested in the lastClosedTabCount for the source window.
8200 // When aIndex is undefined, we restore all the lastClosedTabCount tabs.
8201 let lastClosedTabCount = SessionStore.getLastClosedTabCount(sourceWindow);
8203 // aIndex is undefined if the function is called without a specific tab to restore.
8205 aIndex !== undefined ? [aIndex] : new Array(lastClosedTabCount).fill(0);
8206 let tabsRemoved = false;
8207 for (let index of tabsToRemove) {
8208 if (SessionStore.getClosedTabCountForWindow(sourceWindow) > index) {
8209 tab = SessionStore.undoCloseTab(sourceWindow, index, targetWindow);
8214 if (tabsRemoved && blankTabToRemove) {
8215 targetWindow.gBrowser.removeTab(blankTabToRemove);
8222 * Re-open a closed window.
8224 * The index of the window (via SessionStore.getClosedWindowData)
8225 * @returns a reference to the reopened window.
8227 function undoCloseWindow(aIndex) {
8229 if (SessionStore.getClosedWindowCount() > (aIndex || 0)) {
8230 window = SessionStore.undoCloseWindow(aIndex || 0);
8236 function ReportFalseDeceptiveSite() {
8237 let contextsToVisit = [gBrowser.selectedBrowser.browsingContext];
8238 while (contextsToVisit.length) {
8239 let currentContext = contextsToVisit.pop();
8240 let global = currentContext.currentWindowGlobal;
8245 let docURI = global.documentURI;
8246 // Ensure the page is an about:blocked pagae before handling.
8247 if (docURI && docURI.spec.startsWith("about:blocked?e=deceptiveBlocked")) {
8248 let actor = global.getActor("BlockedSite");
8249 actor.sendQuery("DeceptiveBlockedDetails").then(data => {
8250 let reportUrl = gSafeBrowsing.getReportURL(
8255 openTrustedLinkIn(reportUrl, "tab");
8257 let bundle = Services.strings.createBundle(
8258 "chrome://browser/locale/safebrowsing/safebrowsing.properties"
8260 Services.prompt.alert(
8262 bundle.GetStringFromName("errorReportFalseDeceptiveTitle"),
8263 bundle.formatStringFromName("errorReportFalseDeceptiveMessage", [
8264 data.blockedInfo.provider,
8271 contextsToVisit.push(...currentContext.children);
8276 * This is a temporary hack to connect a Help menu item for reporting
8277 * site issues to the WebCompat team's Site Compatability Reporter
8278 * WebExtension, which ships by default and is enabled on pre-release
8281 * Once we determine if Help is the right place for it, we'll do something
8282 * slightly better than this.
8286 function ReportSiteIssue() {
8287 let subject = { wrappedJSObject: gBrowser.selectedTab };
8288 Services.obs.notifyObservers(subject, "report-site-issue");
8292 * When the browser is being controlled from out-of-process,
8293 * e.g. when Marionette or the remote debugging protocol is used,
8294 * we add a visual hint to the browser UI to indicate to the user
8295 * that the browser session is under remote control.
8297 * This is called when the content browser initialises (from gBrowserInit.onLoad())
8298 * and when the "remote-listening" system notification fires.
8300 const gRemoteControl = {
8301 observe(subject, topic, data) {
8302 gRemoteControl.updateVisualCue();
8306 // Disable updating the remote control cue for performance tests,
8307 // because these could fail due to an early initialization of Marionette.
8308 const disableRemoteControlCue = Services.prefs.getBoolPref(
8309 "browser.chrome.disableRemoteControlCueForTests",
8312 if (disableRemoteControlCue && Cu.isInAutomation) {
8316 const mainWindow = document.documentElement;
8317 const remoteControlComponent = this.getRemoteControlComponent();
8318 if (remoteControlComponent) {
8319 mainWindow.setAttribute("remotecontrol", "true");
8320 const remoteControlIcon = document.getElementById("remote-control-icon");
8321 document.l10n.setAttributes(
8323 "urlbar-remote-control-notification-anchor2",
8324 { component: remoteControlComponent }
8327 mainWindow.removeAttribute("remotecontrol");
8331 getRemoteControlComponent() {
8332 // For DevTools sockets, only show the remote control cue if the socket is
8333 // not coming from a regular Browser Toolbox debugging session.
8335 DevToolsSocketStatus.hasSocketOpened({
8336 excludeBrowserToolboxSockets: true,
8342 if (Marionette.running) {
8343 return "Marionette";
8346 if (RemoteAgent.running) {
8347 return "RemoteAgent";
8354 // Note that this is also called from non-browser windows on OSX, which do
8355 // share menu items but not much else. See nonbrowser-mac.js.
8356 var gPrivateBrowsingUI = {
8357 init: function PBUI_init() {
8358 // Do nothing for normal windows
8359 if (!PrivateBrowsingUtils.isWindowPrivate(window)) {
8363 // Disable the Clear Recent History... menu item when in PB mode
8364 // temporary fix until bug 463607 is fixed
8365 document.getElementById("Tools:Sanitize").setAttribute("disabled", "true");
8367 if (window.location.href != AppConstants.BROWSER_CHROME_URL) {
8371 // Adjust the window's title
8372 let docElement = document.documentElement;
8373 docElement.setAttribute(
8374 "privatebrowsingmode",
8375 PrivateBrowsingUtils.permanentPrivateBrowsing ? "permanent" : "temporary"
8378 gBrowser.updateTitlebar();
8380 // Bug 1846583 - hide pocket button in PBM
8381 if (gUseFeltPrivacyUI) {
8382 const saveToPocketButton = document.getElementById(
8383 "save-to-pocket-button"
8385 if (saveToPocketButton) {
8386 saveToPocketButton.remove();
8387 document.documentElement.setAttribute("pocketdisabled", "true");
8391 if (PrivateBrowsingUtils.permanentPrivateBrowsing) {
8392 // Adjust the New Window menu entries
8393 let newWindow = document.getElementById("menu_newNavigator");
8394 let newPrivateWindow = document.getElementById("menu_newPrivateWindow");
8395 if (newWindow && newPrivateWindow) {
8396 newPrivateWindow.hidden = true;
8397 newWindow.label = newPrivateWindow.label;
8398 newWindow.accessKey = newPrivateWindow.accessKey;
8399 newWindow.command = newPrivateWindow.command;
8406 * Switch to a tab that has a given URI, and focuses its browser window.
8407 * If a matching tab is in this window, it will be switched to. Otherwise, other
8408 * windows will be searched.
8413 * True to open a new tab and switch to it, if no existing tab is found.
8414 * If no suitable window is found, a new one will be opened.
8415 * @param aOpenParams
8416 * If switching to this URI results in us opening a tab, aOpenParams
8417 * will be the parameter object that gets passed to openTrustedLinkIn. Please
8418 * see the documentation for openTrustedLinkIn to see what parameters can be
8419 * passed via this object.
8420 * This object also allows:
8421 * - 'ignoreFragment' property to be set to true to exclude fragment-portion
8422 * matching when comparing URIs.
8423 * If set to "whenComparing", the fragment will be unmodified.
8424 * If set to "whenComparingAndReplace", the fragment will be replaced.
8425 * - 'ignoreQueryString' boolean property to be set to true to exclude query string
8426 * matching when comparing URIs.
8427 * - 'replaceQueryString' boolean property to be set to true to exclude query string
8428 * matching when comparing URIs and overwrite the initial query string with
8429 * the one from the new URI.
8430 * - 'adoptIntoActiveWindow' boolean property to be set to true to adopt the tab
8431 * into the current window.
8432 * @return True if an existing tab was found, false otherwise
8434 function switchToTabHavingURI(aURI, aOpenNew, aOpenParams = {}) {
8435 // Certain URLs can be switched to irrespective of the source or destination
8436 // window being in private browsing mode:
8437 const kPrivateBrowsingWhitelist = new Set(["about:addons"]);
8439 let ignoreFragment = aOpenParams.ignoreFragment;
8440 let ignoreQueryString = aOpenParams.ignoreQueryString;
8441 let replaceQueryString = aOpenParams.replaceQueryString;
8442 let adoptIntoActiveWindow = aOpenParams.adoptIntoActiveWindow;
8444 // These properties are only used by switchToTabHavingURI and should
8445 // not be used as a parameter for the new load.
8446 delete aOpenParams.ignoreFragment;
8447 delete aOpenParams.ignoreQueryString;
8448 delete aOpenParams.replaceQueryString;
8449 delete aOpenParams.adoptIntoActiveWindow;
8451 let isBrowserWindow = !!window.gBrowser;
8453 // This will switch to the tab in aWindow having aURI, if present.
8454 function switchIfURIInWindow(aWindow) {
8455 // We can switch tab only if if both the source and destination windows have
8456 // the same private-browsing status.
8458 !kPrivateBrowsingWhitelist.has(aURI.spec) &&
8459 PrivateBrowsingUtils.isWindowPrivate(window) !==
8460 PrivateBrowsingUtils.isWindowPrivate(aWindow)
8465 // Remove the query string, fragment, both, or neither from a given url.
8466 function cleanURL(url, removeQuery, removeFragment) {
8468 if (removeFragment) {
8469 ret = ret.split("#")[0];
8471 // This removes a query, if present before the fragment.
8472 ret = ret.split("?")[0];
8474 } else if (removeQuery) {
8475 // This is needed in case there is a fragment after the query.
8476 let fragment = ret.split("#")[1];
8479 .concat(fragment != undefined ? "#".concat(fragment) : "");
8484 // Need to handle nsSimpleURIs here too (e.g. about:...), which don't
8485 // work correctly with URL objects - so treat them as strings
8486 let ignoreFragmentWhenComparing =
8487 typeof ignoreFragment == "string" &&
8488 ignoreFragment.startsWith("whenComparing");
8489 let requestedCompare = cleanURL(
8491 ignoreQueryString || replaceQueryString,
8492 ignoreFragmentWhenComparing
8494 let browsers = aWindow.gBrowser.browsers;
8495 for (let i = 0; i < browsers.length; i++) {
8496 let browser = browsers[i];
8497 let browserCompare = cleanURL(
8498 browser.currentURI.displaySpec,
8499 ignoreQueryString || replaceQueryString,
8500 ignoreFragmentWhenComparing
8502 if (requestedCompare == browserCompare) {
8503 // If adoptIntoActiveWindow is set, and this is a cross-window switch,
8504 // adopt the tab into the current window, after the active tab.
8506 adoptIntoActiveWindow && isBrowserWindow && aWindow != window;
8509 const newTab = window.gBrowser.adoptTab(
8510 aWindow.gBrowser.getTabForBrowser(browser),
8511 window.gBrowser.tabContainer.selectedIndex + 1,
8512 /* aSelectTab = */ true
8522 if (ignoreFragment == "whenComparingAndReplace" || replaceQueryString) {
8523 browser.loadURI(aURI, {
8524 triggeringPrincipal:
8525 aOpenParams.triggeringPrincipal ||
8526 _createNullPrincipalFromTabUserContextId(),
8531 aWindow.gBrowser.tabContainer.selectedIndex = i;
8540 // This can be passed either nsIURI or a string.
8541 if (!(aURI instanceof Ci.nsIURI)) {
8542 aURI = Services.io.newURI(aURI);
8545 // Prioritise this window.
8546 if (isBrowserWindow && switchIfURIInWindow(window)) {
8550 for (let browserWin of browserWindows()) {
8551 // Skip closed (but not yet destroyed) windows,
8552 // and the current window (which was checked earlier).
8553 if (browserWin.closed || browserWin == window) {
8556 if (switchIfURIInWindow(browserWin)) {
8561 // No opened tab has that url.
8563 if (isBrowserWindow && gBrowser.selectedTab.isEmpty) {
8564 openTrustedLinkIn(aURI.spec, "current", aOpenParams);
8566 openTrustedLinkIn(aURI.spec, "tab", aOpenParams);
8573 var RestoreLastSessionObserver = {
8576 SessionStore.canRestoreLastSession &&
8577 !PrivateBrowsingUtils.isWindowPrivate(window)
8579 Services.obs.addObserver(this, "sessionstore-last-session-cleared", true);
8580 goSetCommandEnabled("Browser:RestoreLastSession", true);
8581 } else if (SessionStore.willAutoRestore) {
8582 document.getElementById("Browser:RestoreLastSession").hidden = true;
8587 // The last session can only be restored once so there's
8588 // no way we need to re-enable our menu item.
8589 Services.obs.removeObserver(this, "sessionstore-last-session-cleared");
8590 goSetCommandEnabled("Browser:RestoreLastSession", false);
8593 QueryInterface: ChromeUtils.generateQI([
8595 "nsISupportsWeakReference",
8599 /* Observes menus and adjusts their size for better
8600 * usability when opened via a touch screen. */
8601 var MenuTouchModeObserver = {
8603 window.addEventListener("popupshowing", this, true);
8606 handleEvent(event) {
8607 let target = event.originalTarget;
8608 if (event.inputSource == MouseEvent.MOZ_SOURCE_TOUCH) {
8609 target.setAttribute("touchmode", "true");
8611 target.removeAttribute("touchmode");
8616 window.removeEventListener("popupshowing", this, true);
8620 // Prompt user to restart the browser in safe mode
8621 function safeModeRestart() {
8622 if (Services.appinfo.inSafeMode) {
8623 let cancelQuit = Cc["@mozilla.org/supports-PRBool;1"].createInstance(
8624 Ci.nsISupportsPRBool
8626 Services.obs.notifyObservers(
8628 "quit-application-requested",
8632 if (cancelQuit.data) {
8636 Services.startup.quit(
8637 Ci.nsIAppStartup.eRestart | Ci.nsIAppStartup.eAttemptQuit
8642 Services.obs.notifyObservers(window, "restart-in-safe-mode");
8645 /* duplicateTabIn duplicates tab in a place specified by the parameter |where|.
8649 * "tabshifted" same as "tab" but in background if default is to select new
8650 * tabs, and vice versa
8651 * "window" new window
8653 * delta is the offset to the history entry that you want to load.
8655 function duplicateTabIn(aTab, where, delta) {
8658 let otherWin = OpenBrowserWindow({
8659 private: PrivateBrowsingUtils.isBrowserPrivate(aTab.linkedBrowser),
8661 let delayedStartupFinished = (subject, topic) => {
8663 topic == "browser-delayed-startup-finished" &&
8666 Services.obs.removeObserver(delayedStartupFinished, topic);
8667 let otherGBrowser = otherWin.gBrowser;
8668 let otherTab = otherGBrowser.selectedTab;
8669 SessionStore.duplicateTab(otherWin, aTab, delta);
8670 otherGBrowser.removeTab(otherTab, { animate: false });
8674 Services.obs.addObserver(
8675 delayedStartupFinished,
8676 "browser-delayed-startup-finished"
8680 SessionStore.duplicateTab(window, aTab, delta);
8681 // A background tab has been opened, nothing else to do here.
8684 SessionStore.duplicateTab(window, aTab, delta, true, {
8685 inBackground: false,
8691 var MousePosTracker = {
8692 _listeners: new Set(),
8697 * Registers a listener.
8699 * @param listener (object)
8700 * A listener is expected to expose the following properties:
8702 * getMouseTargetRect (function)
8703 * Returns the rect that the MousePosTracker needs to alert
8704 * the listener about if the mouse happens to be within it.
8706 * onMouseEnter (function, optional)
8707 * The function to be called if the mouse enters the rect
8708 * returned by getMouseTargetRect. MousePosTracker always
8709 * runs this inside of a requestAnimationFrame, since it
8710 * assumes that the notification is used to update the DOM.
8712 * onMouseLeave (function, optional)
8713 * The function to be called if the mouse exits the rect
8714 * returned by getMouseTargetRect. MousePosTracker always
8715 * runs this inside of a requestAnimationFrame, since it
8716 * assumes that the notification is used to update the DOM.
8718 addListener(listener) {
8719 if (this._listeners.has(listener)) {
8723 listener._hover = false;
8724 this._listeners.add(listener);
8726 this._callListener(listener);
8729 removeListener(listener) {
8730 this._listeners.delete(listener);
8733 handleEvent(event) {
8734 this._x = event.screenX - window.mozInnerScreenX;
8735 this._y = event.screenY - window.mozInnerScreenY;
8737 this._listeners.forEach(listener => {
8739 this._callListener(listener);
8746 _callListener(listener) {
8747 let rect = listener.getMouseTargetRect();
8749 this._x >= rect.left &&
8750 this._x <= rect.right &&
8751 this._y >= rect.top &&
8752 this._y <= rect.bottom;
8754 if (hover == listener._hover) {
8758 listener._hover = hover;
8761 if (listener.onMouseEnter) {
8762 listener.onMouseEnter();
8764 } else if (listener.onMouseLeave) {
8765 listener.onMouseLeave();
8770 var ToolbarIconColor = {
8774 tabsintitlebar: false,
8777 this._initialized = true;
8779 window.addEventListener("nativethemechange", this);
8780 window.addEventListener("activate", this);
8781 window.addEventListener("deactivate", this);
8782 window.addEventListener("toolbarvisibilitychange", this);
8783 window.addEventListener("windowlwthemeupdate", this);
8785 // If the window isn't active now, we assume that it has never been active
8786 // before and will soon become active such that inferFromText will be
8787 // called from the initial activate event.
8788 if (Services.focus.activeWindow == window) {
8789 this.inferFromText("activate");
8794 this._initialized = false;
8796 window.removeEventListener("nativethemechange", this);
8797 window.removeEventListener("activate", this);
8798 window.removeEventListener("deactivate", this);
8799 window.removeEventListener("toolbarvisibilitychange", this);
8800 window.removeEventListener("windowlwthemeupdate", this);
8803 handleEvent(event) {
8804 switch (event.type) {
8807 case "nativethemechange":
8808 case "windowlwthemeupdate":
8809 this.inferFromText(event.type);
8811 case "toolbarvisibilitychange":
8812 this.inferFromText(event.type, event.visible);
8817 // a cache of luminance values for each toolbar
8818 // to avoid unnecessary calls to getComputedStyle
8819 _toolbarLuminanceCache: new Map(),
8821 inferFromText(reason, reasonValue) {
8822 if (!this._initialized) {
8825 function parseRGB(aColorString) {
8826 let rgb = aColorString.match(/^rgba?\((\d+), (\d+), (\d+)/);
8828 return rgb.map(x => parseInt(x));
8832 case "activate": // falls through
8834 this._windowState.active = reason === "activate";
8837 this._windowState.fullscreen = reasonValue;
8839 case "nativethemechange":
8840 case "windowlwthemeupdate":
8841 // theme change, we'll need to recalculate all color values
8842 this._toolbarLuminanceCache.clear();
8844 case "toolbarvisibilitychange":
8845 // toolbar changes dont require reset of the cached color values
8847 case "tabsintitlebar":
8848 this._windowState.tabsintitlebar = reasonValue;
8852 let toolbarSelector = ".browser-toolbar:not([collapsed=true])";
8853 if (AppConstants.platform == "macosx") {
8854 toolbarSelector += ":not([type=menubar])";
8857 // The getComputedStyle calls and setting the brighttext are separated in
8858 // two loops to avoid flushing layout and making it dirty repeatedly.
8859 let cachedLuminances = this._toolbarLuminanceCache;
8860 let luminances = new Map();
8861 for (let toolbar of document.querySelectorAll(toolbarSelector)) {
8862 // toolbars *should* all have ids, but guard anyway to avoid blowing up
8864 toolbar.id && toolbar.id + JSON.stringify(this._windowState);
8865 // lookup cached luminance value for this toolbar in this window state
8866 let luminance = cacheKey && cachedLuminances.get(cacheKey);
8867 if (isNaN(luminance)) {
8868 let [r, g, b] = parseRGB(getComputedStyle(toolbar).color);
8869 luminance = 0.2125 * r + 0.7154 * g + 0.0721 * b;
8871 cachedLuminances.set(cacheKey, luminance);
8874 luminances.set(toolbar, luminance);
8877 const luminanceThreshold = 127; // In between 0 and 255
8878 for (let [toolbar, luminance] of luminances) {
8879 if (luminance <= luminanceThreshold) {
8880 toolbar.removeAttribute("brighttext");
8882 toolbar.setAttribute("brighttext", "true");
8888 var PanicButtonNotifier = {
8890 this._initialized = true;
8891 if (window.PanicButtonNotifierShouldNotify) {
8892 delete window.PanicButtonNotifierShouldNotify;
8896 createPanelIfNeeded() {
8897 // Lazy load the panic-button-success-notification panel the first time we need to display it.
8898 if (!document.getElementById("panic-button-success-notification")) {
8899 let template = document.getElementById("panicButtonNotificationTemplate");
8900 template.replaceWith(template.content);
8904 if (!this._initialized) {
8905 window.PanicButtonNotifierShouldNotify = true;
8908 // Display notification panel here...
8910 this.createPanelIfNeeded();
8911 let popup = document.getElementById("panic-button-success-notification");
8912 popup.hidden = false;
8913 // To close the popup in 3 seconds after the popup is shown but left uninteracted.
8914 let onTimeout = () => {
8915 PanicButtonNotifier.close();
8918 popup.addEventListener("popupshown", function () {
8919 PanicButtonNotifier.timer = setTimeout(onTimeout, 3000);
8921 // To prevent the popup from closing when user tries to interact with the
8922 // popup using mouse or keyboard.
8923 let onUserInteractsWithPopup = () => {
8924 clearTimeout(PanicButtonNotifier.timer);
8927 popup.addEventListener("mouseover", onUserInteractsWithPopup);
8928 window.addEventListener("keydown", onUserInteractsWithPopup);
8929 let removeListeners = () => {
8930 popup.removeEventListener("mouseover", onUserInteractsWithPopup);
8931 window.removeEventListener("keydown", onUserInteractsWithPopup);
8932 popup.removeEventListener("popuphidden", removeListeners);
8934 popup.addEventListener("popuphidden", removeListeners);
8936 let widget = CustomizableUI.getWidget("panic-button").forWindow(window);
8937 let anchor = widget.anchor.icon;
8938 popup.openPopup(anchor, popup.getAttribute("position"));
8944 let popup = document.getElementById("panic-button-success-notification");
8949 const SafeBrowsingNotificationBox = {
8950 _currentURIBaseDomain: null,
8951 show(title, buttons) {
8952 let uri = gBrowser.currentURI;
8954 // start tracking host so that we know when we leave the domain
8956 this._currentURIBaseDomain = Services.eTLD.getBaseDomain(uri);
8958 // If we can't get the base domain, fallback to use host instead. However,
8959 // host is sometimes empty when the scheme is file. In this case, just use
8961 this._currentURIBaseDomain = uri.asciiHost || uri.asciiSpec;
8964 let notificationBox = gBrowser.getNotificationBox();
8965 let value = "blocked-badware-page";
8967 let previousNotification = notificationBox.getNotificationWithValue(value);
8968 if (previousNotification) {
8969 notificationBox.removeNotification(previousNotification);
8972 let notification = notificationBox.appendNotification(
8976 image: "chrome://global/skin/icons/blocked.svg",
8977 priority: notificationBox.PRIORITY_CRITICAL_HIGH,
8981 // Persist the notification until the user removes so it
8982 // doesn't get removed on redirects.
8983 notification.persistence = -1;
8985 onLocationChange(aLocationURI) {
8986 // take this to represent that you haven't visited a bad place
8987 if (!this._currentURIBaseDomain) {
8991 let newURIBaseDomain = Services.eTLD.getBaseDomain(aLocationURI);
8993 if (newURIBaseDomain !== this._currentURIBaseDomain) {
8994 let notificationBox = gBrowser.getNotificationBox();
8995 let notification = notificationBox.getNotificationWithValue(
8996 "blocked-badware-page"
8999 notificationBox.removeNotification(notification, false);
9002 this._currentURIBaseDomain = null;
9008 * The TabDialogBox supports opening window dialogs as SubDialogs on the tab and content
9009 * level. Both tab and content dialogs have their own separate managers.
9010 * Dialogs will be queued FIFO and cover the web content.
9011 * Dialogs are closed when the user reloads or leaves the page.
9012 * While a dialog is open PopupNotifications, such as permission prompts, are
9015 class TabDialogBox {
9016 static _containerFor(browser) {
9017 return browser.closest(".browserStack, .webextension-popup-stack");
9020 constructor(browser) {
9021 this._weakBrowserRef = Cu.getWeakReference(browser);
9023 // Create parent element for tab dialogs
9024 let template = document.getElementById("dialogStackTemplate");
9025 let dialogStack = template.content.cloneNode(true).firstElementChild;
9026 dialogStack.classList.add("tab-prompt-dialog");
9028 TabDialogBox._containerFor(browser).appendChild(dialogStack);
9030 // Initially the stack only contains the template
9031 let dialogTemplate = dialogStack.firstElementChild;
9033 // Create dialog manager for prompts at the tab level.
9034 this._tabDialogManager = new SubDialogManager({
9037 orderType: SubDialogManager.ORDER_QUEUE,
9038 allowDuplicateDialogs: true,
9040 consumeOutsideClicks: false,
9046 * Open a dialog on tab or content level.
9047 * @param {String} aURL - URL of the dialog to load in the tab box.
9048 * @param {Object} [aOptions]
9049 * @param {String} [aOptions.features] - Comma separated list of window
9051 * @param {Boolean} [aOptions.allowDuplicateDialogs] - Whether to allow
9052 * showing multiple dialogs with aURL at the same time. If false calls for
9053 * duplicate dialogs will be dropped.
9054 * @param {String} [aOptions.sizeTo] - Pass "available" to stretch dialog to
9055 * roughly content size. Any max-width or max-height style values on the document root
9056 * will also be applied to the dialog box.
9057 * @param {Boolean} [aOptions.keepOpenSameOriginNav] - By default dialogs are
9058 * aborted on any navigation.
9059 * Set to true to keep the dialog open for same origin navigation.
9060 * @param {Number} [aOptions.modalType] - The modal type to create the dialog for.
9061 * By default, we show the dialog for tab prompts.
9062 * @param {Boolean} [aOptions.hideContent] - When true, we are about to show a prompt that is requesting the
9063 * users credentials for a toplevel load of a resource from a base domain different from the base domain of the currently loaded page.
9064 * To avoid auth prompt spoofing (see bug 791594) we hide the current sites content
9065 * (among other protection mechanisms, that are not handled here, see the bug for reference).
9066 * @returns {Object} [result] Returns an object { closedPromise, dialog }.
9067 * @returns {Promise} [result.closedPromise] Resolves once the dialog has been closed.
9068 * @returns {SubDialog} [result.dialog] A reference to the opened SubDialog.
9074 allowDuplicateDialogs = true,
9076 keepOpenSameOriginNav,
9078 allowFocusCheckbox = false,
9079 hideContent = false,
9084 let closedPromise = new Promise(resolve => (resolveClosed = resolve));
9085 // Get the dialog manager to open the prompt with.
9087 modalType === Ci.nsIPrompt.MODAL_TYPE_CONTENT
9088 ? this.getContentDialogManager()
9089 : this._tabDialogManager;
9091 let hasDialogs = () =>
9092 this._tabDialogManager.hasDialogs ||
9093 this._contentDialogManager?.hasDialogs;
9095 if (!hasDialogs()) {
9096 this._onFirstDialogOpen();
9099 let closingCallback = event => {
9100 if (!hasDialogs()) {
9101 this._onLastDialogClose();
9104 if (allowFocusCheckbox && !event.detail?.abort) {
9105 this.maybeSetAllowTabSwitchPermission(event.target);
9109 if (modalType == Ci.nsIPrompt.MODAL_TYPE_CONTENT) {
9110 sizeTo = "limitheight";
9113 // Open dialog and resolve once it has been closed
9114 let dialog = dialogManager.open(
9118 allowDuplicateDialogs,
9121 closedCallback: resolveClosed,
9127 // Marking the dialog externally, instead of passing it as an option.
9128 // The SubDialog(Manager) does not care about navigation.
9129 // dialog can be null here if allowDuplicateDialogs = false.
9131 dialog._keepOpenSameOriginNav = keepOpenSameOriginNav;
9133 return { closedPromise, dialog };
9136 _onFirstDialogOpen() {
9137 // Hide PopupNotifications to prevent them from covering up dialogs.
9138 this.browser.setAttribute("tabDialogShowing", true);
9139 UpdatePopupNotificationsVisibility();
9141 // Register listeners
9142 this._lastPrincipal = this.browser.contentPrincipal;
9143 this.browser.addProgressListener(this, Ci.nsIWebProgress.NOTIFY_LOCATION);
9145 this.tab?.addEventListener("TabClose", this);
9148 _onLastDialogClose() {
9149 // Show PopupNotifications again.
9150 this.browser.removeAttribute("tabDialogShowing");
9151 UpdatePopupNotificationsVisibility();
9153 // Clean up listeners
9154 this.browser.removeProgressListener(this);
9155 this._lastPrincipal = null;
9157 this.tab?.removeEventListener("TabClose", this);
9160 _buildContentPromptDialog() {
9161 let template = document.getElementById("dialogStackTemplate");
9162 let contentDialogStack = template.content.cloneNode(true).firstElementChild;
9163 contentDialogStack.classList.add("content-prompt-dialog");
9165 // Create a dialog manager for content prompts.
9166 let browserContainer = TabDialogBox._containerFor(this.browser);
9167 let tabPromptDialog = browserContainer.querySelector(".tab-prompt-dialog");
9168 browserContainer.insertBefore(contentDialogStack, tabPromptDialog);
9170 let contentDialogTemplate = contentDialogStack.firstElementChild;
9171 this._contentDialogManager = new SubDialogManager({
9172 dialogStack: contentDialogStack,
9173 dialogTemplate: contentDialogTemplate,
9174 orderType: SubDialogManager.ORDER_QUEUE,
9175 allowDuplicateDialogs: true,
9177 consumeOutsideClicks: false,
9182 handleEvent(event) {
9183 if (event.type !== "TabClose") {
9186 this.abortAllDialogs();
9190 this._tabDialogManager.abortDialogs();
9191 this._contentDialogManager?.abortDialogs();
9195 // Prioritize focusing the dialog manager for tab prompts
9196 if (this._tabDialogManager._dialogs.length) {
9197 this._tabDialogManager.focusTopDialog();
9200 this._contentDialogManager?.focusTopDialog();
9204 * If the user navigates away or refreshes the page, close all dialogs for
9205 * the current browser.
9207 onLocationChange(aWebProgress, aRequest, aLocation, aFlags) {
9209 !aWebProgress.isTopLevel ||
9210 aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT
9215 // Dialogs can be exempt from closing on same origin location change.
9218 // Test for same origin location change
9220 this._lastPrincipal?.isSameOrigin(
9222 this.browser.browsingContext.usePrivateBrowsing
9225 filterFn = dialog => !dialog._keepOpenSameOriginNav;
9228 this._lastPrincipal = this.browser.contentPrincipal;
9230 this._tabDialogManager.abortDialogs(filterFn);
9231 this._contentDialogManager?.abortDialogs(filterFn);
9235 return gBrowser.getTabForBrowser(this.browser);
9239 let browser = this._weakBrowserRef.get();
9241 throw new Error("Stale dialog box! The associated browser is gone.");
9246 getTabDialogManager() {
9247 return this._tabDialogManager;
9250 getContentDialogManager() {
9251 if (!this._contentDialogManager) {
9252 this._buildContentPromptDialog();
9254 return this._contentDialogManager;
9257 onNextPromptShowAllowFocusCheckboxFor(principal) {
9258 this._allowTabFocusByPromptPrincipal = principal;
9262 * Sets the "focus-tab-by-prompt" permission for the dialog.
9264 maybeSetAllowTabSwitchPermission(dialog) {
9265 let checkbox = dialog.querySelector("checkbox");
9267 if (checkbox.checked) {
9268 Services.perms.addFromPrincipal(
9269 this._allowTabFocusByPromptPrincipal,
9270 "focus-tab-by-prompt",
9271 Services.perms.ALLOW_ACTION
9275 // Don't show the "allow tab switch checkbox" for subsequent prompts.
9276 this._allowTabFocusByPromptPrincipal = null;
9280 TabDialogBox.prototype.QueryInterface = ChromeUtils.generateQI([
9281 "nsIWebProgressListener",
9282 "nsISupportsWeakReference",
9285 function TabModalPromptBox(browser) {
9286 this._weakBrowserRef = Cu.getWeakReference(browser);
9288 * These WeakMaps holds the TabModalPrompt instances, key to the <tabmodalprompt> prompt
9289 * in the DOM. We don't want to hold the instances directly to avoid leaking.
9291 * WeakMap also prevents us from reading back its insertion order.
9292 * Order of the elements in the DOM should be the only order to consider.
9294 this._contentPrompts = new WeakMap();
9295 this._tabPrompts = new WeakMap();
9298 TabModalPromptBox.prototype = {
9299 _promptCloseCallback(
9301 principalToAllowFocusFor,
9306 principalToAllowFocusFor &&
9307 allowFocusCheckbox &&
9308 allowFocusCheckbox.checked
9310 Services.perms.addFromPrincipal(
9311 principalToAllowFocusFor,
9312 "focus-tab-by-prompt",
9313 Services.perms.ALLOW_ACTION
9316 onCloseCallback.apply(this, args);
9319 getPrompt(promptEl) {
9320 if (promptEl.classList.contains("tab-prompt")) {
9321 return this._tabPrompts.get(promptEl);
9323 return this._contentPrompts.get(promptEl);
9326 appendPrompt(args, onCloseCallback) {
9327 let browser = this.browser;
9328 let newPrompt = new TabModalPrompt(browser.ownerGlobal);
9330 if (args.modalType === Ci.nsIPrompt.MODAL_TYPE_TAB) {
9331 newPrompt.element.classList.add("tab-prompt");
9332 this._tabPrompts.set(newPrompt.element, newPrompt);
9334 newPrompt.element.classList.add("content-prompt");
9335 this._contentPrompts.set(newPrompt.element, newPrompt);
9338 browser.parentNode.insertBefore(
9340 browser.nextElementSibling
9342 browser.setAttribute("tabmodalPromptShowing", true);
9344 // Indicate if a tab modal chrome prompt is being shown so that
9345 // PopupNotifications are suppressed.
9347 args.modalType === Ci.nsIPrompt.MODAL_TYPE_TAB &&
9348 !browser.hasAttribute("tabmodalChromePromptShowing")
9350 browser.setAttribute("tabmodalChromePromptShowing", true);
9351 // Notify popup notifications of the UI change so they hide their
9352 // notification panels.
9353 UpdatePopupNotificationsVisibility();
9356 let prompts = this.listPrompts(args.modalType);
9357 if (prompts.length > 1) {
9358 // Let's hide ourself behind the current prompt.
9359 newPrompt.element.hidden = true;
9362 let principalToAllowFocusFor = this._allowTabFocusByPromptPrincipal;
9363 delete this._allowTabFocusByPromptPrincipal;
9365 let allowFocusCheckbox; // Define outside the if block so we can bind it into the callback.
9366 let hostForAllowFocusCheckbox = "";
9368 hostForAllowFocusCheckbox = principalToAllowFocusFor.URI.host;
9370 /* Ignore exceptions for host-less URIs */
9372 if (hostForAllowFocusCheckbox) {
9373 let allowFocusRow = document.createElement("div");
9375 let spacer = document.createElement("div");
9376 allowFocusRow.appendChild(spacer);
9378 allowFocusCheckbox = document.createXULElement("checkbox");
9379 document.l10n.setAttributes(
9381 "tabbrowser-allow-dialogs-to-get-focus",
9382 { domain: hostForAllowFocusCheckbox }
9384 allowFocusRow.appendChild(allowFocusCheckbox);
9386 newPrompt.ui.rows.append(allowFocusRow);
9389 let tab = gBrowser.getTabForBrowser(browser);
9390 let closeCB = this._promptCloseCallback.bind(
9393 principalToAllowFocusFor,
9396 newPrompt.init(args, tab, closeCB);
9400 removePrompt(aPrompt) {
9401 let { modalType } = aPrompt.args;
9402 if (modalType === Ci.nsIPrompt.MODAL_TYPE_TAB) {
9403 this._tabPrompts.delete(aPrompt.element);
9405 this._contentPrompts.delete(aPrompt.element);
9408 let browser = this.browser;
9409 aPrompt.element.remove();
9411 let prompts = this.listPrompts(modalType);
9412 if (prompts.length) {
9413 let prompt = prompts[prompts.length - 1];
9414 prompt.element.hidden = false;
9415 // Because we were hidden before, this won't have been possible, so do it now:
9416 prompt.Dialog.setDefaultFocus();
9417 } else if (modalType === Ci.nsIPrompt.MODAL_TYPE_TAB) {
9418 // If we remove the last tab chrome prompt, also remove the browser
9420 browser.removeAttribute("tabmodalChromePromptShowing");
9421 // Notify popup notifications of the UI change so they show notification
9423 UpdatePopupNotificationsVisibility();
9425 // Check if all prompts are closed
9426 if (!this._hasPrompts()) {
9427 browser.removeAttribute("tabmodalPromptShowing");
9433 * Checks if the prompt box has prompt elements.
9434 * @returns {Boolean} - true if there are prompt elements.
9437 return !!this._getPromptElements().length;
9441 * Get list of current prompt elements.
9442 * @param {Number} [aModalType] - Optionally filter by
9443 * Ci.nsIPrompt.MODAL_TYPE_.
9444 * @returns {NodeList} - A list of tabmodalprompt elements.
9446 _getPromptElements(aModalType = null) {
9447 let selector = "tabmodalprompt";
9449 if (aModalType != null) {
9450 if (aModalType === Ci.nsIPrompt.MODAL_TYPE_TAB) {
9451 selector += ".tab-prompt";
9453 selector += ".content-prompt";
9456 return this.browser.parentNode.querySelectorAll(selector);
9460 * Get a list of all TabModalPrompt objects associated with the prompt box.
9461 * @param {Number} [aModalType] - Optionally filter by
9462 * Ci.nsIPrompt.MODAL_TYPE_.
9463 * @returns {TabModalPrompt[]} - An array of TabModalPrompt objects.
9465 listPrompts(aModalType = null) {
9466 // Get the nodelist, then return the TabModalPrompt instances as an array
9470 if (aModalType === Ci.nsIPrompt.MODAL_TYPE_TAB) {
9471 promptMap = this._tabPrompts;
9473 promptMap = this._contentPrompts;
9477 let elements = this._getPromptElements(aModalType);
9480 return [...elements].map(el => promptMap.get(el));
9482 return [...elements].map(
9483 el => this._contentPrompts.get(el) || this._tabPrompts.get(el)
9487 onNextPromptShowAllowFocusCheckboxFor(principal) {
9488 this._allowTabFocusByPromptPrincipal = principal;
9492 let browser = this._weakBrowserRef.get();
9494 throw new Error("Stale promptbox! The associated browser is gone.");
9500 // Handle window-modal prompts that we want to display with the same style as
9501 // tab-modal prompts.
9504 _nextOpenJumpsQueue: false,
9507 // Used to wait for a `close` event from the HTML
9508 // dialog. The event is fired asynchronously, which means
9509 // that if we open another dialog immediately after the
9510 // previous one, we might be confused into thinking a
9511 // `close` event for the old dialog is for the new one.
9512 // As they have the same event target, we have no way of
9513 // distinguishing them. So we wait for the `close` event
9514 // to have happened before allowing another dialog to open.
9515 _didCloseHTMLDialog: null,
9516 // Whether we managed to open the dialog we tried to open.
9517 // Used to avoid waiting for the above callback in case
9518 // of an error opening the dialog.
9519 _didOpenHTMLDialog: false,
9522 return this._dialog;
9526 return !!this._dialog;
9529 replaceDialogIfOpen() {
9530 this._dialog?.close();
9531 this._nextOpenJumpsQueue = true;
9534 async open(uri, args) {
9535 // If we need to queue, some callers indicate they should go first.
9536 const queueMethod = this._nextOpenJumpsQueue ? "unshift" : "push";
9537 this._nextOpenJumpsQueue = false;
9539 // If we already have a dialog opened and are trying to open another,
9540 // queue the next one to be opened later.
9542 return new Promise((resolve, reject) => {
9543 this._queued[queueMethod]({ resolve, reject, uri, args });
9547 // We're not open. If we're in a modal state though, we can't
9548 // show the dialog effectively. To avoid hanging by deadlock,
9549 // just return immediately for sync prompts:
9550 if (window.windowUtils.isInModalState() && !args.getProperty("async")) {
9551 throw Components.Exception(
9552 "Prompt could not be shown.",
9553 Cr.NS_ERROR_NOT_AVAILABLE
9557 // Indicate if we should wait for the dialog to close.
9558 this._didOpenHTMLDialog = false;
9559 let haveClosedPromise = new Promise(resolve => {
9560 this._didCloseHTMLDialog = resolve;
9563 // Bring the window to the front in case we're minimized or occluded:
9567 await this._open(uri, args);
9571 let dialog = document.getElementById("window-modal-dialog");
9575 // If the dialog was opened successfully, then we can wait for it
9576 // to close before trying to open any others.
9577 if (this._didOpenHTMLDialog) {
9578 await haveClosedPromise;
9580 dialog.style.visibility = "hidden";
9581 dialog.style.height = "0";
9582 dialog.style.width = "0";
9583 document.documentElement.removeAttribute("window-modal-open");
9584 dialog.removeEventListener("dialogopen", this);
9585 dialog.removeEventListener("close", this);
9586 this._updateMenuAndCommandState(true /* to enable */);
9587 this._dialog = null;
9588 UpdatePopupNotificationsVisibility();
9590 if (this._queued.length) {
9591 setTimeout(() => this._openNextDialog(), 0);
9598 let { resolve, reject, uri, args } = this._queued.shift();
9599 this.open(uri, args).then(resolve, reject);
9603 handleEvent(event) {
9604 switch (event.type) {
9606 this._dialog.focus(true);
9609 this._didCloseHTMLDialog();
9610 this._dialog.close();
9616 // Get this offset before we touch style below, as touching style seems
9617 // to reset the cached layout bounds.
9618 let offset = window.windowUtils.getBoundsWithoutFlushing(
9619 gBrowser.selectedBrowser
9621 let parentElement = document.getElementById("window-modal-dialog");
9622 parentElement.style.setProperty("--chrome-offset", offset + "px");
9623 parentElement.style.removeProperty("visibility");
9624 parentElement.style.removeProperty("width");
9625 parentElement.style.removeProperty("height");
9626 document.documentElement.setAttribute("window-modal-open", true);
9627 // Call this first so the contents show up and get layout, which is
9628 // required for SubDialog to work.
9629 parentElement.showModal();
9630 this._didOpenHTMLDialog = true;
9632 // Disable menus and shortcuts.
9633 this._updateMenuAndCommandState(false /* to disable */);
9635 // Now actually set up the dialog contents:
9636 let template = document.getElementById("window-modal-dialog-template")
9637 .content.firstElementChild;
9638 parentElement.addEventListener("dialogopen", this);
9639 parentElement.addEventListener("close", this);
9640 this._dialog = new SubDialog({
9643 id: "window-modal-dialog-subdialog",
9645 consumeOutsideClicks: false,
9648 let closedPromise = new Promise(resolve => {
9649 this._closedCallback = function () {
9650 PromptUtils.fireDialogEvent(window, "DOMModalDialogClosed");
9657 features: "resizable=no",
9658 modalType: Ci.nsIPrompt.MODAL_TYPE_INTERNAL_WINDOW,
9659 closedCallback: () => {
9660 this._closedCallback();
9665 UpdatePopupNotificationsVisibility();
9666 return closedPromise;
9669 _nonUpdatableElements: new Set([
9670 // Make an exception for debugging tools, for developer ease of use.
9671 "key_browserConsole",
9672 "key_browserToolbox",
9674 // Don't touch the editing keys/commands which we might want inside the dialog.
9685 _updateMenuAndCommandState(shouldBeEnabled) {
9686 let editorCommands = document.getElementById("editMenuCommands");
9687 // For the following items, set or clear disabled state:
9688 // - toplevel menubar items (will affect inner items on macOS)
9689 // - command elements
9690 // - key elements not connected to command elements.
9691 for (let element of document.querySelectorAll(
9692 "menubar > menu, command, key:not([command])"
9695 editorCommands?.contains(element) ||
9696 (element.id && this._nonUpdatableElements.has(element.id))
9700 if (element.nodeName == "key" && element.command) {
9703 if (!shouldBeEnabled) {
9704 if (element.getAttribute("disabled") != "true") {
9705 element.setAttribute("disabled", true);
9707 element.setAttribute("wasdisabled", true);
9709 } else if (element.getAttribute("wasdisabled") != "true") {
9710 element.removeAttribute("disabled");
9712 element.removeAttribute("wasdisabled");
9718 // browser.js loads in the library window, too, but we can only show prompts
9719 // in the main browser window:
9720 if (window.location.href != AppConstants.BROWSER_CHROME_URL) {
9724 var ConfirmationHint = {
9728 * Shows a transient, non-interactive confirmation hint anchored to an
9729 * element, usually used in response to a user action to reaffirm that it was
9730 * successful and potentially provide extra context. Examples for such hints:
9731 * - "Saved to bookmarks" after bookmarking a page
9732 * - "Sent!" after sending a tab to another device
9733 * - "Queued (offline)" when attempting to send a tab to another device
9736 * @param anchor (DOM node, required)
9737 * The anchor for the panel.
9738 * @param messageId (string, required)
9739 * For getting the message string from confirmationHints.ftl
9740 * @param options (object, optional)
9741 * An object with the following optional properties:
9742 * - event (DOM event): The event that triggered the feedback
9743 * - descriptionId (string): message ID of the description text
9746 show(anchor, messageId, options = {}) {
9749 MozXULElement.insertFTLIfNeeded("toolkit/branding/brandings.ftl");
9750 MozXULElement.insertFTLIfNeeded("browser/confirmationHints.ftl");
9751 document.l10n.setAttributes(this._message, messageId);
9753 if (options.descriptionId) {
9754 document.l10n.setAttributes(this._description, options.descriptionId);
9755 this._description.hidden = false;
9756 this._panel.classList.add("with-description");
9758 this._description.hidden = true;
9759 this._panel.classList.remove("with-description");
9762 this._panel.setAttribute("data-message-id", messageId);
9764 // The timeout value used here allows the panel to stay open for
9765 // 3s after the text transition (duration=120ms) has finished.
9766 // If there is a description, we show for 6s after the text transition.
9767 const DURATION = options.showDescription ? 6000 : 3000;
9768 this._panel.addEventListener(
9771 this._animationBox.setAttribute("animate", "true");
9772 this._timerID = setTimeout(() => {
9773 this._panel.hidePopup(true);
9779 this._panel.addEventListener(
9782 // reset the timerId in case our timeout wasn't the cause of the popup being hidden
9788 this._panel.openPopup(anchor, {
9789 position: "bottomcenter topleft",
9790 triggerEvent: options.event,
9795 if (this._timerID) {
9796 clearTimeout(this._timerID);
9797 this._timerID = null;
9800 this._animationBox.removeAttribute("animate");
9801 this._panel.removeAttribute("data-message-id");
9806 this._ensurePanel();
9807 return this.__panel;
9810 get _animationBox() {
9811 this._ensurePanel();
9812 delete this._animationBox;
9813 return (this._animationBox = document.getElementById(
9814 "confirmation-hint-checkmark-animation-container"
9819 this._ensurePanel();
9820 delete this._message;
9821 return (this._message = document.getElementById(
9822 "confirmation-hint-message"
9826 get _description() {
9827 this._ensurePanel();
9828 delete this._description;
9829 return (this._description = document.getElementById(
9830 "confirmation-hint-description"
9835 if (!this.__panel) {
9836 let wrapper = document.getElementById("confirmation-hint-wrapper");
9837 wrapper.replaceWith(wrapper.content);
9838 this.__panel = document.getElementById("confirmation-hint");
9843 var FirefoxViewHandler = {
9845 BUTTON_ID: "firefox-view-button",
9848 return document.getElementById(this.BUTTON_ID);
9851 CustomizableUI.addListener(this);
9853 this._updateEnabledState = this._updateEnabledState.bind(this);
9854 this._updateEnabledState();
9855 NimbusFeatures.majorRelease2022.onUpdate(this._updateEnabledState);
9856 NimbusFeatures.firefoxViewNext.onUpdate(this._updateEnabledState);
9858 ChromeUtils.defineESModuleGetters(this, {
9859 SyncedTabs: "resource://services-sync/SyncedTabs.sys.mjs",
9861 Services.obs.addObserver(this, "firefoxview-notification-dot-update");
9864 CustomizableUI.removeListener(this);
9865 Services.obs.removeObserver(this, "firefoxview-notification-dot-update");
9866 NimbusFeatures.majorRelease2022.offUpdate(this._updateEnabledState);
9867 NimbusFeatures.firefoxViewNext.offUpdate(this._updateEnabledState);
9869 _updateEnabledState() {
9871 NimbusFeatures.majorRelease2022.getVariable("firefoxView") ||
9872 NimbusFeatures.firefoxViewNext.getVariable("enabled");
9873 // We use a root attribute because there's no guarantee the button is in the
9874 // DOM, and visibility changes need to take effect even if it isn't in the DOM
9876 document.documentElement.toggleAttribute(
9877 "firefoxviewhidden",
9880 document.getElementById("menu_openFirefoxView").hidden = !this._enabled;
9881 document.getElementById("firefox-view-button").style.listStyleImage =
9882 NimbusFeatures.firefoxViewNext.getVariable("newIcon")
9884 : 'url("chrome://branding/content/about-logo.png")';
9886 onWidgetRemoved(aWidgetId) {
9887 if (aWidgetId == this.BUTTON_ID && this.tab) {
9888 gBrowser.removeTab(this.tab);
9891 onWidgetAdded(aWidgetId) {
9892 if (aWidgetId === this.BUTTON_ID) {
9893 this.button.removeAttribute("open");
9897 if (event?.type == "mousedown" && event?.button != 0) {
9900 if (!CustomizableUI.getPlacementOfWidget(this.BUTTON_ID)) {
9901 CustomizableUI.addWidgetToArea(
9903 CustomizableUI.AREA_TABSTRIP,
9904 CustomizableUI.getPlacementOfWidget("tabbrowser-tabs").position
9907 const viewURL = NimbusFeatures.firefoxViewNext.getVariable("enabled")
9908 ? "about:firefoxview-next"
9909 : "about:firefoxview";
9910 // Need to account for navigation to Firefox View pages
9913 this.tab.linkedBrowser.currentURI.spec.split("#")[0] != viewURL
9915 gBrowser.removeTab(this.tab);
9919 this.tab = gBrowser.addTrustedTab(viewURL);
9920 this.tab.addEventListener("TabClose", this, { once: true });
9921 gBrowser.tabContainer.addEventListener("TabSelect", this);
9922 window.addEventListener("activate", this);
9923 gBrowser.hideTab(this.tab);
9924 this.button.setAttribute("aria-controls", this.tab.linkedPanel);
9926 // we put this here to avoid a race condition that would occur
9927 // if this was called in response to "TabSelect"
9928 this._closeDeviceConnectedTab();
9929 gBrowser.selectedTab = this.tab;
9934 const selected = e.target == this.tab;
9935 this.button?.toggleAttribute("open", selected);
9936 this.button?.setAttribute("aria-pressed", selected);
9937 this._recordViewIfTabSelected();
9938 this._onTabForegrounded();
9939 if (e.target == this.tab) {
9940 // If Fx View is opened, add temporary style to make first available tab focusable
9941 gBrowser.visibleTabs[0].style["-moz-user-focus"] = "normal";
9943 // When Fx View is closed, remove temporary -moz-user-focus style from first available tab
9944 gBrowser.visibleTabs[0].style.removeProperty("-moz-user-focus");
9949 gBrowser.tabContainer.removeEventListener("TabSelect", this);
9950 this.button?.removeAttribute("aria-controls");
9953 this._onTabForegrounded();
9957 observe(sub, topic, data) {
9959 case "firefoxview-notification-dot-update":
9960 let shouldShow = data === "true";
9961 this._toggleNotificationDot(shouldShow);
9965 _closeDeviceConnectedTab() {
9966 if (!TabsSetupFlowManager.didFxaTabOpen) {
9969 // close the tab left behind after a user pairs a device and
9970 // is redirected back to the Firefox View tab
9971 const fxaRoot = Services.prefs.getCharPref(
9972 "identity.fxaccounts.remote.root"
9974 const fxDeviceConnectedTab = gBrowser.tabs.find(tab =>
9975 tab.linkedBrowser.currentURI.displaySpec.startsWith(
9976 `${fxaRoot}pair/auth/complete`
9980 if (!fxDeviceConnectedTab) {
9984 if (gBrowser.tabs.length <= 2) {
9985 // if its the only tab besides the Firefox View tab,
9986 // open a new tab first so the browser doesn't close
9987 gBrowser.addTrustedTab("about:newtab");
9989 gBrowser.removeTab(fxDeviceConnectedTab);
9990 TabsSetupFlowManager.didFxaTabOpen = false;
9992 _onTabForegrounded() {
9993 if (this.tab?.selected) {
9994 this.SyncedTabs.syncTabs();
9995 Services.obs.notifyObservers(
9997 "firefoxview-notification-dot-update",
10002 _recordViewIfTabSelected() {
10003 if (this.tab?.selected) {
10004 const PREF_NAME = "browser.firefox-view.view-count";
10005 const MAX_VIEW_COUNT = 10;
10006 let viewCount = Services.prefs.getIntPref(PREF_NAME, 0);
10007 if (viewCount < MAX_VIEW_COUNT) {
10008 Services.prefs.setIntPref(PREF_NAME, viewCount + 1);
10012 _toggleNotificationDot(shouldShow) {
10013 this.button?.toggleAttribute("attention", shouldShow);
10017 var ShoppingSidebarManager = {
10019 this._updateVisibility = this._updateVisibility.bind(this);
10020 NimbusFeatures.shopping2023.onUpdate(this._updateVisibility);
10021 XPCOMUtils.defineLazyPreferenceGetter(
10024 "browser.shopping.experience2023.optedIn",
10026 this._updateVisibility
10028 XPCOMUtils.defineLazyPreferenceGetter(
10031 ShoppingSidebarParent.SHOPPING_ACTIVE_PREF,
10033 this._updateVisibility
10035 this._updateVisibility();
10037 gBrowser.tabContainer.addEventListener("TabSelect", this);
10038 window.addEventListener("visibilitychange", this);
10042 NimbusFeatures.shopping2023.offUpdate(this._updateVisibility);
10045 _updateVisibility() {
10046 if (window.closed) {
10049 let optedOut = this.optedInPref === 2;
10050 let isPBM = PrivateBrowsingUtils.isWindowPrivate(window);
10053 NimbusFeatures.shopping2023.getVariable("enabled") && !isPBM && !optedOut;
10055 if (!this.isActive) {
10056 document.querySelectorAll("shopping-sidebar").forEach(sidebar => {
10057 sidebar.hidden = true;
10061 if (!this._enabled) {
10062 document.querySelectorAll("shopping-sidebar").forEach(sidebar => {
10067 let button = document.getElementById("shopping-sidebar-button");
10068 button.hidden = true;
10073 let { selectedBrowser, currentURI } = gBrowser;
10074 this.onLocationChange(selectedBrowser, currentURI, 0);
10078 * Called by TabsProgressListener whenever any browser navigates from one
10080 * Note that this includes hash changes / pushState navigations, because
10081 * those can be significant for us.
10083 onLocationChange(aBrowser, aLocationURI, aFlags) {
10084 if (!this._enabled) {
10088 let browserPanel = gBrowser.getPanel(aBrowser);
10089 let sidebar = browserPanel.querySelector("shopping-sidebar");
10092 let { browsingContext } = sidebar.querySelector("browser");
10093 let global = browsingContext.currentWindowGlobal;
10094 actor = global.getExistingActor("ShoppingSidebar");
10096 let isProduct = isProductURL(aLocationURI);
10097 if (isProduct && this.isActive) {
10099 sidebar = document.createXULElement("shopping-sidebar");
10100 sidebar.setAttribute("style", "width: 320px");
10101 sidebar.hidden = false;
10102 browserPanel.appendChild(sidebar);
10104 actor?.updateProductURL(aLocationURI, aFlags);
10105 sidebar.hidden = false;
10107 } else if (sidebar && !sidebar.hidden) {
10108 actor?.updateProductURL(null);
10109 sidebar.hidden = true;
10112 this._updateBCActiveness(aBrowser);
10113 this._setShoppingButtonState(aBrowser);
10116 let isVisible = sidebar && !sidebar.hidden;
10118 // Ignore same-document navigation, except in the case of Walmart
10119 // as they use pushState to navigate between pages.
10120 let isWalmart = aLocationURI.host.includes("walmart");
10121 let isNewDocument = !aFlags;
10123 let isSameDocument =
10124 aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT;
10125 let isReload = aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_RELOAD;
10126 let isSessionRestore =
10127 aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SESSION_STORE;
10131 // On initial visit to a product page, even from another domain, both a page
10132 // load and a pushstate will be triggered by Walmart, so this will
10133 // capture only a single displayed event.
10134 ((!isWalmart && (isNewDocument || isReload || isSessionRestore)) ||
10135 (isWalmart && isSameDocument))
10137 Glean.shopping.surfaceDisplayed.record();
10142 // This is the auto-enable behavior that toggles the `active` pref. It
10143 // must be at the end of this function, or 2 sidebars could be created.
10144 ShoppingUtils.handleAutoActivateOnProduct();
10146 if (!this.isActive) {
10147 ShoppingUtils.sendTrigger({
10149 id: "shoppingProductPageWithSidebarClosed",
10150 context: { isSidebarClosing: !!sidebar },
10156 _updateBCActiveness(aBrowser) {
10157 let browserPanel = gBrowser.getPanel(aBrowser);
10158 let sidebar = browserPanel.querySelector("shopping-sidebar");
10162 let { browsingContext } = sidebar.querySelector("browser");
10164 // Tell Gecko when the sidebar visibility changes to avoid background
10165 // sidebars taking more CPU / energy than needed.
10166 browsingContext.isActive =
10167 !document.hidden &&
10168 aBrowser == gBrowser.selectedBrowser &&
10171 // The setter can throw and we do need to run the rest of this
10172 // code in that case.
10177 _setShoppingButtonState(aBrowser) {
10178 if (aBrowser !== gBrowser.selectedBrowser) {
10182 let button = document.getElementById("shopping-sidebar-button");
10184 let isCurrentBrowserProduct = isProductURL(
10185 gBrowser.selectedBrowser.currentURI
10188 // Only record if the state of the icon will change from hidden to visible.
10189 if (button.hidden && isCurrentBrowserProduct) {
10190 Glean.shopping.addressBarIconDisplayed.record();
10193 button.hidden = !isCurrentBrowserProduct;
10194 button.setAttribute("shoppingsidebaropen", !!this.isActive);
10195 let l10nId = this.isActive
10196 ? "shopping-sidebar-close-button"
10197 : "shopping-sidebar-open-button";
10198 document.l10n.setAttributes(button, l10nId);
10201 handleEvent(event) {
10202 switch (event.type) {
10203 case "TabSelect": {
10204 if (!this._enabled) {
10207 this._updateVisibility();
10208 if (event.detail?.previousTab.linkedBrowser) {
10209 this._updateBCActiveness(event.detail.previousTab.linkedBrowser);
10213 case "visibilitychange": {
10214 if (!this._enabled) {
10217 this._updateBCActiveness(gBrowser.selectedBrowser);