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