tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

browser.js (158221B)


      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/. */
      5 
      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"
     11 );
     12 
     13 // lazy module getters
     14 
     15 ChromeUtils.defineESModuleGetters(this, {
     16  AIWindow:
     17    "moz-src:///browser/components/aiwindow/ui/modules/AIWindow.sys.mjs",
     18  AMTelemetry: "resource://gre/modules/AddonManager.sys.mjs",
     19  AboutNewTab: "resource:///modules/AboutNewTab.sys.mjs",
     20  AboutReaderParent: "resource:///actors/AboutReaderParent.sys.mjs",
     21  ActionsProviderContextualSearch:
     22    "moz-src:///browser/components/urlbar/ActionsProviderContextualSearch.sys.mjs",
     23  AddonManager: "resource://gre/modules/AddonManager.sys.mjs",
     24  BrowserTelemetryUtils: "resource://gre/modules/BrowserTelemetryUtils.sys.mjs",
     25  BrowserUIUtils: "resource:///modules/BrowserUIUtils.sys.mjs",
     26  BrowserUsageTelemetry: "resource:///modules/BrowserUsageTelemetry.sys.mjs",
     27  BrowserWindowTracker: "resource:///modules/BrowserWindowTracker.sys.mjs",
     28  CFRPageActions: "resource:///modules/asrouter/CFRPageActions.sys.mjs",
     29  Color: "resource://gre/modules/Color.sys.mjs",
     30  ContentAnalysis:
     31    "moz-src:///browser/components/contentanalysis/content/ContentAnalysis.sys.mjs",
     32  ContextualIdentityService:
     33    "resource://gre/modules/ContextualIdentityService.sys.mjs",
     34  CustomizableUI:
     35    "moz-src:///browser/components/customizableui/CustomizableUI.sys.mjs",
     36  DevToolsSocketStatus:
     37    "resource://devtools/shared/security/DevToolsSocketStatus.sys.mjs",
     38  DownloadUtils: "resource://gre/modules/DownloadUtils.sys.mjs",
     39  DownloadsCommon:
     40    "moz-src:///browser/components/downloads/DownloadsCommon.sys.mjs",
     41  E10SUtils: "resource://gre/modules/E10SUtils.sys.mjs",
     42  ExtensionsUI: "resource:///modules/ExtensionsUI.sys.mjs",
     43  HomePage: "resource:///modules/HomePage.sys.mjs",
     44  LightweightThemeConsumer:
     45    "resource://gre/modules/LightweightThemeConsumer.sys.mjs",
     46  LoginHelper: "resource://gre/modules/LoginHelper.sys.mjs",
     47  LoginManagerParent: "resource://gre/modules/LoginManagerParent.sys.mjs",
     48  MigrationUtils: "resource:///modules/MigrationUtils.sys.mjs",
     49  NetUtil: "resource://gre/modules/NetUtil.sys.mjs",
     50  NewTabPagePreloading:
     51    "moz-src:///browser/components/tabbrowser/NewTabPagePreloading.sys.mjs",
     52  NewTabUtils: "resource://gre/modules/NewTabUtils.sys.mjs",
     53  NimbusFeatures: "resource://nimbus/ExperimentAPI.sys.mjs",
     54  nsContextMenu: "chrome://browser/content/nsContextMenu.sys.mjs",
     55  OnionLocationParent: "resource:///modules/OnionLocationParent.sys.mjs",
     56  OpenInTabsUtils:
     57    "moz-src:///browser/components/tabbrowser/OpenInTabsUtils.sys.mjs",
     58  OpenSearchManager:
     59    "moz-src:///browser/components/search/OpenSearchManager.sys.mjs",
     60  PageActions: "resource:///modules/PageActions.sys.mjs",
     61  PageThumbs: "resource://gre/modules/PageThumbs.sys.mjs",
     62  PanelMultiView:
     63    "moz-src:///browser/components/customizableui/PanelMultiView.sys.mjs",
     64  PanelView:
     65    "moz-src:///browser/components/customizableui/PanelMultiView.sys.mjs",
     66  PictureInPicture: "resource://gre/modules/PictureInPicture.sys.mjs",
     67  PlacesTransactions: "resource://gre/modules/PlacesTransactions.sys.mjs",
     68  PlacesUIUtils: "moz-src:///browser/components/places/PlacesUIUtils.sys.mjs",
     69  PlacesUtils: "resource://gre/modules/PlacesUtils.sys.mjs",
     70  PopupAndRedirectBlockerObserver:
     71    "resource:///modules/PopupAndRedirectBlockerObserver.sys.mjs",
     72  PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.sys.mjs",
     73  PrivateBrowsingUI: "moz-src:///browser/modules/PrivateBrowsingUI.sys.mjs",
     74  ProcessHangMonitor: "resource:///modules/ProcessHangMonitor.sys.mjs",
     75  ProfilesDatastoreService:
     76    "moz-src:///toolkit/profile/ProfilesDatastoreService.sys.mjs",
     77  PromptUtils: "resource://gre/modules/PromptUtils.sys.mjs",
     78  ReaderMode: "moz-src:///toolkit/components/reader/ReaderMode.sys.mjs",
     79  ResetPBMPanel:
     80    "moz-src:///browser/components/privatebrowsing/ResetPBMPanel.sys.mjs",
     81  SafeBrowsing: "resource://gre/modules/SafeBrowsing.sys.mjs",
     82  Sanitizer: "resource:///modules/Sanitizer.sys.mjs",
     83  ScreenshotsUtils: "resource:///modules/ScreenshotsUtils.sys.mjs",
     84  SearchUIUtils: "moz-src:///browser/components/search/SearchUIUtils.sys.mjs",
     85  SelectableProfileService:
     86    "resource:///modules/profiles/SelectableProfileService.sys.mjs",
     87  SessionStartup: "resource:///modules/sessionstore/SessionStartup.sys.mjs",
     88  SessionStore: "resource:///modules/sessionstore/SessionStore.sys.mjs",
     89  SessionWindowUI: "resource:///modules/sessionstore/SessionWindowUI.sys.mjs",
     90  SharingUtils: "resource:///modules/SharingUtils.sys.mjs",
     91  ShortcutUtils: "resource://gre/modules/ShortcutUtils.sys.mjs",
     92  SiteDataManager: "resource:///modules/SiteDataManager.sys.mjs",
     93  SitePermissions: "resource:///modules/SitePermissions.sys.mjs",
     94  SubDialog: "resource://gre/modules/SubDialog.sys.mjs",
     95  SubDialogManager: "resource://gre/modules/SubDialog.sys.mjs",
     96  TabCrashHandler: "resource:///modules/ContentCrashHandlers.sys.mjs",
     97  TabsSetupFlowManager:
     98    "resource:///modules/firefox-view-tabs-setup-manager.sys.mjs",
     99  TaskbarTabsChrome:
    100    "resource:///modules/taskbartabs/TaskbarTabsChrome.sys.mjs",
    101  TelemetryEnvironment: "resource://gre/modules/TelemetryEnvironment.sys.mjs",
    102  ToolbarContextMenu:
    103    "moz-src:///browser/components/customizableui/ToolbarContextMenu.sys.mjs",
    104  ToolbarDropHandler:
    105    "moz-src:///browser/components/customizableui/ToolbarDropHandler.sys.mjs",
    106  ToolbarIconColor: "moz-src:///browser/themes/ToolbarIconColor.sys.mjs",
    107  TorConnect: "resource://gre/modules/TorConnect.sys.mjs",
    108  TorConnectStage: "resource://gre/modules/TorConnect.sys.mjs",
    109  TorConnectTopics: "resource://gre/modules/TorConnect.sys.mjs",
    110  TorConnectParent: "resource://gre/actors/TorConnectParent.sys.mjs",
    111  TorDomainIsolator: "resource://gre/modules/TorDomainIsolator.sys.mjs",
    112  TorUIUtils: "resource:///modules/TorUIUtils.sys.mjs",
    113  TranslationsParent: "resource://gre/actors/TranslationsParent.sys.mjs",
    114  UITour: "moz-src:///browser/components/uitour/UITour.sys.mjs",
    115  UpdateUtils: "resource://gre/modules/UpdateUtils.sys.mjs",
    116  URILoadingHelper: "resource:///modules/URILoadingHelper.sys.mjs",
    117  UrlbarPrefs: "moz-src:///browser/components/urlbar/UrlbarPrefs.sys.mjs",
    118  UrlbarProviderSearchTips:
    119    "moz-src:///browser/components/urlbar/UrlbarProviderSearchTips.sys.mjs",
    120  UrlbarTokenizer:
    121    "moz-src:///browser/components/urlbar/UrlbarTokenizer.sys.mjs",
    122  UrlbarUtils: "moz-src:///browser/components/urlbar/UrlbarUtils.sys.mjs",
    123  Weave: "resource://services-sync/main.sys.mjs",
    124  WebNavigationFrames: "resource://gre/modules/WebNavigationFrames.sys.mjs",
    125  webrtcUI: "resource:///modules/webrtcUI.sys.mjs",
    126  WebsiteFilter: "resource:///modules/policies/WebsiteFilter.sys.mjs",
    127  ZoomUI: "resource:///modules/ZoomUI.sys.mjs",
    128 });
    129 
    130 ChromeUtils.defineLazyGetter(this, "fxAccounts", () => {
    131  return ChromeUtils.importESModule(
    132    "resource://gre/modules/FxAccounts.sys.mjs"
    133  ).getFxAccountsSingleton();
    134 });
    135 
    136 XPCOMUtils.defineLazyScriptGetter(
    137  this,
    138  ["BrowserCommands", "kSkipCacheFlags"],
    139  "chrome://browser/content/browser-commands.js"
    140 );
    141 
    142 XPCOMUtils.defineLazyScriptGetter(
    143  this,
    144  "PlacesTreeView",
    145  "chrome://browser/content/places/treeView.js"
    146 );
    147 XPCOMUtils.defineLazyScriptGetter(
    148  this,
    149  ["PlacesInsertionPoint", "PlacesController", "PlacesControllerDragHelper"],
    150  "chrome://browser/content/places/controller.js"
    151 );
    152 XPCOMUtils.defineLazyScriptGetter(
    153  this,
    154  "PrintUtils",
    155  "chrome://global/content/printUtils.js"
    156 );
    157 XPCOMUtils.defineLazyScriptGetter(
    158  this,
    159  "ZoomManager",
    160  "chrome://global/content/viewZoomOverlay.js"
    161 );
    162 XPCOMUtils.defineLazyScriptGetter(
    163  this,
    164  "FullZoom",
    165  "chrome://browser/content/tabbrowser/browser-fullZoom.js"
    166 );
    167 XPCOMUtils.defineLazyScriptGetter(
    168  this,
    169  "PanelUI",
    170  "chrome://browser/content/customizableui/panelUI.js"
    171 );
    172 XPCOMUtils.defineLazyScriptGetter(
    173  this,
    174  "gViewSourceUtils",
    175  "chrome://global/content/viewSourceUtils.js"
    176 );
    177 XPCOMUtils.defineLazyScriptGetter(
    178  this,
    179  "gTabsPanel",
    180  "chrome://browser/content/tabbrowser/browser-allTabsMenu.js"
    181 );
    182 XPCOMUtils.defineLazyScriptGetter(
    183  this,
    184  [
    185    "BrowserAddonUI",
    186    "gExtensionsNotifications",
    187    "gUnifiedExtensions",
    188    "gXPInstallObserver",
    189  ],
    190  "chrome://browser/content/browser-addons.js"
    191 );
    192 XPCOMUtils.defineLazyScriptGetter(
    193  this,
    194  "ctrlTab",
    195  "chrome://browser/content/tabbrowser/browser-ctrlTab.js"
    196 );
    197 XPCOMUtils.defineLazyScriptGetter(
    198  this,
    199  ["CustomizationHandler", "AutoHideMenubar"],
    200  "chrome://browser/content/browser-customization.js"
    201 );
    202 XPCOMUtils.defineLazyScriptGetter(
    203  this,
    204  ["PointerLock", "FullScreen"],
    205  "chrome://browser/content/browser-fullScreenAndPointerLock.js"
    206 );
    207 XPCOMUtils.defineLazyScriptGetter(
    208  this,
    209  "gIdentityHandler",
    210  "chrome://browser/content/browser-siteIdentity.js"
    211 );
    212 XPCOMUtils.defineLazyScriptGetter(
    213  this,
    214  "gPermissionPanel",
    215  "chrome://browser/content/browser-sitePermissionPanel.js"
    216 );
    217 XPCOMUtils.defineLazyScriptGetter(
    218  this,
    219  "SelectTranslationsPanel",
    220  "chrome://browser/content/translations/selectTranslationsPanel.js"
    221 );
    222 XPCOMUtils.defineLazyScriptGetter(
    223  this,
    224  "FullPageTranslationsPanel",
    225  "chrome://browser/content/translations/fullPageTranslationsPanel.js"
    226 );
    227 XPCOMUtils.defineLazyScriptGetter(
    228  this,
    229  "gProtectionsHandler",
    230  "chrome://browser/content/browser-siteProtections.js"
    231 );
    232 XPCOMUtils.defineLazyScriptGetter(
    233  this,
    234  "gTrustPanelHandler",
    235  "chrome://browser/content/browser-trustPanel.js"
    236 );
    237 XPCOMUtils.defineLazyScriptGetter(
    238  this,
    239  ["gGestureSupport", "gHistorySwipeAnimation"],
    240  "chrome://browser/content/browser-gestureSupport.js"
    241 );
    242 XPCOMUtils.defineLazyScriptGetter(
    243  this,
    244  "gSafeBrowsing",
    245  "chrome://browser/content/browser-safebrowsing.js"
    246 );
    247 XPCOMUtils.defineLazyScriptGetter(
    248  this,
    249  "gSync",
    250  "chrome://browser/content/browser-sync.js"
    251 );
    252 XPCOMUtils.defineLazyScriptGetter(
    253  this,
    254  "gBrowserThumbnails",
    255  "chrome://browser/content/browser-thumbnails.js"
    256 );
    257 XPCOMUtils.defineLazyScriptGetter(
    258  this,
    259  [
    260    "DownloadsPanel",
    261    "DownloadsOverlayLoader",
    262    "DownloadsView",
    263    "DownloadsViewUI",
    264    "DownloadsViewController",
    265    "DownloadsSummary",
    266    "DownloadsFooter",
    267    "DownloadsBlockedSubview",
    268  ],
    269  "chrome://browser/content/downloads/downloads.js"
    270 );
    271 XPCOMUtils.defineLazyScriptGetter(
    272  this,
    273  ["DownloadsButton", "DownloadsIndicatorView"],
    274  "chrome://browser/content/downloads/indicator.js"
    275 );
    276 XPCOMUtils.defineLazyScriptGetter(
    277  this,
    278  ["SecurityLevelButton"],
    279  "chrome://browser/content/securitylevel/securityLevel.js"
    280 );
    281 XPCOMUtils.defineLazyScriptGetter(
    282  this,
    283  ["NewIdentityButton"],
    284  "chrome://browser/content/newidentity.js"
    285 );
    286 XPCOMUtils.defineLazyScriptGetter(
    287  this,
    288  ["OnionAuthPrompt"],
    289  "chrome://browser/content/onionservices/authPrompt.js"
    290 );
    291 XPCOMUtils.defineLazyScriptGetter(
    292  this,
    293  "gEditItemOverlay",
    294  "chrome://browser/content/places/editBookmark.js"
    295 );
    296 XPCOMUtils.defineLazyScriptGetter(
    297  this,
    298  "gGfxUtils",
    299  "chrome://browser/content/browser-graphics-utils.js"
    300 );
    301 XPCOMUtils.defineLazyScriptGetter(
    302  this,
    303  "ToolbarKeyboardNavigator",
    304  "chrome://browser/content/browser-toolbarKeyNav.js"
    305 );
    306 XPCOMUtils.defineLazyScriptGetter(
    307  this,
    308  "A11yUtils",
    309  "chrome://browser/content/browser-a11yUtils.js"
    310 );
    311 XPCOMUtils.defineLazyScriptGetter(
    312  this,
    313  "gSharedTabWarning",
    314  "chrome://browser/content/browser-webrtc.js"
    315 );
    316 XPCOMUtils.defineLazyScriptGetter(
    317  this,
    318  "gPageStyleMenu",
    319  "chrome://browser/content/browser-pagestyle.js"
    320 );
    321 XPCOMUtils.defineLazyScriptGetter(
    322  this,
    323  "gProfiles",
    324  "chrome://browser/content/browser-profiles.js"
    325 );
    326 XPCOMUtils.defineLazyScriptGetter(
    327  this,
    328  ["gTorConnectUrlbarButton"],
    329  "chrome://global/content/torconnect/torConnectUrlbarButton.js"
    330 );
    331 XPCOMUtils.defineLazyScriptGetter(
    332  this,
    333  ["gTorConnectTitlebarStatus"],
    334  "chrome://global/content/torconnect/torConnectTitlebarStatus.js"
    335 );
    336 XPCOMUtils.defineLazyScriptGetter(
    337  this,
    338  ["gTorCircuitPanel"],
    339  "chrome://browser/content/torCircuitPanel.js"
    340 );
    341 
    342 // lazy service getters
    343 
    344 XPCOMUtils.defineLazyServiceGetters(this, {
    345  ContentPrefService2: [
    346    "@mozilla.org/content-pref/service;1",
    347    Ci.nsIContentPrefService2,
    348  ],
    349  classifierService: [
    350    "@mozilla.org/url-classifier/dbservice;1",
    351    Ci.nsIURIClassifier,
    352  ],
    353  Favicons: ["@mozilla.org/browser/favicon-service;1", Ci.nsIFaviconService],
    354  WindowsUIUtils: ["@mozilla.org/windows-ui-utils;1", Ci.nsIWindowsUIUtils],
    355  BrowserHandler: ["@mozilla.org/browser/clh;1", Ci.nsIBrowserHandler],
    356 });
    357 
    358 if (AppConstants.ENABLE_WEBDRIVER) {
    359  XPCOMUtils.defineLazyServiceGetter(
    360    this,
    361    "Marionette",
    362    "@mozilla.org/remote/marionette;1",
    363    Ci.nsIMarionette
    364  );
    365 
    366  XPCOMUtils.defineLazyServiceGetter(
    367    this,
    368    "RemoteAgent",
    369    "@mozilla.org/remote/agent;1",
    370    Ci.nsIRemoteAgent
    371  );
    372 } else {
    373  this.Marionette = { running: false };
    374  this.RemoteAgent = { running: false };
    375 }
    376 
    377 ChromeUtils.defineLazyGetter(this, "RTL_UI", () => {
    378  return Services.locale.isAppLocaleRTL;
    379 });
    380 function gLocaleChangeObserver() {
    381  delete window.RTL_UI;
    382  window.RTL_UI = Services.locale.isAppLocaleRTL;
    383 }
    384 
    385 ChromeUtils.defineLazyGetter(this, "gBrandBundle", () => {
    386  return Services.strings.createBundle(
    387    "chrome://branding/locale/brand.properties"
    388  );
    389 });
    390 
    391 ChromeUtils.defineLazyGetter(this, "gBrowserBundle", () => {
    392  return Services.strings.createBundle(
    393    "chrome://browser/locale/browser.properties"
    394  );
    395 });
    396 
    397 ChromeUtils.defineLazyGetter(this, "gCustomizeMode", () => {
    398  let { CustomizeMode } = ChromeUtils.importESModule(
    399    "moz-src:///browser/components/customizableui/CustomizeMode.sys.mjs"
    400  );
    401  return new CustomizeMode(window);
    402 });
    403 
    404 ChromeUtils.defineLazyGetter(this, "gNavToolbox", () => {
    405  return document.getElementById("navigator-toolbox");
    406 });
    407 
    408 ChromeUtils.defineLazyGetter(this, "gURLBar", () => {
    409  let urlbar = document.getElementById("urlbar");
    410 
    411  let beforeFocusOrSelect = event => {
    412    // In customize mode, the url bar is disabled. If a new tab is opened or the
    413    // user switches to a different tab, this function gets called before we've
    414    // finished leaving customize mode, and the url bar will still be disabled.
    415    // We can't focus it when it's disabled, so we need to re-run ourselves when
    416    // we've finished leaving customize mode.
    417    if (
    418      CustomizationHandler.isCustomizing() ||
    419      CustomizationHandler.isExitingCustomizeMode
    420    ) {
    421      gNavToolbox.addEventListener(
    422        "aftercustomization",
    423        () => {
    424          if (event.type == "beforeselect") {
    425            gURLBar.select();
    426          } else {
    427            gURLBar.focus();
    428          }
    429        },
    430        {
    431          once: true,
    432        }
    433      );
    434      event.preventDefault();
    435      return;
    436    }
    437 
    438    if (window.fullScreen) {
    439      FullScreen.showNavToolbox();
    440    }
    441  };
    442  urlbar.addEventListener("beforefocus", beforeFocusOrSelect);
    443  urlbar.addEventListener("beforeselect", beforeFocusOrSelect);
    444 
    445  return urlbar;
    446 });
    447 
    448 // High priority notification bars shown at the top of the window.
    449 ChromeUtils.defineLazyGetter(this, "gNotificationBox", () => {
    450  let securityDelayMS = Services.prefs.getIntPref(
    451    "security.notification_enable_delay"
    452  );
    453 
    454  return new MozElements.NotificationBox(element => {
    455    element.classList.add("global-notificationbox");
    456    element.setAttribute("notificationside", "top");
    457    element.setAttribute("prepend-notifications", true);
    458    // We want this before the tab notifications.
    459    document.getElementById("notifications-toolbar").prepend(element);
    460  }, securityDelayMS);
    461 });
    462 
    463 ChromeUtils.defineLazyGetter(this, "InlineSpellCheckerUI", () => {
    464  let { InlineSpellChecker } = ChromeUtils.importESModule(
    465    "resource://gre/modules/InlineSpellChecker.sys.mjs"
    466  );
    467  return new InlineSpellChecker();
    468 });
    469 
    470 ChromeUtils.defineLazyGetter(this, "PopupNotifications", () => {
    471  // eslint-disable-next-line no-shadow
    472  let { PopupNotifications } = ChromeUtils.importESModule(
    473    "resource://gre/modules/PopupNotifications.sys.mjs"
    474  );
    475  try {
    476    // Hide all PopupNotifications while the the address bar has focus,
    477    // including the virtual focus in the results popup, and the URL is being
    478    // edited or the page proxy state is invalid while async tab switching.
    479    let shouldSuppress = () => {
    480      // "Blank" pages, like about:welcome, have a pageproxystate of "invalid", but
    481      // popups like CFRs should not automatically be suppressed when the address
    482      // bar has focus on these pages as it disrupts user navigation using FN+F6.
    483      // See `UrlbarInput.setURI()` where pageproxystate is set to "invalid" for
    484      // all pages that the "isBlankPageURL" method returns true for.
    485      const urlBarEdited = isBlankPageURL(gBrowser.currentURI.spec)
    486        ? gURLBar.hasAttribute("usertyping")
    487        : gURLBar.getAttribute("pageproxystate") != "valid";
    488      return (
    489        (urlBarEdited && gURLBar.focused) ||
    490        (gURLBar.getAttribute("pageproxystate") != "valid" &&
    491          gBrowser.selectedBrowser._awaitingSetURI) ||
    492        shouldSuppressPopupNotifications()
    493      );
    494    };
    495 
    496    // Before a Popup is shown, check that its anchor is visible.
    497    // If the anchor is not visible, use one of the fallbacks.
    498    // If no fallbacks are visible, return null.
    499    const getVisibleAnchorElement = anchorElement => {
    500      // If the anchor element is present in the Urlbar,
    501      // ensure that both the anchor and page URL are visible.
    502      gURLBar.maybeHandleRevertFromPopup(anchorElement);
    503      anchorElement?.dispatchEvent(
    504        new CustomEvent("PopupNotificationsBeforeAnchor", { bubbles: true })
    505      );
    506      if (anchorElement?.checkVisibility()) {
    507        return anchorElement;
    508      }
    509      let fallback = [
    510        document.getElementById("trust-icon-container"),
    511        gURLBar.querySelector(".searchmode-switcher-icon"),
    512        document.getElementById("identity-icon"),
    513        document.getElementById("remote-control-icon"),
    514      ];
    515      return fallback.find(element => element?.checkVisibility()) ?? null;
    516    };
    517 
    518    return new PopupNotifications(
    519      gBrowser,
    520      document.getElementById("notification-popup"),
    521      document.getElementById("notification-popup-box"),
    522      { shouldSuppress, getVisibleAnchorElement }
    523    );
    524  } catch (ex) {
    525    console.error(ex);
    526    return null;
    527  }
    528 });
    529 
    530 ChromeUtils.defineLazyGetter(this, "MacUserActivityUpdater", () => {
    531  if (AppConstants.platform != "macosx") {
    532    return null;
    533  }
    534 
    535  return Cc["@mozilla.org/widget/macuseractivityupdater;1"].getService(
    536    Ci.nsIMacUserActivityUpdater
    537  );
    538 });
    539 
    540 ChromeUtils.defineLazyGetter(this, "Win7Features", () => {
    541  if (AppConstants.platform != "win") {
    542    return null;
    543  }
    544 
    545  const WINTASKBAR_CONTRACTID = "@mozilla.org/windows-taskbar;1";
    546  if (
    547    WINTASKBAR_CONTRACTID in Cc &&
    548    Cc[WINTASKBAR_CONTRACTID].getService(Ci.nsIWinTaskbar).available
    549  ) {
    550    let { AeroPeek } = ChromeUtils.importESModule(
    551      "resource:///modules/WindowsPreviewPerTab.sys.mjs"
    552    );
    553    return {
    554      onOpenWindow() {
    555        AeroPeek.onOpenWindow(window);
    556        this.handledOpening = true;
    557      },
    558      onCloseWindow() {
    559        if (this.handledOpening) {
    560          AeroPeek.onCloseWindow(window);
    561        }
    562      },
    563      handledOpening: false,
    564    };
    565  }
    566  return null;
    567 });
    568 
    569 ChromeUtils.defineLazyGetter(this, "gRestoreLastSessionObserver", () => {
    570  let { RestoreLastSessionObserver } = ChromeUtils.importESModule(
    571    "resource:///modules/sessionstore/SessionWindowUI.sys.mjs"
    572  );
    573  return new RestoreLastSessionObserver(window);
    574 });
    575 
    576 XPCOMUtils.defineLazyPreferenceGetter(
    577  this,
    578  "gToolbarKeyNavEnabled",
    579  "browser.toolbars.keyboard_navigation",
    580  false,
    581  (aPref, aOldVal, aNewVal) => {
    582    if (window.closed) {
    583      return;
    584    }
    585    if (aNewVal) {
    586      ToolbarKeyboardNavigator.init();
    587    } else {
    588      ToolbarKeyboardNavigator.uninit();
    589    }
    590  }
    591 );
    592 
    593 XPCOMUtils.defineLazyPreferenceGetter(
    594  this,
    595  "gBookmarksToolbarVisibility",
    596  "browser.toolbars.bookmarks.visibility",
    597  "newtab"
    598 );
    599 
    600 XPCOMUtils.defineLazyPreferenceGetter(
    601  this,
    602  "gFxaToolbarEnabled",
    603  "identity.fxaccounts.toolbar.enabled",
    604  false,
    605  (aPref, aOldVal, aNewVal) => {
    606    updateFxaToolbarMenu(aNewVal);
    607  }
    608 );
    609 
    610 XPCOMUtils.defineLazyPreferenceGetter(
    611  this,
    612  "gFxaToolbarAccessed",
    613  "identity.fxaccounts.toolbar.accessed",
    614  false,
    615  () => {
    616    updateFxaToolbarMenu(gFxaToolbarEnabled);
    617  }
    618 );
    619 
    620 XPCOMUtils.defineLazyPreferenceGetter(
    621  this,
    622  "gAddonAbuseReportEnabled",
    623  "extensions.abuseReport.enabled",
    624  false
    625 );
    626 
    627 XPCOMUtils.defineLazyPreferenceGetter(
    628  this,
    629  "gMiddleClickNewTabUsesPasteboard",
    630  "browser.tabs.searchclipboardfor.middleclick",
    631  true
    632 );
    633 
    634 XPCOMUtils.defineLazyPreferenceGetter(
    635  this,
    636  "gPrintEnabled",
    637  "print.enabled",
    638  false,
    639  (aPref, aOldVal, aNewVal) => {
    640    updatePrintCommands(aNewVal);
    641  }
    642 );
    643 
    644 XPCOMUtils.defineLazyPreferenceGetter(
    645  this,
    646  "gTranslationsEnabled",
    647  "browser.translations.enable",
    648  false
    649 );
    650 
    651 XPCOMUtils.defineLazyPreferenceGetter(
    652  this,
    653  "gUseFeltPrivacyUI",
    654  "browser.privatebrowsing.felt-privacy-v1",
    655  false
    656 );
    657 
    658 customElements.setElementCreationCallback("screenshots-buttons", () => {
    659  Services.scriptloader.loadSubScript(
    660    "chrome://browser/content/screenshots/screenshots-buttons.js",
    661    window
    662  );
    663 });
    664 
    665 customElements.setElementCreationCallback("menu-message", () => {
    666  ChromeUtils.importESModule(
    667    "chrome://browser/content/asrouter/components/menu-message.mjs",
    668    { global: "current" }
    669  );
    670 });
    671 
    672 customElements.setElementCreationCallback("webrtc-preview", () => {
    673  ChromeUtils.importESModule(
    674    "chrome://browser/content/webrtc/webrtc-preview.mjs",
    675    { global: "current" }
    676  );
    677 });
    678 
    679 var gBrowser;
    680 var gContextMenu = null; // nsContextMenu instance
    681 var gMultiProcessBrowser = window.docShell.QueryInterface(
    682  Ci.nsILoadContext
    683 ).useRemoteTabs;
    684 var gFissionBrowser = window.docShell.QueryInterface(
    685  Ci.nsILoadContext
    686 ).useRemoteSubframes;
    687 
    688 var gBrowserAllowScriptsToCloseInitialTabs = false;
    689 
    690 if (AppConstants.platform != "macosx") {
    691  var gEditUIVisible = true;
    692 }
    693 
    694 Object.defineProperty(this, "gReduceMotion", {
    695  enumerable: true,
    696  get() {
    697    return typeof gReduceMotionOverride == "boolean"
    698      ? gReduceMotionOverride
    699      : gReduceMotionSetting;
    700  },
    701 });
    702 // Reduce motion during startup. The setting will be reset later.
    703 let gReduceMotionSetting = true;
    704 // This is for tests to set.
    705 var gReduceMotionOverride;
    706 
    707 // Smart getter for the findbar.  If you don't wish to force the creation of
    708 // the findbar, check gFindBarInitialized first.
    709 
    710 Object.defineProperty(this, "gFindBar", {
    711  enumerable: true,
    712  get() {
    713    return gBrowser.getCachedFindBar();
    714  },
    715 });
    716 
    717 Object.defineProperty(this, "gFindBarInitialized", {
    718  enumerable: true,
    719  get() {
    720    return gBrowser.isFindBarInitialized();
    721  },
    722 });
    723 
    724 Object.defineProperty(this, "gFindBarPromise", {
    725  enumerable: true,
    726  get() {
    727    return gBrowser.getFindBar();
    728  },
    729 });
    730 
    731 function shouldSuppressPopupNotifications() {
    732  // We have to hide notifications explicitly when the window is
    733  // minimized because of the effects of the "noautohide" attribute on Linux.
    734  // This can be removed once bug 545265 and bug 1320361 are fixed.
    735  // Hide popup notifications when system tab prompts are shown so they
    736  // don't cover up the prompt.
    737  return (
    738    window.windowState == window.STATE_MINIMIZED ||
    739    gBrowser?.selectedBrowser.hasAttribute("tabDialogShowing") ||
    740    gDialogBox?.isOpen
    741  );
    742 }
    743 
    744 async function gLazyFindCommand(cmd, ...args) {
    745  let fb = await gFindBarPromise;
    746  // We could be closed by now, or the tab with XBL binding could have gone away:
    747  if (fb && fb[cmd]) {
    748    fb[cmd].apply(fb, args);
    749  }
    750 }
    751 
    752 var gPageIcons = {
    753  "about:tor": "chrome://branding/content/icon32.png",
    754  "about:home": "chrome://branding/content/icon32.png",
    755  "about:newtab": "chrome://branding/content/icon32.png",
    756  "about:opentabs": "chrome://branding/content/icon32.png",
    757  "about:welcome": "chrome://branding/content/icon32.png",
    758  "about:privatebrowsing": "chrome://browser/skin/privatebrowsing/favicon.svg",
    759 };
    760 
    761 var gInitialPages = [
    762  "about:tor",
    763  "about:torconnect",
    764  "about:blank",
    765  "about:home",
    766  "about:firefoxview",
    767  "about:newtab",
    768  "about:opentabs",
    769  "about:privatebrowsing",
    770  "about:sessionrestore",
    771  "about:welcome",
    772  "about:welcomeback",
    773  "chrome://browser/content/blanktab.html",
    774 ];
    775 
    776 function isInitialPage(url) {
    777  if (!(url instanceof Ci.nsIURI)) {
    778    try {
    779      url = Services.io.newURI(url);
    780    } catch (ex) {
    781      return false;
    782    }
    783  }
    784 
    785  let nonQuery = url.prePath + url.filePath;
    786  return gInitialPages.includes(nonQuery) || nonQuery == BROWSER_NEW_TAB_URL;
    787 }
    788 
    789 function browserWindows() {
    790  return Services.wm.getEnumerator("navigator:browser");
    791 }
    792 
    793 function updateBookmarkToolbarVisibility() {
    794  BookmarkingUI.updateEmptyToolbarMessage();
    795  setToolbarVisibility(
    796    BookmarkingUI.toolbar,
    797    gBookmarksToolbarVisibility,
    798    false,
    799    false
    800  );
    801 }
    802 
    803 // This is a stringbundle-like interface to gBrowserBundle, formerly a getter for
    804 // the "bundle_browser" element.
    805 var gNavigatorBundle = {
    806  getString(key) {
    807    return gBrowserBundle.GetStringFromName(key);
    808  },
    809  getFormattedString(key, array) {
    810    return gBrowserBundle.formatStringFromName(key, array);
    811  },
    812 };
    813 
    814 function updateFxaToolbarMenu(enable, isInitialUpdate = false) {
    815  // We only show the Firefox Account toolbar menu if the feature is enabled and
    816  // if sync is enabled.
    817  const syncEnabled = Services.prefs.getBoolPref(
    818    "identity.fxaccounts.enabled",
    819    false
    820  );
    821 
    822  const mainWindowEl = document.documentElement;
    823  const fxaPanelEl = PanelMultiView.getViewNode(document, "PanelUI-fxa");
    824  const taskbarTab = mainWindowEl.hasAttribute("taskbartab");
    825 
    826  // To minimize the toolbar button flickering or appearing/disappearing during startup,
    827  // we use this pref to anticipate the likely FxA status.
    828  const statusGuess = !!Services.prefs.getStringPref(
    829    "identity.fxaccounts.account.device.name",
    830    ""
    831  );
    832  mainWindowEl.setAttribute(
    833    "fxastatus",
    834    statusGuess ? "signed_in" : "not_configured"
    835  );
    836 
    837  fxaPanelEl.addEventListener("ViewShowing", gSync.updateSendToDeviceTitle);
    838 
    839  if (enable && syncEnabled && !taskbarTab) {
    840    mainWindowEl.setAttribute("fxatoolbarmenu", "visible");
    841 
    842    // We have to manually update the sync state UI when toggling the FxA toolbar
    843    // because it could show an invalid icon if the user is logged in and no sync
    844    // event was performed yet.
    845    if (!isInitialUpdate) {
    846      gSync.maybeUpdateUIState();
    847    }
    848  } else {
    849    mainWindowEl.removeAttribute("fxatoolbarmenu");
    850  }
    851 }
    852 
    853 function UpdateBackForwardCommands(aWebNavigation) {
    854  var backCommand = document.getElementById("Browser:Back");
    855  var forwardCommand = document.getElementById("Browser:Forward");
    856 
    857  // Avoid setting attributes on commands if the value hasn't changed!
    858  // Remember, guys, setting attributes on elements is expensive!  They
    859  // get inherited into anonymous content, broadcast to other widgets, etc.!
    860  // Don't do it if the value hasn't changed! - dwh
    861 
    862  var backDisabled = backCommand.hasAttribute("disabled");
    863  var forwardDisabled = forwardCommand.hasAttribute("disabled");
    864  if (backDisabled == aWebNavigation.canGoBack) {
    865    if (backDisabled) {
    866      backCommand.removeAttribute("disabled");
    867    } else {
    868      backCommand.setAttribute("disabled", true);
    869    }
    870  }
    871 
    872  if (forwardDisabled == aWebNavigation.canGoForward) {
    873    if (forwardDisabled) {
    874      forwardCommand.removeAttribute("disabled");
    875    } else {
    876      forwardCommand.setAttribute("disabled", true);
    877    }
    878  }
    879 }
    880 
    881 function updatePrintCommands(enabled) {
    882  var printCommand = document.getElementById("cmd_print");
    883  var printPreviewCommand = document.getElementById("cmd_printPreviewToggle");
    884 
    885  if (enabled) {
    886    printCommand.removeAttribute("disabled");
    887    printPreviewCommand.removeAttribute("disabled");
    888  } else {
    889    printCommand.setAttribute("disabled", "true");
    890    printPreviewCommand.setAttribute("disabled", "true");
    891  }
    892 }
    893 
    894 /**
    895 * Click-and-Hold implementation for the Back and Forward buttons
    896 * XXXmano: should this live in toolbarbutton.js?
    897 */
    898 function SetClickAndHoldHandlers() {
    899  // Bug 414797: Clone the back/forward buttons' context menu into both buttons.
    900  let popup = document.getElementById("backForwardMenu").cloneNode(true);
    901  popup.removeAttribute("id");
    902  // Prevent the back/forward buttons' context attributes from being inherited.
    903  popup.setAttribute("context", "");
    904 
    905  function backForwardMenuCommand(event) {
    906    BrowserCommands.gotoHistoryIndex(event);
    907    // event.stopPropagation is here for the cloned version
    908    // to prevent already-handled clicks on menu items from
    909    // propagating to the back or forward button.
    910    event.stopPropagation();
    911  }
    912 
    913  let backButton = document.getElementById("back-button");
    914  backButton.setAttribute("type", "menu");
    915  popup.addEventListener("command", backForwardMenuCommand);
    916  popup.addEventListener("popupshowing", FillHistoryMenu);
    917  backButton.prepend(popup);
    918  gClickAndHoldListenersOnElement.add(backButton);
    919 
    920  let forwardButton = document.getElementById("forward-button");
    921  popup = popup.cloneNode(true);
    922  forwardButton.setAttribute("type", "menu");
    923  popup.addEventListener("command", backForwardMenuCommand);
    924  popup.addEventListener("popupshowing", FillHistoryMenu);
    925  forwardButton.prepend(popup);
    926  gClickAndHoldListenersOnElement.add(forwardButton);
    927 }
    928 
    929 const gClickAndHoldListenersOnElement = {
    930  _timers: new Map(),
    931 
    932  _mousedownHandler(aEvent) {
    933    if (
    934      aEvent.button != 0 ||
    935      aEvent.currentTarget.open ||
    936      aEvent.currentTarget.disabled
    937    ) {
    938      return;
    939    }
    940 
    941    // Prevent the menupopup from opening immediately
    942    aEvent.currentTarget.menupopup.hidden = true;
    943 
    944    aEvent.currentTarget.addEventListener("mouseout", this);
    945    aEvent.currentTarget.addEventListener("mouseup", this);
    946    this._timers.set(
    947      aEvent.currentTarget,
    948      setTimeout(b => this._openMenu(b), 500, aEvent.currentTarget)
    949    );
    950  },
    951 
    952  _clickHandler(aEvent) {
    953    if (
    954      aEvent.button == 0 &&
    955      aEvent.target == aEvent.currentTarget &&
    956      !aEvent.currentTarget.open &&
    957      !aEvent.currentTarget.disabled &&
    958      // When menupopup is not hidden and we receive
    959      // a click event, it means the mousedown occurred
    960      // on aEvent.currentTarget and mouseup occurred on
    961      // aEvent.currentTarget.menupopup, we don't
    962      // need to handle the click event as menupopup
    963      // handled mouseup event already.
    964      aEvent.currentTarget.menupopup.hidden
    965    ) {
    966      let cmdEvent = document.createEvent("xulcommandevent");
    967      cmdEvent.initCommandEvent(
    968        "command",
    969        true,
    970        true,
    971        window,
    972        0,
    973        aEvent.ctrlKey,
    974        aEvent.altKey,
    975        aEvent.shiftKey,
    976        aEvent.metaKey,
    977        0,
    978        null,
    979        aEvent.inputSource
    980      );
    981      aEvent.currentTarget.dispatchEvent(cmdEvent);
    982 
    983      // This is here to cancel the XUL default event
    984      // dom.click() triggers a command even if there is a click handler
    985      // however this can now be prevented with preventDefault().
    986      aEvent.preventDefault();
    987    }
    988  },
    989 
    990  _openMenu(aButton) {
    991    this._cancelHold(aButton);
    992    aButton.firstElementChild.hidden = false;
    993    aButton.open = true;
    994  },
    995 
    996  _mouseoutHandler(aEvent) {
    997    let buttonRect = aEvent.currentTarget.getBoundingClientRect();
    998    if (
    999      aEvent.clientX >= buttonRect.left &&
   1000      aEvent.clientX <= buttonRect.right &&
   1001      aEvent.clientY >= buttonRect.bottom
   1002    ) {
   1003      this._openMenu(aEvent.currentTarget);
   1004    } else {
   1005      this._cancelHold(aEvent.currentTarget);
   1006    }
   1007  },
   1008 
   1009  _mouseupHandler(aEvent) {
   1010    this._cancelHold(aEvent.currentTarget);
   1011  },
   1012 
   1013  _cancelHold(aButton) {
   1014    clearTimeout(this._timers.get(aButton));
   1015    aButton.removeEventListener("mouseout", this);
   1016    aButton.removeEventListener("mouseup", this);
   1017  },
   1018 
   1019  _keypressHandler(aEvent) {
   1020    if (aEvent.key == " " || aEvent.key == "Enter") {
   1021      aEvent.preventDefault();
   1022      // Normally, command events get fired for keyboard activation. However,
   1023      // we've set type="menu", so that doesn't happen. Handle this the same
   1024      // way we handle clicks.
   1025      aEvent.target.click();
   1026    }
   1027  },
   1028 
   1029  handleEvent(e) {
   1030    switch (e.type) {
   1031      case "mouseout":
   1032        this._mouseoutHandler(e);
   1033        break;
   1034      case "mousedown":
   1035        this._mousedownHandler(e);
   1036        break;
   1037      case "click":
   1038        this._clickHandler(e);
   1039        break;
   1040      case "mouseup":
   1041        this._mouseupHandler(e);
   1042        break;
   1043      case "keypress":
   1044        // Note that we might not be the only ones dealing with keypresses.
   1045        // See bug 1921772 for more context.
   1046        if (!e.defaultPrevented) {
   1047          this._keypressHandler(e);
   1048        }
   1049        break;
   1050    }
   1051  },
   1052 
   1053  remove(aButton) {
   1054    aButton.removeEventListener("mousedown", this, true);
   1055    aButton.removeEventListener("click", this, true);
   1056    aButton.removeEventListener("keypress", this, true);
   1057  },
   1058 
   1059  add(aElm) {
   1060    this._timers.delete(aElm);
   1061 
   1062    aElm.addEventListener("mousedown", this, true);
   1063    aElm.addEventListener("click", this, true);
   1064    aElm.addEventListener("keypress", this, true);
   1065  },
   1066 };
   1067 
   1068 const gSessionHistoryObserver = {
   1069  observe(subject, topic) {
   1070    if (topic != "browser:purge-session-history") {
   1071      return;
   1072    }
   1073 
   1074    var backCommand = document.getElementById("Browser:Back");
   1075    backCommand.setAttribute("disabled", "true");
   1076    var fwdCommand = document.getElementById("Browser:Forward");
   1077    fwdCommand.setAttribute("disabled", "true");
   1078 
   1079    // Clear undo history of the URL bar
   1080    gURLBar.editor.clearUndoRedo();
   1081  },
   1082 };
   1083 
   1084 const gStoragePressureObserver = {
   1085  _lastNotificationTime: -1,
   1086 
   1087  async observe(subject, topic) {
   1088    if (topic != "QuotaManager::StoragePressure") {
   1089      return;
   1090    }
   1091 
   1092    const NOTIFICATION_VALUE = "storage-pressure-notification";
   1093    if (gNotificationBox.getNotificationWithValue(NOTIFICATION_VALUE)) {
   1094      // Do not display the 2nd notification when there is already one
   1095      return;
   1096    }
   1097 
   1098    // Don't display notification twice within the given interval.
   1099    // This is because
   1100    //   - not to annoy user
   1101    //   - give user some time to clean space.
   1102    //     Even user sees notification and starts acting, it still takes some time.
   1103    const MIN_NOTIFICATION_INTERVAL_MS = Services.prefs.getIntPref(
   1104      "browser.storageManager.pressureNotification.minIntervalMS"
   1105    );
   1106    let duration = Date.now() - this._lastNotificationTime;
   1107    if (duration <= MIN_NOTIFICATION_INTERVAL_MS) {
   1108      return;
   1109    }
   1110    this._lastNotificationTime = Date.now();
   1111 
   1112    MozXULElement.insertFTLIfNeeded("browser/preferences/preferences.ftl");
   1113 
   1114    const BYTES_IN_GIGABYTE = 1073741824;
   1115    const USAGE_THRESHOLD_BYTES =
   1116      BYTES_IN_GIGABYTE *
   1117      Services.prefs.getIntPref(
   1118        "browser.storageManager.pressureNotification.usageThresholdGB"
   1119      );
   1120    let messageFragment = document.createDocumentFragment();
   1121    let message = document.createElement("span");
   1122 
   1123    let buttons = [{ supportPage: "storage-permissions" }];
   1124    let usage = subject.QueryInterface(Ci.nsISupportsPRUint64).data;
   1125    if (usage < USAGE_THRESHOLD_BYTES) {
   1126      // The firefox-used space < 5GB, then warn user to free some disk space.
   1127      // This is because this usage is small and not the main cause for space issue.
   1128      // In order to avoid the bad and wrong impression among users that
   1129      // firefox eats disk space a lot, indicate users to clean up other disk space.
   1130      document.l10n.setAttributes(message, "space-alert-under-5gb-message2");
   1131    } else {
   1132      // The firefox-used space >= 5GB, then guide users to about:preferences
   1133      // to clear some data stored on firefox by websites.
   1134      document.l10n.setAttributes(message, "space-alert-over-5gb-message2");
   1135      buttons.push({
   1136        "l10n-id": "space-alert-over-5gb-settings-button",
   1137        callback() {
   1138          // The advanced subpanes are only supported in the old organization, which will
   1139          // be removed by bug 1349689.
   1140          openPreferences("privacy-sitedata");
   1141        },
   1142      });
   1143    }
   1144    messageFragment.appendChild(message);
   1145 
   1146    await gNotificationBox.appendNotification(
   1147      NOTIFICATION_VALUE,
   1148      {
   1149        label: messageFragment,
   1150        priority: gNotificationBox.PRIORITY_WARNING_HIGH,
   1151      },
   1152      buttons
   1153    );
   1154 
   1155    // This seems to be necessary to get the buttons to display correctly
   1156    // See: https://bugzilla.mozilla.org/show_bug.cgi?id=1504216
   1157    document.l10n.translateFragment(gNotificationBox.currentNotification);
   1158  },
   1159 };
   1160 
   1161 var gKeywordURIFixup = {
   1162  check(browser, { fixedURI, keywordProviderName, preferredURI }) {
   1163    // We get called irrespective of whether we did a keyword search, or
   1164    // whether the original input would be vaguely interpretable as a URL,
   1165    // so figure that out first.
   1166    if (
   1167      !keywordProviderName ||
   1168      !fixedURI ||
   1169      !fixedURI.host ||
   1170      UrlbarPrefs.get("browser.fixup.dns_first_for_single_words") ||
   1171      UrlbarPrefs.get("dnsResolveSingleWordsAfterSearch") == 0
   1172    ) {
   1173      return;
   1174    }
   1175 
   1176    let contentPrincipal = browser.contentPrincipal;
   1177 
   1178    // At this point we're still only just about to load this URI.
   1179    // When the async DNS lookup comes back, we may be in any of these states:
   1180    // 1) still on the previous URI, waiting for the preferredURI (keyword
   1181    //    search) to respond;
   1182    // 2) at the keyword search URI (preferredURI)
   1183    // 3) at some other page because the user stopped navigation.
   1184    // We keep track of the currentURI to detect case (1) in the DNS lookup
   1185    // callback.
   1186    let previousURI = browser.currentURI;
   1187 
   1188    // now swap for a weak ref so we don't hang on to browser needlessly
   1189    // even if the DNS query takes forever
   1190    let weakBrowser = Cu.getWeakReference(browser);
   1191    browser = null;
   1192 
   1193    // Additionally, we need the host of the parsed url
   1194    let hostName = fixedURI.displayHost;
   1195    // and the ascii-only host for the pref:
   1196    let asciiHost = fixedURI.asciiHost;
   1197 
   1198    let onLookupCompleteListener = {
   1199      async onLookupComplete(request, record, status) {
   1200        let browserRef = weakBrowser.get();
   1201        if (!Components.isSuccessCode(status) || !browserRef) {
   1202          return;
   1203        }
   1204 
   1205        let currentURI = browserRef.currentURI;
   1206        // If we're in case (3) (see above), don't show an info bar.
   1207        if (
   1208          !currentURI.equals(previousURI) &&
   1209          !currentURI.equals(preferredURI)
   1210        ) {
   1211          return;
   1212        }
   1213 
   1214        // show infobar offering to visit the host
   1215        let notificationBox = gBrowser.getNotificationBox(browserRef);
   1216        if (notificationBox.getNotificationWithValue("keyword-uri-fixup")) {
   1217          return;
   1218        }
   1219 
   1220        let displayHostName = "http://" + hostName + "/";
   1221        let message = gNavigatorBundle.getFormattedString(
   1222          "keywordURIFixup.message",
   1223          [displayHostName]
   1224        );
   1225        let yesMessage = gNavigatorBundle.getFormattedString(
   1226          "keywordURIFixup.goTo",
   1227          [displayHostName]
   1228        );
   1229 
   1230        let buttons = [
   1231          {
   1232            label: yesMessage,
   1233            accessKey: gNavigatorBundle.getString(
   1234              "keywordURIFixup.goTo.accesskey"
   1235            ),
   1236            callback() {
   1237              // Do not set this preference while in private browsing.
   1238              if (!PrivateBrowsingUtils.isWindowPrivate(window)) {
   1239                let prefHost = asciiHost;
   1240                // Normalize out a single trailing dot - NB: not using endsWith/lastIndexOf
   1241                // because we need to be sure this last dot is the *only* dot, too.
   1242                // More generally, this is used for the pref and should stay in sync with
   1243                // the code in URIFixup::KeywordURIFixup .
   1244                if (prefHost.indexOf(".") == prefHost.length - 1) {
   1245                  prefHost = prefHost.slice(0, -1);
   1246                }
   1247                let pref = "browser.fixup.domainwhitelist." + prefHost;
   1248                Services.prefs.setBoolPref(pref, true);
   1249              }
   1250              openTrustedLinkIn(fixedURI.spec, "current");
   1251            },
   1252          },
   1253        ];
   1254        let notification = await notificationBox.appendNotification(
   1255          "keyword-uri-fixup",
   1256          {
   1257            label: message,
   1258            priority: notificationBox.PRIORITY_INFO_HIGH,
   1259          },
   1260          buttons
   1261        );
   1262        notification.persistence = 1;
   1263      },
   1264    };
   1265 
   1266    try {
   1267      Services.uriFixup.checkHost(
   1268        fixedURI,
   1269        onLookupCompleteListener,
   1270        contentPrincipal.originAttributes
   1271      );
   1272    } catch (ex) {
   1273      // Ignore errors.
   1274    }
   1275  },
   1276 
   1277  observe(fixupInfo) {
   1278    fixupInfo.QueryInterface(Ci.nsIURIFixupInfo);
   1279 
   1280    let browser = fixupInfo.consumer?.top?.embedderElement;
   1281    if (!browser || browser.ownerGlobal != window) {
   1282      return;
   1283    }
   1284 
   1285    this.check(browser, fixupInfo);
   1286  },
   1287 };
   1288 
   1289 function HandleAppCommandEvent(evt) {
   1290  switch (evt.command) {
   1291    case "Back":
   1292      BrowserCommands.back();
   1293      break;
   1294    case "Forward":
   1295      BrowserCommands.forward();
   1296      break;
   1297    case "Reload":
   1298      BrowserCommands.reloadSkipCache();
   1299      break;
   1300    case "Stop":
   1301      if (XULBrowserWindow.stopCommand.hasAttribute("disabled")) {
   1302        BrowserCommands.stop();
   1303      }
   1304      break;
   1305    case "Search":
   1306      SearchUIUtils.webSearch(window);
   1307      break;
   1308    case "Bookmarks":
   1309      SidebarController.toggle("viewBookmarksSidebar");
   1310      break;
   1311    case "Home":
   1312      BrowserCommands.home();
   1313      break;
   1314    case "New":
   1315      BrowserCommands.openTab();
   1316      break;
   1317    case "Close":
   1318      BrowserCommands.closeTabOrWindow();
   1319      break;
   1320    case "Find":
   1321      gLazyFindCommand("onFindCommand");
   1322      break;
   1323    case "Help":
   1324      openHelpLink("firefox-help");
   1325      break;
   1326    case "Open":
   1327      BrowserCommands.openFileWindow();
   1328      break;
   1329    case "Print":
   1330      PrintUtils.startPrintWindow(gBrowser.selectedBrowser.browsingContext);
   1331      break;
   1332    case "Save":
   1333      saveBrowser(gBrowser.selectedBrowser);
   1334      break;
   1335    case "SendMail":
   1336      MailIntegration.sendLinkForBrowser(gBrowser.selectedBrowser);
   1337      break;
   1338    default:
   1339      return;
   1340  }
   1341  evt.stopPropagation();
   1342  evt.preventDefault();
   1343 }
   1344 
   1345 function loadOneOrMoreURIs(aURIString, aTriggeringPrincipal, aPolicyContainer) {
   1346  // we're not a browser window, pass the URI string to a new browser window
   1347  if (window.location.href != AppConstants.BROWSER_CHROME_URL) {
   1348    window.openDialog(
   1349      AppConstants.BROWSER_CHROME_URL,
   1350      "_blank",
   1351      "all,dialog=no",
   1352      aURIString
   1353    );
   1354    return;
   1355  }
   1356 
   1357  // This function throws for certain malformed URIs, so use exception handling
   1358  // so that we don't disrupt startup
   1359  try {
   1360    gBrowser.loadTabs(aURIString.split("|"), {
   1361      inBackground: false,
   1362      replace: true,
   1363      triggeringPrincipal: aTriggeringPrincipal,
   1364      policyContainer: aPolicyContainer,
   1365    });
   1366  } catch (e) {}
   1367 }
   1368 
   1369 function openLocation(event) {
   1370  if (window.location.href == AppConstants.BROWSER_CHROME_URL) {
   1371    gURLBar.select();
   1372    gURLBar.view.autoOpen({ event });
   1373    return;
   1374  }
   1375 
   1376  // If there's an open browser window, redirect the command there.
   1377  let win = URILoadingHelper.getTargetWindow(window);
   1378  if (win) {
   1379    win.focus();
   1380    win.openLocation();
   1381    return;
   1382  }
   1383 
   1384  // There are no open browser windows; open a new one.
   1385  window.openDialog(
   1386    AppConstants.BROWSER_CHROME_URL,
   1387    "_blank",
   1388    "chrome,all,dialog=no",
   1389    BROWSER_NEW_TAB_URL
   1390  );
   1391 }
   1392 
   1393 var gLastOpenDirectory = {
   1394  _lastDir: null,
   1395  get path() {
   1396    if (!this._lastDir || !this._lastDir.exists()) {
   1397      try {
   1398        this._lastDir = Services.prefs.getComplexValue(
   1399          "browser.open.lastDir",
   1400          Ci.nsIFile
   1401        );
   1402        if (!this._lastDir.exists()) {
   1403          this._lastDir = null;
   1404        }
   1405      } catch (e) {}
   1406    }
   1407    return this._lastDir;
   1408  },
   1409  set path(val) {
   1410    try {
   1411      if (!val || !val.isDirectory()) {
   1412        return;
   1413      }
   1414    } catch (e) {
   1415      return;
   1416    }
   1417    this._lastDir = val.clone();
   1418 
   1419    // Don't save the last open directory pref inside the Private Browsing mode
   1420    if (!PrivateBrowsingUtils.isWindowPrivate(window)) {
   1421      Services.prefs.setComplexValue(
   1422        "browser.open.lastDir",
   1423        Ci.nsIFile,
   1424        this._lastDir
   1425      );
   1426    }
   1427  },
   1428  reset() {
   1429    this._lastDir = null;
   1430  },
   1431 };
   1432 
   1433 function readFromClipboard() {
   1434  var url;
   1435 
   1436  try {
   1437    // Create transferable that will transfer the text.
   1438    var trans = Cc["@mozilla.org/widget/transferable;1"].createInstance(
   1439      Ci.nsITransferable
   1440    );
   1441    trans.init(window.docShell.QueryInterface(Ci.nsILoadContext));
   1442 
   1443    trans.addDataFlavor("text/plain");
   1444 
   1445    // If available, use selection clipboard, otherwise global one
   1446    let clipboard = Services.clipboard;
   1447    if (clipboard.isClipboardTypeSupported(clipboard.kSelectionClipboard)) {
   1448      clipboard.getData(trans, clipboard.kSelectionClipboard);
   1449    } else {
   1450      clipboard.getData(trans, clipboard.kGlobalClipboard);
   1451    }
   1452 
   1453    var data = {};
   1454    trans.getTransferData("text/plain", data);
   1455 
   1456    if (data) {
   1457      data = data.value.QueryInterface(Ci.nsISupportsString);
   1458      url = data.data;
   1459    }
   1460  } catch (ex) {}
   1461 
   1462  return url;
   1463 }
   1464 
   1465 function UpdateUrlbarSearchSplitterState() {
   1466  var splitter = document.getElementById("urlbar-search-splitter");
   1467  var urlbar = document.getElementById("urlbar-container");
   1468  var searchbar = document.getElementById("search-container");
   1469 
   1470  if (document.documentElement.hasAttribute("customizing")) {
   1471    if (splitter) {
   1472      splitter.remove();
   1473    }
   1474    return;
   1475  }
   1476 
   1477  // If the splitter is already in the right place, we don't need to do anything:
   1478  if (
   1479    splitter &&
   1480    ((splitter.nextElementSibling == searchbar &&
   1481      splitter.previousElementSibling == urlbar) ||
   1482      (splitter.nextElementSibling == urlbar &&
   1483        splitter.previousElementSibling == searchbar))
   1484  ) {
   1485    return;
   1486  }
   1487 
   1488  let ibefore = null;
   1489  let resizebefore = "none";
   1490  let resizeafter = "none";
   1491  if (urlbar && searchbar) {
   1492    if (urlbar.nextElementSibling == searchbar) {
   1493      resizeafter = "sibling";
   1494      ibefore = searchbar;
   1495    } else if (searchbar.nextElementSibling == urlbar) {
   1496      resizebefore = "sibling";
   1497      ibefore = urlbar;
   1498    }
   1499  }
   1500 
   1501  if (ibefore) {
   1502    if (!splitter) {
   1503      splitter = document.createXULElement("splitter");
   1504      splitter.id = "urlbar-search-splitter";
   1505      splitter.setAttribute("resizebefore", resizebefore);
   1506      splitter.setAttribute("resizeafter", resizeafter);
   1507      splitter.setAttribute("skipintoolbarset", "true");
   1508      splitter.setAttribute("overflows", "false");
   1509      splitter.className = "chromeclass-toolbar-additional";
   1510    }
   1511    urlbar.parentNode.insertBefore(splitter, ibefore);
   1512  } else if (splitter) {
   1513    splitter.remove();
   1514  }
   1515 }
   1516 
   1517 function UpdatePopupNotificationsVisibility() {
   1518  // Only need to update PopupNotifications if it has already been initialized
   1519  // for this window (i.e. its getter no longer exists).
   1520  if (!Object.getOwnPropertyDescriptor(window, "PopupNotifications").get) {
   1521    // Notify PopupNotifications that the visible anchors may have changed. This
   1522    // also checks the suppression state according to the "shouldSuppress"
   1523    // function defined earlier in this file.
   1524    PopupNotifications.anchorVisibilityChange();
   1525  }
   1526 
   1527  // This is similar to the above, but for notifications attached to the
   1528  // hamburger menu icon (such as update notifications and add-on install
   1529  // notifications.)
   1530  PanelUI?.updateNotifications();
   1531 }
   1532 
   1533 function PageProxyClickHandler(aEvent) {
   1534  if (aEvent.button == 1 && Services.prefs.getBoolPref("middlemouse.paste")) {
   1535    middleMousePaste(aEvent);
   1536  }
   1537 }
   1538 
   1539 function CreateContainerTabMenu(event) {
   1540  // Do not open context menus within menus.
   1541  // Note that triggerNode is null if we're opened by long press.
   1542  if (event.target.triggerNode?.closest("menupopup")) {
   1543    event.preventDefault();
   1544    return;
   1545  }
   1546  createUserContextMenu(event, {
   1547    useAccessKeys: false,
   1548    showDefaultTab: true,
   1549  });
   1550 }
   1551 
   1552 function FillHistoryMenu(event) {
   1553  let parent = event.target;
   1554 
   1555  // Lazily add the hover listeners on first showing and never remove them
   1556  if (!parent.hasStatusListener) {
   1557    // Show history item's uri in the status bar when hovering, and clear on exit
   1558    parent.addEventListener("DOMMenuItemActive", function (aEvent) {
   1559      // Only the current page should have the checked attribute, so skip it
   1560      if (!aEvent.target.hasAttribute("checked")) {
   1561        XULBrowserWindow.setOverLink(aEvent.target.getAttribute("uri"));
   1562      }
   1563    });
   1564    parent.addEventListener("DOMMenuItemInactive", function () {
   1565      XULBrowserWindow.setOverLink("");
   1566    });
   1567 
   1568    parent.hasStatusListener = true;
   1569  }
   1570 
   1571  // Remove old entries if any
   1572  let children = parent.children;
   1573  for (var i = children.length - 1; i >= 0; --i) {
   1574    if (children[i].hasAttribute("index")) {
   1575      parent.removeChild(children[i]);
   1576    }
   1577  }
   1578 
   1579  const MAX_HISTORY_MENU_ITEMS = 15;
   1580 
   1581  const tooltipBack = gNavigatorBundle.getString("tabHistory.goBack");
   1582  const tooltipCurrent = gNavigatorBundle.getString("tabHistory.reloadCurrent");
   1583  const tooltipForward = gNavigatorBundle.getString("tabHistory.goForward");
   1584 
   1585  function updateSessionHistory(sessionHistory, initial, ssInParent) {
   1586    let count = ssInParent
   1587      ? sessionHistory.count
   1588      : sessionHistory.entries.length;
   1589 
   1590    if (!initial) {
   1591      if (count <= 1) {
   1592        // if there is only one entry now, close the popup.
   1593        parent.hidePopup();
   1594        return;
   1595      } else if (parent.id != "backForwardMenu" && !parent.parentNode.open) {
   1596        // if the popup wasn't open before, but now needs to be, reopen the menu.
   1597        // It should trigger FillHistoryMenu again. This might happen with the
   1598        // delay from click-and-hold menus but skip this for the context menu
   1599        // (backForwardMenu) rather than figuring out how the menu should be
   1600        // positioned and opened as it is an extreme edgecase.
   1601        parent.parentNode.open = true;
   1602        return;
   1603      }
   1604    }
   1605 
   1606    let index = sessionHistory.index;
   1607    let half_length = Math.floor(MAX_HISTORY_MENU_ITEMS / 2);
   1608    let start = Math.max(index - half_length, 0);
   1609    let end = Math.min(
   1610      start == 0 ? MAX_HISTORY_MENU_ITEMS : index + half_length + 1,
   1611      count
   1612    );
   1613    if (end == count) {
   1614      start = Math.max(count - MAX_HISTORY_MENU_ITEMS, 0);
   1615    }
   1616 
   1617    let existingIndex = 0;
   1618 
   1619    for (let j = end - 1; j >= start; j--) {
   1620      let entry = ssInParent
   1621        ? sessionHistory.getEntryAtIndex(j)
   1622        : sessionHistory.entries[j];
   1623      // Explicitly check for "false" to stay backwards-compatible with session histories
   1624      // from before the hasUserInteraction was implemented.
   1625      if (
   1626        BrowserUtils.navigationRequireUserInteraction &&
   1627        entry.hasUserInteraction === false &&
   1628        // Always list the current and last navigation points.
   1629        j != end - 1 &&
   1630        j != index
   1631      ) {
   1632        continue;
   1633      }
   1634      let uri = ssInParent ? entry.URI.spec : entry.url;
   1635 
   1636      let item =
   1637        existingIndex < children.length
   1638          ? children[existingIndex]
   1639          : document.createXULElement("menuitem");
   1640 
   1641      item.setAttribute("uri", uri);
   1642      item.setAttribute("label", entry.title || uri);
   1643      item.setAttribute("index", j);
   1644 
   1645      // Cache this so that BrowserCommands.gotoHistoryIndex doesn't need the
   1646      // original index
   1647      item.setAttribute("historyindex", j - index);
   1648 
   1649      if (j != index) {
   1650        // Use --menuitem-icon rather than the image attribute in order to
   1651        // allow CSS to override this.
   1652        item.style.setProperty(
   1653          "--menuitem-icon",
   1654          `url(page-icon:${CSS.escape(uri)})`
   1655        );
   1656      }
   1657 
   1658      if (j < index) {
   1659        item.className =
   1660          "unified-nav-back menuitem-iconic menuitem-with-favicon";
   1661        item.setAttribute("tooltiptext", tooltipBack);
   1662      } else if (j == index) {
   1663        item.setAttribute("type", "radio");
   1664        item.setAttribute("checked", "true");
   1665        item.className = "unified-nav-current";
   1666        item.setAttribute("tooltiptext", tooltipCurrent);
   1667      } else {
   1668        item.className =
   1669          "unified-nav-forward menuitem-iconic menuitem-with-favicon";
   1670        item.setAttribute("tooltiptext", tooltipForward);
   1671      }
   1672 
   1673      if (!item.parentNode) {
   1674        parent.appendChild(item);
   1675      }
   1676 
   1677      existingIndex++;
   1678    }
   1679 
   1680    if (!initial) {
   1681      let existingLength = children.length;
   1682      while (existingIndex < existingLength) {
   1683        parent.removeChild(parent.lastElementChild);
   1684        existingIndex++;
   1685      }
   1686    }
   1687  }
   1688 
   1689  // If session history in parent is available, use it. Otherwise, get the session history
   1690  // from session store.
   1691  let sessionHistory = gBrowser.selectedBrowser.browsingContext.sessionHistory;
   1692  if (sessionHistory?.count) {
   1693    // Don't show the context menu if there is only one item.
   1694    if (sessionHistory.count <= 1) {
   1695      event.preventDefault();
   1696      return;
   1697    }
   1698 
   1699    updateSessionHistory(sessionHistory, true, true);
   1700  } else {
   1701    sessionHistory = SessionStore.getSessionHistory(
   1702      gBrowser.selectedTab,
   1703      updateSessionHistory
   1704    );
   1705    updateSessionHistory(sessionHistory, true, false);
   1706  }
   1707 }
   1708 
   1709 function toOpenWindowByType(inType, uri, features) {
   1710  var topWindow = Services.wm.getMostRecentWindow(inType);
   1711 
   1712  if (topWindow) {
   1713    topWindow.focus();
   1714  } else if (features) {
   1715    window.open(uri, "_blank", features);
   1716  } else {
   1717    window.open(
   1718      uri,
   1719      "_blank",
   1720      "chrome,extrachrome,menubar,resizable,scrollbars,status,toolbar"
   1721    );
   1722  }
   1723 }
   1724 /**
   1725 * Open a new browser window. See `BrowserWindowTracker.openWindow` for
   1726 * options.
   1727 *
   1728 * @return a reference to the new window.
   1729 */
   1730 function OpenBrowserWindow(options) {
   1731  let timerId = Glean.browserTimings.newWindow.start();
   1732  options ??= {};
   1733  options.openerWindow ??= window;
   1734 
   1735  let win = BrowserWindowTracker.openWindow(options);
   1736 
   1737  win.addEventListener(
   1738    "MozAfterPaint",
   1739    () => {
   1740      Glean.browserTimings.newWindow.stopAndAccumulate(timerId);
   1741    },
   1742    { once: true }
   1743  );
   1744 
   1745  return win;
   1746 }
   1747 
   1748 /**
   1749 * Update the global flag that tracks whether or not any edit UI (the Edit menu,
   1750 * edit-related items in the context menu, and edit-related toolbar buttons
   1751 * is visible, then update the edit commands' enabled state accordingly.  We use
   1752 * this flag to skip updating the edit commands on focus or selection changes
   1753 * when no UI is visible to improve performance (including pageload performance,
   1754 * since focus changes when you load a new page).
   1755 *
   1756 * If UI is visible, we use goUpdateGlobalEditMenuItems to set the commands'
   1757 * enabled state so the UI will reflect it appropriately.
   1758 *
   1759 * If the UI isn't visible, we enable all edit commands so keyboard shortcuts
   1760 * still work and just lazily disable them as needed when the user presses a
   1761 * shortcut.
   1762 *
   1763 * This doesn't work on Mac, since Mac menus flash when users press their
   1764 * keyboard shortcuts, so edit UI is essentially always visible on the Mac,
   1765 * and we need to always update the edit commands.  Thus on Mac this function
   1766 * is a no op.
   1767 */
   1768 function updateEditUIVisibility() {
   1769  if (AppConstants.platform == "macosx") {
   1770    return;
   1771  }
   1772 
   1773  let editMenuPopupState = document.getElementById("menu_EditPopup").state;
   1774  let contextMenuPopupState = document.getElementById(
   1775    "contentAreaContextMenu"
   1776  ).state;
   1777  let placesContextMenuPopupState =
   1778    document.getElementById("placesContext").state;
   1779 
   1780  let oldVisible = gEditUIVisible;
   1781 
   1782  // The UI is visible if the Edit menu is opening or open, if the context menu
   1783  // is open, or if the toolbar has been customized to include the Cut, Copy,
   1784  // or Paste toolbar buttons.
   1785  gEditUIVisible =
   1786    editMenuPopupState == "showing" ||
   1787    editMenuPopupState == "open" ||
   1788    contextMenuPopupState == "showing" ||
   1789    contextMenuPopupState == "open" ||
   1790    placesContextMenuPopupState == "showing" ||
   1791    placesContextMenuPopupState == "open";
   1792  const kOpenPopupStates = ["showing", "open"];
   1793  if (!gEditUIVisible) {
   1794    // Now check the edit-controls toolbar buttons.
   1795    let placement = CustomizableUI.getPlacementOfWidget("edit-controls");
   1796    let areaType = placement ? CustomizableUI.getAreaType(placement.area) : "";
   1797    if (areaType == CustomizableUI.TYPE_PANEL) {
   1798      let customizablePanel = PanelUI.overflowPanel;
   1799      gEditUIVisible = kOpenPopupStates.includes(customizablePanel.state);
   1800    } else if (
   1801      areaType == CustomizableUI.TYPE_TOOLBAR &&
   1802      window.toolbar.visible
   1803    ) {
   1804      // The edit controls are on a toolbar, so they are visible,
   1805      // unless they're in a panel that isn't visible...
   1806      if (placement.area == "nav-bar") {
   1807        let editControls = document.getElementById("edit-controls");
   1808        gEditUIVisible =
   1809          !editControls.hasAttribute("overflowedItem") ||
   1810          kOpenPopupStates.includes(
   1811            document.getElementById("widget-overflow").state
   1812          );
   1813      } else {
   1814        gEditUIVisible = true;
   1815      }
   1816    }
   1817  }
   1818 
   1819  // Now check the main menu panel
   1820  if (!gEditUIVisible) {
   1821    gEditUIVisible = kOpenPopupStates.includes(PanelUI.panel.state);
   1822  }
   1823 
   1824  // No need to update commands if the edit UI visibility has not changed.
   1825  if (gEditUIVisible == oldVisible) {
   1826    return;
   1827  }
   1828 
   1829  // If UI is visible, update the edit commands' enabled state to reflect
   1830  // whether or not they are actually enabled for the current focus/selection.
   1831  if (gEditUIVisible) {
   1832    goUpdateGlobalEditMenuItems();
   1833  } else {
   1834    // Otherwise, enable all commands, so that keyboard shortcuts still work,
   1835    // then lazily determine their actual enabled state when the user presses
   1836    // a keyboard shortcut.
   1837    goSetCommandEnabled("cmd_undo", true);
   1838    goSetCommandEnabled("cmd_redo", true);
   1839    goSetCommandEnabled("cmd_cut", true);
   1840    goSetCommandEnabled("cmd_copy", true);
   1841    goSetCommandEnabled("cmd_paste", true);
   1842    goSetCommandEnabled("cmd_selectAll", true);
   1843    goSetCommandEnabled("cmd_delete", true);
   1844    goSetCommandEnabled("cmd_switchTextDirection", true);
   1845  }
   1846 }
   1847 
   1848 let gFileMenu = {
   1849  /**
   1850   * Updates User Context Menu Item UI visibility depending on
   1851   * privacy.userContext.enabled pref state.
   1852   */
   1853  updateUserContextUIVisibility() {
   1854    let menu = document.getElementById("menu_newUserContext");
   1855    menu.hidden = !Services.prefs.getBoolPref(
   1856      "privacy.userContext.enabled",
   1857      false
   1858    );
   1859    // Visibility of File menu item shouldn't change frequently.
   1860    if (PrivateBrowsingUtils.isWindowPrivate(window)) {
   1861      menu.setAttribute("disabled", "true");
   1862    }
   1863  },
   1864 
   1865  /**
   1866   * Updates the enabled state of the "Import From Another Browser" command
   1867   * depending on the DisableProfileImport policy.
   1868   */
   1869  updateImportCommandEnabledState() {
   1870    if (!Services.policies.isAllowed("profileImport")) {
   1871      document
   1872        .getElementById("cmd_file_importFromAnotherBrowser")
   1873        .setAttribute("disabled", "true");
   1874    }
   1875  },
   1876 
   1877  /**
   1878   * Updates the "Close tab" command to reflect the number of selected tabs,
   1879   * when applicable.
   1880   */
   1881  updateTabCloseCountState() {
   1882    document.l10n.setAttributes(
   1883      document.getElementById("menu_close"),
   1884      "menu-file-close-tab",
   1885      { tabCount: gBrowser.selectedTabs.length }
   1886    );
   1887  },
   1888 
   1889  onPopupShowing(event) {
   1890    // We don't care about submenus:
   1891    if (event.target.id != "menu_FilePopup") {
   1892      return;
   1893    }
   1894    this.updateUserContextUIVisibility();
   1895    this.updateImportCommandEnabledState();
   1896    this.updateTabCloseCountState();
   1897    if (AppConstants.platform == "macosx") {
   1898      SharingUtils.updateShareURLMenuItem(
   1899        gBrowser.selectedBrowser,
   1900        document.getElementById("menu_savePage")
   1901      );
   1902    }
   1903    PrintUtils.updatePrintSetupMenuHiddenState();
   1904 
   1905    const aiWindowMenu = event.target.querySelector("#menu_newAIWindow");
   1906    const classicWindowMenu = event.target.querySelector(
   1907      "#menu_newClassicWindow"
   1908    );
   1909 
   1910    aiWindowMenu.hidden =
   1911      !AIWindow.isAIWindowEnabled() || AIWindow.isAIWindowActive(window);
   1912    classicWindowMenu.hidden =
   1913      !AIWindow.isAIWindowEnabled() || !AIWindow.isAIWindowActive(window);
   1914  },
   1915 };
   1916 
   1917 /**
   1918 * Opens a new tab with the userContextId specified as an attribute of
   1919 * sourceEvent. This attribute is propagated to the top level originAttributes
   1920 * living on the tab's docShell.
   1921 *
   1922 * @param event
   1923 *        A click event on a userContext File Menu option
   1924 */
   1925 function openNewUserContextTab(event) {
   1926  openTrustedLinkIn(BROWSER_NEW_TAB_URL, "tab", {
   1927    userContextId: parseInt(event.target.getAttribute("data-usercontextid")),
   1928  });
   1929 }
   1930 
   1931 var XULBrowserWindow = {
   1932  // Stored Status, Link and Loading values
   1933  status: "",
   1934  defaultStatus: "",
   1935  overLink: "",
   1936  startTime: 0,
   1937  isBusy: false,
   1938  busyUI: false,
   1939 
   1940  QueryInterface: ChromeUtils.generateQI([
   1941    "nsIWebProgressListener",
   1942    "nsIWebProgressListener2",
   1943    "nsISupportsWeakReference",
   1944    "nsIXULBrowserWindow",
   1945  ]),
   1946 
   1947  get stopCommand() {
   1948    delete this.stopCommand;
   1949    return (this.stopCommand = document.getElementById("Browser:Stop"));
   1950  },
   1951  get reloadCommand() {
   1952    delete this.reloadCommand;
   1953    return (this.reloadCommand = document.getElementById("Browser:Reload"));
   1954  },
   1955  get _elementsForTextBasedTypes() {
   1956    delete this._elementsForTextBasedTypes;
   1957    return (this._elementsForTextBasedTypes = [
   1958      document.getElementById("pageStyleMenu"),
   1959      document.getElementById("context-viewpartialsource-selection"),
   1960      document.getElementById("context-print-selection"),
   1961    ]);
   1962  },
   1963  get _elementsForFind() {
   1964    delete this._elementsForFind;
   1965    return (this._elementsForFind = [
   1966      document.getElementById("cmd_find"),
   1967      document.getElementById("cmd_findAgain"),
   1968      document.getElementById("cmd_findPrevious"),
   1969    ]);
   1970  },
   1971  get _elementsForViewSource() {
   1972    delete this._elementsForViewSource;
   1973    return (this._elementsForViewSource = [
   1974      document.getElementById("context-viewsource"),
   1975      document.getElementById("View:PageSource"),
   1976    ]);
   1977  },
   1978  get _menuItemForRepairTextEncoding() {
   1979    delete this._menuItemForRepairTextEncoding;
   1980    return (this._menuItemForRepairTextEncoding = document.getElementById(
   1981      "repair-text-encoding"
   1982    ));
   1983  },
   1984  get _menuItemForTranslations() {
   1985    delete this._menuItemForTranslations;
   1986    return (this._menuItemForTranslations =
   1987      document.getElementById("cmd_translate"));
   1988  },
   1989 
   1990  setDefaultStatus(status) {
   1991    this.defaultStatus = status;
   1992    StatusPanel.update();
   1993  },
   1994 
   1995  /**
   1996   * Tells the UI what link we are currently over.
   1997   *
   1998   * @param {string} url
   1999   *   The URL of the link.
   2000   * @param {object} [options]
   2001   *   This is an extension of nsIXULBrowserWindow for JS callers, will be
   2002   *   passed on to LinkTargetDisplay.
   2003   */
   2004  setOverLink(url, options = undefined) {
   2005    window.dispatchEvent(
   2006      new CustomEvent("OverLink", {
   2007        detail: { url },
   2008      })
   2009    );
   2010 
   2011    if (url) {
   2012      url = Services.textToSubURI.unEscapeURIForUI(url);
   2013 
   2014      /**
   2015       * Encode bidirectional formatting characters.
   2016       *
   2017       * @see https://url.spec.whatwg.org/#url-rendering-i18n
   2018       * @see https://www.unicode.org/reports/tr9/#Directional_Formatting_Characters
   2019       */
   2020      url = url.replace(
   2021        /[\u061c\u200e\u200f\u202a-\u202e\u2066-\u2069]/g,
   2022        encodeURIComponent
   2023      );
   2024 
   2025      if (UrlbarPrefs.get("trimURLs")) {
   2026        url = BrowserUIUtils.trimURL(url);
   2027      }
   2028    }
   2029 
   2030    this.overLink = url;
   2031    LinkTargetDisplay.update(options);
   2032  },
   2033 
   2034  onEnterDOMFullscreen() {
   2035    // Clear the status panel.
   2036    this.status = "";
   2037    this.setDefaultStatus("");
   2038    this.setOverLink("", { hideStatusPanelImmediately: true });
   2039  },
   2040 
   2041  showTooltip(xDevPix, yDevPix, tooltip, direction, _browser) {
   2042    if (
   2043      Cc["@mozilla.org/widget/dragservice;1"]
   2044        .getService(Ci.nsIDragService)
   2045        .getCurrentSession()
   2046    ) {
   2047      return;
   2048    }
   2049 
   2050    if (!document.hasFocus()) {
   2051      return;
   2052    }
   2053 
   2054    let elt = document.getElementById("remoteBrowserTooltip");
   2055    elt.label = tooltip;
   2056    elt.style.direction = direction;
   2057    elt.openPopupAtScreen(
   2058      xDevPix / window.devicePixelRatio,
   2059      yDevPix / window.devicePixelRatio,
   2060      false,
   2061      null
   2062    );
   2063  },
   2064 
   2065  hideTooltip() {
   2066    let elt = document.getElementById("remoteBrowserTooltip");
   2067    elt.hidePopup();
   2068  },
   2069 
   2070  getTabCount() {
   2071    return gBrowser.tabs.length;
   2072  },
   2073 
   2074  onProgressChange() {
   2075    // Do nothing.
   2076  },
   2077 
   2078  onProgressChange64(
   2079    aWebProgress,
   2080    aRequest,
   2081    aCurSelfProgress,
   2082    aMaxSelfProgress,
   2083    aCurTotalProgress,
   2084    aMaxTotalProgress
   2085  ) {
   2086    return this.onProgressChange(
   2087      aWebProgress,
   2088      aRequest,
   2089      aCurSelfProgress,
   2090      aMaxSelfProgress,
   2091      aCurTotalProgress,
   2092      aMaxTotalProgress
   2093    );
   2094  },
   2095 
   2096  // This function fires only for the currently selected tab.
   2097  onStateChange(aWebProgress, aRequest, aStateFlags, aStatus) {
   2098    const nsIWebProgressListener = Ci.nsIWebProgressListener;
   2099 
   2100    let browser = gBrowser.selectedBrowser;
   2101    gProtectionsHandler.onStateChange(aWebProgress, aStateFlags);
   2102 
   2103    if (
   2104      aStateFlags & nsIWebProgressListener.STATE_START &&
   2105      aStateFlags & nsIWebProgressListener.STATE_IS_NETWORK
   2106    ) {
   2107      if (aRequest && aWebProgress.isTopLevel) {
   2108        OpenSearchManager.clearEngines(browser);
   2109      }
   2110 
   2111      this.isBusy = true;
   2112 
   2113      if (
   2114        !(aStateFlags & nsIWebProgressListener.STATE_RESTORING) &&
   2115        aWebProgress.isTopLevel
   2116      ) {
   2117        this.busyUI = true;
   2118 
   2119        if (this.spinCursorWhileBusy) {
   2120          window.setCursor("progress");
   2121        }
   2122 
   2123        // XXX: This needs to be based on window activity...
   2124        this.stopCommand.removeAttribute("disabled");
   2125        CombinedStopReload.switchToStop(aRequest, aWebProgress);
   2126      }
   2127    } else if (aStateFlags & nsIWebProgressListener.STATE_STOP) {
   2128      // This (thanks to the filter) is a network stop or the last
   2129      // request stop outside of loading the document, stop throbbers
   2130      // and progress bars and such
   2131      if (aRequest) {
   2132        let msg = "";
   2133        let location;
   2134        let canViewSource = true;
   2135        // Get the URI either from a channel or a pseudo-object
   2136        if (aRequest instanceof Ci.nsIChannel || "URI" in aRequest) {
   2137          location = aRequest.URI;
   2138 
   2139          // For keyword URIs clear the user typed value since they will be changed into real URIs
   2140          if (location.scheme == "keyword" && aWebProgress.isTopLevel) {
   2141            gBrowser.userTypedValue = null;
   2142          }
   2143 
   2144          canViewSource = location.scheme != "view-source";
   2145 
   2146          if (location.spec != "about:blank") {
   2147            switch (aStatus) {
   2148              case Cr.NS_ERROR_NET_TIMEOUT:
   2149                msg = gNavigatorBundle.getString("nv_timeout");
   2150                break;
   2151            }
   2152          }
   2153        }
   2154 
   2155        this.status = "";
   2156        this.setDefaultStatus(msg);
   2157 
   2158        // Disable View Source menu entries for images, enable otherwise
   2159        let isText =
   2160          browser.documentContentType &&
   2161          BrowserUtils.mimeTypeIsTextBased(browser.documentContentType);
   2162        for (let element of this._elementsForViewSource) {
   2163          if (canViewSource && isText) {
   2164            element.removeAttribute("disabled");
   2165          } else {
   2166            element.setAttribute("disabled", "true");
   2167          }
   2168        }
   2169 
   2170        this._updateElementsForContentType();
   2171 
   2172        // Update Override Text Encoding state.
   2173        // Can't cache the button, because the presence of the element in the DOM
   2174        // may change over time.
   2175        let button = document.getElementById("characterencoding-button");
   2176        if (browser.mayEnableCharacterEncodingMenu) {
   2177          this._menuItemForRepairTextEncoding.removeAttribute("disabled");
   2178          button?.removeAttribute("disabled");
   2179        } else {
   2180          this._menuItemForRepairTextEncoding.setAttribute("disabled", "true");
   2181          button?.setAttribute("disabled", "true");
   2182        }
   2183      }
   2184 
   2185      this.isBusy = false;
   2186 
   2187      if (this.busyUI && aWebProgress.isTopLevel) {
   2188        this.busyUI = false;
   2189 
   2190        if (this.spinCursorWhileBusy) {
   2191          window.setCursor("auto");
   2192        }
   2193 
   2194        this.stopCommand.setAttribute("disabled", "true");
   2195        CombinedStopReload.switchToReload(aRequest, aWebProgress);
   2196      }
   2197    }
   2198  },
   2199 
   2200  /**
   2201   * An nsIWebProgressListener method called by tabbrowser.  The `aIsSimulated`
   2202   * parameter is extra and not declared in nsIWebProgressListener, however; see
   2203   * below.
   2204   *
   2205   * @param {nsIWebProgress} aWebProgress
   2206   *   The nsIWebProgress instance that fired the notification.
   2207   * @param {nsIRequest} aRequest
   2208   *   The associated nsIRequest.  This may be null in some cases.
   2209   * @param {nsIURI} aLocationURI
   2210   *   The URI of the location that is being loaded.
   2211   * @param {integer} aFlags
   2212   *   Flags that indicate the reason the location changed.  See the
   2213   *   nsIWebProgressListener.LOCATION_CHANGE_* values.
   2214   * @param {boolean} aIsSimulated
   2215   *   True when this is called by tabbrowser due to switching tabs and
   2216   *   undefined otherwise.  This parameter is not declared in
   2217   *   nsIWebProgressListener.onLocationChange; see bug 1478348.
   2218   */
   2219  onLocationChange(aWebProgress, aRequest, aLocationURI, aFlags, aIsSimulated) {
   2220    var location = aLocationURI ? aLocationURI.spec : "";
   2221 
   2222    UpdateBackForwardCommands(gBrowser.webNavigation);
   2223 
   2224    Services.obs.notifyObservers(
   2225      aWebProgress,
   2226      "touchbar-location-change",
   2227      location
   2228    );
   2229 
   2230    // For most changes we only need to update the browser UI if the primary
   2231    // content area was navigated or the selected tab was changed. We don't need
   2232    // to do anything else if there was a subframe navigation.
   2233 
   2234    if (!aWebProgress.isTopLevel) {
   2235      return;
   2236    }
   2237 
   2238    this.setOverLink("", { hideStatusPanelImmediately: true });
   2239 
   2240    let isSameDocument =
   2241      aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT;
   2242    if (
   2243      (location == "about:blank" &&
   2244        BrowserUIUtils.checkEmptyPageOrigin(gBrowser.selectedBrowser)) ||
   2245      location == ""
   2246    ) {
   2247      // Second condition is for new tabs, otherwise
   2248      // reload function is enabled until tab is refreshed.
   2249      this.reloadCommand.setAttribute("disabled", "true");
   2250    } else {
   2251      this.reloadCommand.removeAttribute("disabled");
   2252    }
   2253 
   2254    let isSessionRestore = !!(
   2255      aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SESSION_STORE
   2256    );
   2257 
   2258    // Don't update URL for document PiP window as it shows its opener url
   2259    if (!window.browsingContext.isDocumentPiP) {
   2260      // We want to update the popup visibility if we received this notification
   2261      // via simulated locationchange events such as switching between tabs, however
   2262      // if this is a document navigation then PopupNotifications will be updated
   2263      // via TabsProgressListener.onLocationChange and we do not want it called twice
   2264      gURLBar.setURI({
   2265        uri: aLocationURI,
   2266        dueToTabSwitch: aIsSimulated,
   2267        dueToSessionRestore: isSessionRestore,
   2268        isSameDocument,
   2269      });
   2270    }
   2271 
   2272    BookmarkingUI.onLocationChange();
   2273    // If we've actually changed document, update the toolbar visibility.
   2274    if (!isSameDocument) {
   2275      updateBookmarkToolbarVisibility();
   2276    }
   2277 
   2278    let closeOpenPanels = selector => {
   2279      for (let panel of document.querySelectorAll(selector)) {
   2280        if (panel.state != "closed") {
   2281          panel.hidePopup();
   2282        }
   2283      }
   2284    };
   2285 
   2286    // If the location is changed due to switching tabs,
   2287    // ensure we close any open tabspecific popups.
   2288    if (aIsSimulated) {
   2289      closeOpenPanels(":is(panel, menupopup)[tabspecific='true']");
   2290    }
   2291 
   2292    // Ensure we close any remaining open locationspecific panels
   2293    if (!isSameDocument) {
   2294      closeOpenPanels(":is(panel, menupopup)[locationspecific='true']");
   2295    }
   2296 
   2297    gPermissionPanel.onLocationChange();
   2298 
   2299    gProtectionsHandler.onLocationChange();
   2300 
   2301    BrowserPageActions.onLocationChange();
   2302 
   2303    UrlbarProviderSearchTips.onLocationChange(
   2304      window,
   2305      aLocationURI,
   2306      aWebProgress,
   2307      aFlags
   2308    );
   2309 
   2310    if (aLocationURI.scheme.startsWith("http")) {
   2311      ActionsProviderContextualSearch.onLocationChange(
   2312        window,
   2313        aLocationURI,
   2314        aWebProgress,
   2315        aFlags
   2316      );
   2317    }
   2318 
   2319    this._updateElementsForContentType();
   2320 
   2321    this._updateMacUserActivity(window, aLocationURI, aWebProgress);
   2322 
   2323    // Unconditionally disable the Text Encoding button during load to
   2324    // keep the UI calm when navigating from one modern page to another and
   2325    // the toolbar button is visible.
   2326    // Can't cache the button, because the presence of the element in the DOM
   2327    // may change over time.
   2328    let button = document.getElementById("characterencoding-button");
   2329    this._menuItemForRepairTextEncoding.setAttribute("disabled", "true");
   2330    button?.setAttribute("disabled", "true");
   2331 
   2332    // Try not to instantiate gCustomizeMode as much as possible,
   2333    // so don't use CustomizeMode.sys.mjs to check for URI or customizing.
   2334    if (
   2335      location == "about:blank" &&
   2336      gBrowser.selectedTab.hasAttribute("customizemode")
   2337    ) {
   2338      gCustomizeMode.enter();
   2339    } else if (
   2340      CustomizationHandler.isEnteringCustomizeMode ||
   2341      CustomizationHandler.isCustomizing()
   2342    ) {
   2343      gCustomizeMode.exit();
   2344    }
   2345 
   2346    CFRPageActions.updatePageActions(gBrowser.selectedBrowser);
   2347 
   2348    AboutReaderParent.updateReaderButton(gBrowser.selectedBrowser);
   2349    OnionLocationParent.updateOnionLocationBadge(gBrowser.selectedBrowser);
   2350    TranslationsParent.onLocationChange(gBrowser.selectedBrowser);
   2351 
   2352    PictureInPicture.updateUrlbarToggle(gBrowser.selectedBrowser);
   2353 
   2354    if (!gMultiProcessBrowser) {
   2355      // Bug 1108553 - Cannot rotate images with e10s
   2356      gGestureSupport.restoreRotationState();
   2357    }
   2358 
   2359    // See bug 358202, when tabs are switched during a drag operation,
   2360    // timers don't fire on windows (bug 203573)
   2361    if (aRequest) {
   2362      setTimeout(function () {
   2363        XULBrowserWindow.asyncUpdateUI();
   2364      }, 0);
   2365    } else {
   2366      this.asyncUpdateUI();
   2367    }
   2368 
   2369    if (AppConstants.MOZ_CRASHREPORTER && aLocationURI) {
   2370      let uri = aLocationURI;
   2371      try {
   2372        // If the current URI contains a username/password, remove it.
   2373        uri = aLocationURI.mutate().setUserPass("").finalize();
   2374      } catch (ex) {
   2375        /* Ignore failures on about: URIs. */
   2376      }
   2377 
   2378      try {
   2379        Services.appinfo.annotateCrashReport("URL", uri.spec);
   2380      } catch (ex) {
   2381        // Don't make noise when the crash reporter is built but not enabled.
   2382        if (ex.result != Cr.NS_ERROR_NOT_INITIALIZED) {
   2383          throw ex;
   2384        }
   2385      }
   2386    }
   2387  },
   2388 
   2389  _updateElementsForContentType() {
   2390    let browser = gBrowser.selectedBrowser;
   2391 
   2392    let isText =
   2393      browser.documentContentType &&
   2394      BrowserUtils.mimeTypeIsTextBased(browser.documentContentType);
   2395    for (let element of this._elementsForTextBasedTypes) {
   2396      if (isText) {
   2397        element.removeAttribute("disabled");
   2398      } else {
   2399        element.setAttribute("disabled", "true");
   2400      }
   2401    }
   2402 
   2403    // Always enable find commands in PDF documents, otherwise do it only for
   2404    // text documents whose location is not in the blacklist.
   2405    let enableFind =
   2406      browser.contentPrincipal?.spec == "resource://pdf.js/web/viewer.html" ||
   2407      (isText && BrowserUtils.canFindInPage(gBrowser.currentURI.spec));
   2408    for (let element of this._elementsForFind) {
   2409      if (enableFind) {
   2410        element.removeAttribute("disabled");
   2411      } else {
   2412        element.setAttribute("disabled", "true");
   2413      }
   2414    }
   2415 
   2416    if (TranslationsParent.isFullPageTranslationsRestrictedForPage(gBrowser)) {
   2417      this._menuItemForTranslations.setAttribute("disabled", "true");
   2418    } else {
   2419      this._menuItemForTranslations.removeAttribute("disabled");
   2420    }
   2421    if (gTranslationsEnabled) {
   2422      if (TranslationsParent.getIsTranslationsEngineSupported()) {
   2423        this._menuItemForTranslations.removeAttribute("hidden");
   2424      } else {
   2425        this._menuItemForTranslations.setAttribute("hidden", "true");
   2426      }
   2427    } else {
   2428      this._menuItemForTranslations.setAttribute("hidden", "true");
   2429    }
   2430  },
   2431 
   2432  /**
   2433   * Updates macOS platform code with the current URI and page title.
   2434   * From there, we update the current NSUserActivity, enabling Handoff to other
   2435   * Apple devices.
   2436   *
   2437   * @param {Window} window
   2438   *   The window in which the navigation occurred.
   2439   * @param {nsIURI} uri
   2440   *   The URI pointing to the current page.
   2441   * @param {nsIWebProgress} webProgress
   2442   *   The nsIWebProgress instance that fired a onLocationChange notification.
   2443   */
   2444  _updateMacUserActivity(win, uri, webProgress) {
   2445    if (!webProgress.isTopLevel || AppConstants.platform != "macosx") {
   2446      return;
   2447    }
   2448 
   2449    let url = uri.spec;
   2450    if (PrivateBrowsingUtils.isWindowPrivate(win)) {
   2451      // Passing an empty string to MacUserActivityUpdater will invalidate the
   2452      // current user activity.
   2453      url = "";
   2454    }
   2455    let baseWin = win.docShell.treeOwner.QueryInterface(Ci.nsIBaseWindow);
   2456    MacUserActivityUpdater.updateLocation(
   2457      url,
   2458      win.gBrowser.contentTitle,
   2459      baseWin
   2460    );
   2461  },
   2462 
   2463  /**
   2464   * Potentially gets a URI for a MozBrowser to be shown to the user in the
   2465   * identity panel. For browsers whose content does not have a principal,
   2466   * this tries the precursor. If this is null, we should not override the
   2467   * browser's currentURI.
   2468   *
   2469   * @param {MozBrowser} browser
   2470   *   The browser that we need a URI to show the user in the
   2471   *   identity panel.
   2472   * @return nsIURI of the principal for the browser's content if
   2473   *   the browser's currentURI should not be used, null otherwise.
   2474   */
   2475  _securityURIOverride(browser) {
   2476    let uri = browser.currentURI;
   2477    if (!uri) {
   2478      return null;
   2479    }
   2480 
   2481    // If the browser's currentURI is sufficiently good that we
   2482    // do not require an override, bail out here.
   2483    // browser.currentURI should be used.
   2484    let { URI_INHERITS_SECURITY_CONTEXT } = Ci.nsIProtocolHandler;
   2485    if (
   2486      !(doGetProtocolFlags(uri) & URI_INHERITS_SECURITY_CONTEXT) &&
   2487      !(uri.scheme == "about" && uri.filePath == "srcdoc") &&
   2488      !(uri.scheme == "about" && uri.filePath == "blank")
   2489    ) {
   2490      return null;
   2491    }
   2492 
   2493    let principal = browser.contentPrincipal;
   2494 
   2495    if (principal.isNullPrincipal) {
   2496      principal = principal.precursorPrincipal;
   2497    }
   2498 
   2499    if (!principal) {
   2500      return null;
   2501    }
   2502 
   2503    // Can't get the original URI for a PDF viewer principal yet.
   2504    if (principal.originNoSuffix == "resource://pdf.js") {
   2505      return null;
   2506    }
   2507 
   2508    return principal.URI;
   2509  },
   2510 
   2511  asyncUpdateUI() {
   2512    OpenSearchManager.updateOpenSearchBadge(window);
   2513  },
   2514 
   2515  onStatusChange(aWebProgress, aRequest, aStatus, aMessage) {
   2516    this.status = aMessage;
   2517    StatusPanel.update();
   2518  },
   2519 
   2520  // Properties used to cache security state used to update the UI
   2521  _event: null,
   2522  _lastLocationForEvent: null,
   2523 
   2524  // This is called in multiple ways:
   2525  //  1. Due to the nsIWebProgressListener.onContentBlockingEvent notification.
   2526  //  2. Called by tabbrowser.xml when updating the current browser.
   2527  //  3. Called directly during this object's initializations.
   2528  //  4. Due to the nsIWebProgressListener.onLocationChange notification.
   2529  // aRequest will be null always in case 2 and 3, and sometimes in case 1 (for
   2530  // instance, there won't be a request when STATE_BLOCKED_TRACKING_CONTENT or
   2531  // other blocking events are observed).
   2532  onContentBlockingEvent(aWebProgress, aRequest, aEvent, aIsSimulated) {
   2533    // Don't need to do anything if the data we use to update the UI hasn't
   2534    // changed
   2535    let uri = gBrowser.currentURI;
   2536    let spec = uri.spec;
   2537    if (this._event == aEvent && this._lastLocationForEvent == spec) {
   2538      return;
   2539    }
   2540    this._lastLocationForEvent = spec;
   2541 
   2542    if (
   2543      typeof aIsSimulated != "boolean" &&
   2544      typeof aIsSimulated != "undefined"
   2545    ) {
   2546      throw new Error(
   2547        "onContentBlockingEvent: aIsSimulated receieved an unexpected type"
   2548      );
   2549    }
   2550 
   2551    gProtectionsHandler.onContentBlockingEvent(
   2552      aEvent,
   2553      aWebProgress,
   2554      aIsSimulated,
   2555      this._event // previous content blocking event
   2556    );
   2557 
   2558    gTrustPanelHandler.onContentBlockingEvent(
   2559      aEvent,
   2560      aWebProgress,
   2561      aIsSimulated,
   2562      this._event // previous content blocking event
   2563    );
   2564 
   2565    // We need the state of the previous content blocking event, so update
   2566    // event after onContentBlockingEvent is called.
   2567    this._event = aEvent;
   2568  },
   2569 
   2570  // This is called in multiple ways:
   2571  //  1. Due to the nsIWebProgressListener.onSecurityChange notification.
   2572  //  2. Called by tabbrowser.xml when updating the current browser.
   2573  //  3. Called directly during this object's initializations.
   2574  // aRequest will be null always in case 2 and 3, and sometimes in case 1.
   2575  onSecurityChange(aWebProgress, aRequest, aState, _aIsSimulated) {
   2576    // Make sure the "https" part of the URL is striked out or not,
   2577    // depending on the current mixed active content blocking state.
   2578    gURLBar.formatValue();
   2579 
   2580    // Update the identity panel, making sure we use the precursorPrincipal's
   2581    // URI where appropriate, for example about:blank windows.
   2582    let uri = gBrowser.currentURI;
   2583    let uriOverride = this._securityURIOverride(gBrowser.selectedBrowser);
   2584    if (uriOverride) {
   2585      uri = uriOverride;
   2586      aState |= Ci.nsIWebProgressListener.STATE_IDENTITY_ASSOCIATED;
   2587    }
   2588 
   2589    if (window.browsingContext.isDocumentPiP) {
   2590      gURLBar.setURI({
   2591        uri,
   2592        isSameDocument: true,
   2593      });
   2594    }
   2595 
   2596    try {
   2597      uri = Services.io.createExposableURI(uri);
   2598    } catch (e) {}
   2599    gIdentityHandler.updateIdentity(aState, uri);
   2600    gTrustPanelHandler.updateIdentity(aState, uri);
   2601  },
   2602 
   2603  // simulate all change notifications after switching tabs
   2604  onUpdateCurrentBrowser: function XWB_onUpdateCurrentBrowser(
   2605    aStateFlags,
   2606    aStatus,
   2607    aMessage,
   2608    _aTotalProgress
   2609  ) {
   2610    if (FullZoom.updateBackgroundTabs) {
   2611      FullZoom.onLocationChange(gBrowser.currentURI, true);
   2612    }
   2613 
   2614    CombinedStopReload.onTabSwitch();
   2615 
   2616    // Docshell should normally take care of hiding the tooltip, but we need to do it
   2617    // ourselves for tabswitches.
   2618    this.hideTooltip();
   2619 
   2620    // Also hide tooltips for content loaded in the parent process:
   2621    document.getElementById("aHTMLTooltip").hidePopup();
   2622 
   2623    var nsIWebProgressListener = Ci.nsIWebProgressListener;
   2624    var loadingDone = aStateFlags & nsIWebProgressListener.STATE_STOP;
   2625    // use a pseudo-object instead of a (potentially nonexistent) channel for getting
   2626    // a correct error message - and make sure that the UI is always either in
   2627    // loading (STATE_START) or done (STATE_STOP) mode
   2628    this.onStateChange(
   2629      gBrowser.webProgress,
   2630      { URI: gBrowser.currentURI },
   2631      loadingDone
   2632        ? nsIWebProgressListener.STATE_STOP
   2633        : nsIWebProgressListener.STATE_START,
   2634      aStatus
   2635    );
   2636    // status message and progress value are undefined if we're done with loading
   2637    if (loadingDone) {
   2638      return;
   2639    }
   2640    this.onStatusChange(gBrowser.webProgress, null, 0, aMessage);
   2641  },
   2642 };
   2643 
   2644 XPCOMUtils.defineLazyPreferenceGetter(
   2645  XULBrowserWindow,
   2646  "spinCursorWhileBusy",
   2647  "browser.spin_cursor_while_busy"
   2648 );
   2649 
   2650 var LinkTargetDisplay = {
   2651  get DELAY_SHOW() {
   2652    delete this.DELAY_SHOW;
   2653    return (this.DELAY_SHOW = Services.prefs.getIntPref(
   2654      "browser.overlink-delay"
   2655    ));
   2656  },
   2657 
   2658  DELAY_HIDE: 250,
   2659  _timer: 0,
   2660 
   2661  get _contextMenu() {
   2662    delete this._contextMenu;
   2663    return (this._contextMenu = document.getElementById(
   2664      "contentAreaContextMenu"
   2665    ));
   2666  },
   2667 
   2668  update({ hideStatusPanelImmediately = false } = {}) {
   2669    if (
   2670      this._contextMenu.state == "open" ||
   2671      this._contextMenu.state == "showing"
   2672    ) {
   2673      this._contextMenu.addEventListener("popuphidden", () => this.update(), {
   2674        once: true,
   2675      });
   2676      return;
   2677    }
   2678 
   2679    clearTimeout(this._timer);
   2680    window.removeEventListener("mousemove", this, true);
   2681 
   2682    if (!XULBrowserWindow.overLink) {
   2683      if (hideStatusPanelImmediately) {
   2684        this._hide();
   2685      } else {
   2686        this._timer = setTimeout(this._hide.bind(this), this.DELAY_HIDE);
   2687      }
   2688      return;
   2689    }
   2690 
   2691    if (StatusPanel.isVisible) {
   2692      StatusPanel.update();
   2693    } else {
   2694      // Let the display appear when the mouse doesn't move within the delay
   2695      this._showDelayed();
   2696      window.addEventListener("mousemove", this, true);
   2697    }
   2698  },
   2699 
   2700  handleEvent(event) {
   2701    switch (event.type) {
   2702      case "mousemove":
   2703        // Restart the delay since the mouse was moved
   2704        clearTimeout(this._timer);
   2705        this._showDelayed();
   2706        break;
   2707    }
   2708  },
   2709 
   2710  _showDelayed() {
   2711    this._timer = setTimeout(
   2712      function (self) {
   2713        StatusPanel.update();
   2714        window.removeEventListener("mousemove", self, true);
   2715      },
   2716      this.DELAY_SHOW,
   2717      this
   2718    );
   2719  },
   2720 
   2721  _hide() {
   2722    clearTimeout(this._timer);
   2723 
   2724    StatusPanel.update();
   2725  },
   2726 };
   2727 
   2728 var CombinedStopReload = {
   2729  // Try to initialize. Returns whether initialization was successful, which
   2730  // may mean we had already initialized.
   2731  ensureInitialized() {
   2732    if (this._initialized) {
   2733      return true;
   2734    }
   2735    if (this._destroyed) {
   2736      return false;
   2737    }
   2738 
   2739    let reload = document.getElementById("reload-button");
   2740    let stop = document.getElementById("stop-button");
   2741    // It's possible the stop/reload buttons have been moved to the palette.
   2742    // They may be reinserted later, so we will retry initialization if/when
   2743    // we get notified of document loads.
   2744    if (!stop || !reload) {
   2745      return false;
   2746    }
   2747 
   2748    this._initialized = true;
   2749    if (!XULBrowserWindow.stopCommand.hasAttribute("disabled")) {
   2750      reload.setAttribute("displaystop", "true");
   2751    }
   2752    stop.addEventListener("click", this);
   2753 
   2754    // Removing attributes based on the observed command doesn't happen if the button
   2755    // is in the palette when the command's attribute is removed (cf. bug 309953)
   2756    for (let button of [stop, reload]) {
   2757      if (button.hasAttribute("disabled")) {
   2758        let command = document.getElementById(button.getAttribute("command"));
   2759        if (!command.hasAttribute("disabled")) {
   2760          button.removeAttribute("disabled");
   2761        }
   2762      }
   2763    }
   2764 
   2765    this.reload = reload;
   2766    this.stop = stop;
   2767    this.stopReloadContainer = this.reload.parentNode;
   2768    this.timeWhenSwitchedToStop = 0;
   2769 
   2770    this.stopReloadContainer.addEventListener("animationend", this);
   2771    this.stopReloadContainer.addEventListener("animationcancel", this);
   2772 
   2773    return true;
   2774  },
   2775 
   2776  uninit() {
   2777    this._destroyed = true;
   2778 
   2779    if (!this._initialized) {
   2780      return;
   2781    }
   2782 
   2783    this._cancelTransition();
   2784    this.stop.removeEventListener("click", this);
   2785    this.stopReloadContainer.removeEventListener("animationend", this);
   2786    this.stopReloadContainer.removeEventListener("animationcancel", this);
   2787    this.stopReloadContainer = null;
   2788    this.reload = null;
   2789    this.stop = null;
   2790  },
   2791 
   2792  handleEvent(event) {
   2793    switch (event.type) {
   2794      case "click":
   2795        if (event.button == 0 && !this.stop.disabled) {
   2796          this._stopClicked = true;
   2797        }
   2798        break;
   2799      case "animationcancel":
   2800      case "animationend": {
   2801        if (
   2802          event.target.classList.contains("toolbarbutton-animatable-image") &&
   2803          (event.animationName == "reload-to-stop" ||
   2804            event.animationName == "stop-to-reload")
   2805        ) {
   2806          this.stopReloadContainer.removeAttribute("animate");
   2807        }
   2808      }
   2809    }
   2810  },
   2811 
   2812  onTabSwitch() {
   2813    // Reset the time in the event of a tabswitch since the stored time
   2814    // would have been associated with the previous tab, so the animation will
   2815    // still run if the page has been loading until long after the tab switch.
   2816    this.timeWhenSwitchedToStop = window.performance.now();
   2817  },
   2818 
   2819  switchToStop(aRequest, aWebProgress) {
   2820    if (
   2821      !this.ensureInitialized() ||
   2822      !this._shouldSwitch(aRequest, aWebProgress)
   2823    ) {
   2824      return;
   2825    }
   2826 
   2827    // Store the time that we switched to the stop button only if a request
   2828    // is active. Requests are null if the switch is related to a tabswitch.
   2829    // This is used to determine if we should show the stop->reload animation.
   2830    if (aRequest instanceof Ci.nsIRequest) {
   2831      this.timeWhenSwitchedToStop = window.performance.now();
   2832    }
   2833 
   2834    let shouldAnimate =
   2835      aRequest instanceof Ci.nsIRequest &&
   2836      aWebProgress.isTopLevel &&
   2837      aWebProgress.isLoadingDocument &&
   2838      !gBrowser.tabAnimationsInProgress &&
   2839      !gReduceMotion &&
   2840      this.stopReloadContainer.closest("#nav-bar-customization-target");
   2841 
   2842    this._cancelTransition();
   2843    if (shouldAnimate) {
   2844      this.stopReloadContainer.setAttribute("animate", "true");
   2845    } else {
   2846      this.stopReloadContainer.removeAttribute("animate");
   2847    }
   2848    this.reload.setAttribute("displaystop", "true");
   2849  },
   2850 
   2851  switchToReload(aRequest, aWebProgress) {
   2852    if (!this.ensureInitialized() || !this.reload.hasAttribute("displaystop")) {
   2853      return;
   2854    }
   2855 
   2856    let shouldAnimate =
   2857      aRequest instanceof Ci.nsIRequest &&
   2858      aWebProgress.isTopLevel &&
   2859      !aWebProgress.isLoadingDocument &&
   2860      !gBrowser.tabAnimationsInProgress &&
   2861      !gReduceMotion &&
   2862      this._loadTimeExceedsMinimumForAnimation() &&
   2863      this.stopReloadContainer.closest("#nav-bar-customization-target");
   2864 
   2865    if (shouldAnimate) {
   2866      this.stopReloadContainer.setAttribute("animate", "true");
   2867    } else {
   2868      this.stopReloadContainer.removeAttribute("animate");
   2869    }
   2870 
   2871    this.reload.removeAttribute("displaystop");
   2872 
   2873    if (!shouldAnimate || this._stopClicked) {
   2874      this._stopClicked = false;
   2875      this._cancelTransition();
   2876      this.reload.disabled =
   2877        XULBrowserWindow.reloadCommand.hasAttribute("disabled");
   2878      return;
   2879    }
   2880 
   2881    if (this._timer) {
   2882      return;
   2883    }
   2884 
   2885    // Temporarily disable the reload button to prevent the user from
   2886    // accidentally reloading the page when intending to click the stop button
   2887    this.reload.disabled = true;
   2888    this._timer = setTimeout(
   2889      function (self) {
   2890        self._timer = 0;
   2891        self.reload.disabled =
   2892          XULBrowserWindow.reloadCommand.hasAttribute("disabled");
   2893      },
   2894      650,
   2895      this
   2896    );
   2897  },
   2898 
   2899  _loadTimeExceedsMinimumForAnimation() {
   2900    // If the time between switching to the stop button then switching to
   2901    // the reload button exceeds 150ms, then we will show the animation.
   2902    // If we don't know when we switched to stop (switchToStop is called
   2903    // after init but before switchToReload), then we will prevent the
   2904    // animation from occuring.
   2905    return (
   2906      this.timeWhenSwitchedToStop &&
   2907      window.performance.now() - this.timeWhenSwitchedToStop > 150
   2908    );
   2909  },
   2910 
   2911  _shouldSwitch(aRequest, aWebProgress) {
   2912    if (
   2913      aRequest &&
   2914      aRequest.originalURI &&
   2915      (aRequest.originalURI.schemeIs("chrome") ||
   2916        (aRequest.originalURI.schemeIs("about") &&
   2917          aWebProgress.isTopLevel &&
   2918          !aRequest.originalURI.spec.startsWith("about:reader")))
   2919    ) {
   2920      return false;
   2921    }
   2922 
   2923    return true;
   2924  },
   2925 
   2926  _cancelTransition() {
   2927    if (this._timer) {
   2928      clearTimeout(this._timer);
   2929      this._timer = 0;
   2930    }
   2931  },
   2932 };
   2933 
   2934 var TabsProgressListener = {
   2935  onStateChange(aBrowser, aWebProgress, aRequest, aStateFlags, aStatus) {
   2936    // Clear OnionLocation UI
   2937    if (
   2938      aStateFlags & Ci.nsIWebProgressListener.STATE_START &&
   2939      aStateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK &&
   2940      aRequest &&
   2941      aWebProgress.isTopLevel
   2942    ) {
   2943      OnionLocationParent.onStateChange(aBrowser);
   2944    }
   2945 
   2946    // Collect telemetry data about tab load times.
   2947    if (
   2948      aWebProgress.isTopLevel &&
   2949      (!aRequest.originalURI || aRequest.originalURI.scheme != "about")
   2950    ) {
   2951      let metricName = "pageLoad";
   2952 
   2953      if (aWebProgress.loadType & Ci.nsIDocShell.LOAD_CMD_RELOAD) {
   2954        // loadType is constructed by shifting loadFlags, this is why we need to
   2955        // do the same shifting here.
   2956        // https://searchfox.org/mozilla-central/rev/11cfa0462a6b5d8c5e2111b8cfddcf78098f0141/docshell/base/nsDocShellLoadTypes.h#22
   2957        if (aWebProgress.loadType & (kSkipCacheFlags << 16)) {
   2958          metricName = "pageReloadSkipCache";
   2959        } else if (aWebProgress.loadType == Ci.nsIDocShell.LOAD_CMD_RELOAD) {
   2960          metricName = "pageReloadNormal";
   2961        } else {
   2962          metricName = "";
   2963        }
   2964      }
   2965 
   2966      const timerIdField = `_${metricName}TimerId`;
   2967      if (aStateFlags & Ci.nsIWebProgressListener.STATE_IS_WINDOW) {
   2968        if (aStateFlags & Ci.nsIWebProgressListener.STATE_START) {
   2969          if (metricName) {
   2970            if (aBrowser[timerIdField]) {
   2971              // Oops, we're seeing another start without having noticed the previous stop.
   2972              Glean.browserTimings[metricName].cancel(aBrowser[timerIdField]);
   2973            }
   2974            aBrowser[timerIdField] = Glean.browserTimings[metricName].start();
   2975          }
   2976          Glean.browserEngagement.totalTopVisits.true.add();
   2977        } else if (
   2978          aStateFlags & Ci.nsIWebProgressListener.STATE_STOP &&
   2979          /* we won't see STATE_START events for pre-rendered tabs */
   2980          metricName &&
   2981          aBrowser[timerIdField]
   2982        ) {
   2983          Glean.browserTimings[metricName].stopAndAccumulate(
   2984            aBrowser[timerIdField]
   2985          );
   2986          aBrowser[timerIdField] = null;
   2987          BrowserTelemetryUtils.recordSiteOriginTelemetry(browserWindows());
   2988        }
   2989      } else if (
   2990        aStateFlags & Ci.nsIWebProgressListener.STATE_STOP &&
   2991        /* we won't see STATE_START events for pre-rendered tabs */
   2992        aStatus == Cr.NS_BINDING_ABORTED &&
   2993        metricName &&
   2994        aBrowser[timerIdField]
   2995      ) {
   2996        Glean.browserTimings[metricName].cancel(aBrowser[timerIdField]);
   2997        aBrowser[timerIdField] = null;
   2998      }
   2999    }
   3000  },
   3001 
   3002  onLocationChange(aBrowser, aWebProgress, aRequest, aLocationURI, aFlags) {
   3003    // Filter out location changes in sub documents.
   3004    if (!aWebProgress.isTopLevel) {
   3005      return;
   3006    }
   3007 
   3008    // Filter out location changes caused by anchor navigation
   3009    // or history.push/pop/replaceState.
   3010    if (aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT) {
   3011      // Reader mode cares about history.pushState and friends.
   3012      // FIXME: The content process should manage this directly (bug 1445351).
   3013      aBrowser.sendMessageToActor(
   3014        "Reader:PushState",
   3015        {
   3016          isArticle: aBrowser.isArticle,
   3017        },
   3018        "AboutReader"
   3019      );
   3020      return;
   3021    }
   3022 
   3023    // Only need to call locationChange if the PopupNotifications object
   3024    // for this window has already been initialized (i.e. its getter no
   3025    // longer exists)
   3026    if (!Object.getOwnPropertyDescriptor(window, "PopupNotifications").get) {
   3027      PopupNotifications.locationChange(aBrowser);
   3028    }
   3029 
   3030    let tab = gBrowser.getTabForBrowser(aBrowser);
   3031    if (tab && tab._sharingState) {
   3032      gBrowser.resetBrowserSharing(aBrowser);
   3033    }
   3034 
   3035    gBrowser.readNotificationBox(aBrowser)?.removeTransientNotifications();
   3036 
   3037    // Notify the mailto notification creation code _after_ clearing transient
   3038    // notifications, so its notification does not immediately get removed.
   3039    Services.obs.notifyObservers(aBrowser, "mailto::onLocationChange", aFlags);
   3040 
   3041    FullZoom.onLocationChange(aLocationURI, false, aBrowser);
   3042    CaptivePortalWatcher.onLocationChange(aBrowser);
   3043  },
   3044 
   3045  onLinkIconAvailable(browser, dataURI, iconURI) {
   3046    if (!iconURI) {
   3047      return;
   3048    }
   3049    if (browser == gBrowser.selectedBrowser) {
   3050      // If the "Add Search Engine" page action is in the urlbar, its image
   3051      // needs to be set to the new icon, so call updateOpenSearchBadge.
   3052      OpenSearchManager.updateOpenSearchBadge(window);
   3053    }
   3054  },
   3055 };
   3056 
   3057 function showFullScreenViewContextMenuItems(popup) {
   3058  for (let node of popup.querySelectorAll('[contexttype="fullscreen"]')) {
   3059    node.hidden = !window.fullScreen;
   3060  }
   3061  let autoHide = popup.querySelector(".fullscreen-context-autohide");
   3062  if (autoHide) {
   3063    FullScreen.updateAutohideMenuitem(autoHide);
   3064  }
   3065 }
   3066 
   3067 function onViewToolbarCommand(aEvent) {
   3068  let node = aEvent.originalTarget;
   3069  let menuId;
   3070  let toolbarId;
   3071  let isVisible;
   3072  if (node.dataset.bookmarksToolbarVisibility) {
   3073    isVisible = node.dataset.visibilityEnum;
   3074    toolbarId = "PersonalToolbar";
   3075    menuId = node.parentNode.parentNode.parentNode.id;
   3076    Services.prefs.setCharPref(
   3077      "browser.toolbars.bookmarks.visibility",
   3078      isVisible
   3079    );
   3080  } else {
   3081    menuId = node.parentNode.id;
   3082    toolbarId = node.getAttribute("toolbarId");
   3083    isVisible = node.hasAttribute("checked");
   3084  }
   3085  CustomizableUI.setToolbarVisibility(toolbarId, isVisible);
   3086  BrowserUsageTelemetry.recordToolbarVisibility(toolbarId, isVisible, menuId);
   3087 }
   3088 
   3089 function setToolbarVisibility(
   3090  toolbar,
   3091  isVisible,
   3092  persist = true,
   3093  animated = true
   3094 ) {
   3095  let hidingAttribute;
   3096  if (toolbar.getAttribute("type") == "menubar") {
   3097    hidingAttribute = "autohide";
   3098    if (AppConstants.platform == "linux") {
   3099      Services.prefs.setBoolPref("ui.key.menuAccessKeyFocuses", !isVisible);
   3100    }
   3101  } else {
   3102    hidingAttribute = "collapsed";
   3103  }
   3104 
   3105  if (toolbar == BookmarkingUI.toolbar) {
   3106    // For the bookmarks toolbar, we need to persist state before toggling
   3107    // the visibility in this window, because the state can be different
   3108    // (newtab vs never or always) even when that won't change visibility
   3109    // in this window.
   3110    if (persist) {
   3111      let prefValue;
   3112      if (typeof isVisible == "string") {
   3113        prefValue = isVisible;
   3114      } else {
   3115        prefValue = isVisible ? "always" : "never";
   3116      }
   3117      Services.prefs.setCharPref(
   3118        "browser.toolbars.bookmarks.visibility",
   3119        prefValue
   3120      );
   3121    }
   3122 
   3123    switch (isVisible) {
   3124      case true:
   3125      case "always":
   3126        isVisible = true;
   3127        break;
   3128      case false:
   3129      case "never":
   3130        isVisible = false;
   3131        break;
   3132      case "newtab":
   3133      default: {
   3134        let currentURI;
   3135        if (!gBrowserInit.domContentLoaded) {
   3136          let uriToLoad = gBrowserInit.uriToLoadPromise;
   3137          if (uriToLoad) {
   3138            if (Array.isArray(uriToLoad)) {
   3139              // We only care about the first tab being loaded
   3140              uriToLoad = uriToLoad[0];
   3141            }
   3142            currentURI = URL.parse(uriToLoad)?.URI;
   3143            if (!currentURI) {
   3144              currentURI = gBrowser?.currentURI;
   3145            }
   3146          }
   3147        } else {
   3148          currentURI = gBrowser.currentURI;
   3149        }
   3150        isVisible = BookmarkingUI.isOnNewTabPage(currentURI);
   3151        break;
   3152      }
   3153    }
   3154  }
   3155 
   3156  if (toolbar.hasAttribute(hidingAttribute) != isVisible) {
   3157    // If this call will not result in a visibility change, return early
   3158    // since dispatching toolbarvisibilitychange will cause views to get rebuilt.
   3159    return;
   3160  }
   3161 
   3162  toolbar.classList.toggle("instant", !animated);
   3163  toolbar.toggleAttribute(hidingAttribute, !isVisible);
   3164  // For the bookmarks toolbar, we will have saved state above. For other
   3165  // toolbars, we need to do it after setting the attribute, or we might
   3166  // save the wrong state.
   3167  if (persist && toolbar.id != "PersonalToolbar") {
   3168    Services.xulStore.persist(toolbar, hidingAttribute);
   3169  }
   3170 
   3171  let eventParams = {
   3172    detail: {
   3173      visible: isVisible,
   3174    },
   3175    bubbles: true,
   3176  };
   3177  let event = new CustomEvent("toolbarvisibilitychange", eventParams);
   3178  toolbar.dispatchEvent(event);
   3179 }
   3180 
   3181 function updateToggleControlLabel(control) {
   3182  if (!control.hasAttribute("label-checked")) {
   3183    return;
   3184  }
   3185 
   3186  if (!control.hasAttribute("label-unchecked")) {
   3187    control.setAttribute("label-unchecked", control.getAttribute("label"));
   3188  }
   3189  let prefix = control.hasAttribute("checked") ? "" : "un";
   3190  control.setAttribute("label", control.getAttribute(`label-${prefix}checked`));
   3191 }
   3192 
   3193 // Propagates Win10's tablet mode into the browser CSS. (Win11's tablet mode is
   3194 // more like non-tablet mode and has no need for this.)
   3195 const Win10TabletModeUpdater = {
   3196  init() {
   3197    if (AppConstants.platform == "win") {
   3198      this.update(WindowsUIUtils.inWin10TabletMode);
   3199      Services.obs.addObserver(this, "tablet-mode-change");
   3200    }
   3201  },
   3202 
   3203  uninit() {
   3204    if (AppConstants.platform == "win") {
   3205      Services.obs.removeObserver(this, "tablet-mode-change");
   3206    }
   3207  },
   3208 
   3209  observe(subject, topic, data) {
   3210    this.update(data == "win10-tablet-mode");
   3211  },
   3212 
   3213  update(isInTabletMode) {
   3214    if (isInTabletMode) {
   3215      document.documentElement.setAttribute("win10-tablet-mode", "true");
   3216    } else {
   3217      document.documentElement.removeAttribute("win10-tablet-mode");
   3218    }
   3219  },
   3220 };
   3221 
   3222 function displaySecurityInfo() {
   3223  BrowserCommands.pageInfo(null, "securityTab");
   3224 }
   3225 
   3226 // Updates the UI density (for touch and compact mode) based on the uidensity pref.
   3227 var gUIDensity = {
   3228  MODE_NORMAL: 0,
   3229  MODE_COMPACT: 1,
   3230  MODE_TOUCH: 2,
   3231  uiDensityPref: "browser.uidensity",
   3232  autoTouchModePref: "browser.touchmode.auto",
   3233  knownPrefs: new Set(["browser.uidensity", "browser.touchmode.auto"]),
   3234 
   3235  init() {
   3236    this.update();
   3237    Services.obs.addObserver(this, "tablet-mode-change");
   3238    Services.prefs.addObserver(this.uiDensityPref, this);
   3239    Services.prefs.addObserver(this.autoTouchModePref, this);
   3240  },
   3241 
   3242  uninit() {
   3243    Services.obs.removeObserver(this, "tablet-mode-change");
   3244    Services.prefs.removeObserver(this.uiDensityPref, this);
   3245    Services.prefs.removeObserver(this.autoTouchModePref, this);
   3246  },
   3247 
   3248  observe(aSubject, aTopic, aPrefName) {
   3249    const ok = (() => {
   3250      if (aTopic == "tablet-mode-change") {
   3251        return true;
   3252      }
   3253      if (aTopic == "nsPref:changed" && this.knownPrefs.has(aPrefName)) {
   3254        return true;
   3255      }
   3256      return false;
   3257    })();
   3258    if (!ok) {
   3259      return;
   3260    }
   3261 
   3262    this.update();
   3263  },
   3264 
   3265  getCurrentDensity() {
   3266    // Automatically override the uidensity to touch in Windows tablet mode
   3267    // (either Win10 or Win11).
   3268    if (AppConstants.platform == "win") {
   3269      const inTablet =
   3270        WindowsUIUtils.inWin10TabletMode || WindowsUIUtils.inWin11TabletMode;
   3271      if (inTablet && Services.prefs.getBoolPref(this.autoTouchModePref)) {
   3272        return { mode: this.MODE_TOUCH, overridden: true };
   3273      }
   3274    }
   3275    return {
   3276      mode: Services.prefs.getIntPref(this.uiDensityPref),
   3277      overridden: false,
   3278    };
   3279  },
   3280 
   3281  update(mode) {
   3282    if (mode == null) {
   3283      mode = this.getCurrentDensity().mode;
   3284    }
   3285 
   3286    let docs = [document.documentElement];
   3287    let shouldUpdateSidebar =
   3288      SidebarController.initialized && SidebarController.isOpen;
   3289    if (shouldUpdateSidebar) {
   3290      docs.push(SidebarController.browser.contentDocument.documentElement);
   3291    }
   3292    for (let doc of docs) {
   3293      switch (mode) {
   3294        case this.MODE_COMPACT:
   3295          doc.setAttribute("uidensity", "compact");
   3296          break;
   3297        case this.MODE_TOUCH:
   3298          doc.setAttribute("uidensity", "touch");
   3299          break;
   3300        default:
   3301          doc.removeAttribute("uidensity");
   3302          break;
   3303      }
   3304    }
   3305    if (shouldUpdateSidebar) {
   3306      let tree = SidebarController.browser.contentDocument.querySelector(
   3307        ".sidebar-placesTree"
   3308      );
   3309      if (tree) {
   3310        // Tree items don't update their styles without changing some property on the
   3311        // parent tree element, like background-color or border. See bug 1407399.
   3312        tree.style.border = "1px";
   3313        tree.style.border = "";
   3314      }
   3315    }
   3316 
   3317    gBrowser.tabContainer.uiDensityChanged();
   3318    gURLBar.uiDensityChanged();
   3319  },
   3320 };
   3321 
   3322 const DynamicShortcutTooltip = {
   3323  nodeToTooltipMap: {
   3324    "bookmarks-menu-button": "bookmarksMenuButton.tooltip",
   3325    "context-reload": "reloadButton.tooltip",
   3326    "context-stop": "stopButton.tooltip",
   3327    "downloads-button": "downloads.tooltip",
   3328    "fullscreen-button": "fullscreenButton.tooltip",
   3329    "appMenu-fullscreen-button2": "fullscreenButton.tooltip",
   3330    "new-window-button": "newWindowButton.tooltip",
   3331    "new-tab-button": "newTabButton.tooltip",
   3332    "tabs-newtab-button": "newTabButton.tooltip",
   3333    "reload-button": "reloadButton.tooltip",
   3334    "stop-button": "stopButton.tooltip",
   3335    "urlbar-zoom-button": "urlbar-zoom-button.tooltip",
   3336    "appMenu-zoomEnlarge-button2": "zoomEnlarge-button.tooltip",
   3337    "appMenu-zoomReset-button2": "zoomReset-button.tooltip",
   3338    "appMenu-zoomReduce-button2": "zoomReduce-button.tooltip",
   3339    "reader-mode-button": "reader-mode-button.tooltip",
   3340    "reader-mode-button-icon": "reader-mode-button.tooltip",
   3341    "vertical-tabs-newtab-button": "newTabButton.tooltip",
   3342  },
   3343 
   3344  nodeToShortcutMap: {
   3345    "bookmarks-menu-button": "manBookmarkKb",
   3346    "context-reload": "key_reload",
   3347    "context-stop": "key_stop",
   3348    "downloads-button": "key_openDownloads",
   3349    "fullscreen-button": "key_enterFullScreen",
   3350    "appMenu-fullscreen-button2": "key_enterFullScreen",
   3351    "new-window-button": "key_newNavigator",
   3352    "new-tab-button": "key_newNavigatorTab",
   3353    "tabs-newtab-button": "key_newNavigatorTab",
   3354    "reload-button": "key_reload",
   3355    "stop-button": "key_stop",
   3356    "urlbar-zoom-button": "key_fullZoomReset",
   3357    "appMenu-zoomEnlarge-button2": "key_fullZoomEnlarge",
   3358    "appMenu-zoomReset-button2": "key_fullZoomReset",
   3359    "appMenu-zoomReduce-button2": "key_fullZoomReduce",
   3360    "reader-mode-button": "key_toggleReaderMode",
   3361    "reader-mode-button-icon": "key_toggleReaderMode",
   3362    "vertical-tabs-newtab-button": "key_newNavigatorTab",
   3363  },
   3364 
   3365  getText(nodeId) {
   3366    if (!this.cache.has(nodeId) && nodeId in this.nodeToTooltipMap) {
   3367      let strId = this.nodeToTooltipMap[nodeId];
   3368      let args = [];
   3369      let shouldCache = true;
   3370      if (nodeId in this.nodeToShortcutMap) {
   3371        let shortcutId = this.nodeToShortcutMap[nodeId];
   3372        let shortcut = document.getElementById(shortcutId);
   3373        if (shortcut) {
   3374          let prettyShortcut = ShortcutUtils.prettifyShortcut(shortcut);
   3375          args.push(prettyShortcut);
   3376          if (!prettyShortcut) {
   3377            shouldCache = false;
   3378          }
   3379        }
   3380      }
   3381      let string = gNavigatorBundle.getFormattedString(strId, args);
   3382      if (shouldCache) {
   3383        this.cache.set(nodeId, string);
   3384      }
   3385      return string;
   3386    }
   3387    return this.cache.get(nodeId);
   3388  },
   3389 
   3390  updateText(aTooltip) {
   3391    let nodeId = aTooltip.triggerNode.id;
   3392    aTooltip.setAttribute("label", this.getText(nodeId));
   3393  },
   3394 
   3395  cache: new Map(),
   3396 };
   3397 
   3398 /*
   3399 * - [ Dependencies ] ---------------------------------------------------------
   3400 *  utilityOverlay.js:
   3401 *    - gatherTextUnder
   3402 */
   3403 
   3404 /**
   3405 * Extracts linkNode and href for the current click target.
   3406 *
   3407 * Note: linkNode will be null if the click wasn't on an anchor
   3408 * element (or XLink).
   3409 *
   3410 * @param event
   3411 *        The click event.
   3412 * @return [href, linkNode].
   3413 */
   3414 function hrefAndLinkNodeForClickEvent(event) {
   3415  function isHTMLLink(aNode) {
   3416    // Be consistent with what nsContextMenu.js does.
   3417    return (
   3418      (HTMLAnchorElement.isInstance(aNode) && aNode.href) ||
   3419      (HTMLAreaElement.isInstance(aNode) && aNode.href) ||
   3420      HTMLLinkElement.isInstance(aNode)
   3421    );
   3422  }
   3423 
   3424  let node = event.composedTarget;
   3425  while (node && !isHTMLLink(node)) {
   3426    node = node.flattenedTreeParentNode;
   3427  }
   3428 
   3429  if (node) {
   3430    return [node.href, node];
   3431  }
   3432 
   3433  // If there is no linkNode, try simple XLink.
   3434  let href, baseURI;
   3435  node = event.composedTarget;
   3436  while (node && !href) {
   3437    if (
   3438      node.nodeType == Node.ELEMENT_NODE &&
   3439      (node.localName == "a" ||
   3440        node.namespaceURI == "http://www.w3.org/1998/Math/MathML")
   3441    ) {
   3442      href =
   3443        node.getAttribute("href") ||
   3444        node.getAttributeNS("http://www.w3.org/1999/xlink", "href");
   3445 
   3446      if (href) {
   3447        baseURI = node.baseURI;
   3448        break;
   3449      }
   3450    }
   3451    node = node.flattenedTreeParentNode;
   3452  }
   3453 
   3454  // In case of XLink, we don't return the node we got href from since
   3455  // callers expect <a>-like elements.
   3456  return [href ? makeURLAbsolute(baseURI, href) : null, null];
   3457 }
   3458 
   3459 /**
   3460 * Called whenever the user clicks in the content area.
   3461 *
   3462 * Note: the default event is prevented if the click is handled.
   3463 *
   3464 * @param event
   3465 *        The click event.
   3466 * @param isPanelClick
   3467 *        Whether the event comes from an extension panel.
   3468 */
   3469 function contentAreaClick(event, isPanelClick) {
   3470  if (!event.isTrusted || event.defaultPrevented || event.button != 0) {
   3471    return;
   3472  }
   3473 
   3474  let [href, linkNode] = hrefAndLinkNodeForClickEvent(event);
   3475  if (!href) {
   3476    // Not a link, handle middle mouse navigation.
   3477    if (
   3478      event.button == 1 &&
   3479      Services.prefs.getBoolPref("middlemouse.contentLoadURL") &&
   3480      !Services.prefs.getBoolPref("general.autoScroll")
   3481    ) {
   3482      middleMousePaste(event);
   3483      event.preventDefault();
   3484    }
   3485    return;
   3486  }
   3487 
   3488  // This code only applies if we have a linkNode (i.e. clicks on real anchor
   3489  // elements, as opposed to XLink).
   3490  if (
   3491    linkNode &&
   3492    event.button == 0 &&
   3493    !event.ctrlKey &&
   3494    !event.shiftKey &&
   3495    !event.altKey &&
   3496    !event.metaKey
   3497  ) {
   3498    // An extension panel's links should target the main content area.  Do this
   3499    // if no modifier keys are down and if there's no target or the target
   3500    // equals _main (the IE convention) or _content (the Mozilla convention).
   3501    let target = linkNode.target;
   3502    let mainTarget = !target || target == "_content" || target == "_main";
   3503    if (isPanelClick && mainTarget) {
   3504      // javascript and data links should be executed in the current browser.
   3505      if (
   3506        linkNode.getAttribute("onclick") ||
   3507        href.startsWith("javascript:") ||
   3508        href.startsWith("data:")
   3509      ) {
   3510        return;
   3511      }
   3512 
   3513      try {
   3514        urlSecurityCheck(href, linkNode.ownerDocument.nodePrincipal);
   3515      } catch (ex) {
   3516        // Prevent loading unsecure destinations.
   3517        event.preventDefault();
   3518        return;
   3519      }
   3520 
   3521      openLinkIn(href, "current", {
   3522        allowThirdPartyFixup: false,
   3523      });
   3524      event.preventDefault();
   3525      return;
   3526    }
   3527  }
   3528 
   3529  handleLinkClick(event, href, linkNode);
   3530 
   3531  // Mark the page as a user followed link.  This is done so that history can
   3532  // distinguish automatic embed visits from user activated ones.  For example
   3533  // pages loaded in frames are embed visits and lost with the session, while
   3534  // visits across frames should be preserved.
   3535  try {
   3536    if (!PrivateBrowsingUtils.isWindowPrivate(window)) {
   3537      PlacesUIUtils.markPageAsFollowedLink(href);
   3538    }
   3539  } catch (ex) {
   3540    /* Skip invalid URIs. */
   3541  }
   3542 }
   3543 
   3544 /**
   3545 * Handles clicks on links.
   3546 *
   3547 * @return true if the click event was handled, false otherwise.
   3548 */
   3549 function handleLinkClick(event, href, linkNode) {
   3550  if (event.button == 2) {
   3551    // right click
   3552    return false;
   3553  }
   3554 
   3555  var where = BrowserUtils.whereToOpenLink(event);
   3556  if (where == "current") {
   3557    return false;
   3558  }
   3559 
   3560  var doc = event.target.ownerDocument;
   3561  let referrerInfo = Cc["@mozilla.org/referrer-info;1"].createInstance(
   3562    Ci.nsIReferrerInfo
   3563  );
   3564  if (linkNode) {
   3565    referrerInfo.initWithElement(linkNode);
   3566  } else {
   3567    referrerInfo.initWithDocument(doc);
   3568  }
   3569 
   3570  if (where == "save") {
   3571    saveURL(
   3572      href,
   3573      null,
   3574      linkNode ? gatherTextUnder(linkNode) : "",
   3575      null,
   3576      true,
   3577      true,
   3578      referrerInfo,
   3579      doc.cookieJarSettings,
   3580      doc
   3581    );
   3582    event.preventDefault();
   3583    return true;
   3584  }
   3585 
   3586  let frameID = WebNavigationFrames.getFrameId(doc.defaultView);
   3587 
   3588  urlSecurityCheck(href, doc.nodePrincipal);
   3589  let params = {
   3590    charset: doc.characterSet,
   3591    referrerInfo,
   3592    originPrincipal: doc.nodePrincipal,
   3593    originStoragePrincipal: doc.effectiveStoragePrincipal,
   3594    triggeringPrincipal: doc.nodePrincipal,
   3595    policyContainer: doc.policyContainer,
   3596    frameID,
   3597  };
   3598 
   3599  // The new tab/window must use the same userContextId
   3600  if (doc.nodePrincipal.originAttributes.userContextId) {
   3601    params.userContextId = doc.nodePrincipal.originAttributes.userContextId;
   3602  }
   3603 
   3604  openLinkIn(href, where, params);
   3605  event.preventDefault();
   3606  return true;
   3607 }
   3608 
   3609 /**
   3610 * Handles paste on middle mouse clicks.
   3611 *
   3612 * @param event {Event | Object} Event or JSON object.
   3613 */
   3614 function middleMousePaste(event) {
   3615  let clipboard = readFromClipboard();
   3616  if (!clipboard) {
   3617    return;
   3618  }
   3619 
   3620  // Strip embedded newlines and surrounding whitespace, to match the URL
   3621  // bar's behavior (stripsurroundingwhitespace)
   3622  clipboard = clipboard.replace(/\s*\n\s*/g, "");
   3623 
   3624  clipboard = UrlbarUtils.stripUnsafeProtocolOnPaste(clipboard);
   3625 
   3626  // if it's not the current tab, we don't need to do anything because the
   3627  // browser doesn't exist.
   3628  let where = BrowserUtils.whereToOpenLink(event, true, false);
   3629  let lastLocationChange;
   3630  if (where == "current") {
   3631    lastLocationChange = gBrowser.selectedBrowser.lastLocationChange;
   3632  }
   3633 
   3634  UrlbarUtils.getShortcutOrURIAndPostData(clipboard).then(data => {
   3635    try {
   3636      makeURI(data.url);
   3637    } catch (ex) {
   3638      // Not a valid URI.
   3639      return;
   3640    }
   3641 
   3642    try {
   3643      UrlbarUtils.addToUrlbarHistory(data.url, window);
   3644    } catch (ex) {
   3645      // Things may go wrong when adding url to session history,
   3646      // but don't let that interfere with the loading of the url.
   3647      console.error(ex);
   3648    }
   3649 
   3650    if (
   3651      where != "current" ||
   3652      lastLocationChange == gBrowser.selectedBrowser.lastLocationChange
   3653    ) {
   3654      openUILink(data.url, event, {
   3655        ignoreButton: true,
   3656        allowInheritPrincipal: data.mayInheritPrincipal,
   3657        triggeringPrincipal: gBrowser.selectedBrowser.contentPrincipal,
   3658        policyContainer: gBrowser.selectedBrowser.policyContainer,
   3659      });
   3660    }
   3661  });
   3662 
   3663  if (Event.isInstance(event)) {
   3664    event.stopPropagation();
   3665  }
   3666 }
   3667 
   3668 // handleDroppedLink has the following 2 overloads:
   3669 //   handleDroppedLink(event, url, name, triggeringPrincipal)
   3670 //   handleDroppedLink(event, links, triggeringPrincipal)
   3671 function handleDroppedLink(
   3672  event,
   3673  urlOrLinks,
   3674  nameOrTriggeringPrincipal,
   3675  triggeringPrincipal
   3676 ) {
   3677  let links;
   3678  if (Array.isArray(urlOrLinks)) {
   3679    links = urlOrLinks;
   3680    triggeringPrincipal = nameOrTriggeringPrincipal;
   3681  } else {
   3682    links = [{ url: urlOrLinks, nameOrTriggeringPrincipal, type: "" }];
   3683  }
   3684 
   3685  let lastLocationChange = gBrowser.selectedBrowser.lastLocationChange;
   3686 
   3687  let userContextId = gBrowser.selectedBrowser.getAttribute("usercontextid");
   3688 
   3689  // event is null if links are dropped in content process.
   3690  // inBackground should be false, as it's loading into current browser.
   3691  let inBackground = false;
   3692  if (event) {
   3693    inBackground = Services.prefs.getBoolPref("browser.tabs.loadInBackground");
   3694    if (event.shiftKey) {
   3695      inBackground = !inBackground;
   3696    }
   3697  }
   3698 
   3699  (async function () {
   3700    if (
   3701      links.length >=
   3702      Services.prefs.getIntPref("browser.tabs.maxOpenBeforeWarn")
   3703    ) {
   3704      // Sync dialog cannot be used inside drop event handler.
   3705      let answer = await OpenInTabsUtils.promiseConfirmOpenInTabs(
   3706        links.length,
   3707        window
   3708      );
   3709      if (!answer) {
   3710        return;
   3711      }
   3712    }
   3713 
   3714    let urls = [];
   3715    let postDatas = [];
   3716    for (let link of links) {
   3717      let data = await UrlbarUtils.getShortcutOrURIAndPostData(link.url);
   3718      urls.push(data.url);
   3719      postDatas.push(data.postData);
   3720    }
   3721    if (lastLocationChange == gBrowser.selectedBrowser.lastLocationChange) {
   3722      gBrowser.loadTabs(urls, {
   3723        inBackground,
   3724        replace: true,
   3725        allowThirdPartyFixup: false,
   3726        postDatas,
   3727        userContextId,
   3728        triggeringPrincipal,
   3729      });
   3730    }
   3731  })();
   3732 
   3733  // If links are dropped in content process, event.preventDefault() should be
   3734  // called in content process.
   3735  if (event) {
   3736    // Keep the event from being handled by the dragDrop listeners
   3737    // built-in to gecko if they happen to be above us.
   3738    event.preventDefault();
   3739  }
   3740 }
   3741 
   3742 // Note that this is also called from non-browser windows on OSX, which do
   3743 // share menu items but not much else. See nonbrowser-mac.js.
   3744 var BrowserOffline = {
   3745  _inited: false,
   3746 
   3747  // BrowserOffline Public Methods
   3748  init() {
   3749    if (!this._uiElement) {
   3750      this._uiElement = document.getElementById("cmd_toggleOfflineStatus");
   3751    }
   3752 
   3753    Services.obs.addObserver(this, "network:offline-status-changed");
   3754 
   3755    this._updateOfflineUI(Services.io.offline);
   3756 
   3757    this._inited = true;
   3758  },
   3759 
   3760  uninit() {
   3761    if (this._inited) {
   3762      Services.obs.removeObserver(this, "network:offline-status-changed");
   3763    }
   3764  },
   3765 
   3766  toggleOfflineStatus() {
   3767    var ioService = Services.io;
   3768 
   3769    if (!ioService.offline && !this._canGoOffline()) {
   3770      this._updateOfflineUI(false);
   3771      return;
   3772    }
   3773 
   3774    ioService.offline = !ioService.offline;
   3775  },
   3776 
   3777  // nsIObserver
   3778  observe(aSubject, aTopic) {
   3779    if (aTopic != "network:offline-status-changed") {
   3780      return;
   3781    }
   3782 
   3783    // This notification is also received because of a loss in connectivity,
   3784    // which we ignore by updating the UI to the current value of io.offline
   3785    this._updateOfflineUI(Services.io.offline);
   3786  },
   3787 
   3788  // BrowserOffline Implementation Methods
   3789  _canGoOffline() {
   3790    try {
   3791      var cancelGoOffline = Cc["@mozilla.org/supports-PRBool;1"].createInstance(
   3792        Ci.nsISupportsPRBool
   3793      );
   3794      Services.obs.notifyObservers(cancelGoOffline, "offline-requested");
   3795 
   3796      // Something aborted the quit process.
   3797      if (cancelGoOffline.data) {
   3798        return false;
   3799      }
   3800    } catch (ex) {}
   3801 
   3802    return true;
   3803  },
   3804 
   3805  _uiElement: null,
   3806  _updateOfflineUI(aOffline) {
   3807    var offlineLocked = Services.prefs.prefIsLocked("network.online");
   3808    this._uiElement.toggleAttribute("disabled", !!offlineLocked);
   3809    this._uiElement.toggleAttribute("checked", aOffline);
   3810  },
   3811 };
   3812 
   3813 function CanCloseWindow() {
   3814  // Avoid redundant calls to canClose from showing multiple
   3815  // PermitUnload dialogs.
   3816  if (Services.startup.shuttingDown || window.skipNextCanClose) {
   3817    return true;
   3818  }
   3819 
   3820  for (let browser of gBrowser.browsers) {
   3821    // Don't instantiate lazy browsers.
   3822    if (!browser.isConnected) {
   3823      continue;
   3824    }
   3825 
   3826    let { permitUnload } = browser.permitUnload();
   3827    if (!permitUnload) {
   3828      return false;
   3829    }
   3830  }
   3831  return true;
   3832 }
   3833 
   3834 function WindowIsClosing(event) {
   3835  let source;
   3836  if (event) {
   3837    let target = event.sourceEvent?.target;
   3838    if (target?.id?.startsWith("menu_")) {
   3839      source = "menuitem";
   3840    } else if (target?.nodeName == "toolbarbutton") {
   3841      source = "close-button";
   3842    } else {
   3843      let key = AppConstants.platform == "macosx" ? "metaKey" : "ctrlKey";
   3844      source = event[key] ? "shortcut" : "OS";
   3845    }
   3846  }
   3847  if (!closeWindow(false, warnAboutClosingWindow, source)) {
   3848    return false;
   3849  }
   3850 
   3851  // In theory we should exit here and the Window's internal Close
   3852  // method should trigger canClose on BrowserDOMWindow. However, by
   3853  // that point it's too late to be able to show a prompt for
   3854  // PermitUnload. So we do it here, when we still can.
   3855  if (CanCloseWindow()) {
   3856    // This flag ensures that the later canClose call does nothing.
   3857    // It's only needed to make tests pass, since they detect the
   3858    // prompt even when it's not actually shown.
   3859    window.skipNextCanClose = true;
   3860    return true;
   3861  }
   3862 
   3863  return false;
   3864 }
   3865 
   3866 /**
   3867 * Checks if this is the last full *browser* window around. If it is, this will
   3868 * be communicated like quitting. Otherwise, we warn about closing multiple tabs.
   3869 *
   3870 * @returns true if closing can proceed, false if it got cancelled.
   3871 */
   3872 function warnAboutClosingWindow() {
   3873  // Popups aren't considered full browser windows; we also ignore private windows.
   3874  let isPBWindow =
   3875    PrivateBrowsingUtils.isWindowPrivate(window) &&
   3876    !PrivateBrowsingUtils.permanentPrivateBrowsing;
   3877 
   3878  if (!isPBWindow && !toolbar.visible) {
   3879    return gBrowser.warnAboutClosingTabs(
   3880      gBrowser.openTabs.length,
   3881      gBrowser.closingTabsEnum.ALL
   3882    );
   3883  }
   3884 
   3885  // Figure out if there's at least one other browser window around.
   3886  let otherPBWindowExists = false;
   3887  let otherWindowExists = false;
   3888  for (let win of browserWindows()) {
   3889    if (!win.closed && win != window) {
   3890      otherWindowExists = true;
   3891      if (isPBWindow && PrivateBrowsingUtils.isWindowPrivate(win)) {
   3892        otherPBWindowExists = true;
   3893      }
   3894      // If the current window is not in private browsing mode we don't need to
   3895      // look for other pb windows, we can leave the loop when finding the
   3896      // first non-popup window. If however the current window is in private
   3897      // browsing mode then we need at least one other pb and one non-popup
   3898      // window to break out early.
   3899      if (!isPBWindow || otherPBWindowExists) {
   3900        break;
   3901      }
   3902    }
   3903  }
   3904 
   3905  if (isPBWindow && !otherPBWindowExists) {
   3906    let exitingCanceled = Cc["@mozilla.org/supports-PRBool;1"].createInstance(
   3907      Ci.nsISupportsPRBool
   3908    );
   3909    exitingCanceled.data = false;
   3910    Services.obs.notifyObservers(exitingCanceled, "last-pb-context-exiting");
   3911    if (exitingCanceled.data) {
   3912      return false;
   3913    }
   3914  }
   3915 
   3916  if (otherWindowExists) {
   3917    return (
   3918      isPBWindow ||
   3919      gBrowser.warnAboutClosingTabs(
   3920        gBrowser.openTabs.length,
   3921        gBrowser.closingTabsEnum.ALL
   3922      )
   3923    );
   3924  }
   3925 
   3926  let os = Services.obs;
   3927 
   3928  let closingCanceled = Cc["@mozilla.org/supports-PRBool;1"].createInstance(
   3929    Ci.nsISupportsPRBool
   3930  );
   3931  os.notifyObservers(closingCanceled, "browser-lastwindow-close-requested");
   3932  if (closingCanceled.data) {
   3933    return false;
   3934  }
   3935 
   3936  os.notifyObservers(null, "browser-lastwindow-close-granted");
   3937 
   3938  // OS X doesn't quit the application when the last window is closed, but keeps
   3939  // the session alive. Hence don't prompt users to save tabs, but warn about
   3940  // closing multiple tabs.
   3941  return (
   3942    AppConstants.platform != "macosx" ||
   3943    isPBWindow ||
   3944    gBrowser.warnAboutClosingTabs(
   3945      gBrowser.openTabs.length,
   3946      gBrowser.closingTabsEnum.ALL
   3947    )
   3948  );
   3949 }
   3950 
   3951 var MailIntegration = {
   3952  sendLinkForBrowser(aBrowser) {
   3953    this.sendMessage(
   3954      gURLBar.makeURIReadable(aBrowser.currentURI).displaySpec,
   3955      aBrowser.contentTitle
   3956    );
   3957  },
   3958 
   3959  sendMessage(aBody, aSubject) {
   3960    // generate a mailto url based on the url and the url's title
   3961    var mailtoUrl = "mailto:";
   3962    if (aBody) {
   3963      mailtoUrl += "?body=" + encodeURIComponent(aBody);
   3964      mailtoUrl += "&subject=" + encodeURIComponent(aSubject);
   3965    }
   3966 
   3967    var uri = makeURI(mailtoUrl);
   3968 
   3969    // now pass this uri to the operating system
   3970    this._launchExternalUrl(uri);
   3971  },
   3972 
   3973  // a generic method which can be used to pass arbitrary urls to the operating
   3974  // system.
   3975  // aURL --> a nsIURI which represents the url to launch
   3976  _launchExternalUrl(aURL) {
   3977    var extProtocolSvc = Cc[
   3978      "@mozilla.org/uriloader/external-protocol-service;1"
   3979    ].getService(Ci.nsIExternalProtocolService);
   3980    if (extProtocolSvc) {
   3981      extProtocolSvc.loadURI(
   3982        aURL,
   3983        Services.scriptSecurityManager.getSystemPrincipal()
   3984      );
   3985    }
   3986  },
   3987 };
   3988 
   3989 /**
   3990 * When the browser is being controlled from out-of-process,
   3991 * e.g. when Marionette or the remote debugging protocol is used,
   3992 * we add a visual hint to the browser UI to indicate to the user
   3993 * that the browser session is under remote control.
   3994 *
   3995 * This is called when the content browser initialises (from gBrowserInit.onLoad())
   3996 * and when the "remote-listening" system notification fires.
   3997 */
   3998 const gRemoteControl = {
   3999  observe() {
   4000    gRemoteControl.updateVisualCue();
   4001  },
   4002 
   4003  updateVisualCue() {
   4004    // Disable updating the remote control cue for performance tests,
   4005    // because these could fail due to an early initialization of Marionette.
   4006    const disableRemoteControlCue = Services.prefs.getBoolPref(
   4007      "browser.chrome.disableRemoteControlCueForTests",
   4008      false
   4009    );
   4010    if (disableRemoteControlCue && Cu.isInAutomation) {
   4011      return;
   4012    }
   4013 
   4014    const mainWindow = document.documentElement;
   4015    const remoteControlComponent = this.getRemoteControlComponent();
   4016    if (remoteControlComponent) {
   4017      mainWindow.setAttribute("remotecontrol", "true");
   4018      const remoteControlIcon = document.getElementById("remote-control-icon");
   4019      document.l10n.setAttributes(
   4020        remoteControlIcon,
   4021        "urlbar-remote-control-notification-anchor2",
   4022        { component: remoteControlComponent }
   4023      );
   4024    } else {
   4025      mainWindow.removeAttribute("remotecontrol");
   4026    }
   4027  },
   4028 
   4029  getRemoteControlComponent() {
   4030    // For DevTools sockets, only show the remote control cue if the socket is
   4031    // not coming from a regular Browser Toolbox debugging session.
   4032    if (
   4033      DevToolsSocketStatus.hasSocketOpened({
   4034        excludeBrowserToolboxSockets: true,
   4035      })
   4036    ) {
   4037      return "DevTools";
   4038    }
   4039 
   4040    if (Marionette.running) {
   4041      return "Marionette";
   4042    }
   4043 
   4044    if (RemoteAgent.running) {
   4045      return "RemoteAgent";
   4046    }
   4047 
   4048    return null;
   4049  },
   4050 };
   4051 
   4052 /**
   4053 * Switch to a tab that has a given URI, and focuses its browser window.
   4054 * If a matching tab is in this window, it will be switched to. Otherwise, other
   4055 * windows will be searched.
   4056 *
   4057 * @param aURI
   4058 *        URI to search for
   4059 * @param aOpenNew
   4060 *        True to open a new tab and switch to it, if no existing tab is found.
   4061 *        If no suitable window is found, a new one will be opened.
   4062 * @param aOpenParams
   4063 *        If switching to this URI results in us opening a tab, aOpenParams
   4064 *        will be the parameter object that gets passed to openTrustedLinkIn. Please
   4065 *        see the documentation for openTrustedLinkIn to see what parameters can be
   4066 *        passed via this object.
   4067 *        This object also allows:
   4068 *        - 'ignoreFragment' property to be set to true to exclude fragment-portion
   4069 *        matching when comparing URIs.
   4070 *          If set to "whenComparing", the fragment will be unmodified.
   4071 *          If set to "whenComparingAndReplace", the fragment will be replaced.
   4072 *        - 'ignoreQueryString' boolean property to be set to true to exclude query string
   4073 *        matching when comparing URIs.
   4074 *        - 'replaceQueryString' boolean property to be set to true to exclude query string
   4075 *        matching when comparing URIs and overwrite the initial query string with
   4076 *        the one from the new URI.
   4077 *        - 'adoptIntoActiveWindow' boolean property to be set to true to adopt the tab
   4078 *        into the current window.
   4079 * @param aUserContextId
   4080 *        If not null, will switch to the first found tab having the provided
   4081 *        userContextId.
   4082 * @return True if an existing tab was found, false otherwise
   4083 */
   4084 function switchToTabHavingURI(
   4085  aURI,
   4086  aOpenNew,
   4087  aOpenParams = {},
   4088  aUserContextId = null
   4089 ) {
   4090  return URILoadingHelper.switchToTabHavingURI(
   4091    window,
   4092    aURI,
   4093    aOpenNew,
   4094    aOpenParams,
   4095    aUserContextId
   4096  );
   4097 }
   4098 
   4099 // Prompt user to restart the browser in safe mode
   4100 function safeModeRestart() {
   4101  if (Services.appinfo.inSafeMode) {
   4102    let cancelQuit = Cc["@mozilla.org/supports-PRBool;1"].createInstance(
   4103      Ci.nsISupportsPRBool
   4104    );
   4105    Services.obs.notifyObservers(
   4106      cancelQuit,
   4107      "quit-application-requested",
   4108      "restart"
   4109    );
   4110 
   4111    if (cancelQuit.data) {
   4112      return;
   4113    }
   4114 
   4115    Services.startup.quit(
   4116      Ci.nsIAppStartup.eRestart | Ci.nsIAppStartup.eAttemptQuit
   4117    );
   4118    return;
   4119  }
   4120 
   4121  Services.obs.notifyObservers(window, "restart-in-safe-mode");
   4122 }
   4123 
   4124 /* duplicateTabIn duplicates tab in a place specified by the parameter |where|.
   4125 *
   4126 * |where| can be:
   4127 *  "tab"         new tab
   4128 *  "tabshifted"  same as "tab" but in background if default is to select new
   4129 *                tabs, and vice versa
   4130 *  "window"      new window
   4131 *
   4132 * delta is the offset to the history entry that you want to load.
   4133 */
   4134 function duplicateTabIn(aTab, where, delta) {
   4135  switch (where) {
   4136    case "window": {
   4137      let otherWin = OpenBrowserWindow({
   4138        private: PrivateBrowsingUtils.isBrowserPrivate(aTab.linkedBrowser),
   4139      });
   4140      let delayedStartupFinished = (subject, topic) => {
   4141        if (
   4142          topic == "browser-delayed-startup-finished" &&
   4143          subject == otherWin
   4144        ) {
   4145          Services.obs.removeObserver(delayedStartupFinished, topic);
   4146          let otherGBrowser = otherWin.gBrowser;
   4147          let otherTab = otherGBrowser.selectedTab;
   4148          SessionStore.duplicateTab(otherWin, aTab, delta);
   4149          otherGBrowser.removeTab(otherTab, { animate: false });
   4150        }
   4151      };
   4152 
   4153      Services.obs.addObserver(
   4154        delayedStartupFinished,
   4155        "browser-delayed-startup-finished"
   4156      );
   4157      break;
   4158    }
   4159    case "tabshifted":
   4160      SessionStore.duplicateTab(window, aTab, delta);
   4161      // A background tab has been opened, nothing else to do here.
   4162      break;
   4163    case "tab":
   4164      SessionStore.duplicateTab(window, aTab, delta, true, {
   4165        inBackground: false,
   4166      });
   4167      break;
   4168  }
   4169  if (aTab.group) {
   4170    Glean.tabgroup.tabInteractions.duplicate.add();
   4171  }
   4172 }
   4173 
   4174 var MousePosTracker = {
   4175  _listeners: new Set(),
   4176  _x: 0,
   4177  _y: 0,
   4178 
   4179  /**
   4180   * Registers a listener.
   4181   *
   4182   * @param listener (object)
   4183   *        A listener is expected to expose the following properties:
   4184   *
   4185   *        getMouseTargetRect (function)
   4186   *          Returns the rect that the MousePosTracker needs to alert
   4187   *          the listener about if the mouse happens to be within it.
   4188   *
   4189   *        onMouseEnter (function, optional)
   4190   *          The function to be called if the mouse enters the rect
   4191   *          returned by getMouseTargetRect. MousePosTracker always
   4192   *          runs this inside of a requestAnimationFrame, since it
   4193   *          assumes that the notification is used to update the DOM.
   4194   *
   4195   *        onMouseLeave (function, optional)
   4196   *          The function to be called if the mouse exits the rect
   4197   *          returned by getMouseTargetRect. MousePosTracker always
   4198   *          runs this inside of a requestAnimationFrame, since it
   4199   *          assumes that the notification is used to update the DOM.
   4200   */
   4201  addListener(listener) {
   4202    if (this._listeners.has(listener)) {
   4203      return;
   4204    }
   4205 
   4206    listener._hover = false;
   4207    this._listeners.add(listener);
   4208 
   4209    this._callListener(listener);
   4210  },
   4211 
   4212  removeListener(listener) {
   4213    this._listeners.delete(listener);
   4214  },
   4215 
   4216  handleEvent(event) {
   4217    if (event.type === "mouseout" && event.currentTarget !== window) {
   4218      return;
   4219    }
   4220 
   4221    this._x = event.screenX - window.mozInnerScreenX;
   4222    this._y = event.screenY - window.mozInnerScreenY;
   4223 
   4224    this._listeners.forEach(listener => {
   4225      try {
   4226        this._callListener(listener);
   4227      } catch (e) {
   4228        console.error(e);
   4229      }
   4230    });
   4231  },
   4232 
   4233  _callListener(listener) {
   4234    let rect = listener.getMouseTargetRect();
   4235    let hover =
   4236      this._x >= rect.left &&
   4237      this._x <= rect.right &&
   4238      this._y >= rect.top &&
   4239      this._y <= rect.bottom;
   4240 
   4241    if (hover == listener._hover) {
   4242      return;
   4243    }
   4244 
   4245    listener._hover = hover;
   4246 
   4247    if (hover) {
   4248      if (listener.onMouseEnter) {
   4249        listener.onMouseEnter();
   4250      }
   4251    } else if (listener.onMouseLeave) {
   4252      listener.onMouseLeave();
   4253    }
   4254  },
   4255 };
   4256 
   4257 var PanicButtonNotifier = {
   4258  init() {
   4259    this._initialized = true;
   4260    if (window.PanicButtonNotifierShouldNotify) {
   4261      delete window.PanicButtonNotifierShouldNotify;
   4262      this.notify();
   4263    }
   4264  },
   4265  createPanelIfNeeded() {
   4266    // Lazy load the panic-button-success-notification panel the first time we need to display it.
   4267    if (!document.getElementById("panic-button-success-notification")) {
   4268      let template = document.getElementById("panicButtonNotificationTemplate");
   4269      template.replaceWith(template.content);
   4270    }
   4271  },
   4272  notify() {
   4273    if (!this._initialized) {
   4274      window.PanicButtonNotifierShouldNotify = true;
   4275      return;
   4276    }
   4277    // Display notification panel here...
   4278    try {
   4279      this.createPanelIfNeeded();
   4280      let popup = document.getElementById("panic-button-success-notification");
   4281      popup.hidden = false;
   4282 
   4283      // To close the popup in 3 seconds after the popup is shown but left uninteracted.
   4284      let closePopup = () => {
   4285        popup.hidePopup();
   4286      };
   4287      popup.addEventListener("popupshown", function () {
   4288        PanicButtonNotifier.timer = setTimeout(closePopup, 3000);
   4289      });
   4290 
   4291      let closeButton = document.getElementById(
   4292        "panic-button-success-closebutton"
   4293      );
   4294      closeButton.addEventListener("command", closePopup);
   4295 
   4296      // To prevent the popup from closing when user tries to interact with the
   4297      // popup using mouse or keyboard.
   4298      let onUserInteractsWithPopup = () => {
   4299        clearTimeout(PanicButtonNotifier.timer);
   4300      };
   4301      popup.addEventListener("mouseover", onUserInteractsWithPopup);
   4302      window.addEventListener("keydown", onUserInteractsWithPopup);
   4303 
   4304      let removeListeners = () => {
   4305        popup.removeEventListener("mouseover", onUserInteractsWithPopup);
   4306        window.removeEventListener("keydown", onUserInteractsWithPopup);
   4307        popup.removeEventListener("popuphidden", removeListeners);
   4308        closeButton.removeEventListener("command", closePopup);
   4309        clearTimeout(PanicButtonNotifier.timer);
   4310      };
   4311      popup.addEventListener("popuphidden", removeListeners);
   4312 
   4313      let widget = CustomizableUI.getWidget("panic-button").forWindow(window);
   4314      let anchor = widget.anchor.icon;
   4315      popup.openPopup(anchor, popup.getAttribute("position"));
   4316    } catch (ex) {
   4317      console.error(ex);
   4318    }
   4319  },
   4320 };
   4321 
   4322 /**
   4323 * The TabDialogBox supports opening window dialogs as SubDialogs on the tab and content
   4324 * level. Both tab and content dialogs have their own separate managers.
   4325 * Dialogs will be queued FIFO and cover the web content.
   4326 * Dialogs are closed when the user reloads or leaves the page.
   4327 * While a dialog is open PopupNotifications, such as permission prompts, are
   4328 * suppressed.
   4329 */
   4330 class TabDialogBox {
   4331  static _containerFor(browser) {
   4332    return browser.closest(
   4333      ".browserSidebarContainer, .webextension-popup-stack, .sidebar-browser-stack"
   4334    );
   4335  }
   4336 
   4337  constructor(browser) {
   4338    this._weakBrowserRef = Cu.getWeakReference(browser);
   4339 
   4340    // Create parent element for tab dialogs
   4341    let template = document.getElementById("dialogStackTemplate");
   4342    let dialogStack = template.content.cloneNode(true).firstElementChild;
   4343    dialogStack.classList.add("tab-prompt-dialog");
   4344 
   4345    TabDialogBox._containerFor(browser).appendChild(dialogStack);
   4346 
   4347    // Initially the stack only contains the template
   4348    let dialogTemplate = dialogStack.firstElementChild;
   4349 
   4350    // Create dialog manager for prompts at the tab level.
   4351    this._tabDialogManager = new SubDialogManager({
   4352      dialogStack,
   4353      dialogTemplate,
   4354      orderType: SubDialogManager.ORDER_QUEUE,
   4355      allowDuplicateDialogs: true,
   4356      dialogOptions: {
   4357        consumeOutsideClicks: false,
   4358      },
   4359    });
   4360  }
   4361 
   4362  /**
   4363   * Open a dialog on tab or content level.
   4364   *
   4365   * @param {string} aURL - URL of the dialog to load in the tab box.
   4366   * @param {object} [aOptions]
   4367   * @param {string} [aOptions.features] - Comma separated list of window
   4368   * features.
   4369   * @param {boolean} [aOptions.allowDuplicateDialogs] - Whether to allow
   4370   * showing multiple dialogs with aURL at the same time. If false calls for
   4371   * duplicate dialogs will be dropped.
   4372   * @param {string} [aOptions.sizeTo] - Pass "available" to stretch dialog to
   4373   * roughly content size. Any max-width or max-height style values on the document root
   4374   * will also be applied to the dialog box.
   4375   * @param {boolean} [aOptions.keepOpenSameOriginNav] - By default dialogs are
   4376   * aborted on any navigation.
   4377   * Set to true to keep the dialog open for same origin navigation.
   4378   * @param {number} [aOptions.modalType] - The modal type to create the dialog for.
   4379   * By default, we show the dialog for tab prompts.
   4380   * @param {boolean} [aOptions.hideContent] - When true, we are about to show a prompt that is requesting the
   4381   * users credentials for a toplevel load of a resource from a base domain different from the base domain of the currently loaded page.
   4382   * To avoid auth prompt spoofing (see bug 791594) we hide the current sites content
   4383   * (among other protection mechanisms, that are not handled here, see the bug for reference).
   4384   * @param {nsIWebProgress} [aOptions.webProgress] - If passed, use to detect when a site is being
   4385   * navigated to in order to close the dialog. By default, this.browser.webProgress is used.
   4386   * @returns {object} [result] Returns an object { closedPromise, dialog }.
   4387   * @returns {Promise} [result.closedPromise] Resolves once the dialog has been closed.
   4388   * @returns {SubDialog} [result.dialog] A reference to the opened SubDialog.
   4389   */
   4390  open(
   4391    aURL,
   4392    {
   4393      features = null,
   4394      allowDuplicateDialogs = true,
   4395      sizeTo,
   4396      keepOpenSameOriginNav,
   4397      modalType = null,
   4398      allowFocusCheckbox = false,
   4399      hideContent = false,
   4400      webProgress = undefined,
   4401    } = {},
   4402    ...aParams
   4403  ) {
   4404    let resolveClosed;
   4405    let closedPromise = new Promise(resolve => (resolveClosed = resolve));
   4406    // Get the dialog manager to open the prompt with.
   4407    let dialogManager =
   4408      modalType === Ci.nsIPrompt.MODAL_TYPE_CONTENT
   4409        ? this.getContentDialogManager()
   4410        : this._tabDialogManager;
   4411 
   4412    let hasDialogs = () =>
   4413      this._tabDialogManager.hasDialogs ||
   4414      this._contentDialogManager?.hasDialogs;
   4415 
   4416    if (!hasDialogs()) {
   4417      this._onFirstDialogOpen(webProgress ?? this.browser.webProgress);
   4418    }
   4419 
   4420    let closingCallback = event => {
   4421      if (!hasDialogs()) {
   4422        this._onLastDialogClose(webProgress ?? this.browser.webProgress);
   4423      }
   4424 
   4425      if (allowFocusCheckbox && !event.detail?.abort) {
   4426        this.maybeSetAllowTabSwitchPermission(event.target);
   4427      }
   4428    };
   4429 
   4430    if (modalType == Ci.nsIPrompt.MODAL_TYPE_CONTENT) {
   4431      sizeTo = "limitheight";
   4432    }
   4433 
   4434    // Open dialog and resolve once it has been closed
   4435    let dialog = dialogManager.open(
   4436      aURL,
   4437      {
   4438        features,
   4439        allowDuplicateDialogs,
   4440        sizeTo,
   4441        closingCallback,
   4442        closedCallback: resolveClosed,
   4443        hideContent,
   4444      },
   4445      ...aParams
   4446    );
   4447 
   4448    // Marking the dialog externally, instead of passing it as an option.
   4449    // The SubDialog(Manager) does not care about navigation.
   4450    // dialog can be null here if allowDuplicateDialogs = false.
   4451    if (dialog) {
   4452      dialog._keepOpenSameOriginNav = keepOpenSameOriginNav;
   4453    }
   4454    return { closedPromise, dialog };
   4455  }
   4456 
   4457  _onFirstDialogOpen(webProgress) {
   4458    // Hide PopupNotifications to prevent them from covering up dialogs.
   4459    this.browser.setAttribute("tabDialogShowing", true);
   4460    UpdatePopupNotificationsVisibility();
   4461 
   4462    // Register listeners
   4463    this._lastPrincipal = this.browser.contentPrincipal;
   4464    webProgress.addProgressListener(this, Ci.nsIWebProgress.NOTIFY_LOCATION);
   4465 
   4466    this.tab?.addEventListener("TabClose", this);
   4467  }
   4468 
   4469  _onLastDialogClose(webProgress) {
   4470    // Show PopupNotifications again.
   4471    this.browser.removeAttribute("tabDialogShowing");
   4472    UpdatePopupNotificationsVisibility();
   4473 
   4474    // Clean up listeners
   4475    webProgress.removeProgressListener(this);
   4476    this._lastPrincipal = null;
   4477 
   4478    this.tab?.removeEventListener("TabClose", this);
   4479  }
   4480 
   4481  _buildContentPromptDialog() {
   4482    let template = document.getElementById("dialogStackTemplate");
   4483    let contentDialogStack = template.content.cloneNode(true).firstElementChild;
   4484    contentDialogStack.classList.add("content-prompt-dialog");
   4485 
   4486    // Create a dialog manager for content prompts.
   4487    let browserContainer = TabDialogBox._containerFor(this.browser);
   4488    let tabPromptDialog = browserContainer.querySelector(".tab-prompt-dialog");
   4489    browserContainer.insertBefore(contentDialogStack, tabPromptDialog);
   4490 
   4491    let contentDialogTemplate = contentDialogStack.firstElementChild;
   4492    this._contentDialogManager = new SubDialogManager({
   4493      dialogStack: contentDialogStack,
   4494      dialogTemplate: contentDialogTemplate,
   4495      orderType: SubDialogManager.ORDER_QUEUE,
   4496      allowDuplicateDialogs: true,
   4497      dialogOptions: {
   4498        consumeOutsideClicks: false,
   4499      },
   4500    });
   4501  }
   4502 
   4503  handleEvent(event) {
   4504    if (event.type !== "TabClose") {
   4505      return;
   4506    }
   4507    this.abortAllDialogs();
   4508  }
   4509 
   4510  abortAllDialogs() {
   4511    this._tabDialogManager.abortDialogs();
   4512    this._contentDialogManager?.abortDialogs();
   4513  }
   4514 
   4515  focus() {
   4516    // Prioritize focusing the dialog manager for tab prompts
   4517    if (this._tabDialogManager._dialogs.length) {
   4518      this._tabDialogManager.focusTopDialog();
   4519      return;
   4520    }
   4521    this._contentDialogManager?.focusTopDialog();
   4522  }
   4523 
   4524  /**
   4525   * If the user navigates away or refreshes the page, close all dialogs for
   4526   * the current browser.
   4527   */
   4528  onLocationChange(aWebProgress, aRequest, aLocation, aFlags) {
   4529    if (
   4530      !aWebProgress.isTopLevel ||
   4531      aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT
   4532    ) {
   4533      return;
   4534    }
   4535 
   4536    // Dialogs can be exempt from closing on same origin location change.
   4537    let filterFn;
   4538 
   4539    // Test for same origin location change
   4540    if (
   4541      this._lastPrincipal?.isSameOrigin(
   4542        aLocation,
   4543        this.browser.browsingContext.usePrivateBrowsing
   4544      )
   4545    ) {
   4546      filterFn = dialog => !dialog._keepOpenSameOriginNav;
   4547    }
   4548 
   4549    this._lastPrincipal = this.browser.contentPrincipal;
   4550 
   4551    this._tabDialogManager.abortDialogs(filterFn);
   4552    this._contentDialogManager?.abortDialogs(filterFn);
   4553  }
   4554 
   4555  get tab() {
   4556    return gBrowser.getTabForBrowser(this.browser);
   4557  }
   4558 
   4559  get browser() {
   4560    let browser = this._weakBrowserRef.get();
   4561    if (!browser) {
   4562      throw new Error("Stale dialog box! The associated browser is gone.");
   4563    }
   4564    return browser;
   4565  }
   4566 
   4567  getTabDialogManager() {
   4568    return this._tabDialogManager;
   4569  }
   4570 
   4571  getContentDialogManager() {
   4572    if (!this._contentDialogManager) {
   4573      this._buildContentPromptDialog();
   4574    }
   4575    return this._contentDialogManager;
   4576  }
   4577 
   4578  onNextPromptShowAllowFocusCheckboxFor(principal) {
   4579    this._allowTabFocusByPromptPrincipal = principal;
   4580  }
   4581 
   4582  /**
   4583   * Sets the "focus-tab-by-prompt" permission for the dialog.
   4584   */
   4585  maybeSetAllowTabSwitchPermission(dialog) {
   4586    let checkbox = dialog.querySelector("checkbox");
   4587 
   4588    if (checkbox.checked) {
   4589      Services.perms.addFromPrincipal(
   4590        this._allowTabFocusByPromptPrincipal,
   4591        "focus-tab-by-prompt",
   4592        Services.perms.ALLOW_ACTION
   4593      );
   4594    }
   4595 
   4596    // Don't show the "allow tab switch checkbox" for subsequent prompts.
   4597    this._allowTabFocusByPromptPrincipal = null;
   4598  }
   4599 }
   4600 
   4601 TabDialogBox.prototype.QueryInterface = ChromeUtils.generateQI([
   4602  "nsIWebProgressListener",
   4603  "nsISupportsWeakReference",
   4604 ]);
   4605 
   4606 // Handle window-modal prompts that we want to display with the same style as
   4607 // tab-modal prompts.
   4608 var gDialogBox = {
   4609  _dialog: null,
   4610  _nextOpenJumpsQueue: false,
   4611  _queued: [],
   4612 
   4613  // Used to wait for a `close` event from the HTML
   4614  // dialog. The  event is fired asynchronously, which means
   4615  // that if we open another dialog immediately after the
   4616  // previous one, we might be confused into thinking a
   4617  // `close` event for the old dialog is for the new one.
   4618  // As they have the same event target, we have no way of
   4619  // distinguishing them. So we wait for the `close` event
   4620  // to have happened before allowing another dialog to open.
   4621  _didCloseHTMLDialog: null,
   4622  // Whether we managed to open the dialog we tried to open.
   4623  // Used to avoid waiting for the above callback in case
   4624  // of an error opening the dialog.
   4625  _didOpenHTMLDialog: false,
   4626 
   4627  get dialog() {
   4628    return this._dialog;
   4629  },
   4630 
   4631  get isOpen() {
   4632    return !!this._dialog;
   4633  },
   4634 
   4635  replaceDialogIfOpen() {
   4636    this._dialog?.close();
   4637    this._nextOpenJumpsQueue = true;
   4638  },
   4639 
   4640  async open(uri, args) {
   4641    // If we need to queue, some callers indicate they should go first.
   4642    const queueMethod = this._nextOpenJumpsQueue ? "unshift" : "push";
   4643    this._nextOpenJumpsQueue = false;
   4644 
   4645    // If we already have a dialog opened and are trying to open another,
   4646    // queue the next one to be opened later.
   4647    if (this.isOpen) {
   4648      return new Promise((resolve, reject) => {
   4649        this._queued[queueMethod]({ resolve, reject, uri, args });
   4650      });
   4651    }
   4652 
   4653    // We're not open. If we're in a modal state though, we can't
   4654    // show the dialog effectively. To avoid hanging by deadlock,
   4655    // just return immediately for sync prompts:
   4656    if (window.windowUtils.isInModalState() && !args.getProperty("async")) {
   4657      throw Components.Exception(
   4658        "Prompt could not be shown.",
   4659        Cr.NS_ERROR_NOT_AVAILABLE
   4660      );
   4661    }
   4662 
   4663    // Indicate if we should wait for the dialog to close.
   4664    this._didOpenHTMLDialog = false;
   4665    let haveClosedPromise = new Promise(resolve => {
   4666      this._didCloseHTMLDialog = resolve;
   4667    });
   4668 
   4669    // Bring the window to the front in case we're minimized or occluded:
   4670    window.focus();
   4671 
   4672    try {
   4673      // Prevent URL bar from showing on top of modal
   4674      gURLBar.incrementBreakoutBlockerCount();
   4675    } catch (ex) {
   4676      console.error(ex);
   4677    }
   4678 
   4679    try {
   4680      await this._open(uri, args);
   4681    } catch (ex) {
   4682      console.error(ex);
   4683    } finally {
   4684      let dialog = document.getElementById("window-modal-dialog");
   4685      if (dialog.open) {
   4686        dialog.close();
   4687      }
   4688      // If the dialog was opened successfully, then we can wait for it
   4689      // to close before trying to open any others.
   4690      if (this._didOpenHTMLDialog) {
   4691        await haveClosedPromise;
   4692      }
   4693      dialog.style.visibility = "hidden";
   4694      dialog.style.height = "0";
   4695      dialog.style.width = "0";
   4696      document.documentElement.removeAttribute("window-modal-open");
   4697      dialog.removeEventListener("dialogopen", this);
   4698      dialog.removeEventListener("close", this);
   4699      this._updateMenuAndCommandState(true /* to enable */);
   4700      this._dialog = null;
   4701      UpdatePopupNotificationsVisibility();
   4702      // Restores URL bar breakout if needed
   4703      gURLBar.decrementBreakoutBlockerCount();
   4704    }
   4705    if (this._queued.length) {
   4706      setTimeout(() => this._openNextDialog(), 0);
   4707    }
   4708    return args;
   4709  },
   4710 
   4711  _openNextDialog() {
   4712    if (!this.isOpen) {
   4713      let { resolve, reject, uri, args } = this._queued.shift();
   4714      this.open(uri, args).then(resolve, reject);
   4715    }
   4716  },
   4717 
   4718  handleEvent(event) {
   4719    switch (event.type) {
   4720      case "dialogopen":
   4721        this._dialog.focus(true);
   4722        break;
   4723      case "close":
   4724        this._didCloseHTMLDialog();
   4725        this._dialog.close();
   4726        break;
   4727    }
   4728  },
   4729 
   4730  _open(uri, args) {
   4731    // Get this offset before we touch style below, as touching style seems
   4732    // to reset the cached layout bounds.
   4733    let offset = window.windowUtils.getBoundsWithoutFlushing(
   4734      gBrowser.selectedBrowser
   4735    ).top;
   4736    let parentElement = document.getElementById("window-modal-dialog");
   4737    parentElement.style.setProperty("--chrome-offset", offset + "px");
   4738    parentElement.style.removeProperty("visibility");
   4739    parentElement.style.removeProperty("width");
   4740    parentElement.style.removeProperty("height");
   4741    document.documentElement.setAttribute("window-modal-open", true);
   4742    // Call this first so the contents show up and get layout, which is
   4743    // required for SubDialog to work.
   4744    parentElement.showModal();
   4745    this._didOpenHTMLDialog = true;
   4746 
   4747    // Disable menus and shortcuts.
   4748    this._updateMenuAndCommandState(false /* to disable */);
   4749 
   4750    // Now actually set up the dialog contents:
   4751    let template = document.getElementById("window-modal-dialog-template")
   4752      .content.firstElementChild;
   4753    parentElement.addEventListener("dialogopen", this);
   4754    parentElement.addEventListener("close", this);
   4755    this._dialog = new SubDialog({
   4756      template,
   4757      parentElement,
   4758      id: "window-modal-dialog-subdialog",
   4759      options: {
   4760        consumeOutsideClicks: false,
   4761      },
   4762    });
   4763    let closedPromise = new Promise(resolve => {
   4764      this._closedCallback = function () {
   4765        PromptUtils.fireDialogEvent(window, "DOMModalDialogClosed");
   4766        resolve();
   4767      };
   4768    });
   4769    this._dialog.open(
   4770      uri,
   4771      {
   4772        features: "resizable=no",
   4773        modalType: Ci.nsIPrompt.MODAL_TYPE_INTERNAL_WINDOW,
   4774        closedCallback: () => {
   4775          this._closedCallback();
   4776        },
   4777      },
   4778      args
   4779    );
   4780    UpdatePopupNotificationsVisibility();
   4781    return closedPromise;
   4782  },
   4783 
   4784  _nonUpdatableElements: new Set([
   4785    // Make an exception for debugging tools, for developer ease of use.
   4786    "key_browserConsole",
   4787    "key_browserToolbox",
   4788 
   4789    // Don't touch the editing keys/commands which we might want inside the dialog.
   4790    "key_undo",
   4791    "key_redo",
   4792 
   4793    "key_cut",
   4794    "key_copy",
   4795    "key_paste",
   4796    "key_delete",
   4797    "key_selectAll",
   4798  ]),
   4799 
   4800  _updateMenuAndCommandState(shouldBeEnabled) {
   4801    let editorCommands = document.getElementById("editMenuCommands");
   4802    // For the following items, set or clear disabled state:
   4803    // - toplevel menubar items (will affect inner items on macOS)
   4804    // - command elements
   4805    // - key elements not connected to command elements.
   4806    for (let element of document.querySelectorAll(
   4807      "menubar > menu, command, key:not([command])"
   4808    )) {
   4809      if (
   4810        editorCommands?.contains(element) ||
   4811        (element.id && this._nonUpdatableElements.has(element.id))
   4812      ) {
   4813        continue;
   4814      }
   4815      if (element.nodeName == "key" && element.command) {
   4816        continue;
   4817      }
   4818      if (!shouldBeEnabled) {
   4819        if (!element.hasAttribute("disabled")) {
   4820          element.setAttribute("disabled", true);
   4821        } else {
   4822          element.setAttribute("wasdisabled", true);
   4823        }
   4824      } else if (element.getAttribute("wasdisabled") != "true") {
   4825        element.removeAttribute("disabled");
   4826      } else {
   4827        element.removeAttribute("wasdisabled");
   4828      }
   4829    }
   4830  },
   4831 };
   4832 
   4833 // browser.js loads in the library window, too, but we can only show prompts
   4834 // in the main browser window:
   4835 if (window.location.href != AppConstants.BROWSER_CHROME_URL) {
   4836  gDialogBox = null;
   4837 }
   4838 
   4839 var ConfirmationHint = {
   4840  _timerID: null,
   4841 
   4842  /**
   4843   * Shows a transient, non-interactive confirmation hint anchored to an
   4844   * element, usually used in response to a user action to reaffirm that it was
   4845   * successful and potentially provide extra context. Examples for such hints:
   4846   * - "Saved to bookmarks" after bookmarking a page
   4847   * - "Sent!" after sending a tab to another device
   4848   * - "Queued (offline)" when attempting to send a tab to another device
   4849   *   while offline
   4850   *
   4851   * @param  anchor (DOM node, required)
   4852   *         The anchor for the panel.
   4853   * @param  messageId (string, required)
   4854   *         For getting the message string from confirmationHints.ftl
   4855   * @param  options (object, optional)
   4856   *         An object with the following optional properties:
   4857   *         - event (DOM event): The event that triggered the feedback
   4858   *         - descriptionId (string): message ID of the description text
   4859   *         - position (string): position of the panel relative to the anchor.
   4860   *         - l10nArgs (object): l10n arguments for the messageId.
   4861   */
   4862  show(anchor, messageId, options = {}) {
   4863    this._reset();
   4864 
   4865    MozXULElement.insertFTLIfNeeded("toolkit/branding/brandings.ftl");
   4866    MozXULElement.insertFTLIfNeeded("browser/confirmationHints.ftl");
   4867    document.l10n.setAttributes(this._message, messageId, options.l10nArgs);
   4868    if (options.descriptionId) {
   4869      document.l10n.setAttributes(this._description, options.descriptionId);
   4870      this._description.hidden = false;
   4871      this._panel.classList.add("with-description");
   4872    } else {
   4873      this._description.hidden = true;
   4874      this._panel.classList.remove("with-description");
   4875    }
   4876 
   4877    this._panel.setAttribute("data-message-id", messageId);
   4878 
   4879    // The timeout value used here allows the panel to stay open for
   4880    // 3s after the text transition (duration=120ms) has finished.
   4881    // If there is a description, we show for 6s after the text transition.
   4882    const DURATION = options.showDescription ? 6000 : 3000;
   4883    this._panel.addEventListener(
   4884      "popupshown",
   4885      () => {
   4886        this._animationBox.setAttribute("animate", "true");
   4887        this._timerID = setTimeout(() => {
   4888          this._panel.hidePopup(true);
   4889        }, DURATION + 120);
   4890      },
   4891      { once: true }
   4892    );
   4893 
   4894    this._panel.addEventListener(
   4895      "popuphidden",
   4896      () => {
   4897        // reset the timerId in case our timeout wasn't the cause of the popup being hidden
   4898        this._reset();
   4899      },
   4900      { once: true }
   4901    );
   4902 
   4903    this._panel.openPopup(anchor, {
   4904      position: options.position ?? "bottomleft topleft",
   4905      triggerEvent: options.event,
   4906    });
   4907  },
   4908 
   4909  _reset() {
   4910    if (this._timerID) {
   4911      clearTimeout(this._timerID);
   4912      this._timerID = null;
   4913    }
   4914    if (this.__panel) {
   4915      this._animationBox.removeAttribute("animate");
   4916      this._panel.removeAttribute("data-message-id");
   4917    }
   4918  },
   4919 
   4920  get _panel() {
   4921    this._ensurePanel();
   4922    return this.__panel;
   4923  },
   4924 
   4925  get _animationBox() {
   4926    this._ensurePanel();
   4927    delete this._animationBox;
   4928    return (this._animationBox = document.getElementById(
   4929      "confirmation-hint-checkmark-animation-container"
   4930    ));
   4931  },
   4932 
   4933  get _message() {
   4934    this._ensurePanel();
   4935    delete this._message;
   4936    return (this._message = document.getElementById(
   4937      "confirmation-hint-message"
   4938    ));
   4939  },
   4940 
   4941  get _description() {
   4942    this._ensurePanel();
   4943    delete this._description;
   4944    return (this._description = document.getElementById(
   4945      "confirmation-hint-description"
   4946    ));
   4947  },
   4948 
   4949  _ensurePanel() {
   4950    if (!this.__panel) {
   4951      let wrapper = document.getElementById("confirmation-hint-wrapper");
   4952      wrapper.replaceWith(wrapper.content);
   4953      this.__panel = document.getElementById("confirmation-hint");
   4954    }
   4955  },
   4956 };
   4957 
   4958 var FirefoxViewHandler = {
   4959  tab: null,
   4960  BUTTON_ID: "firefox-view-button",
   4961  get button() {
   4962    return document.getElementById(this.BUTTON_ID);
   4963  },
   4964  init() {
   4965    CustomizableUI.addListener(this);
   4966 
   4967    ChromeUtils.defineESModuleGetters(this, {
   4968      SyncedTabs: "resource://services-sync/SyncedTabs.sys.mjs",
   4969    });
   4970  },
   4971  uninit() {
   4972    CustomizableUI.removeListener(this);
   4973  },
   4974  onWidgetRemoved(aWidgetId) {
   4975    if (aWidgetId == this.BUTTON_ID && this.tab) {
   4976      gBrowser.removeTab(this.tab);
   4977    }
   4978  },
   4979  onWidgetAdded(aWidgetId) {
   4980    if (aWidgetId === this.BUTTON_ID) {
   4981      this.button.removeAttribute("open");
   4982    }
   4983  },
   4984  openTab(section) {
   4985    if (AppConstants.BASE_BROWSER_VERSION) {
   4986      // about:firefoxview is disabled. tor-browser#42037.
   4987      return;
   4988    }
   4989 
   4990    if (!CustomizableUI.getPlacementOfWidget(this.BUTTON_ID)) {
   4991      CustomizableUI.addWidgetToArea(
   4992        this.BUTTON_ID,
   4993        CustomizableUI.AREA_TABSTRIP,
   4994        CustomizableUI.getPlacementOfWidget("tabbrowser-tabs").position
   4995      );
   4996    }
   4997    let viewURL = "about:firefoxview";
   4998    if (section) {
   4999      viewURL = `${viewURL}#${section}`;
   5000    }
   5001    // Need to account for navigation to Firefox View pages
   5002    if (
   5003      this.tab &&
   5004      this.tab.linkedBrowser.currentURI.spec.split("#")[0] != viewURL
   5005    ) {
   5006      gBrowser.removeTab(this.tab);
   5007      this.tab = null;
   5008    }
   5009    if (!this.tab) {
   5010      this.tab = gBrowser.addTrustedTab(viewURL);
   5011      this.tab.addEventListener("TabClose", this, { once: true });
   5012      gBrowser.tabContainer.addEventListener("TabSelect", this);
   5013      window.addEventListener("activate", this);
   5014      gBrowser.hideTab(this.tab);
   5015      this.button.setAttribute("aria-controls", this.tab.linkedPanel);
   5016    }
   5017    // we put this here to avoid a race condition that would occur
   5018    // if this was called in response to "TabSelect"
   5019    this._closeDeviceConnectedTab();
   5020    gBrowser.selectedTab = this.tab;
   5021  },
   5022  openToolbarMouseEvent(event, section) {
   5023    if (event?.type == "mousedown" && event?.button != 0) {
   5024      return;
   5025    }
   5026    this.openTab(section);
   5027  },
   5028  handleEvent(e) {
   5029    switch (e.type) {
   5030      case "TabSelect": {
   5031        const selected = e.target == this.tab;
   5032        this.button?.toggleAttribute("open", selected);
   5033        this.button?.setAttribute("aria-pressed", selected);
   5034        this._recordViewIfTabSelected();
   5035        this._onTabForegrounded();
   5036        // If Fx View is opened, add temporary style to make first available tab focusable
   5037        // When Fx View is closed, remove temporary -moz-user-focus style from first available tab
   5038        gBrowser.visibleTabs[0].style.MozUserFocus =
   5039          e.target == this.tab ? "normal" : "";
   5040        break;
   5041      }
   5042      case "TabClose":
   5043        this.tab = null;
   5044        gBrowser.tabContainer.removeEventListener("TabSelect", this);
   5045        this.button?.removeAttribute("aria-controls");
   5046        break;
   5047      case "activate":
   5048        this._onTabForegrounded();
   5049        break;
   5050    }
   5051  },
   5052  _closeDeviceConnectedTab() {
   5053    if (!TabsSetupFlowManager.didFxaTabOpen) {
   5054      return;
   5055    }
   5056    // close the tab left behind after a user pairs a device and
   5057    // is redirected back to the Firefox View tab
   5058    const fxaRoot = Services.prefs.getCharPref(
   5059      "identity.fxaccounts.remote.root"
   5060    );
   5061    const fxDeviceConnectedTab = gBrowser.tabs.find(tab =>
   5062      tab.linkedBrowser.currentURI.displaySpec.startsWith(
   5063        `${fxaRoot}pair/auth/complete`
   5064      )
   5065    );
   5066 
   5067    if (!fxDeviceConnectedTab) {
   5068      return;
   5069    }
   5070 
   5071    if (gBrowser.tabs.length <= 2) {
   5072      // if its the only tab besides the Firefox View tab,
   5073      // open a new tab first so the browser doesn't close
   5074      gBrowser.addTrustedTab("about:newtab");
   5075    }
   5076    gBrowser.removeTab(fxDeviceConnectedTab);
   5077    TabsSetupFlowManager.didFxaTabOpen = false;
   5078  },
   5079  _onTabForegrounded() {
   5080    if (this.tab?.selected) {
   5081      this.SyncedTabs.syncTabs();
   5082    }
   5083  },
   5084  _recordViewIfTabSelected() {
   5085    if (this.tab?.selected) {
   5086      const PREF_NAME = "browser.firefox-view.view-count";
   5087      const MAX_VIEW_COUNT = 10;
   5088      let viewCount = Services.prefs.getIntPref(PREF_NAME, 0);
   5089 
   5090      // Record telemetry
   5091      Glean.firefoxviewNext.tabSelectedToolbarbutton.record();
   5092 
   5093      if (viewCount < MAX_VIEW_COUNT) {
   5094        Services.prefs.setIntPref(PREF_NAME, viewCount + 1);
   5095      }
   5096    }
   5097  },
   5098 };