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