Merge mozilla-central to autoland. a=merge CLOSED TREE
[gecko.git] / browser / base / content / browser.js
blob789b31c3d973d291b2b94ab12f0bd8d107df6de8
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"
8 );
9 var { AppConstants } = ChromeUtils.importESModule(
10   "resource://gre/modules/AppConstants.sys.mjs"
12 ChromeUtils.importESModule("resource://gre/modules/NotificationDB.sys.mjs");
14 // lazy module getters
16 ChromeUtils.defineESModuleGetters(this, {
17   AMTelemetry: "resource://gre/modules/AddonManager.sys.mjs",
18   AboutNewTab: "resource:///modules/AboutNewTab.sys.mjs",
19   AboutReaderParent: "resource:///actors/AboutReaderParent.sys.mjs",
20   AddonManager: "resource://gre/modules/AddonManager.sys.mjs",
21   BrowserSearchTelemetry: "resource:///modules/BrowserSearchTelemetry.sys.mjs",
22   BrowserTelemetryUtils: "resource://gre/modules/BrowserTelemetryUtils.sys.mjs",
23   BrowserUIUtils: "resource:///modules/BrowserUIUtils.sys.mjs",
24   BrowserUsageTelemetry: "resource:///modules/BrowserUsageTelemetry.sys.mjs",
25   BrowserWindowTracker: "resource:///modules/BrowserWindowTracker.sys.mjs",
26   CFRPageActions: "resource:///modules/asrouter/CFRPageActions.sys.mjs",
27   Color: "resource://gre/modules/Color.sys.mjs",
28   ContentAnalysis: "resource:///modules/ContentAnalysis.sys.mjs",
29   ContextualIdentityService:
30     "resource://gre/modules/ContextualIdentityService.sys.mjs",
31   CustomizableUI: "resource:///modules/CustomizableUI.sys.mjs",
32   DevToolsSocketStatus:
33     "resource://devtools/shared/security/DevToolsSocketStatus.sys.mjs",
34   DownloadUtils: "resource://gre/modules/DownloadUtils.sys.mjs",
35   DownloadsCommon: "resource:///modules/DownloadsCommon.sys.mjs",
36   E10SUtils: "resource://gre/modules/E10SUtils.sys.mjs",
37   ExtensionsUI: "resource:///modules/ExtensionsUI.sys.mjs",
38   FirefoxViewNotificationManager:
39     "resource:///modules/firefox-view-notification-manager.sys.mjs",
40   HomePage: "resource:///modules/HomePage.sys.mjs",
41   isProductURL: "chrome://global/content/shopping/ShoppingProduct.mjs",
42   LightweightThemeConsumer:
43     "resource://gre/modules/LightweightThemeConsumer.sys.mjs",
44   LoginHelper: "resource://gre/modules/LoginHelper.sys.mjs",
45   LoginManagerParent: "resource://gre/modules/LoginManagerParent.sys.mjs",
46   MigrationUtils: "resource:///modules/MigrationUtils.sys.mjs",
47   NetUtil: "resource://gre/modules/NetUtil.sys.mjs",
48   NewTabPagePreloading: "resource:///modules/NewTabPagePreloading.sys.mjs",
49   NewTabUtils: "resource://gre/modules/NewTabUtils.sys.mjs",
50   NimbusFeatures: "resource://nimbus/ExperimentAPI.sys.mjs",
51   OpenInTabsUtils: "resource:///modules/OpenInTabsUtils.sys.mjs",
52   PageActions: "resource:///modules/PageActions.sys.mjs",
53   PageThumbs: "resource://gre/modules/PageThumbs.sys.mjs",
54   PanelMultiView: "resource:///modules/PanelMultiView.sys.mjs",
55   PanelView: "resource:///modules/PanelMultiView.sys.mjs",
56   PictureInPicture: "resource://gre/modules/PictureInPicture.sys.mjs",
57   PlacesTransactions: "resource://gre/modules/PlacesTransactions.sys.mjs",
58   PlacesUIUtils: "resource:///modules/PlacesUIUtils.sys.mjs",
59   PlacesUtils: "resource://gre/modules/PlacesUtils.sys.mjs",
60   Pocket: "chrome://pocket/content/Pocket.sys.mjs",
61   PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.sys.mjs",
62   ProcessHangMonitor: "resource:///modules/ProcessHangMonitor.sys.mjs",
63   PromptUtils: "resource://gre/modules/PromptUtils.sys.mjs",
64   ReaderMode: "resource://gre/modules/ReaderMode.sys.mjs",
65   ResetPBMPanel: "resource:///modules/ResetPBMPanel.sys.mjs",
66   ReportBrokenSite: "resource:///modules/ReportBrokenSite.sys.mjs",
67   SafeBrowsing: "resource://gre/modules/SafeBrowsing.sys.mjs",
68   Sanitizer: "resource:///modules/Sanitizer.sys.mjs",
69   SaveToPocket: "chrome://pocket/content/SaveToPocket.sys.mjs",
70   ScreenshotsUtils: "resource:///modules/ScreenshotsUtils.sys.mjs",
71   SearchUIUtils: "resource:///modules/SearchUIUtils.sys.mjs",
72   SessionStartup: "resource:///modules/sessionstore/SessionStartup.sys.mjs",
73   SessionStore: "resource:///modules/sessionstore/SessionStore.sys.mjs",
74   ShoppingSidebarParent: "resource:///actors/ShoppingSidebarParent.sys.mjs",
75   ShoppingSidebarManager: "resource:///actors/ShoppingSidebarParent.sys.mjs",
76   ShortcutUtils: "resource://gre/modules/ShortcutUtils.sys.mjs",
77   SiteDataManager: "resource:///modules/SiteDataManager.sys.mjs",
78   SitePermissions: "resource:///modules/SitePermissions.sys.mjs",
79   SubDialog: "resource://gre/modules/SubDialog.sys.mjs",
80   SubDialogManager: "resource://gre/modules/SubDialog.sys.mjs",
81   TabCrashHandler: "resource:///modules/ContentCrashHandlers.sys.mjs",
82   TabsSetupFlowManager:
83     "resource:///modules/firefox-view-tabs-setup-manager.sys.mjs",
84   TelemetryEnvironment: "resource://gre/modules/TelemetryEnvironment.sys.mjs",
85   TranslationsParent: "resource://gre/actors/TranslationsParent.sys.mjs",
86   UITour: "resource:///modules/UITour.sys.mjs",
87   UpdateUtils: "resource://gre/modules/UpdateUtils.sys.mjs",
88   URILoadingHelper: "resource:///modules/URILoadingHelper.sys.mjs",
89   UrlbarInput: "resource:///modules/UrlbarInput.sys.mjs",
90   UrlbarPrefs: "resource:///modules/UrlbarPrefs.sys.mjs",
91   UrlbarProviderSearchTips:
92     "resource:///modules/UrlbarProviderSearchTips.sys.mjs",
93   UrlbarTokenizer: "resource:///modules/UrlbarTokenizer.sys.mjs",
94   UrlbarUtils: "resource:///modules/UrlbarUtils.sys.mjs",
95   UrlbarValueFormatter: "resource:///modules/UrlbarValueFormatter.sys.mjs",
96   Weave: "resource://services-sync/main.sys.mjs",
97   WebNavigationFrames: "resource://gre/modules/WebNavigationFrames.sys.mjs",
98   webrtcUI: "resource:///modules/webrtcUI.sys.mjs",
99   WebsiteFilter: "resource:///modules/policies/WebsiteFilter.sys.mjs",
100   ZoomUI: "resource:///modules/ZoomUI.sys.mjs",
103 ChromeUtils.defineLazyGetter(this, "fxAccounts", () => {
104   return ChromeUtils.importESModule(
105     "resource://gre/modules/FxAccounts.sys.mjs"
106   ).getFxAccountsSingleton();
109 XPCOMUtils.defineLazyScriptGetter(
110   this,
111   "PlacesTreeView",
112   "chrome://browser/content/places/treeView.js"
114 XPCOMUtils.defineLazyScriptGetter(
115   this,
116   ["PlacesInsertionPoint", "PlacesController", "PlacesControllerDragHelper"],
117   "chrome://browser/content/places/controller.js"
119 XPCOMUtils.defineLazyScriptGetter(
120   this,
121   "PrintUtils",
122   "chrome://global/content/printUtils.js"
124 XPCOMUtils.defineLazyScriptGetter(
125   this,
126   "ZoomManager",
127   "chrome://global/content/viewZoomOverlay.js"
129 XPCOMUtils.defineLazyScriptGetter(
130   this,
131   "FullZoom",
132   "chrome://browser/content/browser-fullZoom.js"
134 XPCOMUtils.defineLazyScriptGetter(
135   this,
136   "PanelUI",
137   "chrome://browser/content/customizableui/panelUI.js"
139 XPCOMUtils.defineLazyScriptGetter(
140   this,
141   "gViewSourceUtils",
142   "chrome://global/content/viewSourceUtils.js"
144 XPCOMUtils.defineLazyScriptGetter(
145   this,
146   "gTabsPanel",
147   "chrome://browser/content/browser-allTabsMenu.js"
149 XPCOMUtils.defineLazyScriptGetter(
150   this,
151   [
152     "BrowserAddonUI",
153     "gExtensionsNotifications",
154     "gUnifiedExtensions",
155     "gXPInstallObserver",
156   ],
157   "chrome://browser/content/browser-addons.js"
159 XPCOMUtils.defineLazyScriptGetter(
160   this,
161   "ctrlTab",
162   "chrome://browser/content/browser-ctrlTab.js"
164 XPCOMUtils.defineLazyScriptGetter(
165   this,
166   ["CustomizationHandler", "AutoHideMenubar"],
167   "chrome://browser/content/browser-customization.js"
169 XPCOMUtils.defineLazyScriptGetter(
170   this,
171   ["PointerLock", "FullScreen"],
172   "chrome://browser/content/browser-fullScreenAndPointerLock.js"
174 XPCOMUtils.defineLazyScriptGetter(
175   this,
176   "gIdentityHandler",
177   "chrome://browser/content/browser-siteIdentity.js"
179 XPCOMUtils.defineLazyScriptGetter(
180   this,
181   "gPermissionPanel",
182   "chrome://browser/content/browser-sitePermissionPanel.js"
184 XPCOMUtils.defineLazyScriptGetter(
185   this,
186   "SelectTranslationsPanel",
187   "chrome://browser/content/translations/selectTranslationsPanel.js"
189 XPCOMUtils.defineLazyScriptGetter(
190   this,
191   "FullPageTranslationsPanel",
192   "chrome://browser/content/translations/fullPageTranslationsPanel.js"
194 XPCOMUtils.defineLazyScriptGetter(
195   this,
196   "gProtectionsHandler",
197   "chrome://browser/content/browser-siteProtections.js"
199 XPCOMUtils.defineLazyScriptGetter(
200   this,
201   ["gGestureSupport", "gHistorySwipeAnimation"],
202   "chrome://browser/content/browser-gestureSupport.js"
204 XPCOMUtils.defineLazyScriptGetter(
205   this,
206   "gSafeBrowsing",
207   "chrome://browser/content/browser-safebrowsing.js"
209 XPCOMUtils.defineLazyScriptGetter(
210   this,
211   "gSync",
212   "chrome://browser/content/browser-sync.js"
214 XPCOMUtils.defineLazyScriptGetter(
215   this,
216   "gBrowserThumbnails",
217   "chrome://browser/content/browser-thumbnails.js"
219 XPCOMUtils.defineLazyScriptGetter(
220   this,
221   ["openContextMenu", "nsContextMenu"],
222   "chrome://browser/content/nsContextMenu.js"
224 XPCOMUtils.defineLazyScriptGetter(
225   this,
226   [
227     "DownloadsPanel",
228     "DownloadsOverlayLoader",
229     "DownloadsView",
230     "DownloadsViewUI",
231     "DownloadsViewController",
232     "DownloadsSummary",
233     "DownloadsFooter",
234     "DownloadsBlockedSubview",
235   ],
236   "chrome://browser/content/downloads/downloads.js"
238 XPCOMUtils.defineLazyScriptGetter(
239   this,
240   ["DownloadsButton", "DownloadsIndicatorView"],
241   "chrome://browser/content/downloads/indicator.js"
243 XPCOMUtils.defineLazyScriptGetter(
244   this,
245   "gEditItemOverlay",
246   "chrome://browser/content/places/editBookmark.js"
248 XPCOMUtils.defineLazyScriptGetter(
249   this,
250   "gGfxUtils",
251   "chrome://browser/content/browser-graphics-utils.js"
253 XPCOMUtils.defineLazyScriptGetter(
254   this,
255   "pktUI",
256   "chrome://pocket/content/pktUI.js"
258 XPCOMUtils.defineLazyScriptGetter(
259   this,
260   "ToolbarKeyboardNavigator",
261   "chrome://browser/content/browser-toolbarKeyNav.js"
263 XPCOMUtils.defineLazyScriptGetter(
264   this,
265   "A11yUtils",
266   "chrome://browser/content/browser-a11yUtils.js"
268 XPCOMUtils.defineLazyScriptGetter(
269   this,
270   "gSharedTabWarning",
271   "chrome://browser/content/browser-webrtc.js"
273 XPCOMUtils.defineLazyScriptGetter(
274   this,
275   "gPageStyleMenu",
276   "chrome://browser/content/browser-pagestyle.js"
278 XPCOMUtils.defineLazyScriptGetter(
279   this,
280   "gProfiles",
281   "chrome://browser/content/browser-profiles.js"
284 // lazy service getters
286 XPCOMUtils.defineLazyServiceGetters(this, {
287   ContentPrefService2: [
288     "@mozilla.org/content-pref/service;1",
289     "nsIContentPrefService2",
290   ],
291   classifierService: [
292     "@mozilla.org/url-classifier/dbservice;1",
293     "nsIURIClassifier",
294   ],
295   Favicons: ["@mozilla.org/browser/favicon-service;1", "nsIFaviconService"],
296   WindowsUIUtils: ["@mozilla.org/windows-ui-utils;1", "nsIWindowsUIUtils"],
297   BrowserHandler: ["@mozilla.org/browser/clh;1", "nsIBrowserHandler"],
300 if (AppConstants.ENABLE_WEBDRIVER) {
301   XPCOMUtils.defineLazyServiceGetter(
302     this,
303     "Marionette",
304     "@mozilla.org/remote/marionette;1",
305     "nsIMarionette"
306   );
308   XPCOMUtils.defineLazyServiceGetter(
309     this,
310     "RemoteAgent",
311     "@mozilla.org/remote/agent;1",
312     "nsIRemoteAgent"
313   );
314 } else {
315   this.Marionette = { running: false };
316   this.RemoteAgent = { running: false };
319 ChromeUtils.defineLazyGetter(this, "RTL_UI", () => {
320   return Services.locale.isAppLocaleRTL;
323 ChromeUtils.defineLazyGetter(this, "gBrandBundle", () => {
324   return Services.strings.createBundle(
325     "chrome://branding/locale/brand.properties"
326   );
329 ChromeUtils.defineLazyGetter(this, "gBrowserBundle", () => {
330   return Services.strings.createBundle(
331     "chrome://browser/locale/browser.properties"
332   );
335 ChromeUtils.defineLazyGetter(this, "gCustomizeMode", () => {
336   let { CustomizeMode } = ChromeUtils.importESModule(
337     "resource:///modules/CustomizeMode.sys.mjs"
338   );
339   return new CustomizeMode(window);
342 ChromeUtils.defineLazyGetter(this, "gNavToolbox", () => {
343   return document.getElementById("navigator-toolbox");
346 ChromeUtils.defineLazyGetter(this, "gURLBar", () => {
347   let urlbar = new UrlbarInput({
348     textbox: document.getElementById("urlbar"),
349     eventTelemetryCategory: "urlbar",
350   });
352   let beforeFocusOrSelect = event => {
353     // In customize mode, the url bar is disabled. If a new tab is opened or the
354     // user switches to a different tab, this function gets called before we've
355     // finished leaving customize mode, and the url bar will still be disabled.
356     // We can't focus it when it's disabled, so we need to re-run ourselves when
357     // we've finished leaving customize mode.
358     if (
359       CustomizationHandler.isCustomizing() ||
360       CustomizationHandler.isExitingCustomizeMode
361     ) {
362       gNavToolbox.addEventListener(
363         "aftercustomization",
364         () => {
365           if (event.type == "beforeselect") {
366             gURLBar.select();
367           } else {
368             gURLBar.focus();
369           }
370         },
371         {
372           once: true,
373         }
374       );
375       event.preventDefault();
376       return;
377     }
379     if (window.fullScreen) {
380       FullScreen.showNavToolbox();
381     }
382   };
383   urlbar.addEventListener("beforefocus", beforeFocusOrSelect);
384   urlbar.addEventListener("beforeselect", beforeFocusOrSelect);
386   return urlbar;
389 ChromeUtils.defineLazyGetter(this, "ReferrerInfo", () =>
390   Components.Constructor(
391     "@mozilla.org/referrer-info;1",
392     "nsIReferrerInfo",
393     "init"
394   )
397 // High priority notification bars shown at the top of the window.
398 ChromeUtils.defineLazyGetter(this, "gNotificationBox", () => {
399   return new MozElements.NotificationBox(element => {
400     element.classList.add("global-notificationbox");
401     element.setAttribute("notificationside", "top");
402     element.setAttribute("prepend-notifications", true);
403     const tabNotifications = document.getElementById("tab-notification-deck");
404     gNavToolbox.insertBefore(element, tabNotifications);
405   });
408 ChromeUtils.defineLazyGetter(this, "InlineSpellCheckerUI", () => {
409   let { InlineSpellChecker } = ChromeUtils.importESModule(
410     "resource://gre/modules/InlineSpellChecker.sys.mjs"
411   );
412   return new InlineSpellChecker();
415 ChromeUtils.defineLazyGetter(this, "PopupNotifications", () => {
416   // eslint-disable-next-line no-shadow
417   let { PopupNotifications } = ChromeUtils.importESModule(
418     "resource://gre/modules/PopupNotifications.sys.mjs"
419   );
420   try {
421     // Hide all PopupNotifications while the the address bar has focus,
422     // including the virtual focus in the results popup, and the URL is being
423     // edited or the page proxy state is invalid while async tab switching.
424     let shouldSuppress = () => {
425       // "Blank" pages, like about:welcome, have a pageproxystate of "invalid", but
426       // popups like CFRs should not automatically be suppressed when the address
427       // bar has focus on these pages as it disrupts user navigation using FN+F6.
428       // See `UrlbarInput.setURI()` where pageproxystate is set to "invalid" for
429       // all pages that the "isBlankPageURL" method returns true for.
430       const urlBarEdited = isBlankPageURL(gBrowser.currentURI.spec)
431         ? gURLBar.hasAttribute("usertyping")
432         : gURLBar.getAttribute("pageproxystate") != "valid";
433       return (
434         (urlBarEdited && gURLBar.focused) ||
435         (gURLBar.getAttribute("pageproxystate") != "valid" &&
436           gBrowser.selectedBrowser._awaitingSetURI) ||
437         shouldSuppressPopupNotifications()
438       );
439     };
441     // Before a Popup is shown, check that its anchor is visible.
442     // If the anchor is not visible, use one of the fallbacks.
443     // If no fallbacks are visible, return null.
444     const getVisibleAnchorElement = anchorElement => {
445       // If the anchor element is present in the Urlbar,
446       // ensure that both the anchor and page URL are visible.
447       gURLBar.maybeHandleRevertFromPopup(anchorElement);
448       if (anchorElement?.checkVisibility()) {
449         return anchorElement;
450       }
451       let fallback = [
452         document.getElementById("identity-icon"),
453         document.getElementById("urlbar-search-button"),
454       ];
455       return fallback.find(element => element?.checkVisibility()) ?? null;
456     };
458     return new PopupNotifications(
459       gBrowser,
460       document.getElementById("notification-popup"),
461       document.getElementById("notification-popup-box"),
462       { shouldSuppress, getVisibleAnchorElement }
463     );
464   } catch (ex) {
465     console.error(ex);
466     return null;
467   }
470 ChromeUtils.defineLazyGetter(this, "MacUserActivityUpdater", () => {
471   if (AppConstants.platform != "macosx") {
472     return null;
473   }
475   return Cc["@mozilla.org/widget/macuseractivityupdater;1"].getService(
476     Ci.nsIMacUserActivityUpdater
477   );
480 ChromeUtils.defineLazyGetter(this, "Win7Features", () => {
481   if (AppConstants.platform != "win") {
482     return null;
483   }
485   const WINTASKBAR_CONTRACTID = "@mozilla.org/windows-taskbar;1";
486   if (
487     WINTASKBAR_CONTRACTID in Cc &&
488     Cc[WINTASKBAR_CONTRACTID].getService(Ci.nsIWinTaskbar).available
489   ) {
490     let { AeroPeek } = ChromeUtils.importESModule(
491       "resource:///modules/WindowsPreviewPerTab.sys.mjs"
492     );
493     return {
494       onOpenWindow() {
495         AeroPeek.onOpenWindow(window);
496         this.handledOpening = true;
497       },
498       onCloseWindow() {
499         if (this.handledOpening) {
500           AeroPeek.onCloseWindow(window);
501         }
502       },
503       handledOpening: false,
504     };
505   }
506   return null;
509 XPCOMUtils.defineLazyPreferenceGetter(
510   this,
511   "gToolbarKeyNavEnabled",
512   "browser.toolbars.keyboard_navigation",
513   false,
514   (aPref, aOldVal, aNewVal) => {
515     if (window.closed) {
516       return;
517     }
518     if (aNewVal) {
519       ToolbarKeyboardNavigator.init();
520     } else {
521       ToolbarKeyboardNavigator.uninit();
522     }
523   }
526 XPCOMUtils.defineLazyPreferenceGetter(
527   this,
528   "gBookmarksToolbarVisibility",
529   "browser.toolbars.bookmarks.visibility",
530   "newtab"
533 XPCOMUtils.defineLazyPreferenceGetter(
534   this,
535   "gFxaToolbarEnabled",
536   "identity.fxaccounts.toolbar.enabled",
537   false,
538   (aPref, aOldVal, aNewVal) => {
539     updateFxaToolbarMenu(aNewVal);
540   }
543 XPCOMUtils.defineLazyPreferenceGetter(
544   this,
545   "gFxaToolbarAccessed",
546   "identity.fxaccounts.toolbar.accessed",
547   false,
548   () => {
549     updateFxaToolbarMenu(gFxaToolbarEnabled);
550   }
553 XPCOMUtils.defineLazyPreferenceGetter(
554   this,
555   "gAddonAbuseReportEnabled",
556   "extensions.abuseReport.enabled",
557   false
560 XPCOMUtils.defineLazyPreferenceGetter(
561   this,
562   "gAlwaysOpenPanel",
563   "browser.download.alwaysOpenPanel",
564   true
567 XPCOMUtils.defineLazyPreferenceGetter(
568   this,
569   "gMiddleClickNewTabUsesPasteboard",
570   "browser.tabs.searchclipboardfor.middleclick",
571   true
574 XPCOMUtils.defineLazyPreferenceGetter(
575   this,
576   "gScreenshotsDisabled",
577   "extensions.screenshots.disabled",
578   false,
579   () => {
580     Services.obs.notifyObservers(
581       window,
582       "toggle-screenshot-disable",
583       gScreenshots.shouldScreenshotsButtonBeDisabled()
584     );
585   }
588 XPCOMUtils.defineLazyPreferenceGetter(
589   this,
590   "gPrintEnabled",
591   "print.enabled",
592   false,
593   (aPref, aOldVal, aNewVal) => {
594     updatePrintCommands(aNewVal);
595   }
598 XPCOMUtils.defineLazyPreferenceGetter(
599   this,
600   "gScreenshotsComponentEnabled",
601   "screenshots.browser.component.enabled",
602   false,
603   () => {
604     Services.obs.notifyObservers(
605       window,
606       "toggle-screenshot-disable",
607       gScreenshots.shouldScreenshotsButtonBeDisabled()
608     );
609   }
612 XPCOMUtils.defineLazyPreferenceGetter(
613   this,
614   "gTranslationsEnabled",
615   "browser.translations.enable",
616   false
619 XPCOMUtils.defineLazyPreferenceGetter(
620   this,
621   "gUseFeltPrivacyUI",
622   "browser.privatebrowsing.felt-privacy-v1",
623   false
626 customElements.setElementCreationCallback("screenshots-buttons", () => {
627   Services.scriptloader.loadSubScript(
628     "chrome://browser/content/screenshots/screenshots-buttons.js",
629     window
630   );
633 var gBrowser;
634 var gContextMenu = null; // nsContextMenu instance
635 var gMultiProcessBrowser = window.docShell.QueryInterface(
636   Ci.nsILoadContext
637 ).useRemoteTabs;
638 var gFissionBrowser = window.docShell.QueryInterface(
639   Ci.nsILoadContext
640 ).useRemoteSubframes;
642 var gBrowserAllowScriptsToCloseInitialTabs = false;
644 if (AppConstants.platform != "macosx") {
645   var gEditUIVisible = true;
648 Object.defineProperty(this, "gReduceMotion", {
649   enumerable: true,
650   get() {
651     return typeof gReduceMotionOverride == "boolean"
652       ? gReduceMotionOverride
653       : gReduceMotionSetting;
654   },
656 // Reduce motion during startup. The setting will be reset later.
657 let gReduceMotionSetting = true;
658 // This is for tests to set.
659 var gReduceMotionOverride;
661 // Smart getter for the findbar.  If you don't wish to force the creation of
662 // the findbar, check gFindBarInitialized first.
664 Object.defineProperty(this, "gFindBar", {
665   enumerable: true,
666   get() {
667     return gBrowser.getCachedFindBar();
668   },
671 Object.defineProperty(this, "gFindBarInitialized", {
672   enumerable: true,
673   get() {
674     return gBrowser.isFindBarInitialized();
675   },
678 Object.defineProperty(this, "gFindBarPromise", {
679   enumerable: true,
680   get() {
681     return gBrowser.getFindBar();
682   },
685 function shouldSuppressPopupNotifications() {
686   // We have to hide notifications explicitly when the window is
687   // minimized because of the effects of the "noautohide" attribute on Linux.
688   // This can be removed once bug 545265 and bug 1320361 are fixed.
689   // Hide popup notifications when system tab prompts are shown so they
690   // don't cover up the prompt.
691   return (
692     window.windowState == window.STATE_MINIMIZED ||
693     gBrowser?.selectedBrowser.hasAttribute("tabDialogShowing") ||
694     gDialogBox?.isOpen
695   );
698 async function gLazyFindCommand(cmd, ...args) {
699   let fb = await gFindBarPromise;
700   // We could be closed by now, or the tab with XBL binding could have gone away:
701   if (fb && fb[cmd]) {
702     fb[cmd].apply(fb, args);
703   }
706 var gPageIcons = {
707   "about:home": "chrome://branding/content/icon32.png",
708   "about:newtab": "chrome://branding/content/icon32.png",
709   "about:welcome": "chrome://branding/content/icon32.png",
710   "about:privatebrowsing": "chrome://browser/skin/privatebrowsing/favicon.svg",
713 var gInitialPages = [
714   "about:blank",
715   "about:home",
716   "about:firefoxview",
717   "about:newtab",
718   "about:privatebrowsing",
719   "about:sessionrestore",
720   "about:welcome",
721   "about:welcomeback",
722   "chrome://browser/content/blanktab.html",
723   "about:profilemanager",
726 function isInitialPage(url) {
727   if (!(url instanceof Ci.nsIURI)) {
728     try {
729       url = Services.io.newURI(url);
730     } catch (ex) {
731       return false;
732     }
733   }
735   let nonQuery = url.prePath + url.filePath;
736   return gInitialPages.includes(nonQuery) || nonQuery == BROWSER_NEW_TAB_URL;
739 function browserWindows() {
740   return Services.wm.getEnumerator("navigator:browser");
743 function updateBookmarkToolbarVisibility() {
744   BookmarkingUI.updateEmptyToolbarMessage();
745   setToolbarVisibility(
746     BookmarkingUI.toolbar,
747     gBookmarksToolbarVisibility,
748     false,
749     false
750   );
753 // This is a stringbundle-like interface to gBrowserBundle, formerly a getter for
754 // the "bundle_browser" element.
755 var gNavigatorBundle = {
756   getString(key) {
757     return gBrowserBundle.GetStringFromName(key);
758   },
759   getFormattedString(key, array) {
760     return gBrowserBundle.formatStringFromName(key, array);
761   },
764 var gScreenshots = {
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;
776   },
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",
784     false
785   );
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",
794     ""
795   );
796   mainWindowEl.setAttribute(
797     "fxastatus",
798     statusGuess ? "signed_in" : "not_configured"
799   );
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();
813     }
815     Services.telemetry.setEventRecordingEnabled("fxa_avatar_menu", true);
816   } else {
817     mainWindowEl.removeAttribute("fxatoolbarmenu");
818   }
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) {
833     if (backDisabled) {
834       backCommand.removeAttribute("disabled");
835     } else {
836       backCommand.setAttribute("disabled", true);
837     }
838   }
840   if (forwardDisabled == aWebNavigation.canGoForward) {
841     if (forwardDisabled) {
842       forwardCommand.removeAttribute("disabled");
843     } else {
844       forwardCommand.setAttribute("disabled", true);
845     }
846   }
849 function updatePrintCommands(enabled) {
850   var printCommand = document.getElementById("cmd_print");
851   var printPreviewCommand = document.getElementById("cmd_printPreviewToggle");
853   if (enabled) {
854     printCommand.removeAttribute("disabled");
855     printPreviewCommand.removeAttribute("disabled");
856   } else {
857     printCommand.setAttribute("disabled", "true");
858     printPreviewCommand.setAttribute("disabled", "true");
859   }
863  * Click-and-Hold implementation for the Back and Forward buttons
864  * XXXmano: should this live in toolbarbutton.js?
865  */
866 function SetClickAndHoldHandlers() {
867   // Bug 414797: Clone the back/forward buttons' context menu into both buttons.
868   let popup = document.getElementById("backForwardMenu").cloneNode(true);
869   popup.removeAttribute("id");
870   // Prevent the back/forward buttons' context attributes from being inherited.
871   popup.setAttribute("context", "");
873   let backButton = document.getElementById("back-button");
874   backButton.setAttribute("type", "menu");
875   backButton.prepend(popup);
876   gClickAndHoldListenersOnElement.add(backButton);
878   let forwardButton = document.getElementById("forward-button");
879   popup = popup.cloneNode(true);
880   forwardButton.setAttribute("type", "menu");
881   forwardButton.prepend(popup);
882   gClickAndHoldListenersOnElement.add(forwardButton);
885 const gClickAndHoldListenersOnElement = {
886   _timers: new Map(),
888   _mousedownHandler(aEvent) {
889     if (
890       aEvent.button != 0 ||
891       aEvent.currentTarget.open ||
892       aEvent.currentTarget.disabled
893     ) {
894       return;
895     }
897     // Prevent the menupopup from opening immediately
898     aEvent.currentTarget.menupopup.hidden = true;
900     aEvent.currentTarget.addEventListener("mouseout", this);
901     aEvent.currentTarget.addEventListener("mouseup", this);
902     this._timers.set(
903       aEvent.currentTarget,
904       setTimeout(b => this._openMenu(b), 500, aEvent.currentTarget)
905     );
906   },
908   _clickHandler(aEvent) {
909     if (
910       aEvent.button == 0 &&
911       aEvent.target == aEvent.currentTarget &&
912       !aEvent.currentTarget.open &&
913       !aEvent.currentTarget.disabled &&
914       // When menupopup is not hidden and we receive
915       // a click event, it means the mousedown occurred
916       // on aEvent.currentTarget and mouseup occurred on
917       // aEvent.currentTarget.menupopup, we don't
918       // need to handle the click event as menupopup
919       // handled mouseup event already.
920       aEvent.currentTarget.menupopup.hidden
921     ) {
922       let cmdEvent = document.createEvent("xulcommandevent");
923       cmdEvent.initCommandEvent(
924         "command",
925         true,
926         true,
927         window,
928         0,
929         aEvent.ctrlKey,
930         aEvent.altKey,
931         aEvent.shiftKey,
932         aEvent.metaKey,
933         0,
934         null,
935         aEvent.inputSource
936       );
937       aEvent.currentTarget.dispatchEvent(cmdEvent);
939       // This is here to cancel the XUL default event
940       // dom.click() triggers a command even if there is a click handler
941       // however this can now be prevented with preventDefault().
942       aEvent.preventDefault();
943     }
944   },
946   _openMenu(aButton) {
947     this._cancelHold(aButton);
948     aButton.firstElementChild.hidden = false;
949     aButton.open = true;
950   },
952   _mouseoutHandler(aEvent) {
953     let buttonRect = aEvent.currentTarget.getBoundingClientRect();
954     if (
955       aEvent.clientX >= buttonRect.left &&
956       aEvent.clientX <= buttonRect.right &&
957       aEvent.clientY >= buttonRect.bottom
958     ) {
959       this._openMenu(aEvent.currentTarget);
960     } else {
961       this._cancelHold(aEvent.currentTarget);
962     }
963   },
965   _mouseupHandler(aEvent) {
966     this._cancelHold(aEvent.currentTarget);
967   },
969   _cancelHold(aButton) {
970     clearTimeout(this._timers.get(aButton));
971     aButton.removeEventListener("mouseout", this);
972     aButton.removeEventListener("mouseup", this);
973   },
975   _keypressHandler(aEvent) {
976     if (aEvent.key == " " || aEvent.key == "Enter") {
977       // Normally, command events get fired for keyboard activation. However,
978       // we've set type="menu", so that doesn't happen. Handle this the same
979       // way we handle clicks.
980       aEvent.target.click();
981     }
982   },
984   handleEvent(e) {
985     switch (e.type) {
986       case "mouseout":
987         this._mouseoutHandler(e);
988         break;
989       case "mousedown":
990         this._mousedownHandler(e);
991         break;
992       case "click":
993         this._clickHandler(e);
994         break;
995       case "mouseup":
996         this._mouseupHandler(e);
997         break;
998       case "keypress":
999         this._keypressHandler(e);
1000         break;
1001     }
1002   },
1004   remove(aButton) {
1005     aButton.removeEventListener("mousedown", this, true);
1006     aButton.removeEventListener("click", this, true);
1007     aButton.removeEventListener("keypress", this, true);
1008   },
1010   add(aElm) {
1011     this._timers.delete(aElm);
1013     aElm.addEventListener("mousedown", this, true);
1014     aElm.addEventListener("click", this, true);
1015     aElm.addEventListener("keypress", this, true);
1016   },
1019 const gSessionHistoryObserver = {
1020   observe(subject, topic) {
1021     if (topic != "browser:purge-session-history") {
1022       return;
1023     }
1025     var backCommand = document.getElementById("Browser:Back");
1026     backCommand.setAttribute("disabled", "true");
1027     var fwdCommand = document.getElementById("Browser:Forward");
1028     fwdCommand.setAttribute("disabled", "true");
1030     // Clear undo history of the URL bar
1031     gURLBar.editor.clearUndoRedo();
1032   },
1035 const gStoragePressureObserver = {
1036   _lastNotificationTime: -1,
1038   async observe(subject, topic) {
1039     if (topic != "QuotaManager::StoragePressure") {
1040       return;
1041     }
1043     const NOTIFICATION_VALUE = "storage-pressure-notification";
1044     if (gNotificationBox.getNotificationWithValue(NOTIFICATION_VALUE)) {
1045       // Do not display the 2nd notification when there is already one
1046       return;
1047     }
1049     // Don't display notification twice within the given interval.
1050     // This is because
1051     //   - not to annoy user
1052     //   - give user some time to clean space.
1053     //     Even user sees notification and starts acting, it still takes some time.
1054     const MIN_NOTIFICATION_INTERVAL_MS = Services.prefs.getIntPref(
1055       "browser.storageManager.pressureNotification.minIntervalMS"
1056     );
1057     let duration = Date.now() - this._lastNotificationTime;
1058     if (duration <= MIN_NOTIFICATION_INTERVAL_MS) {
1059       return;
1060     }
1061     this._lastNotificationTime = Date.now();
1063     MozXULElement.insertFTLIfNeeded("branding/brand.ftl");
1064     MozXULElement.insertFTLIfNeeded("browser/preferences/preferences.ftl");
1066     const BYTES_IN_GIGABYTE = 1073741824;
1067     const USAGE_THRESHOLD_BYTES =
1068       BYTES_IN_GIGABYTE *
1069       Services.prefs.getIntPref(
1070         "browser.storageManager.pressureNotification.usageThresholdGB"
1071       );
1072     let messageFragment = document.createDocumentFragment();
1073     let message = document.createElement("span");
1075     let buttons = [{ supportPage: "storage-permissions" }];
1076     let usage = subject.QueryInterface(Ci.nsISupportsPRUint64).data;
1077     if (usage < USAGE_THRESHOLD_BYTES) {
1078       // The firefox-used space < 5GB, then warn user to free some disk space.
1079       // This is because this usage is small and not the main cause for space issue.
1080       // In order to avoid the bad and wrong impression among users that
1081       // firefox eats disk space a lot, indicate users to clean up other disk space.
1082       document.l10n.setAttributes(message, "space-alert-under-5gb-message2");
1083     } else {
1084       // The firefox-used space >= 5GB, then guide users to about:preferences
1085       // to clear some data stored on firefox by websites.
1086       document.l10n.setAttributes(message, "space-alert-over-5gb-message2");
1087       buttons.push({
1088         "l10n-id": "space-alert-over-5gb-settings-button",
1089         callback() {
1090           // The advanced subpanes are only supported in the old organization, which will
1091           // be removed by bug 1349689.
1092           openPreferences("privacy-sitedata");
1093         },
1094       });
1095     }
1096     messageFragment.appendChild(message);
1098     await gNotificationBox.appendNotification(
1099       NOTIFICATION_VALUE,
1100       {
1101         label: messageFragment,
1102         priority: gNotificationBox.PRIORITY_WARNING_HIGH,
1103       },
1104       buttons
1105     );
1107     // This seems to be necessary to get the buttons to display correctly
1108     // See: https://bugzilla.mozilla.org/show_bug.cgi?id=1504216
1109     document.l10n.translateFragment(gNotificationBox.currentNotification);
1110   },
1113 var gPopupBlockerObserver = {
1114   async handleEvent(aEvent) {
1115     if (aEvent.originalTarget != gBrowser.selectedBrowser) {
1116       return;
1117     }
1119     gPermissionPanel.refreshPermissionIcons();
1121     let popupCount =
1122       gBrowser.selectedBrowser.popupBlocker.getBlockedPopupCount();
1124     if (!popupCount) {
1125       // Hide the notification box (if it's visible).
1126       let notificationBox = gBrowser.getNotificationBox();
1127       let notification =
1128         notificationBox.getNotificationWithValue("popup-blocked");
1129       if (notification) {
1130         notificationBox.removeNotification(notification, false);
1131       }
1132       return;
1133     }
1135     // Only show the notification again if we've not already shown it. Since
1136     // notifications are per-browser, we don't need to worry about re-adding
1137     // it.
1138     if (gBrowser.selectedBrowser.popupBlocker.shouldShowNotification) {
1139       if (Services.prefs.getBoolPref("privacy.popups.showBrowserMessage")) {
1140         const label = {
1141           "l10n-id":
1142             popupCount < this.maxReportedPopups
1143               ? "popup-warning-message"
1144               : "popup-warning-exceeded-message",
1145           "l10n-args": { popupCount },
1146         };
1148         let notificationBox = gBrowser.getNotificationBox();
1149         let notification =
1150           notificationBox.getNotificationWithValue("popup-blocked") ||
1151           (await this.notificationPromise);
1152         if (notification) {
1153           notification.label = label;
1154         } else {
1155           const image = "chrome://browser/skin/notification-icons/popup.svg";
1156           const priority = notificationBox.PRIORITY_INFO_MEDIUM;
1157           try {
1158             this.notificationPromise = notificationBox.appendNotification(
1159               "popup-blocked",
1160               { label, image, priority },
1161               [
1162                 {
1163                   "l10n-id": "popup-warning-button",
1164                   popup: "blockedPopupOptions",
1165                   callback: null,
1166                 },
1167               ]
1168             );
1169             await this.notificationPromise;
1170           } catch (err) {
1171             console.warn(err);
1172           } finally {
1173             this.notificationPromise = null;
1174           }
1175         }
1176       }
1178       // Record the fact that we've reported this blocked popup, so we don't
1179       // show it again.
1180       gBrowser.selectedBrowser.popupBlocker.didShowNotification();
1181     }
1182   },
1184   toggleAllowPopupsForSite(aEvent) {
1185     var pm = Services.perms;
1186     var shouldBlock = aEvent.target.getAttribute("block") == "true";
1187     var perm = shouldBlock ? pm.DENY_ACTION : pm.ALLOW_ACTION;
1188     pm.addFromPrincipal(gBrowser.contentPrincipal, "popup", perm);
1190     if (!shouldBlock) {
1191       gBrowser.selectedBrowser.popupBlocker.unblockAllPopups();
1192     }
1194     gBrowser.getNotificationBox().removeCurrentNotification();
1195   },
1197   fillPopupList(aEvent) {
1198     // XXXben - rather than using |currentURI| here, which breaks down on multi-framed sites
1199     //          we should really walk the blockedPopups and create a list of "allow for <host>"
1200     //          menuitems for the common subset of hosts present in the report, this will
1201     //          make us frame-safe.
1202     //
1203     // XXXjst - Note that when this is fixed to work with multi-framed sites,
1204     //          also back out the fix for bug 343772 where
1205     //          nsGlobalWindow::CheckOpenAllow() was changed to also
1206     //          check if the top window's location is allow-listed.
1207     let browser = gBrowser.selectedBrowser;
1208     var uriOrPrincipal = browser.contentPrincipal.isContentPrincipal
1209       ? browser.contentPrincipal
1210       : browser.currentURI;
1211     var blockedPopupAllowSite = document.getElementById(
1212       "blockedPopupAllowSite"
1213     );
1214     try {
1215       blockedPopupAllowSite.removeAttribute("hidden");
1216       let uriHost = uriOrPrincipal.asciiHost
1217         ? uriOrPrincipal.host
1218         : uriOrPrincipal.spec;
1219       var pm = Services.perms;
1220       if (
1221         pm.testPermissionFromPrincipal(browser.contentPrincipal, "popup") ==
1222         pm.ALLOW_ACTION
1223       ) {
1224         // Offer an item to block popups for this site, if an allow-list entry exists
1225         // already for it.
1226         document.l10n.setAttributes(
1227           blockedPopupAllowSite,
1228           "popups-infobar-block",
1229           { uriHost }
1230         );
1231         blockedPopupAllowSite.setAttribute("block", "true");
1232       } else {
1233         // Offer an item to allow popups for this site
1234         document.l10n.setAttributes(
1235           blockedPopupAllowSite,
1236           "popups-infobar-allow",
1237           { uriHost }
1238         );
1239         blockedPopupAllowSite.removeAttribute("block");
1240       }
1241     } catch (e) {
1242       blockedPopupAllowSite.hidden = true;
1243     }
1245     let blockedPopupDontShowMessage = document.getElementById(
1246       "blockedPopupDontShowMessage"
1247     );
1248     let showMessage = Services.prefs.getBoolPref(
1249       "privacy.popups.showBrowserMessage"
1250     );
1251     blockedPopupDontShowMessage.setAttribute("checked", !showMessage);
1253     let blockedPopupsSeparator = document.getElementById(
1254       "blockedPopupsSeparator"
1255     );
1256     blockedPopupsSeparator.hidden = true;
1258     browser.popupBlocker.getBlockedPopups().then(blockedPopups => {
1259       let foundUsablePopupURI = false;
1260       if (blockedPopups) {
1261         for (let i = 0; i < blockedPopups.length; i++) {
1262           let blockedPopup = blockedPopups[i];
1264           // popupWindowURI will be null if the file picker popup is blocked.
1265           // xxxdz this should make the option say "Show file picker" and do it (Bug 590306)
1266           if (!blockedPopup.popupWindowURISpec) {
1267             continue;
1268           }
1270           var popupURIspec = blockedPopup.popupWindowURISpec;
1272           // Sometimes the popup URI that we get back from the blockedPopup
1273           // isn't useful (for instance, netscape.com's popup URI ends up
1274           // being "http://www.netscape.com", which isn't really the URI of
1275           // the popup they're trying to show).  This isn't going to be
1276           // useful to the user, so we won't create a menu item for it.
1277           if (
1278             popupURIspec == "" ||
1279             popupURIspec == "about:blank" ||
1280             popupURIspec == "<self>" ||
1281             popupURIspec == uriOrPrincipal.spec
1282           ) {
1283             continue;
1284           }
1286           // Because of the short-circuit above, we may end up in a situation
1287           // in which we don't have any usable popup addresses to show in
1288           // the menu, and therefore we shouldn't show the separator.  However,
1289           // since we got past the short-circuit, we must've found at least
1290           // one usable popup URI and thus we'll turn on the separator later.
1291           foundUsablePopupURI = true;
1293           var menuitem = document.createXULElement("menuitem");
1294           document.l10n.setAttributes(menuitem, "popup-show-popup-menuitem", {
1295             popupURI: popupURIspec,
1296           });
1297           menuitem.setAttribute(
1298             "oncommand",
1299             "gPopupBlockerObserver.showBlockedPopup(event);"
1300           );
1301           menuitem.setAttribute("popupReportIndex", i);
1302           menuitem.setAttribute(
1303             "popupInnerWindowId",
1304             blockedPopup.innerWindowId
1305           );
1306           menuitem.browsingContext = blockedPopup.browsingContext;
1307           menuitem.popupReportBrowser = browser;
1308           aEvent.target.appendChild(menuitem);
1309         }
1310       }
1312       // Show the separator if we added any
1313       // showable popup addresses to the menu.
1314       if (foundUsablePopupURI) {
1315         blockedPopupsSeparator.removeAttribute("hidden");
1316       }
1317     }, null);
1318   },
1320   onPopupHiding(aEvent) {
1321     let item = aEvent.target.lastElementChild;
1322     while (item && item.id != "blockedPopupsSeparator") {
1323       let next = item.previousElementSibling;
1324       item.remove();
1325       item = next;
1326     }
1327   },
1329   showBlockedPopup(aEvent) {
1330     let target = aEvent.target;
1331     let browsingContext = target.browsingContext;
1332     let innerWindowId = target.getAttribute("popupInnerWindowId");
1333     let popupReportIndex = target.getAttribute("popupReportIndex");
1334     let browser = target.popupReportBrowser;
1335     browser.popupBlocker.unblockPopup(
1336       browsingContext,
1337       innerWindowId,
1338       popupReportIndex
1339     );
1340   },
1342   editPopupSettings() {
1343     openPreferences("privacy-permissions-block-popups");
1344   },
1346   dontShowMessage() {
1347     var showMessage = Services.prefs.getBoolPref(
1348       "privacy.popups.showBrowserMessage"
1349     );
1350     Services.prefs.setBoolPref(
1351       "privacy.popups.showBrowserMessage",
1352       !showMessage
1353     );
1354     gBrowser.getNotificationBox().removeCurrentNotification();
1355   },
1358 XPCOMUtils.defineLazyPreferenceGetter(
1359   gPopupBlockerObserver,
1360   "maxReportedPopups",
1361   "privacy.popups.maxReported"
1364 var gKeywordURIFixup = {
1365   check(browser, { fixedURI, keywordProviderName, preferredURI }) {
1366     // We get called irrespective of whether we did a keyword search, or
1367     // whether the original input would be vaguely interpretable as a URL,
1368     // so figure that out first.
1369     if (
1370       !keywordProviderName ||
1371       !fixedURI ||
1372       !fixedURI.host ||
1373       UrlbarPrefs.get("browser.fixup.dns_first_for_single_words") ||
1374       UrlbarPrefs.get("dnsResolveSingleWordsAfterSearch") == 0
1375     ) {
1376       return;
1377     }
1379     let contentPrincipal = browser.contentPrincipal;
1381     // At this point we're still only just about to load this URI.
1382     // When the async DNS lookup comes back, we may be in any of these states:
1383     // 1) still on the previous URI, waiting for the preferredURI (keyword
1384     //    search) to respond;
1385     // 2) at the keyword search URI (preferredURI)
1386     // 3) at some other page because the user stopped navigation.
1387     // We keep track of the currentURI to detect case (1) in the DNS lookup
1388     // callback.
1389     let previousURI = browser.currentURI;
1391     // now swap for a weak ref so we don't hang on to browser needlessly
1392     // even if the DNS query takes forever
1393     let weakBrowser = Cu.getWeakReference(browser);
1394     browser = null;
1396     // Additionally, we need the host of the parsed url
1397     let hostName = fixedURI.displayHost;
1398     // and the ascii-only host for the pref:
1399     let asciiHost = fixedURI.asciiHost;
1401     let onLookupCompleteListener = {
1402       async onLookupComplete(request, record, status) {
1403         let browserRef = weakBrowser.get();
1404         if (!Components.isSuccessCode(status) || !browserRef) {
1405           return;
1406         }
1408         let currentURI = browserRef.currentURI;
1409         // If we're in case (3) (see above), don't show an info bar.
1410         if (
1411           !currentURI.equals(previousURI) &&
1412           !currentURI.equals(preferredURI)
1413         ) {
1414           return;
1415         }
1417         // show infobar offering to visit the host
1418         let notificationBox = gBrowser.getNotificationBox(browserRef);
1419         if (notificationBox.getNotificationWithValue("keyword-uri-fixup")) {
1420           return;
1421         }
1423         let displayHostName = "http://" + hostName + "/";
1424         let message = gNavigatorBundle.getFormattedString(
1425           "keywordURIFixup.message",
1426           [displayHostName]
1427         );
1428         let yesMessage = gNavigatorBundle.getFormattedString(
1429           "keywordURIFixup.goTo",
1430           [displayHostName]
1431         );
1433         let buttons = [
1434           {
1435             label: yesMessage,
1436             accessKey: gNavigatorBundle.getString(
1437               "keywordURIFixup.goTo.accesskey"
1438             ),
1439             callback() {
1440               // Do not set this preference while in private browsing.
1441               if (!PrivateBrowsingUtils.isWindowPrivate(window)) {
1442                 let prefHost = asciiHost;
1443                 // Normalize out a single trailing dot - NB: not using endsWith/lastIndexOf
1444                 // because we need to be sure this last dot is the *only* dot, too.
1445                 // More generally, this is used for the pref and should stay in sync with
1446                 // the code in URIFixup::KeywordURIFixup .
1447                 if (prefHost.indexOf(".") == prefHost.length - 1) {
1448                   prefHost = prefHost.slice(0, -1);
1449                 }
1450                 let pref = "browser.fixup.domainwhitelist." + prefHost;
1451                 Services.prefs.setBoolPref(pref, true);
1452               }
1453               openTrustedLinkIn(fixedURI.spec, "current");
1454             },
1455           },
1456         ];
1457         let notification = await notificationBox.appendNotification(
1458           "keyword-uri-fixup",
1459           {
1460             label: message,
1461             priority: notificationBox.PRIORITY_INFO_HIGH,
1462           },
1463           buttons
1464         );
1465         notification.persistence = 1;
1466       },
1467     };
1469     Services.uriFixup.checkHost(
1470       fixedURI,
1471       onLookupCompleteListener,
1472       contentPrincipal.originAttributes
1473     );
1474   },
1476   observe(fixupInfo) {
1477     fixupInfo.QueryInterface(Ci.nsIURIFixupInfo);
1479     let browser = fixupInfo.consumer?.top?.embedderElement;
1480     if (!browser || browser.ownerGlobal != window) {
1481       return;
1482     }
1484     this.check(browser, fixupInfo);
1485   },
1488 /* Creates a null principal using the userContextId
1489    from the current selected tab or a passed in tab argument */
1490 function _createNullPrincipalFromTabUserContextId(tab = gBrowser.selectedTab) {
1491   let userContextId;
1492   if (tab.hasAttribute("usercontextid")) {
1493     userContextId = tab.getAttribute("usercontextid");
1494   }
1495   return Services.scriptSecurityManager.createNullPrincipal({
1496     userContextId,
1497   });
1500 let _resolveDelayedStartup;
1501 var delayedStartupPromise = new Promise(resolve => {
1502   _resolveDelayedStartup = resolve;
1505 var gBrowserInit = {
1506   delayedStartupFinished: false,
1507   idleTasksFinishedPromise: null,
1508   idleTaskPromiseResolve: null,
1509   domContentLoaded: false,
1511   _tabToAdopt: undefined,
1513   _setupFirstContentWindowPaintPromise() {
1514     let lastTransactionId = window.windowUtils.lastTransactionId;
1515     let layerTreeListener = () => {
1516       if (this.getTabToAdopt()) {
1517         // Need to wait until we finish adopting the tab, or we might end
1518         // up focusing the initial browser and then losing focus when it
1519         // gets swapped out for the tab to adopt.
1520         return;
1521       }
1522       removeEventListener("MozLayerTreeReady", layerTreeListener);
1523       let listener = e => {
1524         if (e.transactionId > lastTransactionId) {
1525           window.removeEventListener("MozAfterPaint", listener);
1526           this._firstContentWindowPaintDeferred.resolve();
1527         }
1528       };
1529       addEventListener("MozAfterPaint", listener);
1530     };
1531     addEventListener("MozLayerTreeReady", layerTreeListener);
1532   },
1534   getTabToAdopt() {
1535     if (this._tabToAdopt !== undefined) {
1536       return this._tabToAdopt;
1537     }
1539     if (window.arguments && window.XULElement.isInstance(window.arguments[0])) {
1540       this._tabToAdopt = window.arguments[0];
1542       // Clear the reference of the tab being adopted from the arguments.
1543       window.arguments[0] = null;
1544     } else {
1545       // There was no tab to adopt in the arguments, set _tabToAdopt to null
1546       // to avoid checking it again.
1547       this._tabToAdopt = null;
1548     }
1550     return this._tabToAdopt;
1551   },
1553   _clearTabToAdopt() {
1554     this._tabToAdopt = null;
1555   },
1557   // Used to check if the new window is still adopting an existing tab as its first tab
1558   // (e.g. from the WebExtensions internals).
1559   isAdoptingTab() {
1560     return !!this.getTabToAdopt();
1561   },
1563   onBeforeInitialXULLayout() {
1564     this._setupFirstContentWindowPaintPromise();
1566     updateBookmarkToolbarVisibility();
1568     // Set a sane starting width/height for all resolutions on new profiles.
1569     if (ChromeUtils.shouldResistFingerprinting("RoundWindowSize", null)) {
1570       // When the fingerprinting resistance is enabled, making sure that we don't
1571       // have a maximum window to interfere with generating rounded window dimensions.
1572       document.documentElement.setAttribute("sizemode", "normal");
1573     } else if (!document.documentElement.hasAttribute("width")) {
1574       const TARGET_WIDTH = 1280;
1575       const TARGET_HEIGHT = 1040;
1576       let width = Math.min(screen.availWidth * 0.9, TARGET_WIDTH);
1577       let height = Math.min(screen.availHeight * 0.9, TARGET_HEIGHT);
1579       document.documentElement.setAttribute("width", width);
1580       document.documentElement.setAttribute("height", height);
1582       if (width < TARGET_WIDTH && height < TARGET_HEIGHT) {
1583         document.documentElement.setAttribute("sizemode", "maximized");
1584       }
1585     }
1586     if (AppConstants.MENUBAR_CAN_AUTOHIDE) {
1587       const toolbarMenubar = document.getElementById("toolbar-menubar");
1588       // set a default value
1589       if (!toolbarMenubar.hasAttribute("autohide")) {
1590         toolbarMenubar.setAttribute("autohide", true);
1591       }
1592       document.l10n.setAttributes(
1593         toolbarMenubar,
1594         "toolbar-context-menu-menu-bar-cmd"
1595       );
1596       toolbarMenubar.setAttribute("data-l10n-attrs", "toolbarname");
1597     }
1599     // Run menubar initialization first, to avoid TabsInTitlebar code picking
1600     // up mutations from it and causing a reflow.
1601     AutoHideMenubar.init();
1602     // Update the chromemargin attribute so the window can be sized correctly.
1603     window.TabBarVisibility.update();
1604     TabsInTitlebar.init();
1606     new LightweightThemeConsumer(document);
1608     if (
1609       Services.prefs.getBoolPref(
1610         "toolkit.legacyUserProfileCustomizations.windowIcon",
1611         false
1612       )
1613     ) {
1614       document.documentElement.setAttribute("icon", "main-window");
1615     }
1617     // Call this after we set attributes that might change toolbars' computed
1618     // text color.
1619     ToolbarIconColor.init();
1620   },
1622   onDOMContentLoaded() {
1623     // This needs setting up before we create the first remote browser.
1624     window.docShell.treeOwner
1625       .QueryInterface(Ci.nsIInterfaceRequestor)
1626       .getInterface(Ci.nsIAppWindow).XULBrowserWindow = window.XULBrowserWindow;
1627     window.browserDOMWindow = new nsBrowserAccess();
1629     gBrowser = window._gBrowser;
1630     delete window._gBrowser;
1631     gBrowser.init();
1633     BrowserWindowTracker.track(window);
1635     FirefoxViewHandler.init();
1637     gNavToolbox.palette = document.getElementById(
1638       "BrowserToolbarPalette"
1639     ).content;
1640     for (let area of CustomizableUI.areas) {
1641       let type = CustomizableUI.getAreaType(area);
1642       if (type == CustomizableUI.TYPE_TOOLBAR) {
1643         let node = document.getElementById(area);
1644         CustomizableUI.registerToolbarNode(node);
1645       }
1646     }
1647     BrowserSearch.initPlaceHolder();
1649     // Hack to ensure that the various initial pages favicon is loaded
1650     // instantaneously, to avoid flickering and improve perceived performance.
1651     this._callWithURIToLoad(uriToLoad => {
1652       let url;
1653       try {
1654         url = Services.io.newURI(uriToLoad);
1655       } catch (e) {
1656         return;
1657       }
1658       let nonQuery = url.prePath + url.filePath;
1659       if (nonQuery in gPageIcons) {
1660         gBrowser.setIcon(gBrowser.selectedTab, gPageIcons[nonQuery]);
1661       }
1662     });
1664     updateFxaToolbarMenu(gFxaToolbarEnabled, true);
1666     updatePrintCommands(gPrintEnabled);
1668     gUnifiedExtensions.init();
1670     // Setting the focus will cause a style flush, it's preferable to call anything
1671     // that will modify the DOM from within this function before this call.
1672     this._setInitialFocus();
1674     this.domContentLoaded = true;
1675   },
1677   onLoad() {
1678     gBrowser.addEventListener("DOMUpdateBlockedPopups", gPopupBlockerObserver);
1679     gBrowser.addEventListener(
1680       "TranslationsParent:LanguageState",
1681       FullPageTranslationsPanel
1682     );
1683     gBrowser.addEventListener(
1684       "TranslationsParent:OfferTranslation",
1685       FullPageTranslationsPanel
1686     );
1687     gBrowser.addTabsProgressListener(FullPageTranslationsPanel);
1689     window.addEventListener("AppCommand", HandleAppCommandEvent, true);
1691     // These routines add message listeners. They must run before
1692     // loading the frame script to ensure that we don't miss any
1693     // message sent between when the frame script is loaded and when
1694     // the listener is registered.
1695     CaptivePortalWatcher.init();
1696     ZoomUI.init(window);
1698     if (!gMultiProcessBrowser) {
1699       // There is a Content:Click message manually sent from content.
1700       gBrowser.tabpanels.addEventListener("click", contentAreaClick, {
1701         capture: true,
1702         mozSystemGroup: true,
1703       });
1704     }
1706     // hook up UI through progress listener
1707     gBrowser.addProgressListener(window.XULBrowserWindow);
1708     gBrowser.addTabsProgressListener(window.TabsProgressListener);
1710     SidebarUI.init();
1712     // We do this in onload because we want to ensure the button's state
1713     // doesn't flicker as the window is being shown.
1714     DownloadsButton.init();
1716     // Certain kinds of automigration rely on this notification to complete
1717     // their tasks BEFORE the browser window is shown. SessionStore uses it to
1718     // restore tabs into windows AFTER important parts like gMultiProcessBrowser
1719     // have been initialized.
1720     Services.obs.notifyObservers(window, "browser-window-before-show");
1722     if (!window.toolbar.visible) {
1723       // adjust browser UI for popups
1724       gURLBar.readOnly = true;
1725     }
1727     // Misc. inits.
1728     gUIDensity.init();
1729     TabletModeUpdater.init();
1730     CombinedStopReload.ensureInitialized();
1731     gPrivateBrowsingUI.init();
1732     BrowserSearch.init();
1733     BrowserPageActions.init();
1734     if (gToolbarKeyNavEnabled) {
1735       ToolbarKeyboardNavigator.init();
1736     }
1738     // Update UI if browser is under remote control.
1739     gRemoteControl.updateVisualCue();
1741     // If we are given a tab to swap in, take care of it before first paint to
1742     // avoid an about:blank flash.
1743     let tabToAdopt = this.getTabToAdopt();
1744     if (tabToAdopt) {
1745       let evt = new CustomEvent("before-initial-tab-adopted", {
1746         bubbles: true,
1747       });
1748       gBrowser.tabpanels.dispatchEvent(evt);
1750       // Stop the about:blank load
1751       gBrowser.stop();
1753       // Remove the speculative focus from the urlbar to let the url be formatted.
1754       gURLBar.removeAttribute("focused");
1756       let swapBrowsers = () => {
1757         try {
1758           gBrowser.swapBrowsersAndCloseOther(gBrowser.selectedTab, tabToAdopt);
1759         } catch (e) {
1760           console.error(e);
1761         }
1763         // Clear the reference to the tab once its adoption has been completed.
1764         this._clearTabToAdopt();
1765       };
1766       if (tabToAdopt.linkedBrowser.isRemoteBrowser) {
1767         // For remote browsers, wait for the paint event, otherwise the tabs
1768         // are not yet ready and focus gets confused because the browser swaps
1769         // out while tabs are switching.
1770         addEventListener("MozAfterPaint", swapBrowsers, { once: true });
1771       } else {
1772         swapBrowsers();
1773       }
1774     }
1776     // Wait until chrome is painted before executing code not critical to making the window visible
1777     this._boundDelayedStartup = this._delayedStartup.bind(this);
1778     window.addEventListener("MozAfterPaint", this._boundDelayedStartup);
1780     if (!PrivateBrowsingUtils.enabled) {
1781       document.getElementById("Tools:PrivateBrowsing").hidden = true;
1782       // Setting disabled doesn't disable the shortcut, so we just remove
1783       // the keybinding.
1784       document.getElementById("key_privatebrowsing").remove();
1785     }
1787     if (BrowserUIUtils.quitShortcutDisabled) {
1788       document.getElementById("key_quitApplication").remove();
1789       document.getElementById("menu_FileQuitItem").removeAttribute("key");
1791       PanelMultiView.getViewNode(
1792         document,
1793         "appMenu-quit-button2"
1794       )?.removeAttribute("key");
1795     }
1797     this._loadHandled = true;
1798   },
1800   _cancelDelayedStartup() {
1801     window.removeEventListener("MozAfterPaint", this._boundDelayedStartup);
1802     this._boundDelayedStartup = null;
1803   },
1805   _delayedStartup() {
1806     let { TelemetryTimestamps } = ChromeUtils.importESModule(
1807       "resource://gre/modules/TelemetryTimestamps.sys.mjs"
1808     );
1809     TelemetryTimestamps.add("delayedStartupStarted");
1811     this._cancelDelayedStartup();
1813     // Bug 1531854 - The hidden window is force-created here
1814     // until all of its dependencies are handled.
1815     Services.appShell.hiddenDOMWindow;
1817     gBrowser.addEventListener(
1818       "PermissionStateChange",
1819       function () {
1820         gIdentityHandler.refreshIdentityBlock();
1821         gPermissionPanel.updateSharingIndicator();
1822       },
1823       true
1824     );
1826     this._handleURIToLoad();
1828     Services.obs.addObserver(gIdentityHandler, "perm-changed");
1829     Services.obs.addObserver(gRemoteControl, "devtools-socket");
1830     Services.obs.addObserver(gRemoteControl, "marionette-listening");
1831     Services.obs.addObserver(gRemoteControl, "remote-listening");
1832     Services.obs.addObserver(
1833       gSessionHistoryObserver,
1834       "browser:purge-session-history"
1835     );
1836     Services.obs.addObserver(
1837       gStoragePressureObserver,
1838       "QuotaManager::StoragePressure"
1839     );
1840     Services.obs.addObserver(gXPInstallObserver, "addon-install-disabled");
1841     Services.obs.addObserver(gXPInstallObserver, "addon-install-started");
1842     Services.obs.addObserver(gXPInstallObserver, "addon-install-blocked");
1843     Services.obs.addObserver(
1844       gXPInstallObserver,
1845       "addon-install-fullscreen-blocked"
1846     );
1847     Services.obs.addObserver(
1848       gXPInstallObserver,
1849       "addon-install-origin-blocked"
1850     );
1851     Services.obs.addObserver(
1852       gXPInstallObserver,
1853       "addon-install-policy-blocked"
1854     );
1855     Services.obs.addObserver(
1856       gXPInstallObserver,
1857       "addon-install-webapi-blocked"
1858     );
1859     Services.obs.addObserver(gXPInstallObserver, "addon-install-failed");
1860     Services.obs.addObserver(gXPInstallObserver, "addon-install-confirmation");
1861     Services.obs.addObserver(gKeywordURIFixup, "keyword-uri-fixup");
1863     BrowserOffline.init();
1864     CanvasPermissionPromptHelper.init();
1865     WebAuthnPromptHelper.init();
1866     ContentAnalysis.initialize();
1868     // Initialize the full zoom setting.
1869     // We do this before the session restore service gets initialized so we can
1870     // apply full zoom settings to tabs restored by the session restore service.
1871     FullZoom.init();
1872     PanelUI.init(shouldSuppressPopupNotifications);
1873     ReportBrokenSite.init(gBrowser);
1875     UpdateUrlbarSearchSplitterState();
1877     BookmarkingUI.init();
1878     BrowserSearch.delayedStartupInit();
1879     SearchUIUtils.init();
1880     gProtectionsHandler.init();
1881     HomePage.delayedStartup().catch(console.error);
1883     let safeMode = document.getElementById("helpSafeMode");
1884     if (Services.appinfo.inSafeMode) {
1885       document.l10n.setAttributes(safeMode, "menu-help-exit-troubleshoot-mode");
1886       safeMode.setAttribute(
1887         "appmenu-data-l10n-id",
1888         "appmenu-help-exit-troubleshoot-mode"
1889       );
1890     }
1892     // BiDi UI
1893     gBidiUI = isBidiEnabled();
1894     if (gBidiUI) {
1895       document.getElementById("documentDirection-separator").hidden = false;
1896       document.getElementById("documentDirection-swap").hidden = false;
1897       document.getElementById("textfieldDirection-separator").hidden = false;
1898       document.getElementById("textfieldDirection-swap").hidden = false;
1899     }
1901     // Setup click-and-hold gestures access to the session history
1902     // menus if global click-and-hold isn't turned on
1903     if (!Services.prefs.getBoolPref("ui.click_hold_context_menus", false)) {
1904       SetClickAndHoldHandlers();
1905     }
1907     function initBackForwardButtonTooltip(tooltipId, l10nId, shortcutId) {
1908       let shortcut = document.getElementById(shortcutId);
1909       shortcut = ShortcutUtils.prettifyShortcut(shortcut);
1911       let tooltip = document.getElementById(tooltipId);
1912       document.l10n.setAttributes(tooltip, l10nId, { shortcut });
1913     }
1915     initBackForwardButtonTooltip(
1916       "back-button-tooltip-description",
1917       "navbar-tooltip-back-2",
1918       "goBackKb"
1919     );
1921     initBackForwardButtonTooltip(
1922       "forward-button-tooltip-description",
1923       "navbar-tooltip-forward-2",
1924       "goForwardKb"
1925     );
1927     PlacesToolbarHelper.init();
1929     ctrlTab.readPref();
1930     Services.prefs.addObserver(ctrlTab.prefName, ctrlTab);
1932     // The object handling the downloads indicator is initialized here in the
1933     // delayed startup function, but the actual indicator element is not loaded
1934     // unless there are downloads to be displayed.
1935     DownloadsButton.initializeIndicator();
1937     if (AppConstants.platform != "macosx") {
1938       updateEditUIVisibility();
1939       let placesContext = document.getElementById("placesContext");
1940       placesContext.addEventListener("popupshowing", updateEditUIVisibility);
1941       placesContext.addEventListener("popuphiding", updateEditUIVisibility);
1942     }
1944     FullScreen.init();
1945     MenuTouchModeObserver.init();
1947     if (AppConstants.MOZ_DATA_REPORTING) {
1948       gDataNotificationInfoBar.init();
1949     }
1951     if (!AppConstants.MOZILLA_OFFICIAL) {
1952       DevelopmentHelpers.init();
1953     }
1955     gExtensionsNotifications.init();
1957     let wasMinimized = window.windowState == window.STATE_MINIMIZED;
1958     window.addEventListener("sizemodechange", () => {
1959       let isMinimized = window.windowState == window.STATE_MINIMIZED;
1960       if (wasMinimized != isMinimized) {
1961         wasMinimized = isMinimized;
1962         UpdatePopupNotificationsVisibility();
1963       }
1964     });
1966     window.addEventListener("mousemove", MousePosTracker);
1967     window.addEventListener("dragover", MousePosTracker);
1969     gNavToolbox.addEventListener("customizationstarting", CustomizationHandler);
1970     gNavToolbox.addEventListener("aftercustomization", CustomizationHandler);
1972     SessionStore.promiseInitialized.then(() => {
1973       // Bail out if the window has been closed in the meantime.
1974       if (window.closed) {
1975         return;
1976       }
1978       // Enable the Restore Last Session command if needed
1979       RestoreLastSessionObserver.init();
1981       SidebarUI.startDelayedLoad();
1983       PanicButtonNotifier.init();
1984     });
1986     if (BrowserHandler.kiosk) {
1987       // We don't modify popup windows for kiosk mode
1988       if (!gURLBar.readOnly) {
1989         window.fullScreen = true;
1990       }
1991     }
1993     if (Services.policies.status === Services.policies.ACTIVE) {
1994       if (!Services.policies.isAllowed("hideShowMenuBar")) {
1995         document
1996           .getElementById("toolbar-menubar")
1997           .removeAttribute("toolbarname");
1998       }
1999       if (!Services.policies.isAllowed("filepickers")) {
2000         let savePageCommand = document.getElementById("Browser:SavePage");
2001         let openFileCommand = document.getElementById("Browser:OpenFile");
2003         savePageCommand.setAttribute("disabled", "true");
2004         openFileCommand.setAttribute("disabled", "true");
2006         document.addEventListener("FilePickerBlocked", function (event) {
2007           let browser = event.target;
2009           let notificationBox = browser
2010             .getTabBrowser()
2011             ?.getNotificationBox(browser);
2013           // Prevent duplicate notifications
2014           if (
2015             notificationBox &&
2016             !notificationBox.getNotificationWithValue("filepicker-blocked")
2017           ) {
2018             notificationBox.appendNotification("filepicker-blocked", {
2019               label: {
2020                 "l10n-id": "filepicker-blocked-infobar",
2021               },
2022               priority: notificationBox.PRIORITY_INFO_LOW,
2023             });
2024           }
2025         });
2026       }
2027       let policies = Services.policies.getActivePolicies();
2028       if ("ManagedBookmarks" in policies) {
2029         let managedBookmarks = policies.ManagedBookmarks;
2030         let children = managedBookmarks.filter(
2031           child => !("toplevel_name" in child)
2032         );
2033         if (children.length) {
2034           let managedBookmarksButton =
2035             document.createXULElement("toolbarbutton");
2036           managedBookmarksButton.setAttribute("id", "managed-bookmarks");
2037           managedBookmarksButton.setAttribute("class", "bookmark-item");
2038           let toplevel = managedBookmarks.find(
2039             element => "toplevel_name" in element
2040           );
2041           if (toplevel) {
2042             managedBookmarksButton.setAttribute(
2043               "label",
2044               toplevel.toplevel_name
2045             );
2046           } else {
2047             document.l10n.setAttributes(
2048               managedBookmarksButton,
2049               "managed-bookmarks"
2050             );
2051           }
2052           managedBookmarksButton.setAttribute("context", "placesContext");
2053           managedBookmarksButton.setAttribute("container", "true");
2054           managedBookmarksButton.setAttribute("removable", "false");
2055           managedBookmarksButton.setAttribute("type", "menu");
2057           let managedBookmarksPopup = document.createXULElement("menupopup");
2058           managedBookmarksPopup.setAttribute("id", "managed-bookmarks-popup");
2059           managedBookmarksPopup.setAttribute(
2060             "oncommand",
2061             "PlacesToolbarHelper.openManagedBookmark(event);"
2062           );
2063           managedBookmarksPopup.setAttribute(
2064             "ondragover",
2065             "event.dataTransfer.effectAllowed='none';"
2066           );
2067           managedBookmarksPopup.setAttribute(
2068             "ondragstart",
2069             "PlacesToolbarHelper.onDragStartManaged(event);"
2070           );
2071           managedBookmarksPopup.setAttribute(
2072             "onpopupshowing",
2073             "PlacesToolbarHelper.populateManagedBookmarks(this);"
2074           );
2075           managedBookmarksPopup.setAttribute("placespopup", "true");
2076           managedBookmarksPopup.setAttribute("is", "places-popup");
2077           managedBookmarksPopup.classList.add("toolbar-menupopup");
2078           managedBookmarksButton.appendChild(managedBookmarksPopup);
2080           gNavToolbox.palette.appendChild(managedBookmarksButton);
2082           CustomizableUI.ensureWidgetPlacedInWindow(
2083             "managed-bookmarks",
2084             window
2085           );
2087           // Add button if it doesn't exist
2088           if (!CustomizableUI.getPlacementOfWidget("managed-bookmarks")) {
2089             CustomizableUI.addWidgetToArea(
2090               "managed-bookmarks",
2091               CustomizableUI.AREA_BOOKMARKS,
2092               0
2093             );
2094           }
2095         }
2096       }
2097     }
2099     CaptivePortalWatcher.delayedStartup();
2101     ShoppingSidebarManager.ensureInitialized();
2103     SessionStore.promiseAllWindowsRestored.then(() => {
2104       this._schedulePerWindowIdleTasks();
2105       document.documentElement.setAttribute("sessionrestored", "true");
2106     });
2108     this.delayedStartupFinished = true;
2109     _resolveDelayedStartup();
2110     Services.obs.notifyObservers(window, "browser-delayed-startup-finished");
2111     TelemetryTimestamps.add("delayedStartupFinished");
2112     // We've announced that delayed startup has finished. Do not add code past this point.
2113   },
2115   /**
2116    * Resolved on the first MozLayerTreeReady and next MozAfterPaint in the
2117    * parent process.
2118    */
2119   get firstContentWindowPaintPromise() {
2120     return this._firstContentWindowPaintDeferred.promise;
2121   },
2123   _setInitialFocus() {
2124     let initiallyFocusedElement = document.commandDispatcher.focusedElement;
2126     // To prevent startup flicker, the urlbar has the 'focused' attribute set
2127     // by default. If we are not sure the urlbar will be focused in this
2128     // window, we need to remove the attribute before first paint.
2129     // TODO (bug 1629956): The urlbar having the 'focused' attribute by default
2130     // isn't a useful optimization anymore since UrlbarInput needs layout
2131     // information to focus the urlbar properly.
2132     let shouldRemoveFocusedAttribute = true;
2134     this._callWithURIToLoad(uriToLoad => {
2135       if (
2136         isBlankPageURL(uriToLoad) ||
2137         uriToLoad == "about:privatebrowsing" ||
2138         this.getTabToAdopt()?.isEmpty
2139       ) {
2140         gURLBar.select();
2141         shouldRemoveFocusedAttribute = false;
2142         return;
2143       }
2145       // If the initial browser is remote, in order to optimize for first paint,
2146       // we'll defer switching focus to that browser until it has painted.
2147       // Otherwise use a regular promise to guarantee that mutationobserver
2148       // microtasks that could affect focusability have run.
2149       let promise = gBrowser.selectedBrowser.isRemoteBrowser
2150         ? this.firstContentWindowPaintPromise
2151         : Promise.resolve();
2153       promise.then(() => {
2154         // If focus didn't move while we were waiting, we're okay to move to
2155         // the browser.
2156         if (
2157           document.commandDispatcher.focusedElement == initiallyFocusedElement
2158         ) {
2159           gBrowser.selectedBrowser.focus();
2160         }
2161       });
2162     });
2164     // Delay removing the attribute using requestAnimationFrame to avoid
2165     // invalidating styles multiple times in a row if uriToLoadPromise
2166     // resolves before first paint.
2167     if (shouldRemoveFocusedAttribute) {
2168       window.requestAnimationFrame(() => {
2169         if (shouldRemoveFocusedAttribute) {
2170           gURLBar.removeAttribute("focused");
2171         }
2172       });
2173     }
2174   },
2176   _handleURIToLoad() {
2177     this._callWithURIToLoad(uriToLoad => {
2178       if (!uriToLoad) {
2179         // We don't check whether window.arguments[5] (userContextId) is set
2180         // because tabbrowser.js takes care of that for the initial tab.
2181         return;
2182       }
2184       // We don't check if uriToLoad is a XULElement because this case has
2185       // already been handled before first paint, and the argument cleared.
2186       if (Array.isArray(uriToLoad)) {
2187         // This function throws for certain malformed URIs, so use exception handling
2188         // so that we don't disrupt startup
2189         try {
2190           gBrowser.loadTabs(uriToLoad, {
2191             inBackground: false,
2192             replace: true,
2193             // See below for the semantics of window.arguments. Only the minimum is supported.
2194             userContextId: window.arguments[5],
2195             triggeringPrincipal:
2196               window.arguments[8] ||
2197               Services.scriptSecurityManager.getSystemPrincipal(),
2198             allowInheritPrincipal: window.arguments[9],
2199             csp: window.arguments[10],
2200             fromExternal: true,
2201           });
2202         } catch (e) {}
2203       } else if (window.arguments.length >= 3) {
2204         // window.arguments[1]: extraOptions (nsIPropertyBag)
2205         //                 [2]: referrerInfo (nsIReferrerInfo)
2206         //                 [3]: postData (nsIInputStream)
2207         //                 [4]: allowThirdPartyFixup (bool)
2208         //                 [5]: userContextId (int)
2209         //                 [6]: originPrincipal (nsIPrincipal)
2210         //                 [7]: originStoragePrincipal (nsIPrincipal)
2211         //                 [8]: triggeringPrincipal (nsIPrincipal)
2212         //                 [9]: allowInheritPrincipal (bool)
2213         //                 [10]: csp (nsIContentSecurityPolicy)
2214         //                 [11]: nsOpenWindowInfo
2215         let userContextId =
2216           window.arguments[5] != undefined
2217             ? window.arguments[5]
2218             : Ci.nsIScriptSecurityManager.DEFAULT_USER_CONTEXT_ID;
2220         let hasValidUserGestureActivation = undefined;
2221         let fromExternal = undefined;
2222         let globalHistoryOptions = undefined;
2223         let triggeringRemoteType = undefined;
2224         let forceAllowDataURI = false;
2225         let wasSchemelessInput = false;
2226         if (window.arguments[1]) {
2227           if (!(window.arguments[1] instanceof Ci.nsIPropertyBag2)) {
2228             throw new Error(
2229               "window.arguments[1] must be null or Ci.nsIPropertyBag2!"
2230             );
2231           }
2233           let extraOptions = window.arguments[1];
2234           if (extraOptions.hasKey("hasValidUserGestureActivation")) {
2235             hasValidUserGestureActivation = extraOptions.getPropertyAsBool(
2236               "hasValidUserGestureActivation"
2237             );
2238           }
2239           if (extraOptions.hasKey("fromExternal")) {
2240             fromExternal = extraOptions.getPropertyAsBool("fromExternal");
2241           }
2242           if (extraOptions.hasKey("triggeringSponsoredURL")) {
2243             globalHistoryOptions = {
2244               triggeringSponsoredURL: extraOptions.getPropertyAsACString(
2245                 "triggeringSponsoredURL"
2246               ),
2247             };
2248             if (extraOptions.hasKey("triggeringSponsoredURLVisitTimeMS")) {
2249               globalHistoryOptions.triggeringSponsoredURLVisitTimeMS =
2250                 extraOptions.getPropertyAsUint64(
2251                   "triggeringSponsoredURLVisitTimeMS"
2252                 );
2253             }
2254           }
2255           if (extraOptions.hasKey("triggeringRemoteType")) {
2256             triggeringRemoteType = extraOptions.getPropertyAsACString(
2257               "triggeringRemoteType"
2258             );
2259           }
2260           if (extraOptions.hasKey("forceAllowDataURI")) {
2261             forceAllowDataURI =
2262               extraOptions.getPropertyAsBool("forceAllowDataURI");
2263           }
2264           if (extraOptions.hasKey("wasSchemelessInput")) {
2265             wasSchemelessInput =
2266               extraOptions.getPropertyAsBool("wasSchemelessInput");
2267           }
2268         }
2270         try {
2271           openLinkIn(uriToLoad, "current", {
2272             referrerInfo: window.arguments[2] || null,
2273             postData: window.arguments[3] || null,
2274             allowThirdPartyFixup: window.arguments[4] || false,
2275             userContextId,
2276             // pass the origin principal (if any) and force its use to create
2277             // an initial about:blank viewer if present:
2278             originPrincipal: window.arguments[6],
2279             originStoragePrincipal: window.arguments[7],
2280             triggeringPrincipal: window.arguments[8],
2281             // TODO fix allowInheritPrincipal to default to false.
2282             // Default to true unless explicitly set to false because of bug 1475201.
2283             allowInheritPrincipal: window.arguments[9] !== false,
2284             csp: window.arguments[10],
2285             forceAboutBlankViewerInCurrent: !!window.arguments[6],
2286             forceAllowDataURI,
2287             hasValidUserGestureActivation,
2288             fromExternal,
2289             globalHistoryOptions,
2290             triggeringRemoteType,
2291             wasSchemelessInput,
2292           });
2293         } catch (e) {
2294           console.error(e);
2295         }
2297         window.focus();
2298       } else {
2299         // Note: loadOneOrMoreURIs *must not* be called if window.arguments.length >= 3.
2300         // Such callers expect that window.arguments[0] is handled as a single URI.
2301         loadOneOrMoreURIs(
2302           uriToLoad,
2303           Services.scriptSecurityManager.getSystemPrincipal(),
2304           null
2305         );
2306       }
2307     });
2308   },
2310   /**
2311    * Use this function as an entry point to schedule tasks that
2312    * need to run once per window after startup, and can be scheduled
2313    * by using an idle callback.
2314    *
2315    * The functions scheduled here will fire from idle callbacks
2316    * once every window has finished being restored by session
2317    * restore, and after the equivalent only-once tasks
2318    * have run (from _scheduleStartupIdleTasks in BrowserGlue.sys.mjs).
2319    */
2320   _schedulePerWindowIdleTasks() {
2321     // Bail out if the window has been closed in the meantime.
2322     if (window.closed) {
2323       return;
2324     }
2326     function scheduleIdleTask(func, options) {
2327       requestIdleCallback(function idleTaskRunner() {
2328         if (!window.closed) {
2329           func();
2330         }
2331       }, options);
2332     }
2334     scheduleIdleTask(() => {
2335       // Initialize the Sync UI
2336       gSync.init();
2337     });
2339     scheduleIdleTask(() => {
2340       // Read prefers-reduced-motion setting
2341       let reduceMotionQuery = window.matchMedia(
2342         "(prefers-reduced-motion: reduce)"
2343       );
2344       function readSetting() {
2345         gReduceMotionSetting = reduceMotionQuery.matches;
2346       }
2347       reduceMotionQuery.addListener(readSetting);
2348       readSetting();
2349     });
2351     scheduleIdleTask(() => {
2352       // load the tab preview component
2353       import("chrome://browser/content/tabpreview/tabpreview.mjs").catch(
2354         console.error
2355       );
2356     });
2358     scheduleIdleTask(() => {
2359       // setup simple gestures support
2360       gGestureSupport.init(true);
2362       // setup history swipe animation
2363       gHistorySwipeAnimation.init();
2364     });
2366     scheduleIdleTask(() => {
2367       gBrowserThumbnails.init();
2368     });
2370     scheduleIdleTask(
2371       () => {
2372         // Initialize the download manager some time after the app starts so that
2373         // auto-resume downloads begin (such as after crashing or quitting with
2374         // active downloads) and speeds up the first-load of the download manager UI.
2375         // If the user manually opens the download manager before the timeout, the
2376         // downloads will start right away, and initializing again won't hurt.
2377         try {
2378           DownloadsCommon.initializeAllDataLinks();
2379           ChromeUtils.importESModule(
2380             "resource:///modules/DownloadsTaskbar.sys.mjs"
2381           ).DownloadsTaskbar.registerIndicator(window);
2382           if (AppConstants.platform == "macosx") {
2383             ChromeUtils.importESModule(
2384               "resource:///modules/DownloadsMacFinderProgress.sys.mjs"
2385             ).DownloadsMacFinderProgress.register();
2386           }
2387           Services.telemetry.setEventRecordingEnabled("downloads", true);
2388         } catch (ex) {
2389           console.error(ex);
2390         }
2391       },
2392       { timeout: 10000 }
2393     );
2395     if (Win7Features) {
2396       scheduleIdleTask(() => Win7Features.onOpenWindow());
2397     }
2399     scheduleIdleTask(async () => {
2400       NewTabPagePreloading.maybeCreatePreloadedBrowser(window);
2401     });
2403     scheduleIdleTask(() => {
2404       gGfxUtils.init();
2405     });
2407     // This should always go last, since the idle tasks (except for the ones with
2408     // timeouts) should execute in order. Note that this observer notification is
2409     // not guaranteed to fire, since the window could close before we get here.
2410     scheduleIdleTask(() => {
2411       this.idleTaskPromiseResolve();
2412       Services.obs.notifyObservers(
2413         window,
2414         "browser-idle-startup-tasks-finished"
2415       );
2416     });
2418     scheduleIdleTask(() => {
2419       gProfiles.init();
2420     });
2421   },
2423   // Returns the URI(s) to load at startup if it is immediately known, or a
2424   // promise resolving to the URI to load.
2425   get uriToLoadPromise() {
2426     delete this.uriToLoadPromise;
2427     return (this.uriToLoadPromise = (function () {
2428       // window.arguments[0]: URI to load (string), or an nsIArray of
2429       //                      nsISupportsStrings to load, or a xul:tab of
2430       //                      a tabbrowser, which will be replaced by this
2431       //                      window (for this case, all other arguments are
2432       //                      ignored).
2433       let uri = window.arguments?.[0];
2434       if (!uri || window.XULElement.isInstance(uri)) {
2435         return null;
2436       }
2438       let defaultArgs = BrowserHandler.defaultArgs;
2440       // If the given URI is different from the homepage, we want to load it.
2441       if (uri != defaultArgs) {
2442         AboutNewTab.noteNonDefaultStartup();
2444         if (uri instanceof Ci.nsIArray) {
2445           // Transform the nsIArray of nsISupportsString's into a JS Array of
2446           // JS strings.
2447           return Array.from(
2448             uri.enumerate(Ci.nsISupportsString),
2449             supportStr => supportStr.data
2450           );
2451         } else if (uri instanceof Ci.nsISupportsString) {
2452           return uri.data;
2453         }
2454         return uri;
2455       }
2457       // The URI appears to be the the homepage. We want to load it only if
2458       // session restore isn't about to override the homepage.
2459       let willOverride = SessionStartup.willOverrideHomepage;
2460       if (typeof willOverride == "boolean") {
2461         return willOverride ? null : uri;
2462       }
2463       return willOverride.then(willOverrideHomepage =>
2464         willOverrideHomepage ? null : uri
2465       );
2466     })());
2467   },
2469   // Calls the given callback with the URI to load at startup.
2470   // Synchronously if possible, or after uriToLoadPromise resolves otherwise.
2471   _callWithURIToLoad(callback) {
2472     let uriToLoad = this.uriToLoadPromise;
2473     if (uriToLoad && uriToLoad.then) {
2474       uriToLoad.then(callback);
2475     } else {
2476       callback(uriToLoad);
2477     }
2478   },
2480   onUnload() {
2481     gUIDensity.uninit();
2483     TabsInTitlebar.uninit();
2485     ToolbarIconColor.uninit();
2487     // In certain scenarios it's possible for unload to be fired before onload,
2488     // (e.g. if the window is being closed after browser.js loads but before the
2489     // load completes). In that case, there's nothing to do here.
2490     if (!this._loadHandled) {
2491       return;
2492     }
2494     // First clean up services initialized in gBrowserInit.onLoad (or those whose
2495     // uninit methods don't depend on the services having been initialized).
2497     CombinedStopReload.uninit();
2499     gGestureSupport.init(false);
2501     gHistorySwipeAnimation.uninit();
2503     FullScreen.uninit();
2505     gSync.uninit();
2507     gExtensionsNotifications.uninit();
2508     gUnifiedExtensions.uninit();
2510     try {
2511       gBrowser.removeProgressListener(window.XULBrowserWindow);
2512       gBrowser.removeTabsProgressListener(window.TabsProgressListener);
2513     } catch (ex) {}
2515     PlacesToolbarHelper.uninit();
2517     BookmarkingUI.uninit();
2519     TabletModeUpdater.uninit();
2521     gTabletModePageCounter.finish();
2523     CaptivePortalWatcher.uninit();
2525     SidebarUI.uninit();
2527     DownloadsButton.uninit();
2529     if (gToolbarKeyNavEnabled) {
2530       ToolbarKeyboardNavigator.uninit();
2531     }
2533     BrowserSearch.uninit();
2535     NewTabPagePreloading.removePreloadedBrowser(window);
2537     FirefoxViewHandler.uninit();
2539     // Now either cancel delayedStartup, or clean up the services initialized from
2540     // it.
2541     if (this._boundDelayedStartup) {
2542       this._cancelDelayedStartup();
2543     } else {
2544       if (Win7Features) {
2545         Win7Features.onCloseWindow();
2546       }
2547       Services.prefs.removeObserver(ctrlTab.prefName, ctrlTab);
2548       ctrlTab.uninit();
2549       gBrowserThumbnails.uninit();
2550       gProtectionsHandler.uninit();
2551       FullZoom.destroy();
2553       Services.obs.removeObserver(gIdentityHandler, "perm-changed");
2554       Services.obs.removeObserver(gRemoteControl, "devtools-socket");
2555       Services.obs.removeObserver(gRemoteControl, "marionette-listening");
2556       Services.obs.removeObserver(gRemoteControl, "remote-listening");
2557       Services.obs.removeObserver(
2558         gSessionHistoryObserver,
2559         "browser:purge-session-history"
2560       );
2561       Services.obs.removeObserver(
2562         gStoragePressureObserver,
2563         "QuotaManager::StoragePressure"
2564       );
2565       Services.obs.removeObserver(gXPInstallObserver, "addon-install-disabled");
2566       Services.obs.removeObserver(gXPInstallObserver, "addon-install-started");
2567       Services.obs.removeObserver(gXPInstallObserver, "addon-install-blocked");
2568       Services.obs.removeObserver(
2569         gXPInstallObserver,
2570         "addon-install-fullscreen-blocked"
2571       );
2572       Services.obs.removeObserver(
2573         gXPInstallObserver,
2574         "addon-install-origin-blocked"
2575       );
2576       Services.obs.removeObserver(
2577         gXPInstallObserver,
2578         "addon-install-policy-blocked"
2579       );
2580       Services.obs.removeObserver(
2581         gXPInstallObserver,
2582         "addon-install-webapi-blocked"
2583       );
2584       Services.obs.removeObserver(gXPInstallObserver, "addon-install-failed");
2585       Services.obs.removeObserver(
2586         gXPInstallObserver,
2587         "addon-install-confirmation"
2588       );
2589       Services.obs.removeObserver(gKeywordURIFixup, "keyword-uri-fixup");
2591       MenuTouchModeObserver.uninit();
2592       BrowserOffline.uninit();
2593       CanvasPermissionPromptHelper.uninit();
2594       WebAuthnPromptHelper.uninit();
2595       PanelUI.uninit();
2596     }
2598     // Final window teardown, do this last.
2599     gBrowser.destroy();
2600     window.XULBrowserWindow = null;
2601     window.docShell.treeOwner
2602       .QueryInterface(Ci.nsIInterfaceRequestor)
2603       .getInterface(Ci.nsIAppWindow).XULBrowserWindow = null;
2604     window.browserDOMWindow = null;
2605   },
2608 ChromeUtils.defineLazyGetter(
2609   gBrowserInit,
2610   "_firstContentWindowPaintDeferred",
2611   () => Promise.withResolvers()
2614 gBrowserInit.idleTasksFinishedPromise = new Promise(resolve => {
2615   gBrowserInit.idleTaskPromiseResolve = resolve;
2618 function HandleAppCommandEvent(evt) {
2619   switch (evt.command) {
2620     case "Back":
2621       BrowserBack();
2622       break;
2623     case "Forward":
2624       BrowserForward();
2625       break;
2626     case "Reload":
2627       BrowserReloadSkipCache();
2628       break;
2629     case "Stop":
2630       if (XULBrowserWindow.stopCommand.getAttribute("disabled") != "true") {
2631         BrowserStop();
2632       }
2633       break;
2634     case "Search":
2635       BrowserSearch.webSearch();
2636       break;
2637     case "Bookmarks":
2638       SidebarUI.toggle("viewBookmarksSidebar");
2639       break;
2640     case "Home":
2641       BrowserHome();
2642       break;
2643     case "New":
2644       BrowserOpenTab();
2645       break;
2646     case "Close":
2647       BrowserCloseTabOrWindow();
2648       break;
2649     case "Find":
2650       gLazyFindCommand("onFindCommand");
2651       break;
2652     case "Help":
2653       openHelpLink("firefox-help");
2654       break;
2655     case "Open":
2656       BrowserOpenFileWindow();
2657       break;
2658     case "Print":
2659       PrintUtils.startPrintWindow(gBrowser.selectedBrowser.browsingContext);
2660       break;
2661     case "Save":
2662       saveBrowser(gBrowser.selectedBrowser);
2663       break;
2664     case "SendMail":
2665       MailIntegration.sendLinkForBrowser(gBrowser.selectedBrowser);
2666       break;
2667     default:
2668       return;
2669   }
2670   evt.stopPropagation();
2671   evt.preventDefault();
2674 function gotoHistoryIndex(aEvent) {
2675   aEvent = getRootEvent(aEvent);
2677   let index = aEvent.target.getAttribute("index");
2678   if (!index) {
2679     return false;
2680   }
2682   let where = whereToOpenLink(aEvent);
2684   if (where == "current") {
2685     // Normal click. Go there in the current tab and update session history.
2687     try {
2688       gBrowser.gotoIndex(index);
2689     } catch (ex) {
2690       return false;
2691     }
2692     return true;
2693   }
2694   // Modified click. Go there in a new tab/window.
2696   let historyindex = aEvent.target.getAttribute("historyindex");
2697   duplicateTabIn(gBrowser.selectedTab, where, Number(historyindex));
2698   return true;
2701 function BrowserForward(aEvent) {
2702   let where = whereToOpenLink(aEvent, false, true);
2704   if (where == "current") {
2705     try {
2706       gBrowser.goForward();
2707     } catch (ex) {}
2708   } else {
2709     duplicateTabIn(gBrowser.selectedTab, where, 1);
2710   }
2713 function BrowserBack(aEvent) {
2714   let where = whereToOpenLink(aEvent, false, true);
2716   if (where == "current") {
2717     try {
2718       gBrowser.goBack();
2719     } catch (ex) {}
2720   } else {
2721     duplicateTabIn(gBrowser.selectedTab, where, -1);
2722   }
2725 function BrowserHandleBackspace() {
2726   switch (Services.prefs.getIntPref("browser.backspace_action")) {
2727     case 0:
2728       BrowserBack();
2729       break;
2730     case 1:
2731       goDoCommand("cmd_scrollPageUp");
2732       break;
2733   }
2736 function BrowserHandleShiftBackspace() {
2737   switch (Services.prefs.getIntPref("browser.backspace_action")) {
2738     case 0:
2739       BrowserForward();
2740       break;
2741     case 1:
2742       goDoCommand("cmd_scrollPageDown");
2743       break;
2744   }
2747 function BrowserStop() {
2748   gBrowser.webNavigation.stop(Ci.nsIWebNavigation.STOP_ALL);
2751 function BrowserReloadOrDuplicate(aEvent) {
2752   aEvent = getRootEvent(aEvent);
2753   let accelKeyPressed =
2754     AppConstants.platform == "macosx" ? aEvent.metaKey : aEvent.ctrlKey;
2755   var backgroundTabModifier = aEvent.button == 1 || accelKeyPressed;
2757   if (aEvent.shiftKey && !backgroundTabModifier) {
2758     BrowserReloadSkipCache();
2759     return;
2760   }
2762   let where = whereToOpenLink(aEvent, false, true);
2763   if (where == "current") {
2764     BrowserReload();
2765   } else {
2766     duplicateTabIn(gBrowser.selectedTab, where);
2767   }
2770 function BrowserReload() {
2771   if (gBrowser.currentURI.schemeIs("view-source")) {
2772     // Bug 1167797: For view source, we always skip the cache
2773     return BrowserReloadSkipCache();
2774   }
2775   const reloadFlags = Ci.nsIWebNavigation.LOAD_FLAGS_NONE;
2776   BrowserReloadWithFlags(reloadFlags);
2779 const kSkipCacheFlags =
2780   Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_PROXY |
2781   Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_CACHE;
2782 function BrowserReloadSkipCache() {
2783   // Bypass proxy and cache.
2784   BrowserReloadWithFlags(kSkipCacheFlags);
2787 function BrowserHome(aEvent) {
2788   if (aEvent && "button" in aEvent && aEvent.button == 2) {
2789     // right-click: do nothing
2790     return;
2791   }
2793   var homePage = HomePage.get(window);
2794   var where = whereToOpenLink(aEvent, false, true);
2795   var urls;
2796   var notifyObservers;
2798   // Don't load the home page in pinned or hidden tabs (e.g. Firefox View).
2799   if (
2800     where == "current" &&
2801     (gBrowser?.selectedTab.pinned || gBrowser?.selectedTab.hidden)
2802   ) {
2803     where = "tab";
2804   }
2806   // openTrustedLinkIn in utilityOverlay.js doesn't handle loading multiple pages
2807   switch (where) {
2808     case "current":
2809       // If we're going to load an initial page in the current tab as the
2810       // home page, we set initialPageLoadedFromURLBar so that the URL
2811       // bar is cleared properly (even during a remoteness flip).
2812       if (isInitialPage(homePage)) {
2813         gBrowser.selectedBrowser.initialPageLoadedFromUserAction = homePage;
2814       }
2815       loadOneOrMoreURIs(
2816         homePage,
2817         Services.scriptSecurityManager.getSystemPrincipal(),
2818         null
2819       );
2820       if (isBlankPageURL(homePage)) {
2821         gURLBar.select();
2822       } else {
2823         gBrowser.selectedBrowser.focus();
2824       }
2825       notifyObservers = true;
2826       aEvent?.preventDefault();
2827       break;
2828     case "tabshifted":
2829     case "tab":
2830       urls = homePage.split("|");
2831       var loadInBackground = Services.prefs.getBoolPref(
2832         "browser.tabs.loadBookmarksInBackground",
2833         false
2834       );
2835       // The homepage observer event should only be triggered when the homepage opens
2836       // in the foreground. This is mostly to support the homepage changed by extension
2837       // doorhanger which doesn't currently support background pages. This may change in
2838       // bug 1438396.
2839       notifyObservers = !loadInBackground;
2840       gBrowser.loadTabs(urls, {
2841         inBackground: loadInBackground,
2842         triggeringPrincipal:
2843           Services.scriptSecurityManager.getSystemPrincipal(),
2844         csp: null,
2845       });
2846       if (!loadInBackground) {
2847         if (isBlankPageURL(homePage)) {
2848           gURLBar.select();
2849         } else {
2850           gBrowser.selectedBrowser.focus();
2851         }
2852       }
2853       aEvent?.preventDefault();
2854       break;
2855     case "window":
2856       // OpenBrowserWindow will trigger the observer event, so no need to do so here.
2857       notifyObservers = false;
2858       OpenBrowserWindow();
2859       aEvent?.preventDefault();
2860       break;
2861   }
2862   if (notifyObservers) {
2863     // A notification for when a user has triggered their homepage. This is used
2864     // to display a doorhanger explaining that an extension has modified the
2865     // homepage, if necessary. Observers are only notified if the homepage
2866     // becomes the active page.
2867     Services.obs.notifyObservers(null, "browser-open-homepage-start");
2868   }
2871 function loadOneOrMoreURIs(aURIString, aTriggeringPrincipal, aCsp) {
2872   // we're not a browser window, pass the URI string to a new browser window
2873   if (window.location.href != AppConstants.BROWSER_CHROME_URL) {
2874     window.openDialog(
2875       AppConstants.BROWSER_CHROME_URL,
2876       "_blank",
2877       "all,dialog=no",
2878       aURIString
2879     );
2880     return;
2881   }
2883   // This function throws for certain malformed URIs, so use exception handling
2884   // so that we don't disrupt startup
2885   try {
2886     gBrowser.loadTabs(aURIString.split("|"), {
2887       inBackground: false,
2888       replace: true,
2889       triggeringPrincipal: aTriggeringPrincipal,
2890       csp: aCsp,
2891     });
2892   } catch (e) {}
2895 function openLocation(event) {
2896   if (window.location.href == AppConstants.BROWSER_CHROME_URL) {
2897     gURLBar.select();
2898     gURLBar.view.autoOpen({ event });
2899     return;
2900   }
2902   // If there's an open browser window, redirect the command there.
2903   let win = URILoadingHelper.getTargetWindow(window);
2904   if (win) {
2905     win.focus();
2906     win.openLocation();
2907     return;
2908   }
2910   // There are no open browser windows; open a new one.
2911   window.openDialog(
2912     AppConstants.BROWSER_CHROME_URL,
2913     "_blank",
2914     "chrome,all,dialog=no",
2915     BROWSER_NEW_TAB_URL
2916   );
2919 function BrowserOpenTab({ event, url } = {}) {
2920   let werePassedURL = !!url;
2921   url ??= BROWSER_NEW_TAB_URL;
2922   let searchClipboard = gMiddleClickNewTabUsesPasteboard && event?.button == 1;
2924   let relatedToCurrent = false;
2925   let where = "tab";
2927   if (event) {
2928     where = whereToOpenLink(event, false, true);
2930     switch (where) {
2931       case "tab":
2932       case "tabshifted":
2933         // When accel-click or middle-click are used, open the new tab as
2934         // related to the current tab.
2935         relatedToCurrent = true;
2936         break;
2937       case "current":
2938         where = "tab";
2939         break;
2940     }
2941   }
2943   // A notification intended to be useful for modular peformance tracking
2944   // starting as close as is reasonably possible to the time when the user
2945   // expressed the intent to open a new tab.  Since there are a lot of
2946   // entry points, this won't catch every single tab created, but most
2947   // initiated by the user should go through here.
2948   //
2949   // Note 1: This notification gets notified with a promise that resolves
2950   //         with the linked browser when the tab gets created
2951   // Note 2: This is also used to notify a user that an extension has changed
2952   //         the New Tab page.
2953   Services.obs.notifyObservers(
2954     {
2955       wrappedJSObject: new Promise(resolve => {
2956         let options = {
2957           relatedToCurrent,
2958           resolveOnNewTabCreated: resolve,
2959         };
2960         if (!werePassedURL && searchClipboard) {
2961           let clipboard = readFromClipboard();
2962           clipboard = UrlbarUtils.stripUnsafeProtocolOnPaste(clipboard).trim();
2963           if (clipboard) {
2964             url = clipboard;
2965             options.allowThirdPartyFixup = true;
2966           }
2967         }
2968         openTrustedLinkIn(url, where, options);
2969       }),
2970     },
2971     "browser-open-newtab-start"
2972   );
2975 var gLastOpenDirectory = {
2976   _lastDir: null,
2977   get path() {
2978     if (!this._lastDir || !this._lastDir.exists()) {
2979       try {
2980         this._lastDir = Services.prefs.getComplexValue(
2981           "browser.open.lastDir",
2982           Ci.nsIFile
2983         );
2984         if (!this._lastDir.exists()) {
2985           this._lastDir = null;
2986         }
2987       } catch (e) {}
2988     }
2989     return this._lastDir;
2990   },
2991   set path(val) {
2992     try {
2993       if (!val || !val.isDirectory()) {
2994         return;
2995       }
2996     } catch (e) {
2997       return;
2998     }
2999     this._lastDir = val.clone();
3001     // Don't save the last open directory pref inside the Private Browsing mode
3002     if (!PrivateBrowsingUtils.isWindowPrivate(window)) {
3003       Services.prefs.setComplexValue(
3004         "browser.open.lastDir",
3005         Ci.nsIFile,
3006         this._lastDir
3007       );
3008     }
3009   },
3010   reset() {
3011     this._lastDir = null;
3012   },
3015 function BrowserOpenFileWindow() {
3016   // Get filepicker component.
3017   try {
3018     const nsIFilePicker = Ci.nsIFilePicker;
3019     let fp = Cc["@mozilla.org/filepicker;1"].createInstance(nsIFilePicker);
3020     let fpCallback = function fpCallback_done(aResult) {
3021       if (aResult == nsIFilePicker.returnOK) {
3022         try {
3023           if (fp.file) {
3024             gLastOpenDirectory.path = fp.file.parent.QueryInterface(Ci.nsIFile);
3025           }
3026         } catch (ex) {}
3027         openTrustedLinkIn(fp.fileURL.spec, "current");
3028       }
3029     };
3031     fp.init(
3032       window.browsingContext,
3033       gNavigatorBundle.getString("openFile"),
3034       nsIFilePicker.modeOpen
3035     );
3036     fp.appendFilters(
3037       nsIFilePicker.filterAll |
3038         nsIFilePicker.filterText |
3039         nsIFilePicker.filterImages |
3040         nsIFilePicker.filterXML |
3041         nsIFilePicker.filterHTML |
3042         nsIFilePicker.filterPDF
3043     );
3044     fp.displayDirectory = gLastOpenDirectory.path;
3045     fp.open(fpCallback);
3046   } catch (ex) {}
3049 function BrowserCloseTabOrWindow(event) {
3050   // If we're not a browser window, just close the window.
3051   if (window.location.href != AppConstants.BROWSER_CHROME_URL) {
3052     closeWindow(true);
3053     return;
3054   }
3056   // In a multi-select context, close all selected tabs
3057   if (gBrowser.multiSelectedTabsCount) {
3058     gBrowser.removeMultiSelectedTabs();
3059     return;
3060   }
3062   // Keyboard shortcuts that would close a tab that is pinned select the first
3063   // unpinned tab instead.
3064   if (
3065     event &&
3066     (event.ctrlKey || event.metaKey || event.altKey) &&
3067     gBrowser.selectedTab.pinned
3068   ) {
3069     if (gBrowser.visibleTabs.length > gBrowser._numPinnedTabs) {
3070       gBrowser.tabContainer.selectedIndex = gBrowser._numPinnedTabs;
3071     }
3072     return;
3073   }
3075   // If the current tab is the last one, this will close the window.
3076   gBrowser.removeCurrentTab({ animate: true });
3079 function BrowserTryToCloseWindow(event) {
3080   if (WindowIsClosing(event)) {
3081     window.close();
3082   } // WindowIsClosing does all the necessary checks
3085 function getLoadContext() {
3086   return window.docShell.QueryInterface(Ci.nsILoadContext);
3089 function readFromClipboard() {
3090   var url;
3092   try {
3093     // Create transferable that will transfer the text.
3094     var trans = Cc["@mozilla.org/widget/transferable;1"].createInstance(
3095       Ci.nsITransferable
3096     );
3097     trans.init(getLoadContext());
3099     trans.addDataFlavor("text/plain");
3101     // If available, use selection clipboard, otherwise global one
3102     let clipboard = Services.clipboard;
3103     if (clipboard.isClipboardTypeSupported(clipboard.kSelectionClipboard)) {
3104       clipboard.getData(trans, clipboard.kSelectionClipboard);
3105     } else {
3106       clipboard.getData(trans, clipboard.kGlobalClipboard);
3107     }
3109     var data = {};
3110     trans.getTransferData("text/plain", data);
3112     if (data) {
3113       data = data.value.QueryInterface(Ci.nsISupportsString);
3114       url = data.data;
3115     }
3116   } catch (ex) {}
3118   return url;
3122  * Open the View Source dialog.
3124  * @param args
3125  *        An object with the following properties:
3127  *        URL (required):
3128  *          A string URL for the page we'd like to view the source of.
3129  *        browser (optional):
3130  *          The browser containing the document that we would like to view the
3131  *          source of. This is required if outerWindowID is passed.
3132  *        outerWindowID (optional):
3133  *          The outerWindowID of the content window containing the document that
3134  *          we want to view the source of. You only need to provide this if you
3135  *          want to attempt to retrieve the document source from the network
3136  *          cache.
3137  *        lineNumber (optional):
3138  *          The line number to focus on once the source is loaded.
3139  */
3140 async function BrowserViewSourceOfDocument(args) {
3141   // Check if external view source is enabled.  If so, try it.  If it fails,
3142   // fallback to internal view source.
3143   if (Services.prefs.getBoolPref("view_source.editor.external")) {
3144     try {
3145       await top.gViewSourceUtils.openInExternalEditor(args);
3146       return;
3147     } catch (data) {}
3148   }
3150   let tabBrowser = gBrowser;
3151   let preferredRemoteType;
3152   let initialBrowsingContextGroupId;
3153   if (args.browser) {
3154     preferredRemoteType = args.browser.remoteType;
3155     initialBrowsingContextGroupId = args.browser.browsingContext.group.id;
3156   } else {
3157     if (!tabBrowser) {
3158       throw new Error(
3159         "BrowserViewSourceOfDocument should be passed the " +
3160           "subject browser if called from a window without " +
3161           "gBrowser defined."
3162       );
3163     }
3164     // Some internal URLs (such as specific chrome: and about: URLs that are
3165     // not yet remote ready) cannot be loaded in a remote browser.  View
3166     // source in tab expects the new view source browser's remoteness to match
3167     // that of the original URL, so disable remoteness if necessary for this
3168     // URL.
3169     var oa = E10SUtils.predictOriginAttributes({ window });
3170     preferredRemoteType = E10SUtils.getRemoteTypeForURI(
3171       args.URL,
3172       gMultiProcessBrowser,
3173       gFissionBrowser,
3174       E10SUtils.DEFAULT_REMOTE_TYPE,
3175       null,
3176       oa
3177     );
3178   }
3180   // In the case of popups, we need to find a non-popup browser window.
3181   if (!tabBrowser || !window.toolbar.visible) {
3182     // This returns only non-popup browser windows by default.
3183     let browserWindow = BrowserWindowTracker.getTopWindow();
3184     tabBrowser = browserWindow.gBrowser;
3185   }
3187   const inNewWindow = !Services.prefs.getBoolPref("view_source.tab");
3189   // `viewSourceInBrowser` will load the source content from the page
3190   // descriptor for the tab (when possible) or fallback to the network if
3191   // that fails.  Either way, the view source module will manage the tab's
3192   // location, so use "about:blank" here to avoid unnecessary redundant
3193   // requests.
3194   let tab = tabBrowser.addTab("about:blank", {
3195     relatedToCurrent: true,
3196     inBackground: inNewWindow,
3197     skipAnimation: inNewWindow,
3198     preferredRemoteType,
3199     initialBrowsingContextGroupId,
3200     triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
3201     skipLoad: true,
3202   });
3203   args.viewSourceBrowser = tabBrowser.getBrowserForTab(tab);
3204   top.gViewSourceUtils.viewSourceInBrowser(args);
3206   if (inNewWindow) {
3207     tabBrowser.hideTab(tab);
3208     tabBrowser.replaceTabWithWindow(tab);
3209   }
3213  * Opens the View Source dialog for the source loaded in the root
3214  * top-level document of the browser. This is really just a
3215  * convenience wrapper around BrowserViewSourceOfDocument.
3217  * @param browser
3218  *        The browser that we want to load the source of.
3219  */
3220 function BrowserViewSource(browser) {
3221   BrowserViewSourceOfDocument({
3222     browser,
3223     outerWindowID: browser.outerWindowID,
3224     URL: browser.currentURI.spec,
3225   });
3228 // documentURL - URL of the document to view, or null for this window's document
3229 // initialTab - name of the initial tab to display, or null for the first tab
3230 // imageElement - image to load in the Media Tab of the Page Info window; can be null/omitted
3231 // browsingContext - the browsingContext of the frame that we want to view information about; can be null/omitted
3232 // browser - the browser containing the document we're interested in inspecting; can be null/omitted
3233 function BrowserPageInfo(
3234   documentURL,
3235   initialTab,
3236   imageElement,
3237   browsingContext,
3238   browser
3239 ) {
3240   let args = { initialTab, imageElement, browsingContext, browser };
3242   documentURL = documentURL || window.gBrowser.selectedBrowser.currentURI.spec;
3244   let isPrivate = PrivateBrowsingUtils.isWindowPrivate(window);
3246   // Check for windows matching the url
3247   for (let currentWindow of Services.wm.getEnumerator("Browser:page-info")) {
3248     if (currentWindow.closed) {
3249       continue;
3250     }
3251     if (
3252       currentWindow.document.documentElement.getAttribute("relatedUrl") ==
3253         documentURL &&
3254       PrivateBrowsingUtils.isWindowPrivate(currentWindow) == isPrivate
3255     ) {
3256       currentWindow.focus();
3257       currentWindow.resetPageInfo(args);
3258       return currentWindow;
3259     }
3260   }
3262   // We didn't find a matching window, so open a new one.
3263   let options = "chrome,toolbar,dialog=no,resizable";
3265   // Ensure the window groups correctly in the Windows taskbar
3266   if (isPrivate) {
3267     options += ",private";
3268   }
3269   return openDialog(
3270     "chrome://browser/content/pageinfo/pageInfo.xhtml",
3271     "",
3272     options,
3273     args
3274   );
3277 function UpdateUrlbarSearchSplitterState() {
3278   var splitter = document.getElementById("urlbar-search-splitter");
3279   var urlbar = document.getElementById("urlbar-container");
3280   var searchbar = document.getElementById("search-container");
3282   if (document.documentElement.getAttribute("customizing") == "true") {
3283     if (splitter) {
3284       splitter.remove();
3285     }
3286     return;
3287   }
3289   // If the splitter is already in the right place, we don't need to do anything:
3290   if (
3291     splitter &&
3292     ((splitter.nextElementSibling == searchbar &&
3293       splitter.previousElementSibling == urlbar) ||
3294       (splitter.nextElementSibling == urlbar &&
3295         splitter.previousElementSibling == searchbar))
3296   ) {
3297     return;
3298   }
3300   let ibefore = null;
3301   let resizebefore = "none";
3302   let resizeafter = "none";
3303   if (urlbar && searchbar) {
3304     if (urlbar.nextElementSibling == searchbar) {
3305       resizeafter = "sibling";
3306       ibefore = searchbar;
3307     } else if (searchbar.nextElementSibling == urlbar) {
3308       resizebefore = "sibling";
3309       ibefore = urlbar;
3310     }
3311   }
3313   if (ibefore) {
3314     if (!splitter) {
3315       splitter = document.createXULElement("splitter");
3316       splitter.id = "urlbar-search-splitter";
3317       splitter.setAttribute("resizebefore", resizebefore);
3318       splitter.setAttribute("resizeafter", resizeafter);
3319       splitter.setAttribute("skipintoolbarset", "true");
3320       splitter.setAttribute("overflows", "false");
3321       splitter.className = "chromeclass-toolbar-additional";
3322     }
3323     urlbar.parentNode.insertBefore(splitter, ibefore);
3324   } else if (splitter) {
3325     splitter.remove();
3326   }
3329 function UpdatePopupNotificationsVisibility() {
3330   // Only need to update PopupNotifications if it has already been initialized
3331   // for this window (i.e. its getter no longer exists).
3332   if (!Object.getOwnPropertyDescriptor(window, "PopupNotifications").get) {
3333     // Notify PopupNotifications that the visible anchors may have changed. This
3334     // also checks the suppression state according to the "shouldSuppress"
3335     // function defined earlier in this file.
3336     PopupNotifications.anchorVisibilityChange();
3337   }
3339   // This is similar to the above, but for notifications attached to the
3340   // hamburger menu icon (such as update notifications and add-on install
3341   // notifications.)
3342   PanelUI?.updateNotifications();
3345 function PageProxyClickHandler(aEvent) {
3346   if (aEvent.button == 1 && Services.prefs.getBoolPref("middlemouse.paste")) {
3347     middleMousePaste(aEvent);
3348   }
3352  * Handle command events bubbling up from error page content
3353  * or from about:newtab or from remote error pages that invoke
3354  * us via async messaging.
3355  */
3356 var BrowserOnClick = {
3357   async ignoreWarningLink(reason, blockedInfo, browsingContext) {
3358     // Add a notify bar before allowing the user to continue through to the
3359     // site, so that they don't lose track after, e.g., tab switching.
3360     // We can't use browser.contentPrincipal which is principal of about:blocked
3361     // Create one from uri with current principal origin attributes
3362     let principal = Services.scriptSecurityManager.createContentPrincipal(
3363       Services.io.newURI(blockedInfo.uri),
3364       browsingContext.currentWindowGlobal.documentPrincipal.originAttributes
3365     );
3366     Services.perms.addFromPrincipal(
3367       principal,
3368       "safe-browsing",
3369       Ci.nsIPermissionManager.ALLOW_ACTION,
3370       Ci.nsIPermissionManager.EXPIRE_SESSION
3371     );
3373     let buttons = [
3374       {
3375         label: gNavigatorBundle.getString(
3376           "safebrowsing.getMeOutOfHereButton.label"
3377         ),
3378         accessKey: gNavigatorBundle.getString(
3379           "safebrowsing.getMeOutOfHereButton.accessKey"
3380         ),
3381         callback() {
3382           getMeOutOfHere(browsingContext);
3383         },
3384       },
3385     ];
3387     let title;
3388     if (reason === "malware") {
3389       let reportUrl = gSafeBrowsing.getReportURL("MalwareMistake", blockedInfo);
3390       title = gNavigatorBundle.getString("safebrowsing.reportedAttackSite");
3391       // There's no button if we can not get report url, for example if the provider
3392       // of blockedInfo is not Google
3393       if (reportUrl) {
3394         buttons[1] = {
3395           label: gNavigatorBundle.getString(
3396             "safebrowsing.notAnAttackButton.label"
3397           ),
3398           accessKey: gNavigatorBundle.getString(
3399             "safebrowsing.notAnAttackButton.accessKey"
3400           ),
3401           callback() {
3402             openTrustedLinkIn(reportUrl, "tab");
3403           },
3404         };
3405       }
3406     } else if (reason === "phishing") {
3407       let reportUrl = gSafeBrowsing.getReportURL("PhishMistake", blockedInfo);
3408       title = gNavigatorBundle.getString("safebrowsing.deceptiveSite");
3409       // There's no button if we can not get report url, for example if the provider
3410       // of blockedInfo is not Google
3411       if (reportUrl) {
3412         buttons[1] = {
3413           label: gNavigatorBundle.getString(
3414             "safebrowsing.notADeceptiveSiteButton.label"
3415           ),
3416           accessKey: gNavigatorBundle.getString(
3417             "safebrowsing.notADeceptiveSiteButton.accessKey"
3418           ),
3419           callback() {
3420             openTrustedLinkIn(reportUrl, "tab");
3421           },
3422         };
3423       }
3424     } else if (reason === "unwanted") {
3425       title = gNavigatorBundle.getString("safebrowsing.reportedUnwantedSite");
3426       // There is no button for reporting errors since Google doesn't currently
3427       // provide a URL endpoint for these reports.
3428     } else if (reason === "harmful") {
3429       title = gNavigatorBundle.getString("safebrowsing.reportedHarmfulSite");
3430       // There is no button for reporting errors since Google doesn't currently
3431       // provide a URL endpoint for these reports.
3432     }
3434     await SafeBrowsingNotificationBox.show(title, buttons);
3436     // Allow users to override and continue through to the site.
3437     // Note that we have to use the passed URI info and can't just
3438     // rely on the document URI, because the latter contains
3439     // additional query parameters that should be stripped.
3440     let triggeringPrincipal =
3441       blockedInfo.triggeringPrincipal ||
3442       _createNullPrincipalFromTabUserContextId();
3444     browsingContext.fixupAndLoadURIString(blockedInfo.uri, {
3445       triggeringPrincipal,
3446       flags: Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_CLASSIFIER,
3447     });
3448   },
3452  * Re-direct the browser to a known-safe page.  This function is
3453  * used when, for example, the user browses to a known malware page
3454  * and is presented with about:blocked.  The "Get me out of here!"
3455  * button should take the user to the default start page so that even
3456  * when their own homepage is infected, we can get them somewhere safe.
3457  */
3458 function getMeOutOfHere(browsingContext) {
3459   browsingContext.top.fixupAndLoadURIString(getDefaultHomePage(), {
3460     triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(), // Also needs to load homepage
3461   });
3465  * Return the default start page for the cases when the user's own homepage is
3466  * infected, so we can get them somewhere safe.
3467  */
3468 function getDefaultHomePage() {
3469   let url = BROWSER_NEW_TAB_URL;
3470   if (PrivateBrowsingUtils.isWindowPrivate(window)) {
3471     return url;
3472   }
3473   url = HomePage.getDefault();
3474   // If url is a pipe-delimited set of pages, just take the first one.
3475   if (url.includes("|")) {
3476     url = url.split("|")[0];
3477   }
3478   return url;
3481 function BrowserFullScreen() {
3482   window.fullScreen = !window.fullScreen || BrowserHandler.kiosk;
3485 function BrowserReloadWithFlags(reloadFlags) {
3486   let unchangedRemoteness = [];
3488   for (let tab of gBrowser.selectedTabs) {
3489     let browser = tab.linkedBrowser;
3490     let url = browser.currentURI;
3491     let urlSpec = url.spec;
3492     // We need to cache the content principal here because the browser will be
3493     // reconstructed when the remoteness changes and the content prinicpal will
3494     // be cleared after reconstruction.
3495     let principal = tab.linkedBrowser.contentPrincipal;
3496     if (gBrowser.updateBrowserRemotenessByURL(browser, urlSpec)) {
3497       // If the remoteness has changed, the new browser doesn't have any
3498       // information of what was loaded before, so we need to load the previous
3499       // URL again.
3500       if (tab.linkedPanel) {
3501         loadBrowserURI(browser, url, principal);
3502       } else {
3503         // Shift to fully loaded browser and make
3504         // sure load handler is instantiated.
3505         tab.addEventListener(
3506           "SSTabRestoring",
3507           () => loadBrowserURI(browser, url, principal),
3508           { once: true }
3509         );
3510         gBrowser._insertBrowser(tab);
3511       }
3512     } else {
3513       unchangedRemoteness.push(tab);
3514     }
3515   }
3517   if (!unchangedRemoteness.length) {
3518     return;
3519   }
3521   // Reset temporary permissions on the remaining tabs to reload.
3522   // This is done here because we only want to reset
3523   // permissions on user reload.
3524   for (let tab of unchangedRemoteness) {
3525     SitePermissions.clearTemporaryBlockPermissions(tab.linkedBrowser);
3526     // Also reset DOS mitigations for the basic auth prompt on reload.
3527     delete tab.linkedBrowser.authPromptAbuseCounter;
3528   }
3529   gIdentityHandler.hidePopup();
3530   gPermissionPanel.hidePopup();
3532   let handlingUserInput = document.hasValidTransientUserGestureActivation;
3534   for (let tab of unchangedRemoteness) {
3535     if (tab.linkedPanel) {
3536       sendReloadMessage(tab);
3537     } else {
3538       // Shift to fully loaded browser and make
3539       // sure load handler is instantiated.
3540       tab.addEventListener("SSTabRestoring", () => sendReloadMessage(tab), {
3541         once: true,
3542       });
3543       gBrowser._insertBrowser(tab);
3544     }
3545   }
3547   function loadBrowserURI(browser, url, principal) {
3548     browser.loadURI(url, {
3549       flags: reloadFlags,
3550       triggeringPrincipal: principal,
3551     });
3552   }
3554   function sendReloadMessage(tab) {
3555     tab.linkedBrowser.sendMessageToActor(
3556       "Browser:Reload",
3557       { flags: reloadFlags, handlingUserInput },
3558       "BrowserTab"
3559     );
3560   }
3563 // TODO: can we pull getPEMString in from pippki.js instead of
3564 // duplicating them here?
3565 function getPEMString(cert) {
3566   var derb64 = cert.getBase64DERString();
3567   // Wrap the Base64 string into lines of 64 characters,
3568   // with CRLF line breaks (as specified in RFC 1421).
3569   var wrapped = derb64.replace(/(\S{64}(?!$))/g, "$1\r\n");
3570   return (
3571     "-----BEGIN CERTIFICATE-----\r\n" +
3572     wrapped +
3573     "\r\n-----END CERTIFICATE-----\r\n"
3574   );
3577 var browserDragAndDrop = {
3578   canDropLink: aEvent => Services.droppedLinkHandler.canDropLink(aEvent, true),
3580   dragOver(aEvent) {
3581     if (this.canDropLink(aEvent)) {
3582       aEvent.preventDefault();
3583     }
3584   },
3586   getTriggeringPrincipal(aEvent) {
3587     return Services.droppedLinkHandler.getTriggeringPrincipal(aEvent);
3588   },
3590   getCsp(aEvent) {
3591     return Services.droppedLinkHandler.getCsp(aEvent);
3592   },
3594   validateURIsForDrop(aEvent, aURIs) {
3595     return Services.droppedLinkHandler.validateURIsForDrop(aEvent, aURIs);
3596   },
3598   dropLinks(aEvent, aDisallowInherit) {
3599     return Services.droppedLinkHandler.dropLinks(aEvent, aDisallowInherit);
3600   },
3603 var homeButtonObserver = {
3604   onDrop(aEvent) {
3605     // disallow setting home pages that inherit the principal
3606     let links = browserDragAndDrop.dropLinks(aEvent, true);
3607     if (links.length) {
3608       let urls = [];
3609       for (let link of links) {
3610         if (link.url.includes("|")) {
3611           urls.push(...link.url.split("|"));
3612         } else {
3613           urls.push(link.url);
3614         }
3615       }
3617       try {
3618         browserDragAndDrop.validateURIsForDrop(aEvent, urls);
3619       } catch (e) {
3620         return;
3621       }
3623       setTimeout(openHomeDialog, 0, urls.join("|"));
3624     }
3625   },
3627   onDragOver(aEvent) {
3628     if (HomePage.locked) {
3629       return;
3630     }
3631     browserDragAndDrop.dragOver(aEvent);
3632     aEvent.dropEffect = "link";
3633   },
3636 function openHomeDialog(aURL) {
3637   var promptTitle = gNavigatorBundle.getString("droponhometitle");
3638   var promptMsg;
3639   if (aURL.includes("|")) {
3640     promptMsg = gNavigatorBundle.getString("droponhomemsgMultiple");
3641   } else {
3642     promptMsg = gNavigatorBundle.getString("droponhomemsg");
3643   }
3645   var pressedVal = Services.prompt.confirmEx(
3646     window,
3647     promptTitle,
3648     promptMsg,
3649     Services.prompt.STD_YES_NO_BUTTONS,
3650     null,
3651     null,
3652     null,
3653     null,
3654     { value: 0 }
3655   );
3657   if (pressedVal == 0) {
3658     HomePage.set(aURL).catch(console.error);
3659   }
3662 var newTabButtonObserver = {
3663   onDragOver(aEvent) {
3664     browserDragAndDrop.dragOver(aEvent);
3665   },
3666   async onDrop(aEvent) {
3667     let links = browserDragAndDrop.dropLinks(aEvent);
3668     if (
3669       links.length >=
3670       Services.prefs.getIntPref("browser.tabs.maxOpenBeforeWarn")
3671     ) {
3672       // Sync dialog cannot be used inside drop event handler.
3673       let answer = await OpenInTabsUtils.promiseConfirmOpenInTabs(
3674         links.length,
3675         window
3676       );
3677       if (!answer) {
3678         return;
3679       }
3680     }
3682     let where = aEvent.shiftKey ? "tabshifted" : "tab";
3683     let triggeringPrincipal = browserDragAndDrop.getTriggeringPrincipal(aEvent);
3684     let csp = browserDragAndDrop.getCsp(aEvent);
3685     for (let link of links) {
3686       if (link.url) {
3687         let data = await UrlbarUtils.getShortcutOrURIAndPostData(link.url);
3688         // Allow third-party services to fixup this URL.
3689         openLinkIn(data.url, where, {
3690           postData: data.postData,
3691           allowThirdPartyFixup: true,
3692           triggeringPrincipal,
3693           csp,
3694         });
3695       }
3696     }
3697   },
3700 var newWindowButtonObserver = {
3701   onDragOver(aEvent) {
3702     browserDragAndDrop.dragOver(aEvent);
3703   },
3704   async onDrop(aEvent) {
3705     let links = browserDragAndDrop.dropLinks(aEvent);
3706     if (
3707       links.length >=
3708       Services.prefs.getIntPref("browser.tabs.maxOpenBeforeWarn")
3709     ) {
3710       // Sync dialog cannot be used inside drop event handler.
3711       let answer = await OpenInTabsUtils.promiseConfirmOpenInTabs(
3712         links.length,
3713         window
3714       );
3715       if (!answer) {
3716         return;
3717       }
3718     }
3720     let triggeringPrincipal = browserDragAndDrop.getTriggeringPrincipal(aEvent);
3721     let csp = browserDragAndDrop.getCsp(aEvent);
3722     for (let link of links) {
3723       if (link.url) {
3724         let data = await UrlbarUtils.getShortcutOrURIAndPostData(link.url);
3725         // Allow third-party services to fixup this URL.
3726         openLinkIn(data.url, "window", {
3727           // TODO fix allowInheritPrincipal
3728           // (this is required by javascript: drop to the new window) Bug 1475201
3729           allowInheritPrincipal: true,
3730           postData: data.postData,
3731           allowThirdPartyFixup: true,
3732           triggeringPrincipal,
3733           csp,
3734         });
3735       }
3736     }
3737   },
3740 const BrowserSearch = {
3741   _searchInitComplete: false,
3743   init() {
3744     Services.obs.addObserver(this, "browser-search-engine-modified");
3745   },
3747   delayedStartupInit() {
3748     // Asynchronously initialize the search service if necessary, to get the
3749     // current engine for working out the placeholder.
3750     this._updateURLBarPlaceholderFromDefaultEngine(
3751       PrivateBrowsingUtils.isWindowPrivate(window),
3752       // Delay the update for this until so that we don't change it while
3753       // the user is looking at it / isn't expecting it.
3754       true
3755     ).then(() => {
3756       this._searchInitComplete = true;
3757     });
3758   },
3760   uninit() {
3761     Services.obs.removeObserver(this, "browser-search-engine-modified");
3762   },
3764   observe(engine, topic, data) {
3765     // There are two kinds of search engine objects, nsISearchEngine objects and
3766     // plain { uri, title, icon } objects.  `engine` in this method is the
3767     // former.  The browser.engines and browser.hiddenEngines arrays are the
3768     // latter, and they're the engines offered by the the page in the browser.
3769     //
3770     // The two types of engines are currently related by their titles/names,
3771     // although that may change; see bug 335102.
3772     let engineName = engine.wrappedJSObject.name;
3773     switch (data) {
3774       case "engine-removed":
3775         // An engine was removed from the search service.  If a page is offering
3776         // the engine, then the engine needs to be added back to the corresponding
3777         // browser's offered engines.
3778         this._addMaybeOfferedEngine(engineName);
3779         break;
3780       case "engine-added":
3781         // An engine was added to the search service.  If a page is offering the
3782         // engine, then the engine needs to be removed from the corresponding
3783         // browser's offered engines.
3784         this._removeMaybeOfferedEngine(engineName);
3785         break;
3786       case "engine-default":
3787         if (
3788           this._searchInitComplete &&
3789           !PrivateBrowsingUtils.isWindowPrivate(window)
3790         ) {
3791           this._updateURLBarPlaceholder(engineName, false);
3792         }
3793         break;
3794       case "engine-default-private":
3795         if (
3796           this._searchInitComplete &&
3797           PrivateBrowsingUtils.isWindowPrivate(window)
3798         ) {
3799           this._updateURLBarPlaceholder(engineName, true);
3800         }
3801         break;
3802     }
3803   },
3805   _addMaybeOfferedEngine(engineName) {
3806     let selectedBrowserOffersEngine = false;
3807     for (let browser of gBrowser.browsers) {
3808       for (let i = 0; i < (browser.hiddenEngines || []).length; i++) {
3809         if (browser.hiddenEngines[i].title == engineName) {
3810           if (!browser.engines) {
3811             browser.engines = [];
3812           }
3813           browser.engines.push(browser.hiddenEngines[i]);
3814           browser.hiddenEngines.splice(i, 1);
3815           if (browser == gBrowser.selectedBrowser) {
3816             selectedBrowserOffersEngine = true;
3817           }
3818           break;
3819         }
3820       }
3821     }
3822     if (selectedBrowserOffersEngine) {
3823       this.updateOpenSearchBadge();
3824     }
3825   },
3827   _removeMaybeOfferedEngine(engineName) {
3828     let selectedBrowserOffersEngine = false;
3829     for (let browser of gBrowser.browsers) {
3830       for (let i = 0; i < (browser.engines || []).length; i++) {
3831         if (browser.engines[i].title == engineName) {
3832           if (!browser.hiddenEngines) {
3833             browser.hiddenEngines = [];
3834           }
3835           browser.hiddenEngines.push(browser.engines[i]);
3836           browser.engines.splice(i, 1);
3837           if (browser == gBrowser.selectedBrowser) {
3838             selectedBrowserOffersEngine = true;
3839           }
3840           break;
3841         }
3842       }
3843     }
3844     if (selectedBrowserOffersEngine) {
3845       this.updateOpenSearchBadge();
3846     }
3847   },
3849   /**
3850    * Initializes the urlbar placeholder to the pre-saved engine name. We do this
3851    * via a preference, to avoid needing to synchronously init the search service.
3852    *
3853    * This should be called around the time of DOMContentLoaded, so that it is
3854    * initialized quickly before the user sees anything.
3855    *
3856    * Note: If the preference doesn't exist, we don't do anything as the default
3857    * placeholder is a string which doesn't have the engine name; however, this
3858    * can be overridden using the `force` parameter.
3859    *
3860    * @param {Boolean} force If true and the preference doesn't exist, the
3861    *                        placeholder will be set to the default version
3862    *                        without an engine name ("Search or enter address").
3863    */
3864   initPlaceHolder(force = false) {
3865     const prefName =
3866       "browser.urlbar.placeholderName" +
3867       (PrivateBrowsingUtils.isWindowPrivate(window) ? ".private" : "");
3868     let engineName = Services.prefs.getStringPref(prefName, "");
3869     if (engineName || force) {
3870       // We can do this directly, since we know we're at DOMContentLoaded.
3871       this._setURLBarPlaceholder(engineName);
3872     }
3873   },
3875   /**
3876    * This is a wrapper around '_updateURLBarPlaceholder' that uses the
3877    * appropriate default engine to get the engine name.
3878    *
3879    * @param {Boolean} isPrivate      Set to true if this is a private window.
3880    * @param {Boolean} [delayUpdate]  Set to true, to delay update until the
3881    *                                 placeholder is not displayed.
3882    */
3883   async _updateURLBarPlaceholderFromDefaultEngine(
3884     isPrivate,
3885     delayUpdate = false
3886   ) {
3887     const getDefault = isPrivate
3888       ? Services.search.getDefaultPrivate
3889       : Services.search.getDefault;
3890     let defaultEngine = await getDefault();
3891     if (!this._searchInitComplete) {
3892       // If we haven't finished initialising, ensure the placeholder
3893       // preference is set for the next startup.
3894       SearchUIUtils.updatePlaceholderNamePreference(defaultEngine, isPrivate);
3895     }
3896     this._updateURLBarPlaceholder(defaultEngine.name, isPrivate, delayUpdate);
3897   },
3899   /**
3900    * Updates the URLBar placeholder for the specified engine, delaying the
3901    * update if required. This also saves the current engine name in preferences
3902    * for the next restart.
3903    *
3904    * Note: The engine name will only be displayed for built-in engines, as we
3905    * know they should have short names.
3906    *
3907    * @param {String}  engineName     The search engine name to use for the update.
3908    * @param {Boolean} isPrivate      Set to true if this is a private window.
3909    * @param {Boolean} [delayUpdate]  Set to true, to delay update until the
3910    *                                 placeholder is not displayed.
3911    */
3912   _updateURLBarPlaceholder(engineName, isPrivate, delayUpdate = false) {
3913     if (!engineName) {
3914       throw new Error("Expected an engineName to be specified");
3915     }
3917     const engine = Services.search.getEngineByName(engineName);
3918     if (!engine.isAppProvided) {
3919       // Set the engine name to an empty string for non-default engines, which'll
3920       // make sure we display the default placeholder string.
3921       engineName = "";
3922     }
3924     // Only delay if requested, and we're not displaying text in the URL bar
3925     // currently.
3926     if (delayUpdate && !gURLBar.value) {
3927       // Delays changing the URL Bar placeholder until the user is not going to be
3928       // seeing it, e.g. when there is a value entered in the bar, or if there is
3929       // a tab switch to a tab which has a url loaded. We delay the update until
3930       // the user is out of search mode since an alternative placeholder is used
3931       // in search mode.
3932       let placeholderUpdateListener = () => {
3933         if (gURLBar.value && !gURLBar.searchMode) {
3934           // By the time the user has switched, they may have changed the engine
3935           // again, so we need to call this function again but with the
3936           // new engine name.
3937           // No need to await for this to finish, we're in a listener here anyway.
3938           this._updateURLBarPlaceholderFromDefaultEngine(isPrivate, false);
3939           gURLBar.removeEventListener("input", placeholderUpdateListener);
3940           gBrowser.tabContainer.removeEventListener(
3941             "TabSelect",
3942             placeholderUpdateListener
3943           );
3944         }
3945       };
3947       gURLBar.addEventListener("input", placeholderUpdateListener);
3948       gBrowser.tabContainer.addEventListener(
3949         "TabSelect",
3950         placeholderUpdateListener
3951       );
3952     } else if (!gURLBar.searchMode) {
3953       this._setURLBarPlaceholder(engineName);
3954     }
3955   },
3957   /**
3958    * Sets the URLBar placeholder to either something based on the engine name,
3959    * or the default placeholder.
3960    *
3961    * @param {String} name The name of the engine to use, an empty string if to
3962    *                      use the default placeholder.
3963    */
3964   _setURLBarPlaceholder(name) {
3965     document.l10n.setAttributes(
3966       gURLBar.inputField,
3967       name ? "urlbar-placeholder-with-name" : "urlbar-placeholder",
3968       name ? { name } : undefined
3969     );
3970   },
3972   addEngine(browser, engine) {
3973     if (!this._searchInitComplete) {
3974       // We haven't finished initialising search yet. This means we can't
3975       // call getEngineByName here. Since this is only on start-up and unlikely
3976       // to happen in the normal case, we'll just return early rather than
3977       // trying to handle it asynchronously.
3978       return;
3979     }
3980     // Check to see whether we've already added an engine with this title
3981     if (browser.engines) {
3982       if (browser.engines.some(e => e.title == engine.title)) {
3983         return;
3984       }
3985     }
3987     var hidden = false;
3988     // If this engine (identified by title) is already in the list, add it
3989     // to the list of hidden engines rather than to the main list.
3990     if (Services.search.getEngineByName(engine.title)) {
3991       hidden = true;
3992     }
3994     var engines = (hidden ? browser.hiddenEngines : browser.engines) || [];
3996     engines.push({
3997       uri: engine.href,
3998       title: engine.title,
3999       get icon() {
4000         return browser.mIconURL;
4001       },
4002     });
4004     if (hidden) {
4005       browser.hiddenEngines = engines;
4006     } else {
4007       browser.engines = engines;
4008       if (browser == gBrowser.selectedBrowser) {
4009         this.updateOpenSearchBadge();
4010       }
4011     }
4012   },
4014   /**
4015    * Update the browser UI to show whether or not additional engines are
4016    * available when a page is loaded or the user switches tabs to a page that
4017    * has search engines.
4018    */
4019   updateOpenSearchBadge() {
4020     gURLBar.addSearchEngineHelper.setEnginesFromBrowser(
4021       gBrowser.selectedBrowser
4022     );
4024     var searchBar = this.searchBar;
4025     if (!searchBar) {
4026       return;
4027     }
4029     var engines = gBrowser.selectedBrowser.engines;
4030     if (engines && engines.length) {
4031       searchBar.setAttribute("addengines", "true");
4032     } else {
4033       searchBar.removeAttribute("addengines");
4034     }
4035   },
4037   /**
4038    * Focuses the search bar if present on the toolbar, or the address bar,
4039    * putting it in search mode. Will do so in an existing non-popup browser
4040    * window or open a new one if necessary.
4041    */
4042   webSearch: function BrowserSearch_webSearch() {
4043     if (
4044       window.location.href != AppConstants.BROWSER_CHROME_URL ||
4045       gURLBar.readOnly
4046     ) {
4047       let win = URILoadingHelper.getTopWin(window, { skipPopups: true });
4048       if (win) {
4049         // If there's an open browser window, it should handle this command
4050         win.focus();
4051         win.BrowserSearch.webSearch();
4052       } else {
4053         // If there are no open browser windows, open a new one
4054         var observer = function (subject) {
4055           if (subject == win) {
4056             BrowserSearch.webSearch();
4057             Services.obs.removeObserver(
4058               observer,
4059               "browser-delayed-startup-finished"
4060             );
4061           }
4062         };
4063         win = window.openDialog(
4064           AppConstants.BROWSER_CHROME_URL,
4065           "_blank",
4066           "chrome,all,dialog=no",
4067           "about:blank"
4068         );
4069         Services.obs.addObserver(observer, "browser-delayed-startup-finished");
4070       }
4071       return;
4072     }
4074     let focusUrlBarIfSearchFieldIsNotActive = function (aSearchBar) {
4075       if (!aSearchBar || document.activeElement != aSearchBar.textbox) {
4076         // Limit the results to search suggestions, like the search bar.
4077         gURLBar.searchModeShortcut();
4078       }
4079     };
4081     let searchBar = this.searchBar;
4082     let placement = CustomizableUI.getPlacementOfWidget("search-container");
4083     let focusSearchBar = () => {
4084       searchBar = this.searchBar;
4085       searchBar.select();
4086       focusUrlBarIfSearchFieldIsNotActive(searchBar);
4087     };
4088     if (
4089       placement &&
4090       searchBar &&
4091       ((searchBar.parentNode.getAttribute("overflowedItem") == "true" &&
4092         placement.area == CustomizableUI.AREA_NAVBAR) ||
4093         placement.area == CustomizableUI.AREA_FIXED_OVERFLOW_PANEL)
4094     ) {
4095       let navBar = document.getElementById(CustomizableUI.AREA_NAVBAR);
4096       navBar.overflowable.show().then(focusSearchBar);
4097       return;
4098     }
4099     if (searchBar) {
4100       if (window.fullScreen) {
4101         FullScreen.showNavToolbox();
4102       }
4103       searchBar.select();
4104     }
4105     focusUrlBarIfSearchFieldIsNotActive(searchBar);
4106   },
4108   /**
4109    * Loads a search results page, given a set of search terms. Uses the current
4110    * engine if the search bar is visible, or the default engine otherwise.
4111    *
4112    * @param searchText
4113    *        The search terms to use for the search.
4114    * @param where
4115    *        String indicating where the search should load. Most commonly used
4116    *        are 'tab' or 'window', defaults to 'current'.
4117    * @param usePrivate
4118    *        Whether to use the Private Browsing mode default search engine.
4119    *        Defaults to `false`.
4120    * @param purpose [optional]
4121    *        A string meant to indicate the context of the search request. This
4122    *        allows the search service to provide a different nsISearchSubmission
4123    *        depending on e.g. where the search is triggered in the UI.
4124    * @param triggeringPrincipal
4125    *        The principal to use for a new window or tab.
4126    * @param csp
4127    *        The content security policy to use for a new window or tab.
4128    * @param inBackground [optional]
4129    *        Set to true for the tab to be loaded in the background, default false.
4130    * @param engine [optional]
4131    *        The search engine to use for the search.
4132    * @param tab [optional]
4133    *        The tab to show the search result.
4134    *
4135    * @return engine The search engine used to perform a search, or null if no
4136    *                search was performed.
4137    */
4138   async _loadSearch(
4139     searchText,
4140     where,
4141     usePrivate,
4142     purpose,
4143     triggeringPrincipal,
4144     csp,
4145     inBackground = false,
4146     engine = null,
4147     tab = null
4148   ) {
4149     if (!triggeringPrincipal) {
4150       throw new Error(
4151         "Required argument triggeringPrincipal missing within _loadSearch"
4152       );
4153     }
4155     if (!engine) {
4156       engine = usePrivate
4157         ? await Services.search.getDefaultPrivate()
4158         : await Services.search.getDefault();
4159     }
4161     let submission = engine.getSubmission(searchText, null, purpose); // HTML response
4163     // getSubmission can return null if the engine doesn't have a URL
4164     // with a text/html response type.  This is unlikely (since
4165     // SearchService._addEngineToStore() should fail for such an engine),
4166     // but let's be on the safe side.
4167     if (!submission) {
4168       return null;
4169     }
4171     openLinkIn(submission.uri.spec, where || "current", {
4172       private: usePrivate && !PrivateBrowsingUtils.isWindowPrivate(window),
4173       postData: submission.postData,
4174       inBackground,
4175       relatedToCurrent: true,
4176       triggeringPrincipal,
4177       csp,
4178       targetBrowser: tab?.linkedBrowser,
4179       globalHistoryOptions: {
4180         triggeringSearchEngine: engine.name,
4181       },
4182     });
4184     return { engine, url: submission.uri };
4185   },
4187   /**
4188    * Perform a search initiated from the context menu.
4189    *
4190    * This should only be called from the context menu. See
4191    * BrowserSearch.loadSearch for the preferred API.
4192    */
4193   async loadSearchFromContext(
4194     terms,
4195     usePrivate,
4196     triggeringPrincipal,
4197     csp,
4198     event
4199   ) {
4200     event = getRootEvent(event);
4201     let where = whereToOpenLink(event);
4202     if (where == "current") {
4203       // override: historically search opens in new tab
4204       where = "tab";
4205     }
4206     if (usePrivate && !PrivateBrowsingUtils.isWindowPrivate(window)) {
4207       where = "window";
4208     }
4209     let inBackground = Services.prefs.getBoolPref(
4210       "browser.search.context.loadInBackground"
4211     );
4212     if (event.button == 1 || event.ctrlKey) {
4213       inBackground = !inBackground;
4214     }
4216     let { engine } = await BrowserSearch._loadSearch(
4217       terms,
4218       where,
4219       usePrivate,
4220       "contextmenu",
4221       Services.scriptSecurityManager.createNullPrincipal(
4222         triggeringPrincipal.originAttributes
4223       ),
4224       csp,
4225       inBackground
4226     );
4228     if (engine) {
4229       BrowserSearchTelemetry.recordSearch(
4230         gBrowser.selectedBrowser,
4231         engine,
4232         "contextmenu"
4233       );
4234     }
4235   },
4237   /**
4238    * Perform a search initiated from the command line.
4239    */
4240   async loadSearchFromCommandLine(terms, usePrivate, triggeringPrincipal, csp) {
4241     let { engine } = await BrowserSearch._loadSearch(
4242       terms,
4243       "current",
4244       usePrivate,
4245       "system",
4246       triggeringPrincipal,
4247       csp
4248     );
4249     if (engine) {
4250       BrowserSearchTelemetry.recordSearch(
4251         gBrowser.selectedBrowser,
4252         engine,
4253         "system"
4254       );
4255     }
4256   },
4258   /**
4259    * Perform a search initiated from an extension.
4260    */
4261   async loadSearchFromExtension({
4262     query,
4263     engine,
4264     where,
4265     tab,
4266     triggeringPrincipal,
4267   }) {
4268     const result = await BrowserSearch._loadSearch(
4269       query,
4270       where,
4271       PrivateBrowsingUtils.isWindowPrivate(window),
4272       "webextension",
4273       triggeringPrincipal,
4274       null,
4275       false,
4276       engine,
4277       tab
4278     );
4280     BrowserSearchTelemetry.recordSearch(
4281       gBrowser.selectedBrowser,
4282       result.engine,
4283       "webextension"
4284     );
4285   },
4287   /**
4288    * Returns the search bar element if it is present in the toolbar, null otherwise.
4289    */
4290   get searchBar() {
4291     return document.getElementById("searchbar");
4292   },
4294   /**
4295    * Infobar to notify the user's search engine has been removed
4296    * and replaced with an application default search engine.
4297    *
4298    * @param {string} oldEngine
4299    *   name of the engine to be moved and replaced.
4300    * @param {string} newEngine
4301    *   name of the application default engine to replaced the removed engine.
4302    */
4303   async removalOfSearchEngineNotificationBox(oldEngine, newEngine) {
4304     let buttons = [
4305       {
4306         "l10n-id": "remove-search-engine-button",
4307         primary: true,
4308         callback() {
4309           const notificationBox = gNotificationBox.getNotificationWithValue(
4310             "search-engine-removal"
4311           );
4312           gNotificationBox.removeNotification(notificationBox);
4313         },
4314       },
4315       {
4316         supportPage: "search-engine-removal",
4317       },
4318     ];
4320     await gNotificationBox.appendNotification(
4321       "search-engine-removal",
4322       {
4323         label: {
4324           "l10n-id": "removed-search-engine-message2",
4325           "l10n-args": { oldEngine, newEngine },
4326         },
4327         priority: gNotificationBox.PRIORITY_SYSTEM,
4328       },
4329       buttons
4330     );
4332     // Update engine name in the placeholder to the new default engine name.
4333     this._updateURLBarPlaceholderFromDefaultEngine(
4334       PrivateBrowsingUtils.isWindowPrivate(window),
4335       false
4336     ).catch(console.error);
4337   },
4340 XPCOMUtils.defineConstant(this, "BrowserSearch", BrowserSearch);
4342 function CreateContainerTabMenu(event) {
4343   // Do not open context menus within menus.
4344   // Note that triggerNode is null if we're opened by long press.
4345   if (event.target.triggerNode?.closest("menupopup")) {
4346     return false;
4347   }
4348   createUserContextMenu(event, {
4349     useAccessKeys: false,
4350     showDefaultTab: true,
4351   });
4354 function FillHistoryMenu(aParent) {
4355   // Lazily add the hover listeners on first showing and never remove them
4356   if (!aParent.hasStatusListener) {
4357     // Show history item's uri in the status bar when hovering, and clear on exit
4358     aParent.addEventListener("DOMMenuItemActive", function (aEvent) {
4359       // Only the current page should have the checked attribute, so skip it
4360       if (!aEvent.target.hasAttribute("checked")) {
4361         XULBrowserWindow.setOverLink(aEvent.target.getAttribute("uri"));
4362       }
4363     });
4364     aParent.addEventListener("DOMMenuItemInactive", function () {
4365       XULBrowserWindow.setOverLink("");
4366     });
4368     aParent.hasStatusListener = true;
4369   }
4371   // Remove old entries if any
4372   let children = aParent.children;
4373   for (var i = children.length - 1; i >= 0; --i) {
4374     if (children[i].hasAttribute("index")) {
4375       aParent.removeChild(children[i]);
4376     }
4377   }
4379   const MAX_HISTORY_MENU_ITEMS = 15;
4381   const tooltipBack = gNavigatorBundle.getString("tabHistory.goBack");
4382   const tooltipCurrent = gNavigatorBundle.getString("tabHistory.reloadCurrent");
4383   const tooltipForward = gNavigatorBundle.getString("tabHistory.goForward");
4385   function updateSessionHistory(sessionHistory, initial, ssInParent) {
4386     let count = ssInParent
4387       ? sessionHistory.count
4388       : sessionHistory.entries.length;
4390     if (!initial) {
4391       if (count <= 1) {
4392         // if there is only one entry now, close the popup.
4393         aParent.hidePopup();
4394         return;
4395       } else if (aParent.id != "backForwardMenu" && !aParent.parentNode.open) {
4396         // if the popup wasn't open before, but now needs to be, reopen the menu.
4397         // It should trigger FillHistoryMenu again. This might happen with the
4398         // delay from click-and-hold menus but skip this for the context menu
4399         // (backForwardMenu) rather than figuring out how the menu should be
4400         // positioned and opened as it is an extreme edgecase.
4401         aParent.parentNode.open = true;
4402         return;
4403       }
4404     }
4406     let index = sessionHistory.index;
4407     let half_length = Math.floor(MAX_HISTORY_MENU_ITEMS / 2);
4408     let start = Math.max(index - half_length, 0);
4409     let end = Math.min(
4410       start == 0 ? MAX_HISTORY_MENU_ITEMS : index + half_length + 1,
4411       count
4412     );
4413     if (end == count) {
4414       start = Math.max(count - MAX_HISTORY_MENU_ITEMS, 0);
4415     }
4417     let existingIndex = 0;
4419     for (let j = end - 1; j >= start; j--) {
4420       let entry = ssInParent
4421         ? sessionHistory.getEntryAtIndex(j)
4422         : sessionHistory.entries[j];
4423       // Explicitly check for "false" to stay backwards-compatible with session histories
4424       // from before the hasUserInteraction was implemented.
4425       if (
4426         BrowserUtils.navigationRequireUserInteraction &&
4427         entry.hasUserInteraction === false &&
4428         // Always allow going to the first and last navigation points.
4429         j != end - 1 &&
4430         j != start
4431       ) {
4432         continue;
4433       }
4434       let uri = ssInParent ? entry.URI.spec : entry.url;
4436       let item =
4437         existingIndex < children.length
4438           ? children[existingIndex]
4439           : document.createXULElement("menuitem");
4441       item.setAttribute("uri", uri);
4442       item.setAttribute("label", entry.title || uri);
4443       item.setAttribute("index", j);
4445       // Cache this so that gotoHistoryIndex doesn't need the original index
4446       item.setAttribute("historyindex", j - index);
4448       if (j != index) {
4449         // Use list-style-image rather than the image attribute in order to
4450         // allow CSS to override this.
4451         item.style.listStyleImage = `url(page-icon:${uri})`;
4452       }
4454       if (j < index) {
4455         item.className =
4456           "unified-nav-back menuitem-iconic menuitem-with-favicon";
4457         item.setAttribute("tooltiptext", tooltipBack);
4458       } else if (j == index) {
4459         item.setAttribute("type", "radio");
4460         item.setAttribute("checked", "true");
4461         item.className = "unified-nav-current";
4462         item.setAttribute("tooltiptext", tooltipCurrent);
4463       } else {
4464         item.className =
4465           "unified-nav-forward menuitem-iconic menuitem-with-favicon";
4466         item.setAttribute("tooltiptext", tooltipForward);
4467       }
4469       if (!item.parentNode) {
4470         aParent.appendChild(item);
4471       }
4473       existingIndex++;
4474     }
4476     if (!initial) {
4477       let existingLength = children.length;
4478       while (existingIndex < existingLength) {
4479         aParent.removeChild(aParent.lastElementChild);
4480         existingIndex++;
4481       }
4482     }
4483   }
4485   // If session history in parent is available, use it. Otherwise, get the session history
4486   // from session store.
4487   let sessionHistory = gBrowser.selectedBrowser.browsingContext.sessionHistory;
4488   if (sessionHistory?.count) {
4489     // Don't show the context menu if there is only one item.
4490     if (sessionHistory.count <= 1) {
4491       return false;
4492     }
4494     updateSessionHistory(sessionHistory, true, true);
4495   } else {
4496     sessionHistory = SessionStore.getSessionHistory(
4497       gBrowser.selectedTab,
4498       updateSessionHistory
4499     );
4500     updateSessionHistory(sessionHistory, true, false);
4501   }
4503   return true;
4506 function BrowserDownloadsUI() {
4507   if (PrivateBrowsingUtils.isWindowPrivate(window)) {
4508     openTrustedLinkIn("about:downloads", "tab");
4509   } else {
4510     PlacesCommandHook.showPlacesOrganizer("Downloads");
4511   }
4514 function toOpenWindowByType(inType, uri, features) {
4515   var topWindow = Services.wm.getMostRecentWindow(inType);
4517   if (topWindow) {
4518     topWindow.focus();
4519   } else if (features) {
4520     window.open(uri, "_blank", features);
4521   } else {
4522     window.open(
4523       uri,
4524       "_blank",
4525       "chrome,extrachrome,menubar,resizable,scrollbars,status,toolbar"
4526     );
4527   }
4531  * Open a new browser window. See `BrowserWindowTracker.openWindow` for
4532  * options.
4534  * @return a reference to the new window.
4535  */
4536 function OpenBrowserWindow(options = {}) {
4537   let telemetryObj = {};
4538   TelemetryStopwatch.start("FX_NEW_WINDOW_MS", telemetryObj);
4540   let win = BrowserWindowTracker.openWindow({
4541     openerWindow: window,
4542     ...options,
4543   });
4545   win.addEventListener(
4546     "MozAfterPaint",
4547     () => {
4548       TelemetryStopwatch.finish("FX_NEW_WINDOW_MS", telemetryObj);
4549     },
4550     { once: true }
4551   );
4553   return win;
4557  * Update the global flag that tracks whether or not any edit UI (the Edit menu,
4558  * edit-related items in the context menu, and edit-related toolbar buttons
4559  * is visible, then update the edit commands' enabled state accordingly.  We use
4560  * this flag to skip updating the edit commands on focus or selection changes
4561  * when no UI is visible to improve performance (including pageload performance,
4562  * since focus changes when you load a new page).
4564  * If UI is visible, we use goUpdateGlobalEditMenuItems to set the commands'
4565  * enabled state so the UI will reflect it appropriately.
4567  * If the UI isn't visible, we enable all edit commands so keyboard shortcuts
4568  * still work and just lazily disable them as needed when the user presses a
4569  * shortcut.
4571  * This doesn't work on Mac, since Mac menus flash when users press their
4572  * keyboard shortcuts, so edit UI is essentially always visible on the Mac,
4573  * and we need to always update the edit commands.  Thus on Mac this function
4574  * is a no op.
4575  */
4576 function updateEditUIVisibility() {
4577   if (AppConstants.platform == "macosx") {
4578     return;
4579   }
4581   let editMenuPopupState = document.getElementById("menu_EditPopup").state;
4582   let contextMenuPopupState = document.getElementById(
4583     "contentAreaContextMenu"
4584   ).state;
4585   let placesContextMenuPopupState =
4586     document.getElementById("placesContext").state;
4588   let oldVisible = gEditUIVisible;
4590   // The UI is visible if the Edit menu is opening or open, if the context menu
4591   // is open, or if the toolbar has been customized to include the Cut, Copy,
4592   // or Paste toolbar buttons.
4593   gEditUIVisible =
4594     editMenuPopupState == "showing" ||
4595     editMenuPopupState == "open" ||
4596     contextMenuPopupState == "showing" ||
4597     contextMenuPopupState == "open" ||
4598     placesContextMenuPopupState == "showing" ||
4599     placesContextMenuPopupState == "open";
4600   const kOpenPopupStates = ["showing", "open"];
4601   if (!gEditUIVisible) {
4602     // Now check the edit-controls toolbar buttons.
4603     let placement = CustomizableUI.getPlacementOfWidget("edit-controls");
4604     let areaType = placement ? CustomizableUI.getAreaType(placement.area) : "";
4605     if (areaType == CustomizableUI.TYPE_PANEL) {
4606       let customizablePanel = PanelUI.overflowPanel;
4607       gEditUIVisible = kOpenPopupStates.includes(customizablePanel.state);
4608     } else if (
4609       areaType == CustomizableUI.TYPE_TOOLBAR &&
4610       window.toolbar.visible
4611     ) {
4612       // The edit controls are on a toolbar, so they are visible,
4613       // unless they're in a panel that isn't visible...
4614       if (placement.area == "nav-bar") {
4615         let editControls = document.getElementById("edit-controls");
4616         gEditUIVisible =
4617           !editControls.hasAttribute("overflowedItem") ||
4618           kOpenPopupStates.includes(
4619             document.getElementById("widget-overflow").state
4620           );
4621       } else {
4622         gEditUIVisible = true;
4623       }
4624     }
4625   }
4627   // Now check the main menu panel
4628   if (!gEditUIVisible) {
4629     gEditUIVisible = kOpenPopupStates.includes(PanelUI.panel.state);
4630   }
4632   // No need to update commands if the edit UI visibility has not changed.
4633   if (gEditUIVisible == oldVisible) {
4634     return;
4635   }
4637   // If UI is visible, update the edit commands' enabled state to reflect
4638   // whether or not they are actually enabled for the current focus/selection.
4639   if (gEditUIVisible) {
4640     goUpdateGlobalEditMenuItems();
4641   } else {
4642     // Otherwise, enable all commands, so that keyboard shortcuts still work,
4643     // then lazily determine their actual enabled state when the user presses
4644     // a keyboard shortcut.
4645     goSetCommandEnabled("cmd_undo", true);
4646     goSetCommandEnabled("cmd_redo", true);
4647     goSetCommandEnabled("cmd_cut", true);
4648     goSetCommandEnabled("cmd_copy", true);
4649     goSetCommandEnabled("cmd_paste", true);
4650     goSetCommandEnabled("cmd_selectAll", true);
4651     goSetCommandEnabled("cmd_delete", true);
4652     goSetCommandEnabled("cmd_switchTextDirection", true);
4653   }
4656 let gFileMenu = {
4657   /**
4658    * Updates User Context Menu Item UI visibility depending on
4659    * privacy.userContext.enabled pref state.
4660    */
4661   updateUserContextUIVisibility() {
4662     let menu = document.getElementById("menu_newUserContext");
4663     menu.hidden = !Services.prefs.getBoolPref(
4664       "privacy.userContext.enabled",
4665       false
4666     );
4667     // Visibility of File menu item shouldn't change frequently.
4668     if (PrivateBrowsingUtils.isWindowPrivate(window)) {
4669       menu.setAttribute("disabled", "true");
4670     }
4671   },
4673   /**
4674    * Updates the enabled state of the "Import From Another Browser" command
4675    * depending on the DisableProfileImport policy.
4676    */
4677   updateImportCommandEnabledState() {
4678     if (!Services.policies.isAllowed("profileImport")) {
4679       document
4680         .getElementById("cmd_file_importFromAnotherBrowser")
4681         .setAttribute("disabled", "true");
4682     }
4683   },
4685   /**
4686    * Updates the "Close tab" command to reflect the number of selected tabs,
4687    * when applicable.
4688    */
4689   updateTabCloseCountState() {
4690     document.l10n.setAttributes(
4691       document.getElementById("menu_close"),
4692       "menu-file-close-tab",
4693       { tabCount: gBrowser.selectedTabs.length }
4694     );
4695   },
4697   onPopupShowing(event) {
4698     // We don't care about submenus:
4699     if (event.target.id != "menu_FilePopup") {
4700       return;
4701     }
4702     this.updateUserContextUIVisibility();
4703     this.updateImportCommandEnabledState();
4704     this.updateTabCloseCountState();
4705     if (AppConstants.platform == "macosx") {
4706       gShareUtils.updateShareURLMenuItem(
4707         gBrowser.selectedBrowser,
4708         document.getElementById("menu_savePage")
4709       );
4710     }
4711     PrintUtils.updatePrintSetupMenuHiddenState();
4712   },
4715 let gShareUtils = {
4716   /**
4717    * Updates a sharing item in a given menu, creating it if necessary.
4718    */
4719   updateShareURLMenuItem(browser, insertAfterEl) {
4720     if (!Services.prefs.getBoolPref("browser.menu.share_url.allow", true)) {
4721       return;
4722     }
4724     // We only support "share URL" on macOS and on Windows:
4725     if (AppConstants.platform != "macosx" && AppConstants.platform != "win") {
4726       return;
4727     }
4729     let shareURL = insertAfterEl.nextElementSibling;
4730     if (!shareURL?.matches(".share-tab-url-item")) {
4731       shareURL = this._createShareURLMenuItem(insertAfterEl);
4732     }
4734     shareURL.browserToShare = Cu.getWeakReference(browser);
4735     if (AppConstants.platform == "win") {
4736       // We disable the item on Windows, as there's no submenu.
4737       // On macOS, we handle this inside the menupopup.
4738       shareURL.hidden = !BrowserUtils.getShareableURL(browser.currentURI);
4739     }
4740   },
4742   /**
4743    * Creates and returns the "Share" menu item.
4744    */
4745   _createShareURLMenuItem(insertAfterEl) {
4746     let menu = insertAfterEl.parentNode;
4747     let shareURL = null;
4748     if (AppConstants.platform == "win") {
4749       shareURL = this._buildShareURLItem(menu.id);
4750     } else if (AppConstants.platform == "macosx") {
4751       shareURL = this._buildShareURLMenu(menu.id);
4752     }
4753     shareURL.className = "share-tab-url-item";
4755     let l10nID =
4756       menu.id == "tabContextMenu"
4757         ? "tab-context-share-url"
4758         : "menu-file-share-url";
4759     document.l10n.setAttributes(shareURL, l10nID);
4761     menu.insertBefore(shareURL, insertAfterEl.nextSibling);
4762     return shareURL;
4763   },
4765   /**
4766    * Returns a menu item specifically for accessing Windows sharing services.
4767    */
4768   _buildShareURLItem() {
4769     let shareURLMenuItem = document.createXULElement("menuitem");
4770     shareURLMenuItem.addEventListener("command", this);
4771     return shareURLMenuItem;
4772   },
4774   /**
4775    * Returns a menu specifically for accessing macOSx sharing services .
4776    */
4777   _buildShareURLMenu() {
4778     let menu = document.createXULElement("menu");
4779     let menuPopup = document.createXULElement("menupopup");
4780     menuPopup.addEventListener("popupshowing", this);
4781     menu.appendChild(menuPopup);
4782     return menu;
4783   },
4785   /**
4786    * Get the sharing data for a given DOM node.
4787    */
4788   getDataToShare(node) {
4789     let browser = node.browserToShare?.get();
4790     let urlToShare = null;
4791     let titleToShare = null;
4793     if (browser) {
4794       let maybeToShare = BrowserUtils.getShareableURL(browser.currentURI);
4795       if (maybeToShare) {
4796         urlToShare = maybeToShare;
4797         titleToShare = browser.contentTitle;
4798       }
4799     }
4800     return { urlToShare, titleToShare };
4801   },
4803   /**
4804    * Populates the "Share" menupopup on macOSx.
4805    */
4806   initializeShareURLPopup(menuPopup) {
4807     if (AppConstants.platform != "macosx") {
4808       return;
4809     }
4811     // Empty menupopup
4812     while (menuPopup.firstChild) {
4813       menuPopup.firstChild.remove();
4814     }
4816     let { urlToShare } = this.getDataToShare(menuPopup.parentNode);
4818     // If we can't share the current URL, we display the items disabled,
4819     // but enable the "more..." item at the bottom, to allow the user to
4820     // change sharing preferences in the system dialog.
4821     let shouldEnable = !!urlToShare;
4822     if (!urlToShare) {
4823       // Fake it so we can ask the sharing service for services:
4824       urlToShare = makeURI("https://mozilla.org/");
4825     }
4827     let sharingService = gBrowser.MacSharingService;
4828     let currentURI = gURLBar.makeURIReadable(urlToShare).displaySpec;
4829     let services = sharingService.getSharingProviders(currentURI);
4831     services.forEach(share => {
4832       let item = document.createXULElement("menuitem");
4833       item.classList.add("menuitem-iconic");
4834       item.setAttribute("label", share.menuItemTitle);
4835       item.setAttribute("share-name", share.name);
4836       item.setAttribute("image", share.image);
4837       if (!shouldEnable) {
4838         item.setAttribute("disabled", "true");
4839       }
4840       menuPopup.appendChild(item);
4841     });
4842     menuPopup.appendChild(document.createXULElement("menuseparator"));
4843     let moreItem = document.createXULElement("menuitem");
4844     document.l10n.setAttributes(moreItem, "menu-share-more");
4845     moreItem.classList.add("menuitem-iconic", "share-more-button");
4846     menuPopup.appendChild(moreItem);
4848     menuPopup.addEventListener("command", this);
4849     menuPopup.parentNode
4850       .closest("menupopup")
4851       .addEventListener("popuphiding", this);
4852     menuPopup.setAttribute("data-initialized", true);
4853   },
4855   onShareURLCommand(event) {
4856     // Only call sharing services for the "Share" menu item. These services
4857     // are accessed from a submenu popup for MacOS or the "Share" menu item
4858     // for Windows. Use .closest() as a hack to find either the item itself
4859     // or a parent with the right class.
4860     let target = event.target.closest(".share-tab-url-item");
4861     if (!target) {
4862       return;
4863     }
4865     // urlToShare/titleToShare may be null, in which case only the "more"
4866     // item is enabled, so handle that case first:
4867     if (event.target.classList.contains("share-more-button")) {
4868       gBrowser.MacSharingService.openSharingPreferences();
4869       return;
4870     }
4872     let { urlToShare, titleToShare } = this.getDataToShare(target);
4873     let currentURI = gURLBar.makeURIReadable(urlToShare).displaySpec;
4875     if (AppConstants.platform == "win") {
4876       WindowsUIUtils.shareUrl(currentURI, titleToShare);
4877       return;
4878     }
4880     // On macOSX platforms
4881     let shareName = event.target.getAttribute("share-name");
4883     if (shareName) {
4884       gBrowser.MacSharingService.shareUrl(shareName, currentURI, titleToShare);
4885     }
4886   },
4888   onPopupHiding(event) {
4889     // We don't want to rebuild the contents of the "Share" menupopup if only its submenu is
4890     // hidden. So bail if this isn't the top menupopup in the DOM tree:
4891     if (event.target.parentNode.closest("menupopup")) {
4892       return;
4893     }
4894     // Otherwise, clear its "data-initialized" attribute.
4895     let menupopup = event.target.querySelector(
4896       ".share-tab-url-item"
4897     )?.menupopup;
4898     menupopup?.removeAttribute("data-initialized");
4900     event.target.removeEventListener("popuphiding", this);
4901   },
4903   onPopupShowing(event) {
4904     if (!event.target.hasAttribute("data-initialized")) {
4905       this.initializeShareURLPopup(event.target);
4906     }
4907   },
4909   handleEvent(aEvent) {
4910     switch (aEvent.type) {
4911       case "command":
4912         this.onShareURLCommand(aEvent);
4913         break;
4914       case "popuphiding":
4915         this.onPopupHiding(aEvent);
4916         break;
4917       case "popupshowing":
4918         this.onPopupShowing(aEvent);
4919         break;
4920     }
4921   },
4925  * Opens a new tab with the userContextId specified as an attribute of
4926  * sourceEvent. This attribute is propagated to the top level originAttributes
4927  * living on the tab's docShell.
4929  * @param event
4930  *        A click event on a userContext File Menu option
4931  */
4932 function openNewUserContextTab(event) {
4933   openTrustedLinkIn(BROWSER_NEW_TAB_URL, "tab", {
4934     userContextId: parseInt(event.target.getAttribute("data-usercontextid")),
4935   });
4938 var XULBrowserWindow = {
4939   // Stored Status, Link and Loading values
4940   status: "",
4941   defaultStatus: "",
4942   overLink: "",
4943   startTime: 0,
4944   isBusy: false,
4945   busyUI: false,
4947   QueryInterface: ChromeUtils.generateQI([
4948     "nsIWebProgressListener",
4949     "nsIWebProgressListener2",
4950     "nsISupportsWeakReference",
4951     "nsIXULBrowserWindow",
4952   ]),
4954   get stopCommand() {
4955     delete this.stopCommand;
4956     return (this.stopCommand = document.getElementById("Browser:Stop"));
4957   },
4958   get reloadCommand() {
4959     delete this.reloadCommand;
4960     return (this.reloadCommand = document.getElementById("Browser:Reload"));
4961   },
4962   get _elementsForTextBasedTypes() {
4963     delete this._elementsForTextBasedTypes;
4964     return (this._elementsForTextBasedTypes = [
4965       document.getElementById("pageStyleMenu"),
4966       document.getElementById("context-viewpartialsource-selection"),
4967       document.getElementById("context-print-selection"),
4968     ]);
4969   },
4970   get _elementsForFind() {
4971     delete this._elementsForFind;
4972     return (this._elementsForFind = [
4973       document.getElementById("cmd_find"),
4974       document.getElementById("cmd_findAgain"),
4975       document.getElementById("cmd_findPrevious"),
4976     ]);
4977   },
4978   get _elementsForViewSource() {
4979     delete this._elementsForViewSource;
4980     return (this._elementsForViewSource = [
4981       document.getElementById("context-viewsource"),
4982       document.getElementById("View:PageSource"),
4983     ]);
4984   },
4985   get _menuItemForRepairTextEncoding() {
4986     delete this._menuItemForRepairTextEncoding;
4987     return (this._menuItemForRepairTextEncoding = document.getElementById(
4988       "repair-text-encoding"
4989     ));
4990   },
4991   get _menuItemForTranslations() {
4992     delete this._menuItemForTranslations;
4993     return (this._menuItemForTranslations =
4994       document.getElementById("cmd_translate"));
4995   },
4997   setDefaultStatus(status) {
4998     this.defaultStatus = status;
4999     StatusPanel.update();
5000   },
5002   /**
5003    * Tells the UI what link we are currently over.
5004    *
5005    * @param {String} url
5006    *   The URL of the link.
5007    * @param {Object} [options]
5008    *   This is an extension of nsIXULBrowserWindow for JS callers, will be
5009    *   passed on to LinkTargetDisplay.
5010    */
5011   setOverLink(url, options = undefined) {
5012     if (url) {
5013       url = Services.textToSubURI.unEscapeURIForUI(url);
5015       // Encode bidirectional formatting characters.
5016       // (RFC 3987 sections 3.2 and 4.1 paragraph 6)
5017       url = url.replace(
5018         /[\u200e\u200f\u202a\u202b\u202c\u202d\u202e]/g,
5019         encodeURIComponent
5020       );
5022       if (UrlbarPrefs.get("trimURLs")) {
5023         url = BrowserUIUtils.trimURL(url);
5024       }
5025     }
5027     this.overLink = url;
5028     LinkTargetDisplay.update(options);
5029   },
5031   onEnterDOMFullscreen() {
5032     // Clear the status panel.
5033     this.status = "";
5034     this.setDefaultStatus("");
5035     this.setOverLink("", { hideStatusPanelImmediately: true });
5036   },
5038   showTooltip(xDevPix, yDevPix, tooltip, direction, _browser) {
5039     if (
5040       Cc["@mozilla.org/widget/dragservice;1"]
5041         .getService(Ci.nsIDragService)
5042         .getCurrentSession()
5043     ) {
5044       return;
5045     }
5047     if (!document.hasFocus()) {
5048       return;
5049     }
5051     let elt = document.getElementById("remoteBrowserTooltip");
5052     elt.label = tooltip;
5053     elt.style.direction = direction;
5054     elt.openPopupAtScreen(
5055       xDevPix / window.devicePixelRatio,
5056       yDevPix / window.devicePixelRatio,
5057       false,
5058       null
5059     );
5060   },
5062   hideTooltip() {
5063     let elt = document.getElementById("remoteBrowserTooltip");
5064     elt.hidePopup();
5065   },
5067   getTabCount() {
5068     return gBrowser.tabs.length;
5069   },
5071   onProgressChange() {
5072     // Do nothing.
5073   },
5075   onProgressChange64(
5076     aWebProgress,
5077     aRequest,
5078     aCurSelfProgress,
5079     aMaxSelfProgress,
5080     aCurTotalProgress,
5081     aMaxTotalProgress
5082   ) {
5083     return this.onProgressChange(
5084       aWebProgress,
5085       aRequest,
5086       aCurSelfProgress,
5087       aMaxSelfProgress,
5088       aCurTotalProgress,
5089       aMaxTotalProgress
5090     );
5091   },
5093   // This function fires only for the currently selected tab.
5094   onStateChange(aWebProgress, aRequest, aStateFlags, aStatus) {
5095     const nsIWebProgressListener = Ci.nsIWebProgressListener;
5097     let browser = gBrowser.selectedBrowser;
5098     gProtectionsHandler.onStateChange(aWebProgress, aStateFlags);
5100     if (
5101       aStateFlags & nsIWebProgressListener.STATE_START &&
5102       aStateFlags & nsIWebProgressListener.STATE_IS_NETWORK
5103     ) {
5104       if (aRequest && aWebProgress.isTopLevel) {
5105         // clear out search-engine data
5106         browser.engines = null;
5107       }
5109       this.isBusy = true;
5111       if (
5112         !(aStateFlags & nsIWebProgressListener.STATE_RESTORING) &&
5113         aWebProgress.isTopLevel
5114       ) {
5115         this.busyUI = true;
5117         // XXX: This needs to be based on window activity...
5118         this.stopCommand.removeAttribute("disabled");
5119         CombinedStopReload.switchToStop(aRequest, aWebProgress);
5120       }
5121     } else if (aStateFlags & nsIWebProgressListener.STATE_STOP) {
5122       // This (thanks to the filter) is a network stop or the last
5123       // request stop outside of loading the document, stop throbbers
5124       // and progress bars and such
5125       if (aRequest) {
5126         let msg = "";
5127         let location;
5128         let canViewSource = true;
5129         // Get the URI either from a channel or a pseudo-object
5130         if (aRequest instanceof Ci.nsIChannel || "URI" in aRequest) {
5131           location = aRequest.URI;
5133           // For keyword URIs clear the user typed value since they will be changed into real URIs
5134           if (location.scheme == "keyword" && aWebProgress.isTopLevel) {
5135             gBrowser.userTypedValue = null;
5136           }
5138           canViewSource = location.scheme != "view-source";
5140           if (location.spec != "about:blank") {
5141             switch (aStatus) {
5142               case Cr.NS_ERROR_NET_TIMEOUT:
5143                 msg = gNavigatorBundle.getString("nv_timeout");
5144                 break;
5145             }
5146           }
5147         }
5149         this.status = "";
5150         this.setDefaultStatus(msg);
5152         // Disable View Source menu entries for images, enable otherwise
5153         let isText =
5154           browser.documentContentType &&
5155           BrowserUtils.mimeTypeIsTextBased(browser.documentContentType);
5156         for (let element of this._elementsForViewSource) {
5157           if (canViewSource && isText) {
5158             element.removeAttribute("disabled");
5159           } else {
5160             element.setAttribute("disabled", "true");
5161           }
5162         }
5164         this._updateElementsForContentType();
5166         // Update Override Text Encoding state.
5167         // Can't cache the button, because the presence of the element in the DOM
5168         // may change over time.
5169         let button = document.getElementById("characterencoding-button");
5170         if (browser.mayEnableCharacterEncodingMenu) {
5171           this._menuItemForRepairTextEncoding.removeAttribute("disabled");
5172           button?.removeAttribute("disabled");
5173         } else {
5174           this._menuItemForRepairTextEncoding.setAttribute("disabled", "true");
5175           button?.setAttribute("disabled", "true");
5176         }
5177       }
5179       this.isBusy = false;
5181       if (this.busyUI && aWebProgress.isTopLevel) {
5182         this.busyUI = false;
5184         this.stopCommand.setAttribute("disabled", "true");
5185         CombinedStopReload.switchToReload(aRequest, aWebProgress);
5186       }
5187     }
5188   },
5190   /**
5191    * An nsIWebProgressListener method called by tabbrowser.  The `aIsSimulated`
5192    * parameter is extra and not declared in nsIWebProgressListener, however; see
5193    * below.
5194    *
5195    * @param {nsIWebProgress} aWebProgress
5196    *   The nsIWebProgress instance that fired the notification.
5197    * @param {nsIRequest} aRequest
5198    *   The associated nsIRequest.  This may be null in some cases.
5199    * @param {nsIURI} aLocationURI
5200    *   The URI of the location that is being loaded.
5201    * @param {integer} aFlags
5202    *   Flags that indicate the reason the location changed.  See the
5203    *   nsIWebProgressListener.LOCATION_CHANGE_* values.
5204    * @param {boolean} aIsSimulated
5205    *   True when this is called by tabbrowser due to switching tabs and
5206    *   undefined otherwise.  This parameter is not declared in
5207    *   nsIWebProgressListener.onLocationChange; see bug 1478348.
5208    */
5209   onLocationChange(aWebProgress, aRequest, aLocationURI, aFlags, aIsSimulated) {
5210     var location = aLocationURI ? aLocationURI.spec : "";
5212     UpdateBackForwardCommands(gBrowser.webNavigation);
5214     Services.obs.notifyObservers(
5215       aWebProgress,
5216       "touchbar-location-change",
5217       location
5218     );
5220     // For most changes we only need to update the browser UI if the primary
5221     // content area was navigated or the selected tab was changed. We don't need
5222     // to do anything else if there was a subframe navigation.
5224     if (!aWebProgress.isTopLevel) {
5225       return;
5226     }
5228     this.setOverLink("", { hideStatusPanelImmediately: true });
5230     let isSameDocument =
5231       aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT;
5232     if (
5233       (location == "about:blank" &&
5234         BrowserUIUtils.checkEmptyPageOrigin(gBrowser.selectedBrowser)) ||
5235       location == ""
5236     ) {
5237       // Second condition is for new tabs, otherwise
5238       // reload function is enabled until tab is refreshed.
5239       this.reloadCommand.setAttribute("disabled", "true");
5240     } else {
5241       this.reloadCommand.removeAttribute("disabled");
5242     }
5244     let isSessionRestore = !!(
5245       aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SESSION_STORE
5246     );
5248     // We want to update the popup visibility if we received this notification
5249     // via simulated locationchange events such as switching between tabs, however
5250     // if this is a document navigation then PopupNotifications will be updated
5251     // via TabsProgressListener.onLocationChange and we do not want it called twice
5252     gURLBar.setURI(
5253       aLocationURI,
5254       aIsSimulated,
5255       isSessionRestore,
5256       false,
5257       isSameDocument
5258     );
5260     BookmarkingUI.onLocationChange();
5261     // If we've actually changed document, update the toolbar visibility.
5262     if (!isSameDocument) {
5263       updateBookmarkToolbarVisibility();
5264     }
5266     let closeOpenPanels = selector => {
5267       for (let panel of document.querySelectorAll(selector)) {
5268         if (panel.state != "closed") {
5269           panel.hidePopup();
5270         }
5271       }
5272     };
5274     // If the location is changed due to switching tabs,
5275     // ensure we close any open tabspecific panels.
5276     if (aIsSimulated) {
5277       closeOpenPanels("panel[tabspecific='true']");
5278     }
5280     // Ensure we close any remaining open locationspecific panels
5281     if (!isSameDocument) {
5282       closeOpenPanels("panel[locationspecific='true']");
5283     }
5285     let screenshotsButtonsDisabled =
5286       gScreenshots.shouldScreenshotsButtonBeDisabled();
5287     Services.obs.notifyObservers(
5288       window,
5289       "toggle-screenshot-disable",
5290       screenshotsButtonsDisabled
5291     );
5293     gPermissionPanel.onLocationChange();
5295     gProtectionsHandler.onLocationChange();
5297     BrowserPageActions.onLocationChange();
5299     SafeBrowsingNotificationBox.onLocationChange(aLocationURI);
5301     SaveToPocket.onLocationChange(window);
5303     let originalURI;
5304     if (aRequest instanceof Ci.nsIChannel) {
5305       originalURI = aRequest.originalURI;
5306     }
5308     UrlbarProviderSearchTips.onLocationChange(
5309       window,
5310       aLocationURI,
5311       originalURI,
5312       aWebProgress,
5313       aFlags
5314     );
5316     gTabletModePageCounter.inc();
5318     this._updateElementsForContentType();
5320     this._updateMacUserActivity(window, aLocationURI, aWebProgress);
5322     // Unconditionally disable the Text Encoding button during load to
5323     // keep the UI calm when navigating from one modern page to another and
5324     // the toolbar button is visible.
5325     // Can't cache the button, because the presence of the element in the DOM
5326     // may change over time.
5327     let button = document.getElementById("characterencoding-button");
5328     this._menuItemForRepairTextEncoding.setAttribute("disabled", "true");
5329     button?.setAttribute("disabled", "true");
5331     // Try not to instantiate gCustomizeMode as much as possible,
5332     // so don't use CustomizeMode.sys.mjs to check for URI or customizing.
5333     if (
5334       location == "about:blank" &&
5335       gBrowser.selectedTab.hasAttribute("customizemode")
5336     ) {
5337       gCustomizeMode.enter();
5338     } else if (
5339       CustomizationHandler.isEnteringCustomizeMode ||
5340       CustomizationHandler.isCustomizing()
5341     ) {
5342       gCustomizeMode.exit();
5343     }
5345     CFRPageActions.updatePageActions(gBrowser.selectedBrowser);
5347     AboutReaderParent.updateReaderButton(gBrowser.selectedBrowser);
5348     TranslationsParent.onLocationChange(gBrowser.selectedBrowser);
5350     PictureInPicture.updateUrlbarToggle(gBrowser.selectedBrowser);
5352     if (!gMultiProcessBrowser) {
5353       // Bug 1108553 - Cannot rotate images with e10s
5354       gGestureSupport.restoreRotationState();
5355     }
5357     // See bug 358202, when tabs are switched during a drag operation,
5358     // timers don't fire on windows (bug 203573)
5359     if (aRequest) {
5360       setTimeout(function () {
5361         XULBrowserWindow.asyncUpdateUI();
5362       }, 0);
5363     } else {
5364       this.asyncUpdateUI();
5365     }
5367     if (AppConstants.MOZ_CRASHREPORTER && aLocationURI) {
5368       let uri = aLocationURI;
5369       try {
5370         // If the current URI contains a username/password, remove it.
5371         uri = aLocationURI.mutate().setUserPass("").finalize();
5372       } catch (ex) {
5373         /* Ignore failures on about: URIs. */
5374       }
5376       try {
5377         Services.appinfo.annotateCrashReport("URL", uri.spec);
5378       } catch (ex) {
5379         // Don't make noise when the crash reporter is built but not enabled.
5380         if (ex.result != Cr.NS_ERROR_NOT_INITIALIZED) {
5381           throw ex;
5382         }
5383       }
5384     }
5385   },
5387   _updateElementsForContentType() {
5388     let browser = gBrowser.selectedBrowser;
5390     let isText =
5391       browser.documentContentType &&
5392       BrowserUtils.mimeTypeIsTextBased(browser.documentContentType);
5393     for (let element of this._elementsForTextBasedTypes) {
5394       if (isText) {
5395         element.removeAttribute("disabled");
5396       } else {
5397         element.setAttribute("disabled", "true");
5398       }
5399     }
5401     // Always enable find commands in PDF documents, otherwise do it only for
5402     // text documents whose location is not in the blacklist.
5403     let enableFind =
5404       browser.contentPrincipal?.spec == "resource://pdf.js/web/viewer.html" ||
5405       (isText && BrowserUtils.canFindInPage(gBrowser.currentURI.spec));
5406     for (let element of this._elementsForFind) {
5407       if (enableFind) {
5408         element.removeAttribute("disabled");
5409       } else {
5410         element.setAttribute("disabled", "true");
5411       }
5412     }
5414     if (TranslationsParent.isRestrictedPage(gBrowser)) {
5415       this._menuItemForTranslations.setAttribute("disabled", "true");
5416     } else {
5417       this._menuItemForTranslations.removeAttribute("disabled");
5418     }
5419     if (gTranslationsEnabled) {
5420       if (TranslationsParent.getIsTranslationsEngineSupported()) {
5421         this._menuItemForTranslations.removeAttribute("hidden");
5422       } else {
5423         this._menuItemForTranslations.setAttribute("hidden", "true");
5424       }
5425     } else {
5426       this._menuItemForTranslations.setAttribute("hidden", "true");
5427     }
5428   },
5430   /**
5431    * Updates macOS platform code with the current URI and page title.
5432    * From there, we update the current NSUserActivity, enabling Handoff to other
5433    * Apple devices.
5434    * @param {Window} window
5435    *   The window in which the navigation occurred.
5436    * @param {nsIURI} uri
5437    *   The URI pointing to the current page.
5438    * @param {nsIWebProgress} webProgress
5439    *   The nsIWebProgress instance that fired a onLocationChange notification.
5440    */
5441   _updateMacUserActivity(win, uri, webProgress) {
5442     if (!webProgress.isTopLevel || AppConstants.platform != "macosx") {
5443       return;
5444     }
5446     let url = uri.spec;
5447     if (PrivateBrowsingUtils.isWindowPrivate(win)) {
5448       // Passing an empty string to MacUserActivityUpdater will invalidate the
5449       // current user activity.
5450       url = "";
5451     }
5452     let baseWin = win.docShell.treeOwner.QueryInterface(Ci.nsIBaseWindow);
5453     MacUserActivityUpdater.updateLocation(
5454       url,
5455       win.gBrowser.contentTitle,
5456       baseWin
5457     );
5458   },
5460   /**
5461    * Potentially gets a URI for a MozBrowser to be shown to the user in the
5462    * identity panel. For browsers whose content does not have a principal,
5463    * this tries the precursor. If this is null, we should not override the
5464    * browser's currentURI.
5465    * @param {MozBrowser} browser
5466    *   The browser that we need a URI to show the user in the
5467    *   identity panel.
5468    * @return nsIURI of the principal for the browser's content if
5469    *   the browser's currentURI should not be used, null otherwise.
5470    */
5471   _securityURIOverride(browser) {
5472     let uri = browser.currentURI;
5473     if (!uri) {
5474       return null;
5475     }
5477     // If the browser's currentURI is sufficiently good that we
5478     // do not require an override, bail out here.
5479     // browser.currentURI should be used.
5480     let { URI_INHERITS_SECURITY_CONTEXT } = Ci.nsIProtocolHandler;
5481     if (
5482       !(doGetProtocolFlags(uri) & URI_INHERITS_SECURITY_CONTEXT) &&
5483       !(uri.scheme == "about" && uri.filePath == "srcdoc") &&
5484       !(uri.scheme == "about" && uri.filePath == "blank")
5485     ) {
5486       return null;
5487     }
5489     let principal = browser.contentPrincipal;
5491     if (principal.isNullPrincipal) {
5492       principal = principal.precursorPrincipal;
5493     }
5495     if (!principal) {
5496       return null;
5497     }
5499     // Can't get the original URI for a PDF viewer principal yet.
5500     if (principal.originNoSuffix == "resource://pdf.js") {
5501       return null;
5502     }
5504     return principal.URI;
5505   },
5507   asyncUpdateUI() {
5508     BrowserSearch.updateOpenSearchBadge();
5509   },
5511   onStatusChange(aWebProgress, aRequest, aStatus, aMessage) {
5512     this.status = aMessage;
5513     StatusPanel.update();
5514   },
5516   // Properties used to cache security state used to update the UI
5517   _state: null,
5518   _lastLocation: null,
5519   _event: null,
5520   _lastLocationForEvent: null,
5521   // _isSecureContext can change without the state/location changing, due to security
5522   // error pages that intercept certain loads. For example this happens sometimes
5523   // with the the HTTPS-Only Mode error page (more details in bug 1656027)
5524   _isSecureContext: null,
5526   // This is called in multiple ways:
5527   //  1. Due to the nsIWebProgressListener.onContentBlockingEvent notification.
5528   //  2. Called by tabbrowser.xml when updating the current browser.
5529   //  3. Called directly during this object's initializations.
5530   //  4. Due to the nsIWebProgressListener.onLocationChange notification.
5531   // aRequest will be null always in case 2 and 3, and sometimes in case 1 (for
5532   // instance, there won't be a request when STATE_BLOCKED_TRACKING_CONTENT or
5533   // other blocking events are observed).
5534   onContentBlockingEvent(aWebProgress, aRequest, aEvent, aIsSimulated) {
5535     // Don't need to do anything if the data we use to update the UI hasn't
5536     // changed
5537     let uri = gBrowser.currentURI;
5538     let spec = uri.spec;
5539     if (this._event == aEvent && this._lastLocationForEvent == spec) {
5540       return;
5541     }
5542     this._lastLocationForEvent = spec;
5544     if (
5545       typeof aIsSimulated != "boolean" &&
5546       typeof aIsSimulated != "undefined"
5547     ) {
5548       throw new Error(
5549         "onContentBlockingEvent: aIsSimulated receieved an unexpected type"
5550       );
5551     }
5553     gProtectionsHandler.onContentBlockingEvent(
5554       aEvent,
5555       aWebProgress,
5556       aIsSimulated,
5557       this._event // previous content blocking event
5558     );
5560     // We need the state of the previous content blocking event, so update
5561     // event after onContentBlockingEvent is called.
5562     this._event = aEvent;
5563   },
5565   // This is called in multiple ways:
5566   //  1. Due to the nsIWebProgressListener.onSecurityChange notification.
5567   //  2. Called by tabbrowser.xml when updating the current browser.
5568   //  3. Called directly during this object's initializations.
5569   // aRequest will be null always in case 2 and 3, and sometimes in case 1.
5570   onSecurityChange(aWebProgress, aRequest, aState, _aIsSimulated) {
5571     // Don't need to do anything if the data we use to update the UI hasn't
5572     // changed
5573     let uri = gBrowser.currentURI;
5574     let spec = uri.spec;
5575     let isSecureContext = gBrowser.securityUI.isSecureContext;
5576     if (
5577       this._state == aState &&
5578       this._lastLocation == spec &&
5579       this._isSecureContext === isSecureContext
5580     ) {
5581       // Switching to a tab of the same URL doesn't change most security
5582       // information, but tab specific permissions may be different.
5583       gIdentityHandler.refreshIdentityBlock();
5584       return;
5585     }
5586     this._state = aState;
5587     this._lastLocation = spec;
5588     this._isSecureContext = isSecureContext;
5590     // Make sure the "https" part of the URL is striked out or not,
5591     // depending on the current mixed active content blocking state.
5592     gURLBar.formatValue();
5594     // Update the identity panel, making sure we use the precursorPrincipal's
5595     // URI where appropriate, for example about:blank windows.
5596     let uriOverride = this._securityURIOverride(gBrowser.selectedBrowser);
5597     if (uriOverride) {
5598       uri = uriOverride;
5599       this._state |= Ci.nsIWebProgressListener.STATE_IDENTITY_ASSOCIATED;
5600     }
5602     try {
5603       uri = Services.io.createExposableURI(uri);
5604     } catch (e) {}
5605     gIdentityHandler.updateIdentity(this._state, uri);
5606   },
5608   // simulate all change notifications after switching tabs
5609   onUpdateCurrentBrowser: function XWB_onUpdateCurrentBrowser(
5610     aStateFlags,
5611     aStatus,
5612     aMessage,
5613     _aTotalProgress
5614   ) {
5615     if (FullZoom.updateBackgroundTabs) {
5616       FullZoom.onLocationChange(gBrowser.currentURI, true);
5617     }
5619     CombinedStopReload.onTabSwitch();
5621     // Docshell should normally take care of hiding the tooltip, but we need to do it
5622     // ourselves for tabswitches.
5623     this.hideTooltip();
5625     // Also hide tooltips for content loaded in the parent process:
5626     document.getElementById("aHTMLTooltip").hidePopup();
5628     var nsIWebProgressListener = Ci.nsIWebProgressListener;
5629     var loadingDone = aStateFlags & nsIWebProgressListener.STATE_STOP;
5630     // use a pseudo-object instead of a (potentially nonexistent) channel for getting
5631     // a correct error message - and make sure that the UI is always either in
5632     // loading (STATE_START) or done (STATE_STOP) mode
5633     this.onStateChange(
5634       gBrowser.webProgress,
5635       { URI: gBrowser.currentURI },
5636       loadingDone
5637         ? nsIWebProgressListener.STATE_STOP
5638         : nsIWebProgressListener.STATE_START,
5639       aStatus
5640     );
5641     // status message and progress value are undefined if we're done with loading
5642     if (loadingDone) {
5643       return;
5644     }
5645     this.onStatusChange(gBrowser.webProgress, null, 0, aMessage);
5646   },
5649 var LinkTargetDisplay = {
5650   get DELAY_SHOW() {
5651     delete this.DELAY_SHOW;
5652     return (this.DELAY_SHOW = Services.prefs.getIntPref(
5653       "browser.overlink-delay"
5654     ));
5655   },
5657   DELAY_HIDE: 250,
5658   _timer: 0,
5660   get _contextMenu() {
5661     delete this._contextMenu;
5662     return (this._contextMenu = document.getElementById(
5663       "contentAreaContextMenu"
5664     ));
5665   },
5667   update({ hideStatusPanelImmediately = false } = {}) {
5668     if (
5669       this._contextMenu.state == "open" ||
5670       this._contextMenu.state == "showing"
5671     ) {
5672       this._contextMenu.addEventListener("popuphidden", () => this.update(), {
5673         once: true,
5674       });
5675       return;
5676     }
5678     clearTimeout(this._timer);
5679     window.removeEventListener("mousemove", this, true);
5681     if (!XULBrowserWindow.overLink) {
5682       if (hideStatusPanelImmediately) {
5683         this._hide();
5684       } else {
5685         this._timer = setTimeout(this._hide.bind(this), this.DELAY_HIDE);
5686       }
5687       return;
5688     }
5690     if (StatusPanel.isVisible) {
5691       StatusPanel.update();
5692     } else {
5693       // Let the display appear when the mouse doesn't move within the delay
5694       this._showDelayed();
5695       window.addEventListener("mousemove", this, true);
5696     }
5697   },
5699   handleEvent(event) {
5700     switch (event.type) {
5701       case "mousemove":
5702         // Restart the delay since the mouse was moved
5703         clearTimeout(this._timer);
5704         this._showDelayed();
5705         break;
5706     }
5707   },
5709   _showDelayed() {
5710     this._timer = setTimeout(
5711       function (self) {
5712         StatusPanel.update();
5713         window.removeEventListener("mousemove", self, true);
5714       },
5715       this.DELAY_SHOW,
5716       this
5717     );
5718   },
5720   _hide() {
5721     clearTimeout(this._timer);
5723     StatusPanel.update();
5724   },
5727 var CombinedStopReload = {
5728   // Try to initialize. Returns whether initialization was successful, which
5729   // may mean we had already initialized.
5730   ensureInitialized() {
5731     if (this._initialized) {
5732       return true;
5733     }
5734     if (this._destroyed) {
5735       return false;
5736     }
5738     let reload = document.getElementById("reload-button");
5739     let stop = document.getElementById("stop-button");
5740     // It's possible the stop/reload buttons have been moved to the palette.
5741     // They may be reinserted later, so we will retry initialization if/when
5742     // we get notified of document loads.
5743     if (!stop || !reload) {
5744       return false;
5745     }
5747     this._initialized = true;
5748     if (XULBrowserWindow.stopCommand.getAttribute("disabled") != "true") {
5749       reload.setAttribute("displaystop", "true");
5750     }
5751     stop.addEventListener("click", this);
5753     // Removing attributes based on the observed command doesn't happen if the button
5754     // is in the palette when the command's attribute is removed (cf. bug 309953)
5755     for (let button of [stop, reload]) {
5756       if (button.hasAttribute("disabled")) {
5757         let command = document.getElementById(button.getAttribute("command"));
5758         if (!command.hasAttribute("disabled")) {
5759           button.removeAttribute("disabled");
5760         }
5761       }
5762     }
5764     this.reload = reload;
5765     this.stop = stop;
5766     this.stopReloadContainer = this.reload.parentNode;
5767     this.timeWhenSwitchedToStop = 0;
5769     this.stopReloadContainer.addEventListener("animationend", this);
5770     this.stopReloadContainer.addEventListener("animationcancel", this);
5772     return true;
5773   },
5775   uninit() {
5776     this._destroyed = true;
5778     if (!this._initialized) {
5779       return;
5780     }
5782     this._cancelTransition();
5783     this.stop.removeEventListener("click", this);
5784     this.stopReloadContainer.removeEventListener("animationend", this);
5785     this.stopReloadContainer.removeEventListener("animationcancel", this);
5786     this.stopReloadContainer = null;
5787     this.reload = null;
5788     this.stop = null;
5789   },
5791   handleEvent(event) {
5792     switch (event.type) {
5793       case "click":
5794         if (event.button == 0 && !this.stop.disabled) {
5795           this._stopClicked = true;
5796         }
5797         break;
5798       case "animationcancel":
5799       case "animationend": {
5800         if (
5801           event.target.classList.contains("toolbarbutton-animatable-image") &&
5802           (event.animationName == "reload-to-stop" ||
5803             event.animationName == "stop-to-reload")
5804         ) {
5805           this.stopReloadContainer.removeAttribute("animate");
5806         }
5807       }
5808     }
5809   },
5811   onTabSwitch() {
5812     // Reset the time in the event of a tabswitch since the stored time
5813     // would have been associated with the previous tab, so the animation will
5814     // still run if the page has been loading until long after the tab switch.
5815     this.timeWhenSwitchedToStop = window.performance.now();
5816   },
5818   switchToStop(aRequest, aWebProgress) {
5819     if (
5820       !this.ensureInitialized() ||
5821       !this._shouldSwitch(aRequest, aWebProgress)
5822     ) {
5823       return;
5824     }
5826     // Store the time that we switched to the stop button only if a request
5827     // is active. Requests are null if the switch is related to a tabswitch.
5828     // This is used to determine if we should show the stop->reload animation.
5829     if (aRequest instanceof Ci.nsIRequest) {
5830       this.timeWhenSwitchedToStop = window.performance.now();
5831     }
5833     let shouldAnimate =
5834       aRequest instanceof Ci.nsIRequest &&
5835       aWebProgress.isTopLevel &&
5836       aWebProgress.isLoadingDocument &&
5837       !gBrowser.tabAnimationsInProgress &&
5838       !gReduceMotion &&
5839       this.stopReloadContainer.closest("#nav-bar-customization-target");
5841     this._cancelTransition();
5842     if (shouldAnimate) {
5843       this.stopReloadContainer.setAttribute("animate", "true");
5844     } else {
5845       this.stopReloadContainer.removeAttribute("animate");
5846     }
5847     this.reload.setAttribute("displaystop", "true");
5848   },
5850   switchToReload(aRequest, aWebProgress) {
5851     if (!this.ensureInitialized() || !this.reload.hasAttribute("displaystop")) {
5852       return;
5853     }
5855     let shouldAnimate =
5856       aRequest instanceof Ci.nsIRequest &&
5857       aWebProgress.isTopLevel &&
5858       !aWebProgress.isLoadingDocument &&
5859       !gBrowser.tabAnimationsInProgress &&
5860       !gReduceMotion &&
5861       this._loadTimeExceedsMinimumForAnimation() &&
5862       this.stopReloadContainer.closest("#nav-bar-customization-target");
5864     if (shouldAnimate) {
5865       this.stopReloadContainer.setAttribute("animate", "true");
5866     } else {
5867       this.stopReloadContainer.removeAttribute("animate");
5868     }
5870     this.reload.removeAttribute("displaystop");
5872     if (!shouldAnimate || this._stopClicked) {
5873       this._stopClicked = false;
5874       this._cancelTransition();
5875       this.reload.disabled =
5876         XULBrowserWindow.reloadCommand.getAttribute("disabled") == "true";
5877       return;
5878     }
5880     if (this._timer) {
5881       return;
5882     }
5884     // Temporarily disable the reload button to prevent the user from
5885     // accidentally reloading the page when intending to click the stop button
5886     this.reload.disabled = true;
5887     this._timer = setTimeout(
5888       function (self) {
5889         self._timer = 0;
5890         self.reload.disabled =
5891           XULBrowserWindow.reloadCommand.getAttribute("disabled") == "true";
5892       },
5893       650,
5894       this
5895     );
5896   },
5898   _loadTimeExceedsMinimumForAnimation() {
5899     // If the time between switching to the stop button then switching to
5900     // the reload button exceeds 150ms, then we will show the animation.
5901     // If we don't know when we switched to stop (switchToStop is called
5902     // after init but before switchToReload), then we will prevent the
5903     // animation from occuring.
5904     return (
5905       this.timeWhenSwitchedToStop &&
5906       window.performance.now() - this.timeWhenSwitchedToStop > 150
5907     );
5908   },
5910   _shouldSwitch(aRequest, aWebProgress) {
5911     if (
5912       aRequest &&
5913       aRequest.originalURI &&
5914       (aRequest.originalURI.schemeIs("chrome") ||
5915         (aRequest.originalURI.schemeIs("about") &&
5916           aWebProgress.isTopLevel &&
5917           !aRequest.originalURI.spec.startsWith("about:reader")))
5918     ) {
5919       return false;
5920     }
5922     return true;
5923   },
5925   _cancelTransition() {
5926     if (this._timer) {
5927       clearTimeout(this._timer);
5928       this._timer = 0;
5929     }
5930   },
5933 var TabsProgressListener = {
5934   onStateChange(aBrowser, aWebProgress, aRequest, aStateFlags, aStatus) {
5935     // Collect telemetry data about tab load times.
5936     if (
5937       aWebProgress.isTopLevel &&
5938       (!aRequest.originalURI || aRequest.originalURI.scheme != "about")
5939     ) {
5940       let histogram = "FX_PAGE_LOAD_MS_2";
5941       let recordLoadTelemetry = true;
5943       if (aWebProgress.loadType & Ci.nsIDocShell.LOAD_CMD_RELOAD) {
5944         // loadType is constructed by shifting loadFlags, this is why we need to
5945         // do the same shifting here.
5946         // https://searchfox.org/mozilla-central/rev/11cfa0462a6b5d8c5e2111b8cfddcf78098f0141/docshell/base/nsDocShellLoadTypes.h#22
5947         if (aWebProgress.loadType & (kSkipCacheFlags << 16)) {
5948           histogram = "FX_PAGE_RELOAD_SKIP_CACHE_MS";
5949         } else if (aWebProgress.loadType == Ci.nsIDocShell.LOAD_CMD_RELOAD) {
5950           histogram = "FX_PAGE_RELOAD_NORMAL_MS";
5951         } else {
5952           recordLoadTelemetry = false;
5953         }
5954       }
5956       let stopwatchRunning = TelemetryStopwatch.running(histogram, aBrowser);
5957       if (aStateFlags & Ci.nsIWebProgressListener.STATE_IS_WINDOW) {
5958         if (aStateFlags & Ci.nsIWebProgressListener.STATE_START) {
5959           if (stopwatchRunning) {
5960             // Oops, we're seeing another start without having noticed the previous stop.
5961             if (recordLoadTelemetry) {
5962               TelemetryStopwatch.cancel(histogram, aBrowser);
5963             }
5964           }
5965           if (recordLoadTelemetry) {
5966             TelemetryStopwatch.start(histogram, aBrowser);
5967           }
5968           Services.telemetry.getHistogramById("FX_TOTAL_TOP_VISITS").add(true);
5969         } else if (
5970           aStateFlags & Ci.nsIWebProgressListener.STATE_STOP &&
5971           stopwatchRunning /* we won't see STATE_START events for pre-rendered tabs */
5972         ) {
5973           if (recordLoadTelemetry) {
5974             TelemetryStopwatch.finish(histogram, aBrowser);
5975             BrowserTelemetryUtils.recordSiteOriginTelemetry(browserWindows());
5976           }
5977         }
5978       } else if (
5979         aStateFlags & Ci.nsIWebProgressListener.STATE_STOP &&
5980         aStatus == Cr.NS_BINDING_ABORTED &&
5981         stopwatchRunning /* we won't see STATE_START events for pre-rendered tabs */
5982       ) {
5983         if (recordLoadTelemetry) {
5984           TelemetryStopwatch.cancel(histogram, aBrowser);
5985         }
5986       }
5987     }
5988   },
5990   onLocationChange(aBrowser, aWebProgress, aRequest, aLocationURI, aFlags) {
5991     // Filter out location changes in sub documents.
5992     if (!aWebProgress.isTopLevel) {
5993       return;
5994     }
5996     // Some shops use pushState to move between individual products, so
5997     // the shopping code needs to be told about all of these.
5998     ShoppingSidebarManager.onLocationChange(aBrowser, aLocationURI, aFlags);
6000     // Filter out location changes caused by anchor navigation
6001     // or history.push/pop/replaceState.
6002     if (aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT) {
6003       // Reader mode cares about history.pushState and friends.
6004       // FIXME: The content process should manage this directly (bug 1445351).
6005       aBrowser.sendMessageToActor(
6006         "Reader:PushState",
6007         {
6008           isArticle: aBrowser.isArticle,
6009         },
6010         "AboutReader"
6011       );
6012       return;
6013     }
6015     // Only need to call locationChange if the PopupNotifications object
6016     // for this window has already been initialized (i.e. its getter no
6017     // longer exists)
6018     if (!Object.getOwnPropertyDescriptor(window, "PopupNotifications").get) {
6019       PopupNotifications.locationChange(aBrowser);
6020     }
6022     let tab = gBrowser.getTabForBrowser(aBrowser);
6023     if (tab && tab._sharingState) {
6024       gBrowser.resetBrowserSharing(aBrowser);
6025     }
6027     gBrowser.readNotificationBox(aBrowser)?.removeTransientNotifications();
6029     FullZoom.onLocationChange(aLocationURI, false, aBrowser);
6030     CaptivePortalWatcher.onLocationChange(aBrowser);
6031   },
6033   onLinkIconAvailable(browser, dataURI, iconURI) {
6034     if (!iconURI) {
6035       return;
6036     }
6037     if (browser == gBrowser.selectedBrowser) {
6038       // If the "Add Search Engine" page action is in the urlbar, its image
6039       // needs to be set to the new icon, so call updateOpenSearchBadge.
6040       BrowserSearch.updateOpenSearchBadge();
6041     }
6042   },
6045 function nsBrowserAccess() {}
6047 nsBrowserAccess.prototype = {
6048   QueryInterface: ChromeUtils.generateQI(["nsIBrowserDOMWindow"]),
6050   _openURIInNewTab(
6051     aURI,
6052     aReferrerInfo,
6053     aIsPrivate,
6054     aIsExternal,
6055     aForceNotRemote = false,
6056     aUserContextId = Ci.nsIScriptSecurityManager.DEFAULT_USER_CONTEXT_ID,
6057     aOpenWindowInfo = null,
6058     aOpenerBrowser = null,
6059     aTriggeringPrincipal = null,
6060     aName = "",
6061     aCsp = null,
6062     aSkipLoad = false,
6063     aForceLoadInBackground = false
6064   ) {
6065     let win, needToFocusWin;
6067     // try the current window.  if we're in a popup, fall back on the most recent browser window
6068     if (window.toolbar.visible) {
6069       win = window;
6070     } else {
6071       win = BrowserWindowTracker.getTopWindow({ private: aIsPrivate });
6072       needToFocusWin = true;
6073     }
6075     if (!win) {
6076       // we couldn't find a suitable window, a new one needs to be opened.
6077       return null;
6078     }
6080     if (aIsExternal && (!aURI || aURI.spec == "about:blank")) {
6081       win.BrowserOpenTab(); // this also focuses the location bar
6082       win.focus();
6083       return win.gBrowser.selectedBrowser;
6084     }
6086     let loadInBackground = Services.prefs.getBoolPref(
6087       "browser.tabs.loadDivertedInBackground"
6088     );
6089     if (aForceLoadInBackground) {
6090       loadInBackground = true;
6091     }
6093     let tab = win.gBrowser.addTab(aURI ? aURI.spec : "about:blank", {
6094       triggeringPrincipal: aTriggeringPrincipal,
6095       referrerInfo: aReferrerInfo,
6096       userContextId: aUserContextId,
6097       fromExternal: aIsExternal,
6098       inBackground: loadInBackground,
6099       forceNotRemote: aForceNotRemote,
6100       openWindowInfo: aOpenWindowInfo,
6101       openerBrowser: aOpenerBrowser,
6102       name: aName,
6103       csp: aCsp,
6104       skipLoad: aSkipLoad,
6105     });
6106     let browser = win.gBrowser.getBrowserForTab(tab);
6108     if (needToFocusWin || (!loadInBackground && aIsExternal)) {
6109       win.focus();
6110     }
6112     return browser;
6113   },
6115   createContentWindow(
6116     aURI,
6117     aOpenWindowInfo,
6118     aWhere,
6119     aFlags,
6120     aTriggeringPrincipal,
6121     aCsp
6122   ) {
6123     return this.getContentWindowOrOpenURI(
6124       null,
6125       aOpenWindowInfo,
6126       aWhere,
6127       aFlags,
6128       aTriggeringPrincipal,
6129       aCsp,
6130       true
6131     );
6132   },
6134   openURI(aURI, aOpenWindowInfo, aWhere, aFlags, aTriggeringPrincipal, aCsp) {
6135     if (!aURI) {
6136       console.error("openURI should only be called with a valid URI");
6137       throw Components.Exception("", Cr.NS_ERROR_FAILURE);
6138     }
6139     return this.getContentWindowOrOpenURI(
6140       aURI,
6141       aOpenWindowInfo,
6142       aWhere,
6143       aFlags,
6144       aTriggeringPrincipal,
6145       aCsp,
6146       false
6147     );
6148   },
6150   getContentWindowOrOpenURI(
6151     aURI,
6152     aOpenWindowInfo,
6153     aWhere,
6154     aFlags,
6155     aTriggeringPrincipal,
6156     aCsp,
6157     aSkipLoad
6158   ) {
6159     var browsingContext = null;
6160     var isExternal = !!(aFlags & Ci.nsIBrowserDOMWindow.OPEN_EXTERNAL);
6161     var guessUserContextIdEnabled =
6162       isExternal &&
6163       !Services.prefs.getBoolPref(
6164         "browser.link.force_default_user_context_id_for_external_opens",
6165         false
6166       );
6167     var openingUserContextId =
6168       (guessUserContextIdEnabled &&
6169         URILoadingHelper.guessUserContextId(aURI)) ||
6170       Ci.nsIScriptSecurityManager.DEFAULT_USER_CONTEXT_ID;
6172     if (aOpenWindowInfo && isExternal) {
6173       console.error(
6174         "nsBrowserAccess.openURI did not expect aOpenWindowInfo to be " +
6175           "passed if the context is OPEN_EXTERNAL."
6176       );
6177       throw Components.Exception("", Cr.NS_ERROR_FAILURE);
6178     }
6180     if (isExternal && aURI && aURI.schemeIs("chrome")) {
6181       dump("use --chrome command-line option to load external chrome urls\n");
6182       return null;
6183     }
6185     if (aWhere == Ci.nsIBrowserDOMWindow.OPEN_DEFAULTWINDOW) {
6186       if (
6187         isExternal &&
6188         Services.prefs.prefHasUserValue(
6189           "browser.link.open_newwindow.override.external"
6190         )
6191       ) {
6192         aWhere = Services.prefs.getIntPref(
6193           "browser.link.open_newwindow.override.external"
6194         );
6195       } else {
6196         aWhere = Services.prefs.getIntPref("browser.link.open_newwindow");
6197       }
6198     }
6200     let referrerInfo;
6201     if (aFlags & Ci.nsIBrowserDOMWindow.OPEN_NO_REFERRER) {
6202       referrerInfo = new ReferrerInfo(Ci.nsIReferrerInfo.EMPTY, false, null);
6203     } else if (
6204       aOpenWindowInfo &&
6205       aOpenWindowInfo.parent &&
6206       aOpenWindowInfo.parent.window
6207     ) {
6208       referrerInfo = new ReferrerInfo(
6209         aOpenWindowInfo.parent.window.document.referrerInfo.referrerPolicy,
6210         true,
6211         makeURI(aOpenWindowInfo.parent.window.location.href)
6212       );
6213     } else {
6214       referrerInfo = new ReferrerInfo(Ci.nsIReferrerInfo.EMPTY, true, null);
6215     }
6217     let isPrivate = aOpenWindowInfo
6218       ? aOpenWindowInfo.originAttributes.privateBrowsingId != 0
6219       : PrivateBrowsingUtils.isWindowPrivate(window);
6221     switch (aWhere) {
6222       case Ci.nsIBrowserDOMWindow.OPEN_NEWWINDOW:
6223         // FIXME: Bug 408379. So how come this doesn't send the
6224         // referrer like the other loads do?
6225         var url = aURI && aURI.spec;
6226         let features = "all,dialog=no";
6227         if (isPrivate) {
6228           features += ",private";
6229         }
6230         // Pass all params to openDialog to ensure that "url" isn't passed through
6231         // loadOneOrMoreURIs, which splits based on "|"
6232         try {
6233           let extraOptions = Cc[
6234             "@mozilla.org/hash-property-bag;1"
6235           ].createInstance(Ci.nsIWritablePropertyBag2);
6236           extraOptions.setPropertyAsBool("fromExternal", isExternal);
6238           openDialog(
6239             AppConstants.BROWSER_CHROME_URL,
6240             "_blank",
6241             features,
6242             // window.arguments
6243             url,
6244             extraOptions,
6245             null,
6246             null,
6247             null,
6248             null,
6249             null,
6250             null,
6251             aTriggeringPrincipal,
6252             null,
6253             aCsp,
6254             aOpenWindowInfo
6255           );
6256           // At this point, the new browser window is just starting to load, and
6257           // hasn't created the content <browser> that we should return.
6258           // If the caller of this function is originating in C++, they can pass a
6259           // callback in nsOpenWindowInfo and it will be invoked when the browsing
6260           // context for a newly opened window is ready.
6261           browsingContext = null;
6262         } catch (ex) {
6263           console.error(ex);
6264         }
6265         break;
6266       case Ci.nsIBrowserDOMWindow.OPEN_NEWTAB:
6267       case Ci.nsIBrowserDOMWindow.OPEN_NEWTAB_BACKGROUND: {
6268         // If we have an opener, that means that the caller is expecting access
6269         // to the nsIDOMWindow of the opened tab right away. For e10s windows,
6270         // this means forcing the newly opened browser to be non-remote so that
6271         // we can hand back the nsIDOMWindow. DocumentLoadListener will do the
6272         // job of shuttling off the newly opened browser to run in the right
6273         // process once it starts loading a URI.
6274         let forceNotRemote = aOpenWindowInfo && !aOpenWindowInfo.isRemote;
6275         let userContextId = aOpenWindowInfo
6276           ? aOpenWindowInfo.originAttributes.userContextId
6277           : openingUserContextId;
6278         let forceLoadInBackground =
6279           aWhere == Ci.nsIBrowserDOMWindow.OPEN_NEWTAB_BACKGROUND;
6280         let browser = this._openURIInNewTab(
6281           aURI,
6282           referrerInfo,
6283           isPrivate,
6284           isExternal,
6285           forceNotRemote,
6286           userContextId,
6287           aOpenWindowInfo,
6288           aOpenWindowInfo?.parent?.top.embedderElement,
6289           aTriggeringPrincipal,
6290           "",
6291           aCsp,
6292           aSkipLoad,
6293           forceLoadInBackground
6294         );
6295         if (browser) {
6296           browsingContext = browser.browsingContext;
6297         }
6298         break;
6299       }
6300       case Ci.nsIBrowserDOMWindow.OPEN_PRINT_BROWSER: {
6301         let browser =
6302           PrintUtils.handleStaticCloneCreatedForPrint(aOpenWindowInfo);
6303         if (browser) {
6304           browsingContext = browser.browsingContext;
6305         }
6306         break;
6307       }
6308       default:
6309         // OPEN_CURRENTWINDOW or an illegal value
6310         browsingContext = window.gBrowser.selectedBrowser.browsingContext;
6311         if (aURI) {
6312           let loadFlags = Ci.nsIWebNavigation.LOAD_FLAGS_NONE;
6313           if (isExternal) {
6314             loadFlags |= Ci.nsIWebNavigation.LOAD_FLAGS_FROM_EXTERNAL;
6315           } else if (!aTriggeringPrincipal.isSystemPrincipal) {
6316             // XXX this code must be reviewed and changed when bug 1616353
6317             // lands.
6318             loadFlags |= Ci.nsIWebNavigation.LOAD_FLAGS_FIRST_LOAD;
6319           }
6320           // This should ideally be able to call loadURI with the actual URI.
6321           // However, that would bypass some styles of fixup (notably Windows
6322           // paths passed as "URI"s), so this needs some further thought. It
6323           // should be addressed in bug 1815509.
6324           gBrowser.fixupAndLoadURIString(aURI.spec, {
6325             triggeringPrincipal: aTriggeringPrincipal,
6326             csp: aCsp,
6327             loadFlags,
6328             referrerInfo,
6329           });
6330         }
6331         if (
6332           !Services.prefs.getBoolPref("browser.tabs.loadDivertedInBackground")
6333         ) {
6334           window.focus();
6335         }
6336     }
6337     return browsingContext;
6338   },
6340   createContentWindowInFrame: function browser_createContentWindowInFrame(
6341     aURI,
6342     aParams,
6343     aWhere,
6344     aFlags,
6345     aName
6346   ) {
6347     // Passing a null-URI to only create the content window,
6348     // and pass true for aSkipLoad to prevent loading of
6349     // about:blank
6350     return this.getContentWindowOrOpenURIInFrame(
6351       null,
6352       aParams,
6353       aWhere,
6354       aFlags,
6355       aName,
6356       true
6357     );
6358   },
6360   openURIInFrame: function browser_openURIInFrame(
6361     aURI,
6362     aParams,
6363     aWhere,
6364     aFlags,
6365     aName
6366   ) {
6367     return this.getContentWindowOrOpenURIInFrame(
6368       aURI,
6369       aParams,
6370       aWhere,
6371       aFlags,
6372       aName,
6373       false
6374     );
6375   },
6377   getContentWindowOrOpenURIInFrame:
6378     function browser_getContentWindowOrOpenURIInFrame(
6379       aURI,
6380       aParams,
6381       aWhere,
6382       aFlags,
6383       aName,
6384       aSkipLoad
6385     ) {
6386       if (aWhere == Ci.nsIBrowserDOMWindow.OPEN_PRINT_BROWSER) {
6387         return PrintUtils.handleStaticCloneCreatedForPrint(
6388           aParams.openWindowInfo
6389         );
6390       }
6392       if (
6393         aWhere != Ci.nsIBrowserDOMWindow.OPEN_NEWTAB &&
6394         aWhere != Ci.nsIBrowserDOMWindow.OPEN_NEWTAB_BACKGROUND
6395       ) {
6396         dump("Error: openURIInFrame can only open in new tabs or print");
6397         return null;
6398       }
6400       var isExternal = !!(aFlags & Ci.nsIBrowserDOMWindow.OPEN_EXTERNAL);
6402       var userContextId =
6403         aParams.openerOriginAttributes &&
6404         "userContextId" in aParams.openerOriginAttributes
6405           ? aParams.openerOriginAttributes.userContextId
6406           : Ci.nsIScriptSecurityManager.DEFAULT_USER_CONTEXT_ID;
6408       var forceLoadInBackground =
6409         aWhere == Ci.nsIBrowserDOMWindow.OPEN_NEWTAB_BACKGROUND;
6411       return this._openURIInNewTab(
6412         aURI,
6413         aParams.referrerInfo,
6414         aParams.isPrivate,
6415         isExternal,
6416         false,
6417         userContextId,
6418         aParams.openWindowInfo,
6419         aParams.openerBrowser,
6420         aParams.triggeringPrincipal,
6421         aName,
6422         aParams.csp,
6423         aSkipLoad,
6424         forceLoadInBackground
6425       );
6426     },
6428   canClose() {
6429     return CanCloseWindow();
6430   },
6432   get tabCount() {
6433     return gBrowser.tabs.length;
6434   },
6437 function showFullScreenViewContextMenuItems(popup) {
6438   for (let node of popup.querySelectorAll('[contexttype="fullscreen"]')) {
6439     node.hidden = !window.fullScreen;
6440   }
6441   let autoHide = popup.querySelector(".fullscreen-context-autohide");
6442   if (autoHide) {
6443     FullScreen.updateAutohideMenuitem(autoHide);
6444   }
6447 function onViewToolbarsPopupShowing(aEvent, aInsertPoint) {
6448   var popup = aEvent.target;
6449   if (popup != aEvent.currentTarget) {
6450     return;
6451   }
6453   // triggerNode can be a nested child element of a toolbaritem.
6454   let toolbarItem = popup.triggerNode;
6455   while (toolbarItem) {
6456     let localName = toolbarItem.localName;
6457     if (localName == "toolbar") {
6458       toolbarItem = null;
6459       break;
6460     }
6461     if (localName == "toolbarpaletteitem") {
6462       toolbarItem = toolbarItem.firstElementChild;
6463       break;
6464     }
6465     if (localName == "menupopup") {
6466       aEvent.preventDefault();
6467       aEvent.stopPropagation();
6468       return;
6469     }
6470     let parent = toolbarItem.parentElement;
6471     if (parent) {
6472       if (
6473         parent.classList.contains("customization-target") ||
6474         parent.getAttribute("overflowfortoolbar") || // Needs to work in the overflow list as well.
6475         parent.localName == "toolbarpaletteitem" ||
6476         parent.localName == "toolbar"
6477       ) {
6478         break;
6479       }
6480     }
6481     toolbarItem = parent;
6482   }
6484   // Empty the menu
6485   for (var i = popup.children.length - 1; i >= 0; --i) {
6486     var deadItem = popup.children[i];
6487     if (deadItem.hasAttribute("toolbarId")) {
6488       popup.removeChild(deadItem);
6489     }
6490   }
6492   MozXULElement.insertFTLIfNeeded("browser/toolbarContextMenu.ftl");
6493   let firstMenuItem = aInsertPoint || popup.firstElementChild;
6494   let toolbarNodes = gNavToolbox.querySelectorAll("toolbar");
6495   for (let toolbar of toolbarNodes) {
6496     if (!toolbar.hasAttribute("toolbarname")) {
6497       continue;
6498     }
6500     if (toolbar.id == "PersonalToolbar") {
6501       let menu = BookmarkingUI.buildBookmarksToolbarSubmenu(toolbar);
6502       popup.insertBefore(menu, firstMenuItem);
6503     } else {
6504       let menuItem = document.createXULElement("menuitem");
6505       menuItem.setAttribute("id", "toggle_" + toolbar.id);
6506       menuItem.setAttribute("toolbarId", toolbar.id);
6507       menuItem.setAttribute("type", "checkbox");
6508       menuItem.setAttribute("label", toolbar.getAttribute("toolbarname"));
6509       let hidingAttribute =
6510         toolbar.getAttribute("type") == "menubar" ? "autohide" : "collapsed";
6511       menuItem.setAttribute(
6512         "checked",
6513         toolbar.getAttribute(hidingAttribute) != "true"
6514       );
6515       menuItem.setAttribute("accesskey", toolbar.getAttribute("accesskey"));
6516       if (popup.id != "toolbar-context-menu") {
6517         menuItem.setAttribute("key", toolbar.getAttribute("key"));
6518       }
6520       popup.insertBefore(menuItem, firstMenuItem);
6521       menuItem.addEventListener("command", onViewToolbarCommand);
6522     }
6523   }
6525   let moveToPanel = popup.querySelector(".customize-context-moveToPanel");
6526   let removeFromToolbar = popup.querySelector(
6527     ".customize-context-removeFromToolbar"
6528   );
6529   // Show/hide fullscreen context menu items and set the
6530   // autohide item's checked state to mirror the autohide pref.
6531   showFullScreenViewContextMenuItems(popup);
6532   // View -> Toolbars menu doesn't have the moveToPanel or removeFromToolbar items.
6533   if (!moveToPanel || !removeFromToolbar) {
6534     return;
6535   }
6537   let showTabStripItems = toolbarItem?.id == "tabbrowser-tabs";
6538   for (let node of popup.querySelectorAll(
6539     'menuitem[contexttype="toolbaritem"]'
6540   )) {
6541     node.hidden = showTabStripItems;
6542   }
6544   for (let node of popup.querySelectorAll('menuitem[contexttype="tabbar"]')) {
6545     node.hidden = !showTabStripItems;
6546   }
6548   document
6549     .getElementById("toolbar-context-menu")
6550     .querySelectorAll("[data-lazy-l10n-id]")
6551     .forEach(el => {
6552       el.setAttribute("data-l10n-id", el.getAttribute("data-lazy-l10n-id"));
6553       el.removeAttribute("data-lazy-l10n-id");
6554     });
6556   // The "normal" toolbar items menu separator is hidden because it's unused
6557   // when hiding the "moveToPanel" and "removeFromToolbar" items on flexible
6558   // space items. But we need to ensure its hidden state is reset in the case
6559   // the context menu is subsequently opened on a non-flexible space item.
6560   let menuSeparator = document.getElementById("toolbarItemsMenuSeparator");
6561   menuSeparator.hidden = false;
6563   document.getElementById("toolbarNavigatorItemsMenuSeparator").hidden =
6564     !showTabStripItems;
6566   if (
6567     !CustomizationHandler.isCustomizing() &&
6568     CustomizableUI.isSpecialWidget(toolbarItem?.id || "")
6569   ) {
6570     moveToPanel.hidden = true;
6571     removeFromToolbar.hidden = true;
6572     menuSeparator.hidden = !showTabStripItems;
6573   }
6575   if (showTabStripItems) {
6576     let multipleTabsSelected = !!gBrowser.multiSelectedTabsCount;
6577     document.getElementById("toolbar-context-bookmarkSelectedTabs").hidden =
6578       !multipleTabsSelected;
6579     document.getElementById("toolbar-context-bookmarkSelectedTab").hidden =
6580       multipleTabsSelected;
6581     document.getElementById("toolbar-context-reloadSelectedTabs").hidden =
6582       !multipleTabsSelected;
6583     document.getElementById("toolbar-context-reloadSelectedTab").hidden =
6584       multipleTabsSelected;
6585     document.getElementById("toolbar-context-selectAllTabs").disabled =
6586       gBrowser.allTabsSelected();
6587     document.getElementById("toolbar-context-undoCloseTab").disabled =
6588       SessionStore.getClosedTabCount() == 0;
6589     return;
6590   }
6592   let movable =
6593     toolbarItem?.id && CustomizableUI.isWidgetRemovable(toolbarItem);
6594   if (movable) {
6595     if (CustomizableUI.isSpecialWidget(toolbarItem.id)) {
6596       moveToPanel.setAttribute("disabled", true);
6597     } else {
6598       moveToPanel.removeAttribute("disabled");
6599     }
6600     removeFromToolbar.removeAttribute("disabled");
6601   } else {
6602     moveToPanel.setAttribute("disabled", true);
6603     removeFromToolbar.setAttribute("disabled", true);
6604   }
6607 function onViewToolbarCommand(aEvent) {
6608   let node = aEvent.originalTarget;
6609   let menuId;
6610   let toolbarId;
6611   let isVisible;
6612   if (node.dataset.bookmarksToolbarVisibility) {
6613     isVisible = node.dataset.visibilityEnum;
6614     toolbarId = "PersonalToolbar";
6615     menuId = node.parentNode.parentNode.parentNode.id;
6616     Services.prefs.setCharPref(
6617       "browser.toolbars.bookmarks.visibility",
6618       isVisible
6619     );
6620   } else {
6621     menuId = node.parentNode.id;
6622     toolbarId = node.getAttribute("toolbarId");
6623     isVisible = node.getAttribute("checked") == "true";
6624   }
6625   CustomizableUI.setToolbarVisibility(toolbarId, isVisible);
6626   BrowserUsageTelemetry.recordToolbarVisibility(toolbarId, isVisible, menuId);
6629 function setToolbarVisibility(
6630   toolbar,
6631   isVisible,
6632   persist = true,
6633   animated = true
6634 ) {
6635   let hidingAttribute;
6636   if (toolbar.getAttribute("type") == "menubar") {
6637     hidingAttribute = "autohide";
6638     if (AppConstants.platform == "linux") {
6639       Services.prefs.setBoolPref("ui.key.menuAccessKeyFocuses", !isVisible);
6640     }
6641   } else {
6642     hidingAttribute = "collapsed";
6643   }
6645   if (toolbar == BookmarkingUI.toolbar) {
6646     // For the bookmarks toolbar, we need to persist state before toggling
6647     // the visibility in this window, because the state can be different
6648     // (newtab vs never or always) even when that won't change visibility
6649     // in this window.
6650     if (persist) {
6651       let prefValue;
6652       if (typeof isVisible == "string") {
6653         prefValue = isVisible;
6654       } else {
6655         prefValue = isVisible ? "always" : "never";
6656       }
6657       Services.prefs.setCharPref(
6658         "browser.toolbars.bookmarks.visibility",
6659         prefValue
6660       );
6661     }
6663     const overlapAttr = "BookmarksToolbarOverlapsBrowser";
6664     switch (isVisible) {
6665       case true:
6666       case "always":
6667         isVisible = true;
6668         document.documentElement.toggleAttribute(overlapAttr, false);
6669         break;
6670       case false:
6671       case "never":
6672         isVisible = false;
6673         document.documentElement.toggleAttribute(overlapAttr, false);
6674         break;
6675       case "newtab":
6676       default:
6677         let currentURI = gBrowser?.currentURI;
6678         if (!gBrowserInit.domContentLoaded) {
6679           let uriToLoad = gBrowserInit.uriToLoadPromise;
6680           if (uriToLoad) {
6681             if (Array.isArray(uriToLoad)) {
6682               // We only care about the first tab being loaded
6683               uriToLoad = uriToLoad[0];
6684             }
6685             try {
6686               currentURI = Services.io.newURI(uriToLoad);
6687             } catch (ex) {}
6688           }
6689         }
6690         isVisible = BookmarkingUI.isOnNewTabPage(currentURI);
6691         document.documentElement.toggleAttribute(overlapAttr, isVisible);
6692         break;
6693     }
6694   }
6696   if (toolbar.getAttribute(hidingAttribute) == (!isVisible).toString()) {
6697     // If this call will not result in a visibility change, return early
6698     // since dispatching toolbarvisibilitychange will cause views to get rebuilt.
6699     return;
6700   }
6702   toolbar.classList.toggle("instant", !animated);
6703   toolbar.setAttribute(hidingAttribute, !isVisible);
6704   // For the bookmarks toolbar, we will have saved state above. For other
6705   // toolbars, we need to do it after setting the attribute, or we might
6706   // save the wrong state.
6707   if (persist && toolbar.id != "PersonalToolbar") {
6708     Services.xulStore.persist(toolbar, hidingAttribute);
6709   }
6711   let eventParams = {
6712     detail: {
6713       visible: isVisible,
6714     },
6715     bubbles: true,
6716   };
6717   let event = new CustomEvent("toolbarvisibilitychange", eventParams);
6718   toolbar.dispatchEvent(event);
6721 function updateToggleControlLabel(control) {
6722   if (!control.hasAttribute("label-checked")) {
6723     return;
6724   }
6726   if (!control.hasAttribute("label-unchecked")) {
6727     control.setAttribute("label-unchecked", control.getAttribute("label"));
6728   }
6729   let prefix = control.getAttribute("checked") == "true" ? "" : "un";
6730   control.setAttribute("label", control.getAttribute(`label-${prefix}checked`));
6733 var TabletModeUpdater = {
6734   init() {
6735     if (AppConstants.platform == "win") {
6736       this.update(WindowsUIUtils.inTabletMode);
6737       Services.obs.addObserver(this, "tablet-mode-change");
6738     }
6739   },
6741   uninit() {
6742     if (AppConstants.platform == "win") {
6743       Services.obs.removeObserver(this, "tablet-mode-change");
6744     }
6745   },
6747   observe(subject, topic, data) {
6748     this.update(data == "tablet-mode");
6749   },
6751   update(isInTabletMode) {
6752     let wasInTabletMode = document.documentElement.hasAttribute("tabletmode");
6753     if (isInTabletMode) {
6754       document.documentElement.setAttribute("tabletmode", "true");
6755     } else {
6756       document.documentElement.removeAttribute("tabletmode");
6757     }
6758     if (wasInTabletMode != isInTabletMode) {
6759       gUIDensity.update();
6760     }
6761   },
6764 var gTabletModePageCounter = {
6765   enabled: false,
6766   inc() {
6767     this.enabled = AppConstants.platform == "win";
6768     if (!this.enabled) {
6769       this.inc = () => {};
6770       return;
6771     }
6772     this.inc = this._realInc;
6773     this.inc();
6774   },
6776   _desktopCount: 0,
6777   _tabletCount: 0,
6778   _realInc() {
6779     let inTabletMode = document.documentElement.hasAttribute("tabletmode");
6780     this[inTabletMode ? "_tabletCount" : "_desktopCount"]++;
6781   },
6783   finish() {
6784     if (this.enabled) {
6785       let histogram = Services.telemetry.getKeyedHistogramById(
6786         "FX_TABLETMODE_PAGE_LOAD"
6787       );
6788       histogram.add("tablet", this._tabletCount);
6789       histogram.add("desktop", this._desktopCount);
6790     }
6791   },
6794 function displaySecurityInfo() {
6795   BrowserPageInfo(null, "securityTab");
6798 // Updates the UI density (for touch and compact mode) based on the uidensity pref.
6799 var gUIDensity = {
6800   MODE_NORMAL: 0,
6801   MODE_COMPACT: 1,
6802   MODE_TOUCH: 2,
6803   uiDensityPref: "browser.uidensity",
6804   autoTouchModePref: "browser.touchmode.auto",
6806   init() {
6807     this.update();
6808     Services.prefs.addObserver(this.uiDensityPref, this);
6809     Services.prefs.addObserver(this.autoTouchModePref, this);
6810   },
6812   uninit() {
6813     Services.prefs.removeObserver(this.uiDensityPref, this);
6814     Services.prefs.removeObserver(this.autoTouchModePref, this);
6815   },
6817   observe(aSubject, aTopic, aPrefName) {
6818     if (
6819       aTopic != "nsPref:changed" ||
6820       (aPrefName != this.uiDensityPref && aPrefName != this.autoTouchModePref)
6821     ) {
6822       return;
6823     }
6825     this.update();
6826   },
6828   getCurrentDensity() {
6829     // Automatically override the uidensity to touch in Windows tablet mode.
6830     if (
6831       AppConstants.platform == "win" &&
6832       WindowsUIUtils.inTabletMode &&
6833       Services.prefs.getBoolPref(this.autoTouchModePref)
6834     ) {
6835       return { mode: this.MODE_TOUCH, overridden: true };
6836     }
6837     return {
6838       mode: Services.prefs.getIntPref(this.uiDensityPref),
6839       overridden: false,
6840     };
6841   },
6843   update(mode) {
6844     if (mode == null) {
6845       mode = this.getCurrentDensity().mode;
6846     }
6848     let docs = [document.documentElement];
6849     let shouldUpdateSidebar = SidebarUI.initialized && SidebarUI.isOpen;
6850     if (shouldUpdateSidebar) {
6851       docs.push(SidebarUI.browser.contentDocument.documentElement);
6852     }
6853     for (let doc of docs) {
6854       switch (mode) {
6855         case this.MODE_COMPACT:
6856           doc.setAttribute("uidensity", "compact");
6857           break;
6858         case this.MODE_TOUCH:
6859           doc.setAttribute("uidensity", "touch");
6860           break;
6861         default:
6862           doc.removeAttribute("uidensity");
6863           break;
6864       }
6865     }
6866     if (shouldUpdateSidebar) {
6867       let tree = SidebarUI.browser.contentDocument.querySelector(
6868         ".sidebar-placesTree"
6869       );
6870       if (tree) {
6871         // Tree items don't update their styles without changing some property on the
6872         // parent tree element, like background-color or border. See bug 1407399.
6873         tree.style.border = "1px";
6874         tree.style.border = "";
6875       }
6876     }
6878     gBrowser.tabContainer.uiDensityChanged();
6879     gURLBar.updateLayoutBreakout();
6880   },
6883 const nodeToTooltipMap = {
6884   "bookmarks-menu-button": "bookmarksMenuButton.tooltip",
6885   "context-reload": "reloadButton.tooltip",
6886   "context-stop": "stopButton.tooltip",
6887   "downloads-button": "downloads.tooltip",
6888   "fullscreen-button": "fullscreenButton.tooltip",
6889   "appMenu-fullscreen-button2": "fullscreenButton.tooltip",
6890   "new-window-button": "newWindowButton.tooltip",
6891   "new-tab-button": "newTabButton.tooltip",
6892   "tabs-newtab-button": "newTabButton.tooltip",
6893   "reload-button": "reloadButton.tooltip",
6894   "stop-button": "stopButton.tooltip",
6895   "urlbar-zoom-button": "urlbar-zoom-button.tooltip",
6896   "appMenu-zoomEnlarge-button2": "zoomEnlarge-button.tooltip",
6897   "appMenu-zoomReset-button2": "zoomReset-button.tooltip",
6898   "appMenu-zoomReduce-button2": "zoomReduce-button.tooltip",
6899   "reader-mode-button": "reader-mode-button.tooltip",
6900   "reader-mode-button-icon": "reader-mode-button.tooltip",
6902 const nodeToShortcutMap = {
6903   "bookmarks-menu-button": "manBookmarkKb",
6904   "context-reload": "key_reload",
6905   "context-stop": "key_stop",
6906   "downloads-button": "key_openDownloads",
6907   "fullscreen-button": "key_enterFullScreen",
6908   "appMenu-fullscreen-button2": "key_enterFullScreen",
6909   "new-window-button": "key_newNavigator",
6910   "new-tab-button": "key_newNavigatorTab",
6911   "tabs-newtab-button": "key_newNavigatorTab",
6912   "reload-button": "key_reload",
6913   "stop-button": "key_stop",
6914   "urlbar-zoom-button": "key_fullZoomReset",
6915   "appMenu-zoomEnlarge-button2": "key_fullZoomEnlarge",
6916   "appMenu-zoomReset-button2": "key_fullZoomReset",
6917   "appMenu-zoomReduce-button2": "key_fullZoomReduce",
6918   "reader-mode-button": "key_toggleReaderMode",
6919   "reader-mode-button-icon": "key_toggleReaderMode",
6922 const gDynamicTooltipCache = new Map();
6923 function GetDynamicShortcutTooltipText(nodeId) {
6924   if (!gDynamicTooltipCache.has(nodeId) && nodeId in nodeToTooltipMap) {
6925     let strId = nodeToTooltipMap[nodeId];
6926     let args = [];
6927     if (nodeId in nodeToShortcutMap) {
6928       let shortcutId = nodeToShortcutMap[nodeId];
6929       let shortcut = document.getElementById(shortcutId);
6930       if (shortcut) {
6931         args.push(ShortcutUtils.prettifyShortcut(shortcut));
6932       }
6933     }
6934     gDynamicTooltipCache.set(
6935       nodeId,
6936       gNavigatorBundle.getFormattedString(strId, args)
6937     );
6938   }
6939   return gDynamicTooltipCache.get(nodeId);
6942 function UpdateDynamicShortcutTooltipText(aTooltip) {
6943   let nodeId =
6944     aTooltip.triggerNode.id || aTooltip.triggerNode.getAttribute("anonid");
6945   aTooltip.setAttribute("label", GetDynamicShortcutTooltipText(nodeId));
6949  * - [ Dependencies ] ---------------------------------------------------------
6950  *  utilityOverlay.js:
6951  *    - gatherTextUnder
6952  */
6955  * Extracts linkNode and href for the current click target.
6957  * @param event
6958  *        The click event.
6959  * @return [href, linkNode].
6961  * @note linkNode will be null if the click wasn't on an anchor
6962  *       element (or XLink).
6963  */
6964 function hrefAndLinkNodeForClickEvent(event) {
6965   function isHTMLLink(aNode) {
6966     // Be consistent with what nsContextMenu.js does.
6967     return (
6968       (HTMLAnchorElement.isInstance(aNode) && aNode.href) ||
6969       (HTMLAreaElement.isInstance(aNode) && aNode.href) ||
6970       HTMLLinkElement.isInstance(aNode)
6971     );
6972   }
6974   let node = event.composedTarget;
6975   while (node && !isHTMLLink(node)) {
6976     node = node.flattenedTreeParentNode;
6977   }
6979   if (node) {
6980     return [node.href, node];
6981   }
6983   // If there is no linkNode, try simple XLink.
6984   let href, baseURI;
6985   node = event.composedTarget;
6986   while (node && !href) {
6987     if (
6988       node.nodeType == Node.ELEMENT_NODE &&
6989       (node.localName == "a" ||
6990         node.namespaceURI == "http://www.w3.org/1998/Math/MathML")
6991     ) {
6992       href =
6993         node.getAttribute("href") ||
6994         node.getAttributeNS("http://www.w3.org/1999/xlink", "href");
6996       if (href) {
6997         baseURI = node.baseURI;
6998         break;
6999       }
7000     }
7001     node = node.flattenedTreeParentNode;
7002   }
7004   // In case of XLink, we don't return the node we got href from since
7005   // callers expect <a>-like elements.
7006   return [href ? makeURLAbsolute(baseURI, href) : null, null];
7010  * Called whenever the user clicks in the content area.
7012  * @param event
7013  *        The click event.
7014  * @param isPanelClick
7015  *        Whether the event comes from an extension panel.
7016  * @note default event is prevented if the click is handled.
7017  */
7018 function contentAreaClick(event, isPanelClick) {
7019   if (!event.isTrusted || event.defaultPrevented || event.button != 0) {
7020     return;
7021   }
7023   let [href, linkNode] = hrefAndLinkNodeForClickEvent(event);
7024   if (!href) {
7025     // Not a link, handle middle mouse navigation.
7026     if (
7027       event.button == 1 &&
7028       Services.prefs.getBoolPref("middlemouse.contentLoadURL") &&
7029       !Services.prefs.getBoolPref("general.autoScroll")
7030     ) {
7031       middleMousePaste(event);
7032       event.preventDefault();
7033     }
7034     return;
7035   }
7037   // This code only applies if we have a linkNode (i.e. clicks on real anchor
7038   // elements, as opposed to XLink).
7039   if (
7040     linkNode &&
7041     event.button == 0 &&
7042     !event.ctrlKey &&
7043     !event.shiftKey &&
7044     !event.altKey &&
7045     !event.metaKey
7046   ) {
7047     // An extension panel's links should target the main content area.  Do this
7048     // if no modifier keys are down and if there's no target or the target
7049     // equals _main (the IE convention) or _content (the Mozilla convention).
7050     let target = linkNode.target;
7051     let mainTarget = !target || target == "_content" || target == "_main";
7052     if (isPanelClick && mainTarget) {
7053       // javascript and data links should be executed in the current browser.
7054       if (
7055         linkNode.getAttribute("onclick") ||
7056         href.startsWith("javascript:") ||
7057         href.startsWith("data:")
7058       ) {
7059         return;
7060       }
7062       try {
7063         urlSecurityCheck(href, linkNode.ownerDocument.nodePrincipal);
7064       } catch (ex) {
7065         // Prevent loading unsecure destinations.
7066         event.preventDefault();
7067         return;
7068       }
7070       openLinkIn(href, "current", {
7071         allowThirdPartyFixup: false,
7072       });
7073       event.preventDefault();
7074       return;
7075     }
7076   }
7078   handleLinkClick(event, href, linkNode);
7080   // Mark the page as a user followed link.  This is done so that history can
7081   // distinguish automatic embed visits from user activated ones.  For example
7082   // pages loaded in frames are embed visits and lost with the session, while
7083   // visits across frames should be preserved.
7084   try {
7085     if (!PrivateBrowsingUtils.isWindowPrivate(window)) {
7086       PlacesUIUtils.markPageAsFollowedLink(href);
7087     }
7088   } catch (ex) {
7089     /* Skip invalid URIs. */
7090   }
7094  * Handles clicks on links.
7096  * @return true if the click event was handled, false otherwise.
7097  */
7098 function handleLinkClick(event, href, linkNode) {
7099   if (event.button == 2) {
7100     // right click
7101     return false;
7102   }
7104   var where = whereToOpenLink(event);
7105   if (where == "current") {
7106     return false;
7107   }
7109   var doc = event.target.ownerDocument;
7110   let referrerInfo = Cc["@mozilla.org/referrer-info;1"].createInstance(
7111     Ci.nsIReferrerInfo
7112   );
7113   if (linkNode) {
7114     referrerInfo.initWithElement(linkNode);
7115   } else {
7116     referrerInfo.initWithDocument(doc);
7117   }
7119   if (where == "save") {
7120     saveURL(
7121       href,
7122       null,
7123       linkNode ? gatherTextUnder(linkNode) : "",
7124       null,
7125       true,
7126       true,
7127       referrerInfo,
7128       doc.cookieJarSettings,
7129       doc
7130     );
7131     event.preventDefault();
7132     return true;
7133   }
7135   let frameID = WebNavigationFrames.getFrameId(doc.defaultView);
7137   urlSecurityCheck(href, doc.nodePrincipal);
7138   let params = {
7139     charset: doc.characterSet,
7140     referrerInfo,
7141     originPrincipal: doc.nodePrincipal,
7142     originStoragePrincipal: doc.effectiveStoragePrincipal,
7143     triggeringPrincipal: doc.nodePrincipal,
7144     csp: doc.csp,
7145     frameID,
7146   };
7148   // The new tab/window must use the same userContextId
7149   if (doc.nodePrincipal.originAttributes.userContextId) {
7150     params.userContextId = doc.nodePrincipal.originAttributes.userContextId;
7151   }
7153   openLinkIn(href, where, params);
7154   event.preventDefault();
7155   return true;
7159  * Handles paste on middle mouse clicks.
7161  * @param event {Event | Object} Event or JSON object.
7162  */
7163 function middleMousePaste(event) {
7164   let clipboard = readFromClipboard();
7165   if (!clipboard) {
7166     return;
7167   }
7169   // Strip embedded newlines and surrounding whitespace, to match the URL
7170   // bar's behavior (stripsurroundingwhitespace)
7171   clipboard = clipboard.replace(/\s*\n\s*/g, "");
7173   clipboard = UrlbarUtils.stripUnsafeProtocolOnPaste(clipboard);
7175   // if it's not the current tab, we don't need to do anything because the
7176   // browser doesn't exist.
7177   let where = whereToOpenLink(event, true, false);
7178   let lastLocationChange;
7179   if (where == "current") {
7180     lastLocationChange = gBrowser.selectedBrowser.lastLocationChange;
7181   }
7183   UrlbarUtils.getShortcutOrURIAndPostData(clipboard).then(data => {
7184     try {
7185       makeURI(data.url);
7186     } catch (ex) {
7187       // Not a valid URI.
7188       return;
7189     }
7191     try {
7192       UrlbarUtils.addToUrlbarHistory(data.url, window);
7193     } catch (ex) {
7194       // Things may go wrong when adding url to session history,
7195       // but don't let that interfere with the loading of the url.
7196       console.error(ex);
7197     }
7199     if (
7200       where != "current" ||
7201       lastLocationChange == gBrowser.selectedBrowser.lastLocationChange
7202     ) {
7203       openUILink(data.url, event, {
7204         ignoreButton: true,
7205         allowInheritPrincipal: data.mayInheritPrincipal,
7206         triggeringPrincipal: gBrowser.selectedBrowser.contentPrincipal,
7207         csp: gBrowser.selectedBrowser.csp,
7208       });
7209     }
7210   });
7212   if (Event.isInstance(event)) {
7213     event.stopPropagation();
7214   }
7217 // handleDroppedLink has the following 2 overloads:
7218 //   handleDroppedLink(event, url, name, triggeringPrincipal)
7219 //   handleDroppedLink(event, links, triggeringPrincipal)
7220 function handleDroppedLink(
7221   event,
7222   urlOrLinks,
7223   nameOrTriggeringPrincipal,
7224   triggeringPrincipal
7225 ) {
7226   let links;
7227   if (Array.isArray(urlOrLinks)) {
7228     links = urlOrLinks;
7229     triggeringPrincipal = nameOrTriggeringPrincipal;
7230   } else {
7231     links = [{ url: urlOrLinks, nameOrTriggeringPrincipal, type: "" }];
7232   }
7234   let lastLocationChange = gBrowser.selectedBrowser.lastLocationChange;
7236   let userContextId = gBrowser.selectedBrowser.getAttribute("usercontextid");
7238   // event is null if links are dropped in content process.
7239   // inBackground should be false, as it's loading into current browser.
7240   let inBackground = false;
7241   if (event) {
7242     inBackground = Services.prefs.getBoolPref("browser.tabs.loadInBackground");
7243     if (event.shiftKey) {
7244       inBackground = !inBackground;
7245     }
7246   }
7248   (async function () {
7249     if (
7250       links.length >=
7251       Services.prefs.getIntPref("browser.tabs.maxOpenBeforeWarn")
7252     ) {
7253       // Sync dialog cannot be used inside drop event handler.
7254       let answer = await OpenInTabsUtils.promiseConfirmOpenInTabs(
7255         links.length,
7256         window
7257       );
7258       if (!answer) {
7259         return;
7260       }
7261     }
7263     let urls = [];
7264     let postDatas = [];
7265     for (let link of links) {
7266       let data = await UrlbarUtils.getShortcutOrURIAndPostData(link.url);
7267       urls.push(data.url);
7268       postDatas.push(data.postData);
7269     }
7270     if (lastLocationChange == gBrowser.selectedBrowser.lastLocationChange) {
7271       gBrowser.loadTabs(urls, {
7272         inBackground,
7273         replace: true,
7274         allowThirdPartyFixup: false,
7275         postDatas,
7276         userContextId,
7277         triggeringPrincipal,
7278       });
7279     }
7280   })();
7282   // If links are dropped in content process, event.preventDefault() should be
7283   // called in content process.
7284   if (event) {
7285     // Keep the event from being handled by the dragDrop listeners
7286     // built-in to gecko if they happen to be above us.
7287     event.preventDefault();
7288   }
7291 function BrowserForceEncodingDetection() {
7292   gBrowser.selectedBrowser.forceEncodingDetection();
7293   BrowserReloadWithFlags(Ci.nsIWebNavigation.LOAD_FLAGS_CHARSET_CHANGE);
7296 var ToolbarContextMenu = {
7297   updateDownloadsAutoHide(popup) {
7298     let checkbox = document.getElementById(
7299       "toolbar-context-autohide-downloads-button"
7300     );
7301     let isDownloads =
7302       popup.triggerNode &&
7303       ["downloads-button", "wrapper-downloads-button"].includes(
7304         popup.triggerNode.id
7305       );
7306     checkbox.hidden = !isDownloads;
7307     if (DownloadsButton.autoHideDownloadsButton) {
7308       checkbox.setAttribute("checked", "true");
7309     } else {
7310       checkbox.removeAttribute("checked");
7311     }
7312   },
7314   onDownloadsAutoHideChange(event) {
7315     let autoHide = event.target.getAttribute("checked") == "true";
7316     Services.prefs.setBoolPref("browser.download.autohideButton", autoHide);
7317   },
7319   updateDownloadsAlwaysOpenPanel(popup) {
7320     let separator = document.getElementById(
7321       "toolbarDownloadsAnchorMenuSeparator"
7322     );
7323     let checkbox = document.getElementById(
7324       "toolbar-context-always-open-downloads-panel"
7325     );
7326     let isDownloads =
7327       popup.triggerNode &&
7328       ["downloads-button", "wrapper-downloads-button"].includes(
7329         popup.triggerNode.id
7330       );
7331     separator.hidden = checkbox.hidden = !isDownloads;
7332     gAlwaysOpenPanel
7333       ? checkbox.setAttribute("checked", "true")
7334       : checkbox.removeAttribute("checked");
7335   },
7337   onDownloadsAlwaysOpenPanelChange(event) {
7338     let alwaysOpen = event.target.getAttribute("checked") == "true";
7339     Services.prefs.setBoolPref("browser.download.alwaysOpenPanel", alwaysOpen);
7340   },
7342   _getUnwrappedTriggerNode(popup) {
7343     // Toolbar buttons are wrapped in customize mode. Unwrap if necessary.
7344     let { triggerNode } = popup;
7345     if (triggerNode && gCustomizeMode.isWrappedToolbarItem(triggerNode)) {
7346       return triggerNode.firstElementChild;
7347     }
7348     return triggerNode;
7349   },
7351   _getExtensionId(popup) {
7352     let node = this._getUnwrappedTriggerNode(popup);
7353     return node && node.getAttribute("data-extensionid");
7354   },
7356   _getWidgetId(popup) {
7357     let node = this._getUnwrappedTriggerNode(popup);
7358     return node?.closest(".unified-extensions-item")?.id;
7359   },
7361   async updateExtension(popup, event) {
7362     let removeExtension = popup.querySelector(
7363       ".customize-context-removeExtension"
7364     );
7365     let manageExtension = popup.querySelector(
7366       ".customize-context-manageExtension"
7367     );
7368     let reportExtension = popup.querySelector(
7369       ".customize-context-reportExtension"
7370     );
7371     let pinToToolbar = popup.querySelector(".customize-context-pinToToolbar");
7372     let separator = reportExtension.nextElementSibling;
7373     let id = this._getExtensionId(popup);
7374     let addon = id && (await AddonManager.getAddonByID(id));
7376     for (let element of [removeExtension, manageExtension, separator]) {
7377       element.hidden = !addon;
7378     }
7380     if (pinToToolbar) {
7381       pinToToolbar.hidden = !addon;
7382     }
7384     reportExtension.hidden = !addon || !gAddonAbuseReportEnabled;
7386     if (addon) {
7387       popup.querySelector(".customize-context-moveToPanel").hidden = true;
7388       popup.querySelector(".customize-context-removeFromToolbar").hidden = true;
7390       if (pinToToolbar) {
7391         let widgetId = this._getWidgetId(popup);
7392         if (widgetId) {
7393           let area = CustomizableUI.getPlacementOfWidget(widgetId).area;
7394           let inToolbar = area != CustomizableUI.AREA_ADDONS;
7395           pinToToolbar.setAttribute("checked", inToolbar);
7396         }
7397       }
7399       removeExtension.disabled = !(
7400         addon.permissions & AddonManager.PERM_CAN_UNINSTALL
7401       );
7403       if (event?.target?.id === "toolbar-context-menu") {
7404         ExtensionsUI.originControlsMenu(popup, id);
7405       }
7406     }
7407   },
7409   async removeExtensionForContextAction(popup) {
7410     let id = this._getExtensionId(popup);
7411     await BrowserAddonUI.removeAddon(id, "browserAction");
7412   },
7414   async reportExtensionForContextAction(popup, reportEntryPoint) {
7415     let id = this._getExtensionId(popup);
7416     await BrowserAddonUI.reportAddon(id, reportEntryPoint);
7417   },
7419   async openAboutAddonsForContextAction(popup) {
7420     let id = this._getExtensionId(popup);
7421     await BrowserAddonUI.manageAddon(id, "browserAction");
7422   },
7425 // Note that this is also called from non-browser windows on OSX, which do
7426 // share menu items but not much else. See nonbrowser-mac.js.
7427 var BrowserOffline = {
7428   _inited: false,
7430   // BrowserOffline Public Methods
7431   init() {
7432     if (!this._uiElement) {
7433       this._uiElement = document.getElementById("cmd_toggleOfflineStatus");
7434     }
7436     Services.obs.addObserver(this, "network:offline-status-changed");
7438     this._updateOfflineUI(Services.io.offline);
7440     this._inited = true;
7441   },
7443   uninit() {
7444     if (this._inited) {
7445       Services.obs.removeObserver(this, "network:offline-status-changed");
7446     }
7447   },
7449   toggleOfflineStatus() {
7450     var ioService = Services.io;
7452     if (!ioService.offline && !this._canGoOffline()) {
7453       this._updateOfflineUI(false);
7454       return;
7455     }
7457     ioService.offline = !ioService.offline;
7458   },
7460   // nsIObserver
7461   observe(aSubject, aTopic) {
7462     if (aTopic != "network:offline-status-changed") {
7463       return;
7464     }
7466     // This notification is also received because of a loss in connectivity,
7467     // which we ignore by updating the UI to the current value of io.offline
7468     this._updateOfflineUI(Services.io.offline);
7469   },
7471   // BrowserOffline Implementation Methods
7472   _canGoOffline() {
7473     try {
7474       var cancelGoOffline = Cc["@mozilla.org/supports-PRBool;1"].createInstance(
7475         Ci.nsISupportsPRBool
7476       );
7477       Services.obs.notifyObservers(cancelGoOffline, "offline-requested");
7479       // Something aborted the quit process.
7480       if (cancelGoOffline.data) {
7481         return false;
7482       }
7483     } catch (ex) {}
7485     return true;
7486   },
7488   _uiElement: null,
7489   _updateOfflineUI(aOffline) {
7490     var offlineLocked = Services.prefs.prefIsLocked("network.online");
7491     if (offlineLocked) {
7492       this._uiElement.setAttribute("disabled", "true");
7493     }
7495     this._uiElement.setAttribute("checked", aOffline);
7496   },
7499 var CanvasPermissionPromptHelper = {
7500   _permissionsPrompt: "canvas-permissions-prompt",
7501   _permissionsPromptHideDoorHanger: "canvas-permissions-prompt-hide-doorhanger",
7502   _notificationIcon: "canvas-notification-icon",
7504   init() {
7505     Services.obs.addObserver(this, this._permissionsPrompt);
7506     Services.obs.addObserver(this, this._permissionsPromptHideDoorHanger);
7507   },
7509   uninit() {
7510     Services.obs.removeObserver(this, this._permissionsPrompt);
7511     Services.obs.removeObserver(this, this._permissionsPromptHideDoorHanger);
7512   },
7514   // aSubject is an nsIBrowser (e10s) or an nsIDOMWindow (non-e10s).
7515   // aData is an Origin string.
7516   observe(aSubject, aTopic, aData) {
7517     if (
7518       aTopic != this._permissionsPrompt &&
7519       aTopic != this._permissionsPromptHideDoorHanger
7520     ) {
7521       return;
7522     }
7524     let browser;
7525     if (aSubject instanceof Ci.nsIDOMWindow) {
7526       browser = aSubject.docShell.chromeEventHandler;
7527     } else {
7528       browser = aSubject;
7529     }
7531     if (gBrowser.selectedBrowser !== browser) {
7532       // Must belong to some other window.
7533       return;
7534     }
7536     let message = gNavigatorBundle.getFormattedString(
7537       "canvas.siteprompt2",
7538       ["<>"],
7539       1
7540     );
7542     let principal =
7543       Services.scriptSecurityManager.createContentPrincipalFromOrigin(aData);
7545     function setCanvasPermission(aPerm, aPersistent) {
7546       Services.perms.addFromPrincipal(
7547         principal,
7548         "canvas",
7549         aPerm,
7550         aPersistent
7551           ? Ci.nsIPermissionManager.EXPIRE_NEVER
7552           : Ci.nsIPermissionManager.EXPIRE_SESSION
7553       );
7554     }
7556     let mainAction = {
7557       label: gNavigatorBundle.getString("canvas.allow2"),
7558       accessKey: gNavigatorBundle.getString("canvas.allow2.accesskey"),
7559       callback(state) {
7560         setCanvasPermission(
7561           Ci.nsIPermissionManager.ALLOW_ACTION,
7562           state && state.checkboxChecked
7563         );
7564       },
7565     };
7567     let secondaryActions = [
7568       {
7569         label: gNavigatorBundle.getString("canvas.block"),
7570         accessKey: gNavigatorBundle.getString("canvas.block.accesskey"),
7571         callback(state) {
7572           setCanvasPermission(
7573             Ci.nsIPermissionManager.DENY_ACTION,
7574             state && state.checkboxChecked
7575           );
7576         },
7577       },
7578     ];
7580     let checkbox = {
7581       // In PB mode, we don't want the "always remember" checkbox
7582       show: !PrivateBrowsingUtils.isWindowPrivate(window),
7583     };
7584     if (checkbox.show) {
7585       checkbox.checked = true;
7586       checkbox.label = gBrowserBundle.GetStringFromName("canvas.remember2");
7587     }
7589     let options = {
7590       checkbox,
7591       name: principal.host,
7592       learnMoreURL:
7593         Services.urlFormatter.formatURLPref("app.support.baseURL") +
7594         "fingerprint-permission",
7595       dismissed: aTopic == this._permissionsPromptHideDoorHanger,
7596       eventCallback(e) {
7597         if (e == "showing") {
7598           this.browser.ownerDocument.getElementById(
7599             "canvas-permissions-prompt-warning"
7600           ).textContent = gBrowserBundle.GetStringFromName(
7601             "canvas.siteprompt2.warning"
7602           );
7603         }
7604       },
7605     };
7606     PopupNotifications.show(
7607       browser,
7608       this._permissionsPrompt,
7609       message,
7610       this._notificationIcon,
7611       mainAction,
7612       secondaryActions,
7613       options
7614     );
7615   },
7618 var WebAuthnPromptHelper = {
7619   _icon: "webauthn-notification-icon",
7620   _topic: "webauthn-prompt",
7622   // The current notification, if any. The U2F manager is a singleton, we will
7623   // never allow more than one active request. And thus we'll never have more
7624   // than one notification either.
7625   _current: null,
7627   // The current transaction ID. Will be checked when we're notified of the
7628   // cancellation of an ongoing WebAuthhn request.
7629   _tid: 0,
7631   // Translation object
7632   _l10n: null,
7634   init() {
7635     this._l10n = new Localization(["browser/webauthnDialog.ftl"], true);
7636     Services.obs.addObserver(this, this._topic);
7637   },
7639   uninit() {
7640     Services.obs.removeObserver(this, this._topic);
7641   },
7643   observe(aSubject, aTopic, aData) {
7644     switch (aTopic) {
7645       case "fullscreen-nav-toolbox":
7646         // Prevent the navigation toolbox from being hidden while a WebAuthn
7647         // prompt is visible.
7648         if (aData == "hidden" && this._tid != 0) {
7649           FullScreen.showNavToolbox();
7650         }
7651         return;
7652       case "fullscreen-painted":
7653         // Prevent DOM elements from going fullscreen while a WebAuthn
7654         // prompt is shown.
7655         if (this._tid != 0) {
7656           FullScreen.exitDomFullScreen();
7657         }
7658         return;
7659       case this._topic:
7660         break;
7661       default:
7662         return;
7663     }
7664     // aTopic is equal to this._topic
7666     let data = JSON.parse(aData);
7668     // If we receive a cancel, it might be a WebAuthn prompt starting in another
7669     // window, and the other window's browsing context will send out the
7670     // cancellations, so any cancel action we get should prompt us to cancel.
7671     if (data.prompt.type == "cancel") {
7672       this.cancel(data);
7673       return;
7674     }
7676     if (
7677       data.browsingContextId !== gBrowser.selectedBrowser.browsingContext.id
7678     ) {
7679       // Must belong to some other window.
7680       return;
7681     }
7683     let mgr = Cc["@mozilla.org/webauthn/service;1"].getService(
7684       Ci.nsIWebAuthnService
7685     );
7687     if (data.prompt.type == "presence") {
7688       this.presence_required(mgr, data);
7689     } else if (data.prompt.type == "register-direct") {
7690       this.registerDirect(mgr, data);
7691     } else if (data.prompt.type == "pin-required") {
7692       this.pin_required(mgr, false, data);
7693     } else if (data.prompt.type == "pin-invalid") {
7694       this.pin_required(mgr, true, data);
7695     } else if (data.prompt.type == "select-sign-result") {
7696       this.select_sign_result(mgr, data);
7697     } else if (data.prompt.type == "already-registered") {
7698       this.show_info(
7699         mgr,
7700         data.origin,
7701         data.tid,
7702         "alreadyRegistered",
7703         "webauthn.alreadyRegisteredPrompt"
7704       );
7705     } else if (data.prompt.type == "select-device") {
7706       this.show_info(
7707         mgr,
7708         data.origin,
7709         data.tid,
7710         "selectDevice",
7711         "webauthn.selectDevicePrompt"
7712       );
7713     } else if (data.prompt.type == "pin-auth-blocked") {
7714       this.show_info(
7715         mgr,
7716         data.origin,
7717         data.tid,
7718         "pinAuthBlocked",
7719         "webauthn.pinAuthBlockedPrompt"
7720       );
7721     } else if (data.prompt.type == "uv-blocked") {
7722       this.show_info(
7723         mgr,
7724         data.origin,
7725         data.tid,
7726         "uvBlocked",
7727         "webauthn.uvBlockedPrompt"
7728       );
7729     } else if (data.prompt.type == "uv-invalid") {
7730       let retriesLeft = data.prompt.retries;
7731       let dialogText;
7732       if (retriesLeft == 0) {
7733         // We can skip that because it will either be replaced
7734         // by uv-blocked or by PIN-prompt
7735         return;
7736       } else if (retriesLeft < 0) {
7737         dialogText = this._l10n.formatValueSync(
7738           "webauthn-uv-invalid-short-prompt"
7739         );
7740       } else {
7741         dialogText = this._l10n.formatValueSync(
7742           "webauthn-uv-invalid-long-prompt",
7743           { retriesLeft }
7744         );
7745       }
7746       let mainAction = this.buildCancelAction(mgr, data.tid);
7747       this.show_formatted_msg(data.tid, "uvInvalid", dialogText, mainAction);
7748     } else if (data.prompt.type == "device-blocked") {
7749       this.show_info(
7750         mgr,
7751         data.origin,
7752         data.tid,
7753         "deviceBlocked",
7754         "webauthn.deviceBlockedPrompt"
7755       );
7756     } else if (data.prompt.type == "pin-not-set") {
7757       this.show_info(
7758         mgr,
7759         data.origin,
7760         data.tid,
7761         "pinNotSet",
7762         "webauthn.pinNotSetPrompt"
7763       );
7764     }
7765   },
7767   prompt_for_password(origin, wasInvalid, retriesLeft, aPassword) {
7768     this.reset();
7769     let dialogText;
7770     if (!wasInvalid) {
7771       dialogText = this._l10n.formatValueSync("webauthn-pin-required-prompt");
7772     } else if (retriesLeft < 0 || retriesLeft > 3) {
7773       // The token will need to be power cycled after three incorrect attempts,
7774       // so we show a short error message that does not include retriesLeft. It
7775       // would be confusing to display retriesLeft at this point, as the user
7776       // will feel that they only get three attempts.
7777       dialogText = this._l10n.formatValueSync(
7778         "webauthn-pin-invalid-short-prompt"
7779       );
7780     } else {
7781       // The user is close to having their PIN permanently blocked. Show a more
7782       // severe warning that includes the retriesLeft counter.
7783       dialogText = this._l10n.formatValueSync(
7784         "webauthn-pin-invalid-long-prompt",
7785         { retriesLeft }
7786       );
7787     }
7789     let res = Services.prompt.promptPasswordBC(
7790       gBrowser.selectedBrowser.browsingContext,
7791       Services.prompt.MODAL_TYPE_TAB,
7792       origin,
7793       dialogText,
7794       aPassword
7795     );
7796     return res;
7797   },
7799   select_sign_result(mgr, { origin, tid, prompt: { entities } }) {
7800     let unknownAccount = this._l10n.formatValueSync(
7801       "webauthn-select-sign-result-unknown-account"
7802     );
7803     let secondaryActions = [];
7804     for (let i = 0; i < entities.length; i++) {
7805       let label = entities[i].name ?? unknownAccount;
7806       secondaryActions.push({
7807         label,
7808         accessKey: i.toString(),
7809         callback() {
7810           mgr.selectionCallback(tid, i);
7811         },
7812       });
7813     }
7814     let mainAction = this.buildCancelAction(mgr, tid);
7815     let options = { escAction: "buttoncommand" };
7816     this.show(
7817       tid,
7818       "select-sign-result",
7819       "webauthn.selectSignResultPrompt",
7820       origin,
7821       mainAction,
7822       secondaryActions,
7823       options
7824     );
7825   },
7827   pin_required(mgr, wasInvalid, { origin, tid, prompt: { retries } }) {
7828     let aPassword = Object.create(null); // create a "null" object
7829     let res = this.prompt_for_password(origin, wasInvalid, retries, aPassword);
7830     if (res) {
7831       mgr.pinCallback(tid, aPassword.value);
7832     } else {
7833       mgr.cancel(tid);
7834     }
7835   },
7837   presence_required(mgr, { origin, tid }) {
7838     let mainAction = this.buildCancelAction(mgr, tid);
7839     let options = { escAction: "buttoncommand" };
7840     let secondaryActions = [];
7841     let message = "webauthn.userPresencePrompt";
7842     this.show(
7843       tid,
7844       "presence",
7845       message,
7846       origin,
7847       mainAction,
7848       secondaryActions,
7849       options
7850     );
7851   },
7853   registerDirect(mgr, { origin, tid }) {
7854     let mainAction = this.buildProceedAction(mgr, tid);
7855     let secondaryActions = [this.buildCancelAction(mgr, tid)];
7857     let learnMoreURL =
7858       Services.urlFormatter.formatURLPref("app.support.baseURL") +
7859       "webauthn-direct-attestation";
7861     let options = {
7862       learnMoreURL,
7863       checkbox: {
7864         label: gNavigatorBundle.getString("webauthn.anonymize"),
7865       },
7866       hintText: "webauthn.registerDirectPromptHint",
7867     };
7868     this.show(
7869       tid,
7870       "register-direct",
7871       "webauthn.registerDirectPrompt3",
7872       origin,
7873       mainAction,
7874       secondaryActions,
7875       options
7876     );
7877   },
7879   show_info(mgr, origin, tid, id, stringId) {
7880     let mainAction = this.buildCancelAction(mgr, tid);
7881     this.show(tid, id, stringId, origin, mainAction);
7882   },
7884   show(
7885     tid,
7886     id,
7887     stringId,
7888     origin,
7889     mainAction,
7890     secondaryActions = [],
7891     options = {}
7892   ) {
7893     let brandShortName = document
7894       .getElementById("bundle_brand")
7895       .getString("brandShortName");
7896     let message = gNavigatorBundle.getFormattedString(stringId, [
7897       "<>",
7898       brandShortName,
7899     ]);
7901     try {
7902       origin = Services.io.newURI(origin).asciiHost;
7903     } catch (e) {
7904       /* Might fail for arbitrary U2F RP IDs. */
7905     }
7906     options.name = origin;
7907     this.show_formatted_msg(
7908       tid,
7909       id,
7910       message,
7911       mainAction,
7912       secondaryActions,
7913       options
7914     );
7915   },
7917   show_formatted_msg(
7918     tid,
7919     id,
7920     message,
7921     mainAction,
7922     secondaryActions = [],
7923     options = {}
7924   ) {
7925     this.reset();
7926     this._tid = tid;
7928     // We need to prevent some fullscreen transitions while WebAuthn prompts
7929     // are shown. The `fullscreen-painted` topic is notified when DOM elements
7930     // go fullscreen.
7931     Services.obs.addObserver(this, "fullscreen-painted");
7933     // The `fullscreen-nav-toolbox` topic is notified when the nav toolbox is
7934     // hidden.
7935     Services.obs.addObserver(this, "fullscreen-nav-toolbox");
7937     // Ensure that no DOM elements are already fullscreen.
7938     FullScreen.exitDomFullScreen();
7940     // Ensure that the nav toolbox is being shown.
7941     if (window.fullScreen) {
7942       FullScreen.showNavToolbox();
7943     }
7945     let brandShortName = document
7946       .getElementById("bundle_brand")
7947       .getString("brandShortName");
7948     if (options.hintText) {
7949       options.hintText = gNavigatorBundle.getFormattedString(options.hintText, [
7950         brandShortName,
7951       ]);
7952     }
7954     options.hideClose = true;
7955     options.persistent = true;
7956     options.eventCallback = event => {
7957       if (event == "removed") {
7958         Services.obs.removeObserver(this, "fullscreen-painted");
7959         Services.obs.removeObserver(this, "fullscreen-nav-toolbox");
7960         this._current = null;
7961         this._tid = 0;
7962       }
7963     };
7965     this._current = PopupNotifications.show(
7966       gBrowser.selectedBrowser,
7967       `webauthn-prompt-${id}`,
7968       message,
7969       this._icon,
7970       mainAction,
7971       secondaryActions,
7972       options
7973     );
7974   },
7976   cancel({ tid }) {
7977     if (this._tid == tid) {
7978       this.reset();
7979     }
7980   },
7982   reset() {
7983     if (this._current) {
7984       this._current.remove();
7985     }
7986   },
7988   buildProceedAction(mgr, tid) {
7989     return {
7990       label: gNavigatorBundle.getString("webauthn.proceed"),
7991       accessKey: gNavigatorBundle.getString("webauthn.proceed.accesskey"),
7992       callback(state) {
7993         mgr.resumeMakeCredential(tid, state.checkboxChecked);
7994       },
7995     };
7996   },
7998   buildCancelAction(mgr, tid) {
7999     return {
8000       label: gNavigatorBundle.getString("webauthn.cancel"),
8001       accessKey: gNavigatorBundle.getString("webauthn.cancel.accesskey"),
8002       callback() {
8003         mgr.cancel(tid);
8004       },
8005     };
8006   },
8009 function CanCloseWindow() {
8010   // Avoid redundant calls to canClose from showing multiple
8011   // PermitUnload dialogs.
8012   if (Services.startup.shuttingDown || window.skipNextCanClose) {
8013     return true;
8014   }
8016   for (let browser of gBrowser.browsers) {
8017     // Don't instantiate lazy browsers.
8018     if (!browser.isConnected) {
8019       continue;
8020     }
8022     let { permitUnload } = browser.permitUnload();
8023     if (!permitUnload) {
8024       return false;
8025     }
8026   }
8027   return true;
8030 function WindowIsClosing(event) {
8031   let source;
8032   if (event) {
8033     let target = event.sourceEvent?.target;
8034     if (target?.id?.startsWith("menu_")) {
8035       source = "menuitem";
8036     } else if (target?.nodeName == "toolbarbutton") {
8037       source = "close-button";
8038     } else {
8039       let key = AppConstants.platform == "macosx" ? "metaKey" : "ctrlKey";
8040       source = event[key] ? "shortcut" : "OS";
8041     }
8042   }
8043   if (!closeWindow(false, warnAboutClosingWindow, source)) {
8044     return false;
8045   }
8047   // In theory we should exit here and the Window's internal Close
8048   // method should trigger canClose on nsBrowserAccess. However, by
8049   // that point it's too late to be able to show a prompt for
8050   // PermitUnload. So we do it here, when we still can.
8051   if (CanCloseWindow()) {
8052     // This flag ensures that the later canClose call does nothing.
8053     // It's only needed to make tests pass, since they detect the
8054     // prompt even when it's not actually shown.
8055     window.skipNextCanClose = true;
8056     return true;
8057   }
8059   return false;
8063  * Checks if this is the last full *browser* window around. If it is, this will
8064  * be communicated like quitting. Otherwise, we warn about closing multiple tabs.
8066  * @param source where the request to close came from (used for telemetry)
8067  * @returns true if closing can proceed, false if it got cancelled.
8068  */
8069 function warnAboutClosingWindow(source) {
8070   // Popups aren't considered full browser windows; we also ignore private windows.
8071   let isPBWindow =
8072     PrivateBrowsingUtils.isWindowPrivate(window) &&
8073     !PrivateBrowsingUtils.permanentPrivateBrowsing;
8075   if (!isPBWindow && !toolbar.visible) {
8076     return gBrowser.warnAboutClosingTabs(
8077       gBrowser.visibleTabs.length,
8078       gBrowser.closingTabsEnum.ALL,
8079       source
8080     );
8081   }
8083   // Figure out if there's at least one other browser window around.
8084   let otherPBWindowExists = false;
8085   let otherWindowExists = false;
8086   for (let win of browserWindows()) {
8087     if (!win.closed && win != window) {
8088       otherWindowExists = true;
8089       if (isPBWindow && PrivateBrowsingUtils.isWindowPrivate(win)) {
8090         otherPBWindowExists = true;
8091       }
8092       // If the current window is not in private browsing mode we don't need to
8093       // look for other pb windows, we can leave the loop when finding the
8094       // first non-popup window. If however the current window is in private
8095       // browsing mode then we need at least one other pb and one non-popup
8096       // window to break out early.
8097       if (!isPBWindow || otherPBWindowExists) {
8098         break;
8099       }
8100     }
8101   }
8103   if (isPBWindow && !otherPBWindowExists) {
8104     let exitingCanceled = Cc["@mozilla.org/supports-PRBool;1"].createInstance(
8105       Ci.nsISupportsPRBool
8106     );
8107     exitingCanceled.data = false;
8108     Services.obs.notifyObservers(exitingCanceled, "last-pb-context-exiting");
8109     if (exitingCanceled.data) {
8110       return false;
8111     }
8112   }
8114   if (otherWindowExists) {
8115     return (
8116       isPBWindow ||
8117       gBrowser.warnAboutClosingTabs(
8118         gBrowser.visibleTabs.length,
8119         gBrowser.closingTabsEnum.ALL,
8120         source
8121       )
8122     );
8123   }
8125   let os = Services.obs;
8127   let closingCanceled = Cc["@mozilla.org/supports-PRBool;1"].createInstance(
8128     Ci.nsISupportsPRBool
8129   );
8130   os.notifyObservers(closingCanceled, "browser-lastwindow-close-requested");
8131   if (closingCanceled.data) {
8132     return false;
8133   }
8135   os.notifyObservers(null, "browser-lastwindow-close-granted");
8137   // OS X doesn't quit the application when the last window is closed, but keeps
8138   // the session alive. Hence don't prompt users to save tabs, but warn about
8139   // closing multiple tabs.
8140   return (
8141     AppConstants.platform != "macosx" ||
8142     isPBWindow ||
8143     gBrowser.warnAboutClosingTabs(
8144       gBrowser.visibleTabs.length,
8145       gBrowser.closingTabsEnum.ALL,
8146       source
8147     )
8148   );
8151 var MailIntegration = {
8152   sendLinkForBrowser(aBrowser) {
8153     this.sendMessage(
8154       gURLBar.makeURIReadable(aBrowser.currentURI).displaySpec,
8155       aBrowser.contentTitle
8156     );
8157   },
8159   sendMessage(aBody, aSubject) {
8160     // generate a mailto url based on the url and the url's title
8161     var mailtoUrl = "mailto:";
8162     if (aBody) {
8163       mailtoUrl += "?body=" + encodeURIComponent(aBody);
8164       mailtoUrl += "&subject=" + encodeURIComponent(aSubject);
8165     }
8167     var uri = makeURI(mailtoUrl);
8169     // now pass this uri to the operating system
8170     this._launchExternalUrl(uri);
8171   },
8173   // a generic method which can be used to pass arbitrary urls to the operating
8174   // system.
8175   // aURL --> a nsIURI which represents the url to launch
8176   _launchExternalUrl(aURL) {
8177     var extProtocolSvc = Cc[
8178       "@mozilla.org/uriloader/external-protocol-service;1"
8179     ].getService(Ci.nsIExternalProtocolService);
8180     if (extProtocolSvc) {
8181       extProtocolSvc.loadURI(
8182         aURL,
8183         Services.scriptSecurityManager.getSystemPrincipal()
8184       );
8185     }
8186   },
8190  * Open about:addons page by given view id.
8191  * @param {String} aView
8192  *                 View id of page that will open.
8193  *                 e.g. "addons://discover/"
8194  * @param {Object} options
8195  *        {
8196  *          selectTabByViewId: If true, if there is the tab opening page having
8197  *                             same view id, select the tab. Else if the current
8198  *                             page is blank, load on it. Otherwise, open a new
8199  *                             tab, then load on it.
8200  *                             If false, if there is the tab opening
8201  *                             about:addoons page, select the tab and load page
8202  *                             for view id on it. Otherwise, leave the loading
8203  *                             behavior to switchToTabHavingURI().
8204  *                             If no options, handles as false.
8205  *        }
8206  * @returns {Promise} When the Promise resolves, returns window object loaded the
8207  *                    view id.
8208  */
8209 function BrowserOpenAddonsMgr(aView, { selectTabByViewId = false } = {}) {
8210   return new Promise(resolve => {
8211     let emWindow;
8212     let browserWindow;
8214     var receivePong = function (aSubject) {
8215       let browserWin = aSubject.browsingContext.topChromeWindow;
8216       if (!emWindow || browserWin == window /* favor the current window */) {
8217         if (
8218           selectTabByViewId &&
8219           aSubject.gViewController.currentViewId !== aView
8220         ) {
8221           return;
8222         }
8224         emWindow = aSubject;
8225         browserWindow = browserWin;
8226       }
8227     };
8228     Services.obs.addObserver(receivePong, "EM-pong");
8229     Services.obs.notifyObservers(null, "EM-ping");
8230     Services.obs.removeObserver(receivePong, "EM-pong");
8232     if (emWindow) {
8233       if (aView && !selectTabByViewId) {
8234         emWindow.loadView(aView);
8235       }
8236       let tab = browserWindow.gBrowser.getTabForBrowser(
8237         emWindow.docShell.chromeEventHandler
8238       );
8239       browserWindow.gBrowser.selectedTab = tab;
8240       emWindow.focus();
8241       resolve(emWindow);
8242       return;
8243     }
8245     if (selectTabByViewId) {
8246       const target = isBlankPageURL(gBrowser.currentURI.spec)
8247         ? "current"
8248         : "tab";
8249       openTrustedLinkIn("about:addons", target);
8250     } else {
8251       // This must be a new load, else the ping/pong would have
8252       // found the window above.
8253       switchToTabHavingURI("about:addons", true);
8254     }
8256     Services.obs.addObserver(function observer(aSubject, aTopic) {
8257       Services.obs.removeObserver(observer, aTopic);
8258       if (aView) {
8259         aSubject.loadView(aView);
8260       }
8261       aSubject.focus();
8262       resolve(aSubject);
8263     }, "EM-loaded");
8264   });
8267 function AddKeywordForSearchField() {
8268   if (!gContextMenu) {
8269     throw new Error("Context menu doesn't seem to be open.");
8270   }
8272   gContextMenu.addKeywordForSearchField();
8276  * Applies only to the cmd|ctrl + shift + T keyboard shortcut
8277  * Undo the last action that was taken - either closing the last tab or closing the last window;
8278  * If none of those were the last actions, restore the last session if possible.
8279  */
8280 function restoreLastClosedTabOrWindowOrSession() {
8281   let lastActionTaken = SessionStore.popLastClosedAction();
8283   if (lastActionTaken) {
8284     switch (lastActionTaken.type) {
8285       case SessionStore.LAST_ACTION_CLOSED_TAB: {
8286         undoCloseTab();
8287         break;
8288       }
8289       case SessionStore.LAST_ACTION_CLOSED_WINDOW: {
8290         undoCloseWindow();
8291         break;
8292       }
8293     }
8294   } else {
8295     let closedTabCount = SessionStore.getLastClosedTabCount(window);
8296     if (SessionStore.canRestoreLastSession) {
8297       SessionStore.restoreLastSession();
8298     } else if (closedTabCount) {
8299       // we need to support users who have automatic session restore enabled
8300       undoCloseTab();
8301     }
8302   }
8306  * Re-open a closed tab into the current window.
8307  * @param [aIndex]
8308  *        The index of the tab (via SessionStore.getClosedTabData).
8309  *        When undefined, the first n closed tabs will be re-opened, where n is provided by getLastClosedTabCount.
8310  * @param {string} [sourceWindowSSId]
8311  *        An optional sessionstore id to identify the source window for the tab.
8312  *        I.e. the window the tab belonged to when closed.
8313  *        When undefined we'll use the current window
8314  * @returns a reference to the reopened tab.
8315  */
8316 function undoCloseTab(aIndex, sourceWindowSSId) {
8317   // the window we'll open the tab into
8318   let targetWindow = window;
8319   // the window the tab was closed from
8320   let sourceWindow;
8321   if (sourceWindowSSId) {
8322     sourceWindow = SessionStore.getWindowById(sourceWindowSSId);
8323     if (!sourceWindow) {
8324       throw new Error(
8325         "sourceWindowSSId argument to undoCloseTab didn't resolve to a window"
8326       );
8327     }
8328   } else {
8329     sourceWindow = window;
8330   }
8332   // wallpaper patch to prevent an unnecessary blank tab (bug 343895)
8333   let blankTabToRemove = null;
8334   if (
8335     targetWindow.gBrowser.visibleTabs.length == 1 &&
8336     targetWindow.gBrowser.selectedTab.isEmpty
8337   ) {
8338     blankTabToRemove = targetWindow.gBrowser.selectedTab;
8339   }
8341   // We are specifically interested in the lastClosedTabCount for the source window.
8342   // When aIndex is undefined, we restore all the lastClosedTabCount tabs.
8343   let lastClosedTabCount = SessionStore.getLastClosedTabCount(sourceWindow);
8344   let tab = null;
8345   // aIndex is undefined if the function is called without a specific tab to restore.
8346   let tabsToRemove =
8347     aIndex !== undefined ? [aIndex] : new Array(lastClosedTabCount).fill(0);
8348   let tabsRemoved = false;
8349   for (let index of tabsToRemove) {
8350     if (SessionStore.getClosedTabCountForWindow(sourceWindow) > index) {
8351       tab = SessionStore.undoCloseTab(sourceWindow, index, targetWindow);
8352       tabsRemoved = true;
8353     }
8354   }
8356   if (tabsRemoved && blankTabToRemove) {
8357     targetWindow.gBrowser.removeTab(blankTabToRemove);
8358   }
8360   return tab;
8364  * Re-open a closed window.
8365  * @param aIndex
8366  *        The index of the window (via SessionStore.getClosedWindowData)
8367  * @returns a reference to the reopened window.
8368  */
8369 function undoCloseWindow(aIndex) {
8370   let window = null;
8371   if (SessionStore.getClosedWindowCount() > (aIndex || 0)) {
8372     window = SessionStore.undoCloseWindow(aIndex || 0);
8373   }
8375   return window;
8378 function ReportFalseDeceptiveSite() {
8379   let contextsToVisit = [gBrowser.selectedBrowser.browsingContext];
8380   while (contextsToVisit.length) {
8381     let currentContext = contextsToVisit.pop();
8382     let global = currentContext.currentWindowGlobal;
8384     if (!global) {
8385       continue;
8386     }
8387     let docURI = global.documentURI;
8388     // Ensure the page is an about:blocked pagae before handling.
8389     if (docURI && docURI.spec.startsWith("about:blocked?e=deceptiveBlocked")) {
8390       let actor = global.getActor("BlockedSite");
8391       actor.sendQuery("DeceptiveBlockedDetails").then(data => {
8392         let reportUrl = gSafeBrowsing.getReportURL(
8393           "PhishMistake",
8394           data.blockedInfo
8395         );
8396         if (reportUrl) {
8397           openTrustedLinkIn(reportUrl, "tab");
8398         } else {
8399           let bundle = Services.strings.createBundle(
8400             "chrome://browser/locale/safebrowsing/safebrowsing.properties"
8401           );
8402           Services.prompt.alert(
8403             window,
8404             bundle.GetStringFromName("errorReportFalseDeceptiveTitle"),
8405             bundle.formatStringFromName("errorReportFalseDeceptiveMessage", [
8406               data.blockedInfo.provider,
8407             ])
8408           );
8409         }
8410       });
8411     }
8413     contextsToVisit.push(...currentContext.children);
8414   }
8418  * This is a temporary hack to connect a Help menu item for reporting
8419  * site issues to the WebCompat team's Site Compatability Reporter
8420  * WebExtension, which ships by default and is enabled on pre-release
8421  * channels.
8423  * Once we determine if Help is the right place for it, we'll do something
8424  * slightly better than this.
8426  * See bug 1690573.
8427  */
8428 function ReportSiteIssue() {
8429   let subject = { wrappedJSObject: gBrowser.selectedTab };
8430   Services.obs.notifyObservers(subject, "report-site-issue");
8434  * When the browser is being controlled from out-of-process,
8435  * e.g. when Marionette or the remote debugging protocol is used,
8436  * we add a visual hint to the browser UI to indicate to the user
8437  * that the browser session is under remote control.
8439  * This is called when the content browser initialises (from gBrowserInit.onLoad())
8440  * and when the "remote-listening" system notification fires.
8441  */
8442 const gRemoteControl = {
8443   observe() {
8444     gRemoteControl.updateVisualCue();
8445   },
8447   updateVisualCue() {
8448     // Disable updating the remote control cue for performance tests,
8449     // because these could fail due to an early initialization of Marionette.
8450     const disableRemoteControlCue = Services.prefs.getBoolPref(
8451       "browser.chrome.disableRemoteControlCueForTests",
8452       false
8453     );
8454     if (disableRemoteControlCue && Cu.isInAutomation) {
8455       return;
8456     }
8458     const mainWindow = document.documentElement;
8459     const remoteControlComponent = this.getRemoteControlComponent();
8460     if (remoteControlComponent) {
8461       mainWindow.setAttribute("remotecontrol", "true");
8462       const remoteControlIcon = document.getElementById("remote-control-icon");
8463       document.l10n.setAttributes(
8464         remoteControlIcon,
8465         "urlbar-remote-control-notification-anchor2",
8466         { component: remoteControlComponent }
8467       );
8468     } else {
8469       mainWindow.removeAttribute("remotecontrol");
8470     }
8471   },
8473   getRemoteControlComponent() {
8474     // For DevTools sockets, only show the remote control cue if the socket is
8475     // not coming from a regular Browser Toolbox debugging session.
8476     if (
8477       DevToolsSocketStatus.hasSocketOpened({
8478         excludeBrowserToolboxSockets: true,
8479       })
8480     ) {
8481       return "DevTools";
8482     }
8484     if (Marionette.running) {
8485       return "Marionette";
8486     }
8488     if (RemoteAgent.running) {
8489       return "RemoteAgent";
8490     }
8492     return null;
8493   },
8496 // Note that this is also called from non-browser windows on OSX, which do
8497 // share menu items but not much else. See nonbrowser-mac.js.
8498 var gPrivateBrowsingUI = {
8499   init: function PBUI_init() {
8500     // Do nothing for normal windows
8501     if (!PrivateBrowsingUtils.isWindowPrivate(window)) {
8502       return;
8503     }
8505     // Disable the Clear Recent History... menu item when in PB mode
8506     // temporary fix until bug 463607 is fixed
8507     document.getElementById("Tools:Sanitize").setAttribute("disabled", "true");
8509     if (window.location.href != AppConstants.BROWSER_CHROME_URL) {
8510       return;
8511     }
8513     // Adjust the window's title
8514     let docElement = document.documentElement;
8515     docElement.setAttribute(
8516       "privatebrowsingmode",
8517       PrivateBrowsingUtils.permanentPrivateBrowsing ? "permanent" : "temporary"
8518     );
8520     gBrowser.updateTitlebar();
8522     // Bug 1846583 - hide pocket button in PBM
8523     if (gUseFeltPrivacyUI) {
8524       const saveToPocketButton = document.getElementById(
8525         "save-to-pocket-button"
8526       );
8527       if (saveToPocketButton) {
8528         saveToPocketButton.remove();
8529         document.documentElement.setAttribute("pocketdisabled", "true");
8530       }
8531     }
8533     if (PrivateBrowsingUtils.permanentPrivateBrowsing) {
8534       // Adjust the New Window menu entries
8535       let newWindow = document.getElementById("menu_newNavigator");
8536       let newPrivateWindow = document.getElementById("menu_newPrivateWindow");
8537       if (newWindow && newPrivateWindow) {
8538         newPrivateWindow.hidden = true;
8539         newWindow.label = newPrivateWindow.label;
8540         newWindow.accessKey = newPrivateWindow.accessKey;
8541         newWindow.command = newPrivateWindow.command;
8542       }
8543     }
8544   },
8548  * Switch to a tab that has a given URI, and focuses its browser window.
8549  * If a matching tab is in this window, it will be switched to. Otherwise, other
8550  * windows will be searched.
8552  * @param aURI
8553  *        URI to search for
8554  * @param aOpenNew
8555  *        True to open a new tab and switch to it, if no existing tab is found.
8556  *        If no suitable window is found, a new one will be opened.
8557  * @param aOpenParams
8558  *        If switching to this URI results in us opening a tab, aOpenParams
8559  *        will be the parameter object that gets passed to openTrustedLinkIn. Please
8560  *        see the documentation for openTrustedLinkIn to see what parameters can be
8561  *        passed via this object.
8562  *        This object also allows:
8563  *        - 'ignoreFragment' property to be set to true to exclude fragment-portion
8564  *        matching when comparing URIs.
8565  *          If set to "whenComparing", the fragment will be unmodified.
8566  *          If set to "whenComparingAndReplace", the fragment will be replaced.
8567  *        - 'ignoreQueryString' boolean property to be set to true to exclude query string
8568  *        matching when comparing URIs.
8569  *        - 'replaceQueryString' boolean property to be set to true to exclude query string
8570  *        matching when comparing URIs and overwrite the initial query string with
8571  *        the one from the new URI.
8572  *        - 'adoptIntoActiveWindow' boolean property to be set to true to adopt the tab
8573  *        into the current window.
8574  * @param aUserContextId
8575  *        If not null, will switch to the first found tab having the provided
8576  *        userContextId.
8577  * @return True if an existing tab was found, false otherwise
8578  */
8579 function switchToTabHavingURI(
8580   aURI,
8581   aOpenNew,
8582   aOpenParams = {},
8583   aUserContextId = null
8584 ) {
8585   // Certain URLs can be switched to irrespective of the source or destination
8586   // window being in private browsing mode:
8587   const kPrivateBrowsingWhitelist = new Set(["about:addons"]);
8589   let ignoreFragment = aOpenParams.ignoreFragment;
8590   let ignoreQueryString = aOpenParams.ignoreQueryString;
8591   let replaceQueryString = aOpenParams.replaceQueryString;
8592   let adoptIntoActiveWindow = aOpenParams.adoptIntoActiveWindow;
8594   // These properties are only used by switchToTabHavingURI and should
8595   // not be used as a parameter for the new load.
8596   delete aOpenParams.ignoreFragment;
8597   delete aOpenParams.ignoreQueryString;
8598   delete aOpenParams.replaceQueryString;
8599   delete aOpenParams.adoptIntoActiveWindow;
8601   let isBrowserWindow = !!window.gBrowser;
8603   // This will switch to the tab in aWindow having aURI, if present.
8604   function switchIfURIInWindow(aWindow) {
8605     // We can switch tab only if if both the source and destination windows have
8606     // the same private-browsing status.
8607     if (
8608       !kPrivateBrowsingWhitelist.has(aURI.spec) &&
8609       PrivateBrowsingUtils.isWindowPrivate(window) !==
8610         PrivateBrowsingUtils.isWindowPrivate(aWindow)
8611     ) {
8612       return false;
8613     }
8615     // Remove the query string, fragment, both, or neither from a given url.
8616     function cleanURL(url, removeQuery, removeFragment) {
8617       let ret = url;
8618       if (removeFragment) {
8619         ret = ret.split("#")[0];
8620         if (removeQuery) {
8621           // This removes a query, if present before the fragment.
8622           ret = ret.split("?")[0];
8623         }
8624       } else if (removeQuery) {
8625         // This is needed in case there is a fragment after the query.
8626         let fragment = ret.split("#")[1];
8627         ret = ret
8628           .split("?")[0]
8629           .concat(fragment != undefined ? "#".concat(fragment) : "");
8630       }
8631       return ret;
8632     }
8634     // Need to handle nsSimpleURIs here too (e.g. about:...), which don't
8635     // work correctly with URL objects - so treat them as strings
8636     let ignoreFragmentWhenComparing =
8637       typeof ignoreFragment == "string" &&
8638       ignoreFragment.startsWith("whenComparing");
8639     let requestedCompare = cleanURL(
8640       aURI.displaySpec,
8641       ignoreQueryString || replaceQueryString,
8642       ignoreFragmentWhenComparing
8643     );
8644     let browsers = aWindow.gBrowser.browsers;
8645     for (let i = 0; i < browsers.length; i++) {
8646       let browser = browsers[i];
8647       let browserCompare = cleanURL(
8648         browser.currentURI.displaySpec,
8649         ignoreQueryString || replaceQueryString,
8650         ignoreFragmentWhenComparing
8651       );
8652       let browserUserContextId = browser.getAttribute("usercontextid") || "";
8653       if (aUserContextId != null && aUserContextId != browserUserContextId) {
8654         continue;
8655       }
8656       if (requestedCompare == browserCompare) {
8657         // If adoptIntoActiveWindow is set, and this is a cross-window switch,
8658         // adopt the tab into the current window, after the active tab.
8659         let doAdopt =
8660           adoptIntoActiveWindow && isBrowserWindow && aWindow != window;
8662         if (doAdopt) {
8663           const newTab = window.gBrowser.adoptTab(
8664             aWindow.gBrowser.getTabForBrowser(browser),
8665             window.gBrowser.tabContainer.selectedIndex + 1,
8666             /* aSelectTab = */ true
8667           );
8668           if (!newTab) {
8669             doAdopt = false;
8670           }
8671         }
8672         if (!doAdopt) {
8673           aWindow.focus();
8674         }
8676         if (ignoreFragment == "whenComparingAndReplace" || replaceQueryString) {
8677           browser.loadURI(aURI, {
8678             triggeringPrincipal:
8679               aOpenParams.triggeringPrincipal ||
8680               _createNullPrincipalFromTabUserContextId(),
8681           });
8682         }
8684         if (!doAdopt) {
8685           aWindow.gBrowser.tabContainer.selectedIndex = i;
8686         }
8688         return true;
8689       }
8690     }
8691     return false;
8692   }
8694   // This can be passed either nsIURI or a string.
8695   if (!(aURI instanceof Ci.nsIURI)) {
8696     aURI = Services.io.newURI(aURI);
8697   }
8699   // Prioritise this window.
8700   if (isBrowserWindow && switchIfURIInWindow(window)) {
8701     return true;
8702   }
8704   for (let browserWin of browserWindows()) {
8705     // Skip closed (but not yet destroyed) windows,
8706     // and the current window (which was checked earlier).
8707     if (browserWin.closed || browserWin == window) {
8708       continue;
8709     }
8710     if (switchIfURIInWindow(browserWin)) {
8711       return true;
8712     }
8713   }
8715   // No opened tab has that url.
8716   if (aOpenNew) {
8717     if (
8718       UrlbarPrefs.get("switchTabs.searchAllContainers") &&
8719       aUserContextId != null
8720     ) {
8721       aOpenParams.userContextId = aUserContextId;
8722     }
8723     if (isBrowserWindow && gBrowser.selectedTab.isEmpty) {
8724       openTrustedLinkIn(aURI.spec, "current", aOpenParams);
8725     } else {
8726       openTrustedLinkIn(aURI.spec, "tab", aOpenParams);
8727     }
8728   }
8730   return false;
8733 var RestoreLastSessionObserver = {
8734   init() {
8735     if (
8736       SessionStore.canRestoreLastSession &&
8737       !PrivateBrowsingUtils.isWindowPrivate(window)
8738     ) {
8739       Services.obs.addObserver(this, "sessionstore-last-session-cleared", true);
8740       goSetCommandEnabled("Browser:RestoreLastSession", true);
8741     } else if (SessionStore.willAutoRestore) {
8742       document.getElementById("Browser:RestoreLastSession").hidden = true;
8743     }
8744   },
8746   observe() {
8747     // The last session can only be restored once so there's
8748     // no way we need to re-enable our menu item.
8749     Services.obs.removeObserver(this, "sessionstore-last-session-cleared");
8750     goSetCommandEnabled("Browser:RestoreLastSession", false);
8751   },
8753   QueryInterface: ChromeUtils.generateQI([
8754     "nsIObserver",
8755     "nsISupportsWeakReference",
8756   ]),
8759 /* Observes menus and adjusts their size for better
8760  * usability when opened via a touch screen. */
8761 var MenuTouchModeObserver = {
8762   init() {
8763     window.addEventListener("popupshowing", this, true);
8764   },
8766   handleEvent(event) {
8767     let target = event.originalTarget;
8768     if (event.inputSource == MouseEvent.MOZ_SOURCE_TOUCH) {
8769       target.setAttribute("touchmode", "true");
8770     } else {
8771       target.removeAttribute("touchmode");
8772     }
8773   },
8775   uninit() {
8776     window.removeEventListener("popupshowing", this, true);
8777   },
8780 // Prompt user to restart the browser in safe mode
8781 function safeModeRestart() {
8782   if (Services.appinfo.inSafeMode) {
8783     let cancelQuit = Cc["@mozilla.org/supports-PRBool;1"].createInstance(
8784       Ci.nsISupportsPRBool
8785     );
8786     Services.obs.notifyObservers(
8787       cancelQuit,
8788       "quit-application-requested",
8789       "restart"
8790     );
8792     if (cancelQuit.data) {
8793       return;
8794     }
8796     Services.startup.quit(
8797       Ci.nsIAppStartup.eRestart | Ci.nsIAppStartup.eAttemptQuit
8798     );
8799     return;
8800   }
8802   Services.obs.notifyObservers(window, "restart-in-safe-mode");
8805 /* duplicateTabIn duplicates tab in a place specified by the parameter |where|.
8807  * |where| can be:
8808  *  "tab"         new tab
8809  *  "tabshifted"  same as "tab" but in background if default is to select new
8810  *                tabs, and vice versa
8811  *  "window"      new window
8813  * delta is the offset to the history entry that you want to load.
8814  */
8815 function duplicateTabIn(aTab, where, delta) {
8816   switch (where) {
8817     case "window":
8818       let otherWin = OpenBrowserWindow({
8819         private: PrivateBrowsingUtils.isBrowserPrivate(aTab.linkedBrowser),
8820       });
8821       let delayedStartupFinished = (subject, topic) => {
8822         if (
8823           topic == "browser-delayed-startup-finished" &&
8824           subject == otherWin
8825         ) {
8826           Services.obs.removeObserver(delayedStartupFinished, topic);
8827           let otherGBrowser = otherWin.gBrowser;
8828           let otherTab = otherGBrowser.selectedTab;
8829           SessionStore.duplicateTab(otherWin, aTab, delta);
8830           otherGBrowser.removeTab(otherTab, { animate: false });
8831         }
8832       };
8834       Services.obs.addObserver(
8835         delayedStartupFinished,
8836         "browser-delayed-startup-finished"
8837       );
8838       break;
8839     case "tabshifted":
8840       SessionStore.duplicateTab(window, aTab, delta);
8841       // A background tab has been opened, nothing else to do here.
8842       break;
8843     case "tab":
8844       SessionStore.duplicateTab(window, aTab, delta, true, {
8845         inBackground: false,
8846       });
8847       break;
8848   }
8851 var MousePosTracker = {
8852   _listeners: new Set(),
8853   _x: 0,
8854   _y: 0,
8856   /**
8857    * Registers a listener.
8858    *
8859    * @param listener (object)
8860    *        A listener is expected to expose the following properties:
8861    *
8862    *        getMouseTargetRect (function)
8863    *          Returns the rect that the MousePosTracker needs to alert
8864    *          the listener about if the mouse happens to be within it.
8865    *
8866    *        onMouseEnter (function, optional)
8867    *          The function to be called if the mouse enters the rect
8868    *          returned by getMouseTargetRect. MousePosTracker always
8869    *          runs this inside of a requestAnimationFrame, since it
8870    *          assumes that the notification is used to update the DOM.
8871    *
8872    *        onMouseLeave (function, optional)
8873    *          The function to be called if the mouse exits the rect
8874    *          returned by getMouseTargetRect. MousePosTracker always
8875    *          runs this inside of a requestAnimationFrame, since it
8876    *          assumes that the notification is used to update the DOM.
8877    */
8878   addListener(listener) {
8879     if (this._listeners.has(listener)) {
8880       return;
8881     }
8883     listener._hover = false;
8884     this._listeners.add(listener);
8886     this._callListener(listener);
8887   },
8889   removeListener(listener) {
8890     this._listeners.delete(listener);
8891   },
8893   handleEvent(event) {
8894     this._x = event.screenX - window.mozInnerScreenX;
8895     this._y = event.screenY - window.mozInnerScreenY;
8897     this._listeners.forEach(listener => {
8898       try {
8899         this._callListener(listener);
8900       } catch (e) {
8901         console.error(e);
8902       }
8903     });
8904   },
8906   _callListener(listener) {
8907     let rect = listener.getMouseTargetRect();
8908     let hover =
8909       this._x >= rect.left &&
8910       this._x <= rect.right &&
8911       this._y >= rect.top &&
8912       this._y <= rect.bottom;
8914     if (hover == listener._hover) {
8915       return;
8916     }
8918     listener._hover = hover;
8920     if (hover) {
8921       if (listener.onMouseEnter) {
8922         listener.onMouseEnter();
8923       }
8924     } else if (listener.onMouseLeave) {
8925       listener.onMouseLeave();
8926     }
8927   },
8930 var ToolbarIconColor = {
8931   _windowState: {
8932     active: false,
8933     fullscreen: false,
8934     tabsintitlebar: false,
8935   },
8936   init() {
8937     this._initialized = true;
8939     window.addEventListener("nativethemechange", this);
8940     window.addEventListener("activate", this);
8941     window.addEventListener("deactivate", this);
8942     window.addEventListener("toolbarvisibilitychange", this);
8943     window.addEventListener("windowlwthemeupdate", this);
8945     // If the window isn't active now, we assume that it has never been active
8946     // before and will soon become active such that inferFromText will be
8947     // called from the initial activate event.
8948     if (Services.focus.activeWindow == window) {
8949       this.inferFromText("activate");
8950     }
8951   },
8953   uninit() {
8954     this._initialized = false;
8956     window.removeEventListener("nativethemechange", this);
8957     window.removeEventListener("activate", this);
8958     window.removeEventListener("deactivate", this);
8959     window.removeEventListener("toolbarvisibilitychange", this);
8960     window.removeEventListener("windowlwthemeupdate", this);
8961   },
8963   handleEvent(event) {
8964     switch (event.type) {
8965       case "activate":
8966       case "deactivate":
8967       case "nativethemechange":
8968       case "windowlwthemeupdate":
8969         this.inferFromText(event.type);
8970         break;
8971       case "toolbarvisibilitychange":
8972         this.inferFromText(event.type, event.visible);
8973         break;
8974     }
8975   },
8977   // a cache of luminance values for each toolbar
8978   // to avoid unnecessary calls to getComputedStyle
8979   _toolbarLuminanceCache: new Map(),
8981   inferFromText(reason, reasonValue) {
8982     if (!this._initialized) {
8983       return;
8984     }
8985     switch (reason) {
8986       case "activate": // falls through
8987       case "deactivate":
8988         this._windowState.active = reason === "activate";
8989         break;
8990       case "fullscreen":
8991         this._windowState.fullscreen = reasonValue;
8992         break;
8993       case "nativethemechange":
8994       case "windowlwthemeupdate":
8995         // theme change, we'll need to recalculate all color values
8996         this._toolbarLuminanceCache.clear();
8997         break;
8998       case "toolbarvisibilitychange":
8999         // toolbar changes dont require reset of the cached color values
9000         break;
9001       case "tabsintitlebar":
9002         this._windowState.tabsintitlebar = reasonValue;
9003         break;
9004     }
9006     let toolbarSelector = ".browser-toolbar:not([collapsed=true])";
9007     if (AppConstants.platform == "macosx") {
9008       toolbarSelector += ":not([type=menubar])";
9009     }
9011     // The getComputedStyle calls and setting the brighttext are separated in
9012     // two loops to avoid flushing layout and making it dirty repeatedly.
9013     let cachedLuminances = this._toolbarLuminanceCache;
9014     let luminances = new Map();
9015     for (let toolbar of document.querySelectorAll(toolbarSelector)) {
9016       // toolbars *should* all have ids, but guard anyway to avoid blowing up
9017       let cacheKey =
9018         toolbar.id && toolbar.id + JSON.stringify(this._windowState);
9019       // lookup cached luminance value for this toolbar in this window state
9020       let luminance = cacheKey && cachedLuminances.get(cacheKey);
9021       if (isNaN(luminance)) {
9022         let { r, g, b } = InspectorUtils.colorToRGBA(
9023           getComputedStyle(toolbar).color
9024         );
9025         luminance = 0.2125 * r + 0.7154 * g + 0.0721 * b;
9026         if (cacheKey) {
9027           cachedLuminances.set(cacheKey, luminance);
9028         }
9029       }
9030       luminances.set(toolbar, luminance);
9031     }
9033     const luminanceThreshold = 127; // In between 0 and 255
9034     for (let [toolbar, luminance] of luminances) {
9035       if (luminance <= luminanceThreshold) {
9036         toolbar.removeAttribute("brighttext");
9037       } else {
9038         toolbar.setAttribute("brighttext", "true");
9039       }
9040     }
9041   },
9044 var PanicButtonNotifier = {
9045   init() {
9046     this._initialized = true;
9047     if (window.PanicButtonNotifierShouldNotify) {
9048       delete window.PanicButtonNotifierShouldNotify;
9049       this.notify();
9050     }
9051   },
9052   createPanelIfNeeded() {
9053     // Lazy load the panic-button-success-notification panel the first time we need to display it.
9054     if (!document.getElementById("panic-button-success-notification")) {
9055       let template = document.getElementById("panicButtonNotificationTemplate");
9056       template.replaceWith(template.content);
9057     }
9058   },
9059   notify() {
9060     if (!this._initialized) {
9061       window.PanicButtonNotifierShouldNotify = true;
9062       return;
9063     }
9064     // Display notification panel here...
9065     try {
9066       this.createPanelIfNeeded();
9067       let popup = document.getElementById("panic-button-success-notification");
9068       popup.hidden = false;
9069       // To close the popup in 3 seconds after the popup is shown but left uninteracted.
9070       let onTimeout = () => {
9071         PanicButtonNotifier.close();
9072         removeListeners();
9073       };
9074       popup.addEventListener("popupshown", function () {
9075         PanicButtonNotifier.timer = setTimeout(onTimeout, 3000);
9076       });
9077       // To prevent the popup from closing when user tries to interact with the
9078       // popup using mouse or keyboard.
9079       let onUserInteractsWithPopup = () => {
9080         clearTimeout(PanicButtonNotifier.timer);
9081         removeListeners();
9082       };
9083       popup.addEventListener("mouseover", onUserInteractsWithPopup);
9084       window.addEventListener("keydown", onUserInteractsWithPopup);
9085       let removeListeners = () => {
9086         popup.removeEventListener("mouseover", onUserInteractsWithPopup);
9087         window.removeEventListener("keydown", onUserInteractsWithPopup);
9088         popup.removeEventListener("popuphidden", removeListeners);
9089       };
9090       popup.addEventListener("popuphidden", removeListeners);
9092       let widget = CustomizableUI.getWidget("panic-button").forWindow(window);
9093       let anchor = widget.anchor.icon;
9094       popup.openPopup(anchor, popup.getAttribute("position"));
9095     } catch (ex) {
9096       console.error(ex);
9097     }
9098   },
9099   close() {
9100     let popup = document.getElementById("panic-button-success-notification");
9101     popup.hidePopup();
9102   },
9105 const SafeBrowsingNotificationBox = {
9106   _currentURIBaseDomain: null,
9107   async show(title, buttons) {
9108     let uri = gBrowser.currentURI;
9110     // start tracking host so that we know when we leave the domain
9111     try {
9112       this._currentURIBaseDomain = Services.eTLD.getBaseDomain(uri);
9113     } catch (e) {
9114       // If we can't get the base domain, fallback to use host instead. However,
9115       // host is sometimes empty when the scheme is file. In this case, just use
9116       // spec.
9117       this._currentURIBaseDomain = uri.asciiHost || uri.asciiSpec;
9118     }
9120     let notificationBox = gBrowser.getNotificationBox();
9121     let value = "blocked-badware-page";
9123     let previousNotification = notificationBox.getNotificationWithValue(value);
9124     if (previousNotification) {
9125       notificationBox.removeNotification(previousNotification);
9126     }
9128     let notification = await notificationBox.appendNotification(
9129       value,
9130       {
9131         label: title,
9132         image: "chrome://global/skin/icons/blocked.svg",
9133         priority: notificationBox.PRIORITY_CRITICAL_HIGH,
9134       },
9135       buttons
9136     );
9137     // Persist the notification until the user removes so it
9138     // doesn't get removed on redirects.
9139     notification.persistence = -1;
9140   },
9141   onLocationChange(aLocationURI) {
9142     // take this to represent that you haven't visited a bad place
9143     if (!this._currentURIBaseDomain) {
9144       return;
9145     }
9147     let newURIBaseDomain = Services.eTLD.getBaseDomain(aLocationURI);
9149     if (newURIBaseDomain !== this._currentURIBaseDomain) {
9150       let notificationBox = gBrowser.getNotificationBox();
9151       let notification = notificationBox.getNotificationWithValue(
9152         "blocked-badware-page"
9153       );
9154       if (notification) {
9155         notificationBox.removeNotification(notification, false);
9156       }
9158       this._currentURIBaseDomain = null;
9159     }
9160   },
9164  * The TabDialogBox supports opening window dialogs as SubDialogs on the tab and content
9165  * level. Both tab and content dialogs have their own separate managers.
9166  * Dialogs will be queued FIFO and cover the web content.
9167  * Dialogs are closed when the user reloads or leaves the page.
9168  * While a dialog is open PopupNotifications, such as permission prompts, are
9169  * suppressed.
9170  */
9171 class TabDialogBox {
9172   static _containerFor(browser) {
9173     return browser.closest(".browserStack, .webextension-popup-stack");
9174   }
9176   constructor(browser) {
9177     this._weakBrowserRef = Cu.getWeakReference(browser);
9179     // Create parent element for tab dialogs
9180     let template = document.getElementById("dialogStackTemplate");
9181     let dialogStack = template.content.cloneNode(true).firstElementChild;
9182     dialogStack.classList.add("tab-prompt-dialog");
9184     TabDialogBox._containerFor(browser).appendChild(dialogStack);
9186     // Initially the stack only contains the template
9187     let dialogTemplate = dialogStack.firstElementChild;
9189     // Create dialog manager for prompts at the tab level.
9190     this._tabDialogManager = new SubDialogManager({
9191       dialogStack,
9192       dialogTemplate,
9193       orderType: SubDialogManager.ORDER_QUEUE,
9194       allowDuplicateDialogs: true,
9195       dialogOptions: {
9196         consumeOutsideClicks: false,
9197       },
9198     });
9199   }
9201   /**
9202    * Open a dialog on tab or content level.
9203    * @param {String} aURL - URL of the dialog to load in the tab box.
9204    * @param {Object} [aOptions]
9205    * @param {String} [aOptions.features] - Comma separated list of window
9206    * features.
9207    * @param {Boolean} [aOptions.allowDuplicateDialogs] - Whether to allow
9208    * showing multiple dialogs with aURL at the same time. If false calls for
9209    * duplicate dialogs will be dropped.
9210    * @param {String} [aOptions.sizeTo] - Pass "available" to stretch dialog to
9211    * roughly content size. Any max-width or max-height style values on the document root
9212    * will also be applied to the dialog box.
9213    * @param {Boolean} [aOptions.keepOpenSameOriginNav] - By default dialogs are
9214    * aborted on any navigation.
9215    * Set to true to keep the dialog open for same origin navigation.
9216    * @param {Number} [aOptions.modalType] - The modal type to create the dialog for.
9217    * By default, we show the dialog for tab prompts.
9218    * @param {Boolean} [aOptions.hideContent] - When true, we are about to show a prompt that is requesting the
9219    * users credentials for a toplevel load of a resource from a base domain different from the base domain of the currently loaded page.
9220    * To avoid auth prompt spoofing (see bug 791594) we hide the current sites content
9221    * (among other protection mechanisms, that are not handled here, see the bug for reference).
9222    * @returns {Object} [result] Returns an object { closedPromise, dialog }.
9223    * @returns {Promise} [result.closedPromise] Resolves once the dialog has been closed.
9224    * @returns {SubDialog} [result.dialog] A reference to the opened SubDialog.
9225    */
9226   open(
9227     aURL,
9228     {
9229       features = null,
9230       allowDuplicateDialogs = true,
9231       sizeTo,
9232       keepOpenSameOriginNav,
9233       modalType = null,
9234       allowFocusCheckbox = false,
9235       hideContent = false,
9236     } = {},
9237     ...aParams
9238   ) {
9239     let resolveClosed;
9240     let closedPromise = new Promise(resolve => (resolveClosed = resolve));
9241     // Get the dialog manager to open the prompt with.
9242     let dialogManager =
9243       modalType === Ci.nsIPrompt.MODAL_TYPE_CONTENT
9244         ? this.getContentDialogManager()
9245         : this._tabDialogManager;
9247     let hasDialogs = () =>
9248       this._tabDialogManager.hasDialogs ||
9249       this._contentDialogManager?.hasDialogs;
9251     if (!hasDialogs()) {
9252       this._onFirstDialogOpen();
9253     }
9255     let closingCallback = event => {
9256       if (!hasDialogs()) {
9257         this._onLastDialogClose();
9258       }
9260       if (allowFocusCheckbox && !event.detail?.abort) {
9261         this.maybeSetAllowTabSwitchPermission(event.target);
9262       }
9263     };
9265     if (modalType == Ci.nsIPrompt.MODAL_TYPE_CONTENT) {
9266       sizeTo = "limitheight";
9267     }
9269     // Open dialog and resolve once it has been closed
9270     let dialog = dialogManager.open(
9271       aURL,
9272       {
9273         features,
9274         allowDuplicateDialogs,
9275         sizeTo,
9276         closingCallback,
9277         closedCallback: resolveClosed,
9278         hideContent,
9279       },
9280       ...aParams
9281     );
9283     // Marking the dialog externally, instead of passing it as an option.
9284     // The SubDialog(Manager) does not care about navigation.
9285     // dialog can be null here if allowDuplicateDialogs = false.
9286     if (dialog) {
9287       dialog._keepOpenSameOriginNav = keepOpenSameOriginNav;
9288     }
9289     return { closedPromise, dialog };
9290   }
9292   _onFirstDialogOpen() {
9293     // Hide PopupNotifications to prevent them from covering up dialogs.
9294     this.browser.setAttribute("tabDialogShowing", true);
9295     UpdatePopupNotificationsVisibility();
9297     // Register listeners
9298     this._lastPrincipal = this.browser.contentPrincipal;
9299     this.browser.addProgressListener(this, Ci.nsIWebProgress.NOTIFY_LOCATION);
9301     this.tab?.addEventListener("TabClose", this);
9302   }
9304   _onLastDialogClose() {
9305     // Show PopupNotifications again.
9306     this.browser.removeAttribute("tabDialogShowing");
9307     UpdatePopupNotificationsVisibility();
9309     // Clean up listeners
9310     this.browser.removeProgressListener(this);
9311     this._lastPrincipal = null;
9313     this.tab?.removeEventListener("TabClose", this);
9314   }
9316   _buildContentPromptDialog() {
9317     let template = document.getElementById("dialogStackTemplate");
9318     let contentDialogStack = template.content.cloneNode(true).firstElementChild;
9319     contentDialogStack.classList.add("content-prompt-dialog");
9321     // Create a dialog manager for content prompts.
9322     let browserContainer = TabDialogBox._containerFor(this.browser);
9323     let tabPromptDialog = browserContainer.querySelector(".tab-prompt-dialog");
9324     browserContainer.insertBefore(contentDialogStack, tabPromptDialog);
9326     let contentDialogTemplate = contentDialogStack.firstElementChild;
9327     this._contentDialogManager = new SubDialogManager({
9328       dialogStack: contentDialogStack,
9329       dialogTemplate: contentDialogTemplate,
9330       orderType: SubDialogManager.ORDER_QUEUE,
9331       allowDuplicateDialogs: true,
9332       dialogOptions: {
9333         consumeOutsideClicks: false,
9334       },
9335     });
9336   }
9338   handleEvent(event) {
9339     if (event.type !== "TabClose") {
9340       return;
9341     }
9342     this.abortAllDialogs();
9343   }
9345   abortAllDialogs() {
9346     this._tabDialogManager.abortDialogs();
9347     this._contentDialogManager?.abortDialogs();
9348   }
9350   focus() {
9351     // Prioritize focusing the dialog manager for tab prompts
9352     if (this._tabDialogManager._dialogs.length) {
9353       this._tabDialogManager.focusTopDialog();
9354       return;
9355     }
9356     this._contentDialogManager?.focusTopDialog();
9357   }
9359   /**
9360    * If the user navigates away or refreshes the page, close all dialogs for
9361    * the current browser.
9362    */
9363   onLocationChange(aWebProgress, aRequest, aLocation, aFlags) {
9364     if (
9365       !aWebProgress.isTopLevel ||
9366       aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT
9367     ) {
9368       return;
9369     }
9371     // Dialogs can be exempt from closing on same origin location change.
9372     let filterFn;
9374     // Test for same origin location change
9375     if (
9376       this._lastPrincipal?.isSameOrigin(
9377         aLocation,
9378         this.browser.browsingContext.usePrivateBrowsing
9379       )
9380     ) {
9381       filterFn = dialog => !dialog._keepOpenSameOriginNav;
9382     }
9384     this._lastPrincipal = this.browser.contentPrincipal;
9386     this._tabDialogManager.abortDialogs(filterFn);
9387     this._contentDialogManager?.abortDialogs(filterFn);
9388   }
9390   get tab() {
9391     return gBrowser.getTabForBrowser(this.browser);
9392   }
9394   get browser() {
9395     let browser = this._weakBrowserRef.get();
9396     if (!browser) {
9397       throw new Error("Stale dialog box! The associated browser is gone.");
9398     }
9399     return browser;
9400   }
9402   getTabDialogManager() {
9403     return this._tabDialogManager;
9404   }
9406   getContentDialogManager() {
9407     if (!this._contentDialogManager) {
9408       this._buildContentPromptDialog();
9409     }
9410     return this._contentDialogManager;
9411   }
9413   onNextPromptShowAllowFocusCheckboxFor(principal) {
9414     this._allowTabFocusByPromptPrincipal = principal;
9415   }
9417   /**
9418    * Sets the "focus-tab-by-prompt" permission for the dialog.
9419    */
9420   maybeSetAllowTabSwitchPermission(dialog) {
9421     let checkbox = dialog.querySelector("checkbox");
9423     if (checkbox.checked) {
9424       Services.perms.addFromPrincipal(
9425         this._allowTabFocusByPromptPrincipal,
9426         "focus-tab-by-prompt",
9427         Services.perms.ALLOW_ACTION
9428       );
9429     }
9431     // Don't show the "allow tab switch checkbox" for subsequent prompts.
9432     this._allowTabFocusByPromptPrincipal = null;
9433   }
9436 TabDialogBox.prototype.QueryInterface = ChromeUtils.generateQI([
9437   "nsIWebProgressListener",
9438   "nsISupportsWeakReference",
9441 // Handle window-modal prompts that we want to display with the same style as
9442 // tab-modal prompts.
9443 var gDialogBox = {
9444   _dialog: null,
9445   _nextOpenJumpsQueue: false,
9446   _queued: [],
9448   // Used to wait for a `close` event from the HTML
9449   // dialog. The  event is fired asynchronously, which means
9450   // that if we open another dialog immediately after the
9451   // previous one, we might be confused into thinking a
9452   // `close` event for the old dialog is for the new one.
9453   // As they have the same event target, we have no way of
9454   // distinguishing them. So we wait for the `close` event
9455   // to have happened before allowing another dialog to open.
9456   _didCloseHTMLDialog: null,
9457   // Whether we managed to open the dialog we tried to open.
9458   // Used to avoid waiting for the above callback in case
9459   // of an error opening the dialog.
9460   _didOpenHTMLDialog: false,
9462   get dialog() {
9463     return this._dialog;
9464   },
9466   get isOpen() {
9467     return !!this._dialog;
9468   },
9470   replaceDialogIfOpen() {
9471     this._dialog?.close();
9472     this._nextOpenJumpsQueue = true;
9473   },
9475   async open(uri, args) {
9476     // If we need to queue, some callers indicate they should go first.
9477     const queueMethod = this._nextOpenJumpsQueue ? "unshift" : "push";
9478     this._nextOpenJumpsQueue = false;
9480     // If we already have a dialog opened and are trying to open another,
9481     // queue the next one to be opened later.
9482     if (this.isOpen) {
9483       return new Promise((resolve, reject) => {
9484         this._queued[queueMethod]({ resolve, reject, uri, args });
9485       });
9486     }
9488     // We're not open. If we're in a modal state though, we can't
9489     // show the dialog effectively. To avoid hanging by deadlock,
9490     // just return immediately for sync prompts:
9491     if (window.windowUtils.isInModalState() && !args.getProperty("async")) {
9492       throw Components.Exception(
9493         "Prompt could not be shown.",
9494         Cr.NS_ERROR_NOT_AVAILABLE
9495       );
9496     }
9498     // Indicate if we should wait for the dialog to close.
9499     this._didOpenHTMLDialog = false;
9500     let haveClosedPromise = new Promise(resolve => {
9501       this._didCloseHTMLDialog = resolve;
9502     });
9504     // Bring the window to the front in case we're minimized or occluded:
9505     window.focus();
9507     try {
9508       await this._open(uri, args);
9509     } catch (ex) {
9510       console.error(ex);
9511     } finally {
9512       let dialog = document.getElementById("window-modal-dialog");
9513       if (dialog.open) {
9514         dialog.close();
9515       }
9516       // If the dialog was opened successfully, then we can wait for it
9517       // to close before trying to open any others.
9518       if (this._didOpenHTMLDialog) {
9519         await haveClosedPromise;
9520       }
9521       dialog.style.visibility = "hidden";
9522       dialog.style.height = "0";
9523       dialog.style.width = "0";
9524       document.documentElement.removeAttribute("window-modal-open");
9525       dialog.removeEventListener("dialogopen", this);
9526       dialog.removeEventListener("close", this);
9527       this._updateMenuAndCommandState(true /* to enable */);
9528       this._dialog = null;
9529       UpdatePopupNotificationsVisibility();
9530     }
9531     if (this._queued.length) {
9532       setTimeout(() => this._openNextDialog(), 0);
9533     }
9534     return args;
9535   },
9537   _openNextDialog() {
9538     if (!this.isOpen) {
9539       let { resolve, reject, uri, args } = this._queued.shift();
9540       this.open(uri, args).then(resolve, reject);
9541     }
9542   },
9544   handleEvent(event) {
9545     switch (event.type) {
9546       case "dialogopen":
9547         this._dialog.focus(true);
9548         break;
9549       case "close":
9550         this._didCloseHTMLDialog();
9551         this._dialog.close();
9552         break;
9553     }
9554   },
9556   _open(uri, args) {
9557     // Get this offset before we touch style below, as touching style seems
9558     // to reset the cached layout bounds.
9559     let offset = window.windowUtils.getBoundsWithoutFlushing(
9560       gBrowser.selectedBrowser
9561     ).top;
9562     let parentElement = document.getElementById("window-modal-dialog");
9563     parentElement.style.setProperty("--chrome-offset", offset + "px");
9564     parentElement.style.removeProperty("visibility");
9565     parentElement.style.removeProperty("width");
9566     parentElement.style.removeProperty("height");
9567     document.documentElement.setAttribute("window-modal-open", true);
9568     // Call this first so the contents show up and get layout, which is
9569     // required for SubDialog to work.
9570     parentElement.showModal();
9571     this._didOpenHTMLDialog = true;
9573     // Disable menus and shortcuts.
9574     this._updateMenuAndCommandState(false /* to disable */);
9576     // Now actually set up the dialog contents:
9577     let template = document.getElementById("window-modal-dialog-template")
9578       .content.firstElementChild;
9579     parentElement.addEventListener("dialogopen", this);
9580     parentElement.addEventListener("close", this);
9581     this._dialog = new SubDialog({
9582       template,
9583       parentElement,
9584       id: "window-modal-dialog-subdialog",
9585       options: {
9586         consumeOutsideClicks: false,
9587       },
9588     });
9589     let closedPromise = new Promise(resolve => {
9590       this._closedCallback = function () {
9591         PromptUtils.fireDialogEvent(window, "DOMModalDialogClosed");
9592         resolve();
9593       };
9594     });
9595     this._dialog.open(
9596       uri,
9597       {
9598         features: "resizable=no",
9599         modalType: Ci.nsIPrompt.MODAL_TYPE_INTERNAL_WINDOW,
9600         closedCallback: () => {
9601           this._closedCallback();
9602         },
9603       },
9604       args
9605     );
9606     UpdatePopupNotificationsVisibility();
9607     return closedPromise;
9608   },
9610   _nonUpdatableElements: new Set([
9611     // Make an exception for debugging tools, for developer ease of use.
9612     "key_browserConsole",
9613     "key_browserToolbox",
9615     // Don't touch the editing keys/commands which we might want inside the dialog.
9616     "key_undo",
9617     "key_redo",
9619     "key_cut",
9620     "key_copy",
9621     "key_paste",
9622     "key_delete",
9623     "key_selectAll",
9624   ]),
9626   _updateMenuAndCommandState(shouldBeEnabled) {
9627     let editorCommands = document.getElementById("editMenuCommands");
9628     // For the following items, set or clear disabled state:
9629     // - toplevel menubar items (will affect inner items on macOS)
9630     // - command elements
9631     // - key elements not connected to command elements.
9632     for (let element of document.querySelectorAll(
9633       "menubar > menu, command, key:not([command])"
9634     )) {
9635       if (
9636         editorCommands?.contains(element) ||
9637         (element.id && this._nonUpdatableElements.has(element.id))
9638       ) {
9639         continue;
9640       }
9641       if (element.nodeName == "key" && element.command) {
9642         continue;
9643       }
9644       if (!shouldBeEnabled) {
9645         if (element.getAttribute("disabled") != "true") {
9646           element.setAttribute("disabled", true);
9647         } else {
9648           element.setAttribute("wasdisabled", true);
9649         }
9650       } else if (element.getAttribute("wasdisabled") != "true") {
9651         element.removeAttribute("disabled");
9652       } else {
9653         element.removeAttribute("wasdisabled");
9654       }
9655     }
9656   },
9659 // browser.js loads in the library window, too, but we can only show prompts
9660 // in the main browser window:
9661 if (window.location.href != AppConstants.BROWSER_CHROME_URL) {
9662   gDialogBox = null;
9665 var ConfirmationHint = {
9666   _timerID: null,
9668   /**
9669    * Shows a transient, non-interactive confirmation hint anchored to an
9670    * element, usually used in response to a user action to reaffirm that it was
9671    * successful and potentially provide extra context. Examples for such hints:
9672    * - "Saved to bookmarks" after bookmarking a page
9673    * - "Sent!" after sending a tab to another device
9674    * - "Queued (offline)" when attempting to send a tab to another device
9675    *   while offline
9676    *
9677    * @param  anchor (DOM node, required)
9678    *         The anchor for the panel.
9679    * @param  messageId (string, required)
9680    *         For getting the message string from confirmationHints.ftl
9681    * @param  options (object, optional)
9682    *         An object with the following optional properties:
9683    *         - event (DOM event): The event that triggered the feedback
9684    *         - descriptionId (string): message ID of the description text
9685    *         - position (string): position of the panel relative to the anchor.
9686    *
9687    */
9688   show(anchor, messageId, options = {}) {
9689     this._reset();
9691     MozXULElement.insertFTLIfNeeded("toolkit/branding/brandings.ftl");
9692     MozXULElement.insertFTLIfNeeded("browser/confirmationHints.ftl");
9693     document.l10n.setAttributes(this._message, messageId);
9695     if (options.descriptionId) {
9696       document.l10n.setAttributes(this._description, options.descriptionId);
9697       this._description.hidden = false;
9698       this._panel.classList.add("with-description");
9699     } else {
9700       this._description.hidden = true;
9701       this._panel.classList.remove("with-description");
9702     }
9704     this._panel.setAttribute("data-message-id", messageId);
9706     // The timeout value used here allows the panel to stay open for
9707     // 3s after the text transition (duration=120ms) has finished.
9708     // If there is a description, we show for 6s after the text transition.
9709     const DURATION = options.showDescription ? 6000 : 3000;
9710     this._panel.addEventListener(
9711       "popupshown",
9712       () => {
9713         this._animationBox.setAttribute("animate", "true");
9714         this._timerID = setTimeout(() => {
9715           this._panel.hidePopup(true);
9716         }, DURATION + 120);
9717       },
9718       { once: true }
9719     );
9721     this._panel.addEventListener(
9722       "popuphidden",
9723       () => {
9724         // reset the timerId in case our timeout wasn't the cause of the popup being hidden
9725         this._reset();
9726       },
9727       { once: true }
9728     );
9730     this._panel.openPopup(anchor, {
9731       position: options.position ?? "bottomleft topleft",
9732       triggerEvent: options.event,
9733     });
9734   },
9736   _reset() {
9737     if (this._timerID) {
9738       clearTimeout(this._timerID);
9739       this._timerID = null;
9740     }
9741     if (this.__panel) {
9742       this._animationBox.removeAttribute("animate");
9743       this._panel.removeAttribute("data-message-id");
9744     }
9745   },
9747   get _panel() {
9748     this._ensurePanel();
9749     return this.__panel;
9750   },
9752   get _animationBox() {
9753     this._ensurePanel();
9754     delete this._animationBox;
9755     return (this._animationBox = document.getElementById(
9756       "confirmation-hint-checkmark-animation-container"
9757     ));
9758   },
9760   get _message() {
9761     this._ensurePanel();
9762     delete this._message;
9763     return (this._message = document.getElementById(
9764       "confirmation-hint-message"
9765     ));
9766   },
9768   get _description() {
9769     this._ensurePanel();
9770     delete this._description;
9771     return (this._description = document.getElementById(
9772       "confirmation-hint-description"
9773     ));
9774   },
9776   _ensurePanel() {
9777     if (!this.__panel) {
9778       let wrapper = document.getElementById("confirmation-hint-wrapper");
9779       wrapper.replaceWith(wrapper.content);
9780       this.__panel = document.getElementById("confirmation-hint");
9781     }
9782   },
9785 var FirefoxViewHandler = {
9786   tab: null,
9787   BUTTON_ID: "firefox-view-button",
9788   get button() {
9789     return document.getElementById(this.BUTTON_ID);
9790   },
9791   init() {
9792     CustomizableUI.addListener(this);
9794     ChromeUtils.defineESModuleGetters(this, {
9795       SyncedTabs: "resource://services-sync/SyncedTabs.sys.mjs",
9796     });
9797     Services.obs.addObserver(this, "firefoxview-notification-dot-update");
9798   },
9799   uninit() {
9800     CustomizableUI.removeListener(this);
9801     Services.obs.removeObserver(this, "firefoxview-notification-dot-update");
9802   },
9803   onWidgetRemoved(aWidgetId) {
9804     if (aWidgetId == this.BUTTON_ID && this.tab) {
9805       gBrowser.removeTab(this.tab);
9806     }
9807   },
9808   onWidgetAdded(aWidgetId) {
9809     if (aWidgetId === this.BUTTON_ID) {
9810       this.button.removeAttribute("open");
9811     }
9812   },
9813   openTab(section) {
9814     if (!CustomizableUI.getPlacementOfWidget(this.BUTTON_ID)) {
9815       CustomizableUI.addWidgetToArea(
9816         this.BUTTON_ID,
9817         CustomizableUI.AREA_TABSTRIP,
9818         CustomizableUI.getPlacementOfWidget("tabbrowser-tabs").position
9819       );
9820     }
9821     let viewURL = "about:firefoxview";
9822     if (section) {
9823       viewURL = `${viewURL}#${section}`;
9824     }
9825     // Need to account for navigation to Firefox View pages
9826     if (
9827       this.tab &&
9828       this.tab.linkedBrowser.currentURI.spec.split("#")[0] != viewURL
9829     ) {
9830       gBrowser.removeTab(this.tab);
9831       this.tab = null;
9832     }
9833     if (!this.tab) {
9834       this.tab = gBrowser.addTrustedTab(viewURL);
9835       this.tab.addEventListener("TabClose", this, { once: true });
9836       gBrowser.tabContainer.addEventListener("TabSelect", this);
9837       window.addEventListener("activate", this);
9838       gBrowser.hideTab(this.tab);
9839       this.button.setAttribute("aria-controls", this.tab.linkedPanel);
9840     }
9841     // we put this here to avoid a race condition that would occur
9842     // if this was called in response to "TabSelect"
9843     this._closeDeviceConnectedTab();
9844     gBrowser.selectedTab = this.tab;
9845   },
9846   openToolbarMouseEvent(event, section) {
9847     if (event?.type == "mousedown" && event?.button != 0) {
9848       return;
9849     }
9850     this.openTab(section);
9851   },
9852   handleEvent(e) {
9853     switch (e.type) {
9854       case "TabSelect":
9855         const selected = e.target == this.tab;
9856         this.button?.toggleAttribute("open", selected);
9857         this.button?.setAttribute("aria-pressed", selected);
9858         this._recordViewIfTabSelected();
9859         this._onTabForegrounded();
9860         // If Fx View is opened, add temporary style to make first available tab focusable
9861         // When Fx View is closed, remove temporary -moz-user-focus style from first available tab
9862         gBrowser.visibleTabs[0].style.MozUserFocus =
9863           e.target == this.tab ? "normal" : "";
9864         break;
9865       case "TabClose":
9866         this.tab = null;
9867         gBrowser.tabContainer.removeEventListener("TabSelect", this);
9868         this.button?.removeAttribute("aria-controls");
9869         break;
9870       case "activate":
9871         this._onTabForegrounded();
9872         break;
9873     }
9874   },
9875   observe(sub, topic, data) {
9876     switch (topic) {
9877       case "firefoxview-notification-dot-update":
9878         let shouldShow = data === "true";
9879         this._toggleNotificationDot(shouldShow);
9880         break;
9881     }
9882   },
9883   _closeDeviceConnectedTab() {
9884     if (!TabsSetupFlowManager.didFxaTabOpen) {
9885       return;
9886     }
9887     // close the tab left behind after a user pairs a device and
9888     // is redirected back to the Firefox View tab
9889     const fxaRoot = Services.prefs.getCharPref(
9890       "identity.fxaccounts.remote.root"
9891     );
9892     const fxDeviceConnectedTab = gBrowser.tabs.find(tab =>
9893       tab.linkedBrowser.currentURI.displaySpec.startsWith(
9894         `${fxaRoot}pair/auth/complete`
9895       )
9896     );
9898     if (!fxDeviceConnectedTab) {
9899       return;
9900     }
9902     if (gBrowser.tabs.length <= 2) {
9903       // if its the only tab besides the Firefox View tab,
9904       // open a new tab first so the browser doesn't close
9905       gBrowser.addTrustedTab("about:newtab");
9906     }
9907     gBrowser.removeTab(fxDeviceConnectedTab);
9908     TabsSetupFlowManager.didFxaTabOpen = false;
9909   },
9910   _onTabForegrounded() {
9911     if (this.tab?.selected) {
9912       this.SyncedTabs.syncTabs();
9913       Services.obs.notifyObservers(
9914         null,
9915         "firefoxview-notification-dot-update",
9916         "false"
9917       );
9918     }
9919   },
9920   _recordViewIfTabSelected() {
9921     if (this.tab?.selected) {
9922       const PREF_NAME = "browser.firefox-view.view-count";
9923       const MAX_VIEW_COUNT = 10;
9924       let viewCount = Services.prefs.getIntPref(PREF_NAME, 0);
9926       // Record telemetry
9927       Services.telemetry.setEventRecordingEnabled("firefoxview_next", true);
9928       Services.telemetry.recordEvent(
9929         "firefoxview_next",
9930         "tab_selected",
9931         "toolbarbutton",
9932         null,
9933         {}
9934       );
9936       if (viewCount < MAX_VIEW_COUNT) {
9937         Services.prefs.setIntPref(PREF_NAME, viewCount + 1);
9938       }
9939     }
9940   },
9941   _toggleNotificationDot(shouldShow) {
9942     this.button?.toggleAttribute("attention", shouldShow);
9943   },