tor-browser

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

browser-init.js (42089B)


      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 const { CustomKeys } = ChromeUtils.importESModule(
      7  "resource:///modules/CustomKeys.sys.mjs"
      8 );
      9 
     10 let _resolveDelayedStartup;
     11 var delayedStartupPromise = new Promise(resolve => {
     12  _resolveDelayedStartup = resolve;
     13 });
     14 
     15 var gBrowserInit = {
     16  delayedStartupFinished: false,
     17  domContentLoaded: false,
     18 
     19  _tabToAdopt: undefined,
     20  _firstContentWindowPaintDeferred: Promise.withResolvers(),
     21  idleTasksFinished: Promise.withResolvers(),
     22 
     23  _setupFirstContentWindowPaintPromise() {
     24    let lastTransactionId = window.windowUtils.lastTransactionId;
     25    let layerTreeListener = () => {
     26      if (this.getTabToAdopt()) {
     27        // Need to wait until we finish adopting the tab, or we might end
     28        // up focusing the initial browser and then losing focus when it
     29        // gets swapped out for the tab to adopt.
     30        return;
     31      }
     32      removeEventListener("MozLayerTreeReady", layerTreeListener);
     33      let listener = e => {
     34        if (e.transactionId > lastTransactionId) {
     35          window.removeEventListener("MozAfterPaint", listener);
     36          this._firstContentWindowPaintDeferred.resolve();
     37        }
     38      };
     39      addEventListener("MozAfterPaint", listener);
     40    };
     41    addEventListener("MozLayerTreeReady", layerTreeListener);
     42  },
     43 
     44  getTabToAdopt() {
     45    if (this._tabToAdopt !== undefined) {
     46      return this._tabToAdopt;
     47    }
     48 
     49    if (window.arguments && window.XULElement.isInstance(window.arguments[0])) {
     50      this._tabToAdopt = window.arguments[0];
     51 
     52      // Clear the reference of the tab being adopted from the arguments.
     53      window.arguments[0] = null;
     54    } else {
     55      // There was no tab to adopt in the arguments, set _tabToAdopt to null
     56      // to avoid checking it again.
     57      this._tabToAdopt = null;
     58    }
     59 
     60    return this._tabToAdopt;
     61  },
     62 
     63  _clearTabToAdopt() {
     64    this._tabToAdopt = null;
     65  },
     66 
     67  // Used to check if the new window is still adopting an existing tab as its first tab
     68  // (e.g. from the WebExtensions internals).
     69  isAdoptingTab() {
     70    return !!this.getTabToAdopt();
     71  },
     72 
     73  onBeforeInitialXULLayout() {
     74    this._setupFirstContentWindowPaintPromise();
     75 
     76    updateBookmarkToolbarVisibility();
     77 
     78    // Set a sane starting width/height for all resolutions on new profiles.
     79    if (ChromeUtils.shouldResistFingerprinting("RoundWindowSize", null)) {
     80      // When the fingerprinting resistance is enabled, making sure that we don't
     81      // have a maximum window to interfere with generating rounded window dimensions.
     82      document.documentElement.setAttribute("sizemode", "normal");
     83    } else if (!document.documentElement.hasAttribute("width")) {
     84      const TARGET_WIDTH = 1280;
     85      const TARGET_HEIGHT = 1040;
     86      let width = Math.min(screen.availWidth * 0.9, TARGET_WIDTH);
     87      let height = Math.min(screen.availHeight * 0.9, TARGET_HEIGHT);
     88 
     89      document.documentElement.setAttribute("width", width);
     90      document.documentElement.setAttribute("height", height);
     91 
     92      if (width < TARGET_WIDTH && height < TARGET_HEIGHT) {
     93        document.documentElement.setAttribute("sizemode", "maximized");
     94      }
     95    }
     96    {
     97      const toolbarMenubar = document.getElementById("toolbar-menubar");
     98      const nativeMenubar = Services.appinfo.nativeMenubar;
     99      toolbarMenubar.collapsed = nativeMenubar;
    100      if (nativeMenubar) {
    101        toolbarMenubar.removeAttribute("autohide");
    102      } else {
    103        document.l10n.setAttributes(
    104          toolbarMenubar,
    105          "toolbar-context-menu-menu-bar-cmd"
    106        );
    107        toolbarMenubar.setAttribute("data-l10n-attrs", "toolbarname");
    108      }
    109    }
    110    // If opening a Taskbar Tab or AI window, add an attribute to the top-level element
    111    // to inform window styling.
    112    if (window.arguments?.[1] instanceof Ci.nsIPropertyBag2) {
    113      let extraOptions = window.arguments[1];
    114      if (extraOptions.hasKey("taskbartab")) {
    115        window.document.documentElement.setAttribute(
    116          "taskbartab",
    117          extraOptions.getPropertyAsAString("taskbartab")
    118        );
    119      }
    120 
    121      if (extraOptions.hasKey("ai-window")) {
    122        document.documentElement.setAttribute("ai-window", true);
    123      }
    124    }
    125 
    126    // Run menubar initialization first, to avoid CustomTitlebar code picking
    127    // up mutations from it and causing a reflow.
    128    AutoHideMenubar.init();
    129    // Update the customtitlebar attribute so the window can be sized
    130    // correctly.
    131    window.TabBarVisibility.update();
    132    CustomTitlebar.init();
    133 
    134    new LightweightThemeConsumer(document);
    135 
    136    if (
    137      Services.prefs.getBoolPref(
    138        "toolkit.legacyUserProfileCustomizations.windowIcon",
    139        false
    140      )
    141    ) {
    142      document.documentElement.setAttribute("icon", "main-window");
    143    }
    144 
    145    // Call this after we set attributes that might change toolbars' computed
    146    // text color.
    147    ToolbarIconColor.init(window);
    148  },
    149 
    150  onDOMContentLoaded() {
    151    // All of this needs setting up before we create the first remote browser.
    152    window.docShell.treeOwner
    153      .QueryInterface(Ci.nsIInterfaceRequestor)
    154      .getInterface(Ci.nsIAppWindow).XULBrowserWindow = window.XULBrowserWindow;
    155    BrowserUtils.callModulesFromCategory(
    156      { categoryName: "browser-window-domcontentloaded-before-tabbrowser" },
    157      window
    158    );
    159 
    160    gBrowser = new window.Tabbrowser();
    161    gBrowser.init();
    162    gURLBar.addGBrowserListeners();
    163    if (Services.prefs.getBoolPref("browser.search.widget.new", false)) {
    164      document.getElementById("searchbar-new")?.addGBrowserListeners();
    165    }
    166 
    167    BrowserUtils.callModulesFromCategory(
    168      { categoryName: "browser-window-domcontentloaded" },
    169      window
    170    );
    171 
    172    FirefoxViewHandler.init();
    173 
    174    gURLBar.initPlaceHolder();
    175 
    176    // Hack to ensure that the various initial pages favicon is loaded
    177    // instantaneously, to avoid flickering and improve perceived performance.
    178    this._callWithURIToLoad(uriToLoad => {
    179      let url = URL.parse(uriToLoad);
    180      if (!url) {
    181        return;
    182      }
    183      let nonQuery = url.URI.prePath + url.pathname;
    184      if (nonQuery in gPageIcons) {
    185        gBrowser.setIcon(gBrowser.selectedTab, gPageIcons[nonQuery]);
    186      }
    187    });
    188 
    189    updateFxaToolbarMenu(gFxaToolbarEnabled, true);
    190 
    191    updatePrintCommands(gPrintEnabled);
    192 
    193    gUnifiedExtensions.init();
    194 
    195    // Setting the focus will cause a style flush, it's preferable to call anything
    196    // that will modify the DOM from within this function before this call.
    197    this._setInitialFocus();
    198 
    199    this.domContentLoaded = true;
    200  },
    201 
    202  onLoad() {
    203    gBrowser.addEventListener("DOMUpdateBlockedPopups", e =>
    204      PopupAndRedirectBlockerObserver.handleEvent(e)
    205    );
    206    gBrowser.addEventListener("DOMUpdateBlockedRedirect", e =>
    207      PopupAndRedirectBlockerObserver.handleEvent(e)
    208    );
    209    gBrowser.addEventListener(
    210      "TranslationsParent:LanguageState",
    211      FullPageTranslationsPanel
    212    );
    213    gBrowser.addEventListener(
    214      "TranslationsParent:OfferTranslation",
    215      FullPageTranslationsPanel
    216    );
    217    gBrowser.addTabsProgressListener(FullPageTranslationsPanel);
    218 
    219    window.addEventListener("AppCommand", HandleAppCommandEvent, true);
    220 
    221    // These routines add message listeners. They must run before
    222    // loading the frame script to ensure that we don't miss any
    223    // message sent between when the frame script is loaded and when
    224    // the listener is registered.
    225    CaptivePortalWatcher.init();
    226    ZoomUI.init(window);
    227 
    228    if (!gMultiProcessBrowser) {
    229      // There is a Content:Click message manually sent from content.
    230      gBrowser.tabpanels.addEventListener("click", contentAreaClick, {
    231        capture: true,
    232        mozSystemGroup: true,
    233      });
    234    }
    235 
    236    // hook up UI through progress listener
    237    gBrowser.addProgressListener(window.XULBrowserWindow);
    238    gBrowser.addTabsProgressListener(window.TabsProgressListener);
    239 
    240    SidebarController.init();
    241 
    242    // We do this in onload because we want to ensure the button's state
    243    // doesn't flicker as the window is being shown.
    244    DownloadsButton.init();
    245 
    246    // Init the SecurityLevelButton
    247    SecurityLevelButton.init();
    248 
    249    gTorConnectUrlbarButton.init();
    250    gTorConnectTitlebarStatus.init();
    251 
    252    // Init the OnionAuthPrompt
    253    OnionAuthPrompt.init();
    254 
    255    // Init the Onion Location pill
    256    OnionLocationParent.init(document);
    257 
    258    gTorCircuitPanel.init();
    259 
    260    // Certain kinds of automigration rely on this notification to complete
    261    // their tasks BEFORE the browser window is shown. SessionStore uses it to
    262    // restore tabs into windows AFTER important parts like gMultiProcessBrowser
    263    // have been initialized.
    264    Services.obs.notifyObservers(window, "browser-window-before-show");
    265 
    266    if (
    267      !window.toolbar.visible ||
    268      window.document.documentElement.hasAttribute("taskbartab")
    269    ) {
    270      // adjust browser UI for popups
    271      gURLBar.readOnly = true;
    272    }
    273 
    274    // Misc. inits.
    275    gUIDensity.init();
    276    Win10TabletModeUpdater.init();
    277    CombinedStopReload.ensureInitialized();
    278    // Initialize private browsing UI only if window is private
    279    if (PrivateBrowsingUtils.isWindowPrivate(window)) {
    280      PrivateBrowsingUI.init(window);
    281    }
    282    TaskbarTabsChrome.init(window);
    283    BrowserPageActions.init();
    284    if (gToolbarKeyNavEnabled) {
    285      ToolbarKeyboardNavigator.init();
    286    }
    287    CustomKeys.initWindow(window);
    288 
    289    // Update UI if browser is under remote control.
    290    gRemoteControl.updateVisualCue();
    291 
    292    // If we are given a tab to swap in, take care of it before first paint to
    293    // avoid an about:blank flash.
    294    let tabToAdopt = this.getTabToAdopt();
    295    if (tabToAdopt) {
    296      let evt = new CustomEvent("before-initial-tab-adopted", {
    297        bubbles: true,
    298      });
    299      gBrowser.tabpanels.dispatchEvent(evt);
    300 
    301      // Stop the about:blank load
    302      gBrowser.stop();
    303 
    304      // Remove the speculative focus from the urlbar to let the url be formatted.
    305      gURLBar.removeAttribute("focused");
    306 
    307      let swapBrowsers = () => {
    308        if (gBrowser.isTabGroupLabel(tabToAdopt)) {
    309          // TODO bug 1967937: Merge this case with the tab group case below.
    310          gBrowser.adoptTabGroup(tabToAdopt.group, { elementIndex: 0 });
    311          gBrowser.removeTab(gBrowser.selectedTab);
    312        } else if (gBrowser.isTabGroup(tabToAdopt)) {
    313          // Via gBrowser.replaceGroupWithWindow
    314          let tempBlankTab = gBrowser.selectedTab;
    315          gBrowser.adoptTabGroup(tabToAdopt, { tabIndex: 0, selectTab: true });
    316          gBrowser.removeTab(tempBlankTab);
    317          Glean.tabgroup.groupInteractions.move_window.add(1);
    318        } else if (gBrowser.isSplitViewWrapper(tabToAdopt)) {
    319          let tempBlankTab = gBrowser.selectedTab;
    320          gBrowser.adoptSplitView(tabToAdopt, {
    321            elementIndex: 0,
    322            selectTab: true,
    323          });
    324          gBrowser.removeTab(tempBlankTab);
    325        } else {
    326          if (tabToAdopt.group) {
    327            Glean.tabgroup.tabInteractions.remove_new_window.add();
    328          }
    329          gBrowser.swapBrowsersAndCloseOther(gBrowser.selectedTab, tabToAdopt);
    330        }
    331 
    332        // Clear the reference to the tab once its adoption has been completed.
    333        this._clearTabToAdopt();
    334      };
    335      if (
    336        gBrowser.isTab(tabToAdopt) &&
    337        !tabToAdopt.linkedBrowser.isRemoteBrowser
    338      ) {
    339        swapBrowsers();
    340      } else {
    341        // For remote browsers, wait for the paint event, otherwise the tabs
    342        // are not yet ready and focus gets confused because the browser swaps
    343        // out while tabs are switching.
    344        addEventListener("MozAfterPaint", swapBrowsers, { once: true });
    345      }
    346    }
    347 
    348    // Wait until chrome is painted before executing code not critical to making the window visible
    349    this._boundDelayedStartup = this._delayedStartup.bind(this);
    350    window.addEventListener("MozAfterPaint", this._boundDelayedStartup);
    351 
    352    if (!PrivateBrowsingUtils.enabled) {
    353      document.getElementById("Tools:PrivateBrowsing").hidden = true;
    354      // Setting disabled doesn't disable the shortcut, so we just remove
    355      // the keybinding.
    356      document.getElementById("key_privatebrowsing").remove();
    357    }
    358 
    359    if (BrowserUIUtils.quitShortcutDisabled) {
    360      document.getElementById("key_quitApplication").remove();
    361      document.getElementById("menu_FileQuitItem").removeAttribute("key");
    362 
    363      PanelMultiView.getViewNode(
    364        document,
    365        "appMenu-quit-button2"
    366      )?.removeAttribute("key");
    367    }
    368 
    369    this._loadHandled = true;
    370  },
    371 
    372  _cancelDelayedStartup() {
    373    window.removeEventListener("MozAfterPaint", this._boundDelayedStartup);
    374    this._boundDelayedStartup = null;
    375  },
    376 
    377  _delayedStartup() {
    378    let { TelemetryTimestamps } = ChromeUtils.importESModule(
    379      "resource://gre/modules/TelemetryTimestamps.sys.mjs"
    380    );
    381    TelemetryTimestamps.add("delayedStartupStarted");
    382    Glean.browserTimings.startupTimeline.delayedStartupStarted.set(
    383      Services.telemetry.msSinceProcessStart()
    384    );
    385 
    386    this._cancelDelayedStartup();
    387 
    388    gBrowser.addEventListener(
    389      "PermissionStateChange",
    390      function () {
    391        gIdentityHandler.refreshIdentityBlock();
    392        gPermissionPanel.updateSharingIndicator();
    393      },
    394      true
    395    );
    396 
    397    this._handleURIToLoad();
    398 
    399    Services.obs.addObserver(gIdentityHandler, "perm-changed");
    400    Services.obs.addObserver(gRemoteControl, "devtools-socket");
    401    Services.obs.addObserver(gRemoteControl, "marionette-listening");
    402    Services.obs.addObserver(gRemoteControl, "remote-listening");
    403    Services.obs.addObserver(
    404      gSessionHistoryObserver,
    405      "browser:purge-session-history"
    406    );
    407    Services.obs.addObserver(
    408      gStoragePressureObserver,
    409      "QuotaManager::StoragePressure"
    410    );
    411    Services.obs.addObserver(gXPInstallObserver, "addon-install-disabled");
    412    Services.obs.addObserver(gXPInstallObserver, "addon-install-started");
    413    Services.obs.addObserver(gXPInstallObserver, "addon-install-blocked");
    414    Services.obs.addObserver(
    415      gXPInstallObserver,
    416      "addon-install-fullscreen-blocked"
    417    );
    418    Services.obs.addObserver(
    419      gXPInstallObserver,
    420      "addon-install-origin-blocked"
    421    );
    422    Services.obs.addObserver(
    423      gXPInstallObserver,
    424      "addon-install-policy-blocked"
    425    );
    426    Services.obs.addObserver(
    427      gXPInstallObserver,
    428      "addon-install-webapi-blocked"
    429    );
    430    Services.obs.addObserver(gXPInstallObserver, "addon-install-failed");
    431    Services.obs.addObserver(gXPInstallObserver, "addon-install-confirmation");
    432    Services.obs.addObserver(gKeywordURIFixup, "keyword-uri-fixup");
    433    Services.obs.addObserver(gLocaleChangeObserver, "intl:app-locales-changed");
    434 
    435    BrowserOffline.init();
    436 
    437    BrowserUtils.callModulesFromCategory(
    438      {
    439        categoryName: "browser-window-delayed-startup",
    440        profilerMarker: "delayed-startup-task",
    441      },
    442      window
    443    );
    444 
    445    // Initialize the full zoom setting.
    446    // We do this before the session restore service gets initialized so we can
    447    // apply full zoom settings to tabs restored by the session restore service.
    448    FullZoom.init();
    449    PanelUI.init(shouldSuppressPopupNotifications);
    450 
    451    UpdateUrlbarSearchSplitterState();
    452 
    453    BookmarkingUI.init();
    454    gURLBar.delayedStartupInit();
    455    if (Services.prefs.getBoolPref("browser.search.widget.new", false)) {
    456      document.getElementById("searchbar-new")?.delayedStartupInit();
    457    }
    458    gProtectionsHandler.init();
    459    gTrustPanelHandler.init();
    460 
    461    let safeMode = document.getElementById("helpSafeMode");
    462    if (Services.appinfo.inSafeMode) {
    463      document.l10n.setAttributes(safeMode, "menu-help-exit-troubleshoot-mode");
    464      safeMode.setAttribute(
    465        "appmenu-data-l10n-id",
    466        "appmenu-help-exit-troubleshoot-mode"
    467      );
    468    }
    469 
    470    // BiDi UI
    471    gBidiUI = isBidiEnabled();
    472    if (gBidiUI) {
    473      document.getElementById("documentDirection-separator").hidden = false;
    474      document.getElementById("documentDirection-swap").hidden = false;
    475      document.getElementById("textfieldDirection-separator").hidden = false;
    476      document.getElementById("textfieldDirection-swap").hidden = false;
    477    }
    478 
    479    // Setup click-and-hold gestures access to the session history
    480    // menus if global click-and-hold isn't turned on
    481    if (!Services.prefs.getBoolPref("ui.click_hold_context_menus", false)) {
    482      SetClickAndHoldHandlers();
    483    }
    484 
    485    function initBackForwardButtonTooltip(tooltipId, l10nId, shortcutId) {
    486      let shortcut = document.getElementById(shortcutId);
    487      shortcut = ShortcutUtils.prettifyShortcut(shortcut);
    488 
    489      let tooltip = document.getElementById(tooltipId);
    490      document.l10n.setAttributes(tooltip, l10nId, { shortcut });
    491    }
    492 
    493    initBackForwardButtonTooltip(
    494      "back-button-tooltip-description",
    495      "navbar-tooltip-back-2",
    496      "goBackKb"
    497    );
    498 
    499    initBackForwardButtonTooltip(
    500      "forward-button-tooltip-description",
    501      "navbar-tooltip-forward-2",
    502      "goForwardKb"
    503    );
    504 
    505    PlacesToolbarHelper.init();
    506 
    507    ctrlTab.readPref();
    508    Services.prefs.addObserver(ctrlTab.prefName, ctrlTab);
    509 
    510    // The object handling the downloads indicator is initialized here in the
    511    // delayed startup function, but the actual indicator element is not loaded
    512    // unless there are downloads to be displayed.
    513    DownloadsButton.initializeIndicator();
    514 
    515    if (AppConstants.platform != "macosx") {
    516      updateEditUIVisibility();
    517      let placesContext = document.getElementById("placesContext");
    518      placesContext.addEventListener("popupshowing", updateEditUIVisibility);
    519      placesContext.addEventListener("popuphiding", updateEditUIVisibility);
    520    }
    521 
    522    FullScreen.init();
    523 
    524    if (AppConstants.MOZ_DATA_REPORTING) {
    525      gDataNotificationInfoBar.init();
    526    }
    527 
    528    if (!AppConstants.MOZILLA_OFFICIAL) {
    529      DevelopmentHelpers.init();
    530    }
    531 
    532    gExtensionsNotifications.init();
    533 
    534    let wasMinimized = window.windowState == window.STATE_MINIMIZED;
    535    window.addEventListener("sizemodechange", () => {
    536      let isMinimized = window.windowState == window.STATE_MINIMIZED;
    537      if (wasMinimized != isMinimized) {
    538        wasMinimized = isMinimized;
    539        UpdatePopupNotificationsVisibility();
    540      }
    541    });
    542 
    543    window.addEventListener("mousemove", MousePosTracker);
    544    window.addEventListener("mouseout", MousePosTracker);
    545    window.addEventListener("dragover", MousePosTracker);
    546 
    547    gNavToolbox.addEventListener("customizationstarting", CustomizationHandler);
    548    gNavToolbox.addEventListener("aftercustomization", CustomizationHandler);
    549 
    550    SessionStore.promiseInitialized.then(() => {
    551      // Bail out if the window has been closed in the meantime.
    552      if (window.closed) {
    553        return;
    554      }
    555 
    556      // Enable the Restore Last Session command if needed
    557      gRestoreLastSessionObserver.init();
    558 
    559      SidebarController.startDelayedLoad();
    560 
    561      PanicButtonNotifier.init();
    562    });
    563 
    564    if (BrowserHandler.kiosk) {
    565      // We don't modify popup windows for kiosk mode
    566      if (!gURLBar.readOnly) {
    567        window.fullScreen = true;
    568      }
    569    }
    570 
    571    if (Services.policies.status === Services.policies.ACTIVE) {
    572      if (!Services.policies.isAllowed("hideShowMenuBar")) {
    573        document
    574          .getElementById("toolbar-menubar")
    575          .removeAttribute("toolbarname");
    576      }
    577      if (!Services.policies.isAllowed("filepickers")) {
    578        let savePageCommand = document.getElementById("Browser:SavePage");
    579        let openFileCommand = document.getElementById("Browser:OpenFile");
    580 
    581        savePageCommand.setAttribute("disabled", "true");
    582        openFileCommand.setAttribute("disabled", "true");
    583 
    584        document.addEventListener("FilePickerBlocked", function (event) {
    585          let browser = event.target;
    586 
    587          let notificationBox = browser
    588            .getTabBrowser()
    589            ?.getNotificationBox(browser);
    590 
    591          // Prevent duplicate notifications
    592          if (
    593            notificationBox &&
    594            !notificationBox.getNotificationWithValue("filepicker-blocked")
    595          ) {
    596            notificationBox.appendNotification("filepicker-blocked", {
    597              label: {
    598                "l10n-id": "filepicker-blocked-infobar",
    599              },
    600              priority: notificationBox.PRIORITY_INFO_LOW,
    601            });
    602          }
    603        });
    604      }
    605      let policies = Services.policies.getActivePolicies();
    606      if ("ManagedBookmarks" in policies) {
    607        let managedBookmarks = policies.ManagedBookmarks;
    608        let children = managedBookmarks.filter(
    609          child => !("toplevel_name" in child)
    610        );
    611        if (children.length) {
    612          let managedBookmarksButton =
    613            document.createXULElement("toolbarbutton");
    614          managedBookmarksButton.setAttribute("id", "managed-bookmarks");
    615          managedBookmarksButton.setAttribute("class", "bookmark-item");
    616          let toplevel = managedBookmarks.find(
    617            element => "toplevel_name" in element
    618          );
    619          if (toplevel) {
    620            managedBookmarksButton.setAttribute(
    621              "label",
    622              toplevel.toplevel_name
    623            );
    624          } else {
    625            document.l10n.setAttributes(
    626              managedBookmarksButton,
    627              "managed-bookmarks"
    628            );
    629          }
    630          managedBookmarksButton.setAttribute("context", "placesContext");
    631          managedBookmarksButton.setAttribute("container", "true");
    632          managedBookmarksButton.setAttribute("removable", "false");
    633          managedBookmarksButton.setAttribute("type", "menu");
    634 
    635          let managedBookmarksPopup = document.createXULElement("menupopup");
    636          managedBookmarksPopup.setAttribute("id", "managed-bookmarks-popup");
    637          managedBookmarksPopup.addEventListener("command", event =>
    638            PlacesToolbarHelper.openManagedBookmark(event)
    639          );
    640          managedBookmarksPopup.addEventListener(
    641            "dragover",
    642            event => (event.dataTransfer.effectAllowed = "none")
    643          );
    644          managedBookmarksPopup.addEventListener("dragstart", event =>
    645            PlacesToolbarHelper.onDragStartManaged(event)
    646          );
    647          managedBookmarksPopup.addEventListener("popupshowing", event =>
    648            PlacesToolbarHelper.populateManagedBookmarks(event.currentTarget)
    649          );
    650          managedBookmarksPopup.setAttribute("placespopup", "true");
    651          managedBookmarksPopup.setAttribute("is", "places-popup");
    652          managedBookmarksPopup.classList.add("toolbar-menupopup");
    653          managedBookmarksButton.appendChild(managedBookmarksPopup);
    654 
    655          gNavToolbox.palette.appendChild(managedBookmarksButton);
    656 
    657          CustomizableUI.ensureWidgetPlacedInWindow(
    658            "managed-bookmarks",
    659            window
    660          );
    661 
    662          // Add button if it doesn't exist
    663          if (!CustomizableUI.getPlacementOfWidget("managed-bookmarks")) {
    664            CustomizableUI.addWidgetToArea(
    665              "managed-bookmarks",
    666              CustomizableUI.AREA_BOOKMARKS,
    667              0
    668            );
    669          }
    670        }
    671      }
    672    }
    673 
    674    CaptivePortalWatcher.delayedStartup();
    675 
    676    SessionStore.promiseAllWindowsRestored.then(() => {
    677      this._schedulePerWindowIdleTasks();
    678      document.documentElement.setAttribute("sessionrestored", "true");
    679    });
    680 
    681    this.delayedStartupFinished = true;
    682    _resolveDelayedStartup();
    683    Services.obs.notifyObservers(window, "browser-delayed-startup-finished");
    684    TelemetryTimestamps.add("delayedStartupFinished");
    685    Glean.browserTimings.startupTimeline.delayedStartupFinished.set(
    686      Services.telemetry.msSinceProcessStart()
    687    );
    688    // We've announced that delayed startup has finished. Do not add code past this point.
    689  },
    690 
    691  /**
    692   * Resolved on the first MozLayerTreeReady and next MozAfterPaint in the
    693   * parent process.
    694   */
    695  get firstContentWindowPaintPromise() {
    696    return this._firstContentWindowPaintDeferred.promise;
    697  },
    698 
    699  _setInitialFocus() {
    700    let initiallyFocusedElement = document.commandDispatcher.focusedElement;
    701 
    702    // To prevent startup flicker, the urlbar has the 'focused' attribute set
    703    // by default. If we are not sure the urlbar will be focused in this
    704    // window, we need to remove the attribute before first paint.
    705    // TODO (bug 1629956): The urlbar having the 'focused' attribute by default
    706    // isn't a useful optimization anymore since UrlbarInput needs layout
    707    // information to focus the urlbar properly.
    708    let shouldRemoveFocusedAttribute = true;
    709 
    710    this._callWithURIToLoad(uriToLoad => {
    711      if (
    712        isBlankPageURL(uriToLoad) ||
    713        uriToLoad == "about:privatebrowsing" ||
    714        this.getTabToAdopt()?.isEmpty
    715      ) {
    716        gURLBar.select();
    717        shouldRemoveFocusedAttribute = false;
    718        return;
    719      }
    720 
    721      // If the initial browser is remote, in order to optimize for first paint,
    722      // we'll defer switching focus to that browser until it has painted.
    723      // Otherwise use a regular promise to guarantee that mutationobserver
    724      // microtasks that could affect focusability have run.
    725      let promise = gBrowser.selectedBrowser.isRemoteBrowser
    726        ? this.firstContentWindowPaintPromise
    727        : Promise.resolve();
    728 
    729      promise.then(() => {
    730        // If focus didn't move while we were waiting, we're okay to move to
    731        // the browser.
    732        if (
    733          document.commandDispatcher.focusedElement == initiallyFocusedElement
    734        ) {
    735          gBrowser.selectedBrowser.focus();
    736        }
    737      });
    738    });
    739 
    740    // Delay removing the attribute using requestAnimationFrame to avoid
    741    // invalidating styles multiple times in a row if uriToLoadPromise
    742    // resolves before first paint.
    743    if (shouldRemoveFocusedAttribute) {
    744      window.requestAnimationFrame(() => {
    745        if (shouldRemoveFocusedAttribute) {
    746          gURLBar.removeAttribute("focused");
    747        }
    748      });
    749    }
    750  },
    751 
    752  _handleURIToLoad() {
    753    this._callWithURIToLoad(uriToLoad => {
    754      if (!uriToLoad) {
    755        // We don't check whether window.arguments[5] (userContextId) is set
    756        // because tabbrowser.js takes care of that for the initial tab.
    757        return;
    758      }
    759 
    760      // We don't check if uriToLoad is a XULElement because this case has
    761      // already been handled before first paint, and the argument cleared.
    762      if (Array.isArray(uriToLoad)) {
    763        // This function throws for certain malformed URIs, so use exception handling
    764        // so that we don't disrupt startup
    765        try {
    766          gBrowser.loadTabs(uriToLoad, {
    767            inBackground: false,
    768            replace: true,
    769            // See below for the semantics of window.arguments. Only the minimum is supported.
    770            userContextId: window.arguments[5],
    771            triggeringPrincipal:
    772              window.arguments[8] ||
    773              Services.scriptSecurityManager.getSystemPrincipal(),
    774            allowInheritPrincipal: window.arguments[9],
    775            policyContainer: window.arguments[10],
    776            fromExternal: true,
    777          });
    778        } catch (e) {}
    779      } else if (window.arguments.length >= 3) {
    780        // window.arguments[1]: extraOptions (nsIPropertyBag)
    781        //                 [2]: referrerInfo (nsIReferrerInfo)
    782        //                 [3]: postData (nsIInputStream)
    783        //                 [4]: allowThirdPartyFixup (bool)
    784        //                 [5]: userContextId (int)
    785        //                 [6]: originPrincipal (nsIPrincipal)
    786        //                 [7]: originStoragePrincipal (nsIPrincipal)
    787        //                 [8]: triggeringPrincipal (nsIPrincipal)
    788        //                 [9]: allowInheritPrincipal (bool)
    789        //                 [10]: policyContainer (nsIPolicyContainer)
    790        //                 [11]: nsOpenWindowInfo
    791        let userContextId =
    792          window.arguments[5] != undefined
    793            ? window.arguments[5]
    794            : Ci.nsIScriptSecurityManager.DEFAULT_USER_CONTEXT_ID;
    795 
    796        let hasValidUserGestureActivation = undefined;
    797        let textDirectiveUserActivation = undefined;
    798        let fromExternal = undefined;
    799        let globalHistoryOptions = undefined;
    800        let triggeringRemoteType = undefined;
    801        let forceAllowDataURI = false;
    802        let schemelessInput = Ci.nsILoadInfo.SchemelessInputTypeUnset;
    803        if (window.arguments[1]) {
    804          if (!(window.arguments[1] instanceof Ci.nsIPropertyBag2)) {
    805            throw new Error(
    806              "window.arguments[1] must be null or Ci.nsIPropertyBag2!"
    807            );
    808          }
    809 
    810          let extraOptions = window.arguments[1];
    811          if (extraOptions.hasKey("hasValidUserGestureActivation")) {
    812            hasValidUserGestureActivation = extraOptions.getPropertyAsBool(
    813              "hasValidUserGestureActivation"
    814            );
    815          }
    816          if (extraOptions.hasKey("textDirectiveUserActivation")) {
    817            textDirectiveUserActivation = extraOptions.getPropertyAsBool(
    818              "textDirectiveUserActivation"
    819            );
    820          }
    821          if (extraOptions.hasKey("fromExternal")) {
    822            fromExternal = extraOptions.getPropertyAsBool("fromExternal");
    823          }
    824          if (extraOptions.hasKey("triggeringSponsoredURL")) {
    825            globalHistoryOptions = {
    826              triggeringSponsoredURL: extraOptions.getPropertyAsACString(
    827                "triggeringSponsoredURL"
    828              ),
    829            };
    830            if (extraOptions.hasKey("triggeringSponsoredURLVisitTimeMS")) {
    831              globalHistoryOptions.triggeringSponsoredURLVisitTimeMS =
    832                extraOptions.getPropertyAsUint64(
    833                  "triggeringSponsoredURLVisitTimeMS"
    834                );
    835            }
    836            if (extraOptions.hasKey("triggeringSource")) {
    837              globalHistoryOptions.triggeringSource =
    838                extraOptions.getPropertyAsACString("triggeringSource");
    839            }
    840          }
    841          if (extraOptions.hasKey("triggeringRemoteType")) {
    842            triggeringRemoteType = extraOptions.getPropertyAsACString(
    843              "triggeringRemoteType"
    844            );
    845          }
    846          if (extraOptions.hasKey("forceAllowDataURI")) {
    847            forceAllowDataURI =
    848              extraOptions.getPropertyAsBool("forceAllowDataURI");
    849          }
    850          if (extraOptions.hasKey("schemelessInput")) {
    851            schemelessInput =
    852              extraOptions.getPropertyAsUint32("schemelessInput");
    853          }
    854        }
    855 
    856        try {
    857          openLinkIn(uriToLoad, "current", {
    858            referrerInfo: window.arguments[2] || null,
    859            postData: window.arguments[3] || null,
    860            allowThirdPartyFixup: window.arguments[4] || false,
    861            userContextId,
    862            // pass the origin principal (if any) and force its use to create
    863            // an initial about:blank viewer if present:
    864            originPrincipal: window.arguments[6],
    865            originStoragePrincipal: window.arguments[7],
    866            triggeringPrincipal: window.arguments[8],
    867            // TODO fix allowInheritPrincipal to default to false.
    868            // Default to true unless explicitly set to false because of bug 1475201.
    869            allowInheritPrincipal: window.arguments[9] !== false,
    870            policyContainer: window.arguments[10],
    871            forceAboutBlankViewerInCurrent: !!window.arguments[6],
    872            forceAllowDataURI,
    873            hasValidUserGestureActivation,
    874            textDirectiveUserActivation,
    875            fromExternal,
    876            globalHistoryOptions,
    877            triggeringRemoteType,
    878            schemelessInput,
    879          });
    880        } catch (e) {
    881          console.error(e);
    882        }
    883 
    884        window.focus();
    885      } else {
    886        // Note: loadOneOrMoreURIs *must not* be called if window.arguments.length >= 3.
    887        // Such callers expect that window.arguments[0] is handled as a single URI.
    888        loadOneOrMoreURIs(
    889          uriToLoad,
    890          Services.scriptSecurityManager.getSystemPrincipal(),
    891          null
    892        );
    893      }
    894    });
    895  },
    896 
    897  /**
    898   * Use this function as an entry point to schedule tasks that
    899   * need to run once per window after startup, and can be scheduled
    900   * by using an idle callback.
    901   *
    902   * The functions scheduled here will fire from idle callbacks
    903   * once every window has finished being restored by session
    904   * restore, and after the equivalent only-once tasks
    905   * have run (from _scheduleStartupIdleTasks in BrowserGlue.sys.mjs).
    906   */
    907  _schedulePerWindowIdleTasks() {
    908    // Bail out if the window has been closed in the meantime.
    909    if (window.closed) {
    910      return;
    911    }
    912 
    913    function scheduleIdleTask(func, options) {
    914      requestIdleCallback(function idleTaskRunner() {
    915        if (!window.closed) {
    916          func();
    917        }
    918      }, options);
    919    }
    920 
    921    scheduleIdleTask(() => {
    922      // Initialize the Sync UI
    923      gSync.init();
    924    });
    925 
    926    scheduleIdleTask(() => {
    927      // Read prefers-reduced-motion setting
    928      let reduceMotionQuery = window.matchMedia(
    929        "(prefers-reduced-motion: reduce)"
    930      );
    931      function readSetting() {
    932        gReduceMotionSetting = reduceMotionQuery.matches;
    933      }
    934      reduceMotionQuery.addListener(readSetting);
    935      readSetting();
    936    });
    937 
    938    scheduleIdleTask(() => {
    939      // setup simple gestures support
    940      gGestureSupport.init(true);
    941 
    942      // setup history swipe animation
    943      gHistorySwipeAnimation.init();
    944    });
    945 
    946    scheduleIdleTask(() => {
    947      gBrowserThumbnails.init();
    948    });
    949 
    950    scheduleIdleTask(
    951      () => {
    952        // Initialize the download manager some time after the app starts so that
    953        // auto-resume downloads begin (such as after crashing or quitting with
    954        // active downloads) and speeds up the first-load of the download manager UI.
    955        // If the user manually opens the download manager before the timeout, the
    956        // downloads will start right away, and initializing again won't hurt.
    957        try {
    958          DownloadsCommon.initializeAllDataLinks();
    959          ChromeUtils.importESModule(
    960            "moz-src:///browser/components/downloads/DownloadsTaskbar.sys.mjs"
    961          )
    962            .DownloadsTaskbar.registerIndicator(window)
    963            .catch(ex => {
    964              console.error(ex);
    965            });
    966          if (AppConstants.platform == "macosx") {
    967            ChromeUtils.importESModule(
    968              "moz-src:///browser/components/downloads/DownloadsMacFinderProgress.sys.mjs"
    969            ).DownloadsMacFinderProgress.register();
    970          }
    971        } catch (ex) {
    972          console.error(ex);
    973        }
    974      },
    975      { timeout: 10000 }
    976    );
    977 
    978    if (Win7Features) {
    979      scheduleIdleTask(() => Win7Features.onOpenWindow());
    980    }
    981 
    982    scheduleIdleTask(async () => {
    983      NewTabPagePreloading.maybeCreatePreloadedBrowser(window);
    984    });
    985 
    986    scheduleIdleTask(() => {
    987      gGfxUtils.init();
    988    });
    989 
    990    scheduleIdleTask(async () => {
    991      await gProfiles.init();
    992    });
    993 
    994    // This should always go last, since the idle tasks (except for the ones with
    995    // timeouts) should execute in order. Note that this observer notification is
    996    // not guaranteed to fire, since the window could close before we get here.
    997    scheduleIdleTask(() => {
    998      this.idleTasksFinished.resolve();
    999      Services.obs.notifyObservers(
   1000        window,
   1001        "browser-idle-startup-tasks-finished"
   1002      );
   1003    });
   1004  },
   1005 
   1006  // Returns the URI(s) to load at startup if it is immediately known, or a
   1007  // promise resolving to the URI to load.
   1008  get uriToLoadPromise() {
   1009    delete this.uriToLoadPromise;
   1010    return (this.uriToLoadPromise = (function () {
   1011      // window.arguments[0]: URI to load (string), or an nsIArray of
   1012      //                      nsISupportsStrings to load, or a xul:tab of
   1013      //                      a tabbrowser, which will be replaced by this
   1014      //                      window (for this case, all other arguments are
   1015      //                      ignored).
   1016      let uri = window.arguments?.[0];
   1017      if (!uri || window.XULElement.isInstance(uri)) {
   1018        return null;
   1019      }
   1020 
   1021      let defaultArgs = BrowserHandler.defaultArgs;
   1022 
   1023      // figure out which URI to actually load (or a Promise to get the uri)
   1024      uri = (aUri => {
   1025        // If the given URI is different from the homepage, we want to load it.
   1026        if (aUri != defaultArgs) {
   1027          AboutNewTab.noteNonDefaultStartup();
   1028 
   1029          if (aUri instanceof Ci.nsIArray) {
   1030            // Transform the nsIArray of nsISupportsString's into a JS Array of
   1031            // JS strings.
   1032            return Array.from(
   1033              aUri.enumerate(Ci.nsISupportsString),
   1034              supportStr => supportStr.data
   1035            );
   1036          } else if (aUri instanceof Ci.nsISupportsString) {
   1037            return aUri.data;
   1038          }
   1039          return aUri;
   1040        }
   1041 
   1042        // The URI appears to be the the homepage. We want to load it only if
   1043        // session restore isn't about to override the homepage.
   1044        let willOverride = SessionStartup.willOverrideHomepage;
   1045        if (typeof willOverride == "boolean") {
   1046          return willOverride ? null : uri;
   1047        }
   1048        return willOverride.then(willOverrideHomepage =>
   1049          willOverrideHomepage ? null : uri
   1050        );
   1051      })(uri);
   1052 
   1053      // if using TorConnect, convert these uris to redirects
   1054      if (TorConnect.shouldShowTorConnect) {
   1055        return Promise.resolve(uri).then(aUri =>
   1056          TorConnectParent.getURIsToLoad(aUri ?? [])
   1057        );
   1058      }
   1059      return uri;
   1060    })());
   1061  },
   1062 
   1063  // Calls the given callback with the URI to load at startup.
   1064  // Synchronously if possible, or after uriToLoadPromise resolves otherwise.
   1065  _callWithURIToLoad(callback) {
   1066    let uriToLoad = this.uriToLoadPromise;
   1067    if (uriToLoad && uriToLoad.then) {
   1068      uriToLoad.then(callback);
   1069    } else {
   1070      callback(uriToLoad);
   1071    }
   1072  },
   1073 
   1074  onUnload() {
   1075    gUIDensity.uninit();
   1076 
   1077    CustomTitlebar.uninit();
   1078 
   1079    ToolbarIconColor.uninit(window);
   1080 
   1081    // In certain scenarios it's possible for unload to be fired before onload,
   1082    // (e.g. if the window is being closed after browser.js loads but before the
   1083    // load completes). In that case, there's nothing to do here.
   1084    if (!this._loadHandled) {
   1085      return;
   1086    }
   1087 
   1088    // First clean up services initialized in gBrowserInit.onLoad (or those whose
   1089    // uninit methods don't depend on the services having been initialized).
   1090 
   1091    CombinedStopReload.uninit();
   1092 
   1093    gGestureSupport.init(false);
   1094 
   1095    gHistorySwipeAnimation.uninit();
   1096 
   1097    FullScreen.uninit();
   1098 
   1099    gSync.uninit();
   1100 
   1101    gExtensionsNotifications.uninit();
   1102    gUnifiedExtensions.uninit();
   1103 
   1104    try {
   1105      gBrowser.removeProgressListener(window.XULBrowserWindow);
   1106      gBrowser.removeTabsProgressListener(window.TabsProgressListener);
   1107    } catch (ex) {}
   1108 
   1109    PlacesToolbarHelper.uninit();
   1110 
   1111    BookmarkingUI.uninit();
   1112 
   1113    Win10TabletModeUpdater.uninit();
   1114 
   1115    CaptivePortalWatcher.uninit();
   1116 
   1117    SidebarController.uninit();
   1118 
   1119    DownloadsButton.uninit();
   1120 
   1121    SecurityLevelButton.uninit();
   1122 
   1123    gTorConnectUrlbarButton.uninit();
   1124    gTorConnectTitlebarStatus.uninit();
   1125 
   1126    OnionAuthPrompt.uninit();
   1127 
   1128    gTorCircuitPanel.uninit();
   1129 
   1130    if (gToolbarKeyNavEnabled) {
   1131      ToolbarKeyboardNavigator.uninit();
   1132    }
   1133    CustomKeys.uninitWindow(window);
   1134 
   1135    // LinkPreview.sys.mjs is missing. tor-browser#44045.
   1136 
   1137    FirefoxViewHandler.uninit();
   1138 
   1139    // Now either cancel delayedStartup, or clean up the services initialized from
   1140    // it.
   1141    if (this._boundDelayedStartup) {
   1142      this._cancelDelayedStartup();
   1143    } else {
   1144      if (Win7Features) {
   1145        Win7Features.onCloseWindow();
   1146      }
   1147      Services.prefs.removeObserver(ctrlTab.prefName, ctrlTab);
   1148      ctrlTab.uninit();
   1149      gBrowserThumbnails.uninit();
   1150      gProtectionsHandler.uninit();
   1151      gTrustPanelHandler.uninit();
   1152      FullZoom.destroy();
   1153 
   1154      Services.obs.removeObserver(gIdentityHandler, "perm-changed");
   1155      Services.obs.removeObserver(gRemoteControl, "devtools-socket");
   1156      Services.obs.removeObserver(gRemoteControl, "marionette-listening");
   1157      Services.obs.removeObserver(gRemoteControl, "remote-listening");
   1158      Services.obs.removeObserver(
   1159        gSessionHistoryObserver,
   1160        "browser:purge-session-history"
   1161      );
   1162      Services.obs.removeObserver(
   1163        gStoragePressureObserver,
   1164        "QuotaManager::StoragePressure"
   1165      );
   1166      Services.obs.removeObserver(gXPInstallObserver, "addon-install-disabled");
   1167      Services.obs.removeObserver(gXPInstallObserver, "addon-install-started");
   1168      Services.obs.removeObserver(gXPInstallObserver, "addon-install-blocked");
   1169      Services.obs.removeObserver(
   1170        gXPInstallObserver,
   1171        "addon-install-fullscreen-blocked"
   1172      );
   1173      Services.obs.removeObserver(
   1174        gXPInstallObserver,
   1175        "addon-install-origin-blocked"
   1176      );
   1177      Services.obs.removeObserver(
   1178        gXPInstallObserver,
   1179        "addon-install-policy-blocked"
   1180      );
   1181      Services.obs.removeObserver(
   1182        gXPInstallObserver,
   1183        "addon-install-webapi-blocked"
   1184      );
   1185      Services.obs.removeObserver(gXPInstallObserver, "addon-install-failed");
   1186      Services.obs.removeObserver(
   1187        gXPInstallObserver,
   1188        "addon-install-confirmation"
   1189      );
   1190      Services.obs.removeObserver(gKeywordURIFixup, "keyword-uri-fixup");
   1191      Services.obs.removeObserver(
   1192        gLocaleChangeObserver,
   1193        "intl:app-locales-changed"
   1194      );
   1195 
   1196      BrowserOffline.uninit();
   1197      PanelUI.uninit();
   1198    }
   1199 
   1200    // Final window teardown, do this last.
   1201    gBrowser.destroy();
   1202    window.XULBrowserWindow = null;
   1203    window.docShell.treeOwner
   1204      .QueryInterface(Ci.nsIInterfaceRequestor)
   1205      .getInterface(Ci.nsIAppWindow).XULBrowserWindow = null;
   1206 
   1207    BrowserUtils.callModulesFromCategory(
   1208      { categoryName: "browser-window-unload" },
   1209      window
   1210    );
   1211  },
   1212 };