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