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