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