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