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 5var EXPORTED_SYMBOLS = [ 6 "AboutHomeStartupCache", 7 "BrowserGlue", 8 "ContentPermissionPrompt", 9 "DefaultBrowserCheck", 10]; 11 12const { XPCOMUtils } = ChromeUtils.import( 13 "resource://gre/modules/XPCOMUtils.jsm" 14); 15const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm"); 16const { AppConstants } = ChromeUtils.import( 17 "resource://gre/modules/AppConstants.jsm" 18); 19 20XPCOMUtils.defineLazyModuleGetters(this, { 21 AboutNewTab: "resource:///modules/AboutNewTab.jsm", 22 ActorManagerParent: "resource://gre/modules/ActorManagerParent.jsm", 23 AddonManager: "resource://gre/modules/AddonManager.jsm", 24 AppMenuNotifications: "resource://gre/modules/AppMenuNotifications.jsm", 25 ASRouterDefaultConfig: 26 "resource://activity-stream/lib/ASRouterDefaultConfig.jsm", 27 ASRouterNewTabHook: "resource://activity-stream/lib/ASRouterNewTabHook.jsm", 28 ASRouter: "resource://activity-stream/lib/ASRouter.jsm", 29 AsyncShutdown: "resource://gre/modules/AsyncShutdown.jsm", 30 BackgroundUpdate: "resource://gre/modules/BackgroundUpdate.jsm", 31 Blocklist: "resource://gre/modules/Blocklist.jsm", 32 BookmarkHTMLUtils: "resource://gre/modules/BookmarkHTMLUtils.jsm", 33 BookmarkJSONUtils: "resource://gre/modules/BookmarkJSONUtils.jsm", 34 BrowserSearchTelemetry: "resource:///modules/BrowserSearchTelemetry.jsm", 35 BrowserUsageTelemetry: "resource:///modules/BrowserUsageTelemetry.jsm", 36 BrowserUIUtils: "resource:///modules/BrowserUIUtils.jsm", 37 BrowserWindowTracker: "resource:///modules/BrowserWindowTracker.jsm", 38 BuiltInThemes: "resource:///modules/BuiltInThemes.jsm", 39 ContextualIdentityService: 40 "resource://gre/modules/ContextualIdentityService.jsm", 41 Corroborate: "resource://gre/modules/Corroborate.jsm", 42 DeferredTask: "resource://gre/modules/DeferredTask.jsm", 43 Discovery: "resource:///modules/Discovery.jsm", 44 DoHController: "resource:///modules/DoHController.jsm", 45 DownloadsViewableInternally: 46 "resource:///modules/DownloadsViewableInternally.jsm", 47 E10SUtils: "resource://gre/modules/E10SUtils.jsm", 48 ExtensionsUI: "resource:///modules/ExtensionsUI.jsm", 49 FeatureGate: "resource://featuregates/FeatureGate.jsm", 50 FxAccounts: "resource://gre/modules/FxAccounts.jsm", 51 HomePage: "resource:///modules/HomePage.jsm", 52 Integration: "resource://gre/modules/Integration.jsm", 53 Interactions: "resource:///modules/Interactions.jsm", 54 Log: "resource://gre/modules/Log.jsm", 55 LoginBreaches: "resource:///modules/LoginBreaches.jsm", 56 PageDataService: "resource:///modules/pagedata/PageDataService.jsm", 57 NetUtil: "resource://gre/modules/NetUtil.jsm", 58 NewTabUtils: "resource://gre/modules/NewTabUtils.jsm", 59 NimbusFeatures: "resource://nimbus/ExperimentAPI.jsm", 60 Normandy: "resource://normandy/Normandy.jsm", 61 OsEnvironment: "resource://gre/modules/OsEnvironment.jsm", 62 PageActions: "resource:///modules/PageActions.jsm", 63 PageThumbs: "resource://gre/modules/PageThumbs.jsm", 64 PdfJs: "resource://pdf.js/PdfJs.jsm", 65 PermissionUI: "resource:///modules/PermissionUI.jsm", 66 PlacesBackups: "resource://gre/modules/PlacesBackups.jsm", 67 PlacesDBUtils: "resource://gre/modules/PlacesDBUtils.jsm", 68 PlacesUIUtils: "resource:///modules/PlacesUIUtils.jsm", 69 PlacesUtils: "resource://gre/modules/PlacesUtils.jsm", 70 PluralForm: "resource://gre/modules/PluralForm.jsm", 71 PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.jsm", 72 ProcessHangMonitor: "resource:///modules/ProcessHangMonitor.jsm", 73 PublicSuffixList: "resource://gre/modules/netwerk-dns/PublicSuffixList.jsm", 74 RemoteSettings: "resource://services-settings/remote-settings.js", 75 RemoteSecuritySettings: 76 "resource://gre/modules/psm/RemoteSecuritySettings.jsm", 77 RFPHelper: "resource://gre/modules/RFPHelper.jsm", 78 SafeBrowsing: "resource://gre/modules/SafeBrowsing.jsm", 79 Sanitizer: "resource:///modules/Sanitizer.jsm", 80 SaveToPocket: "chrome://pocket/content/SaveToPocket.jsm", 81 ScreenshotsUtils: "resource:///modules/ScreenshotsUtils.jsm", 82 SearchSERPTelemetry: "resource:///modules/SearchSERPTelemetry.jsm", 83 SessionStartup: "resource:///modules/sessionstore/SessionStartup.jsm", 84 SessionStore: "resource:///modules/sessionstore/SessionStore.jsm", 85 ShellService: "resource:///modules/ShellService.jsm", 86 ShortcutUtils: "resource://gre/modules/ShortcutUtils.jsm", 87 SnapshotMonitor: "resource:///modules/SnapshotMonitor.jsm", 88 TabCrashHandler: "resource:///modules/ContentCrashHandlers.jsm", 89 TabUnloader: "resource:///modules/TabUnloader.jsm", 90 TelemetryUtils: "resource://gre/modules/TelemetryUtils.jsm", 91 TRRRacer: "resource:///modules/TRRPerformance.jsm", 92 UIState: "resource://services-sync/UIState.jsm", 93 UrlbarQuickSuggest: "resource:///modules/UrlbarQuickSuggest.jsm", 94 UrlbarPrefs: "resource:///modules/UrlbarPrefs.jsm", 95 WebChannel: "resource://gre/modules/WebChannel.jsm", 96 WindowsRegistry: "resource://gre/modules/WindowsRegistry.jsm", 97}); 98 99// eslint-disable-next-line no-unused-vars 100XPCOMUtils.defineLazyModuleGetters(this, { 101 AboutLoginsParent: "resource:///modules/AboutLoginsParent.jsm", 102 PluginManager: "resource:///actors/PluginParent.jsm", 103}); 104 105// Modules requiring an initialization method call. 106let initializedModules = {}; 107[ 108 [ 109 "ContentPrefServiceParent", 110 "resource://gre/modules/ContentPrefServiceParent.jsm", 111 "alwaysInit", 112 ], 113 ["UpdateListener", "resource://gre/modules/UpdateListener.jsm", "init"], 114].forEach(([name, resource, init]) => { 115 XPCOMUtils.defineLazyGetter(this, name, () => { 116 ChromeUtils.import(resource, initializedModules); 117 initializedModules[name][init](); 118 return initializedModules[name]; 119 }); 120}); 121 122XPCOMUtils.defineLazyServiceGetters(this, { 123 BrowserHandler: ["@mozilla.org/browser/clh;1", "nsIBrowserHandler"], 124 PushService: ["@mozilla.org/push/Service;1", "nsIPushService"], 125}); 126 127const PREF_PDFJS_ISDEFAULT_CACHE_STATE = "pdfjs.enabledCache.state"; 128const PREF_DFPI_ENABLED_BY_DEFAULT = 129 "privacy.restrict3rdpartystorage.rollout.enabledByDefault"; 130 131/** 132 * Fission-compatible JSProcess implementations. 133 * Each actor options object takes the form of a ProcessActorOptions dictionary. 134 * Detailed documentation of these options is in dom/docs/ipc/jsactors.rst, 135 * available at https://firefox-source-docs.mozilla.org/dom/ipc/jsactors.html 136 */ 137let JSPROCESSACTORS = { 138 // Miscellaneous stuff that needs to be initialized per process. 139 BrowserProcess: { 140 child: { 141 moduleURI: "resource:///actors/BrowserProcessChild.jsm", 142 observers: [ 143 // WebRTC related notifications. They are here to avoid loading WebRTC 144 // components when not needed. 145 "getUserMedia:request", 146 "recording-device-stopped", 147 "PeerConnection:request", 148 "recording-device-events", 149 "recording-window-ended", 150 ], 151 }, 152 }, 153 154 RefreshBlockerObserver: { 155 child: { 156 moduleURI: "resource:///actors/RefreshBlockerChild.jsm", 157 observers: [ 158 "webnavigation-create", 159 "chrome-webnavigation-create", 160 "webnavigation-destroy", 161 "chrome-webnavigation-destroy", 162 ], 163 }, 164 165 enablePreference: "accessibility.blockautorefresh", 166 onPreferenceChanged: (prefName, prevValue, isEnabled) => { 167 BrowserWindowTracker.orderedWindows.forEach(win => { 168 for (let browser of win.gBrowser.browsers) { 169 try { 170 browser.sendMessageToActor( 171 "PreferenceChanged", 172 { isEnabled }, 173 "RefreshBlocker", 174 "all" 175 ); 176 } catch (ex) {} 177 } 178 }); 179 }, 180 }, 181}; 182 183/** 184 * Fission-compatible JSWindowActor implementations. 185 * Detailed documentation of these options is in dom/docs/ipc/jsactors.rst, 186 * available at https://firefox-source-docs.mozilla.org/dom/ipc/jsactors.html 187 */ 188let JSWINDOWACTORS = { 189 AboutLogins: { 190 parent: { 191 moduleURI: "resource:///actors/AboutLoginsParent.jsm", 192 }, 193 child: { 194 moduleURI: "resource:///actors/AboutLoginsChild.jsm", 195 events: { 196 AboutLoginsCopyLoginDetail: { wantUntrusted: true }, 197 AboutLoginsCreateLogin: { wantUntrusted: true }, 198 AboutLoginsDeleteLogin: { wantUntrusted: true }, 199 AboutLoginsDismissBreachAlert: { wantUntrusted: true }, 200 AboutLoginsImportFromBrowser: { wantUntrusted: true }, 201 AboutLoginsImportFromFile: { wantUntrusted: true }, 202 AboutLoginsImportReportInit: { wantUntrusted: true }, 203 AboutLoginsImportReportReady: { wantUntrusted: true }, 204 AboutLoginsInit: { wantUntrusted: true }, 205 AboutLoginsGetHelp: { wantUntrusted: true }, 206 AboutLoginsOpenPreferences: { wantUntrusted: true }, 207 AboutLoginsOpenSite: { wantUntrusted: true }, 208 AboutLoginsRecordTelemetryEvent: { wantUntrusted: true }, 209 AboutLoginsRemoveAllLogins: { wantUntrusted: true }, 210 AboutLoginsSortChanged: { wantUntrusted: true }, 211 AboutLoginsSyncEnable: { wantUntrusted: true }, 212 AboutLoginsSyncOptions: { wantUntrusted: true }, 213 AboutLoginsUpdateLogin: { wantUntrusted: true }, 214 AboutLoginsExportPasswords: { wantUntrusted: true }, 215 }, 216 }, 217 matches: ["about:logins", "about:logins?*", "about:loginsimportreport"], 218 }, 219 220 AboutNewTab: { 221 parent: { 222 moduleURI: "resource:///actors/AboutNewTabParent.jsm", 223 }, 224 child: { 225 moduleURI: "resource:///actors/AboutNewTabChild.jsm", 226 events: { 227 DOMContentLoaded: {}, 228 pageshow: {}, 229 visibilitychange: {}, 230 }, 231 }, 232 // The wildcard on about:newtab is for the ?endpoint query parameter 233 // that is used for snippets debugging. The wildcard for about:home 234 // is similar, and also allows for falling back to loading the 235 // about:home document dynamically if an attempt is made to load 236 // about:home?jscache from the AboutHomeStartupCache as a top-level 237 // load. 238 matches: ["about:home*", "about:welcome", "about:newtab*"], 239 remoteTypes: ["privilegedabout"], 240 }, 241 242 AboutPlugins: { 243 parent: { 244 moduleURI: "resource:///actors/AboutPluginsParent.jsm", 245 }, 246 child: { 247 moduleURI: "resource:///actors/AboutPluginsChild.jsm", 248 249 events: { 250 DOMDocElementInserted: { capture: true }, 251 }, 252 }, 253 254 matches: ["about:plugins"], 255 }, 256 257 AboutPocket: { 258 parent: { 259 moduleURI: "resource:///actors/AboutPocketParent.jsm", 260 }, 261 child: { 262 moduleURI: "resource:///actors/AboutPocketChild.jsm", 263 264 events: { 265 DOMDocElementInserted: { capture: true }, 266 }, 267 }, 268 269 matches: [ 270 "about:pocket-saved*", 271 "about:pocket-signup*", 272 "about:pocket-home*", 273 "about:pocket-style-guide*", 274 ], 275 }, 276 277 AboutPrivateBrowsing: { 278 parent: { 279 moduleURI: "resource:///actors/AboutPrivateBrowsingParent.jsm", 280 }, 281 child: { 282 moduleURI: "resource:///actors/AboutPrivateBrowsingChild.jsm", 283 284 events: { 285 DOMDocElementInserted: { capture: true }, 286 }, 287 }, 288 289 matches: ["about:privatebrowsing"], 290 }, 291 292 AboutProtections: { 293 parent: { 294 moduleURI: "resource:///actors/AboutProtectionsParent.jsm", 295 }, 296 child: { 297 moduleURI: "resource:///actors/AboutProtectionsChild.jsm", 298 299 events: { 300 DOMDocElementInserted: { capture: true }, 301 }, 302 }, 303 304 matches: ["about:protections", "about:protections?*"], 305 }, 306 307 AboutReader: { 308 parent: { 309 moduleURI: "resource:///actors/AboutReaderParent.jsm", 310 }, 311 child: { 312 moduleURI: "resource:///actors/AboutReaderChild.jsm", 313 events: { 314 DOMContentLoaded: {}, 315 pageshow: { mozSystemGroup: true }, 316 // Don't try to create the actor if only the pagehide event fires. 317 // This can happen with the initial about:blank documents. 318 pagehide: { mozSystemGroup: true, createActor: false }, 319 }, 320 }, 321 messageManagerGroups: ["browsers"], 322 }, 323 324 AboutTabCrashed: { 325 parent: { 326 moduleURI: "resource:///actors/AboutTabCrashedParent.jsm", 327 }, 328 child: { 329 moduleURI: "resource:///actors/AboutTabCrashedChild.jsm", 330 331 events: { 332 DOMDocElementInserted: { capture: true }, 333 }, 334 }, 335 336 matches: ["about:tabcrashed*"], 337 }, 338 339 AboutWelcome: { 340 parent: { 341 moduleURI: "resource:///actors/AboutWelcomeParent.jsm", 342 }, 343 child: { 344 moduleURI: "resource:///actors/AboutWelcomeChild.jsm", 345 events: { 346 // This is added so the actor instantiates immediately and makes 347 // methods available to the page js on load. 348 DOMDocElementInserted: {}, 349 }, 350 }, 351 matches: ["about:welcome"], 352 remoteTypes: ["privilegedabout"], 353 354 // See Bug 1618306 355 // Remove this preference check when we turn on separate about:welcome for all users. 356 enablePreference: "browser.aboutwelcome.enabled", 357 }, 358 359 BlockedSite: { 360 parent: { 361 moduleURI: "resource:///actors/BlockedSiteParent.jsm", 362 }, 363 child: { 364 moduleURI: "resource:///actors/BlockedSiteChild.jsm", 365 events: { 366 AboutBlockedLoaded: { wantUntrusted: true }, 367 click: {}, 368 }, 369 }, 370 matches: ["about:blocked?*"], 371 allFrames: true, 372 }, 373 374 BrowserTab: { 375 parent: { 376 moduleURI: "resource:///actors/BrowserTabParent.jsm", 377 }, 378 child: { 379 moduleURI: "resource:///actors/BrowserTabChild.jsm", 380 381 events: { 382 DOMDocElementInserted: {}, 383 MozAfterPaint: {}, 384 }, 385 }, 386 387 messageManagerGroups: ["browsers"], 388 }, 389 390 ClickHandler: { 391 parent: { 392 moduleURI: "resource:///actors/ClickHandlerParent.jsm", 393 }, 394 child: { 395 moduleURI: "resource:///actors/ClickHandlerChild.jsm", 396 events: { 397 chromelinkclick: { capture: true, mozSystemGroup: true }, 398 }, 399 }, 400 401 allFrames: true, 402 }, 403 404 /* Note: this uses the same JSMs as ClickHandler, but because it 405 * relies on "normal" click events anywhere on the page (not just 406 * links) and is expensive, and only does something for the 407 * small group of people who have the feature enabled, it is its 408 * own actor which is only registered if the pref is enabled. 409 */ 410 MiddleMousePasteHandler: { 411 parent: { 412 moduleURI: "resource:///actors/ClickHandlerParent.jsm", 413 }, 414 child: { 415 moduleURI: "resource:///actors/ClickHandlerChild.jsm", 416 events: { 417 auxclick: { capture: true, mozSystemGroup: true }, 418 }, 419 }, 420 enablePreference: "middlemouse.contentLoadURL", 421 422 allFrames: true, 423 }, 424 425 ContentSearch: { 426 parent: { 427 moduleURI: "resource:///actors/ContentSearchParent.jsm", 428 }, 429 child: { 430 moduleURI: "resource:///actors/ContentSearchChild.jsm", 431 events: { 432 ContentSearchClient: { capture: true, wantUntrusted: true }, 433 }, 434 }, 435 matches: [ 436 "about:home", 437 "about:welcome", 438 "about:newtab", 439 "about:privatebrowsing", 440 "about:test-about-content-search-ui", 441 ], 442 remoteTypes: ["privilegedabout"], 443 }, 444 445 ContextMenu: { 446 parent: { 447 moduleURI: "resource:///actors/ContextMenuParent.jsm", 448 }, 449 450 child: { 451 moduleURI: "resource:///actors/ContextMenuChild.jsm", 452 events: { 453 contextmenu: { mozSystemGroup: true }, 454 }, 455 }, 456 457 allFrames: true, 458 }, 459 460 DecoderDoctor: { 461 parent: { 462 moduleURI: "resource:///actors/DecoderDoctorParent.jsm", 463 }, 464 465 child: { 466 moduleURI: "resource:///actors/DecoderDoctorChild.jsm", 467 observers: ["decoder-doctor-notification"], 468 }, 469 470 messageManagerGroups: ["browsers"], 471 allFrames: true, 472 }, 473 474 DOMFullscreen: { 475 parent: { 476 moduleURI: "resource:///actors/DOMFullscreenParent.jsm", 477 }, 478 479 child: { 480 moduleURI: "resource:///actors/DOMFullscreenChild.jsm", 481 events: { 482 "MozDOMFullscreen:Request": {}, 483 "MozDOMFullscreen:Entered": {}, 484 "MozDOMFullscreen:NewOrigin": {}, 485 "MozDOMFullscreen:Exit": {}, 486 "MozDOMFullscreen:Exited": {}, 487 }, 488 }, 489 490 messageManagerGroups: ["browsers"], 491 allFrames: true, 492 }, 493 494 EncryptedMedia: { 495 parent: { 496 moduleURI: "resource:///actors/EncryptedMediaParent.jsm", 497 }, 498 499 child: { 500 moduleURI: "resource:///actors/EncryptedMediaChild.jsm", 501 observers: ["mediakeys-request"], 502 }, 503 504 messageManagerGroups: ["browsers"], 505 allFrames: true, 506 }, 507 508 FormValidation: { 509 parent: { 510 moduleURI: "resource:///actors/FormValidationParent.jsm", 511 }, 512 513 child: { 514 moduleURI: "resource:///actors/FormValidationChild.jsm", 515 events: { 516 MozInvalidForm: {}, 517 // Listening to ‘pageshow’ event is only relevant if an invalid form 518 // popup was open, so don't create the actor when fired. 519 pageshow: { createActor: false }, 520 }, 521 }, 522 523 allFrames: true, 524 }, 525 526 LightweightTheme: { 527 child: { 528 moduleURI: "resource:///actors/LightweightThemeChild.jsm", 529 events: { 530 pageshow: { mozSystemGroup: true }, 531 DOMContentLoaded: {}, 532 }, 533 }, 534 includeChrome: true, 535 allFrames: true, 536 matches: [ 537 "about:home", 538 "about:newtab", 539 "about:welcome", 540 "chrome://browser/content/syncedtabs/sidebar.xhtml", 541 "chrome://browser/content/places/historySidebar.xhtml", 542 "chrome://browser/content/places/bookmarksSidebar.xhtml", 543 ], 544 }, 545 546 LinkHandler: { 547 parent: { 548 moduleURI: "resource:///actors/LinkHandlerParent.jsm", 549 }, 550 child: { 551 moduleURI: "resource:///actors/LinkHandlerChild.jsm", 552 events: { 553 DOMHeadElementParsed: {}, 554 DOMLinkAdded: {}, 555 DOMLinkChanged: {}, 556 pageshow: {}, 557 // The `pagehide` event is only used to clean up state which will not be 558 // present if the actor hasn't been created. 559 pagehide: { createActor: false }, 560 }, 561 }, 562 563 messageManagerGroups: ["browsers"], 564 }, 565 566 NetError: { 567 parent: { 568 moduleURI: "resource:///actors/NetErrorParent.jsm", 569 }, 570 child: { 571 moduleURI: "resource:///actors/NetErrorChild.jsm", 572 events: { 573 DOMDocElementInserted: {}, 574 click: {}, 575 }, 576 }, 577 578 matches: ["about:certerror?*", "about:neterror?*"], 579 allFrames: true, 580 }, 581 582 PageInfo: { 583 child: { 584 moduleURI: "resource:///actors/PageInfoChild.jsm", 585 }, 586 587 allFrames: true, 588 }, 589 590 PageStyle: { 591 parent: { 592 moduleURI: "resource:///actors/PageStyleParent.jsm", 593 }, 594 child: { 595 moduleURI: "resource:///actors/PageStyleChild.jsm", 596 events: { 597 pageshow: {}, 598 }, 599 }, 600 601 // Only matching web pages, as opposed to internal about:, chrome: or 602 // resource: pages. See https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Match_patterns 603 matches: ["*://*/*", "file:///*"], 604 messageManagerGroups: ["browsers"], 605 allFrames: true, 606 }, 607 608 Pdfjs: { 609 parent: { 610 moduleURI: "resource://pdf.js/PdfjsParent.jsm", 611 }, 612 child: { 613 moduleURI: "resource://pdf.js/PdfjsChild.jsm", 614 }, 615 allFrames: true, 616 }, 617 618 // GMP crash reporting 619 Plugin: { 620 parent: { 621 moduleURI: "resource:///actors/PluginParent.jsm", 622 }, 623 child: { 624 moduleURI: "resource:///actors/PluginChild.jsm", 625 events: { 626 PluginCrashed: { capture: true }, 627 }, 628 }, 629 630 allFrames: true, 631 }, 632 633 PointerLock: { 634 parent: { 635 moduleURI: "resource:///actors/PointerLockParent.jsm", 636 }, 637 child: { 638 moduleURI: "resource:///actors/PointerLockChild.jsm", 639 events: { 640 "MozDOMPointerLock:Entered": {}, 641 "MozDOMPointerLock:Exited": {}, 642 }, 643 }, 644 645 messageManagerGroups: ["browsers"], 646 allFrames: true, 647 }, 648 649 Prompt: { 650 parent: { 651 moduleURI: "resource:///actors/PromptParent.jsm", 652 }, 653 includeChrome: true, 654 allFrames: true, 655 }, 656 657 RefreshBlocker: { 658 parent: { 659 moduleURI: "resource:///actors/RefreshBlockerParent.jsm", 660 }, 661 child: { 662 moduleURI: "resource:///actors/RefreshBlockerChild.jsm", 663 }, 664 665 messageManagerGroups: ["browsers"], 666 enablePreference: "accessibility.blockautorefresh", 667 }, 668 669 ScreenshotsComponent: { 670 parent: { 671 moduleURI: "resource:///modules/ScreenshotsUtils.jsm", 672 }, 673 child: { 674 moduleURI: "resource:///actors/ScreenshotsComponentChild.jsm", 675 }, 676 enablePreference: "screenshots.browser.component.enabled", 677 }, 678 679 SearchSERPTelemetry: { 680 parent: { 681 moduleURI: "resource:///actors/SearchSERPTelemetryParent.jsm", 682 }, 683 child: { 684 moduleURI: "resource:///actors/SearchSERPTelemetryChild.jsm", 685 events: { 686 DOMContentLoaded: {}, 687 pageshow: { mozSystemGroup: true }, 688 // The 'unload' event is only used to clean up state, and should not 689 // force actor creation. 690 unload: { createActor: false }, 691 }, 692 }, 693 }, 694 695 ShieldFrame: { 696 parent: { 697 moduleURI: "resource://normandy-content/ShieldFrameParent.jsm", 698 }, 699 child: { 700 moduleURI: "resource://normandy-content/ShieldFrameChild.jsm", 701 events: { 702 pageshow: {}, 703 pagehide: {}, 704 ShieldPageEvent: { wantUntrusted: true }, 705 }, 706 }, 707 matches: ["about:studies*"], 708 }, 709 710 ASRouter: { 711 parent: { 712 moduleURI: "resource:///actors/ASRouterParent.jsm", 713 }, 714 child: { 715 moduleURI: "resource:///actors/ASRouterChild.jsm", 716 events: { 717 // This is added so the actor instantiates immediately and makes 718 // methods available to the page js on load. 719 DOMDocElementInserted: {}, 720 }, 721 }, 722 matches: [ 723 "about:home*", 724 "about:newtab*", 725 "about:welcome*", 726 "about:privatebrowsing", 727 ], 728 remoteTypes: ["privilegedabout"], 729 }, 730 731 SwitchDocumentDirection: { 732 child: { 733 moduleURI: "resource:///actors/SwitchDocumentDirectionChild.jsm", 734 }, 735 736 allFrames: true, 737 }, 738 739 Translation: { 740 parent: { 741 moduleURI: "resource:///modules/translation/TranslationParent.jsm", 742 }, 743 child: { 744 moduleURI: "resource:///modules/translation/TranslationChild.jsm", 745 events: { 746 pageshow: {}, 747 load: { mozSystemGroup: true, capture: true }, 748 }, 749 }, 750 enablePreference: "browser.translation.detectLanguage", 751 }, 752 753 UITour: { 754 parent: { 755 moduleURI: "resource:///modules/UITourParent.jsm", 756 }, 757 child: { 758 moduleURI: "resource:///modules/UITourChild.jsm", 759 events: { 760 mozUITour: { wantUntrusted: true }, 761 }, 762 }, 763 764 messageManagerGroups: ["browsers"], 765 }, 766 767 WebRTC: { 768 parent: { 769 moduleURI: "resource:///actors/WebRTCParent.jsm", 770 }, 771 child: { 772 moduleURI: "resource:///actors/WebRTCChild.jsm", 773 }, 774 775 allFrames: true, 776 }, 777}; 778 779(function earlyBlankFirstPaint() { 780 let startTime = Cu.now(); 781 if ( 782 AppConstants.platform == "macosx" || 783 Services.startup.wasSilentlyStarted || 784 !Services.prefs.getBoolPref("browser.startup.blankWindow", false) 785 ) { 786 return; 787 } 788 789 // Until bug 1450626 and bug 1488384 are fixed, skip the blank window when 790 // using a non-default theme. 791 if ( 792 !Services.startup.showedPreXULSkeletonUI && 793 Services.prefs.getCharPref( 794 "extensions.activeThemeID", 795 "default-theme@mozilla.org" 796 ) != "default-theme@mozilla.org" 797 ) { 798 return; 799 } 800 801 let store = Services.xulStore; 802 let getValue = attr => 803 store.getValue(AppConstants.BROWSER_CHROME_URL, "main-window", attr); 804 let width = getValue("width"); 805 let height = getValue("height"); 806 807 // The clean profile case isn't handled yet. Return early for now. 808 if (!width || !height) { 809 return; 810 } 811 812 let browserWindowFeatures = 813 "chrome,all,dialog=no,extrachrome,menubar,resizable,scrollbars,status," + 814 "location,toolbar,personalbar"; 815 let win = Services.ww.openWindow( 816 null, 817 "about:blank", 818 null, 819 browserWindowFeatures, 820 null 821 ); 822 823 // Hide the titlebar if the actual browser window will draw in it. 824 let hiddenTitlebar = Services.appinfo.drawInTitlebar; 825 if (hiddenTitlebar) { 826 win.windowUtils.setChromeMargin(0, 2, 2, 2); 827 } 828 829 let docElt = win.document.documentElement; 830 docElt.setAttribute("screenX", getValue("screenX")); 831 docElt.setAttribute("screenY", getValue("screenY")); 832 833 // The sizemode="maximized" attribute needs to be set before first paint. 834 let sizemode = getValue("sizemode"); 835 if (sizemode == "maximized") { 836 docElt.setAttribute("sizemode", sizemode); 837 838 // Set the size to use when the user leaves the maximized mode. 839 // The persisted size is the outer size, but the height/width 840 // attributes set the inner size. 841 let appWin = win.docShell.treeOwner 842 .QueryInterface(Ci.nsIInterfaceRequestor) 843 .getInterface(Ci.nsIAppWindow); 844 height -= appWin.outerToInnerHeightDifferenceInCSSPixels; 845 width -= appWin.outerToInnerWidthDifferenceInCSSPixels; 846 docElt.setAttribute("height", height); 847 docElt.setAttribute("width", width); 848 } else { 849 // Setting the size of the window in the features string instead of here 850 // causes the window to grow by the size of the titlebar. 851 win.resizeTo(width, height); 852 } 853 854 // Set this before showing the window so that graphics code can use it to 855 // decide to skip some expensive code paths (eg. starting the GPU process). 856 docElt.setAttribute("windowtype", "navigator:blank"); 857 858 // The window becomes visible after OnStopRequest, so make this happen now. 859 win.stop(); 860 861 ChromeUtils.addProfilerMarker("earlyBlankFirstPaint", startTime); 862 win.openTime = Cu.now(); 863 864 let { TelemetryTimestamps } = ChromeUtils.import( 865 "resource://gre/modules/TelemetryTimestamps.jsm" 866 ); 867 TelemetryTimestamps.add("blankWindowShown"); 868})(); 869 870XPCOMUtils.defineLazyGetter( 871 this, 872 "WeaveService", 873 () => Cc["@mozilla.org/weave/service;1"].getService().wrappedJSObject 874); 875 876if (AppConstants.MOZ_CRASHREPORTER) { 877 XPCOMUtils.defineLazyModuleGetters(this, { 878 UnsubmittedCrashHandler: "resource:///modules/ContentCrashHandlers.jsm", 879 }); 880} 881 882XPCOMUtils.defineLazyGetter(this, "gBrandBundle", function() { 883 return Services.strings.createBundle( 884 "chrome://branding/locale/brand.properties" 885 ); 886}); 887 888XPCOMUtils.defineLazyGetter(this, "gBrowserBundle", function() { 889 return Services.strings.createBundle( 890 "chrome://browser/locale/browser.properties" 891 ); 892}); 893 894XPCOMUtils.defineLazyGetter(this, "gTabbrowserBundle", function() { 895 return Services.strings.createBundle( 896 "chrome://browser/locale/tabbrowser.properties" 897 ); 898}); 899 900const global = this; 901 902const listeners = { 903 observers: { 904 "update-downloading": ["UpdateListener"], 905 "update-staged": ["UpdateListener"], 906 "update-downloaded": ["UpdateListener"], 907 "update-available": ["UpdateListener"], 908 "update-error": ["UpdateListener"], 909 "update-swap": ["UpdateListener"], 910 "gmp-plugin-crash": ["PluginManager"], 911 "plugin-crashed": ["PluginManager"], 912 }, 913 914 observe(subject, topic, data) { 915 for (let module of this.observers[topic]) { 916 try { 917 global[module].observe(subject, topic, data); 918 } catch (e) { 919 Cu.reportError(e); 920 } 921 } 922 }, 923 924 init() { 925 for (let observer of Object.keys(this.observers)) { 926 Services.obs.addObserver(this, observer); 927 } 928 }, 929}; 930 931// Seconds of idle before trying to create a bookmarks backup. 932const BOOKMARKS_BACKUP_IDLE_TIME_SEC = 8 * 60; 933// Minimum interval between backups. We try to not create more than one backup 934// per interval. 935const BOOKMARKS_BACKUP_MIN_INTERVAL_DAYS = 1; 936// Seconds of idle time before the late idle tasks will be scheduled. 937const LATE_TASKS_IDLE_TIME_SEC = 20; 938// Time after we stop tracking startup crashes. 939const STARTUP_CRASHES_END_DELAY_MS = 30 * 1000; 940 941/* 942 * OS X has the concept of zero-window sessions and therefore ignores the 943 * browser-lastwindow-close-* topics. 944 */ 945const OBSERVE_LASTWINDOW_CLOSE_TOPICS = AppConstants.platform != "macosx"; 946 947function BrowserGlue() { 948 XPCOMUtils.defineLazyServiceGetter( 949 this, 950 "_userIdleService", 951 "@mozilla.org/widget/useridleservice;1", 952 "nsIUserIdleService" 953 ); 954 955 XPCOMUtils.defineLazyGetter(this, "_distributionCustomizer", function() { 956 const { DistributionCustomizer } = ChromeUtils.import( 957 "resource:///modules/distribution.js" 958 ); 959 return new DistributionCustomizer(); 960 }); 961 962 XPCOMUtils.defineLazyServiceGetter( 963 this, 964 "AlertsService", 965 "@mozilla.org/alerts-service;1", 966 "nsIAlertsService" 967 ); 968 969 this._init(); 970} 971 972BrowserGlue.prototype = { 973 _saveSession: false, 974 _migrationImportsDefaultBookmarks: false, 975 _placesBrowserInitComplete: false, 976 _isNewProfile: undefined, 977 978 _setPrefToSaveSession: function BG__setPrefToSaveSession(aForce) { 979 if (!this._saveSession && !aForce) { 980 return; 981 } 982 983 if (!PrivateBrowsingUtils.permanentPrivateBrowsing) { 984 Services.prefs.setBoolPref( 985 "browser.sessionstore.resume_session_once", 986 true 987 ); 988 } 989 990 // This method can be called via [NSApplication terminate:] on Mac, which 991 // ends up causing prefs not to be flushed to disk, so we need to do that 992 // explicitly here. See bug 497652. 993 Services.prefs.savePrefFile(null); 994 }, 995 996 _setSyncAutoconnectDelay: function BG__setSyncAutoconnectDelay() { 997 // Assume that a non-zero value for services.sync.autoconnectDelay should override 998 if (Services.prefs.prefHasUserValue("services.sync.autoconnectDelay")) { 999 let prefDelay = Services.prefs.getIntPref( 1000 "services.sync.autoconnectDelay" 1001 ); 1002 1003 if (prefDelay > 0) { 1004 return; 1005 } 1006 } 1007 1008 // delays are in seconds 1009 const MAX_DELAY = 300; 1010 let delay = 3; 1011 for (let win of Services.wm.getEnumerator("navigator:browser")) { 1012 // browser windows without a gBrowser almost certainly means we are 1013 // shutting down, so instead of just ignoring that window we abort. 1014 if (win.closed || !win.gBrowser) { 1015 return; 1016 } 1017 delay += win.gBrowser.tabs.length; 1018 } 1019 delay = delay <= MAX_DELAY ? delay : MAX_DELAY; 1020 1021 const { Weave } = ChromeUtils.import("resource://services-sync/main.js"); 1022 Weave.Service.scheduler.delayedAutoConnect(delay); 1023 }, 1024 1025 // nsIObserver implementation 1026 observe: async function BG_observe(subject, topic, data) { 1027 switch (topic) { 1028 case "notifications-open-settings": 1029 this._openPreferences("privacy-permissions"); 1030 break; 1031 case "final-ui-startup": 1032 this._beforeUIStartup(); 1033 break; 1034 case "browser-delayed-startup-finished": 1035 this._onFirstWindowLoaded(subject); 1036 Services.obs.removeObserver(this, "browser-delayed-startup-finished"); 1037 break; 1038 case "sessionstore-windows-restored": 1039 this._onWindowsRestored(); 1040 break; 1041 case "browser:purge-session-history": 1042 // reset the console service's error buffer 1043 Services.console.logStringMessage(null); // clear the console (in case it's open) 1044 Services.console.reset(); 1045 break; 1046 case "restart-in-safe-mode": 1047 this._onSafeModeRestart(subject); 1048 break; 1049 case "quit-application-requested": 1050 this._onQuitRequest(subject, data); 1051 break; 1052 case "quit-application-granted": 1053 this._onQuitApplicationGranted(); 1054 break; 1055 case "browser-lastwindow-close-requested": 1056 if (OBSERVE_LASTWINDOW_CLOSE_TOPICS) { 1057 // The application is not actually quitting, but the last full browser 1058 // window is about to be closed. 1059 this._onQuitRequest(subject, "lastwindow"); 1060 } 1061 break; 1062 case "browser-lastwindow-close-granted": 1063 if (OBSERVE_LASTWINDOW_CLOSE_TOPICS) { 1064 this._setPrefToSaveSession(); 1065 } 1066 break; 1067 case "weave:service:ready": 1068 this._setSyncAutoconnectDelay(); 1069 break; 1070 case "fxaccounts:onverified": 1071 this._onThisDeviceConnected(); 1072 break; 1073 case "fxaccounts:device_connected": 1074 this._onDeviceConnected(data); 1075 break; 1076 case "fxaccounts:verify_login": 1077 this._onVerifyLoginNotification(JSON.parse(data)); 1078 break; 1079 case "fxaccounts:device_disconnected": 1080 data = JSON.parse(data); 1081 if (data.isLocalDevice) { 1082 this._onDeviceDisconnected(); 1083 } 1084 break; 1085 case "fxaccounts:commands:open-uri": 1086 this._onDisplaySyncURIs(subject); 1087 break; 1088 case "session-save": 1089 this._setPrefToSaveSession(true); 1090 subject.QueryInterface(Ci.nsISupportsPRBool); 1091 subject.data = true; 1092 break; 1093 case "places-init-complete": 1094 Services.obs.removeObserver(this, "places-init-complete"); 1095 if (!this._migrationImportsDefaultBookmarks) { 1096 this._initPlaces(false); 1097 } 1098 break; 1099 case "idle": 1100 this._backupBookmarks(); 1101 break; 1102 case "distribution-customization-complete": 1103 Services.obs.removeObserver( 1104 this, 1105 "distribution-customization-complete" 1106 ); 1107 // Customization has finished, we don't need the customizer anymore. 1108 delete this._distributionCustomizer; 1109 break; 1110 case "browser-glue-test": // used by tests 1111 if (data == "force-ui-migration") { 1112 this._migrateUI(); 1113 } else if (data == "force-distribution-customization") { 1114 this._distributionCustomizer.applyCustomizations(); 1115 // To apply distribution bookmarks use "places-init-complete". 1116 } else if (data == "test-force-places-init") { 1117 this._placesInitialized = false; 1118 this._initPlaces(false); 1119 } else if (data == "mock-alerts-service") { 1120 Object.defineProperty(this, "AlertsService", { 1121 value: subject.wrappedJSObject, 1122 }); 1123 } else if (data == "places-browser-init-complete") { 1124 if (this._placesBrowserInitComplete) { 1125 Services.obs.notifyObservers(null, "places-browser-init-complete"); 1126 } 1127 } else if (data == "add-breaches-sync-handler") { 1128 this._addBreachesSyncHandler(); 1129 } 1130 break; 1131 case "initial-migration-will-import-default-bookmarks": 1132 this._migrationImportsDefaultBookmarks = true; 1133 break; 1134 case "initial-migration-did-import-default-bookmarks": 1135 this._initPlaces(true); 1136 break; 1137 case "handle-xul-text-link": 1138 let linkHandled = subject.QueryInterface(Ci.nsISupportsPRBool); 1139 if (!linkHandled.data) { 1140 let win = BrowserWindowTracker.getTopWindow(); 1141 if (win) { 1142 data = JSON.parse(data); 1143 let where = win.whereToOpenLink(data); 1144 // Preserve legacy behavior of non-modifier left-clicks 1145 // opening in a new selected tab. 1146 if (where == "current") { 1147 where = "tab"; 1148 } 1149 win.openTrustedLinkIn(data.href, where); 1150 linkHandled.data = true; 1151 } 1152 } 1153 break; 1154 case "profile-before-change": 1155 // Any component depending on Places should be finalized in 1156 // _onPlacesShutdown. Any component that doesn't need to act after 1157 // the UI has gone should be finalized in _onQuitApplicationGranted. 1158 this._dispose(); 1159 break; 1160 case "keyword-search": 1161 // This notification is broadcast by the docshell when it "fixes up" a 1162 // URI that it's been asked to load into a keyword search. 1163 let engine = null; 1164 try { 1165 engine = Services.search.getEngineByName( 1166 subject.QueryInterface(Ci.nsISupportsString).data 1167 ); 1168 } catch (ex) { 1169 Cu.reportError(ex); 1170 } 1171 let win = BrowserWindowTracker.getTopWindow(); 1172 BrowserSearchTelemetry.recordSearch( 1173 win.gBrowser.selectedBrowser, 1174 engine, 1175 "urlbar" 1176 ); 1177 break; 1178 case "browser-search-engine-modified": 1179 // Ensure we cleanup the hiddenOneOffs pref when removing 1180 // an engine, and that newly added engines are visible. 1181 if (data == "engine-added" || data == "engine-removed") { 1182 let engineName = subject.QueryInterface(Ci.nsISearchEngine).name; 1183 let pref = Services.prefs.getStringPref( 1184 "browser.search.hiddenOneOffs" 1185 ); 1186 let hiddenList = pref ? pref.split(",") : []; 1187 hiddenList = hiddenList.filter(x => x !== engineName); 1188 Services.prefs.setStringPref( 1189 "browser.search.hiddenOneOffs", 1190 hiddenList.join(",") 1191 ); 1192 } 1193 break; 1194 case "flash-plugin-hang": 1195 this._handleFlashHang(); 1196 break; 1197 case "xpi-signature-changed": 1198 let disabledAddons = JSON.parse(data).disabled; 1199 let addons = await AddonManager.getAddonsByIDs(disabledAddons); 1200 if (addons.some(addon => addon)) { 1201 this._notifyUnsignedAddonsDisabled(); 1202 } 1203 break; 1204 case "sync-ui-state:update": 1205 this._updateFxaBadges(BrowserWindowTracker.getTopWindow()); 1206 break; 1207 case "handlersvc-store-initialized": 1208 // Initialize PdfJs when running in-process and remote. This only 1209 // happens once since PdfJs registers global hooks. If the PdfJs 1210 // extension is installed the init method below will be overridden 1211 // leaving initialization to the extension. 1212 // parent only: configure default prefs, set up pref observers, register 1213 // pdf content handler, and initializes parent side message manager 1214 // shim for privileged api access. 1215 PdfJs.init(this._isNewProfile); 1216 1217 // Allow certain viewable internally types to be opened from downloads. 1218 DownloadsViewableInternally.register(); 1219 1220 break; 1221 } 1222 }, 1223 1224 // initialization (called on application startup) 1225 _init: function BG__init() { 1226 let os = Services.obs; 1227 [ 1228 "notifications-open-settings", 1229 "final-ui-startup", 1230 "browser-delayed-startup-finished", 1231 "sessionstore-windows-restored", 1232 "browser:purge-session-history", 1233 "quit-application-requested", 1234 "quit-application-granted", 1235 "weave:service:ready", 1236 "fxaccounts:onverified", 1237 "fxaccounts:device_connected", 1238 "fxaccounts:verify_login", 1239 "fxaccounts:device_disconnected", 1240 "fxaccounts:commands:open-uri", 1241 "session-save", 1242 "places-init-complete", 1243 "distribution-customization-complete", 1244 "handle-xul-text-link", 1245 "profile-before-change", 1246 "keyword-search", 1247 "browser-search-engine-modified", 1248 "restart-in-safe-mode", 1249 "flash-plugin-hang", 1250 "xpi-signature-changed", 1251 "sync-ui-state:update", 1252 "handlersvc-store-initialized", 1253 ].forEach(topic => os.addObserver(this, topic, true)); 1254 if (OBSERVE_LASTWINDOW_CLOSE_TOPICS) { 1255 os.addObserver(this, "browser-lastwindow-close-requested", true); 1256 os.addObserver(this, "browser-lastwindow-close-granted", true); 1257 } 1258 1259 ActorManagerParent.addJSProcessActors(JSPROCESSACTORS); 1260 ActorManagerParent.addJSWindowActors(JSWINDOWACTORS); 1261 1262 this._flashHangCount = 0; 1263 this._firstWindowReady = new Promise( 1264 resolve => (this._firstWindowLoaded = resolve) 1265 ); 1266 if (AppConstants.platform == "win") { 1267 JawsScreenReaderVersionCheck.init(); 1268 } 1269 }, 1270 1271 // cleanup (called on application shutdown) 1272 _dispose: function BG__dispose() { 1273 // AboutHomeStartupCache might write to the cache during 1274 // quit-application-granted, so we defer uninitialization 1275 // until here. 1276 AboutHomeStartupCache.uninit(); 1277 1278 if (this._bookmarksBackupIdleTime) { 1279 this._userIdleService.removeIdleObserver( 1280 this, 1281 this._bookmarksBackupIdleTime 1282 ); 1283 this._bookmarksBackupIdleTime = null; 1284 } 1285 if (this._lateTasksIdleObserver) { 1286 this._userIdleService.removeIdleObserver( 1287 this._lateTasksIdleObserver, 1288 LATE_TASKS_IDLE_TIME_SEC 1289 ); 1290 delete this._lateTasksIdleObserver; 1291 } 1292 if (this._gmpInstallManager) { 1293 this._gmpInstallManager.uninit(); 1294 delete this._gmpInstallManager; 1295 } 1296 1297 Services.prefs.removeObserver( 1298 "privacy.trackingprotection", 1299 this._matchCBCategory 1300 ); 1301 Services.prefs.removeObserver( 1302 "network.cookie.cookieBehavior", 1303 this._matchCBCategory 1304 ); 1305 Services.prefs.removeObserver( 1306 "network.cookie.cookieBehavior.pbmode", 1307 this._matchCBCategory 1308 ); 1309 Services.prefs.removeObserver( 1310 "network.http.referer.disallowCrossSiteRelaxingDefault", 1311 this._matchCBCategory 1312 ); 1313 Services.prefs.removeObserver( 1314 "privacy.partition.network_state.ocsp_cache", 1315 this._matchCBCategory 1316 ); 1317 Services.prefs.removeObserver( 1318 ContentBlockingCategoriesPrefs.PREF_CB_CATEGORY, 1319 this._updateCBCategory 1320 ); 1321 Services.prefs.removeObserver( 1322 "privacy.trackingprotection", 1323 this._setPrefExpectations 1324 ); 1325 Services.prefs.removeObserver( 1326 "browser.contentblocking.features.strict", 1327 this._setPrefExpectationsAndUpdate 1328 ); 1329 Services.prefs.removeObserver( 1330 PREF_DFPI_ENABLED_BY_DEFAULT, 1331 this._setDefaultCookieBehavior 1332 ); 1333 }, 1334 1335 // runs on startup, before the first command line handler is invoked 1336 // (i.e. before the first window is opened) 1337 _beforeUIStartup: function BG__beforeUIStartup() { 1338 SessionStartup.init(); 1339 1340 // check if we're in safe mode 1341 if (Services.appinfo.inSafeMode) { 1342 Services.ww.openWindow( 1343 null, 1344 "chrome://browser/content/safeMode.xhtml", 1345 "_blank", 1346 "chrome,centerscreen,modal,resizable=no", 1347 null 1348 ); 1349 } 1350 1351 // apply distribution customizations 1352 this._distributionCustomizer.applyCustomizations(); 1353 1354 // handle any UI migration 1355 this._migrateUI(); 1356 1357 if (!Services.prefs.prefHasUserValue(PREF_PDFJS_ISDEFAULT_CACHE_STATE)) { 1358 PdfJs.checkIsDefault(this._isNewProfile); 1359 } 1360 1361 listeners.init(); 1362 1363 SessionStore.init(); 1364 1365 BuiltInThemes.maybeInstallActiveBuiltInTheme(); 1366 1367 if (AppConstants.MOZ_NORMANDY) { 1368 Normandy.init(); 1369 } 1370 1371 SaveToPocket.init(); 1372 1373 AboutHomeStartupCache.init(); 1374 1375 Services.obs.notifyObservers(null, "browser-ui-startup-complete"); 1376 }, 1377 1378 _checkForOldBuildUpdates() { 1379 // check for update if our build is old 1380 if ( 1381 AppConstants.MOZ_UPDATER && 1382 Services.prefs.getBoolPref("app.update.checkInstallTime") 1383 ) { 1384 let buildID = Services.appinfo.appBuildID; 1385 let today = new Date().getTime(); 1386 /* eslint-disable no-multi-spaces */ 1387 let buildDate = new Date( 1388 buildID.slice(0, 4), // year 1389 buildID.slice(4, 6) - 1, // months are zero-based. 1390 buildID.slice(6, 8), // day 1391 buildID.slice(8, 10), // hour 1392 buildID.slice(10, 12), // min 1393 buildID.slice(12, 14) 1394 ) // ms 1395 .getTime(); 1396 /* eslint-enable no-multi-spaces */ 1397 1398 const millisecondsIn24Hours = 86400000; 1399 let acceptableAge = 1400 Services.prefs.getIntPref("app.update.checkInstallTime.days") * 1401 millisecondsIn24Hours; 1402 1403 if (buildDate + acceptableAge < today) { 1404 Cc["@mozilla.org/updates/update-service;1"] 1405 .getService(Ci.nsIApplicationUpdateService) 1406 .checkForBackgroundUpdates(); 1407 } 1408 } 1409 }, 1410 1411 async _onSafeModeRestart(window) { 1412 // prompt the user to confirm 1413 let productName = gBrandBundle.GetStringFromName("brandShortName"); 1414 let strings = gBrowserBundle; 1415 let promptTitle = strings.formatStringFromName( 1416 "troubleshootModeRestartPromptTitle", 1417 [productName] 1418 ); 1419 let promptMessage = strings.GetStringFromName( 1420 "troubleshootModeRestartPromptMessage" 1421 ); 1422 let restartText = strings.GetStringFromName( 1423 "troubleshootModeRestartButton" 1424 ); 1425 let buttonFlags = 1426 Services.prompt.BUTTON_POS_0 * Services.prompt.BUTTON_TITLE_IS_STRING + 1427 Services.prompt.BUTTON_POS_1 * Services.prompt.BUTTON_TITLE_CANCEL + 1428 Services.prompt.BUTTON_POS_0_DEFAULT; 1429 1430 let rv = await Services.prompt.asyncConfirmEx( 1431 window.browsingContext, 1432 Ci.nsIPrompt.MODAL_TYPE_INTERNAL_WINDOW, 1433 promptTitle, 1434 promptMessage, 1435 buttonFlags, 1436 restartText, 1437 null, 1438 null, 1439 null, 1440 {} 1441 ); 1442 if (rv.get("buttonNumClicked") != 0) { 1443 return; 1444 } 1445 1446 let cancelQuit = Cc["@mozilla.org/supports-PRBool;1"].createInstance( 1447 Ci.nsISupportsPRBool 1448 ); 1449 Services.obs.notifyObservers( 1450 cancelQuit, 1451 "quit-application-requested", 1452 "restart" 1453 ); 1454 1455 if (!cancelQuit.data) { 1456 Services.startup.restartInSafeMode(Ci.nsIAppStartup.eAttemptQuit); 1457 } 1458 }, 1459 1460 /** 1461 * Show a notification bar offering a reset. 1462 * 1463 * @param reason 1464 * String of either "unused" or "uninstall", specifying the reason 1465 * why a profile reset is offered. 1466 */ 1467 _resetProfileNotification(reason) { 1468 let win = BrowserWindowTracker.getTopWindow(); 1469 if (!win) { 1470 return; 1471 } 1472 1473 const { ResetProfile } = ChromeUtils.import( 1474 "resource://gre/modules/ResetProfile.jsm" 1475 ); 1476 if (!ResetProfile.resetSupported()) { 1477 return; 1478 } 1479 1480 let productName = gBrandBundle.GetStringFromName("brandShortName"); 1481 let resetBundle = Services.strings.createBundle( 1482 "chrome://global/locale/resetProfile.properties" 1483 ); 1484 1485 let message; 1486 if (reason == "unused") { 1487 message = resetBundle.formatStringFromName("resetUnusedProfile.message", [ 1488 productName, 1489 ]); 1490 } else if (reason == "uninstall") { 1491 message = resetBundle.formatStringFromName("resetUninstalled.message", [ 1492 productName, 1493 ]); 1494 } else { 1495 throw new Error( 1496 `Unknown reason (${reason}) given to _resetProfileNotification.` 1497 ); 1498 } 1499 let buttons = [ 1500 { 1501 label: resetBundle.formatStringFromName( 1502 "refreshProfile.resetButton.label", 1503 [productName] 1504 ), 1505 accessKey: resetBundle.GetStringFromName( 1506 "refreshProfile.resetButton.accesskey" 1507 ), 1508 callback() { 1509 ResetProfile.openConfirmationDialog(win); 1510 }, 1511 }, 1512 ]; 1513 1514 win.gNotificationBox.appendNotification( 1515 "reset-profile-notification", 1516 { 1517 label: message, 1518 image: "chrome://global/skin/icons/question-64.png", 1519 priority: win.gNotificationBox.PRIORITY_INFO_LOW, 1520 }, 1521 buttons 1522 ); 1523 }, 1524 1525 _notifyUnsignedAddonsDisabled() { 1526 let win = BrowserWindowTracker.getTopWindow(); 1527 if (!win) { 1528 return; 1529 } 1530 1531 let message = win.gNavigatorBundle.getString( 1532 "unsignedAddonsDisabled.message" 1533 ); 1534 let buttons = [ 1535 { 1536 label: win.gNavigatorBundle.getString( 1537 "unsignedAddonsDisabled.learnMore.label" 1538 ), 1539 accessKey: win.gNavigatorBundle.getString( 1540 "unsignedAddonsDisabled.learnMore.accesskey" 1541 ), 1542 callback() { 1543 win.BrowserOpenAddonsMgr("addons://list/extension?unsigned=true"); 1544 }, 1545 }, 1546 ]; 1547 1548 win.gNotificationBox.appendNotification( 1549 "unsigned-addons-disabled", 1550 { 1551 label: message, 1552 priority: win.gNotificationBox.PRIORITY_WARNING_MEDIUM, 1553 }, 1554 buttons 1555 ); 1556 }, 1557 1558 _firstWindowTelemetry(aWindow) { 1559 let scaling = aWindow.devicePixelRatio * 100; 1560 try { 1561 Services.telemetry.getHistogramById("DISPLAY_SCALING").add(scaling); 1562 } catch (ex) {} 1563 }, 1564 1565 _collectStartupConditionsTelemetry() { 1566 let nowSeconds = Math.round(Date.now() / 1000); 1567 // Don't include cases where we don't have the pref. This rules out the first install 1568 // as well as the first run of a build since this was introduced. These could by some 1569 // definitions be referred to as "cold" startups, but probably not since we likely 1570 // just wrote many of the files we use to disk. This way we should approximate a lower 1571 // bound to the number of cold startups rather than an upper bound. 1572 let lastCheckSeconds = Services.prefs.getIntPref( 1573 "browser.startup.lastColdStartupCheck", 1574 nowSeconds 1575 ); 1576 Services.prefs.setIntPref( 1577 "browser.startup.lastColdStartupCheck", 1578 nowSeconds 1579 ); 1580 try { 1581 let secondsSinceLastOSRestart = 1582 Services.startup.secondsSinceLastOSRestart; 1583 let isColdStartup = 1584 nowSeconds - secondsSinceLastOSRestart > lastCheckSeconds; 1585 Services.telemetry.scalarSet("startup.is_cold", isColdStartup); 1586 Services.telemetry.scalarSet( 1587 "startup.seconds_since_last_os_restart", 1588 secondsSinceLastOSRestart 1589 ); 1590 } catch (ex) { 1591 Cu.reportError(ex); 1592 } 1593 }, 1594 1595 // the first browser window has finished initializing 1596 _onFirstWindowLoaded: function BG__onFirstWindowLoaded(aWindow) { 1597 AboutNewTab.init(); 1598 1599 TabCrashHandler.init(); 1600 1601 ProcessHangMonitor.init(); 1602 1603 UrlbarPrefs.updateFirefoxSuggestScenario(); 1604 1605 // A channel for "remote troubleshooting" code... 1606 let channel = new WebChannel( 1607 "remote-troubleshooting", 1608 "remote-troubleshooting" 1609 ); 1610 channel.listen((id, data, target) => { 1611 if (data.command == "request") { 1612 let { Troubleshoot } = ChromeUtils.import( 1613 "resource://gre/modules/Troubleshoot.jsm" 1614 ); 1615 Troubleshoot.snapshot(snapshotData => { 1616 // for privacy we remove crash IDs and all preferences (but bug 1091944 1617 // exists to expose prefs once we are confident of privacy implications) 1618 delete snapshotData.crashes; 1619 delete snapshotData.modifiedPreferences; 1620 delete snapshotData.printingPreferences; 1621 channel.send(snapshotData, target); 1622 }); 1623 } 1624 }); 1625 1626 // Offer to reset a user's profile if it hasn't been used for 60 days. 1627 const OFFER_PROFILE_RESET_INTERVAL_MS = 60 * 24 * 60 * 60 * 1000; 1628 let lastUse = Services.appinfo.replacedLockTime; 1629 let disableResetPrompt = Services.prefs.getBoolPref( 1630 "browser.disableResetPrompt", 1631 false 1632 ); 1633 1634 if ( 1635 !disableResetPrompt && 1636 lastUse && 1637 Date.now() - lastUse >= OFFER_PROFILE_RESET_INTERVAL_MS 1638 ) { 1639 this._resetProfileNotification("unused"); 1640 } else if (AppConstants.platform == "win" && !disableResetPrompt) { 1641 // Check if we were just re-installed and offer Firefox Reset 1642 let updateChannel; 1643 try { 1644 updateChannel = ChromeUtils.import( 1645 "resource://gre/modules/UpdateUtils.jsm", 1646 {} 1647 ).UpdateUtils.UpdateChannel; 1648 } catch (ex) {} 1649 if (updateChannel) { 1650 let uninstalledValue = WindowsRegistry.readRegKey( 1651 Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER, 1652 "Software\\Mozilla\\Firefox", 1653 `Uninstalled-${updateChannel}` 1654 ); 1655 let removalSuccessful = WindowsRegistry.removeRegKey( 1656 Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER, 1657 "Software\\Mozilla\\Firefox", 1658 `Uninstalled-${updateChannel}` 1659 ); 1660 if (removalSuccessful && uninstalledValue == "True") { 1661 this._resetProfileNotification("uninstall"); 1662 } 1663 } 1664 } 1665 1666 this._checkForOldBuildUpdates(); 1667 1668 // Check if Sync is configured 1669 if (Services.prefs.prefHasUserValue("services.sync.username")) { 1670 WeaveService.init(); 1671 } 1672 1673 PageThumbs.init(); 1674 1675 NewTabUtils.init(); 1676 1677 Services.telemetry.setEventRecordingEnabled( 1678 "security.ui.protections", 1679 true 1680 ); 1681 1682 PageActions.init(); 1683 1684 DoHController.init(); 1685 1686 SnapshotMonitor.init(); 1687 1688 this._firstWindowTelemetry(aWindow); 1689 this._firstWindowLoaded(); 1690 1691 this._collectStartupConditionsTelemetry(); 1692 1693 // Set the default favicon size for UI views that use the page-icon protocol. 1694 PlacesUtils.favicons.setDefaultIconURIPreferredSize( 1695 16 * aWindow.devicePixelRatio 1696 ); 1697 // _setDefaultCookieBehavior needs to run before other functions that modify 1698 // privacy preferences such as _setPrefExpectationsAndUpdate and _matchCBCategory 1699 this._setDefaultCookieBehavior(); 1700 this._setPrefExpectationsAndUpdate(); 1701 this._matchCBCategory(); 1702 1703 // This observes the entire privacy.trackingprotection.* pref tree. 1704 Services.prefs.addObserver( 1705 "privacy.trackingprotection", 1706 this._matchCBCategory 1707 ); 1708 Services.prefs.addObserver( 1709 "network.cookie.cookieBehavior", 1710 this._matchCBCategory 1711 ); 1712 Services.prefs.addObserver( 1713 "network.cookie.cookieBehavior.pbmode", 1714 this._matchCBCategory 1715 ); 1716 Services.prefs.addObserver( 1717 "network.http.referer.disallowCrossSiteRelaxingDefault", 1718 this._matchCBCategory 1719 ); 1720 Services.prefs.addObserver( 1721 "privacy.partition.network_state.ocsp_cache", 1722 this._matchCBCategory 1723 ); 1724 Services.prefs.addObserver( 1725 ContentBlockingCategoriesPrefs.PREF_CB_CATEGORY, 1726 this._updateCBCategory 1727 ); 1728 Services.prefs.addObserver( 1729 "media.autoplay.default", 1730 this._updateAutoplayPref 1731 ); 1732 Services.prefs.addObserver( 1733 "privacy.trackingprotection", 1734 this._setPrefExpectations 1735 ); 1736 Services.prefs.addObserver( 1737 "browser.contentblocking.features.strict", 1738 this._setPrefExpectationsAndUpdate 1739 ); 1740 Services.prefs.addObserver( 1741 PREF_DFPI_ENABLED_BY_DEFAULT, 1742 this._setDefaultCookieBehavior 1743 ); 1744 }, 1745 1746 _updateAutoplayPref() { 1747 const blocked = Services.prefs.getIntPref("media.autoplay.default", 1); 1748 const telemetry = Services.telemetry.getHistogramById( 1749 "AUTOPLAY_DEFAULT_SETTING_CHANGE" 1750 ); 1751 const labels = { 0: "allow", 1: "blockAudible", 5: "blockAll" }; 1752 if (blocked in labels) { 1753 telemetry.add(labels[blocked]); 1754 } 1755 }, 1756 1757 // For the initial rollout of dFPI, set the default cookieBehavior based on the pref 1758 // set during onboarding when the user chooses to enable protections or not. 1759 _setDefaultCookieBehavior() { 1760 if (!Services.prefs.prefHasUserValue(PREF_DFPI_ENABLED_BY_DEFAULT)) { 1761 Services.telemetry.scalarSet("privacy.dfpi_rollout_enabledByDefault", 2); 1762 return; 1763 } 1764 let dFPIEnabled = Services.prefs.getBoolPref(PREF_DFPI_ENABLED_BY_DEFAULT); 1765 1766 let defaultPrefs = Services.prefs.getDefaultBranch(""); 1767 defaultPrefs.setIntPref( 1768 "network.cookie.cookieBehavior", 1769 dFPIEnabled 1770 ? Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN 1771 : Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER 1772 ); 1773 1774 Services.telemetry.scalarSet( 1775 "privacy.dfpi_rollout_enabledByDefault", 1776 dFPIEnabled ? 1 : 0 1777 ); 1778 1779 if (dFPIEnabled) { 1780 Services.prefs.setStringPref( 1781 "browser.search.param.google_channel_us", 1782 "tus7" 1783 ); 1784 Services.prefs.setStringPref( 1785 "browser.search.param.google_channel_row", 1786 "trow7" 1787 ); 1788 Services.prefs.setStringPref( 1789 "browser.search.param.bing_ptag", 1790 "MOZZ0000000031" 1791 ); 1792 } else { 1793 Services.prefs.setStringPref( 1794 "browser.search.param.google_channel_us", 1795 "xus7" 1796 ); 1797 Services.prefs.setStringPref( 1798 "browser.search.param.google_channel_row", 1799 "xrow7" 1800 ); 1801 Services.prefs.setStringPref( 1802 "browser.search.param.bing_ptag", 1803 "MOZZ0000000032" 1804 ); 1805 } 1806 }, 1807 1808 _setPrefExpectations() { 1809 ContentBlockingCategoriesPrefs.setPrefExpectations(); 1810 }, 1811 1812 _setPrefExpectationsAndUpdate() { 1813 ContentBlockingCategoriesPrefs.setPrefExpectations(); 1814 ContentBlockingCategoriesPrefs.updateCBCategory(); 1815 }, 1816 1817 _matchCBCategory() { 1818 ContentBlockingCategoriesPrefs.matchCBCategory(); 1819 }, 1820 1821 _updateCBCategory() { 1822 ContentBlockingCategoriesPrefs.updateCBCategory(); 1823 }, 1824 1825 _recordContentBlockingTelemetry() { 1826 Services.telemetry.setEventRecordingEnabled( 1827 "security.ui.protectionspopup", 1828 Services.prefs.getBoolPref( 1829 "security.protectionspopup.recordEventTelemetry" 1830 ) 1831 ); 1832 Services.telemetry.setEventRecordingEnabled( 1833 "security.ui.app_menu", 1834 Services.prefs.getBoolPref("security.app_menu.recordEventTelemetry") 1835 ); 1836 1837 let tpEnabled = Services.prefs.getBoolPref( 1838 "privacy.trackingprotection.enabled" 1839 ); 1840 Services.telemetry 1841 .getHistogramById("TRACKING_PROTECTION_ENABLED") 1842 .add(tpEnabled); 1843 1844 let tpPBDisabled = Services.prefs.getBoolPref( 1845 "privacy.trackingprotection.pbmode.enabled" 1846 ); 1847 Services.telemetry 1848 .getHistogramById("TRACKING_PROTECTION_PBM_DISABLED") 1849 .add(!tpPBDisabled); 1850 1851 let cookieBehavior = Services.prefs.getIntPref( 1852 "network.cookie.cookieBehavior" 1853 ); 1854 Services.telemetry.getHistogramById("COOKIE_BEHAVIOR").add(cookieBehavior); 1855 1856 let fpEnabled = Services.prefs.getBoolPref( 1857 "privacy.trackingprotection.fingerprinting.enabled" 1858 ); 1859 let cmEnabled = Services.prefs.getBoolPref( 1860 "privacy.trackingprotection.cryptomining.enabled" 1861 ); 1862 let categoryPref; 1863 switch ( 1864 Services.prefs.getStringPref("browser.contentblocking.category", null) 1865 ) { 1866 case "standard": 1867 categoryPref = 0; 1868 break; 1869 case "strict": 1870 categoryPref = 1; 1871 break; 1872 case "custom": 1873 categoryPref = 2; 1874 break; 1875 default: 1876 // Any other value is unsupported. 1877 categoryPref = 3; 1878 break; 1879 } 1880 1881 Services.telemetry.scalarSet( 1882 "contentblocking.fingerprinting_blocking_enabled", 1883 fpEnabled 1884 ); 1885 Services.telemetry.scalarSet( 1886 "contentblocking.cryptomining_blocking_enabled", 1887 cmEnabled 1888 ); 1889 Services.telemetry.scalarSet("contentblocking.category", categoryPref); 1890 }, 1891 1892 _recordDataSanitizationPrefs() { 1893 Services.telemetry.scalarSet( 1894 "datasanitization.network_cookie_lifetimePolicy", 1895 Services.prefs.getIntPref("network.cookie.lifetimePolicy") 1896 ); 1897 Services.telemetry.scalarSet( 1898 "datasanitization.privacy_sanitize_sanitizeOnShutdown", 1899 Services.prefs.getBoolPref("privacy.sanitize.sanitizeOnShutdown") 1900 ); 1901 Services.telemetry.scalarSet( 1902 "datasanitization.privacy_clearOnShutdown_cookies", 1903 Services.prefs.getBoolPref("privacy.clearOnShutdown.cookies") 1904 ); 1905 Services.telemetry.scalarSet( 1906 "datasanitization.privacy_clearOnShutdown_history", 1907 Services.prefs.getBoolPref("privacy.clearOnShutdown.history") 1908 ); 1909 Services.telemetry.scalarSet( 1910 "datasanitization.privacy_clearOnShutdown_formdata", 1911 Services.prefs.getBoolPref("privacy.clearOnShutdown.formdata") 1912 ); 1913 Services.telemetry.scalarSet( 1914 "datasanitization.privacy_clearOnShutdown_downloads", 1915 Services.prefs.getBoolPref("privacy.clearOnShutdown.downloads") 1916 ); 1917 Services.telemetry.scalarSet( 1918 "datasanitization.privacy_clearOnShutdown_cache", 1919 Services.prefs.getBoolPref("privacy.clearOnShutdown.cache") 1920 ); 1921 Services.telemetry.scalarSet( 1922 "datasanitization.privacy_clearOnShutdown_sessions", 1923 Services.prefs.getBoolPref("privacy.clearOnShutdown.sessions") 1924 ); 1925 Services.telemetry.scalarSet( 1926 "datasanitization.privacy_clearOnShutdown_offlineApps", 1927 Services.prefs.getBoolPref("privacy.clearOnShutdown.offlineApps") 1928 ); 1929 Services.telemetry.scalarSet( 1930 "datasanitization.privacy_clearOnShutdown_siteSettings", 1931 Services.prefs.getBoolPref("privacy.clearOnShutdown.siteSettings") 1932 ); 1933 Services.telemetry.scalarSet( 1934 "datasanitization.privacy_clearOnShutdown_openWindows", 1935 Services.prefs.getBoolPref("privacy.clearOnShutdown.openWindows") 1936 ); 1937 1938 let exceptions = 0; 1939 for (let permission of Services.perms.all) { 1940 // We consider just permissions set for http, https and file URLs. 1941 if ( 1942 permission.type == "cookie" && 1943 permission.capability == Ci.nsICookiePermission.ACCESS_SESSION && 1944 ["http", "https", "file"].some(scheme => 1945 permission.principal.schemeIs(scheme) 1946 ) 1947 ) { 1948 exceptions++; 1949 } 1950 } 1951 Services.telemetry.scalarSet( 1952 "datasanitization.session_permission_exceptions", 1953 exceptions 1954 ); 1955 }, 1956 1957 /** 1958 * Application shutdown handler. 1959 */ 1960 _onQuitApplicationGranted() { 1961 let tasks = [ 1962 // This pref must be set here because SessionStore will use its value 1963 // on quit-application. 1964 () => this._setPrefToSaveSession(), 1965 1966 // Call trackStartupCrashEnd here in case the delayed call on startup hasn't 1967 // yet occurred (see trackStartupCrashEnd caller in browser.js). 1968 () => Services.startup.trackStartupCrashEnd(), 1969 1970 () => { 1971 if (this._bookmarksBackupIdleTime) { 1972 this._userIdleService.removeIdleObserver( 1973 this, 1974 this._bookmarksBackupIdleTime 1975 ); 1976 this._bookmarksBackupIdleTime = null; 1977 } 1978 }, 1979 1980 () => BrowserUsageTelemetry.uninit(), 1981 () => SearchSERPTelemetry.uninit(), 1982 () => Interactions.uninit(), 1983 () => PageDataService.uninit(), 1984 () => PageThumbs.uninit(), 1985 () => NewTabUtils.uninit(), 1986 () => Normandy.uninit(), 1987 () => RFPHelper.uninit(), 1988 () => ASRouterNewTabHook.destroy(), 1989 ]; 1990 1991 tasks.push( 1992 ...Object.values(initializedModules) 1993 .filter(m => m.uninit) 1994 .map(m => () => m.uninit()) 1995 ); 1996 1997 for (let task of tasks) { 1998 try { 1999 task(); 2000 } catch (ex) { 2001 console.error(`Error during quit-application-granted: ${ex}`); 2002 if (Cu.isInAutomation) { 2003 // This usually happens after the test harness is done collecting 2004 // test errors, thus we can't easily add a failure to it. The only 2005 // noticeable solution we have is crashing. 2006 Cc["@mozilla.org/xpcom/debug;1"] 2007 .getService(Ci.nsIDebug2) 2008 .abort(ex.filename, ex.lineNumber); 2009 } 2010 } 2011 } 2012 }, 2013 2014 // Set up a listener to enable/disable the screenshots extension 2015 // based on its preference. 2016 _monitorScreenshotsPref() { 2017 const SCREENSHOTS_PREF = "extensions.screenshots.disabled"; 2018 const COMPONENT_PREF = "screenshots.browser.component.enabled"; 2019 const ID = "screenshots@mozilla.org"; 2020 const _checkScreenshotsPref = async () => { 2021 let addon = await AddonManager.getAddonByID(ID); 2022 let screenshotsDisabled = Services.prefs.getBoolPref( 2023 SCREENSHOTS_PREF, 2024 false 2025 ); 2026 let componentEnabled = Services.prefs.getBoolPref(COMPONENT_PREF, false); 2027 if (screenshotsDisabled) { 2028 if (componentEnabled) { 2029 ScreenshotsUtils.uninitialize(); 2030 } else { 2031 await addon.disable({ allowSystemAddons: true }); 2032 } 2033 } else if (componentEnabled) { 2034 ScreenshotsUtils.initialize(); 2035 await addon.disable({ allowSystemAddons: true }); 2036 } else { 2037 await addon.enable({ allowSystemAddons: true }); 2038 ScreenshotsUtils.uninitialize(); 2039 } 2040 }; 2041 Services.prefs.addObserver(SCREENSHOTS_PREF, _checkScreenshotsPref); 2042 Services.prefs.addObserver(COMPONENT_PREF, _checkScreenshotsPref); 2043 _checkScreenshotsPref(); 2044 }, 2045 2046 _monitorWebcompatReporterPref() { 2047 const PREF = "extensions.webcompat-reporter.enabled"; 2048 const ID = "webcompat-reporter@mozilla.org"; 2049 Services.prefs.addObserver(PREF, async () => { 2050 let addon = await AddonManager.getAddonByID(ID); 2051 let enabled = Services.prefs.getBoolPref(PREF, false); 2052 if (enabled && !addon.isActive) { 2053 await addon.enable({ allowSystemAddons: true }); 2054 } else if (!enabled && addon.isActive) { 2055 await addon.disable({ allowSystemAddons: true }); 2056 } 2057 }); 2058 }, 2059 2060 // Set up a listener to enable/disable the translation extension 2061 // based on its preference. 2062 _monitorTranslationsPref() { 2063 const PREF = "extensions.translations.disabled"; 2064 const ID = "firefox-translations@mozilla.org"; 2065 const oldID = "firefox-infobar-ui-bergamot-browser-extension@browser.mt"; 2066 2067 // First, try to uninstall the old extension, if exists. 2068 (async () => { 2069 let addon = await AddonManager.getAddonByID(oldID); 2070 if (addon) { 2071 addon.uninstall().catch(Cu.reportError); 2072 } 2073 })(); 2074 2075 const _checkTranslationsPref = async () => { 2076 let addon = await AddonManager.getAddonByID(ID); 2077 let disabled = Services.prefs.getBoolPref(PREF, false); 2078 if (!addon && disabled) { 2079 // not installed, bail out early. 2080 return; 2081 } 2082 if (!disabled) { 2083 // first time install of addon and install on firefox update 2084 addon = 2085 (await AddonManager.maybeInstallBuiltinAddon( 2086 ID, 2087 "0.4.3", 2088 "resource://builtin-addons/translations/" 2089 )) || addon; 2090 await addon.enable(); 2091 } else if (addon) { 2092 await addon.disable(); 2093 } 2094 }; 2095 Services.prefs.addObserver(PREF, _checkTranslationsPref); 2096 _checkTranslationsPref(); 2097 }, 2098 2099 async _setupSearchDetection() { 2100 // There is no pref for this add-on because it shouldn't be disabled. 2101 const ID = "addons-search-detection@mozilla.com"; 2102 2103 let addon = await AddonManager.getAddonByID(ID); 2104 2105 // first time install of addon and install on firefox update 2106 addon = 2107 (await AddonManager.maybeInstallBuiltinAddon( 2108 ID, 2109 "2.0.0", 2110 "resource://builtin-addons/search-detection/" 2111 )) || addon; 2112 2113 if (!addon.isActive) { 2114 addon.enable(); 2115 } 2116 }, 2117 2118 _monitorHTTPSOnlyPref() { 2119 const PREF_ENABLED = "dom.security.https_only_mode"; 2120 const PREF_WAS_ENABLED = "dom.security.https_only_mode_ever_enabled"; 2121 const _checkHTTPSOnlyPref = async () => { 2122 const enabled = Services.prefs.getBoolPref(PREF_ENABLED, false); 2123 const was_enabled = Services.prefs.getBoolPref(PREF_WAS_ENABLED, false); 2124 let value = 0; 2125 if (enabled) { 2126 value = 1; 2127 Services.prefs.setBoolPref(PREF_WAS_ENABLED, true); 2128 } else if (was_enabled) { 2129 value = 2; 2130 } 2131 Services.telemetry.scalarSet("security.https_only_mode_enabled", value); 2132 }; 2133 2134 Services.prefs.addObserver(PREF_ENABLED, _checkHTTPSOnlyPref); 2135 _checkHTTPSOnlyPref(); 2136 2137 const PREF_PBM_WAS_ENABLED = 2138 "dom.security.https_only_mode_ever_enabled_pbm"; 2139 const PREF_PBM_ENABLED = "dom.security.https_only_mode_pbm"; 2140 2141 const _checkHTTPSOnlyPBMPref = async () => { 2142 const enabledPBM = Services.prefs.getBoolPref(PREF_PBM_ENABLED, false); 2143 const was_enabledPBM = Services.prefs.getBoolPref( 2144 PREF_PBM_WAS_ENABLED, 2145 false 2146 ); 2147 let valuePBM = 0; 2148 if (enabledPBM) { 2149 valuePBM = 1; 2150 Services.prefs.setBoolPref(PREF_PBM_WAS_ENABLED, true); 2151 } else if (was_enabledPBM) { 2152 valuePBM = 2; 2153 } 2154 Services.telemetry.scalarSet( 2155 "security.https_only_mode_enabled_pbm", 2156 valuePBM 2157 ); 2158 }; 2159 2160 Services.prefs.addObserver(PREF_PBM_ENABLED, _checkHTTPSOnlyPBMPref); 2161 _checkHTTPSOnlyPBMPref(); 2162 }, 2163 2164 _monitorIonPref() { 2165 const PREF_ION_ID = "toolkit.telemetry.pioneerId"; 2166 2167 const _checkIonPref = async () => { 2168 for (let win of Services.wm.getEnumerator("navigator:browser")) { 2169 win.document.getElementById( 2170 "ion-button" 2171 ).hidden = !Services.prefs.getStringPref(PREF_ION_ID, null); 2172 } 2173 }; 2174 2175 const windowListener = { 2176 onOpenWindow(xulWindow) { 2177 const win = xulWindow.docShell.domWindow; 2178 win.addEventListener("load", () => { 2179 const ionButton = win.document.getElementById("ion-button"); 2180 if (ionButton) { 2181 ionButton.hidden = !Services.prefs.getStringPref(PREF_ION_ID, null); 2182 } 2183 }); 2184 }, 2185 onCloseWindow() {}, 2186 }; 2187 2188 Services.prefs.addObserver(PREF_ION_ID, _checkIonPref); 2189 Services.wm.addListener(windowListener); 2190 _checkIonPref(); 2191 }, 2192 2193 _monitorIonStudies() { 2194 const STUDY_ADDON_COLLECTION_KEY = "pioneer-study-addons-v1"; 2195 const PREF_ION_NEW_STUDIES_AVAILABLE = 2196 "toolkit.telemetry.pioneer-new-studies-available"; 2197 2198 const _badgeIcon = async () => { 2199 for (let win of Services.wm.getEnumerator("navigator:browser")) { 2200 win.document 2201 .getElementById("ion-button") 2202 .querySelector(".toolbarbutton-badge") 2203 .classList.add("feature-callout"); 2204 } 2205 }; 2206 2207 const windowListener = { 2208 onOpenWindow(xulWindow) { 2209 const win = xulWindow.docShell.domWindow; 2210 win.addEventListener("load", () => { 2211 const ionButton = win.document.getElementById("ion-button"); 2212 if (ionButton) { 2213 const badge = ionButton.querySelector(".toolbarbutton-badge"); 2214 if ( 2215 Services.prefs.getBoolPref(PREF_ION_NEW_STUDIES_AVAILABLE, false) 2216 ) { 2217 badge.classList.add("feature-callout"); 2218 } else { 2219 badge.classList.remove("feature-callout"); 2220 } 2221 } 2222 }); 2223 }, 2224 onCloseWindow() {}, 2225 }; 2226 2227 // Update all open windows if the pref changes. 2228 Services.prefs.addObserver(PREF_ION_NEW_STUDIES_AVAILABLE, _badgeIcon); 2229 2230 // Badge any currently-open windows. 2231 if (Services.prefs.getBoolPref(PREF_ION_NEW_STUDIES_AVAILABLE, false)) { 2232 _badgeIcon(); 2233 } 2234 2235 RemoteSettings(STUDY_ADDON_COLLECTION_KEY).on("sync", async event => { 2236 Services.prefs.setBoolPref(PREF_ION_NEW_STUDIES_AVAILABLE, true); 2237 }); 2238 2239 // When a new window opens, check if we need to badge the icon. 2240 Services.wm.addListener(windowListener); 2241 }, 2242 2243 _monitorGPCPref() { 2244 const FEATURE_PREF_ENABLED = "privacy.globalprivacycontrol.enabled"; 2245 const FUNCTIONALITY_PREF_ENABLED = 2246 "privacy.globalprivacycontrol.functionality.enabled"; 2247 const PREF_WAS_ENABLED = "privacy.globalprivacycontrol.was_ever_enabled"; 2248 const _checkGPCPref = async () => { 2249 const feature_enabled = Services.prefs.getBoolPref( 2250 FEATURE_PREF_ENABLED, 2251 false 2252 ); 2253 const functionality_enabled = Services.prefs.getBoolPref( 2254 FUNCTIONALITY_PREF_ENABLED, 2255 false 2256 ); 2257 const was_enabled = Services.prefs.getBoolPref(PREF_WAS_ENABLED, false); 2258 let value = 0; 2259 if (feature_enabled && functionality_enabled) { 2260 value = 1; 2261 Services.prefs.setBoolPref(PREF_WAS_ENABLED, true); 2262 } else if (was_enabled) { 2263 value = 2; 2264 } 2265 Services.telemetry.scalarSet( 2266 "security.global_privacy_control_enabled", 2267 value 2268 ); 2269 }; 2270 2271 Services.prefs.addObserver(FEATURE_PREF_ENABLED, _checkGPCPref); 2272 Services.prefs.addObserver(FUNCTIONALITY_PREF_ENABLED, _checkGPCPref); 2273 _checkGPCPref(); 2274 }, 2275 2276 _monitorPrivacySegmentationPref() { 2277 const PREF_ENABLED = "browser.privacySegmentation.enabled"; 2278 const EVENT_CATEGORY = "privacy_segmentation"; 2279 2280 let checkPrivacySegmentationPref = () => { 2281 let isEnabled = Services.prefs.getBoolPref(PREF_ENABLED, false); 2282 Services.telemetry.recordEvent( 2283 EVENT_CATEGORY, 2284 isEnabled ? "enable" : "disable", 2285 "pref" 2286 ); 2287 }; 2288 2289 Services.telemetry.setEventRecordingEnabled(EVENT_CATEGORY, true); 2290 Services.prefs.addObserver(PREF_ENABLED, checkPrivacySegmentationPref); 2291 }, 2292 2293 // All initial windows have opened. 2294 _onWindowsRestored: function BG__onWindowsRestored() { 2295 if (this._windowsWereRestored) { 2296 return; 2297 } 2298 this._windowsWereRestored = true; 2299 2300 BrowserUsageTelemetry.init(); 2301 SearchSERPTelemetry.init(); 2302 2303 Interactions.init(); 2304 PageDataService.init(); 2305 ExtensionsUI.init(); 2306 2307 let signingRequired; 2308 if (AppConstants.MOZ_REQUIRE_SIGNING) { 2309 signingRequired = true; 2310 } else { 2311 signingRequired = Services.prefs.getBoolPref( 2312 "xpinstall.signatures.required" 2313 ); 2314 } 2315 2316 if (signingRequired) { 2317 let disabledAddons = AddonManager.getStartupChanges( 2318 AddonManager.STARTUP_CHANGE_DISABLED 2319 ); 2320 AddonManager.getAddonsByIDs(disabledAddons).then(addons => { 2321 for (let addon of addons) { 2322 if (addon.signedState <= AddonManager.SIGNEDSTATE_MISSING) { 2323 this._notifyUnsignedAddonsDisabled(); 2324 break; 2325 } 2326 } 2327 }); 2328 } 2329 2330 if (AppConstants.MOZ_CRASHREPORTER) { 2331 UnsubmittedCrashHandler.init(); 2332 UnsubmittedCrashHandler.scheduleCheckForUnsubmittedCrashReports(); 2333 FeatureGate.annotateCrashReporter(); 2334 FeatureGate.observePrefChangesForCrashReportAnnotation(); 2335 } 2336 2337 if (AppConstants.ASAN_REPORTER) { 2338 var { AsanReporter } = ChromeUtils.import( 2339 "resource:///modules/AsanReporter.jsm" 2340 ); 2341 AsanReporter.init(); 2342 } 2343 2344 Sanitizer.onStartup(); 2345 this._maybeShowRestoreSessionInfoBar(); 2346 this._scheduleStartupIdleTasks(); 2347 this._lateTasksIdleObserver = (idleService, topic, data) => { 2348 if (topic == "idle") { 2349 idleService.removeIdleObserver( 2350 this._lateTasksIdleObserver, 2351 LATE_TASKS_IDLE_TIME_SEC 2352 ); 2353 delete this._lateTasksIdleObserver; 2354 this._scheduleBestEffortUserIdleTasks(); 2355 } 2356 }; 2357 this._userIdleService.addIdleObserver( 2358 this._lateTasksIdleObserver, 2359 LATE_TASKS_IDLE_TIME_SEC 2360 ); 2361 2362 this._monitorScreenshotsPref(); 2363 this._monitorWebcompatReporterPref(); 2364 this._monitorHTTPSOnlyPref(); 2365 this._monitorIonPref(); 2366 this._monitorIonStudies(); 2367 this._setupSearchDetection(); 2368 2369 if (AppConstants.NIGHTLY_BUILD) { 2370 this._monitorTranslationsPref(); 2371 } 2372 this._monitorGPCPref(); 2373 this._monitorPrivacySegmentationPref(); 2374 }, 2375 2376 /** 2377 * Use this function as an entry point to schedule tasks that 2378 * need to run only once after startup, and can be scheduled 2379 * by using an idle callback. 2380 * 2381 * The functions scheduled here will fire from idle callbacks 2382 * once every window has finished being restored by session 2383 * restore, and it's guaranteed that they will run before 2384 * the equivalent per-window idle tasks 2385 * (from _schedulePerWindowIdleTasks in browser.js). 2386 * 2387 * If you have something that can wait even further than the 2388 * per-window initialization, and is okay with not being run in some 2389 * sessions, please schedule them using 2390 * _scheduleBestEffortUserIdleTasks. 2391 * Don't be fooled by thinking that the use of the timeout parameter 2392 * will delay your function: it will just ensure that it potentially 2393 * happens _earlier_ than expected (when the timeout limit has been reached), 2394 * but it will not make it happen later (and out of order) compared 2395 * to the other ones scheduled together. 2396 */ 2397 _scheduleStartupIdleTasks() { 2398 const idleTasks = [ 2399 // It's important that SafeBrowsing is initialized reasonably 2400 // early, so we use a maximum timeout for it. 2401 { 2402 task: () => { 2403 SafeBrowsing.init(); 2404 }, 2405 timeout: 5000, 2406 }, 2407 2408 { 2409 task: async () => { 2410 await ContextualIdentityService.load(); 2411 Discovery.update(); 2412 }, 2413 }, 2414 2415 { 2416 task: () => { 2417 // We postponed loading bookmarks toolbar content until startup 2418 // has finished, so we can start loading it now: 2419 PlacesUIUtils.unblockToolbars(); 2420 }, 2421 }, 2422 2423 { 2424 condition: TelemetryUtils.isTelemetryEnabled, 2425 task: () => { 2426 PlacesDBUtils.telemetry().catch(console.error); 2427 }, 2428 }, 2429 2430 // Begin listening for incoming push messages. 2431 { 2432 task: () => { 2433 try { 2434 PushService.wrappedJSObject.ensureReady(); 2435 } catch (ex) { 2436 // NS_ERROR_NOT_AVAILABLE will get thrown for the PushService 2437 // getter if the PushService is disabled. 2438 if (ex.result != Cr.NS_ERROR_NOT_AVAILABLE) { 2439 throw ex; 2440 } 2441 } 2442 }, 2443 }, 2444 2445 { 2446 task: () => { 2447 this._recordContentBlockingTelemetry(); 2448 }, 2449 }, 2450 2451 { 2452 task: () => { 2453 this._recordDataSanitizationPrefs(); 2454 }, 2455 }, 2456 2457 { 2458 task: () => { 2459 let enableCertErrorUITelemetry = Services.prefs.getBoolPref( 2460 "security.certerrors.recordEventTelemetry", 2461 true 2462 ); 2463 Services.telemetry.setEventRecordingEnabled( 2464 "security.ui.certerror", 2465 enableCertErrorUITelemetry 2466 ); 2467 }, 2468 }, 2469 2470 // Load the Login Manager data from disk off the main thread, some time 2471 // after startup. If the data is required before this runs, for example 2472 // because a restored page contains a password field, it will be loaded on 2473 // the main thread, and this initialization request will be ignored. 2474 { 2475 task: () => { 2476 try { 2477 Services.logins; 2478 } catch (ex) { 2479 Cu.reportError(ex); 2480 } 2481 }, 2482 timeout: 3000, 2483 }, 2484 2485 // Add breach alerts pref observer reasonably early so the pref flip works 2486 { 2487 task: () => { 2488 this._addBreachAlertsPrefObserver(); 2489 }, 2490 }, 2491 2492 // Report pinning status and the type of shortcut used to launch 2493 { 2494 condition: AppConstants.platform == "win", 2495 task: async () => { 2496 let shellService = Cc[ 2497 "@mozilla.org/browser/shell-service;1" 2498 ].getService(Ci.nsIWindowsShellService); 2499 2500 try { 2501 Services.telemetry.scalarSet( 2502 "os.environment.is_taskbar_pinned", 2503 await shellService.isCurrentAppPinnedToTaskbarAsync() 2504 ); 2505 } catch (ex) { 2506 Cu.reportError(ex); 2507 } 2508 2509 let classification; 2510 let shortcut; 2511 try { 2512 shortcut = Services.appinfo.processStartupShortcut; 2513 classification = shellService.classifyShortcut(shortcut); 2514 } catch (ex) { 2515 Cu.reportError(ex); 2516 } 2517 2518 if (!classification) { 2519 if (shortcut) { 2520 classification = "OtherShortcut"; 2521 } else { 2522 classification = "Other"; 2523 } 2524 } 2525 2526 Services.telemetry.scalarSet( 2527 "os.environment.launch_method", 2528 classification 2529 ); 2530 }, 2531 }, 2532 2533 // Report whether Firefox is the default handler for various files types, 2534 // in particular, ".pdf". 2535 { 2536 condition: AppConstants.platform == "win", 2537 task: () => { 2538 Services.telemetry.keyedScalarSet( 2539 "os.environment.is_default_handler", 2540 ".pdf", 2541 ShellService.isDefaultHandlerFor(".pdf") 2542 ); 2543 }, 2544 }, 2545 2546 // Install built-in themes. We already installed the active built-in 2547 // theme, if any, before UI startup. 2548 { 2549 task: async () => { 2550 await BuiltInThemes.ensureBuiltInThemes(); 2551 }, 2552 }, 2553 2554 { 2555 condition: AppConstants.platform == "win", 2556 task: () => { 2557 // For Windows 7, initialize the jump list module. 2558 const WINTASKBAR_CONTRACTID = "@mozilla.org/windows-taskbar;1"; 2559 if ( 2560 WINTASKBAR_CONTRACTID in Cc && 2561 Cc[WINTASKBAR_CONTRACTID].getService(Ci.nsIWinTaskbar).available 2562 ) { 2563 let temp = {}; 2564 ChromeUtils.import( 2565 "resource:///modules/WindowsJumpLists.jsm", 2566 temp 2567 ); 2568 temp.WinTaskbarJumpList.startup(); 2569 } 2570 }, 2571 }, 2572 2573 // Report macOS Dock status 2574 { 2575 condition: AppConstants.platform == "macosx", 2576 task: () => { 2577 try { 2578 Services.telemetry.scalarSet( 2579 "os.environment.is_kept_in_dock", 2580 Cc["@mozilla.org/widget/macdocksupport;1"].getService( 2581 Ci.nsIMacDockSupport 2582 ).isAppInDock 2583 ); 2584 } catch (ex) { 2585 Cu.reportError(ex); 2586 } 2587 }, 2588 }, 2589 2590 { 2591 task: () => { 2592 this._maybeShowDefaultBrowserPrompt(); 2593 }, 2594 }, 2595 2596 { 2597 task: () => { 2598 if ( 2599 Services.prefs.getBoolPref("screenshots.browser.component.enabled") 2600 ) { 2601 ScreenshotsUtils.initialize(); 2602 } 2603 }, 2604 }, 2605 2606 { 2607 task: () => { 2608 let { setTimeout } = ChromeUtils.import( 2609 "resource://gre/modules/Timer.jsm" 2610 ); 2611 setTimeout(function() { 2612 Services.tm.idleDispatchToMainThread( 2613 Services.startup.trackStartupCrashEnd 2614 ); 2615 }, STARTUP_CRASHES_END_DELAY_MS); 2616 }, 2617 }, 2618 2619 { 2620 task: () => { 2621 let handlerService = Cc[ 2622 "@mozilla.org/uriloader/handler-service;1" 2623 ].getService(Ci.nsIHandlerService); 2624 handlerService.asyncInit(); 2625 }, 2626 }, 2627 2628 { 2629 condition: AppConstants.platform == "win", 2630 task: () => { 2631 JawsScreenReaderVersionCheck.onWindowsRestored(); 2632 }, 2633 }, 2634 2635 { 2636 task: () => { 2637 RFPHelper.init(); 2638 }, 2639 }, 2640 2641 { 2642 task: () => { 2643 Blocklist.loadBlocklistAsync(); 2644 }, 2645 }, 2646 2647 { 2648 task: () => { 2649 TabUnloader.init(); 2650 }, 2651 }, 2652 2653 { 2654 task: () => { 2655 // Init the url query stripping list. 2656 let urlQueryStrippingListService = Cc[ 2657 "@mozilla.org/query-stripping-list-service;1" 2658 ].getService(Ci.nsIURLQueryStrippingListService); 2659 urlQueryStrippingListService.init(); 2660 }, 2661 }, 2662 2663 // Run TRR performance measurements for DoH. 2664 { 2665 task: () => { 2666 let enabledPref = "doh-rollout.trrRace.enabled"; 2667 let completePref = "doh-rollout.trrRace.complete"; 2668 2669 if (Services.prefs.getBoolPref(enabledPref, false)) { 2670 if (!Services.prefs.getBoolPref(completePref, false)) { 2671 new TRRRacer().run(() => { 2672 Services.prefs.setBoolPref(completePref, true); 2673 }); 2674 } 2675 } else { 2676 Services.prefs.addObserver(enabledPref, function observer() { 2677 if (Services.prefs.getBoolPref(enabledPref, false)) { 2678 Services.prefs.removeObserver(enabledPref, observer); 2679 2680 if (!Services.prefs.getBoolPref(completePref, false)) { 2681 new TRRRacer().run(() => { 2682 Services.prefs.setBoolPref(completePref, true); 2683 }); 2684 } 2685 } 2686 }); 2687 } 2688 }, 2689 }, 2690 2691 // FOG doesn't need to be initialized _too_ early because it has a 2692 // pre-init buffer. 2693 { 2694 task: () => { 2695 Services.fog.initializeFOG(); 2696 }, 2697 }, 2698 2699 // Add the import button if this is the first startup. 2700 { 2701 task: async () => { 2702 // First check if we've already added the import button, in which 2703 // case we should check for events indicating we can remove it. 2704 if ( 2705 Services.prefs.getBoolPref( 2706 "browser.bookmarks.addedImportButton", 2707 false 2708 ) 2709 ) { 2710 PlacesUIUtils.removeImportButtonWhenImportSucceeds(); 2711 return; 2712 } 2713 2714 // Otherwise, check if this is a new profile where we need to add it. 2715 // `maybeAddImportButton` will call 2716 // `removeImportButtonWhenImportSucceeds`itself if/when it adds the 2717 // button. Doing things in this order avoids listening for removal 2718 // more than once. 2719 if ( 2720 this._isNewProfile && 2721 // Not in automation: the button changes CUI state, breaking tests 2722 !Cu.isInAutomation 2723 ) { 2724 await PlacesUIUtils.maybeAddImportButton(); 2725 } 2726 }, 2727 }, 2728 2729 { 2730 task: () => { 2731 ASRouterNewTabHook.createInstance(ASRouterDefaultConfig()); 2732 }, 2733 }, 2734 2735 { 2736 condition: AppConstants.MOZ_UPDATE_AGENT, 2737 task: () => { 2738 // Never in automation! This is close to 2739 // `UpdateService.disabledForTesting`, but without creating the 2740 // service, which can perform a good deal of I/O in order to log its 2741 // state. Since this is in the startup path, we avoid all of that. 2742 // We also don't test for Marionette and Remote Agent since they are 2743 // not yet initialized. 2744 let disabledForTesting = 2745 Cu.isInAutomation && 2746 Services.prefs.getBoolPref("app.update.disabledForTesting", false); 2747 if (!disabledForTesting) { 2748 BackgroundUpdate.maybeScheduleBackgroundUpdateTask(); 2749 } 2750 }, 2751 }, 2752 2753 // Login detection service is used in fission to identify high value sites. 2754 { 2755 task: () => { 2756 let loginDetection = Cc[ 2757 "@mozilla.org/login-detection-service;1" 2758 ].createInstance(Ci.nsILoginDetectionService); 2759 loginDetection.init(); 2760 }, 2761 }, 2762 2763 { 2764 task: () => { 2765 this._collectTelemetryPiPEnabled(); 2766 }, 2767 }, 2768 2769 { 2770 condition: AppConstants.platform == "win", 2771 task: () => { 2772 Services.obs.notifyObservers( 2773 null, 2774 "unblock-untrusted-modules-thread" 2775 ); 2776 }, 2777 }, 2778 2779 // WebDriver components (Remote Agent and Marionette) need to be 2780 // initialized as very last step. 2781 { 2782 condition: AppConstants.ENABLE_WEBDRIVER, 2783 task: () => { 2784 // Use idleDispatch a second time to run this after the per-window 2785 // idle tasks. 2786 ChromeUtils.idleDispatch(() => { 2787 Services.obs.notifyObservers( 2788 null, 2789 "browser-startup-idle-tasks-finished" 2790 ); 2791 2792 // Request startup of the Remote Agent (support for WebDriver BiDi 2793 // and the partial Chrome DevTools protocol) before Marionette. 2794 Services.obs.notifyObservers(null, "remote-startup-requested"); 2795 Services.obs.notifyObservers(null, "marionette-startup-requested"); 2796 }); 2797 }, 2798 }, 2799 // Do NOT add anything after WebDriver initialization. 2800 ]; 2801 2802 for (let task of idleTasks) { 2803 if ("condition" in task && !task.condition) { 2804 continue; 2805 } 2806 2807 ChromeUtils.idleDispatch( 2808 () => { 2809 if (!Services.startup.shuttingDown) { 2810 let startTime = Cu.now(); 2811 try { 2812 task.task(); 2813 } catch (ex) { 2814 Cu.reportError(ex); 2815 } finally { 2816 ChromeUtils.addProfilerMarker("startupIdleTask", startTime); 2817 } 2818 } 2819 }, 2820 task.timeout ? { timeout: task.timeout } : undefined 2821 ); 2822 } 2823 }, 2824 2825 /** 2826 * Use this function as an entry point to schedule tasks that we hope 2827 * to run once per session, at any arbitrary point in time, and which we 2828 * are okay with sometimes not running at all. 2829 * 2830 * This function will be called from an idle observer. Check the value of 2831 * LATE_TASKS_IDLE_TIME_SEC to see the current value for this idle 2832 * observer. 2833 * 2834 * Note: this function may never be called if the user is never idle for the 2835 * requisite time (LATE_TASKS_IDLE_TIME_SEC). Be certain before adding 2836 * something here that it's okay that it never be run. 2837 */ 2838 _scheduleBestEffortUserIdleTasks() { 2839 const idleTasks = [ 2840 () => { 2841 // Telemetry for primary-password - we do this after a delay as it 2842 // can cause IO if NSS/PSM has not already initialized. 2843 let tokenDB = Cc["@mozilla.org/security/pk11tokendb;1"].getService( 2844 Ci.nsIPK11TokenDB 2845 ); 2846 let token = tokenDB.getInternalKeyToken(); 2847 let mpEnabled = token.hasPassword; 2848 if (mpEnabled) { 2849 Services.telemetry 2850 .getHistogramById("MASTER_PASSWORD_ENABLED") 2851 .add(mpEnabled); 2852 } 2853 }, 2854 2855 () => { 2856 let obj = {}; 2857 ChromeUtils.import("resource://gre/modules/GMPInstallManager.jsm", obj); 2858 this._gmpInstallManager = new obj.GMPInstallManager(); 2859 // We don't really care about the results, if someone is interested they 2860 // can check the log. 2861 this._gmpInstallManager.simpleCheckAndInstall().catch(() => {}); 2862 }, 2863 2864 () => { 2865 RemoteSettings.init(); 2866 this._addBreachesSyncHandler(); 2867 }, 2868 2869 () => { 2870 PublicSuffixList.init(); 2871 }, 2872 2873 () => { 2874 RemoteSecuritySettings.init(); 2875 }, 2876 2877 () => { 2878 if (Services.prefs.getBoolPref("corroborator.enabled", false)) { 2879 Corroborate.init().catch(Cu.reportError); 2880 } 2881 }, 2882 2883 () => BrowserUsageTelemetry.reportProfileCount(), 2884 2885 () => OsEnvironment.reportAllowedAppSources(), 2886 2887 () => Services.search.runBackgroundChecks(), 2888 2889 () => BrowserUsageTelemetry.reportInstallationTelemetry(), 2890 ]; 2891 2892 for (let task of idleTasks) { 2893 ChromeUtils.idleDispatch(async () => { 2894 if (!Services.startup.shuttingDown) { 2895 let startTime = Cu.now(); 2896 try { 2897 await task(); 2898 } catch (ex) { 2899 Cu.reportError(ex); 2900 } finally { 2901 ChromeUtils.addProfilerMarker("startupLateIdleTask", startTime); 2902 } 2903 } 2904 }); 2905 } 2906 }, 2907 2908 _addBreachesSyncHandler() { 2909 if ( 2910 Services.prefs.getBoolPref( 2911 "signon.management.page.breach-alerts.enabled", 2912 false 2913 ) 2914 ) { 2915 RemoteSettings(LoginBreaches.REMOTE_SETTINGS_COLLECTION).on( 2916 "sync", 2917 async event => { 2918 await LoginBreaches.update(event.data.current); 2919 } 2920 ); 2921 } 2922 }, 2923 2924 _addBreachAlertsPrefObserver() { 2925 const BREACH_ALERTS_PREF = "signon.management.page.breach-alerts.enabled"; 2926 const clearVulnerablePasswordsIfBreachAlertsDisabled = async function() { 2927 if (!Services.prefs.getBoolPref(BREACH_ALERTS_PREF)) { 2928 await LoginBreaches.clearAllPotentiallyVulnerablePasswords(); 2929 } 2930 }; 2931 clearVulnerablePasswordsIfBreachAlertsDisabled(); 2932 Services.prefs.addObserver( 2933 BREACH_ALERTS_PREF, 2934 clearVulnerablePasswordsIfBreachAlertsDisabled 2935 ); 2936 }, 2937 2938 _quitSource: "unknown", 2939 _registerQuitSource(source) { 2940 this._quitSource = source; 2941 }, 2942 2943 _onQuitRequest: function BG__onQuitRequest(aCancelQuit, aQuitType) { 2944 // If user has already dismissed quit request, then do nothing 2945 if (aCancelQuit instanceof Ci.nsISupportsPRBool && aCancelQuit.data) { 2946 return; 2947 } 2948 2949 // There are several cases where we won't show a dialog here: 2950 // 1. There is only 1 tab open in 1 window 2951 // 2. browser.warnOnQuit == false 2952 // 3. The browser is currently in Private Browsing mode 2953 // 4. The browser will be restarted. 2954 // 5. The user has automatic session restore enabled and 2955 // browser.sessionstore.warnOnQuit is not set to true. 2956 // 6. The user doesn't have automatic session restore enabled 2957 // and browser.tabs.warnOnClose is not set to true. 2958 // 2959 // Otherwise, we will show the "closing multiple tabs" dialog. 2960 // 2961 // aQuitType == "lastwindow" is overloaded. "lastwindow" is used to indicate 2962 // "the last window is closing but we're not quitting (a non-browser window is open)" 2963 // and also "we're quitting by closing the last window". 2964 2965 if (aQuitType == "restart" || aQuitType == "os-restart") { 2966 return; 2967 } 2968 2969 // browser.warnOnQuit is a hidden global boolean to override all quit prompts. 2970 if (!Services.prefs.getBoolPref("browser.warnOnQuit")) { 2971 return; 2972 } 2973 2974 var windowcount = 0; 2975 var pagecount = 0; 2976 let pinnedcount = 0; 2977 for (let win of BrowserWindowTracker.orderedWindows) { 2978 if (win.closed) { 2979 continue; 2980 } 2981 windowcount++; 2982 let tabbrowser = win.gBrowser; 2983 if (tabbrowser) { 2984 pinnedcount += tabbrowser._numPinnedTabs; 2985 pagecount += 2986 tabbrowser.browsers.length - 2987 tabbrowser._numPinnedTabs - 2988 tabbrowser._removingTabs.length; 2989 } 2990 } 2991 2992 // No windows open so no need for a warning. 2993 if (!windowcount) { 2994 return; 2995 } 2996 2997 // browser.warnOnQuitShortcut is checked when quitting using the shortcut key. 2998 // The warning will appear even when only one window/tab is open. For other 2999 // methods of quitting, the warning only appears when there is more than one 3000 // window or tab open. 3001 let shouldWarnForShortcut = 3002 this._quitSource == "shortcut" && 3003 Services.prefs.getBoolPref("browser.warnOnQuitShortcut"); 3004 let shouldWarnForTabs = 3005 pagecount >= 2 && Services.prefs.getBoolPref("browser.tabs.warnOnClose"); 3006 if (!shouldWarnForTabs && !shouldWarnForShortcut) { 3007 return; 3008 } 3009 3010 if (!aQuitType) { 3011 aQuitType = "quit"; 3012 } 3013 3014 let win = BrowserWindowTracker.getTopWindow(); 3015 3016 // Our prompt for quitting is most important, so replace others. 3017 win.gDialogBox.replaceDialogIfOpen(); 3018 3019 let title, buttonLabel; 3020 // More than 1 window. Compose our own message. 3021 if (windowcount > 1) { 3022 title = gTabbrowserBundle.GetStringFromName("tabs.closeWindowsTitle"); 3023 title = PluralForm.get(windowcount, title).replace(/#1/, windowcount); 3024 3025 buttonLabel = 3026 AppConstants.platform == "win" 3027 ? "tabs.closeWindowsButtonWin" 3028 : "tabs.closeWindowsButton"; 3029 buttonLabel = gTabbrowserBundle.GetStringFromName(buttonLabel); 3030 } else if (shouldWarnForShortcut) { 3031 let productName = gBrandBundle.GetStringFromName("brandShorterName"); 3032 title = gTabbrowserBundle.formatStringFromName( 3033 "tabs.closeTabsWithKeyTitle", 3034 [productName] 3035 ); 3036 buttonLabel = gTabbrowserBundle.formatStringFromName( 3037 "tabs.closeTabsWithKeyButton", 3038 [productName] 3039 ); 3040 } else { 3041 title = gTabbrowserBundle.GetStringFromName("tabs.closeTabsTitle"); 3042 title = PluralForm.get(pagecount, title).replace("#1", pagecount); 3043 buttonLabel = gTabbrowserBundle.GetStringFromName( 3044 "tabs.closeButtonMultiple" 3045 ); 3046 } 3047 3048 // The checkbox label is different depending on whether the shortcut 3049 // was used to quit or not. 3050 let checkboxLabel; 3051 if (shouldWarnForShortcut) { 3052 let quitKeyElement = win.document.getElementById("key_quitApplication"); 3053 let quitKey = ShortcutUtils.prettifyShortcut(quitKeyElement); 3054 3055 checkboxLabel = gTabbrowserBundle.formatStringFromName( 3056 "tabs.closeTabsWithKeyConfirmCheckbox", 3057 [quitKey] 3058 ); 3059 } else { 3060 checkboxLabel = gTabbrowserBundle.GetStringFromName( 3061 "tabs.closeTabsConfirmCheckbox" 3062 ); 3063 } 3064 3065 let warnOnClose = { value: true }; 3066 let flags = 3067 Services.prompt.BUTTON_TITLE_IS_STRING * Services.prompt.BUTTON_POS_0 + 3068 Services.prompt.BUTTON_TITLE_CANCEL * Services.prompt.BUTTON_POS_1; 3069 // buttonPressed will be 0 for closing, 1 for cancel (don't close/quit) 3070 let buttonPressed = Services.prompt.confirmEx( 3071 win, 3072 title, 3073 null, 3074 flags, 3075 buttonLabel, 3076 null, 3077 null, 3078 checkboxLabel, 3079 warnOnClose 3080 ); 3081 Services.telemetry.setEventRecordingEnabled("close_tab_warning", true); 3082 let warnCheckbox = warnOnClose.value ? "checked" : "unchecked"; 3083 3084 let sessionWillBeRestored = 3085 Services.prefs.getIntPref("browser.startup.page") == 3 || 3086 Services.prefs.getBoolPref("browser.sessionstore.resume_session_once"); 3087 Services.telemetry.recordEvent( 3088 "close_tab_warning", 3089 "shown", 3090 "application", 3091 null, 3092 { 3093 source: this._quitSource, 3094 button: buttonPressed == 0 ? "close" : "cancel", 3095 warn_checkbox: warnCheckbox, 3096 closing_wins: "" + windowcount, 3097 closing_tabs: "" + (pagecount + pinnedcount), 3098 will_restore: sessionWillBeRestored ? "yes" : "no", 3099 } 3100 ); 3101 3102 // If the user has unticked the box, and has confirmed closing, stop showing 3103 // the warning. 3104 if (buttonPressed == 0 && !warnOnClose.value) { 3105 if (shouldWarnForShortcut) { 3106 Services.prefs.setBoolPref("browser.warnOnQuitShortcut", false); 3107 } else { 3108 Services.prefs.setBoolPref("browser.tabs.warnOnClose", false); 3109 } 3110 } 3111 3112 this._quitSource = "unknown"; 3113 3114 aCancelQuit.data = buttonPressed != 0; 3115 }, 3116 3117 /** 3118 * Initialize Places 3119 * - imports the bookmarks html file if bookmarks database is empty, try to 3120 * restore bookmarks from a JSON backup if the backend indicates that the 3121 * database was corrupt. 3122 * 3123 * These prefs can be set up by the frontend: 3124 * 3125 * WARNING: setting these preferences to true will overwite existing bookmarks 3126 * 3127 * - browser.places.importBookmarksHTML 3128 * Set to true will import the bookmarks.html file from the profile folder. 3129 * - browser.bookmarks.restore_default_bookmarks 3130 * Set to true by safe-mode dialog to indicate we must restore default 3131 * bookmarks. 3132 */ 3133 _initPlaces: function BG__initPlaces(aInitialMigrationPerformed) { 3134 if (this._placesInitialized) { 3135 throw new Error("Cannot initialize Places more than once"); 3136 } 3137 this._placesInitialized = true; 3138 3139 // We must instantiate the history service since it will tell us if we 3140 // need to import or restore bookmarks due to first-run, corruption or 3141 // forced migration (due to a major schema change). 3142 // If the database is corrupt or has been newly created we should 3143 // import bookmarks. 3144 let dbStatus = PlacesUtils.history.databaseStatus; 3145 3146 // Show a notification with a "more info" link for a locked places.sqlite. 3147 if (dbStatus == PlacesUtils.history.DATABASE_STATUS_LOCKED) { 3148 // Note: initPlaces should always happen when the first window is ready, 3149 // in any case, better safe than sorry. 3150 this._firstWindowReady.then(() => { 3151 this._showPlacesLockedNotificationBox(); 3152 this._placesBrowserInitComplete = true; 3153 Services.obs.notifyObservers(null, "places-browser-init-complete"); 3154 }); 3155 return; 3156 } 3157 3158 let importBookmarks = 3159 !aInitialMigrationPerformed && 3160 (dbStatus == PlacesUtils.history.DATABASE_STATUS_CREATE || 3161 dbStatus == PlacesUtils.history.DATABASE_STATUS_CORRUPT); 3162 3163 // Check if user or an extension has required to import bookmarks.html 3164 let importBookmarksHTML = false; 3165 try { 3166 importBookmarksHTML = Services.prefs.getBoolPref( 3167 "browser.places.importBookmarksHTML" 3168 ); 3169 if (importBookmarksHTML) { 3170 importBookmarks = true; 3171 } 3172 } catch (ex) {} 3173 3174 // Support legacy bookmarks.html format for apps that depend on that format. 3175 let autoExportHTML = Services.prefs.getBoolPref( 3176 "browser.bookmarks.autoExportHTML", 3177 false 3178 ); // Do not export. 3179 if (autoExportHTML) { 3180 // Sqlite.jsm and Places shutdown happen at profile-before-change, thus, 3181 // to be on the safe side, this should run earlier. 3182 AsyncShutdown.profileChangeTeardown.addBlocker( 3183 "Places: export bookmarks.html", 3184 () => BookmarkHTMLUtils.exportToFile(BookmarkHTMLUtils.defaultPath) 3185 ); 3186 } 3187 3188 (async () => { 3189 // Check if Safe Mode or the user has required to restore bookmarks from 3190 // default profile's bookmarks.html 3191 let restoreDefaultBookmarks = false; 3192 try { 3193 restoreDefaultBookmarks = Services.prefs.getBoolPref( 3194 "browser.bookmarks.restore_default_bookmarks" 3195 ); 3196 if (restoreDefaultBookmarks) { 3197 // Ensure that we already have a bookmarks backup for today. 3198 await this._backupBookmarks(); 3199 importBookmarks = true; 3200 } 3201 } catch (ex) {} 3202 3203 // If the user did not require to restore default bookmarks, or import 3204 // from bookmarks.html, we will try to restore from JSON 3205 if (importBookmarks && !restoreDefaultBookmarks && !importBookmarksHTML) { 3206 // get latest JSON backup 3207 let lastBackupFile = await PlacesBackups.getMostRecentBackup(); 3208 if (lastBackupFile) { 3209 // restore from JSON backup 3210 await BookmarkJSONUtils.importFromFile(lastBackupFile, { 3211 replace: true, 3212 source: PlacesUtils.bookmarks.SOURCES.RESTORE_ON_STARTUP, 3213 }); 3214 importBookmarks = false; 3215 } else { 3216 // We have created a new database but we don't have any backup available 3217 importBookmarks = true; 3218 if (await IOUtils.exists(BookmarkHTMLUtils.defaultPath)) { 3219 // If bookmarks.html is available in current profile import it... 3220 importBookmarksHTML = true; 3221 } else { 3222 // ...otherwise we will restore defaults 3223 restoreDefaultBookmarks = true; 3224 } 3225 } 3226 } 3227 3228 // Import default bookmarks when necessary. 3229 // Otherwise, if any kind of import runs, default bookmarks creation should be 3230 // delayed till the import operations has finished. Not doing so would 3231 // cause them to be overwritten by the newly imported bookmarks. 3232 if (!importBookmarks) { 3233 // Now apply distribution customized bookmarks. 3234 // This should always run after Places initialization. 3235 try { 3236 await this._distributionCustomizer.applyBookmarks(); 3237 } catch (e) { 3238 Cu.reportError(e); 3239 } 3240 } else { 3241 // An import operation is about to run. 3242 let bookmarksUrl = null; 3243 if (restoreDefaultBookmarks) { 3244 // User wants to restore the default set of bookmarks shipped with the 3245 // browser, those that new profiles start with. 3246 bookmarksUrl = "chrome://browser/content/default-bookmarks.html"; 3247 } else if (await IOUtils.exists(BookmarkHTMLUtils.defaultPath)) { 3248 bookmarksUrl = PathUtils.toFileURI(BookmarkHTMLUtils.defaultPath); 3249 } 3250 3251 if (bookmarksUrl) { 3252 // Import from bookmarks.html file. 3253 try { 3254 if (Services.policies.isAllowed("defaultBookmarks")) { 3255 await BookmarkHTMLUtils.importFromURL(bookmarksUrl, { 3256 replace: true, 3257 source: PlacesUtils.bookmarks.SOURCES.RESTORE_ON_STARTUP, 3258 }); 3259 } 3260 } catch (e) { 3261 Cu.reportError("Bookmarks.html file could be corrupt. " + e); 3262 } 3263 try { 3264 // Now apply distribution customized bookmarks. 3265 // This should always run after Places initialization. 3266 await this._distributionCustomizer.applyBookmarks(); 3267 } catch (e) { 3268 Cu.reportError(e); 3269 } 3270 } else { 3271 Cu.reportError(new Error("Unable to find bookmarks.html file.")); 3272 } 3273 3274 // Reset preferences, so we won't try to import again at next run 3275 if (importBookmarksHTML) { 3276 Services.prefs.setBoolPref( 3277 "browser.places.importBookmarksHTML", 3278 false 3279 ); 3280 } 3281 if (restoreDefaultBookmarks) { 3282 Services.prefs.setBoolPref( 3283 "browser.bookmarks.restore_default_bookmarks", 3284 false 3285 ); 3286 } 3287 } 3288 3289 // Initialize bookmark archiving on idle. 3290 // If the last backup has been created before the last browser session, 3291 // and is days old, be more aggressive with the idle timer. 3292 let idleTime = BOOKMARKS_BACKUP_IDLE_TIME_SEC; 3293 if (!(await PlacesBackups.hasRecentBackup())) { 3294 idleTime /= 2; 3295 } 3296 this._userIdleService.addIdleObserver(this, idleTime); 3297 this._bookmarksBackupIdleTime = idleTime; 3298 3299 if (this._isNewProfile) { 3300 try { 3301 // New profiles may have existing bookmarks (imported from another browser or 3302 // copied into the profile) and we want to show the bookmark toolbar for them 3303 // in some cases. 3304 PlacesUIUtils.maybeToggleBookmarkToolbarVisibility(); 3305 } catch (ex) { 3306 Cu.reportError(ex); 3307 } 3308 } 3309 })() 3310 .catch(ex => { 3311 Cu.reportError(ex); 3312 }) 3313 .then(() => { 3314 // NB: deliberately after the catch so that we always do this, even if 3315 // we threw halfway through initializing in the Task above. 3316 this._placesBrowserInitComplete = true; 3317 Services.obs.notifyObservers(null, "places-browser-init-complete"); 3318 }); 3319 }, 3320 3321 /** 3322 * If a backup for today doesn't exist, this creates one. 3323 */ 3324 _backupBookmarks: function BG__backupBookmarks() { 3325 return (async function() { 3326 let lastBackupFile = await PlacesBackups.getMostRecentBackup(); 3327 // Should backup bookmarks if there are no backups or the maximum 3328 // interval between backups elapsed. 3329 if ( 3330 !lastBackupFile || 3331 new Date() - PlacesBackups.getDateForFile(lastBackupFile) > 3332 BOOKMARKS_BACKUP_MIN_INTERVAL_DAYS * 86400000 3333 ) { 3334 let maxBackups = Services.prefs.getIntPref( 3335 "browser.bookmarks.max_backups" 3336 ); 3337 await PlacesBackups.create(maxBackups); 3338 } 3339 })(); 3340 }, 3341 3342 /** 3343 * Show the notificationBox for a locked places database. 3344 */ 3345 _showPlacesLockedNotificationBox: function BG__showPlacesLockedNotificationBox() { 3346 var applicationName = gBrandBundle.GetStringFromName("brandShortName"); 3347 var placesBundle = Services.strings.createBundle( 3348 "chrome://browser/locale/places/places.properties" 3349 ); 3350 var text = placesBundle.formatStringFromName("lockPrompt.text", [ 3351 applicationName, 3352 ]); 3353 3354 var win = BrowserWindowTracker.getTopWindow(); 3355 var buttons = [{ supportPage: "places-locked" }]; 3356 3357 var notifyBox = win.gBrowser.getNotificationBox(); 3358 var notification = notifyBox.appendNotification( 3359 "places-locked", 3360 { 3361 label: text, 3362 priority: win.gNotificationBox.PRIORITY_CRITICAL_MEDIUM, 3363 }, 3364 buttons 3365 ); 3366 notification.persistence = -1; // Until user closes it 3367 }, 3368 3369 _onThisDeviceConnected() { 3370 let bundle = Services.strings.createBundle( 3371 "chrome://browser/locale/accounts.properties" 3372 ); 3373 let title = bundle.GetStringFromName("deviceConnDisconnTitle"); 3374 let body = bundle.GetStringFromName("thisDeviceConnectedBody"); 3375 3376 let clickCallback = (subject, topic, data) => { 3377 if (topic != "alertclickcallback") { 3378 return; 3379 } 3380 this._openPreferences("sync"); 3381 }; 3382 this.AlertsService.showAlertNotification( 3383 null, 3384 title, 3385 body, 3386 true, 3387 null, 3388 clickCallback 3389 ); 3390 }, 3391 3392 _migrateXULStoreForDocument(fromURL, toURL) { 3393 Array.from(Services.xulStore.getIDsEnumerator(fromURL)).forEach(id => { 3394 Array.from(Services.xulStore.getAttributeEnumerator(fromURL, id)).forEach( 3395 attr => { 3396 let value = Services.xulStore.getValue(fromURL, id, attr); 3397 Services.xulStore.setValue(toURL, id, attr, value); 3398 } 3399 ); 3400 }); 3401 }, 3402 3403 _migrateHashedKeysForXULStoreForDocument(docUrl) { 3404 Array.from(Services.xulStore.getIDsEnumerator(docUrl)) 3405 .filter(id => id.startsWith("place:")) 3406 .forEach(id => { 3407 Services.xulStore.removeValue(docUrl, id, "open"); 3408 let hashedId = PlacesUIUtils.obfuscateUrlForXulStore(id); 3409 Services.xulStore.setValue(docUrl, hashedId, "open", "true"); 3410 }); 3411 }, 3412 3413 // eslint-disable-next-line complexity 3414 _migrateUI: function BG__migrateUI() { 3415 // Use an increasing number to keep track of the current migration state. 3416 // Completely unrelated to the current Firefox release number. 3417 const UI_VERSION = 125; 3418 const BROWSER_DOCURL = AppConstants.BROWSER_CHROME_URL; 3419 3420 const PROFILE_DIR = Services.dirsvc.get("ProfD", Ci.nsIFile).path; 3421 3422 if (!Services.prefs.prefHasUserValue("browser.migration.version")) { 3423 // This is a new profile, nothing to migrate. 3424 Services.prefs.setIntPref("browser.migration.version", UI_VERSION); 3425 this._isNewProfile = true; 3426 return; 3427 } 3428 3429 this._isNewProfile = false; 3430 let currentUIVersion = Services.prefs.getIntPref( 3431 "browser.migration.version" 3432 ); 3433 if (currentUIVersion >= UI_VERSION) { 3434 return; 3435 } 3436 3437 let xulStore = Services.xulStore; 3438 3439 if (currentUIVersion < 64) { 3440 IOUtils.remove(PathUtils.join(PROFILE_DIR, "directoryLinks.json"), { 3441 ignoreAbsent: true, 3442 }); 3443 } 3444 3445 if ( 3446 currentUIVersion < 65 && 3447 Services.prefs.getCharPref("general.config.filename", "") == 3448 "dsengine.cfg" 3449 ) { 3450 let searchInitializedPromise = new Promise(resolve => { 3451 if (Services.search.isInitialized) { 3452 resolve(); 3453 } 3454 const SEARCH_SERVICE_TOPIC = "browser-search-service"; 3455 Services.obs.addObserver(function observer(subject, topic, data) { 3456 if (data != "init-complete") { 3457 return; 3458 } 3459 Services.obs.removeObserver(observer, SEARCH_SERVICE_TOPIC); 3460 resolve(); 3461 }, SEARCH_SERVICE_TOPIC); 3462 }); 3463 searchInitializedPromise.then(() => { 3464 let engineNames = [ 3465 "Bing Search Engine", 3466 "Yahoo! Search Engine", 3467 "Yandex Search Engine", 3468 ]; 3469 for (let engineName of engineNames) { 3470 let engine = Services.search.getEngineByName(engineName); 3471 if (engine) { 3472 Services.search.removeEngine(engine); 3473 } 3474 } 3475 }); 3476 } 3477 3478 if (currentUIVersion < 67) { 3479 // Migrate devtools firebug theme users to light theme (bug 1378108): 3480 if (Services.prefs.getCharPref("devtools.theme") == "firebug") { 3481 Services.prefs.setCharPref("devtools.theme", "light"); 3482 } 3483 } 3484 3485 if (currentUIVersion < 68) { 3486 // Remove blocklists legacy storage, now relying on IndexedDB. 3487 IOUtils.remove(PathUtils.join(PROFILE_DIR, "kinto.sqlite"), { 3488 ignoreAbsent: true, 3489 }); 3490 } 3491 3492 if (currentUIVersion < 69) { 3493 // Clear old social prefs from profile (bug 1460675) 3494 let socialPrefs = Services.prefs.getBranch("social."); 3495 if (socialPrefs) { 3496 let socialPrefsArray = socialPrefs.getChildList(""); 3497 for (let item of socialPrefsArray) { 3498 Services.prefs.clearUserPref("social." + item); 3499 } 3500 } 3501 } 3502 3503 if (currentUIVersion < 70) { 3504 // Migrate old ctrl-tab pref to new one in existing profiles. (This code 3505 // doesn't run at all in new profiles.) 3506 Services.prefs.setBoolPref( 3507 "browser.ctrlTab.recentlyUsedOrder", 3508 Services.prefs.getBoolPref("browser.ctrlTab.previews", false) 3509 ); 3510 Services.prefs.clearUserPref("browser.ctrlTab.previews"); 3511 // Remember that we migrated the pref in case we decide to flip it for 3512 // these users. 3513 Services.prefs.setBoolPref("browser.ctrlTab.migrated", true); 3514 } 3515 3516 if (currentUIVersion < 71) { 3517 // Clear legacy saved prefs for content handlers. 3518 let savedContentHandlers = Services.prefs.getChildList( 3519 "browser.contentHandlers.types" 3520 ); 3521 for (let savedHandlerPref of savedContentHandlers) { 3522 Services.prefs.clearUserPref(savedHandlerPref); 3523 } 3524 } 3525 3526 if (currentUIVersion < 72) { 3527 // Migrate performance tool's recording interval value from msec to usec. 3528 let pref = "devtools.performance.recording.interval"; 3529 Services.prefs.setIntPref( 3530 pref, 3531 Services.prefs.getIntPref(pref, 1) * 1000 3532 ); 3533 } 3534 3535 if (currentUIVersion < 73) { 3536 // Remove blocklist JSON local dumps in profile. 3537 IOUtils.remove(PathUtils.join(PROFILE_DIR, "blocklists"), { 3538 recursive: true, 3539 ignoreAbsent: true, 3540 }); 3541 IOUtils.remove(PathUtils.join(PROFILE_DIR, "blocklists-preview"), { 3542 recursive: true, 3543 ignoreAbsent: true, 3544 }); 3545 for (const filename of ["addons.json", "plugins.json", "gfx.json"]) { 3546 // Some old versions used to dump without subfolders. Clean them while we are at it. 3547 const path = PathUtils.join(PROFILE_DIR, `blocklists-${filename}`); 3548 IOUtils.remove(path, { ignoreAbsent: true }); 3549 } 3550 } 3551 3552 if (currentUIVersion < 76) { 3553 // Clear old onboarding prefs from profile (bug 1462415) 3554 let onboardingPrefs = Services.prefs.getBranch("browser.onboarding."); 3555 if (onboardingPrefs) { 3556 let onboardingPrefsArray = onboardingPrefs.getChildList(""); 3557 for (let item of onboardingPrefsArray) { 3558 Services.prefs.clearUserPref("browser.onboarding." + item); 3559 } 3560 } 3561 } 3562 3563 if (currentUIVersion < 77) { 3564 // Remove currentset from all the toolbars 3565 let toolbars = [ 3566 "nav-bar", 3567 "PersonalToolbar", 3568 "TabsToolbar", 3569 "toolbar-menubar", 3570 ]; 3571 for (let toolbarId of toolbars) { 3572 xulStore.removeValue(BROWSER_DOCURL, toolbarId, "currentset"); 3573 } 3574 } 3575 3576 if (currentUIVersion < 78) { 3577 Services.prefs.clearUserPref("browser.search.region"); 3578 } 3579 3580 if (currentUIVersion < 79) { 3581 // The handler app service will read this. We need to wait with migrating 3582 // until the handler service has started up, so just set a pref here. 3583 Services.prefs.setCharPref("browser.handlers.migrations", "30boxes"); 3584 } 3585 3586 if (currentUIVersion < 80) { 3587 let hosts = Services.prefs.getCharPref("network.proxy.no_proxies_on"); 3588 // remove "localhost" and "127.0.0.1" from the no_proxies_on list 3589 const kLocalHosts = new Set(["localhost", "127.0.0.1"]); 3590 hosts = hosts 3591 .split(/[ ,]+/) 3592 .filter(host => !kLocalHosts.has(host)) 3593 .join(", "); 3594 Services.prefs.setCharPref("network.proxy.no_proxies_on", hosts); 3595 } 3596 3597 if (currentUIVersion < 81) { 3598 // Reset homepage pref for users who have it set to a default from before Firefox 4: 3599 // <locale>.(start|start2|start3).mozilla.(com|org) 3600 if (HomePage.overridden) { 3601 const DEFAULT = HomePage.getDefault(); 3602 let value = HomePage.get(); 3603 let updated = value.replace( 3604 /https?:\/\/([\w\-]+\.)?start\d*\.mozilla\.(org|com)[^|]*/gi, 3605 DEFAULT 3606 ); 3607 if (updated != value) { 3608 if (updated == DEFAULT) { 3609 HomePage.reset(); 3610 } else { 3611 value = updated; 3612 HomePage.safeSet(value); 3613 } 3614 } 3615 } 3616 } 3617 3618 if (currentUIVersion < 82) { 3619 this._migrateXULStoreForDocument( 3620 "chrome://browser/content/browser.xul", 3621 "chrome://browser/content/browser.xhtml" 3622 ); 3623 } 3624 3625 if (currentUIVersion < 83) { 3626 Services.prefs.clearUserPref("browser.search.reset.status"); 3627 } 3628 3629 if (currentUIVersion < 84) { 3630 // Reset flash "always allow/block" permissions 3631 // We keep session and policy permissions, which could both be 3632 // the result of enterprise policy settings. "Never/Always allow" 3633 // settings for flash were actually time-bound on recent-ish Firefoxen, 3634 // so we remove EXPIRE_TIME entries, too. 3635 const { EXPIRE_NEVER, EXPIRE_TIME } = Services.perms; 3636 let flashPermissions = Services.perms 3637 .getAllWithTypePrefix("plugin:flash") 3638 .filter( 3639 p => 3640 p.type == "plugin:flash" && 3641 (p.expireType == EXPIRE_NEVER || p.expireType == EXPIRE_TIME) 3642 ); 3643 flashPermissions.forEach(p => Services.perms.removePermission(p)); 3644 } 3645 3646 // currentUIVersion < 85 is missing due to the following: 3647 // Origianlly, Bug #1568900 added currentUIVersion 85 but was targeting FF70 release. 3648 // In between it landing in FF70, Bug #1562601 (currentUIVersion 86) landed and 3649 // was uplifted to Beta. To make sure the migration doesn't get skipped, the 3650 // code block that was at 85 has been moved/bumped to currentUIVersion 87. 3651 3652 if (currentUIVersion < 86) { 3653 // If the user has set "media.autoplay.allow-muted" to false 3654 // migrate that to media.autoplay.default=BLOCKED_ALL. 3655 if ( 3656 Services.prefs.prefHasUserValue("media.autoplay.allow-muted") && 3657 !Services.prefs.getBoolPref("media.autoplay.allow-muted") && 3658 !Services.prefs.prefHasUserValue("media.autoplay.default") && 3659 Services.prefs.getIntPref("media.autoplay.default") == 3660 Ci.nsIAutoplay.BLOCKED 3661 ) { 3662 Services.prefs.setIntPref( 3663 "media.autoplay.default", 3664 Ci.nsIAutoplay.BLOCKED_ALL 3665 ); 3666 } 3667 Services.prefs.clearUserPref("media.autoplay.allow-muted"); 3668 } 3669 3670 if (currentUIVersion < 87) { 3671 const TRACKING_TABLE_PREF = "urlclassifier.trackingTable"; 3672 const CUSTOM_BLOCKING_PREF = 3673 "browser.contentblocking.customBlockList.preferences.ui.enabled"; 3674 // Check if user has set custom tables pref, and show custom block list UI 3675 // in the about:preferences#privacy custom panel. 3676 if (Services.prefs.prefHasUserValue(TRACKING_TABLE_PREF)) { 3677 Services.prefs.setBoolPref(CUSTOM_BLOCKING_PREF, true); 3678 } 3679 } 3680 3681 if (currentUIVersion < 88) { 3682 // If the user the has "browser.contentblocking.category = custom", but has 3683 // the exact same settings as "standard", move them once to "standard". This is 3684 // to reset users who we may have moved accidentally, or moved to get ETP early. 3685 let category_prefs = [ 3686 "network.cookie.cookieBehavior", 3687 "privacy.trackingprotection.pbmode.enabled", 3688 "privacy.trackingprotection.enabled", 3689 "privacy.trackingprotection.socialtracking.enabled", 3690 "privacy.trackingprotection.fingerprinting.enabled", 3691 "privacy.trackingprotection.cryptomining.enabled", 3692 ]; 3693 if ( 3694 Services.prefs.getStringPref( 3695 "browser.contentblocking.category", 3696 "standard" 3697 ) == "custom" 3698 ) { 3699 let shouldMigrate = true; 3700 for (let pref of category_prefs) { 3701 if (Services.prefs.prefHasUserValue(pref)) { 3702 shouldMigrate = false; 3703 } 3704 } 3705 if (shouldMigrate) { 3706 Services.prefs.setStringPref( 3707 "browser.contentblocking.category", 3708 "standard" 3709 ); 3710 } 3711 } 3712 } 3713 3714 if (currentUIVersion < 89) { 3715 // This file was renamed in https://bugzilla.mozilla.org/show_bug.cgi?id=1595636. 3716 this._migrateXULStoreForDocument( 3717 "chrome://devtools/content/framework/toolbox-window.xul", 3718 "chrome://devtools/content/framework/toolbox-window.xhtml" 3719 ); 3720 } 3721 3722 if (currentUIVersion < 90) { 3723 this._migrateXULStoreForDocument( 3724 "chrome://browser/content/places/historySidebar.xul", 3725 "chrome://browser/content/places/historySidebar.xhtml" 3726 ); 3727 this._migrateXULStoreForDocument( 3728 "chrome://browser/content/places/places.xul", 3729 "chrome://browser/content/places/places.xhtml" 3730 ); 3731 this._migrateXULStoreForDocument( 3732 "chrome://browser/content/places/bookmarksSidebar.xul", 3733 "chrome://browser/content/places/bookmarksSidebar.xhtml" 3734 ); 3735 } 3736 3737 // Clear socks proxy values if they were shared from http, to prevent 3738 // websocket breakage after bug 1577862 (see bug 969282). 3739 if ( 3740 currentUIVersion < 91 && 3741 Services.prefs.getBoolPref("network.proxy.share_proxy_settings", false) && 3742 Services.prefs.getIntPref("network.proxy.type", 0) == 1 3743 ) { 3744 let httpProxy = Services.prefs.getCharPref("network.proxy.http", ""); 3745 let httpPort = Services.prefs.getIntPref("network.proxy.http_port", 0); 3746 let socksProxy = Services.prefs.getCharPref("network.proxy.socks", ""); 3747 let socksPort = Services.prefs.getIntPref("network.proxy.socks_port", 0); 3748 if (httpProxy && httpProxy == socksProxy && httpPort == socksPort) { 3749 Services.prefs.setCharPref( 3750 "network.proxy.socks", 3751 Services.prefs.getCharPref("network.proxy.backup.socks", "") 3752 ); 3753 Services.prefs.setIntPref( 3754 "network.proxy.socks_port", 3755 Services.prefs.getIntPref("network.proxy.backup.socks_port", 0) 3756 ); 3757 } 3758 } 3759 3760 if (currentUIVersion < 92) { 3761 // privacy.userContext.longPressBehavior pref was renamed and changed to a boolean 3762 let longpress = Services.prefs.getIntPref( 3763 "privacy.userContext.longPressBehavior", 3764 0 3765 ); 3766 if (longpress == 1) { 3767 Services.prefs.setBoolPref( 3768 "privacy.userContext.newTabContainerOnLeftClick.enabled", 3769 true 3770 ); 3771 } 3772 } 3773 3774 if (currentUIVersion < 93) { 3775 // The Gecko Profiler Addon is now an internal component. Remove the old 3776 // addon, and enable the new UI. 3777 3778 function enableProfilerButton(wasAddonActive) { 3779 // Enable the feature pref. This will add it to the customization palette, 3780 // but not to the the navbar. 3781 Services.prefs.setBoolPref( 3782 "devtools.performance.popup.feature-flag", 3783 true 3784 ); 3785 3786 if (wasAddonActive) { 3787 const { ProfilerMenuButton } = ChromeUtils.import( 3788 "resource://devtools/client/performance-new/popup/menu-button.jsm.js" 3789 ); 3790 if (!ProfilerMenuButton.isInNavbar()) { 3791 // The profiler menu button is not enabled. Turn it on now. 3792 const win = BrowserWindowTracker.getTopWindow(); 3793 if (win && win.document) { 3794 ProfilerMenuButton.addToNavbar(win.document); 3795 } 3796 } 3797 } 3798 } 3799 3800 let addonPromise; 3801 try { 3802 addonPromise = AddonManager.getAddonByID("geckoprofiler@mozilla.com"); 3803 } catch (error) { 3804 Cu.reportError( 3805 "Could not access the AddonManager to upgrade the profile. This is most " + 3806 "likely because the upgrader is being run from an xpcshell test where " + 3807 "the AddonManager is not initialized." 3808 ); 3809 } 3810 Promise.resolve(addonPromise).then(addon => { 3811 if (!addon) { 3812 // Either the addon wasn't installed, or the call to getAddonByID failed. 3813 return; 3814 } 3815 // Remove the old addon. 3816 const wasAddonActive = addon.isActive; 3817 addon 3818 .uninstall() 3819 .catch(Cu.reportError) 3820 .then(() => enableProfilerButton(wasAddonActive)) 3821 .catch(Cu.reportError); 3822 }, Cu.reportError); 3823 } 3824 3825 // Clear unused socks proxy backup values - see bug 1625773. 3826 if (currentUIVersion < 94) { 3827 let backup = Services.prefs.getCharPref("network.proxy.backup.socks", ""); 3828 let backupPort = Services.prefs.getIntPref( 3829 "network.proxy.backup.socks_port", 3830 0 3831 ); 3832 let socksProxy = Services.prefs.getCharPref("network.proxy.socks", ""); 3833 let socksPort = Services.prefs.getIntPref("network.proxy.socks_port", 0); 3834 if (backup == socksProxy) { 3835 Services.prefs.clearUserPref("network.proxy.backup.socks"); 3836 } 3837 if (backupPort == socksPort) { 3838 Services.prefs.clearUserPref("network.proxy.backup.socks_port"); 3839 } 3840 } 3841 3842 if (currentUIVersion < 95) { 3843 const oldPrefName = "media.autoplay.enabled.user-gestures-needed"; 3844 const oldPrefValue = Services.prefs.getBoolPref(oldPrefName, true); 3845 const newPrefValue = oldPrefValue ? 0 : 1; 3846 Services.prefs.setIntPref("media.autoplay.blocking_policy", newPrefValue); 3847 Services.prefs.clearUserPref(oldPrefName); 3848 } 3849 3850 if (currentUIVersion < 96) { 3851 const oldPrefName = "browser.urlbar.openViewOnFocus"; 3852 const oldPrefValue = Services.prefs.getBoolPref(oldPrefName, true); 3853 Services.prefs.setBoolPref( 3854 "browser.urlbar.suggest.topsites", 3855 oldPrefValue 3856 ); 3857 Services.prefs.clearUserPref(oldPrefName); 3858 } 3859 3860 if (currentUIVersion < 97) { 3861 let userCustomizedWheelMax = Services.prefs.prefHasUserValue( 3862 "general.smoothScroll.mouseWheel.durationMaxMS" 3863 ); 3864 let userCustomizedWheelMin = Services.prefs.prefHasUserValue( 3865 "general.smoothScroll.mouseWheel.durationMinMS" 3866 ); 3867 3868 if (!userCustomizedWheelMin && !userCustomizedWheelMax) { 3869 // If the user has an existing profile but hasn't customized the wheel 3870 // animation duration, they will now get the new default values. This 3871 // condition used to set a migrationPercent pref to 0, so that users 3872 // upgrading an older profile would gradually have their wheel animation 3873 // speed migrated to the new values. However, that "gradual migration" 3874 // was phased out by FF 86, so we don't need to set that pref anymore. 3875 } else if (userCustomizedWheelMin && !userCustomizedWheelMax) { 3876 // If they customized just one of the two, save the old value for the 3877 // other one as well, because the two values go hand-in-hand and we 3878 // don't want to move just one to a new value and leave the other one 3879 // at a customized value. In both of these cases, we leave the "migration 3880 // complete" percentage at 100, because they have customized this and 3881 // don't need any further migration. 3882 Services.prefs.setIntPref( 3883 "general.smoothScroll.mouseWheel.durationMaxMS", 3884 400 3885 ); 3886 } else if (!userCustomizedWheelMin && userCustomizedWheelMax) { 3887 // Same as above case, but for the other pref. 3888 Services.prefs.setIntPref( 3889 "general.smoothScroll.mouseWheel.durationMinMS", 3890 200 3891 ); 3892 } else { 3893 // The last remaining case is if they customized both values, in which 3894 // case also don't need to do anything; the user's customized values 3895 // will be retained and respected. 3896 } 3897 } 3898 3899 if (currentUIVersion < 98) { 3900 Services.prefs.clearUserPref("browser.search.cohort"); 3901 } 3902 3903 if (currentUIVersion < 99) { 3904 Services.prefs.clearUserPref("security.tls.version.enable-deprecated"); 3905 } 3906 3907 if (currentUIVersion < 102) { 3908 // In Firefox 83, we moved to a dynamic button, so it needs to be removed 3909 // from default placement. This is done early enough that it doesn't 3910 // impact adding new managed bookmarks. 3911 const { CustomizableUI } = ChromeUtils.import( 3912 "resource:///modules/CustomizableUI.jsm" 3913 ); 3914 CustomizableUI.removeWidgetFromArea("managed-bookmarks"); 3915 } 3916 3917 // We have to rerun these because we had to use 102 on beta. 3918 // They were 101 and 102 before. 3919 if (currentUIVersion < 103) { 3920 // Set a pref if the bookmarks toolbar was already visible, 3921 // so we can keep it visible when navigating away from newtab 3922 let bookmarksToolbarWasVisible = 3923 Services.xulStore.getValue( 3924 BROWSER_DOCURL, 3925 "PersonalToolbar", 3926 "collapsed" 3927 ) == "false"; 3928 if (bookmarksToolbarWasVisible) { 3929 // Migrate the user to the "always visible" value. See firefox.js for 3930 // the other possible states. 3931 Services.prefs.setCharPref( 3932 "browser.toolbars.bookmarks.visibility", 3933 "always" 3934 ); 3935 } 3936 Services.xulStore.removeValue( 3937 BROWSER_DOCURL, 3938 "PersonalToolbar", 3939 "collapsed" 3940 ); 3941 3942 Services.prefs.clearUserPref( 3943 "browser.livebookmarks.migrationAttemptsLeft" 3944 ); 3945 } 3946 3947 // For existing profiles, continue putting bookmarks in the 3948 // "other bookmarks" folder. 3949 if (currentUIVersion < 104) { 3950 Services.prefs.setCharPref( 3951 "browser.bookmarks.defaultLocation", 3952 "unfiled" 3953 ); 3954 } 3955 3956 // Renamed and flipped the logic of a pref to make its purpose more clear. 3957 if (currentUIVersion < 105) { 3958 const oldPrefName = "browser.urlbar.imeCompositionClosesPanel"; 3959 const oldPrefValue = Services.prefs.getBoolPref(oldPrefName, true); 3960 Services.prefs.setBoolPref( 3961 "browser.urlbar.keepPanelOpenDuringImeComposition", 3962 !oldPrefValue 3963 ); 3964 Services.prefs.clearUserPref(oldPrefName); 3965 } 3966 3967 // Initialize the new browser.urlbar.showSuggestionsBeforeGeneral pref. 3968 if (currentUIVersion < 106) { 3969 UrlbarPrefs.initializeShowSearchSuggestionsFirstPref(); 3970 } 3971 3972 if (currentUIVersion < 107) { 3973 // Migrate old http URIs for mailto handlers to their https equivalents. 3974 // The handler service will do this. We need to wait with migrating 3975 // until the handler service has started up, so just set a pref here. 3976 const kPref = "browser.handlers.migrations"; 3977 // We might have set up another migration further up. Create an array, 3978 // and drop empty strings resulting from the `split`: 3979 let migrations = Services.prefs 3980 .getCharPref(kPref, "") 3981 .split(",") 3982 .filter(x => !!x); 3983 migrations.push("secure-mail"); 3984 Services.prefs.setCharPref(kPref, migrations.join(",")); 3985 } 3986 3987 if (currentUIVersion < 108) { 3988 // Migrate old ctrlTab pref to new ctrlTab pref 3989 let defaultValue = false; 3990 let oldPrefName = "browser.ctrlTab.recentlyUsedOrder"; 3991 let oldPrefDefault = true; 3992 // Use old pref value if the user used Ctrl+Tab before, elsewise use new default value 3993 if (Services.prefs.getBoolPref("browser.engagement.ctrlTab.has-used")) { 3994 let newPrefValue = Services.prefs.getBoolPref( 3995 oldPrefName, 3996 oldPrefDefault 3997 ); 3998 Services.prefs.setBoolPref( 3999 "browser.ctrlTab.sortByRecentlyUsed", 4000 newPrefValue 4001 ); 4002 } else { 4003 Services.prefs.setBoolPref( 4004 "browser.ctrlTab.sortByRecentlyUsed", 4005 defaultValue 4006 ); 4007 } 4008 } 4009 4010 if (currentUIVersion < 109) { 4011 // Migrate old pref to new pref 4012 if ( 4013 Services.prefs.prefHasUserValue("signon.recipes.remoteRecipesEnabled") 4014 ) { 4015 // Fetch the previous value of signon.recipes.remoteRecipesEnabled and assign it to signon.recipes.remoteRecipes.enabled. 4016 Services.prefs.setBoolPref( 4017 "signon.recipes.remoteRecipes.enabled", 4018 Services.prefs.getBoolPref( 4019 "signon.recipes.remoteRecipesEnabled", 4020 true 4021 ) 4022 ); 4023 //Then clear user pref 4024 Services.prefs.clearUserPref("signon.recipes.remoteRecipesEnabled"); 4025 } 4026 } 4027 4028 if (currentUIVersion < 117) { 4029 // Update urlbar result groups for the following changes: 4030 // 110 (bug 1662167): Add INPUT_HISTORY group 4031 // 111 (bug 1677126): Add REMOTE_TABS group 4032 // 112 (bug 1712352): Add ABOUT_PAGES group 4033 // 113 (bug 1714409): Add HEURISTIC_ENGINE_ALIAS group 4034 // 114 (bug 1662172): Add HEURISTIC_BOOKMARK_KEYWORD group 4035 // 115 (bug 1713322): Move TAIL_SUGGESTION group and rename properties 4036 // 116 (bug 1717509): Remove HEURISTIC_UNIFIED_COMPLETE group 4037 // 117 (bug 1710518): Add GENERAL_PARENT group 4038 UrlbarPrefs.migrateResultGroups(); 4039 } 4040 4041 if (currentUIVersion < 119 && AppConstants.NIGHTLY_BUILD) { 4042 // Uninstall outdated monochromatic themes for the following UI versions: 4043 // 118: Uninstall prototype monochromatic purple theme. 4044 // 119 (bug 1732957): Uninstall themes with old IDs. 4045 const themeIdsToMigrate = [ 4046 "firefox-monochromatic-purple@mozilla.org", 4047 "firefox-lush-soft@mozilla.org", 4048 "firefox-lush-balanced@mozilla.org", 4049 "firefox-lush-bold@mozilla.org", 4050 "firefox-abstract-soft@mozilla.org", 4051 "firefox-abstract-balanced@mozilla.org", 4052 "firefox-abstract-bold@mozilla.org", 4053 "firefox-elemental-soft@mozilla.org", 4054 "firefox-elemental-balanced@mozilla.org", 4055 "firefox-elemental-bold@mozilla.org", 4056 "firefox-cheers-soft@mozilla.org", 4057 "firefox-cheers-balanced@mozilla.org", 4058 "firefox-cheers-bold@mozilla.org", 4059 "firefox-graffiti-soft@mozilla.org", 4060 "firefox-graffiti-balanced@mozilla.org", 4061 "firefox-graffiti-bold@mozilla.org", 4062 "firefox-foto-soft@mozilla.org", 4063 "firefox-foto-balanced@mozilla.org", 4064 "firefox-foto-bold@mozilla.org", 4065 ]; 4066 try { 4067 for (let id of themeIdsToMigrate) { 4068 AddonManager.getAddonByID(id).then(addon => { 4069 if (!addon) { 4070 // Either the addon wasn't installed, or the call to getAddonByID failed. 4071 return; 4072 } 4073 addon.uninstall().catch(Cu.reportError); 4074 }, Cu.reportError); 4075 } 4076 } catch (error) { 4077 Cu.reportError( 4078 "Could not access the AddonManager to upgrade the profile. This is most " + 4079 "likely because the upgrader is being run from an xpcshell test where " + 4080 "the AddonManager is not initialized." 4081 ); 4082 } 4083 } 4084 4085 if (currentUIVersion < 120) { 4086 // Migrate old titlebar bool pref to new int-based one. 4087 const oldPref = "browser.tabs.drawInTitlebar"; 4088 const newPref = "browser.tabs.inTitlebar"; 4089 if (Services.prefs.prefHasUserValue(oldPref)) { 4090 // We may have int prefs for builds between bug 1736518 and bug 1739539. 4091 const oldPrefType = Services.prefs.getPrefType(oldPref); 4092 if (oldPrefType == Services.prefs.PREF_BOOL) { 4093 Services.prefs.setIntPref( 4094 newPref, 4095 Services.prefs.getBoolPref(oldPref) ? 1 : 0 4096 ); 4097 } else { 4098 Services.prefs.setIntPref( 4099 newPref, 4100 Services.prefs.getIntPref(oldPref) 4101 ); 4102 } 4103 Services.prefs.clearUserPref(oldPref); 4104 } 4105 } 4106 4107 if (currentUIVersion < 121) { 4108 // Migrate stored uris and convert them to use hashed keys 4109 this._migrateHashedKeysForXULStoreForDocument(BROWSER_DOCURL); 4110 this._migrateHashedKeysForXULStoreForDocument( 4111 "chrome://browser/content/places/bookmarksSidebar.xhtml" 4112 ); 4113 this._migrateHashedKeysForXULStoreForDocument( 4114 "chrome://browser/content/places/historySidebar.xhtml" 4115 ); 4116 } 4117 4118 if (currentUIVersion < 122) { 4119 // Migrate xdg-desktop-portal pref from old to new prefs. 4120 try { 4121 const oldPref = "widget.use-xdg-desktop-portal"; 4122 if (Services.prefs.getBoolPref(oldPref)) { 4123 Services.prefs.setIntPref( 4124 "widget.use-xdg-desktop-portal.file-picker", 4125 1 4126 ); 4127 Services.prefs.setIntPref( 4128 "widget.use-xdg-desktop-portal.mime-handler", 4129 1 4130 ); 4131 } 4132 Services.prefs.clearUserPref(oldPref); 4133 } catch (ex) {} 4134 } 4135 4136 // Bug 1745248: Due to multiple backouts, do not use UI Version 123 4137 // as this version is most likely set for the Nightly channel 4138 4139 if (currentUIVersion < 124) { 4140 // Migrate "extensions.formautofill.available" and 4141 // "extensions.formautofill.creditCards.available" from old to new prefs 4142 const oldFormAutofillModule = "extensions.formautofill.available"; 4143 const oldCreditCardsAvailable = 4144 "extensions.formautofill.creditCards.available"; 4145 const newCreditCardsAvailable = 4146 "extensions.formautofill.creditCards.supported"; 4147 const newAddressesAvailable = 4148 "extensions.formautofill.addresses.supported"; 4149 if (Services.prefs.prefHasUserValue(oldFormAutofillModule)) { 4150 let moduleAvailability = Services.prefs.getCharPref( 4151 oldFormAutofillModule 4152 ); 4153 if (moduleAvailability == "on") { 4154 Services.prefs.setCharPref(newAddressesAvailable, moduleAvailability); 4155 Services.prefs.setCharPref( 4156 newCreditCardsAvailable, 4157 Services.prefs.getBoolPref(oldCreditCardsAvailable) ? "on" : "off" 4158 ); 4159 } 4160 4161 if (moduleAvailability == "off") { 4162 Services.prefs.setCharPref( 4163 newCreditCardsAvailable, 4164 moduleAvailability 4165 ); 4166 Services.prefs.setCharPref(newAddressesAvailable, moduleAvailability); 4167 } 4168 } 4169 4170 // after migrating, clear old prefs so we can remove them later. 4171 Services.prefs.clearUserPref(oldFormAutofillModule); 4172 Services.prefs.clearUserPref(oldCreditCardsAvailable); 4173 } 4174 4175 if (currentUIVersion < 125) { 4176 // Bug 1756243 - Clear PiP cached coordinates since we changed their 4177 // coordinate space. 4178 const PIP_PLAYER_URI = 4179 "chrome://global/content/pictureinpicture/player.xhtml"; 4180 try { 4181 for (let value of ["left", "top", "width", "height"]) { 4182 Services.xulStore.removeValue( 4183 PIP_PLAYER_URI, 4184 "picture-in-picture", 4185 value 4186 ); 4187 } 4188 } catch (ex) { 4189 Cu.reportError("Failed to clear XULStore PiP values: " + ex); 4190 } 4191 } 4192 4193 // Update the migration version. 4194 Services.prefs.setIntPref("browser.migration.version", UI_VERSION); 4195 }, 4196 4197 _showUpgradeDialog() { 4198 BrowserWindowTracker.getTopWindow().gDialogBox.open( 4199 "chrome://browser/content/upgradeDialog.html" 4200 ); 4201 }, 4202 4203 async _maybeShowDefaultBrowserPrompt() { 4204 // Highest priority is the upgrade dialog, which can include a "primary 4205 // browser" request and is limited in various ways, e.g., major upgrades. 4206 const dialogVersion = 94; 4207 const dialogVersionPref = "browser.startup.upgradeDialog.version"; 4208 const dialogReason = await (async () => { 4209 if (!BrowserHandler.majorUpgrade) { 4210 return "not-major"; 4211 } 4212 const lastVersion = Services.prefs.getIntPref(dialogVersionPref, 0); 4213 if (lastVersion > dialogVersion) { 4214 return "newer-shown"; 4215 } 4216 if (lastVersion === dialogVersion) { 4217 return "already-shown"; 4218 } 4219 4220 // Check the default branch as enterprise policies can set prefs there. 4221 const defaultPrefs = Services.prefs.getDefaultBranch(""); 4222 if ( 4223 !defaultPrefs.getBoolPref( 4224 "browser.messaging-system.whatsNewPanel.enabled", 4225 true 4226 ) 4227 ) { 4228 return "no-whatsNew"; 4229 } 4230 if (!defaultPrefs.getBoolPref("browser.aboutwelcome.enabled", true)) { 4231 return "no-welcome"; 4232 } 4233 if (!Services.policies.isAllowed("postUpdateCustomPage")) { 4234 return "disallow-postUpdate"; 4235 } 4236 4237 return NimbusFeatures.upgradeDialog.isEnabled() ? "" : "disabled"; 4238 })(); 4239 4240 // Record why the dialog is showing or not. 4241 Services.telemetry.setEventRecordingEnabled("upgrade_dialog", true); 4242 Services.telemetry.recordEvent( 4243 "upgrade_dialog", 4244 "trigger", 4245 "reason", 4246 dialogReason || "satisfied" 4247 ); 4248 4249 // Show the upgrade dialog if allowed and remember the version. 4250 if (!dialogReason) { 4251 Services.prefs.setIntPref(dialogVersionPref, dialogVersion); 4252 4253 // Show Firefox Home behind the upgrade dialog to see theme changes. 4254 const { gBrowser } = BrowserWindowTracker.getTopWindow(); 4255 gBrowser.selectedTab = gBrowser.addTrustedTab("about:home"); 4256 this._showUpgradeDialog(); 4257 return; 4258 } 4259 4260 const willPrompt = await DefaultBrowserCheck.willCheckDefaultBrowser( 4261 /* isStartupCheck */ true 4262 ); 4263 if (willPrompt) { 4264 let win = BrowserWindowTracker.getTopWindow(); 4265 DefaultBrowserCheck.prompt(win); 4266 } else if (await UrlbarQuickSuggest.maybeShowOnboardingDialog()) { 4267 return; 4268 } 4269 4270 await ASRouter.waitForInitialized; 4271 ASRouter.sendTriggerMessage({ 4272 browser: BrowserWindowTracker.getTopWindow()?.gBrowser.selectedBrowser, 4273 // triggerId and triggerContext 4274 id: "defaultBrowserCheck", 4275 context: { willShowDefaultPrompt: willPrompt, source: "startup" }, 4276 }); 4277 }, 4278 4279 /** 4280 * Only show the infobar when canRestoreLastSession and the pref value == 1 4281 */ 4282 async _maybeShowRestoreSessionInfoBar() { 4283 let count = Services.prefs.getIntPref( 4284 "browser.startup.couldRestoreSession.count", 4285 0 4286 ); 4287 if (count < 0 || count >= 2) { 4288 return; 4289 } 4290 if (count == 0) { 4291 // We don't show the infobar right after the update which establishes this pref 4292 // Increment the counter so we can consider it next time 4293 Services.prefs.setIntPref( 4294 "browser.startup.couldRestoreSession.count", 4295 ++count 4296 ); 4297 return; 4298 } 4299 4300 const win = BrowserWindowTracker.getTopWindow(); 4301 // We've restarted at least once; we will show the notification if possible. 4302 // We can't do that if there's no session to restore, or this is a private window. 4303 if ( 4304 !SessionStore.canRestoreLastSession || 4305 PrivateBrowsingUtils.isWindowPrivate(win) 4306 ) { 4307 return; 4308 } 4309 4310 Services.prefs.setIntPref( 4311 "browser.startup.couldRestoreSession.count", 4312 ++count 4313 ); 4314 4315 const messageFragment = win.document.createDocumentFragment(); 4316 const message = win.document.createElement("span"); 4317 const icon = win.document.createElement("img"); 4318 icon.src = "chrome://browser/skin/menu.svg"; 4319 icon.setAttribute("data-l10n-name", "icon"); 4320 icon.className = "inline-icon"; 4321 message.appendChild(icon); 4322 messageFragment.appendChild(message); 4323 win.document.l10n.setAttributes( 4324 message, 4325 "restore-session-startup-suggestion-message" 4326 ); 4327 4328 const buttons = [ 4329 { 4330 "l10n-id": "restore-session-startup-suggestion-button", 4331 primary: true, 4332 callback: () => { 4333 win.PanelUI.selectAndMarkItem([ 4334 "appMenu-history-button", 4335 "appMenu-restoreSession", 4336 ]); 4337 }, 4338 }, 4339 ]; 4340 4341 const notifyBox = win.gBrowser.getNotificationBox(); 4342 const notification = notifyBox.appendNotification( 4343 "startup-restore-session-suggestion", 4344 { 4345 label: messageFragment, 4346 priority: notifyBox.PRIORITY_INFO_MEDIUM, 4347 }, 4348 buttons 4349 ); 4350 // Don't allow it to be immediately hidden: 4351 notification.timeout = Date.now() + 3000; 4352 }, 4353 4354 /** 4355 * Open preferences even if there are no open windows. 4356 */ 4357 _openPreferences(...args) { 4358 let chromeWindow = BrowserWindowTracker.getTopWindow(); 4359 if (chromeWindow) { 4360 chromeWindow.openPreferences(...args); 4361 return; 4362 } 4363 4364 if (Services.appShell.hiddenDOMWindow.openPreferences) { 4365 Services.appShell.hiddenDOMWindow.openPreferences(...args); 4366 } 4367 }, 4368 4369 _openURLInNewWindow(url) { 4370 let urlString = Cc["@mozilla.org/supports-string;1"].createInstance( 4371 Ci.nsISupportsString 4372 ); 4373 urlString.data = url; 4374 return new Promise(resolve => { 4375 let win = Services.ww.openWindow( 4376 null, 4377 AppConstants.BROWSER_CHROME_URL, 4378 "_blank", 4379 "chrome,all,dialog=no", 4380 urlString 4381 ); 4382 win.addEventListener( 4383 "load", 4384 () => { 4385 resolve(win); 4386 }, 4387 { once: true } 4388 ); 4389 }); 4390 }, 4391 4392 /** 4393 * Called as an observer when Sync's "display URIs" notification is fired. 4394 * 4395 * We open the received URIs in background tabs. 4396 */ 4397 async _onDisplaySyncURIs(data) { 4398 try { 4399 // The payload is wrapped weirdly because of how Sync does notifications. 4400 const URIs = data.wrappedJSObject.object; 4401 4402 // win can be null, but it's ok, we'll assign it later in openTab() 4403 let win = BrowserWindowTracker.getTopWindow({ private: false }); 4404 4405 const openTab = async URI => { 4406 let tab; 4407 if (!win) { 4408 win = await this._openURLInNewWindow(URI.uri); 4409 let tabs = win.gBrowser.tabs; 4410 tab = tabs[tabs.length - 1]; 4411 } else { 4412 tab = win.gBrowser.addWebTab(URI.uri); 4413 } 4414 tab.setAttribute("attention", true); 4415 return tab; 4416 }; 4417 4418 const firstTab = await openTab(URIs[0]); 4419 await Promise.all(URIs.slice(1).map(URI => openTab(URI))); 4420 4421 const deviceName = URIs[0].sender && URIs[0].sender.name; 4422 let title, body; 4423 const bundle = Services.strings.createBundle( 4424 "chrome://browser/locale/accounts.properties" 4425 ); 4426 if (URIs.length == 1) { 4427 // Due to bug 1305895, tabs from iOS may not have device information, so 4428 // we have separate strings to handle those cases. (See Also 4429 // unnamedTabsArrivingNotificationNoDevice.body below) 4430 if (deviceName) { 4431 title = bundle.formatStringFromName( 4432 "tabArrivingNotificationWithDevice.title", 4433 [deviceName] 4434 ); 4435 } else { 4436 title = bundle.GetStringFromName("tabArrivingNotification.title"); 4437 } 4438 // Use the page URL as the body. We strip the fragment and query (after 4439 // the `?` and `#` respectively) to reduce size, and also format it the 4440 // same way that the url bar would. 4441 body = URIs[0].uri.replace(/([?#]).*$/, "$1"); 4442 let wasTruncated = body.length < URIs[0].uri.length; 4443 body = BrowserUIUtils.trimURL(body); 4444 if (wasTruncated) { 4445 body = bundle.formatStringFromName( 4446 "singleTabArrivingWithTruncatedURL.body", 4447 [body] 4448 ); 4449 } 4450 } else { 4451 title = bundle.GetStringFromName( 4452 "multipleTabsArrivingNotification.title" 4453 ); 4454 const allKnownSender = URIs.every(URI => URI.sender != null); 4455 const allSameDevice = 4456 allKnownSender && 4457 URIs.every(URI => URI.sender.id == URIs[0].sender.id); 4458 let tabArrivingBody; 4459 if (allSameDevice) { 4460 if (deviceName) { 4461 tabArrivingBody = "unnamedTabsArrivingNotification2.body"; 4462 } else { 4463 tabArrivingBody = "unnamedTabsArrivingNotificationNoDevice.body"; 4464 } 4465 } else { 4466 tabArrivingBody = "unnamedTabsArrivingNotificationMultiple2.body"; 4467 } 4468 4469 body = bundle.GetStringFromName(tabArrivingBody); 4470 body = PluralForm.get(URIs.length, body); 4471 body = body.replace("#1", URIs.length); 4472 body = body.replace("#2", deviceName); 4473 } 4474 4475 const clickCallback = (obsSubject, obsTopic, obsData) => { 4476 if (obsTopic == "alertclickcallback") { 4477 win.gBrowser.selectedTab = firstTab; 4478 } 4479 }; 4480 4481 // Specify an icon because on Windows no icon is shown at the moment 4482 let imageURL; 4483 if (AppConstants.platform == "win") { 4484 imageURL = "chrome://branding/content/icon64.png"; 4485 } 4486 this.AlertsService.showAlertNotification( 4487 imageURL, 4488 title, 4489 body, 4490 true, 4491 null, 4492 clickCallback 4493 ); 4494 } catch (ex) { 4495 Cu.reportError("Error displaying tab(s) received by Sync: " + ex); 4496 } 4497 }, 4498 4499 async _onVerifyLoginNotification({ body, title, url }) { 4500 let tab; 4501 let imageURL; 4502 if (AppConstants.platform == "win") { 4503 imageURL = "chrome://branding/content/icon64.png"; 4504 } 4505 let win = BrowserWindowTracker.getTopWindow({ private: false }); 4506 if (!win) { 4507 win = await this._openURLInNewWindow(url); 4508 let tabs = win.gBrowser.tabs; 4509 tab = tabs[tabs.length - 1]; 4510 } else { 4511 tab = win.gBrowser.addWebTab(url); 4512 } 4513 tab.setAttribute("attention", true); 4514 let clickCallback = (subject, topic, data) => { 4515 if (topic != "alertclickcallback") { 4516 return; 4517 } 4518 win.gBrowser.selectedTab = tab; 4519 }; 4520 4521 try { 4522 this.AlertsService.showAlertNotification( 4523 imageURL, 4524 title, 4525 body, 4526 true, 4527 null, 4528 clickCallback 4529 ); 4530 } catch (ex) { 4531 Cu.reportError("Error notifying of a verify login event: " + ex); 4532 } 4533 }, 4534 4535 _onDeviceConnected(deviceName) { 4536 let accountsBundle = Services.strings.createBundle( 4537 "chrome://browser/locale/accounts.properties" 4538 ); 4539 let title = accountsBundle.GetStringFromName("deviceConnDisconnTitle"); 4540 let body = accountsBundle.formatStringFromName( 4541 "otherDeviceConnectedBody" + (deviceName ? "" : ".noDeviceName"), 4542 [deviceName] 4543 ); 4544 4545 let clickCallback = async (subject, topic, data) => { 4546 if (topic != "alertclickcallback") { 4547 return; 4548 } 4549 let url = await FxAccounts.config.promiseManageDevicesURI( 4550 "device-connected-notification" 4551 ); 4552 let win = BrowserWindowTracker.getTopWindow({ private: false }); 4553 if (!win) { 4554 this._openURLInNewWindow(url); 4555 } else { 4556 win.gBrowser.addWebTab(url); 4557 } 4558 }; 4559 4560 try { 4561 this.AlertsService.showAlertNotification( 4562 null, 4563 title, 4564 body, 4565 true, 4566 null, 4567 clickCallback 4568 ); 4569 } catch (ex) { 4570 Cu.reportError("Error notifying of a new Sync device: " + ex); 4571 } 4572 }, 4573 4574 _onDeviceDisconnected() { 4575 let bundle = Services.strings.createBundle( 4576 "chrome://browser/locale/accounts.properties" 4577 ); 4578 let title = bundle.GetStringFromName("deviceConnDisconnTitle"); 4579 let body = bundle.GetStringFromName("thisDeviceDisconnectedBody"); 4580 4581 let clickCallback = (subject, topic, data) => { 4582 if (topic != "alertclickcallback") { 4583 return; 4584 } 4585 this._openPreferences("sync"); 4586 }; 4587 this.AlertsService.showAlertNotification( 4588 null, 4589 title, 4590 body, 4591 true, 4592 null, 4593 clickCallback 4594 ); 4595 }, 4596 4597 _handleFlashHang() { 4598 ++this._flashHangCount; 4599 if (this._flashHangCount < 2) { 4600 return; 4601 } 4602 // protected mode only applies to win32 4603 if (Services.appinfo.XPCOMABI != "x86-msvc") { 4604 return; 4605 } 4606 4607 if ( 4608 Services.prefs.getBoolPref("dom.ipc.plugins.flash.disable-protected-mode") 4609 ) { 4610 return; 4611 } 4612 if ( 4613 !Services.prefs.getBoolPref("browser.flash-protected-mode-flip.enable") 4614 ) { 4615 return; 4616 } 4617 if (Services.prefs.getBoolPref("browser.flash-protected-mode-flip.done")) { 4618 return; 4619 } 4620 Services.prefs.setBoolPref( 4621 "dom.ipc.plugins.flash.disable-protected-mode", 4622 true 4623 ); 4624 Services.prefs.setBoolPref("browser.flash-protected-mode-flip.done", true); 4625 4626 let win = BrowserWindowTracker.getTopWindow(); 4627 if (!win) { 4628 return; 4629 } 4630 let productName = gBrandBundle.GetStringFromName("brandShortName"); 4631 let message = win.gNavigatorBundle.getFormattedString("flashHang.message", [ 4632 productName, 4633 ]); 4634 let buttons = [ 4635 { 4636 label: win.gNavigatorBundle.getString("flashHang.helpButton.label"), 4637 accessKey: win.gNavigatorBundle.getString( 4638 "flashHang.helpButton.accesskey" 4639 ), 4640 link: 4641 "https://support.mozilla.org/kb/flash-protected-mode-autodisabled", 4642 }, 4643 ]; 4644 4645 // XXXndeakin is this notification still relevant? 4646 win.gNotificationBox.appendNotification( 4647 "flash-hang", 4648 { 4649 label: message, 4650 priority: win.gNotificationBox.PRIORITY_INFO_MEDIUM, 4651 }, 4652 buttons 4653 ); 4654 }, 4655 4656 _updateFxaBadges(win) { 4657 let fxaButton = win.document.getElementById("fxa-toolbar-menu-button"); 4658 let badge = fxaButton?.querySelector(".toolbarbutton-badge"); 4659 4660 let state = UIState.get(); 4661 if ( 4662 state.status == UIState.STATUS_LOGIN_FAILED || 4663 state.status == UIState.STATUS_NOT_VERIFIED 4664 ) { 4665 // If the fxa toolbar button is in the toolbox, we display the notification 4666 // on the fxa button instead of the app menu. 4667 let navToolbox = win.document.getElementById("navigator-toolbox"); 4668 let isFxAButtonShown = navToolbox.contains(fxaButton); 4669 if (isFxAButtonShown) { 4670 state.status == UIState.STATUS_LOGIN_FAILED 4671 ? fxaButton?.setAttribute("badge-status", state.status) 4672 : badge?.classList.add("feature-callout"); 4673 } else { 4674 AppMenuNotifications.showBadgeOnlyNotification( 4675 "fxa-needs-authentication" 4676 ); 4677 } 4678 } else { 4679 fxaButton?.removeAttribute("badge-status"); 4680 badge?.classList.remove("feature-callout"); 4681 AppMenuNotifications.removeNotification("fxa-needs-authentication"); 4682 } 4683 }, 4684 4685 _collectTelemetryPiPEnabled() { 4686 Services.telemetry.setEventRecordingEnabled( 4687 "pictureinpicture.settings", 4688 true 4689 ); 4690 Services.telemetry.setEventRecordingEnabled("pictureinpicture", true); 4691 4692 const TOGGLE_ENABLED_PREF = 4693 "media.videocontrols.picture-in-picture.video-toggle.enabled"; 4694 4695 const observe = (subject, topic, data) => { 4696 const enabled = Services.prefs.getBoolPref(TOGGLE_ENABLED_PREF, false); 4697 Services.telemetry.scalarSet("pictureinpicture.toggle_enabled", enabled); 4698 4699 // Record events when preferences change 4700 if (topic === "nsPref:changed") { 4701 if (enabled) { 4702 Services.telemetry.recordEvent( 4703 "pictureinpicture.settings", 4704 "enable", 4705 "player" 4706 ); 4707 } else { 4708 Services.telemetry.recordEvent( 4709 "pictureinpicture.settings", 4710 "disable", 4711 "player" 4712 ); 4713 } 4714 } 4715 }; 4716 4717 Services.prefs.addObserver(TOGGLE_ENABLED_PREF, observe); 4718 observe(); 4719 }, 4720 4721 QueryInterface: ChromeUtils.generateQI([ 4722 "nsIObserver", 4723 "nsISupportsWeakReference", 4724 ]), 4725}; 4726 4727var ContentBlockingCategoriesPrefs = { 4728 PREF_CB_CATEGORY: "browser.contentblocking.category", 4729 PREF_STRICT_DEF: "browser.contentblocking.features.strict", 4730 switchingCategory: false, 4731 4732 setPrefExpectations() { 4733 // The prefs inside CATEGORY_PREFS are initial values. 4734 // If the pref remains null, then it will expect the default value. 4735 // The "standard" category is defined as expecting all 5 default values. 4736 this.CATEGORY_PREFS = { 4737 strict: { 4738 "network.cookie.cookieBehavior": null, 4739 "network.cookie.cookieBehavior.pbmode": null, 4740 "privacy.trackingprotection.pbmode.enabled": null, 4741 "privacy.trackingprotection.enabled": null, 4742 "privacy.trackingprotection.socialtracking.enabled": null, 4743 "privacy.trackingprotection.fingerprinting.enabled": null, 4744 "privacy.trackingprotection.cryptomining.enabled": null, 4745 "privacy.annotate_channels.strict_list.enabled": null, 4746 "network.http.referer.disallowCrossSiteRelaxingDefault": null, 4747 "privacy.partition.network_state.ocsp_cache": null, 4748 }, 4749 standard: { 4750 "network.cookie.cookieBehavior": null, 4751 "network.cookie.cookieBehavior.pbmode": null, 4752 "privacy.trackingprotection.pbmode.enabled": null, 4753 "privacy.trackingprotection.enabled": null, 4754 "privacy.trackingprotection.socialtracking.enabled": null, 4755 "privacy.trackingprotection.fingerprinting.enabled": null, 4756 "privacy.trackingprotection.cryptomining.enabled": null, 4757 "privacy.annotate_channels.strict_list.enabled": null, 4758 "network.http.referer.disallowCrossSiteRelaxingDefault": null, 4759 "privacy.partition.network_state.ocsp_cache": null, 4760 }, 4761 }; 4762 let type = "strict"; 4763 let rulesArray = Services.prefs 4764 .getStringPref(this.PREF_STRICT_DEF) 4765 .split(","); 4766 for (let item of rulesArray) { 4767 switch (item) { 4768 case "tp": 4769 this.CATEGORY_PREFS[type][ 4770 "privacy.trackingprotection.enabled" 4771 ] = true; 4772 break; 4773 case "-tp": 4774 this.CATEGORY_PREFS[type][ 4775 "privacy.trackingprotection.enabled" 4776 ] = false; 4777 break; 4778 case "tpPrivate": 4779 this.CATEGORY_PREFS[type][ 4780 "privacy.trackingprotection.pbmode.enabled" 4781 ] = true; 4782 break; 4783 case "-tpPrivate": 4784 this.CATEGORY_PREFS[type][ 4785 "privacy.trackingprotection.pbmode.enabled" 4786 ] = false; 4787 break; 4788 case "fp": 4789 this.CATEGORY_PREFS[type][ 4790 "privacy.trackingprotection.fingerprinting.enabled" 4791 ] = true; 4792 break; 4793 case "-fp": 4794 this.CATEGORY_PREFS[type][ 4795 "privacy.trackingprotection.fingerprinting.enabled" 4796 ] = false; 4797 break; 4798 case "cm": 4799 this.CATEGORY_PREFS[type][ 4800 "privacy.trackingprotection.cryptomining.enabled" 4801 ] = true; 4802 break; 4803 case "-cm": 4804 this.CATEGORY_PREFS[type][ 4805 "privacy.trackingprotection.cryptomining.enabled" 4806 ] = false; 4807 break; 4808 case "stp": 4809 this.CATEGORY_PREFS[type][ 4810 "privacy.trackingprotection.socialtracking.enabled" 4811 ] = true; 4812 break; 4813 case "-stp": 4814 this.CATEGORY_PREFS[type][ 4815 "privacy.trackingprotection.socialtracking.enabled" 4816 ] = false; 4817 break; 4818 case "lvl2": 4819 this.CATEGORY_PREFS[type][ 4820 "privacy.annotate_channels.strict_list.enabled" 4821 ] = true; 4822 break; 4823 case "-lvl2": 4824 this.CATEGORY_PREFS[type][ 4825 "privacy.annotate_channels.strict_list.enabled" 4826 ] = false; 4827 break; 4828 case "rp": 4829 this.CATEGORY_PREFS[type][ 4830 "network.http.referer.disallowCrossSiteRelaxingDefault" 4831 ] = true; 4832 break; 4833 case "-rp": 4834 this.CATEGORY_PREFS[type][ 4835 "network.http.referer.disallowCrossSiteRelaxingDefault" 4836 ] = false; 4837 break; 4838 case "ocsp": 4839 this.CATEGORY_PREFS[type][ 4840 "privacy.partition.network_state.ocsp_cache" 4841 ] = true; 4842 break; 4843 case "-ocsp": 4844 this.CATEGORY_PREFS[type][ 4845 "privacy.partition.network_state.ocsp_cache" 4846 ] = false; 4847 break; 4848 case "cookieBehavior0": 4849 this.CATEGORY_PREFS[type]["network.cookie.cookieBehavior"] = 4850 Ci.nsICookieService.BEHAVIOR_ACCEPT; 4851 break; 4852 case "cookieBehavior1": 4853 this.CATEGORY_PREFS[type]["network.cookie.cookieBehavior"] = 4854 Ci.nsICookieService.BEHAVIOR_REJECT_FOREIGN; 4855 break; 4856 case "cookieBehavior2": 4857 this.CATEGORY_PREFS[type]["network.cookie.cookieBehavior"] = 4858 Ci.nsICookieService.BEHAVIOR_REJECT; 4859 break; 4860 case "cookieBehavior3": 4861 this.CATEGORY_PREFS[type]["network.cookie.cookieBehavior"] = 4862 Ci.nsICookieService.BEHAVIOR_LIMIT_FOREIGN; 4863 break; 4864 case "cookieBehavior4": 4865 this.CATEGORY_PREFS[type]["network.cookie.cookieBehavior"] = 4866 Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER; 4867 break; 4868 case "cookieBehavior5": 4869 this.CATEGORY_PREFS[type]["network.cookie.cookieBehavior"] = 4870 Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN; 4871 break; 4872 case "cookieBehaviorPBM0": 4873 this.CATEGORY_PREFS[type]["network.cookie.cookieBehavior.pbmode"] = 4874 Ci.nsICookieService.BEHAVIOR_ACCEPT; 4875 break; 4876 case "cookieBehaviorPBM1": 4877 this.CATEGORY_PREFS[type]["network.cookie.cookieBehavior.pbmode"] = 4878 Ci.nsICookieService.BEHAVIOR_REJECT_FOREIGN; 4879 break; 4880 case "cookieBehaviorPBM2": 4881 this.CATEGORY_PREFS[type]["network.cookie.cookieBehavior.pbmode"] = 4882 Ci.nsICookieService.BEHAVIOR_REJECT; 4883 break; 4884 case "cookieBehaviorPBM3": 4885 this.CATEGORY_PREFS[type]["network.cookie.cookieBehavior.pbmode"] = 4886 Ci.nsICookieService.BEHAVIOR_LIMIT_FOREIGN; 4887 break; 4888 case "cookieBehaviorPBM4": 4889 this.CATEGORY_PREFS[type]["network.cookie.cookieBehavior.pbmode"] = 4890 Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER; 4891 break; 4892 case "cookieBehaviorPBM5": 4893 this.CATEGORY_PREFS[type]["network.cookie.cookieBehavior.pbmode"] = 4894 Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN; 4895 break; 4896 default: 4897 Cu.reportError(`Error: Unknown rule observed ${item}`); 4898 } 4899 } 4900 }, 4901 4902 /** 4903 * Checks if CB prefs match perfectly with one of our pre-defined categories. 4904 */ 4905 prefsMatch(category) { 4906 // The category pref must be either unset, or match. 4907 if ( 4908 Services.prefs.prefHasUserValue(this.PREF_CB_CATEGORY) && 4909 Services.prefs.getStringPref(this.PREF_CB_CATEGORY) != category 4910 ) { 4911 return false; 4912 } 4913 for (let pref in this.CATEGORY_PREFS[category]) { 4914 let value = this.CATEGORY_PREFS[category][pref]; 4915 if (value == null) { 4916 if (Services.prefs.prefHasUserValue(pref)) { 4917 return false; 4918 } 4919 } else { 4920 let prefType = Services.prefs.getPrefType(pref); 4921 if ( 4922 (prefType == Services.prefs.PREF_BOOL && 4923 Services.prefs.getBoolPref(pref) != value) || 4924 (prefType == Services.prefs.PREF_INT && 4925 Services.prefs.getIntPref(pref) != value) || 4926 (prefType == Services.prefs.PREF_STRING && 4927 Services.prefs.getStringPref(pref) != value) 4928 ) { 4929 return false; 4930 } 4931 } 4932 } 4933 return true; 4934 }, 4935 4936 matchCBCategory() { 4937 if (this.switchingCategory) { 4938 return; 4939 } 4940 // If PREF_CB_CATEGORY is not set match users to a Content Blocking category. Check if prefs fit 4941 // perfectly into strict or standard, otherwise match with custom. If PREF_CB_CATEGORY has previously been set, 4942 // a change of one of these prefs necessarily puts us in "custom". 4943 if (this.prefsMatch("standard")) { 4944 Services.prefs.setStringPref(this.PREF_CB_CATEGORY, "standard"); 4945 } else if (this.prefsMatch("strict")) { 4946 Services.prefs.setStringPref(this.PREF_CB_CATEGORY, "strict"); 4947 } else { 4948 Services.prefs.setStringPref(this.PREF_CB_CATEGORY, "custom"); 4949 } 4950 4951 // If there is a custom policy which changes a related pref, then put the user in custom so 4952 // they still have access to other content blocking prefs, and to keep our default definitions 4953 // from changing. 4954 let policy = Services.policies.getActivePolicies(); 4955 if (policy && (policy.EnableTrackingProtection || policy.Cookies)) { 4956 Services.prefs.setStringPref(this.PREF_CB_CATEGORY, "custom"); 4957 } 4958 }, 4959 4960 updateCBCategory() { 4961 if ( 4962 this.switchingCategory || 4963 !Services.prefs.prefHasUserValue(this.PREF_CB_CATEGORY) 4964 ) { 4965 return; 4966 } 4967 // Turn on switchingCategory flag, to ensure that when the individual prefs that change as a result 4968 // of the category change do not trigger yet another category change. 4969 this.switchingCategory = true; 4970 let value = Services.prefs.getStringPref(this.PREF_CB_CATEGORY); 4971 this.setPrefsToCategory(value); 4972 this.switchingCategory = false; 4973 }, 4974 4975 /** 4976 * Sets all user-exposed content blocking preferences to values that match the selected category. 4977 */ 4978 setPrefsToCategory(category) { 4979 // Leave prefs as they were if we are switching to "custom" category. 4980 if (category == "custom") { 4981 return; 4982 } 4983 4984 for (let pref in this.CATEGORY_PREFS[category]) { 4985 let value = this.CATEGORY_PREFS[category][pref]; 4986 if (!Services.prefs.prefIsLocked(pref)) { 4987 if (value == null) { 4988 Services.prefs.clearUserPref(pref); 4989 } else { 4990 switch (Services.prefs.getPrefType(pref)) { 4991 case Services.prefs.PREF_BOOL: 4992 Services.prefs.setBoolPref(pref, value); 4993 break; 4994 case Services.prefs.PREF_INT: 4995 Services.prefs.setIntPref(pref, value); 4996 break; 4997 case Services.prefs.PREF_STRING: 4998 Services.prefs.setStringPref(pref, value); 4999 break; 5000 } 5001 } 5002 } 5003 } 5004 }, 5005}; 5006 5007/** 5008 * ContentPermissionIntegration is responsible for showing the user 5009 * simple permission prompts when content requests additional 5010 * capabilities. 5011 * 5012 * While there are some built-in permission prompts, createPermissionPrompt 5013 * can also be overridden by system add-ons or tests to provide new ones. 5014 * 5015 * This override ability is provided by Integration.jsm. See 5016 * PermissionUI.jsm for an example of how to provide a new prompt 5017 * from an add-on. 5018 */ 5019const ContentPermissionIntegration = { 5020 /** 5021 * Creates a PermissionPrompt for a given permission type and 5022 * nsIContentPermissionRequest. 5023 * 5024 * @param {string} type 5025 * The type of the permission request from content. This normally 5026 * matches the "type" field of an nsIContentPermissionType, but it 5027 * can be something else if the permission does not use the 5028 * nsIContentPermissionRequest model. Note that this type might also 5029 * be different from the permission key used in the permissions 5030 * database. 5031 * Example: "geolocation" 5032 * @param {nsIContentPermissionRequest} request 5033 * The request for a permission from content. 5034 * @return {PermissionPrompt} (see PermissionUI.jsm), 5035 * or undefined if the type cannot be handled. 5036 */ 5037 createPermissionPrompt(type, request) { 5038 switch (type) { 5039 case "geolocation": { 5040 return new PermissionUI.GeolocationPermissionPrompt(request); 5041 } 5042 case "xr": { 5043 return new PermissionUI.XRPermissionPrompt(request); 5044 } 5045 case "desktop-notification": { 5046 return new PermissionUI.DesktopNotificationPermissionPrompt(request); 5047 } 5048 case "persistent-storage": { 5049 return new PermissionUI.PersistentStoragePermissionPrompt(request); 5050 } 5051 case "midi": { 5052 return new PermissionUI.MIDIPermissionPrompt(request); 5053 } 5054 case "storage-access": { 5055 return new PermissionUI.StorageAccessPermissionPrompt(request); 5056 } 5057 } 5058 return undefined; 5059 }, 5060}; 5061 5062function ContentPermissionPrompt() {} 5063 5064ContentPermissionPrompt.prototype = { 5065 classID: Components.ID("{d8903bf6-68d5-4e97-bcd1-e4d3012f721a}"), 5066 5067 QueryInterface: ChromeUtils.generateQI(["nsIContentPermissionPrompt"]), 5068 5069 /** 5070 * This implementation of nsIContentPermissionPrompt.prompt ensures 5071 * that there's only one nsIContentPermissionType in the request, 5072 * and that it's of type nsIContentPermissionType. Failing to 5073 * satisfy either of these conditions will result in this method 5074 * throwing NS_ERRORs. If the combined ContentPermissionIntegration 5075 * cannot construct a prompt for this particular request, an 5076 * NS_ERROR_FAILURE will be thrown. 5077 * 5078 * Any time an error is thrown, the nsIContentPermissionRequest is 5079 * cancelled automatically. 5080 * 5081 * @param {nsIContentPermissionRequest} request 5082 * The request that we're to show a prompt for. 5083 */ 5084 prompt(request) { 5085 if (request.element && request.element.fxrPermissionPrompt) { 5086 // For Firefox Reality on Desktop, switch to a different mechanism to 5087 // prompt the user since fewer permissions are available and since many 5088 // UI dependencies are not availabe. 5089 request.element.fxrPermissionPrompt(request); 5090 return; 5091 } 5092 5093 let type; 5094 try { 5095 // Only allow exactly one permission request here. 5096 let types = request.types.QueryInterface(Ci.nsIArray); 5097 if (types.length != 1) { 5098 throw Components.Exception( 5099 "Expected an nsIContentPermissionRequest with only 1 type.", 5100 Cr.NS_ERROR_UNEXPECTED 5101 ); 5102 } 5103 5104 type = types.queryElementAt(0, Ci.nsIContentPermissionType).type; 5105 let combinedIntegration = Integration.contentPermission.getCombined( 5106 ContentPermissionIntegration 5107 ); 5108 5109 let permissionPrompt = combinedIntegration.createPermissionPrompt( 5110 type, 5111 request 5112 ); 5113 if (!permissionPrompt) { 5114 throw Components.Exception( 5115 `Failed to handle permission of type ${type}`, 5116 Cr.NS_ERROR_FAILURE 5117 ); 5118 } 5119 5120 permissionPrompt.prompt(); 5121 } catch (ex) { 5122 Cu.reportError(ex); 5123 request.cancel(); 5124 throw ex; 5125 } 5126 5127 let schemeHistogram = Services.telemetry.getKeyedHistogramById( 5128 "PERMISSION_REQUEST_ORIGIN_SCHEME" 5129 ); 5130 let scheme = 0; 5131 try { 5132 if (request.principal.schemeIs("http")) { 5133 scheme = 1; 5134 } else if (request.principal.schemeIs("https")) { 5135 scheme = 2; 5136 } 5137 } catch (ex) { 5138 // If the request principal is not available at this point, 5139 // the request has likely been cancelled before being shown to the 5140 // user. We shouldn't record this request. 5141 if (ex.result != Cr.NS_ERROR_FAILURE) { 5142 Cu.reportError(ex); 5143 } 5144 return; 5145 } 5146 schemeHistogram.add(type, scheme); 5147 5148 let userInputHistogram = Services.telemetry.getKeyedHistogramById( 5149 "PERMISSION_REQUEST_HANDLING_USER_INPUT" 5150 ); 5151 userInputHistogram.add( 5152 type, 5153 request.hasValidTransientUserGestureActivation 5154 ); 5155 }, 5156}; 5157 5158var DefaultBrowserCheck = { 5159 async prompt(win) { 5160 const shellService = win.getShellService(); 5161 const needPin = await shellService.doesAppNeedPin(); 5162 5163 win.MozXULElement.insertFTLIfNeeded("branding/brand.ftl"); 5164 win.MozXULElement.insertFTLIfNeeded( 5165 "browser/defaultBrowserNotification.ftl" 5166 ); 5167 // Resolve the translations for the prompt elements and return only the 5168 // string values 5169 const pinMessage = 5170 AppConstants.platform == "macosx" 5171 ? "default-browser-prompt-message-pin-mac" 5172 : "default-browser-prompt-message-pin"; 5173 let [promptTitle, promptMessage, askLabel, yesButton, notNowButton] = ( 5174 await win.document.l10n.formatMessages([ 5175 { 5176 id: needPin 5177 ? "default-browser-prompt-title-pin" 5178 : "default-browser-prompt-title-alt", 5179 }, 5180 { 5181 id: needPin ? pinMessage : "default-browser-prompt-message-alt", 5182 }, 5183 { id: "default-browser-prompt-checkbox-not-again-label" }, 5184 { 5185 id: needPin 5186 ? "default-browser-prompt-button-primary-pin" 5187 : "default-browser-prompt-button-primary-alt", 5188 }, 5189 { id: "default-browser-prompt-button-secondary" }, 5190 ]) 5191 ).map(({ value }) => value); 5192 5193 let ps = Services.prompt; 5194 let buttonFlags = 5195 ps.BUTTON_TITLE_IS_STRING * ps.BUTTON_POS_0 + 5196 ps.BUTTON_TITLE_IS_STRING * ps.BUTTON_POS_1 + 5197 ps.BUTTON_POS_0_DEFAULT; 5198 let rv = await ps.asyncConfirmEx( 5199 win.browsingContext, 5200 ps.MODAL_TYPE_INTERNAL_WINDOW, 5201 promptTitle, 5202 promptMessage, 5203 buttonFlags, 5204 yesButton, 5205 notNowButton, 5206 null, 5207 askLabel, 5208 false, // checkbox state 5209 { headerIconURL: "chrome://branding/content/icon32.png" } 5210 ); 5211 let buttonNumClicked = rv.get("buttonNumClicked"); 5212 let checkboxState = rv.get("checked"); 5213 if (buttonNumClicked == 0) { 5214 shellService.setAsDefault(); 5215 shellService.pinToTaskbar(); 5216 } else if (checkboxState) { 5217 shellService.shouldCheckDefaultBrowser = false; 5218 } 5219 5220 try { 5221 let resultEnum = buttonNumClicked * 2 + !checkboxState; 5222 Services.telemetry 5223 .getHistogramById("BROWSER_SET_DEFAULT_RESULT") 5224 .add(resultEnum); 5225 } catch (ex) { 5226 /* Don't break if Telemetry is acting up. */ 5227 } 5228 }, 5229 5230 /** 5231 * Checks if the default browser check prompt will be shown. 5232 * @param {boolean} isStartupCheck 5233 * If true, prefs will be set and telemetry will be recorded. 5234 * @returns {boolean} True if the default browser check prompt will be shown. 5235 */ 5236 async willCheckDefaultBrowser(isStartupCheck) { 5237 let win = BrowserWindowTracker.getTopWindow(); 5238 let shellService = win.getShellService(); 5239 5240 // Perform default browser checking. 5241 if (!shellService) { 5242 return false; 5243 } 5244 5245 let shouldCheck = 5246 !AppConstants.DEBUG && shellService.shouldCheckDefaultBrowser; 5247 5248 // Even if we shouldn't check the default browser, we still continue when 5249 // isStartupCheck = true to set prefs and telemetry. 5250 if (!shouldCheck && !isStartupCheck) { 5251 return false; 5252 } 5253 5254 // Skip the "Set Default Browser" check during first-run or after the 5255 // browser has been run a few times. 5256 const skipDefaultBrowserCheck = 5257 Services.prefs.getBoolPref( 5258 "browser.shell.skipDefaultBrowserCheckOnFirstRun" 5259 ) && 5260 !Services.prefs.getBoolPref( 5261 "browser.shell.didSkipDefaultBrowserCheckOnFirstRun" 5262 ); 5263 5264 let promptCount = Services.prefs.getIntPref( 5265 "browser.shell.defaultBrowserCheckCount", 5266 0 5267 ); 5268 5269 // If SessionStartup's state is not initialized, checking sessionType will set 5270 // its internal state to "do not restore". 5271 await SessionStartup.onceInitialized; 5272 let willRecoverSession = 5273 SessionStartup.sessionType == SessionStartup.RECOVER_SESSION; 5274 5275 // Don't show the prompt if we're already the default browser. 5276 let isDefault = false; 5277 let isDefaultError = false; 5278 try { 5279 isDefault = shellService.isDefaultBrowser(isStartupCheck, false); 5280 } catch (ex) { 5281 isDefaultError = true; 5282 } 5283 5284 if (isDefault && isStartupCheck) { 5285 let now = Math.floor(Date.now() / 1000).toString(); 5286 Services.prefs.setCharPref( 5287 "browser.shell.mostRecentDateSetAsDefault", 5288 now 5289 ); 5290 } 5291 5292 let willPrompt = shouldCheck && !isDefault && !willRecoverSession; 5293 5294 if (willPrompt) { 5295 if (skipDefaultBrowserCheck) { 5296 if (isStartupCheck) { 5297 Services.prefs.setBoolPref( 5298 "browser.shell.didSkipDefaultBrowserCheckOnFirstRun", 5299 true 5300 ); 5301 } 5302 willPrompt = false; 5303 } else { 5304 promptCount++; 5305 if (isStartupCheck) { 5306 Services.prefs.setIntPref( 5307 "browser.shell.defaultBrowserCheckCount", 5308 promptCount 5309 ); 5310 } 5311 if (!AppConstants.RELEASE_OR_BETA && promptCount > 3) { 5312 willPrompt = false; 5313 } 5314 } 5315 } 5316 5317 if (isStartupCheck) { 5318 try { 5319 // Report default browser status on startup to telemetry 5320 // so we can track whether we are the default. 5321 Services.telemetry 5322 .getHistogramById("BROWSER_IS_USER_DEFAULT") 5323 .add(isDefault); 5324 Services.telemetry 5325 .getHistogramById("BROWSER_IS_USER_DEFAULT_ERROR") 5326 .add(isDefaultError); 5327 Services.telemetry 5328 .getHistogramById("BROWSER_SET_DEFAULT_ALWAYS_CHECK") 5329 .add(shouldCheck); 5330 Services.telemetry 5331 .getHistogramById("BROWSER_SET_DEFAULT_DIALOG_PROMPT_RAWCOUNT") 5332 .add(promptCount); 5333 } catch (ex) { 5334 /* Don't break the default prompt if telemetry is broken. */ 5335 } 5336 } 5337 5338 return willPrompt; 5339 }, 5340}; 5341 5342/* 5343 * Prompts users who have an outdated JAWS screen reader informing 5344 * them they need to update JAWS or switch to esr. Can be removed 5345 * 12/31/2018. 5346 */ 5347var JawsScreenReaderVersionCheck = { 5348 _prompted: false, 5349 5350 init() { 5351 Services.obs.addObserver(this, "a11y-init-or-shutdown", true); 5352 }, 5353 5354 QueryInterface: ChromeUtils.generateQI([ 5355 "nsIObserver", 5356 "nsISupportsWeakReference", 5357 ]), 5358 5359 observe(subject, topic, data) { 5360 if (topic == "a11y-init-or-shutdown" && data == "1") { 5361 Services.tm.dispatchToMainThread(() => this._checkVersionAndPrompt()); 5362 } 5363 }, 5364 5365 onWindowsRestored() { 5366 Services.tm.dispatchToMainThread(() => this._checkVersionAndPrompt()); 5367 }, 5368 5369 _checkVersionAndPrompt() { 5370 // Make sure we only prompt for versions of JAWS we do not 5371 // support and never prompt if e10s is disabled or if we're on 5372 // nightly. 5373 if ( 5374 !Services.appinfo.shouldBlockIncompatJaws || 5375 !Services.appinfo.browserTabsRemoteAutostart || 5376 AppConstants.NIGHTLY_BUILD 5377 ) { 5378 return; 5379 } 5380 5381 let win = BrowserWindowTracker.getTopWindow(); 5382 if (!win || !win.gBrowser || !win.gBrowser.selectedBrowser) { 5383 Services.console.logStringMessage( 5384 "Content access support for older versions of JAWS is disabled " + 5385 "due to compatibility issues with this version of Firefox." 5386 ); 5387 this._prompted = false; 5388 return; 5389 } 5390 5391 // Only prompt once per session 5392 if (this._prompted) { 5393 return; 5394 } 5395 this._prompted = true; 5396 5397 let browser = win.gBrowser.selectedBrowser; 5398 5399 // Prompt JAWS users to let them know they need to update 5400 let promptMessage = win.gNavigatorBundle.getFormattedString( 5401 "e10s.accessibilityNotice.jawsMessage", 5402 [gBrandBundle.GetStringFromName("brandShortName")] 5403 ); 5404 let notification; 5405 // main option: an Ok button, keeps running with content accessibility disabled 5406 let mainAction = { 5407 label: win.gNavigatorBundle.getString( 5408 "e10s.accessibilityNotice.acceptButton.label" 5409 ), 5410 accessKey: win.gNavigatorBundle.getString( 5411 "e10s.accessibilityNotice.acceptButton.accesskey" 5412 ), 5413 callback() { 5414 // If the user invoked the button option remove the notification, 5415 // otherwise keep the alert icon around in the address bar. 5416 notification.remove(); 5417 }, 5418 }; 5419 let options = { 5420 popupIconURL: "chrome://browser/skin/e10s-64@2x.png", 5421 persistWhileVisible: true, 5422 persistent: true, 5423 persistence: 100, 5424 }; 5425 5426 notification = win.PopupNotifications.show( 5427 browser, 5428 "e10s_enabled_with_incompat_jaws", 5429 promptMessage, 5430 null, 5431 mainAction, 5432 null, 5433 options 5434 ); 5435 }, 5436}; 5437 5438/** 5439 * AboutHomeStartupCache is responsible for reading and writing the 5440 * initial about:home document from the HTTP cache as a startup 5441 * performance optimization. It only works when the "privileged about 5442 * content process" is enabled and when ENABLED_PREF is set to true. 5443 * 5444 * See https://firefox-source-docs.mozilla.org/browser/components/newtab/docs/v2-system-addon/about_home_startup_cache.html 5445 * for further details. 5446 */ 5447var AboutHomeStartupCache = { 5448 ABOUT_HOME_URI_STRING: "about:home", 5449 SCRIPT_EXTENSION: "script", 5450 ENABLED_PREF: "browser.startup.homepage.abouthome_cache.enabled", 5451 PRELOADED_NEWTAB_PREF: "browser.newtab.preload", 5452 LOG_LEVEL_PREF: "browser.startup.homepage.abouthome_cache.loglevel", 5453 5454 // It's possible that the layout of about:home will change such that 5455 // we want to invalidate any pre-existing caches. We do this by setting 5456 // this meta key in the nsICacheEntry for the page. 5457 // 5458 // The version is currently set to the build ID, meaning that the cache 5459 // is invalidated after every upgrade (like the main startup cache). 5460 CACHE_VERSION_META_KEY: "version", 5461 5462 LOG_NAME: "AboutHomeStartupCache", 5463 5464 // These messages are used to request the "privileged about content process" 5465 // to create the cached document, and then to receive that document. 5466 CACHE_REQUEST_MESSAGE: "AboutHomeStartupCache:CacheRequest", 5467 CACHE_RESPONSE_MESSAGE: "AboutHomeStartupCache:CacheResponse", 5468 CACHE_USAGE_RESULT_MESSAGE: "AboutHomeStartupCache:UsageResult", 5469 5470 // When a "privileged about content process" is launched, this message is 5471 // sent to give it some nsIInputStream's for the about:home document they 5472 // should load. 5473 SEND_STREAMS_MESSAGE: "AboutHomeStartupCache:InputStreams", 5474 5475 // This time in ms is used to debounce messages that are broadcast to 5476 // all about:newtab's, or the preloaded about:newtab. We use those 5477 // messages as a signal that it's likely time to refresh the cache. 5478 CACHE_DEBOUNCE_RATE_MS: 5000, 5479 5480 // This is how long we'll block the AsyncShutdown while waiting for 5481 // the cache to write. If we fail to write within that time, we will 5482 // allow the shutdown to proceed. 5483 SHUTDOWN_CACHE_WRITE_TIMEOUT_MS: 1000, 5484 5485 // The following values are as possible values for the 5486 // browser.startup.abouthome_cache_result scalar. Keep these in sync with the 5487 // scalar definition in Scalars.yaml. See setDeferredResult for more 5488 // information. 5489 CACHE_RESULT_SCALARS: { 5490 UNSET: 0, 5491 DOES_NOT_EXIST: 1, 5492 CORRUPT_PAGE: 2, 5493 CORRUPT_SCRIPT: 3, 5494 INVALIDATED: 4, 5495 LATE: 5, 5496 VALID_AND_USED: 6, 5497 DISABLED: 7, 5498 NOT_LOADING_ABOUTHOME: 8, 5499 PRELOADING_DISABLED: 9, 5500 }, 5501 5502 // This will be set to one of the values of CACHE_RESULT_SCALARS 5503 // once it is determined which result best suits what occurred. 5504 _cacheDeferredResultScalar: -1, 5505 5506 // A reference to the nsICacheEntry to read from and write to. 5507 _cacheEntry: null, 5508 5509 // These nsIPipe's are sent down to the "privileged about content process" 5510 // immediately after the process launches. This allows us to race the loading 5511 // of the cache entry in the parent process with the load of the about:home 5512 // page in the content process, since we'll connect the InputStream's to 5513 // the pipes as soon as the nsICacheEntry is available. 5514 // 5515 // The page pipe is for the HTML markup for the page. 5516 _pagePipe: null, 5517 // The script pipe is for the JavaScript that the HTML markup loads 5518 // to set its internal state. 5519 _scriptPipe: null, 5520 _cacheDeferred: null, 5521 5522 _enabled: false, 5523 _initted: false, 5524 _hasWrittenThisSession: false, 5525 _finalized: false, 5526 _firstPrivilegedProcessCreated: false, 5527 5528 init() { 5529 if (this._initted) { 5530 throw new Error("AboutHomeStartupCache already initted."); 5531 } 5532 5533 this.setDeferredResult(this.CACHE_RESULT_SCALARS.UNSET); 5534 5535 this._enabled = NimbusFeatures.abouthomecache.isEnabled(); 5536 5537 if (!this._enabled) { 5538 this.recordResult(this.CACHE_RESULT_SCALARS.DISABLED); 5539 return; 5540 } 5541 5542 this.log = Log.repository.getLogger(this.LOG_NAME); 5543 this.log.manageLevelFromPref(this.LOG_LEVEL_PREF); 5544 this._appender = new Log.ConsoleAppender(new Log.BasicFormatter()); 5545 this.log.addAppender(this._appender); 5546 5547 this.log.trace("Initting."); 5548 5549 // If the user is not configured to load about:home at startup, then 5550 // let's not bother with the cache - loading it needlessly is more likely 5551 // to hinder what we're actually trying to load. 5552 let willLoadAboutHome = 5553 !HomePage.overridden && 5554 Services.prefs.getIntPref("browser.startup.page") === 1; 5555 5556 if (!willLoadAboutHome) { 5557 this.log.trace("Not configured to load about:home by default."); 5558 this.recordResult(this.CACHE_RESULT_SCALARS.NOT_LOADING_ABOUTHOME); 5559 return; 5560 } 5561 5562 if (!Services.prefs.getBoolPref(this.PRELOADED_NEWTAB_PREF, false)) { 5563 this.log.trace("Preloaded about:newtab disabled."); 5564 this.recordResult(this.CACHE_RESULT_SCALARS.PRELOADING_DISABLED); 5565 return; 5566 } 5567 5568 Services.obs.addObserver(this, "ipc:content-created"); 5569 Services.obs.addObserver(this, "process-type-set"); 5570 Services.obs.addObserver(this, "ipc:content-shutdown"); 5571 Services.obs.addObserver(this, "intl:app-locales-changed"); 5572 5573 this.log.trace("Constructing pipes."); 5574 this._pagePipe = this.makePipe(); 5575 this._scriptPipe = this.makePipe(); 5576 5577 this._cacheEntryPromise = new Promise(resolve => { 5578 this._cacheEntryResolver = resolve; 5579 }); 5580 5581 let lci = Services.loadContextInfo.default; 5582 let storage = Services.cache2.diskCacheStorage(lci); 5583 try { 5584 storage.asyncOpenURI( 5585 this.aboutHomeURI, 5586 "", 5587 Ci.nsICacheStorage.OPEN_PRIORITY, 5588 this 5589 ); 5590 } catch (e) { 5591 this.log.error("Failed to open about:home cache entry", e); 5592 } 5593 5594 this._cacheTask = new DeferredTask(async () => { 5595 await this.cacheNow(); 5596 }, this.CACHE_DEBOUNCE_RATE_MS); 5597 5598 AsyncShutdown.quitApplicationGranted.addBlocker( 5599 "AboutHomeStartupCache: Writing cache", 5600 async () => { 5601 await this.onShutdown(); 5602 }, 5603 () => this._cacheProgress 5604 ); 5605 5606 this._cacheDeferred = null; 5607 this._initted = true; 5608 this.log.trace("Initialized."); 5609 }, 5610 5611 get initted() { 5612 return this._initted; 5613 }, 5614 5615 uninit() { 5616 if (!this._enabled) { 5617 return; 5618 } 5619 5620 try { 5621 Services.obs.removeObserver(this, "ipc:content-created"); 5622 Services.obs.removeObserver(this, "process-type-set"); 5623 Services.obs.removeObserver(this, "ipc:content-shutdown"); 5624 Services.obs.removeObserver(this, "intl:app-locales-changed"); 5625 } catch (e) { 5626 // If we failed to initialize and register for these observer 5627 // notifications, then attempting to remove them will throw. 5628 // It's fine to ignore that case on shutdown. 5629 } 5630 5631 if (this._cacheTask) { 5632 this._cacheTask.disarm(); 5633 this._cacheTask = null; 5634 } 5635 5636 this._pagePipe = null; 5637 this._scriptPipe = null; 5638 this._initted = false; 5639 this._cacheEntry = null; 5640 this._hasWrittenThisSession = false; 5641 this._cacheEntryPromise = null; 5642 this._cacheEntryResolver = null; 5643 this._cacheDeferredResultScalar = -1; 5644 5645 if (this.log) { 5646 this.log.trace("Uninitialized."); 5647 this.log.removeAppender(this._appender); 5648 this.log = null; 5649 } 5650 5651 this._procManager = null; 5652 this._procManagerID = null; 5653 this._appender = null; 5654 this._cacheDeferred = null; 5655 this._finalized = false; 5656 this._firstPrivilegedProcessCreated = false; 5657 }, 5658 5659 _aboutHomeURI: null, 5660 5661 get aboutHomeURI() { 5662 if (this._aboutHomeURI) { 5663 return this._aboutHomeURI; 5664 } 5665 5666 this._aboutHomeURI = Services.io.newURI(this.ABOUT_HOME_URI_STRING); 5667 return this._aboutHomeURI; 5668 }, 5669 5670 // For the AsyncShutdown blocker, this is used to populate the progress 5671 // value. 5672 _cacheProgress: "Not yet begun", 5673 5674 /** 5675 * Called by the AsyncShutdown blocker on quit-application-granted 5676 * to potentially flush the most recent cache to disk. If one was 5677 * never written during the session, one is generated and written 5678 * before the async function resolves. 5679 * 5680 * @param withTimeout (boolean) 5681 * Whether or not the timeout mechanism should be used. Defaults 5682 * to true. 5683 * @returns Promise 5684 * @resolves boolean 5685 * If a cache has never been written, or a cache write is in 5686 * progress, resolves true when the cache has been written. Also 5687 * resolves to true if a cache didn't need to be written. 5688 * 5689 * Resolves to false if a cache write unexpectedly timed out. 5690 */ 5691 async onShutdown(withTimeout = true) { 5692 // If we never wrote this session, arm the task so that the next 5693 // step can finalize. 5694 if (!this._hasWrittenThisSession) { 5695 this.log.trace("Never wrote a cache this session. Arming cache task."); 5696 this._cacheTask.arm(); 5697 } 5698 5699 Services.telemetry.scalarSet( 5700 "browser.startup.abouthome_cache_shutdownwrite", 5701 this._cacheTask.isArmed 5702 ); 5703 5704 if (this._cacheTask.isArmed) { 5705 this.log.trace("Finalizing cache task on shutdown"); 5706 this._finalized = true; 5707 5708 // To avoid hanging shutdowns, we'll ensure that we wait a maximum of 5709 // SHUTDOWN_CACHE_WRITE_TIMEOUT_MS millseconds before giving up. 5710 let { setTimeout, clearTimeout } = ChromeUtils.import( 5711 "resource://gre/modules/Timer.jsm" 5712 ); 5713 5714 const TIMED_OUT = Symbol(); 5715 let timeoutID = 0; 5716 5717 let timeoutPromise = new Promise(resolve => { 5718 timeoutID = setTimeout( 5719 () => resolve(TIMED_OUT), 5720 this.SHUTDOWN_CACHE_WRITE_TIMEOUT_MS 5721 ); 5722 }); 5723 5724 let promises = [this._cacheTask.finalize()]; 5725 if (withTimeout) { 5726 this.log.trace("Using timeout mechanism."); 5727 promises.push(timeoutPromise); 5728 } else { 5729 this.log.trace("Skipping timeout mechanism."); 5730 } 5731 5732 let result = await Promise.race(promises); 5733 this.log.trace("Done blocking shutdown."); 5734 clearTimeout(timeoutID); 5735 if (result === TIMED_OUT) { 5736 this.log.error("Timed out getting cache streams. Skipping cache task."); 5737 return false; 5738 } 5739 } 5740 this.log.trace("onShutdown is exiting"); 5741 return true; 5742 }, 5743 5744 /** 5745 * Called by the _cacheTask DeferredTask to actually do the work of 5746 * caching the about:home document. 5747 * 5748 * @returns Promise 5749 * @resolves undefined 5750 * Resolves when a fresh version of the cache has been written. 5751 */ 5752 async cacheNow() { 5753 this.log.trace("Caching now."); 5754 this._cacheProgress = "Getting cache streams"; 5755 5756 let { pageInputStream, scriptInputStream } = await this.requestCache(); 5757 5758 if (!pageInputStream || !scriptInputStream) { 5759 this.log.trace("Failed to get cache streams."); 5760 this._cacheProgress = "Failed to get streams"; 5761 return; 5762 } 5763 5764 this.log.trace("Got cache streams."); 5765 5766 this._cacheProgress = "Writing to cache"; 5767 5768 try { 5769 this.log.trace("Populating cache."); 5770 await this.populateCache(pageInputStream, scriptInputStream); 5771 } catch (e) { 5772 this._cacheProgress = "Failed to populate cache"; 5773 this.log.error("Populating the cache failed: ", e); 5774 return; 5775 } 5776 5777 this._cacheProgress = "Done"; 5778 this.log.trace("Done writing to cache."); 5779 this._hasWrittenThisSession = true; 5780 }, 5781 5782 /** 5783 * Requests the cached document streams from the "privileged about content 5784 * process". 5785 * 5786 * @returns Promise 5787 * @resolves Object 5788 * Resolves with an Object with the following properties: 5789 * 5790 * pageInputStream (nsIInputStream) 5791 * The page content to write to the cache, or null if request the streams 5792 * failed. 5793 * 5794 * scriptInputStream (nsIInputStream) 5795 * The script content to write to the cache, or null if request the streams 5796 * failed. 5797 */ 5798 requestCache() { 5799 this.log.trace("Parent is requesting Activity Stream state object."); 5800 if (!this._procManager) { 5801 this.log.error("requestCache called with no _procManager!"); 5802 return { pageInputStream: null, scriptInputStream: null }; 5803 } 5804 5805 if (this._procManager.remoteType != E10SUtils.PRIVILEGEDABOUT_REMOTE_TYPE) { 5806 this.log.error("Somehow got the wrong process type."); 5807 return { pageInputStream: null, scriptInputStream: null }; 5808 } 5809 5810 let state = AboutNewTab.activityStream.store.getState(); 5811 return new Promise(resolve => { 5812 this._cacheDeferred = resolve; 5813 this.log.trace("Parent is requesting cache streams."); 5814 this._procManager.sendAsyncMessage(this.CACHE_REQUEST_MESSAGE, { state }); 5815 }); 5816 }, 5817 5818 /** 5819 * Helper function that returns a newly constructed nsIPipe instance. 5820 * 5821 * @return nsIPipe 5822 */ 5823 makePipe() { 5824 let pipe = Cc["@mozilla.org/pipe;1"].createInstance(Ci.nsIPipe); 5825 pipe.init( 5826 true /* non-blocking input */, 5827 true /* non-blocking output */, 5828 0 /* segment size */, 5829 0 /* max segments */ 5830 ); 5831 return pipe; 5832 }, 5833 5834 get pagePipe() { 5835 return this._pagePipe; 5836 }, 5837 5838 get scriptPipe() { 5839 return this._scriptPipe; 5840 }, 5841 5842 /** 5843 * Called when the nsICacheEntry has been accessed. If the nsICacheEntry 5844 * has content that we want to send down to the "privileged about content 5845 * process", then we connect that content to the nsIPipe's that may or 5846 * may not have already been sent down to the process. 5847 * 5848 * In the event that the nsICacheEntry doesn't contain anything usable, 5849 * the nsInputStreams on the nsIPipe's are closed. 5850 */ 5851 connectToPipes() { 5852 this.log.trace(`Connecting nsICacheEntry to pipes.`); 5853 5854 // If the cache doesn't yet exist, we'll know because the version metadata 5855 // won't exist yet. 5856 let version; 5857 try { 5858 this.log.trace(""); 5859 version = this._cacheEntry.getMetaDataElement( 5860 this.CACHE_VERSION_META_KEY 5861 ); 5862 } catch (e) { 5863 if (e.result == Cr.NS_ERROR_NOT_AVAILABLE) { 5864 this.log.debug("Cache meta data does not exist. Closing streams."); 5865 this.pagePipe.outputStream.close(); 5866 this.scriptPipe.outputStream.close(); 5867 this.setDeferredResult(this.CACHE_RESULT_SCALARS.DOES_NOT_EXIST); 5868 return; 5869 } 5870 5871 throw e; 5872 } 5873 5874 this.log.info("Version retrieved is", version); 5875 5876 if (version != Services.appinfo.appBuildID) { 5877 this.log.info("Version does not match! Dooming and closing streams.\n"); 5878 // This cache is no good - doom it, and prepare for a new one. 5879 this.clearCache(); 5880 this.pagePipe.outputStream.close(); 5881 this.scriptPipe.outputStream.close(); 5882 this.setDeferredResult(this.CACHE_RESULT_SCALARS.INVALIDATED); 5883 return; 5884 } 5885 5886 let cachePageInputStream; 5887 5888 try { 5889 cachePageInputStream = this._cacheEntry.openInputStream(0); 5890 } catch (e) { 5891 this.log.error("Failed to open main input stream for cache entry", e); 5892 this.pagePipe.outputStream.close(); 5893 this.scriptPipe.outputStream.close(); 5894 this.setDeferredResult(this.CACHE_RESULT_SCALARS.CORRUPT_PAGE); 5895 return; 5896 } 5897 5898 this.log.trace("Connecting page stream to pipe."); 5899 NetUtil.asyncCopy(cachePageInputStream, this.pagePipe.outputStream, () => { 5900 this.log.info("Page stream connected to pipe."); 5901 }); 5902 5903 let cacheScriptInputStream; 5904 try { 5905 this.log.trace("Connecting script stream to pipe."); 5906 cacheScriptInputStream = this._cacheEntry.openAlternativeInputStream( 5907 "script" 5908 ); 5909 NetUtil.asyncCopy( 5910 cacheScriptInputStream, 5911 this.scriptPipe.outputStream, 5912 () => { 5913 this.log.info("Script stream connected to pipe."); 5914 } 5915 ); 5916 } catch (e) { 5917 if (e.result == Cr.NS_ERROR_NOT_AVAILABLE) { 5918 // For some reason, the script was not available. We'll close the pipe 5919 // without sending anything into it. The privileged about content process 5920 // will notice that there's nothing available in the pipe, and fall back 5921 // to dynamically generating the page. 5922 this.log.error("Script stream not available! Closing pipe."); 5923 this.scriptPipe.outputStream.close(); 5924 this.setDeferredResult(this.CACHE_RESULT_SCALARS.CORRUPT_SCRIPT); 5925 } else { 5926 throw e; 5927 } 5928 } 5929 5930 this.setDeferredResult(this.CACHE_RESULT_SCALARS.VALID_AND_USED); 5931 this.log.trace("Streams connected to pipes."); 5932 }, 5933 5934 /** 5935 * Called when we have received a the cache values from the "privileged 5936 * about content process". The page and script streams are written to 5937 * the nsICacheEntry. 5938 * 5939 * This writing is asynchronous, and if a write happens to already be 5940 * underway when this function is called, that latter call will be 5941 * ignored. 5942 * 5943 * @param pageInputStream (nsIInputStream) 5944 * A stream containing the HTML markup to be saved to the cache. 5945 * @param scriptInputStream (nsIInputStream) 5946 * A stream containing the JS hydration script to be saved to the cache. 5947 * @returns Promise 5948 * @resolves undefined 5949 * When the cache has been successfully written to. 5950 * @rejects Error 5951 * Rejects with a JS Error if writing any part of the cache happens to 5952 * fail. 5953 */ 5954 async populateCache(pageInputStream, scriptInputStream) { 5955 await this.ensureCacheEntry(); 5956 5957 await new Promise((resolve, reject) => { 5958 // Doom the old cache entry, so we can start writing to a new one. 5959 this.log.trace("Populating the cache. Dooming old entry."); 5960 this.clearCache(); 5961 5962 this.log.trace("Opening the page output stream."); 5963 let pageOutputStream; 5964 try { 5965 pageOutputStream = this._cacheEntry.openOutputStream(0, -1); 5966 } catch (e) { 5967 reject(e); 5968 return; 5969 } 5970 5971 this.log.info("Writing the page cache."); 5972 NetUtil.asyncCopy(pageInputStream, pageOutputStream, pageResult => { 5973 if (!Components.isSuccessCode(pageResult)) { 5974 this.log.error("Failed to write page. Result: " + pageResult); 5975 reject(new Error(pageResult)); 5976 return; 5977 } 5978 5979 this.log.trace( 5980 "Writing the page data is complete. Now opening the " + 5981 "script output stream." 5982 ); 5983 5984 let scriptOutputStream; 5985 try { 5986 scriptOutputStream = this._cacheEntry.openAlternativeOutputStream( 5987 "script", 5988 -1 5989 ); 5990 } catch (e) { 5991 reject(e); 5992 return; 5993 } 5994 5995 this.log.info("Writing the script cache."); 5996 NetUtil.asyncCopy( 5997 scriptInputStream, 5998 scriptOutputStream, 5999 scriptResult => { 6000 if (!Components.isSuccessCode(scriptResult)) { 6001 this.log.error("Failed to write script. Result: " + scriptResult); 6002 reject(new Error(scriptResult)); 6003 return; 6004 } 6005 6006 this.log.trace( 6007 "Writing the script cache is done. Setting version." 6008 ); 6009 try { 6010 this._cacheEntry.setMetaDataElement( 6011 "version", 6012 Services.appinfo.appBuildID 6013 ); 6014 } catch (e) { 6015 this.log.error("Failed to write version."); 6016 reject(e); 6017 return; 6018 } 6019 this.log.trace(`Version is set to ${Services.appinfo.appBuildID}.`); 6020 this.log.info("Caching of page and script is done."); 6021 resolve(); 6022 } 6023 ); 6024 }); 6025 }); 6026 6027 this.log.trace("populateCache has finished."); 6028 }, 6029 6030 /** 6031 * Returns a Promise that resolves once the nsICacheEntry for the cache 6032 * is available to write to and read from. 6033 * 6034 * @returns Promise 6035 * @resolves nsICacheEntry 6036 * Once the cache entry has become available. 6037 * @rejects String 6038 * Rejects with an error message if getting the cache entry is attempted 6039 * before the AboutHomeStartupCache component has been initialized. 6040 */ 6041 ensureCacheEntry() { 6042 if (!this._initted) { 6043 return Promise.reject( 6044 "Cannot ensureCacheEntry - AboutHomeStartupCache is not initted" 6045 ); 6046 } 6047 6048 return this._cacheEntryPromise; 6049 }, 6050 6051 /** 6052 * Clears the contents of the cache. 6053 */ 6054 clearCache() { 6055 this.log.trace("Clearing the cache."); 6056 this._cacheEntry = this._cacheEntry.recreate(); 6057 this._cacheEntryPromise = new Promise(resolve => { 6058 resolve(this._cacheEntry); 6059 }); 6060 this._hasWrittenThisSession = false; 6061 }, 6062 6063 /** 6064 * Called when a content process is created. If this is the "privileged 6065 * about content process", then the cache streams will be sent to it. 6066 * 6067 * @param childID (Number) 6068 * The unique ID for the content process that was created, as passed by 6069 * ipc:content-created. 6070 * @param procManager (ProcessMessageManager) 6071 * The ProcessMessageManager for the created content process. 6072 * @param processParent 6073 * The nsIDOMProcessParent for the tab. 6074 */ 6075 onContentProcessCreated(childID, procManager, processParent) { 6076 if (procManager.remoteType == E10SUtils.PRIVILEGEDABOUT_REMOTE_TYPE) { 6077 if (this._finalized) { 6078 this.log.trace( 6079 "Ignoring privileged about content process launch after finalization." 6080 ); 6081 return; 6082 } 6083 6084 if (this._firstPrivilegedProcessCreated) { 6085 this.log.trace( 6086 "Ignoring non-first privileged about content processes." 6087 ); 6088 return; 6089 } 6090 6091 this.log.trace( 6092 `A privileged about content process is launching with ID ${childID}.` 6093 ); 6094 6095 this.log.info("Sending input streams down to content process."); 6096 let actor = processParent.getActor("BrowserProcess"); 6097 actor.sendAsyncMessage(this.SEND_STREAMS_MESSAGE, { 6098 pageInputStream: this.pagePipe.inputStream, 6099 scriptInputStream: this.scriptPipe.inputStream, 6100 }); 6101 6102 procManager.addMessageListener(this.CACHE_RESPONSE_MESSAGE, this); 6103 procManager.addMessageListener(this.CACHE_USAGE_RESULT_MESSAGE, this); 6104 this._procManager = procManager; 6105 this._procManagerID = childID; 6106 this._firstPrivilegedProcessCreated = true; 6107 } 6108 }, 6109 6110 /** 6111 * Called when a content process is destroyed. Either it shut down normally, 6112 * or it crashed. If this is the "privileged about content process", then some 6113 * internal state is cleared. 6114 * 6115 * @param childID (Number) 6116 * The unique ID for the content process that was created, as passed by 6117 * ipc:content-shutdown. 6118 */ 6119 onContentProcessShutdown(childID) { 6120 this.log.info(`Content process shutdown: ${childID}`); 6121 if (this._procManagerID == childID) { 6122 this.log.info("It was the current privileged about process."); 6123 if (this._cacheDeferred) { 6124 this.log.error( 6125 "A privileged about content process shut down while cache streams " + 6126 "were still en route." 6127 ); 6128 // The crash occurred while we were waiting on cache input streams to 6129 // be returned to us. Resolve with null streams instead. 6130 this._cacheDeferred({ pageInputStream: null, scriptInputStream: null }); 6131 this._cacheDeferred = null; 6132 } 6133 6134 this._procManager.removeMessageListener( 6135 this.CACHE_RESPONSE_MESSAGE, 6136 this 6137 ); 6138 this._procManager.removeMessageListener( 6139 this.CACHE_USAGE_RESULT_MESSAGE, 6140 this 6141 ); 6142 this._procManager = null; 6143 this._procManagerID = null; 6144 } 6145 }, 6146 6147 /** 6148 * Called externally by ActivityStreamMessageChannel anytime 6149 * a message is broadcast to all about:newtabs, or sent to the 6150 * preloaded about:newtab. This is used to determine if we need 6151 * to refresh the cache. 6152 */ 6153 onPreloadedNewTabMessage() { 6154 if (!this._initted || !this._enabled) { 6155 return; 6156 } 6157 6158 if (this._finalized) { 6159 this.log.trace("Ignoring preloaded newtab update after finalization."); 6160 return; 6161 } 6162 6163 this.log.trace("Preloaded about:newtab was updated."); 6164 6165 this._cacheTask.disarm(); 6166 this._cacheTask.arm(); 6167 }, 6168 6169 /** 6170 * Stores the CACHE_RESULT_SCALARS value that most accurately represents 6171 * the current notion of how the cache has operated so far. It is stored 6172 * temporarily like this because we need to hear from the privileged 6173 * about content process to hear whether or not retrieving the cache 6174 * actually worked on that end. The success state reported back from 6175 * the privileged about content process will be compared against the 6176 * deferred result scalar to compute what will be recorded to 6177 * Telemetry. 6178 * 6179 * Note that this value will only be recorded if its value is GREATER 6180 * than the currently recorded value. This is because it's possible for 6181 * certain functions that record results to re-enter - but we want to record 6182 * the _first_ condition that caused the cache to not be read from. 6183 * 6184 * @param result (Number) 6185 * One of the CACHE_RESULT_SCALARS values. If this value is less than 6186 * the currently recorded value, it is ignored. 6187 */ 6188 setDeferredResult(result) { 6189 if (this._cacheDeferredResultScalar < result) { 6190 this._cacheDeferredResultScalar = result; 6191 } 6192 }, 6193 6194 /** 6195 * Records the final result of how the cache operated for the user 6196 * during this session to Telemetry. 6197 */ 6198 recordResult(result) { 6199 // Note: this can be called very early on in the lifetime of 6200 // AboutHomeStartupCache, so things like this.log might not exist yet. 6201 Services.telemetry.scalarSet( 6202 "browser.startup.abouthome_cache_result", 6203 result 6204 ); 6205 }, 6206 6207 /** 6208 * Called when the parent process receives a message from the privileged 6209 * about content process saying whether or not reading from the cache 6210 * was successful. 6211 * 6212 * @param success (boolean) 6213 * True if reading from the cache succeeded. 6214 */ 6215 onUsageResult(success) { 6216 this.log.trace(`Received usage result. Success = ${success}`); 6217 if (success) { 6218 if ( 6219 this._cacheDeferredResultScalar != 6220 this.CACHE_RESULT_SCALARS.VALID_AND_USED 6221 ) { 6222 this.log.error( 6223 "Somehow got a success result despite having never " + 6224 "successfully sent down the cache streams" 6225 ); 6226 this.recordResult(this._cacheDeferredResultScalar); 6227 } else { 6228 this.recordResult(this.CACHE_RESULT_SCALARS.VALID_AND_USED); 6229 } 6230 6231 return; 6232 } 6233 6234 if ( 6235 this._cacheDeferredResultScalar == 6236 this.CACHE_RESULT_SCALARS.VALID_AND_USED 6237 ) { 6238 // We failed to read from the cache despite having successfully 6239 // sent it down to the content process. We presume then that the 6240 // streams just didn't provide any bytes in time. 6241 this.recordResult(this.CACHE_RESULT_SCALARS.LATE); 6242 } else { 6243 // We failed to read the cache, but already knew why. We can 6244 // now record that value. 6245 this.recordResult(this._cacheDeferredResultScalar); 6246 } 6247 }, 6248 6249 QueryInterface: ChromeUtils.generateQI([ 6250 "nsICacheEntryOpenallback", 6251 "nsIObserver", 6252 ]), 6253 6254 /** MessageListener **/ 6255 6256 receiveMessage(message) { 6257 // Only the privileged about content process can write to the cache. 6258 if (message.target.remoteType != E10SUtils.PRIVILEGEDABOUT_REMOTE_TYPE) { 6259 this.log.error( 6260 "Received a message from a non-privileged content process!" 6261 ); 6262 return; 6263 } 6264 6265 switch (message.name) { 6266 case this.CACHE_RESPONSE_MESSAGE: { 6267 this.log.trace("Parent received cache streams."); 6268 if (!this._cacheDeferred) { 6269 this.log.error("Parent doesn't have _cacheDeferred set up!"); 6270 return; 6271 } 6272 6273 this._cacheDeferred(message.data); 6274 this._cacheDeferred = null; 6275 break; 6276 } 6277 case this.CACHE_USAGE_RESULT_MESSAGE: { 6278 this.onUsageResult(message.data.success); 6279 break; 6280 } 6281 } 6282 }, 6283 6284 /** nsIObserver **/ 6285 6286 observe(aSubject, aTopic, aData) { 6287 switch (aTopic) { 6288 case "intl:app-locales-changed": { 6289 this.clearCache(); 6290 break; 6291 } 6292 case "process-type-set": 6293 // Intentional fall-through 6294 case "ipc:content-created": { 6295 let childID = aData; 6296 let procManager = aSubject 6297 .QueryInterface(Ci.nsIInterfaceRequestor) 6298 .getInterface(Ci.nsIMessageSender); 6299 let pp = aSubject.QueryInterface(Ci.nsIDOMProcessParent); 6300 this.onContentProcessCreated(childID, procManager, pp); 6301 break; 6302 } 6303 6304 case "ipc:content-shutdown": { 6305 let childID = aData; 6306 this.onContentProcessShutdown(childID); 6307 break; 6308 } 6309 } 6310 }, 6311 6312 /** nsICacheEntryOpenCallback **/ 6313 6314 onCacheEntryCheck(aEntry) { 6315 return Ci.nsICacheEntryOpenCallback.ENTRY_WANTED; 6316 }, 6317 6318 onCacheEntryAvailable(aEntry, aNew, aResult) { 6319 this.log.trace("Cache entry is available."); 6320 6321 this._cacheEntry = aEntry; 6322 this.connectToPipes(); 6323 this._cacheEntryResolver(this._cacheEntry); 6324 }, 6325}; 6326