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 };