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