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