tor-browser

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

BrowserGlue.sys.mjs (59040B)


      1 /* This Source Code Form is subject to the terms of the Mozilla Public
      2 * License, v. 2.0. If a copy of the MPL was not distributed with this
      3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 
      5 import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";
      6 import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
      7 
      8 const lazy = {};
      9 
     10 ChromeUtils.defineESModuleGetters(lazy, {
     11  // newtab component is disabled. tor-browser#43886
     12  AWToolbarButton: "resource:///modules/aboutwelcome/AWToolbarUtils.sys.mjs",
     13  ASRouter: "resource:///modules/asrouter/ASRouter.sys.mjs",
     14  AddonManager: "resource://gre/modules/AddonManager.sys.mjs",
     15  BackupService: "resource:///modules/backup/BackupService.sys.mjs",
     16  BrowserSearchTelemetry:
     17    "moz-src:///browser/components/search/BrowserSearchTelemetry.sys.mjs",
     18  BrowserUtils: "resource://gre/modules/BrowserUtils.sys.mjs",
     19  BrowserUsageTelemetry: "resource:///modules/BrowserUsageTelemetry.sys.mjs",
     20  BrowserWindowTracker: "resource:///modules/BrowserWindowTracker.sys.mjs",
     21  ContextualIdentityService:
     22    "resource://gre/modules/ContextualIdentityService.sys.mjs",
     23  DAPIncrementality: "resource://gre/modules/DAPIncrementality.sys.mjs",
     24  DAPTelemetrySender: "resource://gre/modules/DAPTelemetrySender.sys.mjs",
     25  DAPVisitCounter: "resource://gre/modules/DAPVisitCounter.sys.mjs",
     26  DefaultBrowserCheck:
     27    "moz-src:///browser/components/DefaultBrowserCheck.sys.mjs",
     28  DesktopActorRegistry:
     29    "moz-src:///browser/components/DesktopActorRegistry.sys.mjs",
     30  Discovery: "resource:///modules/Discovery.sys.mjs",
     31  DistributionManagement: "resource:///modules/distribution.sys.mjs",
     32  DownloadsViewableInternally:
     33    "moz-src:///browser/components/downloads/DownloadsViewableInternally.sys.mjs",
     34  ExtensionsUI: "resource:///modules/ExtensionsUI.sys.mjs",
     35  FormAutofillUtils: "resource://gre/modules/shared/FormAutofillUtils.sys.mjs",
     36  Interactions: "moz-src:///browser/components/places/Interactions.sys.mjs",
     37  LoginBreaches: "resource:///modules/LoginBreaches.sys.mjs",
     38  LoginHelper: "resource://gre/modules/LoginHelper.sys.mjs",
     39  MigrationUtils: "resource:///modules/MigrationUtils.sys.mjs",
     40  NimbusFeatures: "resource://nimbus/ExperimentAPI.sys.mjs",
     41  OnboardingMessageProvider:
     42    "resource:///modules/asrouter/OnboardingMessageProvider.sys.mjs",
     43  PageDataService:
     44    "moz-src:///browser/components/pagedata/PageDataService.sys.mjs",
     45  PdfJs: "resource://pdf.js/PdfJs.sys.mjs",
     46  PlacesBrowserStartup:
     47    "moz-src:///browser/components/places/PlacesBrowserStartup.sys.mjs",
     48  PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.sys.mjs",
     49  ProfileDataUpgrader:
     50    "moz-src:///browser/components/ProfileDataUpgrader.sys.mjs",
     51  RemoteSettings: "resource://services-settings/remote-settings.sys.mjs",
     52  SafeBrowsing: "resource://gre/modules/SafeBrowsing.sys.mjs",
     53  Sanitizer: "resource:///modules/Sanitizer.sys.mjs",
     54  ScreenshotsUtils: "resource:///modules/ScreenshotsUtils.sys.mjs",
     55  SearchSERPTelemetry:
     56    "moz-src:///browser/components/search/SearchSERPTelemetry.sys.mjs",
     57  SessionStartup: "resource:///modules/sessionstore/SessionStartup.sys.mjs",
     58  SessionWindowUI: "resource:///modules/sessionstore/SessionWindowUI.sys.mjs",
     59  ShortcutUtils: "resource://gre/modules/ShortcutUtils.sys.mjs",
     60  SpecialMessageActions:
     61    "resource://messaging-system/lib/SpecialMessageActions.sys.mjs",
     62  StartupOSIntegration:
     63    "moz-src:///browser/components/shell/StartupOSIntegration.sys.mjs",
     64  TelemetryReportingPolicy:
     65    "resource://gre/modules/TelemetryReportingPolicy.sys.mjs",
     66  TRRRacer: "resource:///modules/TRRPerformance.sys.mjs",
     67  WebChannel: "resource://gre/modules/WebChannel.sys.mjs",
     68  WebProtocolHandlerRegistrar:
     69    "resource:///modules/WebProtocolHandlerRegistrar.sys.mjs",
     70  WindowsRegistry: "resource://gre/modules/WindowsRegistry.sys.mjs",
     71  setTimeout: "resource://gre/modules/Timer.sys.mjs",
     72 });
     73 
     74 XPCOMUtils.defineLazyServiceGetters(lazy, {
     75  BrowserHandler: ["@mozilla.org/browser/clh;1", Ci.nsIBrowserHandler],
     76  PushService: ["@mozilla.org/push/Service;1", Ci.nsIPushService],
     77 });
     78 
     79 if (AppConstants.ENABLE_WEBDRIVER) {
     80  XPCOMUtils.defineLazyServiceGetter(
     81    lazy,
     82    "Marionette",
     83    "@mozilla.org/remote/marionette;1",
     84    Ci.nsIMarionette
     85  );
     86 
     87  XPCOMUtils.defineLazyServiceGetter(
     88    lazy,
     89    "RemoteAgent",
     90    "@mozilla.org/remote/agent;1",
     91    Ci.nsIRemoteAgent
     92  );
     93 } else {
     94  lazy.Marionette = { running: false };
     95  lazy.RemoteAgent = { running: false };
     96 }
     97 
     98 const PREF_PDFJS_ISDEFAULT_CACHE_STATE = "pdfjs.enabledCache.state";
     99 
    100 ChromeUtils.defineLazyGetter(
    101  lazy,
    102  "WeaveService",
    103  () => Cc["@mozilla.org/weave/service;1"].getService().wrappedJSObject
    104 );
    105 
    106 if (AppConstants.MOZ_CRASHREPORTER) {
    107  ChromeUtils.defineESModuleGetters(lazy, {
    108    UnsubmittedCrashHandler: "resource:///modules/ContentCrashHandlers.sys.mjs",
    109  });
    110 }
    111 
    112 ChromeUtils.defineLazyGetter(lazy, "gBrandBundle", function () {
    113  return Services.strings.createBundle(
    114    "chrome://branding/locale/brand.properties"
    115  );
    116 });
    117 
    118 ChromeUtils.defineLazyGetter(lazy, "gBrowserBundle", function () {
    119  return Services.strings.createBundle(
    120    "chrome://browser/locale/browser.properties"
    121  );
    122 });
    123 
    124 // Seconds of idle time before the late idle tasks will be scheduled.
    125 const LATE_TASKS_IDLE_TIME_SEC = 20;
    126 // Time after we stop tracking startup crashes.
    127 const STARTUP_CRASHES_END_DELAY_MS = 30 * 1000;
    128 
    129 /*
    130 * OS X has the concept of zero-window sessions and therefore ignores the
    131 * browser-lastwindow-close-* topics.
    132 */
    133 const OBSERVE_LASTWINDOW_CLOSE_TOPICS = AppConstants.platform != "macosx";
    134 
    135 export let BrowserInitState = {};
    136 BrowserInitState.startupIdleTaskPromise = new Promise(resolve => {
    137  BrowserInitState._resolveStartupIdleTask = resolve;
    138 });
    139 // Whether this launch was initiated by the OS.  A launch-on-login will contain
    140 // the "os-autostart" flag in the initial launch command line.
    141 BrowserInitState.isLaunchOnLogin = false;
    142 // Whether this launch was initiated by a taskbar tab shortcut. A launch from
    143 // a taskbar tab shortcut will contain the "taskbar-tab" flag.
    144 BrowserInitState.isTaskbarTab = false;
    145 
    146 export function BrowserGlue() {
    147  XPCOMUtils.defineLazyServiceGetter(
    148    this,
    149    "_userIdleService",
    150    "@mozilla.org/widget/useridleservice;1",
    151    Ci.nsIUserIdleService
    152  );
    153 
    154  this._init();
    155 }
    156 
    157 BrowserGlue.prototype = {
    158  _saveSession: false,
    159  _isNewProfile: undefined,
    160  _defaultCookieBehaviorAtStartup: null,
    161 
    162  _setPrefToSaveSession: function BG__setPrefToSaveSession(aForce) {
    163    if (!this._saveSession && !aForce) {
    164      return;
    165    }
    166 
    167    if (!lazy.PrivateBrowsingUtils.permanentPrivateBrowsing) {
    168      Services.prefs.setBoolPref(
    169        "browser.sessionstore.resume_session_once",
    170        true
    171      );
    172    }
    173 
    174    // This method can be called via [NSApplication terminate:] on Mac, which
    175    // ends up causing prefs not to be flushed to disk, so we need to do that
    176    // explicitly here. See bug 497652.
    177    Services.prefs.savePrefFile(null);
    178  },
    179 
    180  // nsIObserver implementation
    181  observe: async function BG_observe(subject, topic, data) {
    182    switch (topic) {
    183      case "notifications-open-settings":
    184        this._openPreferences("privacy-permissions");
    185        break;
    186      case "final-ui-startup":
    187        this._beforeUIStartup();
    188        break;
    189      case "browser-delayed-startup-finished":
    190        this._onFirstWindowLoaded(subject);
    191        Services.obs.removeObserver(this, "browser-delayed-startup-finished");
    192        break;
    193      case "sessionstore-windows-restored":
    194        this._onWindowsRestored();
    195        break;
    196      case "browser:purge-session-history":
    197        // reset the console service's error buffer
    198        Services.console.logStringMessage(null); // clear the console (in case it's open)
    199        Services.console.reset();
    200        break;
    201      case "restart-in-safe-mode":
    202        this._onSafeModeRestart(subject);
    203        break;
    204      case "quit-application-requested":
    205        this._onQuitRequest(subject, data);
    206        break;
    207      case "quit-application-granted":
    208        this._onQuitApplicationGranted();
    209        break;
    210      case "browser-lastwindow-close-requested":
    211        if (OBSERVE_LASTWINDOW_CLOSE_TOPICS) {
    212          // The application is not actually quitting, but the last full browser
    213          // window is about to be closed.
    214          this._onQuitRequest(subject, "lastwindow");
    215        }
    216        break;
    217      case "browser-lastwindow-close-granted":
    218        if (OBSERVE_LASTWINDOW_CLOSE_TOPICS) {
    219          this._setPrefToSaveSession();
    220        }
    221        break;
    222      case "session-save":
    223        this._setPrefToSaveSession(true);
    224        subject.QueryInterface(Ci.nsISupportsPRBool);
    225        subject.data = true;
    226        break;
    227      case "places-init-complete":
    228        Services.obs.removeObserver(this, "places-init-complete");
    229        lazy.PlacesBrowserStartup.backendInitComplete();
    230        break;
    231      case "browser-glue-test": // used by tests
    232        if (data == "force-ui-migration") {
    233          this._migrateUI();
    234        } else if (data == "places-browser-init-complete") {
    235          lazy.PlacesBrowserStartup.notifyIfInitializationComplete();
    236        } else if (data == "add-breaches-sync-handler") {
    237          this._addBreachesSyncHandler();
    238        }
    239        break;
    240      case "handle-xul-text-link": {
    241        let linkHandled = subject.QueryInterface(Ci.nsISupportsPRBool);
    242        if (!linkHandled.data) {
    243          let win =
    244            lazy.BrowserWindowTracker.getTopWindow() ??
    245            (await lazy.BrowserWindowTracker.promiseOpenWindow());
    246          if (win) {
    247            data = JSON.parse(data);
    248            let where = lazy.BrowserUtils.whereToOpenLink(data);
    249            // Preserve legacy behavior of non-modifier left-clicks
    250            // opening in a new selected tab.
    251            if (where == "current") {
    252              where = "tab";
    253            }
    254            win.openTrustedLinkIn(data.href, where);
    255            linkHandled.data = true;
    256          }
    257        }
    258        break;
    259      }
    260      case "profile-before-change":
    261        // Any component that doesn't need to act after
    262        // the UI has gone should be finalized in _onQuitApplicationGranted.
    263        this._dispose();
    264        break;
    265      case "keyword-search": {
    266        // This notification is broadcast by the docshell when it "fixes up" a
    267        // URI that it's been asked to load into a keyword search.
    268        let engine = null;
    269        try {
    270          engine = Services.search.getEngineByName(
    271            subject.QueryInterface(Ci.nsISupportsString).data
    272          );
    273        } catch (ex) {
    274          console.error(ex);
    275        }
    276        let win = lazy.BrowserWindowTracker.getTopWindow({
    277          allowFromInactiveWorkspace: true,
    278        });
    279        lazy.BrowserSearchTelemetry.recordSearch(
    280          win.gBrowser.selectedBrowser,
    281          engine,
    282          "urlbar"
    283        );
    284        break;
    285      }
    286      case "xpi-signature-changed": {
    287        let disabledAddons = JSON.parse(data).disabled;
    288        let addons = await lazy.AddonManager.getAddonsByIDs(disabledAddons);
    289        if (addons.some(addon => addon)) {
    290          this._notifyUnsignedAddonsDisabled();
    291        }
    292        break;
    293      }
    294      case "handlersvc-store-initialized":
    295        // Initialize PdfJs when running in-process and remote. This only
    296        // happens once since PdfJs registers global hooks. If the PdfJs
    297        // extension is installed the init method below will be overridden
    298        // leaving initialization to the extension.
    299        // parent only: configure default prefs, set up pref observers, register
    300        // pdf content handler, and initializes parent side message manager
    301        // shim for privileged api access.
    302        lazy.PdfJs.init(this._isNewProfile);
    303 
    304        // Allow certain viewable internally types to be opened from downloads.
    305        lazy.DownloadsViewableInternally.register();
    306 
    307        break;
    308      case "app-startup": {
    309        this._earlyBlankFirstPaint(subject);
    310        // The "taskbar-tab" flag and its param will be handled in
    311        // TaskbarTabCmd.sys.mjs
    312        BrowserInitState.isTaskbarTab =
    313          subject.findFlag("taskbar-tab", false) != -1;
    314        BrowserInitState.isLaunchOnLogin = subject.handleFlag(
    315          "os-autostart",
    316          false
    317        );
    318        if (AppConstants.platform == "win") {
    319          lazy.StartupOSIntegration.checkForLaunchOnLogin();
    320        }
    321        break;
    322      }
    323    }
    324  },
    325 
    326  // initialization (called on application startup)
    327  _init: function BG__init() {
    328    let os = Services.obs;
    329    [
    330      "notifications-open-settings",
    331      "final-ui-startup",
    332      "browser-delayed-startup-finished",
    333      "sessionstore-windows-restored",
    334      "browser:purge-session-history",
    335      "quit-application-requested",
    336      "quit-application-granted",
    337      "session-save",
    338      "places-init-complete",
    339      "handle-xul-text-link",
    340      "profile-before-change",
    341      "keyword-search",
    342      "restart-in-safe-mode",
    343      "xpi-signature-changed",
    344      "handlersvc-store-initialized",
    345    ].forEach(topic => os.addObserver(this, topic, true));
    346    if (OBSERVE_LASTWINDOW_CLOSE_TOPICS) {
    347      os.addObserver(this, "browser-lastwindow-close-requested", true);
    348      os.addObserver(this, "browser-lastwindow-close-granted", true);
    349    }
    350 
    351    lazy.DesktopActorRegistry.init();
    352  },
    353 
    354  // cleanup (called on application shutdown)
    355  _dispose: function BG__dispose() {
    356    // newtab component is disabled. tor-browser#43886
    357 
    358    if (this._lateTasksIdleObserver) {
    359      this._userIdleService.removeIdleObserver(
    360        this._lateTasksIdleObserver,
    361        LATE_TASKS_IDLE_TIME_SEC
    362      );
    363      delete this._lateTasksIdleObserver;
    364    }
    365    if (this._gmpInstallManager) {
    366      this._gmpInstallManager.uninit();
    367      delete this._gmpInstallManager;
    368    }
    369  },
    370 
    371  // runs on startup, before the first command line handler is invoked
    372  // (i.e. before the first window is opened)
    373  _beforeUIStartup: function BG__beforeUIStartup() {
    374    lazy.SessionStartup.init();
    375 
    376    // check if we're in safe mode
    377    if (Services.appinfo.inSafeMode) {
    378      Services.ww.openWindow(
    379        null,
    380        "chrome://browser/content/safeMode.xhtml",
    381        "_blank",
    382        "chrome,centerscreen,modal,resizable=no",
    383        null
    384      );
    385    }
    386 
    387    // apply distribution customizations
    388    lazy.DistributionManagement.applyCustomizations();
    389 
    390    // handle any UI migration
    391    this._migrateUI();
    392    lazy.ProfileDataUpgrader.upgradeBB(this._isNewProfile);
    393    lazy.ProfileDataUpgrader.upgradeTB(this._isNewProfile);
    394 
    395    if (!Services.prefs.prefHasUserValue(PREF_PDFJS_ISDEFAULT_CACHE_STATE)) {
    396      lazy.PdfJs.checkIsDefault(this._isNewProfile);
    397    }
    398 
    399    if (!AppConstants.NIGHTLY_BUILD && this._isNewProfile) {
    400      lazy.FormAutofillUtils.setOSAuthEnabled(false);
    401      lazy.LoginHelper.setOSAuthEnabled(false);
    402    }
    403 
    404    lazy.BrowserUtils.callModulesFromCategory({
    405      categoryName: "browser-before-ui-startup",
    406    });
    407 
    408    Services.obs.notifyObservers(null, "browser-ui-startup-complete");
    409  },
    410 
    411  _checkForOldBuildUpdates() {
    412    // check for update if our build is old
    413    if (
    414      AppConstants.MOZ_UPDATER &&
    415      Services.prefs.getBoolPref("app.update.checkInstallTime")
    416    ) {
    417      let buildID = Services.appinfo.appBuildID;
    418      let today = new Date().getTime();
    419      /* eslint-disable no-multi-spaces */
    420      let buildDate = new Date(
    421        buildID.slice(0, 4), // year
    422        buildID.slice(4, 6) - 1, // months are zero-based.
    423        buildID.slice(6, 8), // day
    424        buildID.slice(8, 10), // hour
    425        buildID.slice(10, 12), // min
    426        buildID.slice(12, 14)
    427      ) // ms
    428        .getTime();
    429      /* eslint-enable no-multi-spaces */
    430 
    431      const millisecondsIn24Hours = 86400000;
    432      let acceptableAge =
    433        Services.prefs.getIntPref("app.update.checkInstallTime.days") *
    434        millisecondsIn24Hours;
    435 
    436      if (buildDate + acceptableAge < today) {
    437        // This is asynchronous, but just kick it off rather than waiting.
    438        Cc["@mozilla.org/updates/update-service;1"]
    439          .getService(Ci.nsIApplicationUpdateService)
    440          .checkForBackgroundUpdates();
    441      }
    442    }
    443  },
    444 
    445  async _onSafeModeRestart(window) {
    446    // prompt the user to confirm
    447    let productName = lazy.gBrandBundle.GetStringFromName("brandShortName");
    448    let strings = lazy.gBrowserBundle;
    449    let promptTitle = strings.formatStringFromName(
    450      "troubleshootModeRestartPromptTitle",
    451      [productName]
    452    );
    453    let promptMessage = strings.GetStringFromName(
    454      "troubleshootModeRestartPromptMessage"
    455    );
    456    let restartText = strings.GetStringFromName(
    457      "troubleshootModeRestartButton"
    458    );
    459    let buttonFlags =
    460      Services.prompt.BUTTON_POS_0 * Services.prompt.BUTTON_TITLE_IS_STRING +
    461      Services.prompt.BUTTON_POS_1 * Services.prompt.BUTTON_TITLE_CANCEL +
    462      Services.prompt.BUTTON_POS_0_DEFAULT;
    463 
    464    let rv = await Services.prompt.asyncConfirmEx(
    465      window.browsingContext,
    466      Ci.nsIPrompt.MODAL_TYPE_INTERNAL_WINDOW,
    467      promptTitle,
    468      promptMessage,
    469      buttonFlags,
    470      restartText,
    471      null,
    472      null,
    473      null,
    474      {}
    475    );
    476    if (rv.get("buttonNumClicked") != 0) {
    477      return;
    478    }
    479 
    480    let cancelQuit = Cc["@mozilla.org/supports-PRBool;1"].createInstance(
    481      Ci.nsISupportsPRBool
    482    );
    483    Services.obs.notifyObservers(
    484      cancelQuit,
    485      "quit-application-requested",
    486      "restart"
    487    );
    488 
    489    if (!cancelQuit.data) {
    490      Services.startup.restartInSafeMode(Ci.nsIAppStartup.eAttemptQuit);
    491    }
    492  },
    493 
    494  /**
    495   * Show a notification bar offering a reset.
    496   *
    497   * @param reason
    498   *        String of either "unused" or "uninstall", specifying the reason
    499   *        why a profile reset is offered.
    500   */
    501  _resetProfileNotification(reason) {
    502    let win = lazy.BrowserWindowTracker.getTopWindow({
    503      allowFromInactiveWorkspace: true,
    504    });
    505    if (!win) {
    506      return;
    507    }
    508 
    509    const { ResetProfile } = ChromeUtils.importESModule(
    510      "resource://gre/modules/ResetProfile.sys.mjs"
    511    );
    512    if (!ResetProfile.resetSupported()) {
    513      return;
    514    }
    515 
    516    let productName = lazy.gBrandBundle.GetStringFromName("brandShortName");
    517    let resetBundle = Services.strings.createBundle(
    518      "chrome://global/locale/resetProfile.properties"
    519    );
    520 
    521    let message;
    522    if (reason == "unused") {
    523      message = resetBundle.formatStringFromName("resetUnusedProfile.message", [
    524        productName,
    525      ]);
    526    } else if (reason == "uninstall") {
    527      message = resetBundle.formatStringFromName("resetUninstalled.message", [
    528        productName,
    529      ]);
    530    } else {
    531      throw new Error(
    532        `Unknown reason (${reason}) given to _resetProfileNotification.`
    533      );
    534    }
    535    let buttons = [
    536      {
    537        label: resetBundle.formatStringFromName(
    538          "refreshProfile.resetButton.label",
    539          [productName]
    540        ),
    541        accessKey: resetBundle.GetStringFromName(
    542          "refreshProfile.resetButton.accesskey"
    543        ),
    544        callback() {
    545          ResetProfile.openConfirmationDialog(win);
    546        },
    547      },
    548    ];
    549 
    550    win.gNotificationBox.appendNotification(
    551      "reset-profile-notification",
    552      {
    553        label: message,
    554        image: "chrome://global/skin/icons/question-64.png",
    555        priority: win.gNotificationBox.PRIORITY_INFO_LOW,
    556      },
    557      buttons
    558    );
    559  },
    560 
    561  _notifyUnsignedAddonsDisabled() {
    562    let win = lazy.BrowserWindowTracker.getTopWindow({
    563      allowFromInactiveWorkspace: true,
    564    });
    565    if (!win) {
    566      return;
    567    }
    568 
    569    let message = win.gNavigatorBundle.getString(
    570      "unsignedAddonsDisabled.message"
    571    );
    572    let buttons = [
    573      {
    574        label: win.gNavigatorBundle.getString(
    575          "unsignedAddonsDisabled.learnMore.label"
    576        ),
    577        accessKey: win.gNavigatorBundle.getString(
    578          "unsignedAddonsDisabled.learnMore.accesskey"
    579        ),
    580        callback() {
    581          win.BrowserAddonUI.openAddonsMgr(
    582            "addons://list/extension?unsigned=true"
    583          );
    584        },
    585      },
    586    ];
    587 
    588    win.gNotificationBox.appendNotification(
    589      "unsigned-addons-disabled",
    590      {
    591        label: message,
    592        priority: win.gNotificationBox.PRIORITY_WARNING_MEDIUM,
    593      },
    594      buttons
    595    );
    596  },
    597 
    598  _earlyBlankFirstPaint(cmdLine) {
    599    let startTime = ChromeUtils.now();
    600 
    601    let shouldCreateWindow = isPrivateWindow => {
    602      if (cmdLine.findFlag("wait-for-jsdebugger", false) != -1) {
    603        return true;
    604      }
    605 
    606      if (
    607        AppConstants.platform == "macosx" ||
    608        Services.startup.wasSilentlyStarted ||
    609        !Services.prefs.getBoolPref("browser.startup.blankWindow", false)
    610      ) {
    611        return false;
    612      }
    613 
    614      // Until bug 1450626 and bug 1488384 are fixed, skip the blank window when
    615      // using a non-default theme.
    616      if (
    617        !Services.startup.showedPreXULSkeletonUI &&
    618        Services.prefs.getCharPref(
    619          "extensions.activeThemeID",
    620          "default-theme@mozilla.org"
    621        ) != "default-theme@mozilla.org"
    622      ) {
    623        return false;
    624      }
    625 
    626      // Bug 1448423: Skip the blank window if the user is resisting fingerprinting
    627      if (
    628        Services.prefs.getBoolPref(
    629          "privacy.resistFingerprinting.skipEarlyBlankFirstPaint",
    630          true
    631        ) &&
    632        ChromeUtils.shouldResistFingerprinting(
    633          "RoundWindowSize",
    634          null,
    635          isPrivateWindow ||
    636            Services.prefs.getBoolPref(
    637              "browser.privatebrowsing.autostart",
    638              false
    639            )
    640        )
    641      ) {
    642        return false;
    643      }
    644 
    645      let width = getValue("width");
    646      let height = getValue("height");
    647 
    648      // The clean profile case isn't handled yet. Return early for now.
    649      if (!width || !height) {
    650        return false;
    651      }
    652 
    653      return true;
    654    };
    655 
    656    let makeWindowPrivate =
    657      cmdLine.findFlag("private-window", false) != -1 &&
    658      lazy.StartupOSIntegration.isPrivateBrowsingAllowedInRegistry();
    659    if (!shouldCreateWindow(makeWindowPrivate)) {
    660      return;
    661    }
    662 
    663    let browserWindowFeatures =
    664      "chrome,all,dialog=no,extrachrome,menubar,resizable,scrollbars,status," +
    665      "location,toolbar,personalbar";
    666    // This needs to be set when opening the window to ensure that the AppUserModelID
    667    // is set correctly on Windows. Without it, initial launches with `-private-window`
    668    // will show up under the regular Firefox taskbar icon first, and then switch
    669    // to the Private Browsing icon shortly thereafter.
    670    if (makeWindowPrivate) {
    671      browserWindowFeatures += ",private";
    672    }
    673 
    674    // We use a null URI such that the window stays on the initial uncommitted about:blank
    675    let win = Services.ww.openWindow(
    676      null,
    677      null,
    678      null,
    679      browserWindowFeatures,
    680      null
    681    );
    682 
    683    // Hide the titlebar if the actual browser window will draw in it.
    684    let hiddenTitlebar = Services.appinfo.drawInTitlebar;
    685    if (hiddenTitlebar) {
    686      win.windowUtils.setCustomTitlebar(true);
    687    }
    688 
    689    let docElt = win.document.documentElement;
    690    docElt.setAttribute("screenX", getValue("screenX"));
    691    docElt.setAttribute("screenY", getValue("screenY"));
    692 
    693    let appWin = win.docShell.treeOwner
    694      .QueryInterface(Ci.nsIInterfaceRequestor)
    695      .getInterface(Ci.nsIAppWindow);
    696 
    697    // The sizemode="maximized" attribute needs to be set before first paint.
    698    let sizemode = getValue("sizemode");
    699    let width = getValue("width") || 500;
    700    let height = getValue("height") || 500;
    701    if (sizemode == "maximized") {
    702      docElt.setAttribute("sizemode", sizemode);
    703 
    704      // Set the size to use when the user leaves the maximized mode.
    705      // The persisted size is the outer size, but the height/width
    706      // attributes set the inner size.
    707      height -= appWin.outerToInnerHeightDifferenceInCSSPixels;
    708      width -= appWin.outerToInnerWidthDifferenceInCSSPixels;
    709      docElt.setAttribute("height", height);
    710      docElt.setAttribute("width", width);
    711    } else {
    712      // Setting the size of the window in the features string instead of here
    713      // causes the window to grow by the size of the titlebar.
    714      win.resizeTo(width, height);
    715    }
    716 
    717    // Set this before showing the window so that graphics code can use it to
    718    // decide to skip some expensive code paths (eg. starting the GPU process).
    719    docElt.setAttribute("windowtype", "navigator:blank");
    720 
    721    // Show a blank window as soon as possible after start-up
    722    appWin.showInitialViewer();
    723 
    724    ChromeUtils.addProfilerMarker("earlyBlankFirstPaint", startTime);
    725    win.openTime = ChromeUtils.now();
    726 
    727    let { TelemetryTimestamps } = ChromeUtils.importESModule(
    728      "resource://gre/modules/TelemetryTimestamps.sys.mjs"
    729    );
    730    TelemetryTimestamps.add("blankWindowShown");
    731    Glean.browserTimings.startupTimeline.blankWindowShown.set(
    732      Services.telemetry.msSinceProcessStart()
    733    );
    734 
    735    function getValue(attr) {
    736      return Services.xulStore.getValue(
    737        AppConstants.BROWSER_CHROME_URL,
    738        "main-window",
    739        attr
    740      );
    741    }
    742  },
    743 
    744  _firstWindowTelemetry(aWindow) {
    745    let scaling = aWindow.devicePixelRatio * 100;
    746    Glean.gfxDisplay.scaling.accumulateSingleSample(scaling);
    747  },
    748 
    749  // the first browser window has finished initializing
    750  _onFirstWindowLoaded: function BG__onFirstWindowLoaded(aWindow) {
    751    // A channel for "remote troubleshooting" code...
    752    let channel = new lazy.WebChannel(
    753      "remote-troubleshooting",
    754      "remote-troubleshooting"
    755    );
    756    channel.listen((id, data, target) => {
    757      if (data.command == "request") {
    758        let { Troubleshoot } = ChromeUtils.importESModule(
    759          "resource://gre/modules/Troubleshoot.sys.mjs"
    760        );
    761        Troubleshoot.snapshot().then(snapshotData => {
    762          // for privacy we remove crash IDs and all preferences (but bug 1091944
    763          // exists to expose prefs once we are confident of privacy implications)
    764          delete snapshotData.crashes;
    765          delete snapshotData.modifiedPreferences;
    766          delete snapshotData.printingPreferences;
    767          channel.send(snapshotData, target);
    768        });
    769      }
    770    });
    771 
    772    this._maybeOfferProfileReset();
    773 
    774    this._checkForOldBuildUpdates();
    775 
    776    // Check if Sync is configured
    777    if (Services.prefs.prefHasUserValue("services.sync.username")) {
    778      lazy.WeaveService.init();
    779    }
    780 
    781    lazy.BrowserUtils.callModulesFromCategory(
    782      {
    783        categoryName: "browser-first-window-ready",
    784        profilerMarker: "browserFirstWindowReady",
    785      },
    786      aWindow
    787    );
    788 
    789    this._firstWindowTelemetry(aWindow);
    790  },
    791 
    792  _maybeOfferProfileReset() {
    793    // Offer to reset a user's profile if it hasn't been used for 60 days.
    794    const OFFER_PROFILE_RESET_INTERVAL_MS = 60 * 24 * 60 * 60 * 1000;
    795    let lastUse = Services.appinfo.replacedLockTime;
    796    let disableResetPrompt = Services.prefs.getBoolPref(
    797      "browser.disableResetPrompt",
    798      false
    799    );
    800 
    801    // Also check prefs.js last modified timestamp as a backstop.
    802    // This helps for cases where the lock file checks don't work,
    803    // e.g. NFS or because the previous time Firefox ran, it ran
    804    // for a very long time. See bug 1054947 and related bugs.
    805    lastUse = Math.max(
    806      lastUse,
    807      Services.prefs.userPrefsFileLastModifiedAtStartup
    808    );
    809 
    810    if (
    811      !disableResetPrompt &&
    812      lastUse &&
    813      Date.now() - lastUse >= OFFER_PROFILE_RESET_INTERVAL_MS
    814    ) {
    815      this._resetProfileNotification("unused");
    816    } else if (AppConstants.platform == "win" && !disableResetPrompt) {
    817      // Check if we were just re-installed and offer Firefox Reset
    818      let updateChannel;
    819      try {
    820        updateChannel = ChromeUtils.importESModule(
    821          "resource://gre/modules/UpdateUtils.sys.mjs"
    822        ).UpdateUtils.UpdateChannel;
    823      } catch (ex) {}
    824      if (updateChannel) {
    825        let uninstalledValue = lazy.WindowsRegistry.readRegKey(
    826          Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER,
    827          "Software\\Mozilla\\Firefox",
    828          `Uninstalled-${updateChannel}`
    829        );
    830        let removalSuccessful = lazy.WindowsRegistry.removeRegKey(
    831          Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER,
    832          "Software\\Mozilla\\Firefox",
    833          `Uninstalled-${updateChannel}`
    834        );
    835        if (removalSuccessful && uninstalledValue == "True") {
    836          this._resetProfileNotification("uninstall");
    837        }
    838      }
    839    }
    840  },
    841 
    842  /**
    843   * Application shutdown handler.
    844   *
    845   * If you need new code to be called on shutdown, please use
    846   * the category manager browser-quit-application-granted category
    847   * instead of adding new manual code to this function.
    848   */
    849  _onQuitApplicationGranted() {
    850    function failureHandler(ex) {
    851      if (Cu.isInAutomation) {
    852        // This usually happens after the test harness is done collecting
    853        // test errors, thus we can't easily add a failure to it. The only
    854        // noticeable solution we have is crashing.
    855        Cc["@mozilla.org/xpcom/debug;1"]
    856          .getService(Ci.nsIDebug2)
    857          .abort(ex.filename, ex.lineNumber);
    858      }
    859    }
    860 
    861    lazy.BrowserUtils.callModulesFromCategory({
    862      categoryName: "browser-quit-application-granted",
    863      failureHandler,
    864    });
    865 
    866    let tasks = [
    867      // This pref must be set here because SessionStore will use its value
    868      // on quit-application.
    869      () => this._setPrefToSaveSession(),
    870 
    871      // Call trackStartupCrashEnd here in case the delayed call on startup hasn't
    872      // yet occurred (see trackStartupCrashEnd caller in browser.js).
    873      () => Services.startup.trackStartupCrashEnd(),
    874 
    875      () => {
    876        // bug 1839426 - The FOG service needs to be instantiated reliably so it
    877        // can perform at-shutdown tasks later in shutdown.
    878        Services.fog;
    879      },
    880    ];
    881 
    882    for (let task of tasks) {
    883      try {
    884        task();
    885      } catch (ex) {
    886        console.error(`Error during quit-application-granted: ${ex}`);
    887        failureHandler(ex);
    888      }
    889    }
    890  },
    891 
    892  _monitorWebcompatReporterPref() {
    893    const PREF = "extensions.webcompat-reporter.enabled";
    894    const ID = "webcompat-reporter@mozilla.org";
    895    Services.prefs.addObserver(PREF, async () => {
    896      let addon = await lazy.AddonManager.getAddonByID(ID);
    897      if (!addon) {
    898        return;
    899      }
    900      let enabled = Services.prefs.getBoolPref(PREF, false);
    901      if (enabled && !addon.isActive) {
    902        await addon.enable({ allowSystemAddons: true });
    903      } else if (!enabled && addon.isActive) {
    904        await addon.disable({ allowSystemAddons: true });
    905      }
    906    });
    907  },
    908 
    909  // All initial windows have opened.
    910  _onWindowsRestored: function BG__onWindowsRestored() {
    911    if (this._windowsWereRestored) {
    912      return;
    913    }
    914    this._windowsWereRestored = true;
    915 
    916    lazy.BrowserUsageTelemetry.init();
    917    lazy.SearchSERPTelemetry.init();
    918 
    919    lazy.Interactions.init();
    920    lazy.PageDataService.init();
    921    lazy.ExtensionsUI.init();
    922 
    923    let signingRequired;
    924    if (AppConstants.MOZ_REQUIRE_SIGNING) {
    925      signingRequired = true;
    926    } else {
    927      signingRequired = Services.prefs.getBoolPref(
    928        "xpinstall.signatures.required"
    929      );
    930    }
    931 
    932    if (signingRequired) {
    933      let disabledAddons = lazy.AddonManager.getStartupChanges(
    934        lazy.AddonManager.STARTUP_CHANGE_DISABLED
    935      );
    936      lazy.AddonManager.getAddonsByIDs(disabledAddons).then(addons => {
    937        for (let addon of addons) {
    938          if (addon.signedState <= lazy.AddonManager.SIGNEDSTATE_MISSING) {
    939            this._notifyUnsignedAddonsDisabled();
    940            break;
    941          }
    942        }
    943      });
    944    }
    945 
    946    if (AppConstants.MOZ_CRASHREPORTER) {
    947      lazy.UnsubmittedCrashHandler.init();
    948      lazy.UnsubmittedCrashHandler.scheduleCheckForUnsubmittedCrashReports();
    949    }
    950 
    951    if (AppConstants.ASAN_REPORTER) {
    952      var { AsanReporter } = ChromeUtils.importESModule(
    953        "resource://gre/modules/AsanReporter.sys.mjs"
    954      );
    955      AsanReporter.init();
    956    }
    957 
    958    lazy.Sanitizer.onStartup();
    959    lazy.SessionWindowUI.maybeShowRestoreSessionInfoBar();
    960    this._scheduleStartupIdleTasks();
    961    this._lateTasksIdleObserver = (idleService, topic) => {
    962      if (topic == "idle") {
    963        idleService.removeIdleObserver(
    964          this._lateTasksIdleObserver,
    965          LATE_TASKS_IDLE_TIME_SEC
    966        );
    967        delete this._lateTasksIdleObserver;
    968        this._scheduleBestEffortUserIdleTasks();
    969      }
    970    };
    971    this._userIdleService.addIdleObserver(
    972      this._lateTasksIdleObserver,
    973      LATE_TASKS_IDLE_TIME_SEC
    974    );
    975 
    976    this._monitorWebcompatReporterPref();
    977 
    978    // Loading the MigrationUtils module does the work of registering the
    979    // migration wizard JSWindowActor pair. In case nothing else has done
    980    // this yet, load the MigrationUtils so that the wizard is ready to be
    981    // used.
    982    lazy.MigrationUtils;
    983  },
    984 
    985  /**
    986   * Use this function as an entry point to schedule tasks that
    987   * need to run only once after startup, and can be scheduled
    988   * by using an idle callback.
    989   *
    990   * The functions scheduled here will fire from idle callbacks
    991   * once every window has finished being restored by session
    992   * restore, and it's guaranteed that they will run before
    993   * the equivalent per-window idle tasks
    994   * (from _schedulePerWindowIdleTasks in browser.js).
    995   *
    996   * If you have something that can wait even further than the
    997   * per-window initialization, and is okay with not being run in some
    998   * sessions, please schedule them using
    999   * _scheduleBestEffortUserIdleTasks.
   1000   * Don't be fooled by thinking that the use of the timeout parameter
   1001   * will delay your function: it will just ensure that it potentially
   1002   * happens _earlier_ than expected (when the timeout limit has been reached),
   1003   * but it will not make it happen later (and out of order) compared
   1004   * to the other ones scheduled together.
   1005   */
   1006  _scheduleStartupIdleTasks() {
   1007    function runIdleTasks(idleTasks) {
   1008      for (let task of idleTasks) {
   1009        if ("condition" in task && !task.condition) {
   1010          continue;
   1011        }
   1012 
   1013        ChromeUtils.idleDispatch(
   1014          async () => {
   1015            if (!Services.startup.shuttingDown) {
   1016              let startTime = ChromeUtils.now();
   1017              try {
   1018                await task.task();
   1019              } catch (ex) {
   1020                console.error(ex);
   1021              } finally {
   1022                ChromeUtils.addProfilerMarker(
   1023                  "startupIdleTask",
   1024                  startTime,
   1025                  task.name
   1026                );
   1027              }
   1028            }
   1029          },
   1030          task.timeout ? { timeout: task.timeout } : undefined
   1031        );
   1032      }
   1033    }
   1034 
   1035    // Note: unless you need a timeout, please do not add new tasks here, and
   1036    // instead use the category manager. You can do this in a manifest file in
   1037    // the component that needs to run code, or in BrowserComponents.manifest
   1038    // in this folder. The callModulesFromCategory call below will call them.
   1039    const earlyTasks = [
   1040      // It's important that SafeBrowsing is initialized reasonably
   1041      // early, so we use a maximum timeout for it.
   1042      {
   1043        name: "SafeBrowsing.init",
   1044        task: () => {
   1045          lazy.SafeBrowsing.init();
   1046        },
   1047        timeout: 5000,
   1048      },
   1049 
   1050      {
   1051        name: "ContextualIdentityService.load",
   1052        task: async () => {
   1053          await lazy.ContextualIdentityService.load();
   1054          lazy.Discovery.update();
   1055        },
   1056      },
   1057    ];
   1058 
   1059    runIdleTasks(earlyTasks);
   1060 
   1061    lazy.BrowserUtils.callModulesFromCategory({
   1062      categoryName: "browser-idle-startup",
   1063      profilerMarker: "startupIdleTask",
   1064      idleDispatch: true,
   1065    });
   1066 
   1067    const lateTasks = [
   1068      // Begin listening for incoming push messages.
   1069      {
   1070        name: "PushService.ensureReady",
   1071        task: () => {
   1072          try {
   1073            lazy.PushService.wrappedJSObject.ensureReady();
   1074          } catch (ex) {
   1075            // NS_ERROR_NOT_AVAILABLE will get thrown for the PushService
   1076            // getter if the PushService is disabled.
   1077            if (ex.result != Cr.NS_ERROR_NOT_AVAILABLE) {
   1078              throw ex;
   1079            }
   1080          }
   1081        },
   1082      },
   1083 
   1084      // Load the Login Manager data from disk off the main thread, some time
   1085      // after startup.  If the data is required before this runs, for example
   1086      // because a restored page contains a password field, it will be loaded on
   1087      // the main thread, and this initialization request will be ignored.
   1088      {
   1089        name: "Services.logins",
   1090        task: () => {
   1091          try {
   1092            Services.logins;
   1093          } catch (ex) {
   1094            console.error(ex);
   1095          }
   1096        },
   1097        timeout: 3000,
   1098      },
   1099 
   1100      // Add breach alerts pref observer reasonably early so the pref flip works
   1101      {
   1102        name: "_addBreachAlertsPrefObserver",
   1103        task: () => {
   1104          this._addBreachAlertsPrefObserver();
   1105        },
   1106      },
   1107 
   1108      {
   1109        name: "BrowserGlue._maybeShowDefaultBrowserPrompt",
   1110        task: () => {
   1111          this._maybeShowDefaultBrowserPrompt();
   1112        },
   1113      },
   1114 
   1115      {
   1116        name: "BrowserGlue._monitorScreenshotsPref",
   1117        task: () => {
   1118          lazy.ScreenshotsUtils.monitorScreenshotsPref();
   1119        },
   1120      },
   1121 
   1122      {
   1123        name: "trackStartupCrashEndSetTimeout",
   1124        task: () => {
   1125          lazy.setTimeout(function () {
   1126            Services.tm.idleDispatchToMainThread(
   1127              Services.startup.trackStartupCrashEnd
   1128            );
   1129          }, STARTUP_CRASHES_END_DELAY_MS);
   1130        },
   1131      },
   1132 
   1133      {
   1134        name: "handlerService.asyncInit",
   1135        task: () => {
   1136          let handlerService = Cc[
   1137            "@mozilla.org/uriloader/handler-service;1"
   1138          ].getService(Ci.nsIHandlerService);
   1139          handlerService.asyncInit();
   1140        },
   1141      },
   1142 
   1143      {
   1144        name: "webProtocolHandlerService.asyncInit",
   1145        task: () => {
   1146          lazy.WebProtocolHandlerRegistrar.prototype.init(true);
   1147        },
   1148      },
   1149 
   1150      // Run TRR performance measurements for DoH.
   1151      {
   1152        name: "doh-rollout.trrRacer.run",
   1153        task: () => {
   1154          let enabledPref = "doh-rollout.trrRace.enabled";
   1155          let completePref = "doh-rollout.trrRace.complete";
   1156 
   1157          if (Services.prefs.getBoolPref(enabledPref, false)) {
   1158            if (!Services.prefs.getBoolPref(completePref, false)) {
   1159              new lazy.TRRRacer().run(() => {
   1160                Services.prefs.setBoolPref(completePref, true);
   1161              });
   1162            }
   1163          } else {
   1164            Services.prefs.addObserver(enabledPref, function observer() {
   1165              if (Services.prefs.getBoolPref(enabledPref, false)) {
   1166                Services.prefs.removeObserver(enabledPref, observer);
   1167 
   1168                if (!Services.prefs.getBoolPref(completePref, false)) {
   1169                  new lazy.TRRRacer().run(() => {
   1170                    Services.prefs.setBoolPref(completePref, true);
   1171                  });
   1172                }
   1173              }
   1174            });
   1175          }
   1176        },
   1177      },
   1178 
   1179      // Add the setup button if this is the first startup
   1180      {
   1181        name: "AWToolbarButton.SetupButton",
   1182        task: async () => {
   1183          if (
   1184            // Not in automation: the button changes CUI state,
   1185            // breaking tests. Check this first, so that the module
   1186            // doesn't load if it doesn't have to.
   1187            !Cu.isInAutomation &&
   1188            lazy.AWToolbarButton.hasToolbarButtonEnabled
   1189          ) {
   1190            await lazy.AWToolbarButton.maybeAddSetupButton();
   1191          }
   1192        },
   1193      },
   1194 
   1195      {
   1196        name: "BackgroundUpdate",
   1197        condition: AppConstants.MOZ_UPDATE_AGENT && AppConstants.MOZ_UPDATER,
   1198        task: async () => {
   1199          let updateServiceStub = Cc[
   1200            "@mozilla.org/updates/update-service-stub;1"
   1201          ].getService(Ci.nsIApplicationUpdateServiceStub);
   1202          // Never in automation!
   1203          if (!updateServiceStub.updateDisabledForTesting) {
   1204            let { BackgroundUpdate } = ChromeUtils.importESModule(
   1205              "resource://gre/modules/BackgroundUpdate.sys.mjs"
   1206            );
   1207            try {
   1208              await BackgroundUpdate.scheduleFirefoxMessagingSystemTargetingSnapshotting();
   1209            } catch (e) {
   1210              console.error(
   1211                "There was an error scheduling Firefox Messaging System targeting snapshotting: ",
   1212                e
   1213              );
   1214            }
   1215            await BackgroundUpdate.maybeScheduleBackgroundUpdateTask();
   1216          }
   1217        },
   1218      },
   1219 
   1220      // Login detection service is used in fission to identify high value sites.
   1221      {
   1222        name: "LoginDetection.init",
   1223        task: () => {
   1224          let loginDetection = Cc[
   1225            "@mozilla.org/login-detection-service;1"
   1226          ].createInstance(Ci.nsILoginDetectionService);
   1227          loginDetection.init();
   1228        },
   1229      },
   1230 
   1231      // Schedule a sync (if enabled) after we've loaded
   1232      {
   1233        name: "WeaveService",
   1234        task: async () => {
   1235          if (lazy.WeaveService.enabled) {
   1236            await lazy.WeaveService.whenLoaded();
   1237            lazy.WeaveService.Weave.Service.scheduler.autoConnect();
   1238          }
   1239        },
   1240      },
   1241 
   1242      {
   1243        name: "unblock-untrusted-modules-thread",
   1244        condition: AppConstants.platform == "win",
   1245        task: () => {
   1246          Services.obs.notifyObservers(
   1247            null,
   1248            "unblock-untrusted-modules-thread"
   1249          );
   1250        },
   1251      },
   1252 
   1253      {
   1254        name: "DAPTelemetrySender.startup",
   1255        condition: AppConstants.MOZ_TELEMETRY_REPORTING,
   1256        task: async () => {
   1257          await lazy.DAPTelemetrySender.startup();
   1258          await lazy.DAPVisitCounter.startup();
   1259          await lazy.DAPIncrementality.startup();
   1260        },
   1261      },
   1262 
   1263      {
   1264        // Starts the JSOracle process for ORB JavaScript validation, if it hasn't started already.
   1265        name: "start-orb-javascript-oracle",
   1266        task: () => {
   1267          ChromeUtils.ensureJSOracleStarted();
   1268        },
   1269      },
   1270 
   1271      {
   1272        name: "BackupService initialization",
   1273        condition: Services.prefs.getBoolPref("browser.backup.enabled", false),
   1274        task: () => {
   1275          lazy.BackupService.init();
   1276        },
   1277      },
   1278 
   1279      {
   1280        name: "Init hasSSD for SystemInfo",
   1281        condition: AppConstants.platform == "win",
   1282        // Initializes diskInfo to be able to get hasSSD which is part
   1283        // of the PageLoad event. Only runs on windows, since diskInfo
   1284        // is a no-op on other platforms
   1285        task: () => Services.sysinfo.diskInfo,
   1286      },
   1287 
   1288      {
   1289        name: "browser-startup-idle-tasks-finished",
   1290        task: () => {
   1291          // Use idleDispatch a second time to run this after the per-window
   1292          // idle tasks.
   1293          ChromeUtils.idleDispatch(() => {
   1294            Services.obs.notifyObservers(
   1295              null,
   1296              "browser-startup-idle-tasks-finished"
   1297            );
   1298            BrowserInitState._resolveStartupIdleTask();
   1299          });
   1300        },
   1301      },
   1302      // Do NOT add anything after idle tasks finished.
   1303    ];
   1304 
   1305    runIdleTasks(lateTasks);
   1306  },
   1307 
   1308  /**
   1309   * Use this function as an entry point to schedule tasks that we hope
   1310   * to run once per session, at any arbitrary point in time, and which we
   1311   * are okay with sometimes not running at all.
   1312   *
   1313   * This function will be called from an idle observer. Check the value of
   1314   * LATE_TASKS_IDLE_TIME_SEC to see the current value for this idle
   1315   * observer.
   1316   *
   1317   * Note: this function may never be called if the user is never idle for the
   1318   * requisite time (LATE_TASKS_IDLE_TIME_SEC). Be certain before adding
   1319   * something here that it's okay that it never be run.
   1320   */
   1321  _scheduleBestEffortUserIdleTasks() {
   1322    const idleTasks = [
   1323      function GMPInstallManagerSimpleCheckAndInstall() {
   1324        let { GMPInstallManager } = ChromeUtils.importESModule(
   1325          "resource://gre/modules/GMPInstallManager.sys.mjs"
   1326        );
   1327        this._gmpInstallManager = new GMPInstallManager();
   1328        // We don't really care about the results, if someone is interested they
   1329        // can check the log.
   1330        this._gmpInstallManager.simpleCheckAndInstall().catch(() => {});
   1331      }.bind(this),
   1332 
   1333      function RemoteSettingsInit() {
   1334        lazy.RemoteSettings.init();
   1335        this._addBreachesSyncHandler();
   1336      }.bind(this),
   1337 
   1338      function RemoteSettingsPollChanges() {
   1339        // Support clients that use the "sync" event or "remote-settings:changes-poll-end".
   1340        lazy.RemoteSettings.pollChanges({ trigger: "timer" });
   1341      },
   1342 
   1343      function searchBackgroundChecks() {
   1344        Services.search.runBackgroundChecks();
   1345      },
   1346    ];
   1347 
   1348    for (let task of idleTasks) {
   1349      ChromeUtils.idleDispatch(async () => {
   1350        if (!Services.startup.shuttingDown) {
   1351          let startTime = ChromeUtils.now();
   1352          try {
   1353            await task();
   1354          } catch (ex) {
   1355            console.error(ex);
   1356          } finally {
   1357            ChromeUtils.addProfilerMarker(
   1358              "startupLateIdleTask",
   1359              startTime,
   1360              task.name
   1361            );
   1362          }
   1363        }
   1364      });
   1365    }
   1366 
   1367    lazy.BrowserUtils.callModulesFromCategory({
   1368      categoryName: "browser-best-effort-idle-startup",
   1369      idleDispatch: true,
   1370      profilerMarker: "startupLateIdleTask",
   1371    });
   1372  },
   1373 
   1374  _addBreachesSyncHandler() {
   1375    if (
   1376      Services.prefs.getBoolPref(
   1377        "signon.management.page.breach-alerts.enabled",
   1378        false
   1379      )
   1380    ) {
   1381      lazy
   1382        .RemoteSettings(lazy.LoginBreaches.REMOTE_SETTINGS_COLLECTION)
   1383        .on("sync", async event => {
   1384          await lazy.LoginBreaches.update(event.data.current);
   1385        });
   1386    }
   1387  },
   1388 
   1389  _addBreachAlertsPrefObserver() {
   1390    const BREACH_ALERTS_PREF = "signon.management.page.breach-alerts.enabled";
   1391    const clearVulnerablePasswordsIfBreachAlertsDisabled = async function () {
   1392      if (!Services.prefs.getBoolPref(BREACH_ALERTS_PREF)) {
   1393        await lazy.LoginBreaches.clearAllPotentiallyVulnerablePasswords();
   1394      }
   1395    };
   1396    clearVulnerablePasswordsIfBreachAlertsDisabled();
   1397    Services.prefs.addObserver(
   1398      BREACH_ALERTS_PREF,
   1399      clearVulnerablePasswordsIfBreachAlertsDisabled
   1400    );
   1401  },
   1402 
   1403  _quitSource: "unknown",
   1404  _registerQuitSource(source) {
   1405    this._quitSource = source;
   1406  },
   1407 
   1408  _onQuitRequest: function BG__onQuitRequest(aCancelQuit, aQuitType) {
   1409    // If user has already dismissed quit request, then do nothing
   1410    if (aCancelQuit instanceof Ci.nsISupportsPRBool && aCancelQuit.data) {
   1411      return;
   1412    }
   1413 
   1414    // There are several cases where we won't show a dialog here:
   1415    // 1. There is only 1 tab open in 1 window
   1416    // 2. browser.warnOnQuit == false
   1417    // 3. The browser is currently in Private Browsing mode
   1418    // 4. The browser will be restarted.
   1419    // 5. The user has automatic session restore enabled and
   1420    //    browser.sessionstore.warnOnQuit is not set to true.
   1421    // 6. The user doesn't have automatic session restore enabled
   1422    //    and browser.tabs.warnOnClose is not set to true.
   1423    //
   1424    // Otherwise, we will show the "closing multiple tabs" dialog.
   1425    //
   1426    // aQuitType == "lastwindow" is overloaded. "lastwindow" is used to indicate
   1427    // "the last window is closing but we're not quitting (a non-browser window is open)"
   1428    // and also "we're quitting by closing the last window".
   1429 
   1430    if (aQuitType == "restart" || aQuitType == "os-restart") {
   1431      return;
   1432    }
   1433 
   1434    // browser.warnOnQuit is a hidden global boolean to override all quit prompts.
   1435    if (!Services.prefs.getBoolPref("browser.warnOnQuit")) {
   1436      return;
   1437    }
   1438 
   1439    let windowcount = 0;
   1440    let pagecount = 0;
   1441    for (let win of lazy.BrowserWindowTracker.orderedWindows) {
   1442      if (win.closed) {
   1443        continue;
   1444      }
   1445      windowcount++;
   1446      let tabbrowser = win.gBrowser;
   1447      if (tabbrowser) {
   1448        pagecount += tabbrowser.visibleTabs.length - tabbrowser.pinnedTabCount;
   1449      }
   1450    }
   1451 
   1452    // No windows open so no need for a warning.
   1453    if (!windowcount) {
   1454      return;
   1455    }
   1456 
   1457    // browser.warnOnQuitShortcut is checked when quitting using the shortcut key.
   1458    // The warning will appear even when only one window/tab is open. For other
   1459    // methods of quitting, the warning only appears when there is more than one
   1460    // window or tab open.
   1461    let shouldWarnForShortcut =
   1462      this._quitSource == "shortcut" &&
   1463      Services.prefs.getBoolPref("browser.warnOnQuitShortcut");
   1464    let shouldWarnForTabs =
   1465      pagecount >= 2 && Services.prefs.getBoolPref("browser.tabs.warnOnClose");
   1466    if (!shouldWarnForTabs && !shouldWarnForShortcut) {
   1467      return;
   1468    }
   1469 
   1470    if (!aQuitType) {
   1471      aQuitType = "quit";
   1472    }
   1473 
   1474    let win = lazy.BrowserWindowTracker.getTopWindow({
   1475      allowFromInactiveWorkspace: true,
   1476    });
   1477 
   1478    // Our prompt for quitting is most important, so replace others.
   1479    win.gDialogBox.replaceDialogIfOpen();
   1480 
   1481    let titleId = {
   1482      id: "tabbrowser-confirm-close-tabs-title",
   1483      args: { tabCount: pagecount },
   1484    };
   1485    let quitButtonLabelId = "tabbrowser-confirm-close-tabs-button";
   1486    let closeTabButtonLabelId = "tabbrowser-confirm-close-tab-only-button";
   1487 
   1488    let showCloseCurrentTabOption = false;
   1489    if (windowcount > 1) {
   1490      // More than 1 window. Compose our own message based on whether
   1491      // the shortcut warning is on or not.
   1492      if (shouldWarnForShortcut) {
   1493        showCloseCurrentTabOption = true;
   1494        titleId = "tabbrowser-confirm-close-warn-shortcut-title";
   1495        quitButtonLabelId =
   1496          "tabbrowser-confirm-close-windows-warn-shortcut-button";
   1497      } else {
   1498        titleId = {
   1499          id: "tabbrowser-confirm-close-windows-title",
   1500          args: { windowCount: windowcount },
   1501        };
   1502        quitButtonLabelId = "tabbrowser-confirm-close-windows-button";
   1503      }
   1504    } else if (shouldWarnForShortcut) {
   1505      if (win.gBrowser.visibleTabs.length > 1) {
   1506        showCloseCurrentTabOption = true;
   1507        titleId = "tabbrowser-confirm-close-warn-shortcut-title";
   1508        quitButtonLabelId = "tabbrowser-confirm-close-tabs-with-key-button";
   1509      } else {
   1510        titleId = "tabbrowser-confirm-close-tabs-with-key-title";
   1511        quitButtonLabelId = "tabbrowser-confirm-close-tabs-with-key-button";
   1512      }
   1513    }
   1514 
   1515    // The checkbox label is different depending on whether the shortcut
   1516    // was used to quit or not.
   1517    let checkboxLabelId;
   1518    if (shouldWarnForShortcut) {
   1519      const quitKeyElement = win.document.getElementById("key_quitApplication");
   1520      const quitKey = lazy.ShortcutUtils.prettifyShortcut(quitKeyElement);
   1521      checkboxLabelId = {
   1522        id: "tabbrowser-ask-close-tabs-with-key-checkbox",
   1523        args: { quitKey },
   1524      };
   1525    } else {
   1526      checkboxLabelId = "tabbrowser-ask-close-tabs-checkbox";
   1527    }
   1528 
   1529    const [title, quitButtonLabel, checkboxLabel] =
   1530      win.gBrowser.tabLocalization.formatMessagesSync([
   1531        titleId,
   1532        quitButtonLabelId,
   1533        checkboxLabelId,
   1534      ]);
   1535 
   1536    // Only format the "close current tab" message if needed
   1537    let closeTabButtonLabel;
   1538    if (showCloseCurrentTabOption) {
   1539      [closeTabButtonLabel] = win.gBrowser.tabLocalization.formatMessagesSync([
   1540        closeTabButtonLabelId,
   1541      ]);
   1542    }
   1543 
   1544    let warnOnClose = { value: true };
   1545 
   1546    let flags;
   1547    if (showCloseCurrentTabOption) {
   1548      // Adds buttons for quit (BUTTON_POS_0), cancel (BUTTON_POS_1), and close current tab (BUTTON_POS_2).
   1549      // Also sets a flag to reorder dialog buttons so that cancel is reordered on Unix platforms.
   1550      flags =
   1551        (Services.prompt.BUTTON_TITLE_IS_STRING * Services.prompt.BUTTON_POS_0 +
   1552          Services.prompt.BUTTON_TITLE_CANCEL * Services.prompt.BUTTON_POS_1 +
   1553          Services.prompt.BUTTON_TITLE_IS_STRING *
   1554            Services.prompt.BUTTON_POS_2) |
   1555        Services.prompt.BUTTON_POS_1_IS_SECONDARY;
   1556      Services.prompt.BUTTON_TITLE_CANCEL * Services.prompt.BUTTON_POS_1;
   1557    } else {
   1558      // Adds quit and cancel buttons
   1559      flags =
   1560        Services.prompt.BUTTON_TITLE_IS_STRING * Services.prompt.BUTTON_POS_0 +
   1561        Services.prompt.BUTTON_TITLE_CANCEL * Services.prompt.BUTTON_POS_1;
   1562    }
   1563 
   1564    // buttonPressed will be 0 for close all, 1 for cancel (don't close/quit), 2 for close current tab
   1565    let buttonPressed = Services.prompt.confirmEx(
   1566      win,
   1567      title.value,
   1568      null,
   1569      flags,
   1570      quitButtonLabel.value,
   1571      null,
   1572      showCloseCurrentTabOption ? closeTabButtonLabel.value : null,
   1573      checkboxLabel.value,
   1574      warnOnClose
   1575    );
   1576 
   1577    // If the user has unticked the box, and has confirmed closing, stop showing
   1578    // the warning.
   1579    if (buttonPressed == 0 && !warnOnClose.value) {
   1580      if (shouldWarnForShortcut) {
   1581        Services.prefs.setBoolPref("browser.warnOnQuitShortcut", false);
   1582      } else {
   1583        Services.prefs.setBoolPref("browser.tabs.warnOnClose", false);
   1584      }
   1585    }
   1586 
   1587    // Close the current tab if user selected BUTTON_POS_2
   1588    if (buttonPressed === 2) {
   1589      win.gBrowser.removeTab(win.gBrowser.selectedTab);
   1590    }
   1591 
   1592    this._quitSource = "unknown";
   1593 
   1594    aCancelQuit.data = buttonPressed != 0;
   1595  },
   1596 
   1597  _migrateUI() {
   1598    // Use an increasing number to keep track of the current state of the user's
   1599    // profile, so we can move data around as needed as the browser evolves.
   1600    // Completely unrelated to the current Firefox release number.
   1601    const APP_DATA_VERSION = 163;
   1602    const PREF = "browser.migration.version";
   1603 
   1604    let profileDataVersion = Services.prefs.getIntPref(PREF, -1);
   1605    this._isNewProfile = profileDataVersion == -1;
   1606 
   1607    if (this._isNewProfile) {
   1608      // This is a new profile, nothing to upgrade.
   1609      Services.prefs.setIntPref(PREF, APP_DATA_VERSION);
   1610    } else if (profileDataVersion < APP_DATA_VERSION) {
   1611      lazy.ProfileDataUpgrader.upgrade(profileDataVersion, APP_DATA_VERSION);
   1612    }
   1613  },
   1614 
   1615  async _showUpgradeDialog() {
   1616    const data = await lazy.OnboardingMessageProvider.getUpgradeMessage();
   1617    const { gBrowser } = lazy.BrowserWindowTracker.getTopWindow({
   1618      allowFromInactiveWorkspace: true,
   1619    });
   1620 
   1621    // We'll be adding a new tab open the tab-modal dialog in.
   1622    let tab;
   1623 
   1624    const upgradeTabsProgressListener = {
   1625      onLocationChange(aBrowser) {
   1626        if (aBrowser === tab.linkedBrowser) {
   1627          lazy.setTimeout(() => {
   1628            // We're now far enough along in the load that we no longer have to
   1629            // worry about a call to onLocationChange triggering SubDialog.abort,
   1630            // so display the dialog
   1631            const config = {
   1632              type: "SHOW_SPOTLIGHT",
   1633              data,
   1634            };
   1635            lazy.SpecialMessageActions.handleAction(config, tab.linkedBrowser);
   1636 
   1637            gBrowser.removeTabsProgressListener(upgradeTabsProgressListener);
   1638          }, 0);
   1639        }
   1640      },
   1641    };
   1642 
   1643    // Make sure we're ready to show the dialog once onLocationChange gets
   1644    // called.
   1645    gBrowser.addTabsProgressListener(upgradeTabsProgressListener);
   1646 
   1647    tab = gBrowser.addTrustedTab("about:home", {
   1648      relatedToCurrent: true,
   1649    });
   1650 
   1651    gBrowser.selectedTab = tab;
   1652  },
   1653 
   1654  _showSetToDefaultSpotlight(message, browser) {
   1655    const config = {
   1656      type: "SHOW_SPOTLIGHT",
   1657      data: message,
   1658    };
   1659 
   1660    try {
   1661      lazy.SpecialMessageActions.handleAction(config, browser);
   1662    } catch (e) {
   1663      console.error("Couldn't render spotlight", message, e);
   1664    }
   1665  },
   1666 
   1667  async _maybeShowDefaultBrowserPrompt() {
   1668    // Ensuring the user is notified arranges the following ordering.  Highest
   1669    // priority is datareporting policy modal, if present.  Second highest
   1670    // priority is the upgrade dialog, which can include a "primary browser"
   1671    // request and is limited in various ways, e.g., major upgrades.
   1672    await lazy.TelemetryReportingPolicy.ensureUserIsNotified();
   1673 
   1674    const dialogVersion = 106;
   1675    const dialogVersionPref = "browser.startup.upgradeDialog.version";
   1676    const dialogReason = await (async () => {
   1677      if (!lazy.BrowserHandler.majorUpgrade) {
   1678        return "not-major";
   1679      }
   1680      const lastVersion = Services.prefs.getIntPref(dialogVersionPref, 0);
   1681      if (lastVersion > dialogVersion) {
   1682        return "newer-shown";
   1683      }
   1684      if (lastVersion === dialogVersion) {
   1685        return "already-shown";
   1686      }
   1687 
   1688      // Check the default branch as enterprise policies can set prefs there.
   1689      const defaultPrefs = Services.prefs.getDefaultBranch("");
   1690      if (!defaultPrefs.getBoolPref("browser.aboutwelcome.enabled", true)) {
   1691        return "no-welcome";
   1692      }
   1693      if (!Services.policies.isAllowed("postUpdateCustomPage")) {
   1694        return "disallow-postUpdate";
   1695      }
   1696 
   1697      const showUpgradeDialog =
   1698        lazy.NimbusFeatures.upgradeDialog.getVariable("enabled");
   1699 
   1700      return showUpgradeDialog ? "" : "disabled";
   1701    })();
   1702 
   1703    // Record why the dialog is showing or not.
   1704    Glean.upgradeDialog.triggerReason.record({
   1705      value: dialogReason || "satisfied",
   1706    });
   1707 
   1708    // Show the upgrade dialog if allowed and remember the version.
   1709    if (!dialogReason) {
   1710      Services.prefs.setIntPref(dialogVersionPref, dialogVersion);
   1711      this._showUpgradeDialog();
   1712      return;
   1713    }
   1714 
   1715    const willPrompt = await lazy.DefaultBrowserCheck.willCheckDefaultBrowser(
   1716      /* isStartupCheck */ true
   1717    );
   1718    if (willPrompt) {
   1719      let win = lazy.BrowserWindowTracker.getTopWindow({
   1720        allowFromInactiveWorkspace: true,
   1721      });
   1722      let setToDefaultFeature = lazy.NimbusFeatures.setToDefaultPrompt;
   1723 
   1724      // Send exposure telemetry if user will see default prompt or experimental
   1725      // message
   1726      await setToDefaultFeature.ready();
   1727      await setToDefaultFeature.recordExposureEvent();
   1728 
   1729      const { showSpotlightPrompt, message } =
   1730        setToDefaultFeature.getAllVariables();
   1731 
   1732      if (showSpotlightPrompt && message) {
   1733        // Show experimental message
   1734        this._showSetToDefaultSpotlight(message, win.gBrowser.selectedBrowser);
   1735        return;
   1736      }
   1737 
   1738      // Intentionally don't await the returned user's response promise.
   1739      lazy.DefaultBrowserCheck.prompt(win);
   1740    }
   1741 
   1742    await lazy.ASRouter.waitForInitialized;
   1743    await lazy.ASRouter.sendTriggerMessage({
   1744      browser: lazy.BrowserWindowTracker.getTopWindow({
   1745        allowFromInactiveWorkspace: true,
   1746      })?.gBrowser.selectedBrowser,
   1747      // triggerId and triggerContext
   1748      id: "defaultBrowserCheck",
   1749      context: { willShowDefaultPrompt: willPrompt, source: "startup" },
   1750    });
   1751  },
   1752 
   1753  /**
   1754   * Open preferences even if there are no open windows.
   1755   */
   1756  _openPreferences(...args) {
   1757    let chromeWindow = lazy.BrowserWindowTracker.getTopWindow({
   1758      allowFromInactiveWorkspace: true,
   1759    });
   1760    if (chromeWindow) {
   1761      chromeWindow.openPreferences(...args);
   1762      return;
   1763    }
   1764 
   1765    if (AppConstants.platform == "macosx") {
   1766      Services.appShell.hiddenDOMWindow.openPreferences(...args);
   1767    }
   1768  },
   1769 
   1770  QueryInterface: ChromeUtils.generateQI([
   1771    "nsIObserver",
   1772    "nsISupportsWeakReference",
   1773  ]),
   1774 };