Bug 1824490 - Use the end page value rather than the start page value of the previous...
[gecko.git] / browser / components / preferences / privacy.js
blob203ee389bb827dfce459c45d862854214d86542e
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 file,
3  * You can obtain one at http://mozilla.org/MPL/2.0/. */
5 /* import-globals-from extensionControlled.js */
6 /* import-globals-from preferences.js */
8 const PREF_UPLOAD_ENABLED = "datareporting.healthreport.uploadEnabled";
10 const TRACKING_PROTECTION_KEY = "websites.trackingProtectionMode";
11 const TRACKING_PROTECTION_PREFS = [
12   "privacy.trackingprotection.enabled",
13   "privacy.trackingprotection.pbmode.enabled",
15 const CONTENT_BLOCKING_PREFS = [
16   "privacy.trackingprotection.enabled",
17   "privacy.trackingprotection.pbmode.enabled",
18   "network.cookie.cookieBehavior",
19   "privacy.trackingprotection.fingerprinting.enabled",
20   "privacy.trackingprotection.cryptomining.enabled",
21   "privacy.firstparty.isolate",
24 const PREF_URLBAR_QUICKSUGGEST_BLOCKLIST =
25   "browser.urlbar.quicksuggest.blockedDigests";
26 const PREF_URLBAR_WEATHER_USER_ENABLED = "browser.urlbar.suggest.weather";
29  * Prefs that are unique to sanitizeOnShutdown and are not shared
30  * with the deleteOnClose mechanism like privacy.clearOnShutdown.cookies, -cache and -offlineApps
31  */
32 const SANITIZE_ON_SHUTDOWN_PREFS_ONLY = [
33   "privacy.clearOnShutdown.history",
34   "privacy.clearOnShutdown.downloads",
35   "privacy.clearOnShutdown.sessions",
36   "privacy.clearOnShutdown.formdata",
37   "privacy.clearOnShutdown.siteSettings",
40 const PREF_OPT_OUT_STUDIES_ENABLED = "app.shield.optoutstudies.enabled";
41 const PREF_NORMANDY_ENABLED = "app.normandy.enabled";
43 const PREF_ADDON_RECOMMENDATIONS_ENABLED = "browser.discovery.enabled";
45 const PREF_PASSWORD_GENERATION_AVAILABLE = "signon.generation.available";
46 const { BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN } = Ci.nsICookieService;
48 const PASSWORD_MANAGER_PREF_ID = "services.passwordSavingEnabled";
50 XPCOMUtils.defineLazyGetter(this, "AlertsServiceDND", function() {
51   try {
52     let alertsService = Cc["@mozilla.org/alerts-service;1"]
53       .getService(Ci.nsIAlertsService)
54       .QueryInterface(Ci.nsIAlertsDoNotDisturb);
55     // This will throw if manualDoNotDisturb isn't implemented.
56     alertsService.manualDoNotDisturb;
57     return alertsService;
58   } catch (ex) {
59     return undefined;
60   }
61 });
63 XPCOMUtils.defineLazyPreferenceGetter(
64   this,
65   "OS_AUTH_ENABLED",
66   "signon.management.page.os-auth.enabled",
67   true
70 XPCOMUtils.defineLazyPreferenceGetter(
71   this,
72   "gIsFirstPartyIsolated",
73   "privacy.firstparty.isolate",
74   false
77 Preferences.addAll([
78   // Content blocking / Tracking Protection
79   { id: "privacy.trackingprotection.enabled", type: "bool" },
80   { id: "privacy.trackingprotection.pbmode.enabled", type: "bool" },
81   { id: "privacy.trackingprotection.fingerprinting.enabled", type: "bool" },
82   { id: "privacy.trackingprotection.cryptomining.enabled", type: "bool" },
84   // Social tracking
85   { id: "privacy.trackingprotection.socialtracking.enabled", type: "bool" },
86   { id: "privacy.socialtracking.block_cookies.enabled", type: "bool" },
88   // Tracker list
89   { id: "urlclassifier.trackingTable", type: "string" },
91   // Button prefs
92   { id: "pref.privacy.disable_button.cookie_exceptions", type: "bool" },
93   { id: "pref.privacy.disable_button.view_cookies", type: "bool" },
94   { id: "pref.privacy.disable_button.change_blocklist", type: "bool" },
95   {
96     id: "pref.privacy.disable_button.tracking_protection_exceptions",
97     type: "bool",
98   },
100   // Location Bar
101   { id: "browser.urlbar.suggest.bestmatch", type: "bool" },
102   { id: "browser.urlbar.suggest.bookmark", type: "bool" },
103   { id: "browser.urlbar.suggest.history", type: "bool" },
104   { id: "browser.urlbar.suggest.openpage", type: "bool" },
105   { id: "browser.urlbar.suggest.topsites", type: "bool" },
106   { id: "browser.urlbar.suggest.engines", type: "bool" },
107   { id: "browser.urlbar.suggest.quicksuggest.nonsponsored", type: "bool" },
108   { id: "browser.urlbar.suggest.quicksuggest.sponsored", type: "bool" },
109   { id: "browser.urlbar.quicksuggest.dataCollection.enabled", type: "bool" },
110   { id: PREF_URLBAR_QUICKSUGGEST_BLOCKLIST, type: "string" },
111   { id: PREF_URLBAR_WEATHER_USER_ENABLED, type: "bool" },
113   // History
114   { id: "places.history.enabled", type: "bool" },
115   { id: "browser.formfill.enable", type: "bool" },
116   { id: "privacy.history.custom", type: "bool" },
118   // Cookies
119   { id: "network.cookie.cookieBehavior", type: "int" },
120   { id: "network.cookie.blockFutureCookies", type: "bool" },
121   // Content blocking category
122   { id: "browser.contentblocking.category", type: "string" },
123   { id: "browser.contentblocking.features.strict", type: "string" },
125   // Clear Private Data
126   { id: "privacy.sanitize.sanitizeOnShutdown", type: "bool" },
127   { id: "privacy.sanitize.timeSpan", type: "int" },
128   { id: "privacy.clearOnShutdown.cookies", type: "bool" },
129   { id: "privacy.clearOnShutdown.cache", type: "bool" },
130   { id: "privacy.clearOnShutdown.offlineApps", type: "bool" },
131   { id: "privacy.clearOnShutdown.history", type: "bool" },
132   { id: "privacy.clearOnShutdown.downloads", type: "bool" },
133   { id: "privacy.clearOnShutdown.sessions", type: "bool" },
134   { id: "privacy.clearOnShutdown.formdata", type: "bool" },
135   { id: "privacy.clearOnShutdown.siteSettings", type: "bool" },
137   // Do not track
138   { id: "privacy.donottrackheader.enabled", type: "bool" },
140   // Media
141   { id: "media.autoplay.default", type: "int" },
143   // Popups
144   { id: "dom.disable_open_during_load", type: "bool" },
145   // Passwords
146   { id: "signon.rememberSignons", type: "bool" },
147   { id: "signon.generation.enabled", type: "bool" },
148   { id: "signon.autofillForms", type: "bool" },
149   { id: "signon.management.page.breach-alerts.enabled", type: "bool" },
150   { id: "signon.firefoxRelay.feature", type: "string" },
152   // Buttons
153   { id: "pref.privacy.disable_button.view_passwords", type: "bool" },
154   { id: "pref.privacy.disable_button.view_passwords_exceptions", type: "bool" },
156   /* Certificates tab
157    * security.default_personal_cert
158    *   - a string:
159    *       "Select Automatically"   select a certificate automatically when a site
160    *                                requests one
161    *       "Ask Every Time"         present a dialog to the user so he can select
162    *                                the certificate to use on a site which
163    *                                requests one
164    */
165   { id: "security.default_personal_cert", type: "string" },
167   { id: "security.disable_button.openCertManager", type: "bool" },
169   { id: "security.disable_button.openDeviceManager", type: "bool" },
171   { id: "security.OCSP.enabled", type: "int" },
173   // Add-ons, malware, phishing
174   { id: "xpinstall.whitelist.required", type: "bool" },
176   { id: "browser.safebrowsing.malware.enabled", type: "bool" },
177   { id: "browser.safebrowsing.phishing.enabled", type: "bool" },
179   { id: "browser.safebrowsing.downloads.enabled", type: "bool" },
181   { id: "urlclassifier.malwareTable", type: "string" },
183   {
184     id: "browser.safebrowsing.downloads.remote.block_potentially_unwanted",
185     type: "bool",
186   },
187   { id: "browser.safebrowsing.downloads.remote.block_uncommon", type: "bool" },
189   // First-Party Isolation
190   { id: "privacy.firstparty.isolate", type: "bool" },
192   // HTTPS-Only
193   { id: "dom.security.https_only_mode", type: "bool" },
194   { id: "dom.security.https_only_mode_pbm", type: "bool" },
196   // Windows SSO
197   { id: "network.http.windows-sso.enabled", type: "bool" },
199   // Quick Actions
200   { id: "browser.urlbar.quickactions.showPrefs", type: "bool" },
201   { id: "browser.urlbar.suggest.quickactions", type: "bool" },
203   // Cookie Banner Handling
204   { id: "cookiebanners.ui.desktop.enabled", type: "bool" },
205   { id: "cookiebanners.service.mode", type: "int" },
206   { id: "cookiebanners.service.detectOnly", type: "bool" },
209 // Study opt out
210 if (AppConstants.MOZ_DATA_REPORTING) {
211   Preferences.addAll([
212     // Preference instances for prefs that we need to monitor while the page is open.
213     { id: PREF_OPT_OUT_STUDIES_ENABLED, type: "bool" },
214     { id: PREF_ADDON_RECOMMENDATIONS_ENABLED, type: "bool" },
215     { id: PREF_UPLOAD_ENABLED, type: "bool" },
216   ]);
218 // Privacy segmentation section
219 Preferences.add({
220   id: "browser.dataFeatureRecommendations.enabled",
221   type: "bool",
224 // Data Choices tab
225 if (AppConstants.MOZ_CRASHREPORTER) {
226   Preferences.add({
227     id: "browser.crashReports.unsubmittedCheck.autoSubmit2",
228     type: "bool",
229   });
232 function setEventListener(aId, aEventType, aCallback) {
233   document
234     .getElementById(aId)
235     .addEventListener(aEventType, aCallback.bind(gPrivacyPane));
238 function setSyncFromPrefListener(aId, aCallback) {
239   Preferences.addSyncFromPrefListener(document.getElementById(aId), aCallback);
242 function setSyncToPrefListener(aId, aCallback) {
243   Preferences.addSyncToPrefListener(document.getElementById(aId), aCallback);
246 function dataCollectionCheckboxHandler({
247   checkbox,
248   pref,
249   matchPref = () => true,
250   isDisabled = () => false,
251 }) {
252   function updateCheckbox() {
253     let collectionEnabled = Services.prefs.getBoolPref(
254       PREF_UPLOAD_ENABLED,
255       false
256     );
258     if (collectionEnabled && matchPref()) {
259       if (Services.prefs.getBoolPref(pref, false)) {
260         checkbox.setAttribute("checked", "true");
261       } else {
262         checkbox.removeAttribute("checked");
263       }
264       checkbox.setAttribute("preference", pref);
265     } else {
266       checkbox.removeAttribute("preference");
267       checkbox.removeAttribute("checked");
268     }
270     checkbox.disabled =
271       !collectionEnabled || Services.prefs.prefIsLocked(pref) || isDisabled();
272   }
274   Preferences.get(PREF_UPLOAD_ENABLED).on("change", updateCheckbox);
275   updateCheckbox();
278 // Sets the "Learn how" SUMO link in the Strict/Custom options of Content Blocking.
279 function setUpContentBlockingWarnings() {
280   document.getElementById(
281     "fpiIncompatibilityWarning"
282   ).hidden = !gIsFirstPartyIsolated;
285 function initTCPStandardSection() {
286   let cookieBehaviorPref = Preferences.get("network.cookie.cookieBehavior");
287   let updateTCPSectionVisibilityState = () => {
288     document.getElementById("etpStandardTCPBox").hidden =
289       cookieBehaviorPref.value !=
290       Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN;
291   };
293   cookieBehaviorPref.on("change", updateTCPSectionVisibilityState);
295   updateTCPSectionVisibilityState();
298 var gPrivacyPane = {
299   _pane: null,
301   /**
302    * Whether the prompt to restart Firefox should appear when changing the autostart pref.
303    */
304   _shouldPromptForRestart: true,
306   /**
307    * Update the tracking protection UI to deal with extension control.
308    */
309   _updateTrackingProtectionUI() {
310     let cBPrefisLocked = CONTENT_BLOCKING_PREFS.some(pref =>
311       Services.prefs.prefIsLocked(pref)
312     );
313     let tPPrefisLocked = TRACKING_PROTECTION_PREFS.some(pref =>
314       Services.prefs.prefIsLocked(pref)
315     );
317     function setInputsDisabledState(isControlled) {
318       let tpDisabled = tPPrefisLocked || isControlled;
319       let disabled = cBPrefisLocked || isControlled;
320       let tpCheckbox = document.getElementById(
321         "contentBlockingTrackingProtectionCheckbox"
322       );
323       // Only enable the TP menu if Detect All Trackers is enabled.
324       document.getElementById("trackingProtectionMenu").disabled =
325         tpDisabled || !tpCheckbox.checked;
326       tpCheckbox.disabled = tpDisabled;
328       document.getElementById("standardRadio").disabled = disabled;
329       document.getElementById("strictRadio").disabled = disabled;
330       document
331         .getElementById("contentBlockingOptionStrict")
332         .classList.toggle("disabled", disabled);
333       document
334         .getElementById("contentBlockingOptionStandard")
335         .classList.toggle("disabled", disabled);
336       let arrowButtons = document.querySelectorAll("button.arrowhead");
337       for (let button of arrowButtons) {
338         button.disabled = disabled;
339       }
341       // Notify observers that the TP UI has been updated.
342       // This is needed since our tests need to be notified about the
343       // trackingProtectionMenu element getting disabled/enabled at the right time.
344       Services.obs.notifyObservers(window, "privacy-pane-tp-ui-updated");
345     }
347     let policy = Services.policies.getActivePolicies();
348     if (
349       policy &&
350       ((policy.EnableTrackingProtection &&
351         policy.EnableTrackingProtection.Locked) ||
352         (policy.Cookies && policy.Cookies.Locked))
353     ) {
354       setInputsDisabledState(true);
355     }
356     if (tPPrefisLocked) {
357       // An extension can't control this setting if either pref is locked.
358       hideControllingExtension(TRACKING_PROTECTION_KEY);
359       setInputsDisabledState(false);
360     } else {
361       handleControllingExtension(
362         PREF_SETTING_TYPE,
363         TRACKING_PROTECTION_KEY
364       ).then(setInputsDisabledState);
365     }
366   },
368   /**
369    * Hide the "Change Block List" link for trackers/tracking content in the
370    * custom Content Blocking/ETP panel. By default, it will not be visible.
371    */
372   _showCustomBlockList() {
373     let prefValue = Services.prefs.getBoolPref(
374       "browser.contentblocking.customBlockList.preferences.ui.enabled"
375     );
376     if (!prefValue) {
377       document.getElementById("changeBlockListLink").style.display = "none";
378     } else {
379       setEventListener("changeBlockListLink", "click", this.showBlockLists);
380     }
381   },
383   /**
384    * Set up handlers for showing and hiding controlling extension info
385    * for tracking protection.
386    */
387   _initTrackingProtectionExtensionControl() {
388     setEventListener(
389       "contentBlockingDisableTrackingProtectionExtension",
390       "command",
391       makeDisableControllingExtension(
392         PREF_SETTING_TYPE,
393         TRACKING_PROTECTION_KEY
394       )
395     );
397     let trackingProtectionObserver = {
398       observe(subject, topic, data) {
399         gPrivacyPane._updateTrackingProtectionUI();
400       },
401     };
403     for (let pref of TRACKING_PROTECTION_PREFS) {
404       Services.prefs.addObserver(pref, trackingProtectionObserver);
405     }
406     window.addEventListener("unload", () => {
407       for (let pref of TRACKING_PROTECTION_PREFS) {
408         Services.prefs.removeObserver(pref, trackingProtectionObserver);
409       }
410     });
411   },
413   _initQuickActionsSection() {
414     let showPref = Preferences.get("browser.urlbar.quickactions.showPrefs");
415     let showQuickActionsGroup = () => {
416       document.getElementById("quickActionsBox").hidden = !showPref.value;
417     };
418     showPref.on("change", showQuickActionsGroup);
419     showQuickActionsGroup();
420   },
422   syncFromHttpsOnlyPref() {
423     let httpsOnlyOnPref = Services.prefs.getBoolPref(
424       "dom.security.https_only_mode"
425     );
426     let httpsOnlyOnPBMPref = Services.prefs.getBoolPref(
427       "dom.security.https_only_mode_pbm"
428     );
429     let httpsOnlyRadioGroup = document.getElementById("httpsOnlyRadioGroup");
430     let httpsOnlyExceptionButton = document.getElementById(
431       "httpsOnlyExceptionButton"
432     );
434     if (httpsOnlyOnPref) {
435       httpsOnlyRadioGroup.value = "enabled";
436       httpsOnlyExceptionButton.disabled = false;
437     } else if (httpsOnlyOnPBMPref) {
438       httpsOnlyRadioGroup.value = "privateOnly";
439       httpsOnlyExceptionButton.disabled = true;
440     } else {
441       httpsOnlyRadioGroup.value = "disabled";
442       httpsOnlyExceptionButton.disabled = true;
443     }
445     if (
446       Services.prefs.prefIsLocked("dom.security.https_only_mode") ||
447       Services.prefs.prefIsLocked("dom.security.https_only_mode_pbm")
448     ) {
449       httpsOnlyRadioGroup.disabled = true;
450     }
451   },
453   syncToHttpsOnlyPref() {
454     let value = document.getElementById("httpsOnlyRadioGroup").value;
455     Services.prefs.setBoolPref(
456       "dom.security.https_only_mode_pbm",
457       value == "privateOnly"
458     );
459     Services.prefs.setBoolPref(
460       "dom.security.https_only_mode",
461       value == "enabled"
462     );
463   },
465   /**
466    * Init HTTPS-Only mode and corresponding prefs
467    */
468   initHttpsOnly() {
469     // Set radio-value based on the pref value
470     this.syncFromHttpsOnlyPref();
472     // Create event listener for when the user clicks
473     // on one of the radio buttons
474     setEventListener(
475       "httpsOnlyRadioGroup",
476       "command",
477       this.syncToHttpsOnlyPref
478     );
479     // Update radio-value when the pref changes
480     Preferences.get("dom.security.https_only_mode").on("change", () =>
481       this.syncFromHttpsOnlyPref()
482     );
483     Preferences.get("dom.security.https_only_mode_pbm").on("change", () =>
484       this.syncFromHttpsOnlyPref()
485     );
486   },
488   /**
489    * Sets up the UI for the number of days of history to keep, and updates the
490    * label of the "Clear Now..." button.
491    */
492   init() {
493     this._updateSanitizeSettingsButton();
494     this.initDeleteOnCloseBox();
495     this.syncSanitizationPrefsWithDeleteOnClose();
496     this.initializeHistoryMode();
497     this.updateHistoryModePane();
498     this.updatePrivacyMicroControls();
499     this.initAutoStartPrivateBrowsingReverter();
501     /* Initialize Content Blocking */
502     this.initContentBlocking();
504     this._showCustomBlockList();
505     this.trackingProtectionReadPrefs();
506     this.networkCookieBehaviorReadPrefs();
507     this._initTrackingProtectionExtensionControl();
509     Services.telemetry.setEventRecordingEnabled("pwmgr", true);
511     Preferences.get("privacy.trackingprotection.enabled").on(
512       "change",
513       gPrivacyPane.trackingProtectionReadPrefs.bind(gPrivacyPane)
514     );
515     Preferences.get("privacy.trackingprotection.pbmode.enabled").on(
516       "change",
517       gPrivacyPane.trackingProtectionReadPrefs.bind(gPrivacyPane)
518     );
520     // Watch all of the prefs that the new Cookies & Site Data UI depends on
521     Preferences.get("network.cookie.cookieBehavior").on(
522       "change",
523       gPrivacyPane.networkCookieBehaviorReadPrefs.bind(gPrivacyPane)
524     );
525     Preferences.get("browser.privatebrowsing.autostart").on(
526       "change",
527       gPrivacyPane.networkCookieBehaviorReadPrefs.bind(gPrivacyPane)
528     );
529     Preferences.get("privacy.firstparty.isolate").on(
530       "change",
531       gPrivacyPane.networkCookieBehaviorReadPrefs.bind(gPrivacyPane)
532     );
534     setEventListener(
535       "trackingProtectionExceptions",
536       "command",
537       gPrivacyPane.showTrackingProtectionExceptions
538     );
540     Preferences.get("privacy.sanitize.sanitizeOnShutdown").on(
541       "change",
542       gPrivacyPane._updateSanitizeSettingsButton.bind(gPrivacyPane)
543     );
544     Preferences.get("browser.privatebrowsing.autostart").on(
545       "change",
546       gPrivacyPane.updatePrivacyMicroControls.bind(gPrivacyPane)
547     );
548     setEventListener("historyMode", "command", function() {
549       gPrivacyPane.updateHistoryModePane();
550       gPrivacyPane.updateHistoryModePrefs();
551       gPrivacyPane.updatePrivacyMicroControls();
552       gPrivacyPane.updateAutostart();
553     });
554     setEventListener("clearHistoryButton", "command", function() {
555       let historyMode = document.getElementById("historyMode");
556       // Select "everything" in the clear history dialog if the
557       // user has set their history mode to never remember history.
558       gPrivacyPane.clearPrivateDataNow(historyMode.value == "dontremember");
559     });
560     setEventListener("openSearchEnginePreferences", "click", function(event) {
561       if (event.button == 0) {
562         gotoPref("search");
563       }
564       return false;
565     });
566     setEventListener(
567       "privateBrowsingAutoStart",
568       "command",
569       gPrivacyPane.updateAutostart
570     );
571     setEventListener(
572       "cookieExceptions",
573       "command",
574       gPrivacyPane.showCookieExceptions
575     );
576     setEventListener(
577       "httpsOnlyExceptionButton",
578       "command",
579       gPrivacyPane.showHttpsOnlyModeExceptions
580     );
581     setEventListener(
582       "clearDataSettings",
583       "command",
584       gPrivacyPane.showClearPrivateDataSettings
585     );
586     setEventListener(
587       "passwordExceptions",
588       "command",
589       gPrivacyPane.showPasswordExceptions
590     );
591     setEventListener(
592       "useMasterPassword",
593       "command",
594       gPrivacyPane.updateMasterPasswordButton
595     );
596     setEventListener(
597       "changeMasterPassword",
598       "command",
599       gPrivacyPane.changeMasterPassword
600     );
601     setEventListener("showPasswords", "command", gPrivacyPane.showPasswords);
602     setEventListener(
603       "addonExceptions",
604       "command",
605       gPrivacyPane.showAddonExceptions
606     );
607     setEventListener(
608       "viewCertificatesButton",
609       "command",
610       gPrivacyPane.showCertificates
611     );
612     setEventListener(
613       "viewSecurityDevicesButton",
614       "command",
615       gPrivacyPane.showSecurityDevices
616     );
618     this._pane = document.getElementById("panePrivacy");
620     this._initPasswordGenerationUI();
621     this._initRelayIntegrationUI();
622     this._initMasterPasswordUI();
624     this.initListenersForExtensionControllingPasswordManager();
626     this._initSafeBrowsing();
628     setEventListener(
629       "autoplaySettingsButton",
630       "command",
631       gPrivacyPane.showAutoplayMediaExceptions
632     );
633     setEventListener(
634       "notificationSettingsButton",
635       "command",
636       gPrivacyPane.showNotificationExceptions
637     );
638     setEventListener(
639       "locationSettingsButton",
640       "command",
641       gPrivacyPane.showLocationExceptions
642     );
643     setEventListener(
644       "xrSettingsButton",
645       "command",
646       gPrivacyPane.showXRExceptions
647     );
648     setEventListener(
649       "cameraSettingsButton",
650       "command",
651       gPrivacyPane.showCameraExceptions
652     );
653     setEventListener(
654       "microphoneSettingsButton",
655       "command",
656       gPrivacyPane.showMicrophoneExceptions
657     );
658     document.getElementById(
659       "speakerSettingsRow"
660     ).hidden = !Services.prefs.getBoolPref("media.setsinkid.enabled", false);
661     setEventListener(
662       "speakerSettingsButton",
663       "command",
664       gPrivacyPane.showSpeakerExceptions
665     );
666     setEventListener(
667       "popupPolicyButton",
668       "command",
669       gPrivacyPane.showPopupExceptions
670     );
671     setEventListener(
672       "notificationsDoNotDisturb",
673       "command",
674       gPrivacyPane.toggleDoNotDisturbNotifications
675     );
677     setSyncFromPrefListener("contentBlockingBlockCookiesCheckbox", () =>
678       this.readBlockCookies()
679     );
680     setSyncToPrefListener("contentBlockingBlockCookiesCheckbox", () =>
681       this.writeBlockCookies()
682     );
683     setSyncFromPrefListener("blockCookiesMenu", () =>
684       this.readBlockCookiesFrom()
685     );
686     setSyncToPrefListener("blockCookiesMenu", () =>
687       this.writeBlockCookiesFrom()
688     );
690     setSyncFromPrefListener("savePasswords", () => this.readSavePasswords());
692     let microControlHandler = el =>
693       this.ensurePrivacyMicroControlUncheckedWhenDisabled(el);
694     setSyncFromPrefListener("rememberHistory", microControlHandler);
695     setSyncFromPrefListener("rememberForms", microControlHandler);
696     setSyncFromPrefListener("alwaysClear", microControlHandler);
698     setSyncFromPrefListener("popupPolicy", () =>
699       this.updateButtons("popupPolicyButton", "dom.disable_open_during_load")
700     );
701     setSyncFromPrefListener("warnAddonInstall", () =>
702       this.readWarnAddonInstall()
703     );
704     setSyncFromPrefListener("enableOCSP", () => this.readEnableOCSP());
705     setSyncToPrefListener("enableOCSP", () => this.writeEnableOCSP());
707     if (AlertsServiceDND) {
708       let notificationsDoNotDisturbBox = document.getElementById(
709         "notificationsDoNotDisturbBox"
710       );
711       notificationsDoNotDisturbBox.removeAttribute("hidden");
712       let checkbox = document.getElementById("notificationsDoNotDisturb");
713       document.l10n.setAttributes(checkbox, "permissions-notification-pause");
714       if (AlertsServiceDND.manualDoNotDisturb) {
715         let notificationsDoNotDisturb = document.getElementById(
716           "notificationsDoNotDisturb"
717         );
718         notificationsDoNotDisturb.setAttribute("checked", true);
719       }
720     }
722     this._initAddressBar();
724     this.initSiteDataControls();
725     setEventListener(
726       "clearSiteDataButton",
727       "command",
728       gPrivacyPane.clearSiteData
729     );
730     setEventListener(
731       "siteDataSettings",
732       "command",
733       gPrivacyPane.showSiteDataSettings
734     );
736     this.initCookieBannerHandling();
738     this.initDataCollection();
740     if (AppConstants.MOZ_DATA_REPORTING) {
741       if (AppConstants.MOZ_CRASHREPORTER) {
742         this.initSubmitCrashes();
743       }
744       this.initSubmitHealthReport();
745       setEventListener(
746         "submitHealthReportBox",
747         "command",
748         gPrivacyPane.updateSubmitHealthReport
749       );
750       setEventListener(
751         "telemetryDataDeletionLearnMore",
752         "click",
753         gPrivacyPane.showDataDeletion
754       );
755       if (AppConstants.MOZ_NORMANDY) {
756         this.initOptOutStudyCheckbox();
757       }
758       this.initAddonRecommendationsCheckbox();
759     }
761     let signonBundle = document.getElementById("signonBundle");
762     let pkiBundle = document.getElementById("pkiBundle");
763     appendSearchKeywords("showPasswords", [
764       signonBundle.getString("loginsDescriptionAll2"),
765     ]);
766     appendSearchKeywords("viewSecurityDevicesButton", [
767       pkiBundle.getString("enable_fips"),
768     ]);
770     if (!PrivateBrowsingUtils.enabled) {
771       document.getElementById("privateBrowsingAutoStart").hidden = true;
772       document.querySelector("menuitem[value='dontremember']").hidden = true;
773     }
775     /* init HTTPS-Only mode */
776     this.initHttpsOnly();
778     // Notify observers that the UI is now ready
779     Services.obs.notifyObservers(window, "privacy-pane-loaded");
780   },
782   initSiteDataControls() {
783     Services.obs.addObserver(this, "sitedatamanager:sites-updated");
784     Services.obs.addObserver(this, "sitedatamanager:updating-sites");
785     let unload = () => {
786       window.removeEventListener("unload", unload);
787       Services.obs.removeObserver(this, "sitedatamanager:sites-updated");
788       Services.obs.removeObserver(this, "sitedatamanager:updating-sites");
789     };
790     window.addEventListener("unload", unload);
791     SiteDataManager.updateSites();
792   },
794   // CONTENT BLOCKING
796   /**
797    * Initializes the content blocking section.
798    */
799   initContentBlocking() {
800     setEventListener(
801       "contentBlockingTrackingProtectionCheckbox",
802       "command",
803       this.trackingProtectionWritePrefs
804     );
805     setEventListener(
806       "contentBlockingTrackingProtectionCheckbox",
807       "command",
808       this._updateTrackingProtectionUI
809     );
810     setEventListener(
811       "contentBlockingCryptominersCheckbox",
812       "command",
813       this.updateCryptominingLists
814     );
815     setEventListener(
816       "contentBlockingFingerprintersCheckbox",
817       "command",
818       this.updateFingerprintingLists
819     );
820     setEventListener(
821       "trackingProtectionMenu",
822       "command",
823       this.trackingProtectionWritePrefs
824     );
825     setEventListener("standardArrow", "command", this.toggleExpansion);
826     setEventListener("strictArrow", "command", this.toggleExpansion);
827     setEventListener("customArrow", "command", this.toggleExpansion);
829     Preferences.get("network.cookie.cookieBehavior").on(
830       "change",
831       gPrivacyPane.readBlockCookies.bind(gPrivacyPane)
832     );
833     Preferences.get("browser.contentblocking.category").on(
834       "change",
835       gPrivacyPane.highlightCBCategory
836     );
838     // If any relevant content blocking pref changes, show a warning that the changes will
839     // not be implemented until they refresh their tabs.
840     for (let pref of CONTENT_BLOCKING_PREFS) {
841       Preferences.get(pref).on("change", gPrivacyPane.maybeNotifyUserToReload);
842       // If the value changes, run populateCategoryContents, since that change might have been
843       // triggered by a default value changing in the standard category.
844       Preferences.get(pref).on("change", gPrivacyPane.populateCategoryContents);
845     }
846     Preferences.get("urlclassifier.trackingTable").on(
847       "change",
848       gPrivacyPane.maybeNotifyUserToReload
849     );
850     for (let button of document.querySelectorAll(".reload-tabs-button")) {
851       button.addEventListener("command", gPrivacyPane.reloadAllOtherTabs);
852     }
854     let cryptoMinersOption = document.getElementById(
855       "contentBlockingCryptominersOption"
856     );
857     let fingerprintersOption = document.getElementById(
858       "contentBlockingFingerprintersOption"
859     );
860     let trackingAndIsolateOption = document.querySelector(
861       "#blockCookiesMenu menuitem[value='trackers-plus-isolate']"
862     );
863     cryptoMinersOption.hidden = !Services.prefs.getBoolPref(
864       "browser.contentblocking.cryptomining.preferences.ui.enabled"
865     );
866     fingerprintersOption.hidden = !Services.prefs.getBoolPref(
867       "browser.contentblocking.fingerprinting.preferences.ui.enabled"
868     );
869     let updateTrackingAndIsolateOption = () => {
870       trackingAndIsolateOption.hidden =
871         !Services.prefs.getBoolPref(
872           "browser.contentblocking.reject-and-isolate-cookies.preferences.ui.enabled",
873           false
874         ) || gIsFirstPartyIsolated;
875     };
876     Preferences.get("privacy.firstparty.isolate").on(
877       "change",
878       updateTrackingAndIsolateOption
879     );
880     updateTrackingAndIsolateOption();
882     Preferences.get("browser.contentblocking.features.strict").on(
883       "change",
884       this.populateCategoryContents
885     );
886     this.populateCategoryContents();
887     this.highlightCBCategory();
888     this.readBlockCookies();
890     // Toggles the text "Cross-site and social media trackers" based on the
891     // social tracking pref. If the pref is false, the text reads
892     // "Cross-site trackers".
893     const STP_COOKIES_PREF = "privacy.socialtracking.block_cookies.enabled";
894     if (Services.prefs.getBoolPref(STP_COOKIES_PREF)) {
895       let contentBlockOptionSocialMedia = document.getElementById(
896         "blockCookiesSocialMedia"
897       );
899       document.l10n.setAttributes(
900         contentBlockOptionSocialMedia,
901         "sitedata-option-block-cross-site-tracking-cookies"
902       );
903     }
905     setUpContentBlockingWarnings();
907     initTCPStandardSection();
908   },
910   populateCategoryContents() {
911     for (let type of ["strict", "standard"]) {
912       let rulesArray = [];
913       let selector;
914       if (type == "strict") {
915         selector = "#contentBlockingOptionStrict";
916         rulesArray = Services.prefs
917           .getStringPref("browser.contentblocking.features.strict")
918           .split(",");
919         if (gIsFirstPartyIsolated) {
920           let idx = rulesArray.indexOf("cookieBehavior5");
921           if (idx != -1) {
922             rulesArray[idx] = "cookieBehavior4";
923           }
924         }
925       } else {
926         selector = "#contentBlockingOptionStandard";
927         // In standard show/hide UI items based on the default values of the relevant prefs.
928         let defaults = Services.prefs.getDefaultBranch("");
930         let cookieBehavior = defaults.getIntPref(
931           "network.cookie.cookieBehavior"
932         );
933         switch (cookieBehavior) {
934           case Ci.nsICookieService.BEHAVIOR_ACCEPT:
935             rulesArray.push("cookieBehavior0");
936             break;
937           case Ci.nsICookieService.BEHAVIOR_REJECT_FOREIGN:
938             rulesArray.push("cookieBehavior1");
939             break;
940           case Ci.nsICookieService.BEHAVIOR_REJECT:
941             rulesArray.push("cookieBehavior2");
942             break;
943           case Ci.nsICookieService.BEHAVIOR_LIMIT_FOREIGN:
944             rulesArray.push("cookieBehavior3");
945             break;
946           case Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER:
947             rulesArray.push("cookieBehavior4");
948             break;
949           case BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN:
950             rulesArray.push(
951               gIsFirstPartyIsolated ? "cookieBehavior4" : "cookieBehavior5"
952             );
953             break;
954         }
955         let cookieBehaviorPBM = defaults.getIntPref(
956           "network.cookie.cookieBehavior.pbmode"
957         );
958         switch (cookieBehaviorPBM) {
959           case Ci.nsICookieService.BEHAVIOR_ACCEPT:
960             rulesArray.push("cookieBehaviorPBM0");
961             break;
962           case Ci.nsICookieService.BEHAVIOR_REJECT_FOREIGN:
963             rulesArray.push("cookieBehaviorPBM1");
964             break;
965           case Ci.nsICookieService.BEHAVIOR_REJECT:
966             rulesArray.push("cookieBehaviorPBM2");
967             break;
968           case Ci.nsICookieService.BEHAVIOR_LIMIT_FOREIGN:
969             rulesArray.push("cookieBehaviorPBM3");
970             break;
971           case Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER:
972             rulesArray.push("cookieBehaviorPBM4");
973             break;
974           case BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN:
975             rulesArray.push(
976               gIsFirstPartyIsolated
977                 ? "cookieBehaviorPBM4"
978                 : "cookieBehaviorPBM5"
979             );
980             break;
981         }
982         rulesArray.push(
983           defaults.getBoolPref(
984             "privacy.trackingprotection.cryptomining.enabled"
985           )
986             ? "cm"
987             : "-cm"
988         );
989         rulesArray.push(
990           defaults.getBoolPref(
991             "privacy.trackingprotection.fingerprinting.enabled"
992           )
993             ? "fp"
994             : "-fp"
995         );
996         rulesArray.push(
997           Services.prefs.getBoolPref(
998             "privacy.socialtracking.block_cookies.enabled"
999           )
1000             ? "stp"
1001             : "-stp"
1002         );
1003         rulesArray.push(
1004           defaults.getBoolPref("privacy.trackingprotection.enabled")
1005             ? "tp"
1006             : "-tp"
1007         );
1008         rulesArray.push(
1009           defaults.getBoolPref("privacy.trackingprotection.pbmode.enabled")
1010             ? "tpPrivate"
1011             : "-tpPrivate"
1012         );
1013       }
1015       // Hide all cookie options first, until we learn which one should be showing.
1016       document.querySelector(selector + " .all-cookies-option").hidden = true;
1017       document.querySelector(
1018         selector + " .unvisited-cookies-option"
1019       ).hidden = true;
1020       document.querySelector(
1021         selector + " .cross-site-cookies-option"
1022       ).hidden = true;
1023       document.querySelector(
1024         selector + " .third-party-tracking-cookies-option"
1025       ).hidden = true;
1026       document.querySelector(
1027         selector + " .all-third-party-cookies-private-windows-option"
1028       ).hidden = true;
1029       document.querySelector(
1030         selector + " .all-third-party-cookies-option"
1031       ).hidden = true;
1032       document.querySelector(selector + " .social-media-option").hidden = true;
1034       for (let item of rulesArray) {
1035         // Note "cookieBehavior0", will result in no UI changes, so is not listed here.
1036         switch (item) {
1037           case "tp":
1038             document.querySelector(
1039               selector + " .trackers-option"
1040             ).hidden = false;
1041             break;
1042           case "-tp":
1043             document.querySelector(
1044               selector + " .trackers-option"
1045             ).hidden = true;
1046             break;
1047           case "tpPrivate":
1048             document.querySelector(
1049               selector + " .pb-trackers-option"
1050             ).hidden = false;
1051             break;
1052           case "-tpPrivate":
1053             document.querySelector(
1054               selector + " .pb-trackers-option"
1055             ).hidden = true;
1056             break;
1057           case "fp":
1058             document.querySelector(
1059               selector + " .fingerprinters-option"
1060             ).hidden = false;
1061             break;
1062           case "-fp":
1063             document.querySelector(
1064               selector + " .fingerprinters-option"
1065             ).hidden = true;
1066             break;
1067           case "cm":
1068             document.querySelector(
1069               selector + " .cryptominers-option"
1070             ).hidden = false;
1071             break;
1072           case "-cm":
1073             document.querySelector(
1074               selector + " .cryptominers-option"
1075             ).hidden = true;
1076             break;
1077           case "stp":
1078             // Store social tracking cookies pref
1079             const STP_COOKIES_PREF =
1080               "privacy.socialtracking.block_cookies.enabled";
1082             if (Services.prefs.getBoolPref(STP_COOKIES_PREF)) {
1083               document.querySelector(
1084                 selector + " .social-media-option"
1085               ).hidden = false;
1086             }
1087             break;
1088           case "-stp":
1089             // Store social tracking cookies pref
1090             document.querySelector(
1091               selector + " .social-media-option"
1092             ).hidden = true;
1093             break;
1094           case "cookieBehavior1":
1095             document.querySelector(
1096               selector + " .all-third-party-cookies-option"
1097             ).hidden = false;
1098             break;
1099           case "cookieBehavior2":
1100             document.querySelector(
1101               selector + " .all-cookies-option"
1102             ).hidden = false;
1103             break;
1104           case "cookieBehavior3":
1105             document.querySelector(
1106               selector + " .unvisited-cookies-option"
1107             ).hidden = false;
1108             break;
1109           case "cookieBehavior4":
1110             document.querySelector(
1111               selector + " .third-party-tracking-cookies-option"
1112             ).hidden = false;
1113             break;
1114           case "cookieBehavior5":
1115             document.querySelector(
1116               selector + " .cross-site-cookies-option"
1117             ).hidden = false;
1118             break;
1119           case "cookieBehaviorPBM5":
1120             // We only need to show the cookie option for private windows if the
1121             // cookieBehaviors are different between regular windows and private
1122             // windows.
1123             if (!rulesArray.includes("cookieBehavior5")) {
1124               document.querySelector(
1125                 selector + " .all-third-party-cookies-private-windows-option"
1126               ).hidden = false;
1127             }
1128             break;
1129         }
1130       }
1131       // Hide the "tracking protection in private browsing" list item
1132       // if the "tracking protection enabled in all windows" list item is showing.
1133       if (!document.querySelector(selector + " .trackers-option").hidden) {
1134         document.querySelector(selector + " .pb-trackers-option").hidden = true;
1135       }
1136     }
1137   },
1139   highlightCBCategory() {
1140     let value = Preferences.get("browser.contentblocking.category").value;
1141     let standardEl = document.getElementById("contentBlockingOptionStandard");
1142     let strictEl = document.getElementById("contentBlockingOptionStrict");
1143     let customEl = document.getElementById("contentBlockingOptionCustom");
1144     standardEl.classList.remove("selected");
1145     strictEl.classList.remove("selected");
1146     customEl.classList.remove("selected");
1148     switch (value) {
1149       case "strict":
1150         strictEl.classList.add("selected");
1151         break;
1152       case "custom":
1153         customEl.classList.add("selected");
1154         break;
1155       case "standard":
1156       /* fall through */
1157       default:
1158         standardEl.classList.add("selected");
1159         break;
1160     }
1161   },
1163   updateCryptominingLists() {
1164     let listPrefs = [
1165       "urlclassifier.features.cryptomining.blacklistTables",
1166       "urlclassifier.features.cryptomining.whitelistTables",
1167     ];
1169     let listValue = listPrefs
1170       .map(l => Services.prefs.getStringPref(l))
1171       .join(",");
1172     listManager.forceUpdates(listValue);
1173   },
1175   updateFingerprintingLists() {
1176     let listPrefs = [
1177       "urlclassifier.features.fingerprinting.blacklistTables",
1178       "urlclassifier.features.fingerprinting.whitelistTables",
1179     ];
1181     let listValue = listPrefs
1182       .map(l => Services.prefs.getStringPref(l))
1183       .join(",");
1184     listManager.forceUpdates(listValue);
1185   },
1187   // TRACKING PROTECTION MODE
1189   /**
1190    * Selects the right item of the Tracking Protection menulist and checkbox.
1191    */
1192   trackingProtectionReadPrefs() {
1193     let enabledPref = Preferences.get("privacy.trackingprotection.enabled");
1194     let pbmPref = Preferences.get("privacy.trackingprotection.pbmode.enabled");
1195     let tpMenu = document.getElementById("trackingProtectionMenu");
1196     let tpCheckbox = document.getElementById(
1197       "contentBlockingTrackingProtectionCheckbox"
1198     );
1200     this._updateTrackingProtectionUI();
1202     // Global enable takes precedence over enabled in Private Browsing.
1203     if (enabledPref.value) {
1204       tpMenu.value = "always";
1205       tpCheckbox.checked = true;
1206     } else if (pbmPref.value) {
1207       tpMenu.value = "private";
1208       tpCheckbox.checked = true;
1209     } else {
1210       tpMenu.value = "never";
1211       tpCheckbox.checked = false;
1212     }
1213   },
1215   /**
1216    * Selects the right items of the new Cookies & Site Data UI.
1217    */
1218   networkCookieBehaviorReadPrefs() {
1219     let behavior = Services.cookies.getCookieBehavior(false);
1220     let blockCookiesMenu = document.getElementById("blockCookiesMenu");
1221     let deleteOnCloseCheckbox = document.getElementById("deleteOnClose");
1222     let deleteOnCloseNote = document.getElementById("deleteOnCloseNote");
1223     let blockCookies = behavior != Ci.nsICookieService.BEHAVIOR_ACCEPT;
1224     let cookieBehaviorLocked = Services.prefs.prefIsLocked(
1225       "network.cookie.cookieBehavior"
1226     );
1227     let blockCookiesControlsDisabled = !blockCookies || cookieBehaviorLocked;
1228     blockCookiesMenu.disabled = blockCookiesControlsDisabled;
1230     let completelyBlockCookies =
1231       behavior == Ci.nsICookieService.BEHAVIOR_REJECT;
1232     let privateBrowsing = Preferences.get("browser.privatebrowsing.autostart")
1233       .value;
1234     deleteOnCloseCheckbox.disabled = privateBrowsing || completelyBlockCookies;
1235     deleteOnCloseNote.hidden = !privateBrowsing;
1237     switch (behavior) {
1238       case Ci.nsICookieService.BEHAVIOR_ACCEPT:
1239         break;
1240       case Ci.nsICookieService.BEHAVIOR_REJECT_FOREIGN:
1241         blockCookiesMenu.value = "all-third-parties";
1242         break;
1243       case Ci.nsICookieService.BEHAVIOR_REJECT:
1244         blockCookiesMenu.value = "always";
1245         break;
1246       case Ci.nsICookieService.BEHAVIOR_LIMIT_FOREIGN:
1247         blockCookiesMenu.value = "unvisited";
1248         break;
1249       case Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER:
1250         blockCookiesMenu.value = "trackers";
1251         break;
1252       case BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN:
1253         blockCookiesMenu.value = "trackers-plus-isolate";
1254         break;
1255     }
1256   },
1258   /**
1259    * Sets the pref values based on the selected item of the radiogroup.
1260    */
1261   trackingProtectionWritePrefs() {
1262     let enabledPref = Preferences.get("privacy.trackingprotection.enabled");
1263     let pbmPref = Preferences.get("privacy.trackingprotection.pbmode.enabled");
1264     let stpPref = Preferences.get(
1265       "privacy.trackingprotection.socialtracking.enabled"
1266     );
1267     let stpCookiePref = Preferences.get(
1268       "privacy.socialtracking.block_cookies.enabled"
1269     );
1270     let tpMenu = document.getElementById("trackingProtectionMenu");
1271     let tpCheckbox = document.getElementById(
1272       "contentBlockingTrackingProtectionCheckbox"
1273     );
1275     let value;
1276     if (tpCheckbox.checked) {
1277       if (tpMenu.value == "never") {
1278         tpMenu.value = "private";
1279       }
1280       value = tpMenu.value;
1281     } else {
1282       tpMenu.value = "never";
1283       value = "never";
1284     }
1286     switch (value) {
1287       case "always":
1288         enabledPref.value = true;
1289         pbmPref.value = true;
1290         if (stpCookiePref.value) {
1291           stpPref.value = true;
1292         }
1293         break;
1294       case "private":
1295         enabledPref.value = false;
1296         pbmPref.value = true;
1297         if (stpCookiePref.value) {
1298           stpPref.value = false;
1299         }
1300         break;
1301       case "never":
1302         enabledPref.value = false;
1303         pbmPref.value = false;
1304         if (stpCookiePref.value) {
1305           stpPref.value = false;
1306         }
1307         break;
1308     }
1309   },
1311   toggleExpansion(e) {
1312     let carat = e.target;
1313     carat.classList.toggle("up");
1314     carat.closest(".content-blocking-category").classList.toggle("expanded");
1315     carat.setAttribute(
1316       "aria-expanded",
1317       carat.getAttribute("aria-expanded") === "false"
1318     );
1319   },
1321   // HISTORY MODE
1323   /**
1324    * The list of preferences which affect the initial history mode settings.
1325    * If the auto start private browsing mode pref is active, the initial
1326    * history mode would be set to "Don't remember anything".
1327    * If ALL of these preferences are set to the values that correspond
1328    * to keeping some part of history, and the auto-start
1329    * private browsing mode is not active, the initial history mode would be
1330    * set to "Remember everything".
1331    * Otherwise, the initial history mode would be set to "Custom".
1332    *
1333    * Extensions adding their own preferences can set values here if needed.
1334    */
1335   prefsForKeepingHistory: {
1336     "places.history.enabled": true, // History is enabled
1337     "browser.formfill.enable": true, // Form information is saved
1338     "privacy.sanitize.sanitizeOnShutdown": false, // Private date is NOT cleared on shutdown
1339   },
1341   /**
1342    * The list of control IDs which are dependent on the auto-start private
1343    * browsing setting, such that in "Custom" mode they would be disabled if
1344    * the auto-start private browsing checkbox is checked, and enabled otherwise.
1345    *
1346    * Extensions adding their own controls can append their IDs to this array if needed.
1347    */
1348   dependentControls: [
1349     "rememberHistory",
1350     "rememberForms",
1351     "alwaysClear",
1352     "clearDataSettings",
1353   ],
1355   /**
1356    * Check whether preferences values are set to keep history
1357    *
1358    * @param aPrefs an array of pref names to check for
1359    * @returns boolean true if all of the prefs are set to keep history,
1360    *                  false otherwise
1361    */
1362   _checkHistoryValues(aPrefs) {
1363     for (let pref of Object.keys(aPrefs)) {
1364       if (Preferences.get(pref).value != aPrefs[pref]) {
1365         return false;
1366       }
1367     }
1368     return true;
1369   },
1371   /**
1372    * Initialize the history mode menulist based on the privacy preferences
1373    */
1374   initializeHistoryMode() {
1375     let mode;
1376     let getVal = aPref => Preferences.get(aPref).value;
1378     if (getVal("privacy.history.custom")) {
1379       mode = "custom";
1380     } else if (this._checkHistoryValues(this.prefsForKeepingHistory)) {
1381       if (getVal("browser.privatebrowsing.autostart")) {
1382         mode = "dontremember";
1383       } else {
1384         mode = "remember";
1385       }
1386     } else {
1387       mode = "custom";
1388     }
1390     document.getElementById("historyMode").value = mode;
1391   },
1393   /**
1394    * Update the selected pane based on the history mode menulist
1395    */
1396   updateHistoryModePane() {
1397     let selectedIndex = -1;
1398     switch (document.getElementById("historyMode").value) {
1399       case "remember":
1400         selectedIndex = 0;
1401         break;
1402       case "dontremember":
1403         selectedIndex = 1;
1404         break;
1405       case "custom":
1406         selectedIndex = 2;
1407         break;
1408     }
1409     document.getElementById("historyPane").selectedIndex = selectedIndex;
1410     Preferences.get("privacy.history.custom").value = selectedIndex == 2;
1411   },
1413   /**
1414    * Update the private browsing auto-start pref and the history mode
1415    * micro-management prefs based on the history mode menulist
1416    */
1417   updateHistoryModePrefs() {
1418     let pref = Preferences.get("browser.privatebrowsing.autostart");
1419     switch (document.getElementById("historyMode").value) {
1420       case "remember":
1421         if (pref.value) {
1422           pref.value = false;
1423         }
1425         // select the remember history option if needed
1426         Preferences.get("places.history.enabled").value = true;
1428         // select the remember forms history option
1429         Preferences.get("browser.formfill.enable").value = true;
1431         // select the clear on close option
1432         Preferences.get("privacy.sanitize.sanitizeOnShutdown").value = false;
1433         break;
1434       case "dontremember":
1435         if (!pref.value) {
1436           pref.value = true;
1437         }
1438         break;
1439     }
1440   },
1442   /**
1443    * Update the privacy micro-management controls based on the
1444    * value of the private browsing auto-start preference.
1445    */
1446   updatePrivacyMicroControls() {
1447     let clearDataSettings = document.getElementById("clearDataSettings");
1449     if (document.getElementById("historyMode").value == "custom") {
1450       let disabled = Preferences.get("browser.privatebrowsing.autostart").value;
1451       this.dependentControls.forEach(aElement => {
1452         let control = document.getElementById(aElement);
1453         let preferenceId = control.getAttribute("preference");
1454         if (!preferenceId) {
1455           let dependentControlId = control.getAttribute("control");
1456           if (dependentControlId) {
1457             let dependentControl = document.getElementById(dependentControlId);
1458             preferenceId = dependentControl.getAttribute("preference");
1459           }
1460         }
1462         let preference = preferenceId ? Preferences.get(preferenceId) : {};
1463         control.disabled = disabled || preference.locked;
1464         if (control != clearDataSettings) {
1465           this.ensurePrivacyMicroControlUncheckedWhenDisabled(control);
1466         }
1467       });
1469       clearDataSettings.removeAttribute("hidden");
1471       if (!disabled) {
1472         // adjust the Settings button for sanitizeOnShutdown
1473         this._updateSanitizeSettingsButton();
1474       }
1475     } else {
1476       clearDataSettings.hidden = true;
1477     }
1478   },
1480   ensurePrivacyMicroControlUncheckedWhenDisabled(el) {
1481     if (Preferences.get("browser.privatebrowsing.autostart").value) {
1482       // Set checked to false when called from updatePrivacyMicroControls
1483       el.checked = false;
1484       // return false for the onsyncfrompreference case:
1485       return false;
1486     }
1487     return undefined; // tell preferencesBindings to assign the 'right' value.
1488   },
1490   // CLEAR PRIVATE DATA
1492   /*
1493    * Preferences:
1494    *
1495    * privacy.sanitize.sanitizeOnShutdown
1496    * - true if the user's private data is cleared on startup according to the
1497    *   Clear Private Data settings, false otherwise
1498    */
1500   /**
1501    * Displays the Clear Private Data settings dialog.
1502    */
1503   showClearPrivateDataSettings() {
1504     gSubDialog.open(
1505       "chrome://browser/content/preferences/dialogs/sanitize.xhtml",
1506       { features: "resizable=no" }
1507     );
1508   },
1510   /**
1511    * Displays a dialog from which individual parts of private data may be
1512    * cleared.
1513    */
1514   clearPrivateDataNow(aClearEverything) {
1515     var ts = Preferences.get("privacy.sanitize.timeSpan");
1516     var timeSpanOrig = ts.value;
1518     if (aClearEverything) {
1519       ts.value = 0;
1520     }
1522     gSubDialog.open("chrome://browser/content/sanitize.xhtml", {
1523       features: "resizable=no",
1524       closingCallback: () => {
1525         // reset the timeSpan pref
1526         if (aClearEverything) {
1527           ts.value = timeSpanOrig;
1528         }
1530         Services.obs.notifyObservers(null, "clear-private-data");
1531       },
1532     });
1533   },
1535   /*
1536    * On loading the page, assigns the state to the deleteOnClose checkbox that fits the pref selection
1537    */
1538   initDeleteOnCloseBox() {
1539     let deleteOnCloseBox = document.getElementById("deleteOnClose");
1540     deleteOnCloseBox.checked =
1541       (Preferences.get("privacy.sanitize.sanitizeOnShutdown").value &&
1542         Preferences.get("privacy.clearOnShutdown.cookies").value &&
1543         Preferences.get("privacy.clearOnShutdown.cache").value &&
1544         Preferences.get("privacy.clearOnShutdown.offlineApps").value) ||
1545       Preferences.get("browser.privatebrowsing.autostart").value;
1546   },
1548   /*
1549    * Keeps the state of the deleteOnClose checkbox in sync with the pref selection
1550    */
1551   syncSanitizationPrefsWithDeleteOnClose() {
1552     let deleteOnCloseBox = document.getElementById("deleteOnClose");
1553     let historyMode = Preferences.get("privacy.history.custom");
1554     let sanitizeOnShutdownPref = Preferences.get(
1555       "privacy.sanitize.sanitizeOnShutdown"
1556     );
1557     // ClearOnClose cleaning categories
1558     let cookiePref = Preferences.get("privacy.clearOnShutdown.cookies");
1559     let cachePref = Preferences.get("privacy.clearOnShutdown.cache");
1560     let offlineAppsPref = Preferences.get(
1561       "privacy.clearOnShutdown.offlineApps"
1562     );
1564     // Sync the cleaning prefs with the deleteOnClose box
1565     deleteOnCloseBox.addEventListener("command", () => {
1566       let { checked } = deleteOnCloseBox;
1567       cookiePref.value = checked;
1568       cachePref.value = checked;
1569       offlineAppsPref.value = checked;
1570       // Forget the current pref selection if sanitizeOnShutdown is disabled,
1571       // to not over clear when it gets enabled by the sync mechanism
1572       if (!sanitizeOnShutdownPref.value) {
1573         this._resetCleaningPrefs();
1574       }
1575       // If no other cleaning category is selected, sanitizeOnShutdown gets synced with deleteOnClose
1576       sanitizeOnShutdownPref.value =
1577         this._isCustomCleaningPrefPresent() || checked;
1579       // Update the view of the history settings
1580       if (checked && !historyMode.value) {
1581         historyMode.value = "custom";
1582         this.initializeHistoryMode();
1583         this.updateHistoryModePane();
1584         this.updatePrivacyMicroControls();
1585       }
1586     });
1588     cookiePref.on("change", this._onSanitizePrefChangeSyncClearOnClose);
1589     cachePref.on("change", this._onSanitizePrefChangeSyncClearOnClose);
1590     offlineAppsPref.on("change", this._onSanitizePrefChangeSyncClearOnClose);
1591     sanitizeOnShutdownPref.on(
1592       "change",
1593       this._onSanitizePrefChangeSyncClearOnClose
1594     );
1595   },
1597   /*
1598    * Sync the deleteOnClose box to its cleaning prefs
1599    */
1600   _onSanitizePrefChangeSyncClearOnClose() {
1601     let deleteOnCloseBox = document.getElementById("deleteOnClose");
1602     deleteOnCloseBox.checked =
1603       Preferences.get("privacy.clearOnShutdown.cookies").value &&
1604       Preferences.get("privacy.clearOnShutdown.cache").value &&
1605       Preferences.get("privacy.clearOnShutdown.offlineApps").value &&
1606       Preferences.get("privacy.sanitize.sanitizeOnShutdown").value;
1607   },
1609   /*
1610    * Unsets cleaning prefs that do not belong to DeleteOnClose
1611    */
1612   _resetCleaningPrefs() {
1613     SANITIZE_ON_SHUTDOWN_PREFS_ONLY.forEach(
1614       pref => (Preferences.get(pref).value = false)
1615     );
1616   },
1618   /*
1619    Checks if the user set cleaning prefs that do not belong to DeleteOnClose
1620    */
1621   _isCustomCleaningPrefPresent() {
1622     return SANITIZE_ON_SHUTDOWN_PREFS_ONLY.some(
1623       pref => Preferences.get(pref).value
1624     );
1625   },
1627   /**
1628    * Enables or disables the "Settings..." button depending
1629    * on the privacy.sanitize.sanitizeOnShutdown preference value
1630    */
1631   _updateSanitizeSettingsButton() {
1632     var settingsButton = document.getElementById("clearDataSettings");
1633     var sanitizeOnShutdownPref = Preferences.get(
1634       "privacy.sanitize.sanitizeOnShutdown"
1635     );
1637     settingsButton.disabled = !sanitizeOnShutdownPref.value;
1638   },
1640   toggleDoNotDisturbNotifications(event) {
1641     AlertsServiceDND.manualDoNotDisturb = event.target.checked;
1642   },
1644   // PRIVATE BROWSING
1646   /**
1647    * Initialize the starting state for the auto-start private browsing mode pref reverter.
1648    */
1649   initAutoStartPrivateBrowsingReverter() {
1650     // We determine the mode in initializeHistoryMode, which is guaranteed to have been
1651     // called before now, so this is up-to-date.
1652     let mode = document.getElementById("historyMode");
1653     this._lastMode = mode.selectedIndex;
1654     // The value of the autostart pref, on the other hand, is gotten from Preferences,
1655     // which updates the DOM asynchronously, so we can't rely on the DOM. Get it directly
1656     // from the prefs.
1657     this._lastCheckState = Preferences.get(
1658       "browser.privatebrowsing.autostart"
1659     ).value;
1660   },
1662   _lastMode: null,
1663   _lastCheckState: null,
1664   async updateAutostart() {
1665     let mode = document.getElementById("historyMode");
1666     let autoStart = document.getElementById("privateBrowsingAutoStart");
1667     let pref = Preferences.get("browser.privatebrowsing.autostart");
1668     if (
1669       (mode.value == "custom" && this._lastCheckState == autoStart.checked) ||
1670       (mode.value == "remember" && !this._lastCheckState) ||
1671       (mode.value == "dontremember" && this._lastCheckState)
1672     ) {
1673       // These are all no-op changes, so we don't need to prompt.
1674       this._lastMode = mode.selectedIndex;
1675       this._lastCheckState = autoStart.hasAttribute("checked");
1676       return;
1677     }
1679     if (!this._shouldPromptForRestart) {
1680       // We're performing a revert. Just let it happen.
1681       return;
1682     }
1684     let buttonIndex = await confirmRestartPrompt(
1685       autoStart.checked,
1686       1,
1687       true,
1688       false
1689     );
1690     if (buttonIndex == CONFIRM_RESTART_PROMPT_RESTART_NOW) {
1691       pref.value = autoStart.hasAttribute("checked");
1692       Services.startup.quit(
1693         Ci.nsIAppStartup.eAttemptQuit | Ci.nsIAppStartup.eRestart
1694       );
1695       return;
1696     }
1698     this._shouldPromptForRestart = false;
1700     if (this._lastCheckState) {
1701       autoStart.checked = "checked";
1702     } else {
1703       autoStart.removeAttribute("checked");
1704     }
1705     pref.value = autoStart.hasAttribute("checked");
1706     mode.selectedIndex = this._lastMode;
1707     mode.doCommand();
1709     this._shouldPromptForRestart = true;
1710   },
1712   /**
1713    * Displays fine-grained, per-site preferences for tracking protection.
1714    */
1715   showTrackingProtectionExceptions() {
1716     let params = {
1717       permissionType: "trackingprotection",
1718       disableETPVisible: true,
1719       prefilledHost: "",
1720       hideStatusColumn: true,
1721     };
1722     gSubDialog.open(
1723       "chrome://browser/content/preferences/dialogs/permissions.xhtml",
1724       undefined,
1725       params
1726     );
1727   },
1729   /**
1730    * Displays the available block lists for tracking protection.
1731    */
1732   showBlockLists() {
1733     gSubDialog.open(
1734       "chrome://browser/content/preferences/dialogs/blocklists.xhtml"
1735     );
1736   },
1738   // COOKIES AND SITE DATA
1740   /*
1741    * Preferences:
1742    *
1743    * network.cookie.cookieBehavior
1744    * - determines how the browser should handle cookies:
1745    *     0   means enable all cookies
1746    *     1   means reject all third party cookies
1747    *     2   means disable all cookies
1748    *     3   means reject third party cookies unless at least one is already set for the eTLD
1749    *     4   means reject all trackers
1750    *     5   means reject all trackers and partition third-party cookies
1751    *         see netwerk/cookie/src/CookieService.cpp for details
1752    */
1754   /**
1755    * Reads the network.cookie.cookieBehavior preference value and
1756    * enables/disables the "blockCookiesMenu" menulist accordingly.
1757    */
1758   readBlockCookies() {
1759     let bcControl = document.getElementById("blockCookiesMenu");
1760     bcControl.disabled =
1761       Services.cookies.getCookieBehavior(false) ==
1762       Ci.nsICookieService.BEHAVIOR_ACCEPT;
1763   },
1765   /**
1766    * Updates the "accept third party cookies" menu based on whether the
1767    * "contentBlockingBlockCookiesCheckbox" checkbox is checked.
1768    */
1769   writeBlockCookies() {
1770     let block = document.getElementById("contentBlockingBlockCookiesCheckbox");
1771     let blockCookiesMenu = document.getElementById("blockCookiesMenu");
1773     if (block.checked) {
1774       // Automatically select 'third-party trackers' as the default.
1775       blockCookiesMenu.selectedIndex = 0;
1776       return this.writeBlockCookiesFrom();
1777     }
1778     return Ci.nsICookieService.BEHAVIOR_ACCEPT;
1779   },
1781   readBlockCookiesFrom() {
1782     switch (Services.cookies.getCookieBehavior(false)) {
1783       case Ci.nsICookieService.BEHAVIOR_REJECT_FOREIGN:
1784         return "all-third-parties";
1785       case Ci.nsICookieService.BEHAVIOR_REJECT:
1786         return "always";
1787       case Ci.nsICookieService.BEHAVIOR_LIMIT_FOREIGN:
1788         return "unvisited";
1789       case Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER:
1790         return "trackers";
1791       case BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN:
1792         return "trackers-plus-isolate";
1793       default:
1794         return undefined;
1795     }
1796   },
1798   writeBlockCookiesFrom() {
1799     let block = document.getElementById("blockCookiesMenu").selectedItem;
1800     switch (block.value) {
1801       case "trackers":
1802         return Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER;
1803       case "unvisited":
1804         return Ci.nsICookieService.BEHAVIOR_LIMIT_FOREIGN;
1805       case "always":
1806         return Ci.nsICookieService.BEHAVIOR_REJECT;
1807       case "all-third-parties":
1808         return Ci.nsICookieService.BEHAVIOR_REJECT_FOREIGN;
1809       case "trackers-plus-isolate":
1810         return Ci.nsICookieService
1811           .BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN;
1812       default:
1813         return undefined;
1814     }
1815   },
1817   /**
1818    * Discard the browsers of all tabs in all windows. Pinned tabs, as
1819    * well as tabs for which discarding doesn't succeed (e.g. selected
1820    * tabs, tabs with beforeunload listeners), are reloaded.
1821    */
1822   reloadAllOtherTabs() {
1823     let ourTab = BrowserWindowTracker.getTopWindow().gBrowser.selectedTab;
1824     BrowserWindowTracker.orderedWindows.forEach(win => {
1825       let otherGBrowser = win.gBrowser;
1826       for (let tab of otherGBrowser.tabs) {
1827         if (tab == ourTab) {
1828           // Don't reload our preferences tab.
1829           continue;
1830         }
1832         if (tab.pinned || tab.selected) {
1833           otherGBrowser.reloadTab(tab);
1834         } else {
1835           otherGBrowser.discardBrowser(tab);
1836         }
1837       }
1838     });
1840     for (let notification of document.querySelectorAll(".reload-tabs")) {
1841       notification.hidden = true;
1842     }
1843   },
1845   /**
1846    * If there are more tabs than just the preferences tab, show a warning to the user that
1847    * they need to reload their tabs to apply the setting.
1848    */
1849   maybeNotifyUserToReload() {
1850     let shouldShow = false;
1851     if (window.BrowserWindowTracker.orderedWindows.length > 1) {
1852       shouldShow = true;
1853     } else {
1854       let tabbrowser = window.BrowserWindowTracker.getTopWindow().gBrowser;
1855       if (tabbrowser.tabs.length > 1) {
1856         shouldShow = true;
1857       }
1858     }
1859     if (shouldShow) {
1860       for (let notification of document.querySelectorAll(".reload-tabs")) {
1861         notification.hidden = false;
1862       }
1863     }
1864   },
1866   /**
1867    * Displays fine-grained, per-site preferences for cookies.
1868    */
1869   showCookieExceptions() {
1870     var params = {
1871       blockVisible: true,
1872       sessionVisible: true,
1873       allowVisible: true,
1874       prefilledHost: "",
1875       permissionType: "cookie",
1876     };
1877     gSubDialog.open(
1878       "chrome://browser/content/preferences/dialogs/permissions.xhtml",
1879       undefined,
1880       params
1881     );
1882   },
1884   /**
1885    * Displays per-site preferences for HTTPS-Only Mode exceptions.
1886    */
1887   showHttpsOnlyModeExceptions() {
1888     var params = {
1889       blockVisible: false,
1890       sessionVisible: true,
1891       allowVisible: false,
1892       prefilledHost: "",
1893       permissionType: "https-only-load-insecure",
1894     };
1895     gSubDialog.open(
1896       "chrome://browser/content/preferences/dialogs/permissions.xhtml",
1897       undefined,
1898       params
1899     );
1900   },
1902   showSiteDataSettings() {
1903     gSubDialog.open(
1904       "chrome://browser/content/preferences/dialogs/siteDataSettings.xhtml"
1905     );
1906   },
1908   toggleSiteData(shouldShow) {
1909     let clearButton = document.getElementById("clearSiteDataButton");
1910     let settingsButton = document.getElementById("siteDataSettings");
1911     clearButton.disabled = !shouldShow;
1912     settingsButton.disabled = !shouldShow;
1913   },
1915   showSiteDataLoading() {
1916     let totalSiteDataSizeLabel = document.getElementById("totalSiteDataSize");
1917     document.l10n.setAttributes(
1918       totalSiteDataSizeLabel,
1919       "sitedata-total-size-calculating"
1920     );
1921   },
1923   updateTotalDataSizeLabel(siteDataUsage) {
1924     SiteDataManager.getCacheSize().then(function(cacheUsage) {
1925       let totalSiteDataSizeLabel = document.getElementById("totalSiteDataSize");
1926       let totalUsage = siteDataUsage + cacheUsage;
1927       let [value, unit] = DownloadUtils.convertByteUnits(totalUsage);
1928       document.l10n.setAttributes(
1929         totalSiteDataSizeLabel,
1930         "sitedata-total-size",
1931         {
1932           value,
1933           unit,
1934         }
1935       );
1936     });
1937   },
1939   clearSiteData() {
1940     gSubDialog.open(
1941       "chrome://browser/content/preferences/dialogs/clearSiteData.xhtml"
1942     );
1943   },
1945   /**
1946    * Initializes the cookie banner handling subgroup on the privacy pane.
1947    *
1948    * This UI is shown if the "cookiebanners.ui.desktop.enabled" pref is true.
1949    *
1950    * The cookie banner handling checkbox reflects the cookie banner feature
1951    * state. It is enabled when the service enabled via the
1952    * cookiebanners.service.mode pref. If detection-only mode is enabled the
1953    * checkbox is unchecked, since in this mode no banners are handled. It is
1954    * only used for detection for banners which means we may prompt the user to
1955    * enable the feature via other UI surfaces such as the onboarding doorhanger.
1956    *
1957    * If the user checks the checkbox, the pref value is set to
1958    * nsICookieBannerService.MODE_REJECT_OR_ACCEPT.
1959    *
1960    * If the user unchecks the checkbox, the mode pref value is set to
1961    * nsICookieBannerService.MODE_DISABLED.
1962    *
1963    * Advanced users can choose other int-valued modes via about:config.
1964    */
1965   initCookieBannerHandling() {
1966     setSyncFromPrefListener("handleCookieBanners", () =>
1967       this.readCookieBannerMode()
1968     );
1969     setSyncToPrefListener("handleCookieBanners", () =>
1970       this.writeCookieBannerMode()
1971     );
1973     let preference = Preferences.get("cookiebanners.ui.desktop.enabled");
1974     preference.on("change", () => this.updateCookieBannerHandlingVisibility());
1976     this.updateCookieBannerHandlingVisibility();
1977   },
1979   /**
1980    * Reads the cookiebanners.service.mode and detectOnly preference value and
1981    * updates the cookie banner handling checkbox accordingly.
1982    */
1983   readCookieBannerMode() {
1984     if (Preferences.get("cookiebanners.service.detectOnly").value) {
1985       return false;
1986     }
1987     return (
1988       Preferences.get("cookiebanners.service.mode").value !=
1989       Ci.nsICookieBannerService.MODE_DISABLED
1990     );
1991   },
1993   /**
1994    * Translates user clicks on the cookie banner handling checkbox to the
1995    * corresponding integer-valued cookie banner mode preference.
1996    */
1997   writeCookieBannerMode() {
1998     let checkbox = document.getElementById("handleCookieBanners");
1999     let mode;
2000     if (checkbox.checked) {
2001       mode = Ci.nsICookieBannerService.MODE_REJECT;
2003       // Also unset the detect-only mode pref, just in case the user enabled
2004       // the feature via about:preferences, not the onboarding doorhanger.
2005       Services.prefs.setBoolPref("cookiebanners.service.detectOnly", false);
2006     } else {
2007       mode = Ci.nsICookieBannerService.MODE_DISABLED;
2008     }
2010     /**
2011      * There is a second service.mode pref for private browsing,
2012      * but for now we want it always be the same as service.mode
2013      * more info: https://bugzilla.mozilla.org/show_bug.cgi?id=1817201
2014      */
2015     Services.prefs.setIntPref(
2016       "cookiebanners.service.mode.privateBrowsing",
2017       mode
2018     );
2019     return mode;
2020   },
2022   /**
2023    * Shows or hides the cookie banner handling section based on the value of
2024    * the "cookiebanners.ui.desktop.enabled" pref.
2025    */
2026   updateCookieBannerHandlingVisibility() {
2027     let groupbox = document.getElementById("cookieBannerHandlingGroup");
2028     let isEnabled = Preferences.get("cookiebanners.ui.desktop.enabled").value;
2030     // Because the top-level pane showing code unsets the hidden attribute, we
2031     // manually hide the section when cookie banner handling is preffed off.
2032     if (isEnabled) {
2033       groupbox.removeAttribute("style");
2034     } else {
2035       groupbox.setAttribute("style", "display: none !important");
2036     }
2037   },
2039   // ADDRESS BAR
2041   /**
2042    * Initializes the address bar section.
2043    */
2044   _initAddressBar() {
2045     // Update the Firefox Suggest section when its Nimbus config changes.
2046     let onNimbus = () => this._updateFirefoxSuggestSection();
2047     NimbusFeatures.urlbar.onUpdate(onNimbus);
2048     window.addEventListener("unload", () => {
2049       NimbusFeatures.urlbar.offUpdate(onNimbus);
2050     });
2052     // The Firefox Suggest info box potentially needs updating when any of the
2053     // toggles change.
2054     let infoBoxPrefs = [
2055       "browser.urlbar.suggest.quicksuggest.nonsponsored",
2056       "browser.urlbar.suggest.quicksuggest.sponsored",
2057       "browser.urlbar.quicksuggest.dataCollection.enabled",
2058     ];
2059     for (let pref of infoBoxPrefs) {
2060       Preferences.get(pref).on("change", () =>
2061         this._updateFirefoxSuggestInfoBox()
2062       );
2063     }
2065     this._updateFirefoxSuggestSection(true);
2066     this._initQuickActionsSection();
2067   },
2069   /**
2070    * Updates the Firefox Suggest section (in the address bar section) depending
2071    * on whether the user is enrolled in a Firefox Suggest rollout.
2072    *
2073    * @param {boolean} [onInit]
2074    *   Pass true when calling this when initializing the pane.
2075    */
2076   _updateFirefoxSuggestSection(onInit = false) {
2077     // Show the best match checkbox container as appropriate.
2078     document.getElementById(
2079       "firefoxSuggestBestMatchContainer"
2080     ).hidden = !UrlbarPrefs.get("bestMatchEnabled");
2082     let container = document.getElementById("firefoxSuggestContainer");
2084     if (UrlbarPrefs.get("quickSuggestEnabled")) {
2085       // Update the l10n IDs of text elements.
2086       let l10nIdByElementId = {
2087         locationBarGroupHeader: "addressbar-header-firefox-suggest",
2088         locationBarSuggestionLabel: "addressbar-suggest-firefox-suggest",
2089       };
2090       for (let [elementId, l10nId] of Object.entries(l10nIdByElementId)) {
2091         let element = document.getElementById(elementId);
2092         element.dataset.l10nIdOriginal ??= element.dataset.l10nId;
2093         element.dataset.l10nId = l10nId;
2094       }
2096       // Add the extraMargin class to the engine-prefs link.
2097       document
2098         .getElementById("openSearchEnginePreferences")
2099         .classList.add("extraMargin");
2101       // Show the container.
2102       this._updateFirefoxSuggestInfoBox();
2104       this._updateDismissedSuggestionsStatus();
2105       Preferences.get(PREF_URLBAR_QUICKSUGGEST_BLOCKLIST).on("change", () =>
2106         this._updateDismissedSuggestionsStatus()
2107       );
2108       Preferences.get(PREF_URLBAR_WEATHER_USER_ENABLED).on("change", () =>
2109         this._updateDismissedSuggestionsStatus()
2110       );
2111       setEventListener("restoreDismissedSuggestions", "command", () =>
2112         this.restoreDismissedSuggestions()
2113       );
2115       container.removeAttribute("hidden");
2116     } else if (!onInit) {
2117       // Firefox Suggest is not enabled. This is the default, so to avoid
2118       // accidentally messing anything up, only modify the doc if we're being
2119       // called due to a change in the rollout-enabled status (!onInit).
2120       container.setAttribute("hidden", "true");
2121       let elementIds = ["locationBarGroupHeader", "locationBarSuggestionLabel"];
2122       for (let id of elementIds) {
2123         let element = document.getElementById(id);
2124         element.dataset.l10nId = element.dataset.l10nIdOriginal;
2125         delete element.dataset.l10nIdOriginal;
2126         document.l10n.translateElements([element]);
2127       }
2128       document
2129         .getElementById("openSearchEnginePreferences")
2130         .classList.remove("extraMargin");
2131     }
2132   },
2134   /**
2135    * Updates the Firefox Suggest info box (in the address bar section) depending
2136    * on the states of the Firefox Suggest toggles.
2137    */
2138   _updateFirefoxSuggestInfoBox() {
2139     let nonsponsored = Preferences.get(
2140       "browser.urlbar.suggest.quicksuggest.nonsponsored"
2141     ).value;
2142     let sponsored = Preferences.get(
2143       "browser.urlbar.suggest.quicksuggest.sponsored"
2144     ).value;
2145     let dataCollection = Preferences.get(
2146       "browser.urlbar.quicksuggest.dataCollection.enabled"
2147     ).value;
2149     // Get the l10n ID of the appropriate text based on the values of the three
2150     // prefs.
2151     let l10nId;
2152     if (nonsponsored && sponsored && dataCollection) {
2153       l10nId = "addressbar-firefox-suggest-info-all";
2154     } else if (nonsponsored && sponsored && !dataCollection) {
2155       l10nId = "addressbar-firefox-suggest-info-nonsponsored-sponsored";
2156     } else if (nonsponsored && !sponsored && dataCollection) {
2157       l10nId = "addressbar-firefox-suggest-info-nonsponsored-data";
2158     } else if (nonsponsored && !sponsored && !dataCollection) {
2159       l10nId = "addressbar-firefox-suggest-info-nonsponsored";
2160     } else if (!nonsponsored && sponsored && dataCollection) {
2161       l10nId = "addressbar-firefox-suggest-info-sponsored-data";
2162     } else if (!nonsponsored && sponsored && !dataCollection) {
2163       l10nId = "addressbar-firefox-suggest-info-sponsored";
2164     } else if (!nonsponsored && !sponsored && dataCollection) {
2165       l10nId = "addressbar-firefox-suggest-info-data";
2166     }
2168     let instance = (this._firefoxSuggestInfoBoxInstance = {});
2169     let infoBox = document.getElementById("firefoxSuggestInfoBox");
2170     if (!l10nId) {
2171       infoBox.hidden = true;
2172     } else {
2173       let infoText = document.getElementById("firefoxSuggestInfoText");
2174       infoText.dataset.l10nId = l10nId;
2176       // If the info box is currently hidden and we unhide it immediately, it
2177       // will show its old text until the new text is asyncly fetched and shown.
2178       // That's ugly, so wait for the fetch to finish before unhiding it.
2179       document.l10n.translateElements([infoText]).then(() => {
2180         if (instance == this._firefoxSuggestInfoBoxInstance) {
2181           infoBox.hidden = false;
2182         }
2183       });
2184     }
2185   },
2187   /**
2188    * Enables/disables the "Restore" button for dismissed Firefox Suggest
2189    * suggestions.
2190    */
2191   _updateDismissedSuggestionsStatus() {
2192     document.getElementById("restoreDismissedSuggestions").disabled =
2193       !Services.prefs.prefHasUserValue(PREF_URLBAR_QUICKSUGGEST_BLOCKLIST) &&
2194       !(
2195         Services.prefs.prefHasUserValue(PREF_URLBAR_WEATHER_USER_ENABLED) &&
2196         !Services.prefs.getBoolPref(PREF_URLBAR_WEATHER_USER_ENABLED)
2197       );
2198   },
2200   /**
2201    * Restores Firefox Suggest suggestions dismissed by the user.
2202    */
2203   restoreDismissedSuggestions() {
2204     Services.prefs.clearUserPref(PREF_URLBAR_QUICKSUGGEST_BLOCKLIST);
2205     Services.prefs.clearUserPref(PREF_URLBAR_WEATHER_USER_ENABLED);
2206   },
2208   // GEOLOCATION
2210   /**
2211    * Displays the location exceptions dialog where specific site location
2212    * preferences can be set.
2213    */
2214   showLocationExceptions() {
2215     let params = { permissionType: "geo" };
2217     gSubDialog.open(
2218       "chrome://browser/content/preferences/dialogs/sitePermissions.xhtml",
2219       { features: "resizable=yes" },
2220       params
2221     );
2222   },
2224   // XR
2226   /**
2227    * Displays the XR exceptions dialog where specific site XR
2228    * preferences can be set.
2229    */
2230   showXRExceptions() {
2231     let params = { permissionType: "xr" };
2233     gSubDialog.open(
2234       "chrome://browser/content/preferences/dialogs/sitePermissions.xhtml",
2235       { features: "resizable=yes" },
2236       params
2237     );
2238   },
2240   // CAMERA
2242   /**
2243    * Displays the camera exceptions dialog where specific site camera
2244    * preferences can be set.
2245    */
2246   showCameraExceptions() {
2247     let params = { permissionType: "camera" };
2249     gSubDialog.open(
2250       "chrome://browser/content/preferences/dialogs/sitePermissions.xhtml",
2251       { features: "resizable=yes" },
2252       params
2253     );
2254   },
2256   // MICROPHONE
2258   /**
2259    * Displays the microphone exceptions dialog where specific site microphone
2260    * preferences can be set.
2261    */
2262   showMicrophoneExceptions() {
2263     let params = { permissionType: "microphone" };
2265     gSubDialog.open(
2266       "chrome://browser/content/preferences/dialogs/sitePermissions.xhtml",
2267       { features: "resizable=yes" },
2268       params
2269     );
2270   },
2272   // SPEAKER
2274   /**
2275    * Displays the speaker exceptions dialog where specific site speaker
2276    * preferences can be set.
2277    */
2278   showSpeakerExceptions() {
2279     let params = { permissionType: "speaker" };
2281     gSubDialog.open(
2282       "chrome://browser/content/preferences/dialogs/sitePermissions.xhtml",
2283       { features: "resizable=yes" },
2284       params
2285     );
2286   },
2288   // NOTIFICATIONS
2290   /**
2291    * Displays the notifications exceptions dialog where specific site notification
2292    * preferences can be set.
2293    */
2294   showNotificationExceptions() {
2295     let params = { permissionType: "desktop-notification" };
2297     gSubDialog.open(
2298       "chrome://browser/content/preferences/dialogs/sitePermissions.xhtml",
2299       { features: "resizable=yes" },
2300       params
2301     );
2302   },
2304   // MEDIA
2306   showAutoplayMediaExceptions() {
2307     var params = { permissionType: "autoplay-media" };
2309     gSubDialog.open(
2310       "chrome://browser/content/preferences/dialogs/sitePermissions.xhtml",
2311       { features: "resizable=yes" },
2312       params
2313     );
2314   },
2316   // POP-UPS
2318   /**
2319    * Displays the popup exceptions dialog where specific site popup preferences
2320    * can be set.
2321    */
2322   showPopupExceptions() {
2323     var params = {
2324       blockVisible: false,
2325       sessionVisible: false,
2326       allowVisible: true,
2327       prefilledHost: "",
2328       permissionType: "popup",
2329     };
2331     gSubDialog.open(
2332       "chrome://browser/content/preferences/dialogs/permissions.xhtml",
2333       { features: "resizable=yes" },
2334       params
2335     );
2336   },
2338   // UTILITY FUNCTIONS
2340   /**
2341    * Utility function to enable/disable the button specified by aButtonID based
2342    * on the value of the Boolean preference specified by aPreferenceID.
2343    */
2344   updateButtons(aButtonID, aPreferenceID) {
2345     var button = document.getElementById(aButtonID);
2346     var preference = Preferences.get(aPreferenceID);
2347     button.disabled = !preference.value || preference.locked;
2348     return undefined;
2349   },
2351   // BEGIN UI CODE
2353   /*
2354    * Preferences:
2355    *
2356    * dom.disable_open_during_load
2357    * - true if popups are blocked by default, false otherwise
2358    */
2360   // POP-UPS
2362   /**
2363    * Displays a dialog in which the user can view and modify the list of sites
2364    * where passwords are never saved.
2365    */
2366   showPasswordExceptions() {
2367     var params = {
2368       blockVisible: true,
2369       sessionVisible: false,
2370       allowVisible: false,
2371       hideStatusColumn: true,
2372       prefilledHost: "",
2373       permissionType: "login-saving",
2374     };
2376     gSubDialog.open(
2377       "chrome://browser/content/preferences/dialogs/permissions.xhtml",
2378       undefined,
2379       params
2380     );
2381   },
2383   /**
2384    * Initializes master password UI: the "use master password" checkbox, selects
2385    * the master password button to show, and enables/disables it as necessary.
2386    * The master password is controlled by various bits of NSS functionality, so
2387    * the UI for it can't be controlled by the normal preference bindings.
2388    */
2389   _initMasterPasswordUI() {
2390     var noMP = !LoginHelper.isPrimaryPasswordSet();
2392     var button = document.getElementById("changeMasterPassword");
2393     button.disabled = noMP;
2395     var checkbox = document.getElementById("useMasterPassword");
2396     checkbox.checked = !noMP;
2397     checkbox.disabled =
2398       (noMP && !Services.policies.isAllowed("createMasterPassword")) ||
2399       (!noMP && !Services.policies.isAllowed("removeMasterPassword"));
2400   },
2402   /**
2403    * Enables/disables the master password button depending on the state of the
2404    * "use master password" checkbox, and prompts for master password removal if
2405    * one is set.
2406    */
2407   async updateMasterPasswordButton() {
2408     var checkbox = document.getElementById("useMasterPassword");
2409     var button = document.getElementById("changeMasterPassword");
2410     button.disabled = !checkbox.checked;
2412     // unchecking the checkbox should try to immediately remove the master
2413     // password, because it's impossible to non-destructively remove the master
2414     // password used to encrypt all the passwords without providing it (by
2415     // design), and it would be extremely odd to pop up that dialog when the
2416     // user closes the prefwindow and saves his settings
2417     if (!checkbox.checked) {
2418       await this._removeMasterPassword();
2419     } else {
2420       await this.changeMasterPassword();
2421     }
2423     this._initMasterPasswordUI();
2424   },
2426   /**
2427    * Displays the "remove master password" dialog to allow the user to remove
2428    * the current master password.  When the dialog is dismissed, master password
2429    * UI is automatically updated.
2430    */
2431   async _removeMasterPassword() {
2432     var secmodDB = Cc["@mozilla.org/security/pkcs11moduledb;1"].getService(
2433       Ci.nsIPKCS11ModuleDB
2434     );
2435     if (secmodDB.isFIPSEnabled) {
2436       let title = document.getElementById("fips-title").textContent;
2437       let desc = document.getElementById("fips-desc").textContent;
2438       Services.prompt.alert(window, title, desc);
2439       this._initMasterPasswordUI();
2440     } else {
2441       gSubDialog.open("chrome://mozapps/content/preferences/removemp.xhtml", {
2442         closingCallback: this._initMasterPasswordUI.bind(this),
2443       });
2444     }
2445   },
2447   /**
2448    * Displays a dialog in which the primary password may be changed.
2449    */
2450   async changeMasterPassword() {
2451     // Require OS authentication before the user can set a Primary Password.
2452     // OS reauthenticate functionality is not available on Linux yet (bug 1527745)
2453     if (
2454       !LoginHelper.isPrimaryPasswordSet() &&
2455       OS_AUTH_ENABLED &&
2456       OSKeyStore.canReauth()
2457     ) {
2458       // Uses primary-password-os-auth-dialog-message-win and
2459       // primary-password-os-auth-dialog-message-macosx via concatenation:
2460       let messageId =
2461         "primary-password-os-auth-dialog-message-" + AppConstants.platform;
2462       let [messageText, captionText] = await document.l10n.formatMessages([
2463         {
2464           id: messageId,
2465         },
2466         {
2467           id: "master-password-os-auth-dialog-caption",
2468         },
2469       ]);
2470       let win = Services.wm.getMostRecentBrowserWindow();
2471       let loggedIn = await OSKeyStore.ensureLoggedIn(
2472         messageText.value,
2473         captionText.value,
2474         win,
2475         false
2476       );
2477       if (!loggedIn.authenticated) {
2478         return;
2479       }
2480     }
2482     gSubDialog.open("chrome://mozapps/content/preferences/changemp.xhtml", {
2483       features: "resizable=no",
2484       closingCallback: this._initMasterPasswordUI.bind(this),
2485     });
2486   },
2488   /**
2489    * Set up the initial state for the password generation UI.
2490    * It will be hidden unless the .available pref is true
2491    */
2492   _initPasswordGenerationUI() {
2493     // we don't watch the .available pref for runtime changes
2494     let prefValue = Services.prefs.getBoolPref(
2495       PREF_PASSWORD_GENERATION_AVAILABLE,
2496       false
2497     );
2498     document.getElementById("generatePasswordsBox").hidden = !prefValue;
2499   },
2501   toggleRelayIntegration() {
2502     const checkbox = document.getElementById("relayIntegration");
2503     if (checkbox.checked) {
2504       FirefoxRelay.markAsEnabled();
2505       FirefoxRelayTelemetry.recordRelayPrefEvent("enabled");
2506     } else {
2507       FirefoxRelay.markAsDisabled();
2508       FirefoxRelayTelemetry.recordRelayPrefEvent("disabled");
2509     }
2510   },
2512   _updateRelayIntegrationUI() {
2513     document.getElementById(
2514       "relayIntegrationBox"
2515     ).hidden = !FirefoxRelay.isAvailable;
2516     document.getElementById("relayIntegration").checked =
2517       FirefoxRelay.isEnabled;
2518   },
2520   _initRelayIntegrationUI() {
2521     document
2522       .getElementById("relayIntegrationLearnMoreLink")
2523       .setAttribute("href", FirefoxRelay.learnMoreUrl);
2525     setEventListener(
2526       "relayIntegration",
2527       "command",
2528       gPrivacyPane.toggleRelayIntegration.bind(gPrivacyPane)
2529     );
2530     Preferences.get("signon.firefoxRelay.feature").on(
2531       "change",
2532       gPrivacyPane._updateRelayIntegrationUI.bind(gPrivacyPane)
2533     );
2535     this._updateRelayIntegrationUI();
2536   },
2538   /**
2539    * Shows the sites where the user has saved passwords and the associated login
2540    * information.
2541    */
2542   showPasswords() {
2543     let loginManager = window.windowGlobalChild.getActor("LoginManager");
2544     loginManager.sendAsyncMessage("PasswordManager:OpenPreferences", {
2545       entryPoint: "preferences",
2546     });
2547   },
2549   /**
2550    * Enables/disables dependent controls related to password saving
2551    * When password saving is not enabled, we need to also disable the password generation checkbox
2552    * The Exceptions button is used to configure sites where passwords are never saved.
2553    */
2554   readSavePasswords() {
2555     var prefValue = Preferences.get("signon.rememberSignons").value;
2556     document.getElementById("passwordExceptions").disabled = !prefValue;
2557     document.getElementById("generatePasswords").disabled = !prefValue;
2558     document.getElementById("passwordAutofillCheckbox").disabled = !prefValue;
2559     document.getElementById("relayIntegration").disabled = !prefValue;
2561     // don't override pref value in UI
2562     return undefined;
2563   },
2565   /**
2566    * Initalizes pref listeners for the password manager.
2567    *
2568    * This ensures that the user is always notified if an extension is controlling the password manager.
2569    */
2570   initListenersForExtensionControllingPasswordManager() {
2571     this._passwordManagerCheckbox = document.getElementById("savePasswords");
2572     this._disableExtensionButton = document.getElementById(
2573       "disablePasswordManagerExtension"
2574     );
2576     this._disableExtensionButton.addEventListener(
2577       "command",
2578       makeDisableControllingExtension(
2579         PREF_SETTING_TYPE,
2580         PASSWORD_MANAGER_PREF_ID
2581       )
2582     );
2584     initListenersForPrefChange(
2585       PREF_SETTING_TYPE,
2586       PASSWORD_MANAGER_PREF_ID,
2587       this._passwordManagerCheckbox
2588     );
2589   },
2591   /**
2592    * Enables/disables the add-ons Exceptions button depending on whether
2593    * or not add-on installation warnings are displayed.
2594    */
2595   readWarnAddonInstall() {
2596     var warn = Preferences.get("xpinstall.whitelist.required");
2597     var exceptions = document.getElementById("addonExceptions");
2599     exceptions.disabled = !warn.value;
2601     // don't override the preference value
2602     return undefined;
2603   },
2605   _initSafeBrowsing() {
2606     let enableSafeBrowsing = document.getElementById("enableSafeBrowsing");
2607     let blockDownloads = document.getElementById("blockDownloads");
2608     let blockUncommonUnwanted = document.getElementById(
2609       "blockUncommonUnwanted"
2610     );
2612     let safeBrowsingPhishingPref = Preferences.get(
2613       "browser.safebrowsing.phishing.enabled"
2614     );
2615     let safeBrowsingMalwarePref = Preferences.get(
2616       "browser.safebrowsing.malware.enabled"
2617     );
2619     let blockDownloadsPref = Preferences.get(
2620       "browser.safebrowsing.downloads.enabled"
2621     );
2622     let malwareTable = Preferences.get("urlclassifier.malwareTable");
2624     let blockUnwantedPref = Preferences.get(
2625       "browser.safebrowsing.downloads.remote.block_potentially_unwanted"
2626     );
2627     let blockUncommonPref = Preferences.get(
2628       "browser.safebrowsing.downloads.remote.block_uncommon"
2629     );
2631     enableSafeBrowsing.addEventListener("command", function() {
2632       safeBrowsingPhishingPref.value = enableSafeBrowsing.checked;
2633       safeBrowsingMalwarePref.value = enableSafeBrowsing.checked;
2635       blockDownloads.disabled =
2636         !enableSafeBrowsing.checked || blockDownloadsPref.locked;
2637       blockUncommonUnwanted.disabled =
2638         !blockDownloads.checked ||
2639         !enableSafeBrowsing.checked ||
2640         blockUnwantedPref.locked ||
2641         blockUncommonPref.locked;
2642     });
2644     blockDownloads.addEventListener("command", function() {
2645       blockDownloadsPref.value = blockDownloads.checked;
2646       blockUncommonUnwanted.disabled =
2647         !blockDownloads.checked ||
2648         blockUnwantedPref.locked ||
2649         blockUncommonPref.locked;
2650     });
2652     blockUncommonUnwanted.addEventListener("command", function() {
2653       blockUnwantedPref.value = blockUncommonUnwanted.checked;
2654       blockUncommonPref.value = blockUncommonUnwanted.checked;
2656       let malware = malwareTable.value
2657         .split(",")
2658         .filter(
2659           x =>
2660             x !== "goog-unwanted-proto" &&
2661             x !== "goog-unwanted-shavar" &&
2662             x !== "moztest-unwanted-simple"
2663         );
2665       if (blockUncommonUnwanted.checked) {
2666         if (malware.includes("goog-malware-shavar")) {
2667           malware.push("goog-unwanted-shavar");
2668         } else {
2669           malware.push("goog-unwanted-proto");
2670         }
2672         malware.push("moztest-unwanted-simple");
2673       }
2675       // sort alphabetically to keep the pref consistent
2676       malware.sort();
2678       malwareTable.value = malware.join(",");
2680       // Force an update after changing the malware table.
2681       listManager.forceUpdates(malwareTable.value);
2682     });
2684     // set initial values
2686     enableSafeBrowsing.checked =
2687       safeBrowsingPhishingPref.value && safeBrowsingMalwarePref.value;
2688     if (!enableSafeBrowsing.checked) {
2689       blockDownloads.setAttribute("disabled", "true");
2690       blockUncommonUnwanted.setAttribute("disabled", "true");
2691     }
2693     blockDownloads.checked = blockDownloadsPref.value;
2694     if (!blockDownloadsPref.value) {
2695       blockUncommonUnwanted.setAttribute("disabled", "true");
2696     }
2697     blockUncommonUnwanted.checked =
2698       blockUnwantedPref.value && blockUncommonPref.value;
2700     if (safeBrowsingPhishingPref.locked || safeBrowsingMalwarePref.locked) {
2701       enableSafeBrowsing.disabled = true;
2702     }
2703     if (blockDownloadsPref.locked) {
2704       blockDownloads.disabled = true;
2705     }
2706     if (blockUnwantedPref.locked || blockUncommonPref.locked) {
2707       blockUncommonUnwanted.disabled = true;
2708     }
2709   },
2711   /**
2712    * Displays the exceptions lists for add-on installation warnings.
2713    */
2714   showAddonExceptions() {
2715     var params = this._addonParams;
2717     gSubDialog.open(
2718       "chrome://browser/content/preferences/dialogs/permissions.xhtml",
2719       undefined,
2720       params
2721     );
2722   },
2724   /**
2725    * Parameters for the add-on install permissions dialog.
2726    */
2727   _addonParams: {
2728     blockVisible: false,
2729     sessionVisible: false,
2730     allowVisible: true,
2731     prefilledHost: "",
2732     permissionType: "install",
2733   },
2735   /**
2736    * readEnableOCSP is used by the preferences UI to determine whether or not
2737    * the checkbox for OCSP fetching should be checked (it returns true if it
2738    * should be checked and false otherwise). The about:config preference
2739    * "security.OCSP.enabled" is an integer rather than a boolean, so it can't be
2740    * directly mapped from {true,false} to {checked,unchecked}. The possible
2741    * values for "security.OCSP.enabled" are:
2742    * 0: fetching is disabled
2743    * 1: fetch for all certificates
2744    * 2: fetch only for EV certificates
2745    * Hence, if "security.OCSP.enabled" is non-zero, the checkbox should be
2746    * checked. Otherwise, it should be unchecked.
2747    */
2748   readEnableOCSP() {
2749     var preference = Preferences.get("security.OCSP.enabled");
2750     // This is the case if the preference is the default value.
2751     if (preference.value === undefined) {
2752       return true;
2753     }
2754     return preference.value != 0;
2755   },
2757   /**
2758    * writeEnableOCSP is used by the preferences UI to map the checked/unchecked
2759    * state of the OCSP fetching checkbox to the value that the preference
2760    * "security.OCSP.enabled" should be set to (it returns that value). See the
2761    * readEnableOCSP documentation for more background. We unfortunately don't
2762    * have enough information to map from {true,false} to all possible values for
2763    * "security.OCSP.enabled", but a reasonable alternative is to map from
2764    * {true,false} to {<the default value>,0}. That is, if the box is checked,
2765    * "security.OCSP.enabled" will be set to whatever default it should be, given
2766    * the platform and channel. If the box is unchecked, the preference will be
2767    * set to 0. Obviously this won't work if the default is 0, so we will have to
2768    * revisit this if we ever set it to 0.
2769    */
2770   writeEnableOCSP() {
2771     var checkbox = document.getElementById("enableOCSP");
2772     var defaults = Services.prefs.getDefaultBranch(null);
2773     var defaultValue = defaults.getIntPref("security.OCSP.enabled");
2774     return checkbox.checked ? defaultValue : 0;
2775   },
2777   /**
2778    * Displays the user's certificates and associated options.
2779    */
2780   showCertificates() {
2781     gSubDialog.open("chrome://pippki/content/certManager.xhtml");
2782   },
2784   /**
2785    * Displays a dialog from which the user can manage his security devices.
2786    */
2787   showSecurityDevices() {
2788     gSubDialog.open("chrome://pippki/content/device_manager.xhtml");
2789   },
2791   /**
2792    * Displays the learn more health report page when a user opts out of data collection.
2793    */
2794   showDataDeletion() {
2795     let url =
2796       Services.urlFormatter.formatURLPref("app.support.baseURL") +
2797       "telemetry-clientid";
2798     window.open(url, "_blank");
2799   },
2801   initDataCollection() {
2802     if (
2803       !AppConstants.MOZ_DATA_REPORTING &&
2804       !NimbusFeatures.majorRelease2022.getVariable(
2805         "feltPrivacyShowPreferencesSection"
2806       )
2807     ) {
2808       // Nothing to control in the data collection section, remove it.
2809       document.getElementById("dataCollectionCategory").remove();
2810       document.getElementById("dataCollectionGroup").remove();
2811       return;
2812     }
2814     this._setupLearnMoreLink(
2815       "toolkit.datacollection.infoURL",
2816       "dataCollectionPrivacyNotice"
2817     );
2818     this.initPrivacySegmentation();
2819   },
2821   initSubmitCrashes() {
2822     this._setupLearnMoreLink(
2823       "toolkit.crashreporter.infoURL",
2824       "crashReporterLearnMore"
2825     );
2826     setEventListener("crashReporterLabel", "click", function(event) {
2827       if (event.target.localName == "a") {
2828         return;
2829       }
2830       const checkboxId = event.target.getAttribute("for");
2831       document.getElementById(checkboxId).click();
2832     });
2833   },
2835   initPrivacySegmentation() {
2836     // Section visibility
2837     let section = document.getElementById("privacySegmentationSection");
2838     let updatePrivacySegmentationSectionVisibilityState = () => {
2839       section.hidden = !NimbusFeatures.majorRelease2022.getVariable(
2840         "feltPrivacyShowPreferencesSection"
2841       );
2842     };
2844     NimbusFeatures.majorRelease2022.onUpdate(
2845       updatePrivacySegmentationSectionVisibilityState
2846     );
2847     window.addEventListener("unload", () => {
2848       NimbusFeatures.majorRelease2022.offUpdate(
2849         updatePrivacySegmentationSectionVisibilityState
2850       );
2851     });
2853     updatePrivacySegmentationSectionVisibilityState();
2854   },
2856   /**
2857    * Set up or hide the Learn More links for various data collection options
2858    */
2859   _setupLearnMoreLink(pref, element) {
2860     // set up the Learn More link with the correct URL
2861     let url = Services.urlFormatter.formatURLPref(pref);
2862     let el = document.getElementById(element);
2864     if (url) {
2865       el.setAttribute("href", url);
2866     } else {
2867       el.hidden = true;
2868     }
2869   },
2871   /**
2872    * Initialize the health report service reference and checkbox.
2873    */
2874   initSubmitHealthReport() {
2875     this._setupLearnMoreLink(
2876       "datareporting.healthreport.infoURL",
2877       "FHRLearnMore"
2878     );
2880     let checkbox = document.getElementById("submitHealthReportBox");
2882     // Telemetry is only sending data if MOZ_TELEMETRY_REPORTING is defined.
2883     // We still want to display the preferences panel if that's not the case, but
2884     // we want it to be disabled and unchecked.
2885     if (
2886       Services.prefs.prefIsLocked(PREF_UPLOAD_ENABLED) ||
2887       !AppConstants.MOZ_TELEMETRY_REPORTING
2888     ) {
2889       checkbox.setAttribute("disabled", "true");
2890       return;
2891     }
2893     checkbox.checked =
2894       Services.prefs.getBoolPref(PREF_UPLOAD_ENABLED) &&
2895       AppConstants.MOZ_TELEMETRY_REPORTING;
2896   },
2898   /**
2899    * Update the health report preference with state from checkbox.
2900    */
2901   updateSubmitHealthReport() {
2902     let checkbox = document.getElementById("submitHealthReportBox");
2903     let telemetryContainer = document.getElementById("telemetry-container");
2905     Services.prefs.setBoolPref(PREF_UPLOAD_ENABLED, checkbox.checked);
2906     telemetryContainer.hidden = checkbox.checked;
2907   },
2909   /**
2910    * Initialize the opt-out-study preference checkbox into about:preferences and
2911    * handles events coming from the UI for it.
2912    */
2913   initOptOutStudyCheckbox(doc) {
2914     // The checkbox should be disabled if any of the below are true. This
2915     // prevents the user from changing the value in the box.
2916     //
2917     // * the policy forbids shield
2918     // * Normandy is disabled
2919     //
2920     // The checkbox should match the value of the preference only if all of
2921     // these are true. Otherwise, the checkbox should remain unchecked. This
2922     // is because in these situations, Shield studies are always disabled, and
2923     // so showing a checkbox would be confusing.
2924     //
2925     // * the policy allows Shield
2926     // * Normandy is enabled
2928     const allowedByPolicy = Services.policies.isAllowed("Shield");
2929     const checkbox = document.getElementById("optOutStudiesEnabled");
2931     if (
2932       allowedByPolicy &&
2933       Services.prefs.getBoolPref(PREF_NORMANDY_ENABLED, false)
2934     ) {
2935       if (Services.prefs.getBoolPref(PREF_OPT_OUT_STUDIES_ENABLED, false)) {
2936         checkbox.setAttribute("checked", "true");
2937       } else {
2938         checkbox.removeAttribute("checked");
2939       }
2940       checkbox.setAttribute("preference", PREF_OPT_OUT_STUDIES_ENABLED);
2941       checkbox.removeAttribute("disabled");
2942     } else {
2943       checkbox.removeAttribute("preference");
2944       checkbox.removeAttribute("checked");
2945       checkbox.setAttribute("disabled", "true");
2946     }
2947   },
2949   initAddonRecommendationsCheckbox() {
2950     // Setup the checkbox.
2951     dataCollectionCheckboxHandler({
2952       checkbox: document.getElementById("addonRecommendationEnabled"),
2953       pref: PREF_ADDON_RECOMMENDATIONS_ENABLED,
2954     });
2955   },
2957   observe(aSubject, aTopic, aData) {
2958     switch (aTopic) {
2959       case "sitedatamanager:updating-sites":
2960         // While updating, we want to disable this section and display loading message until updated
2961         this.toggleSiteData(false);
2962         this.showSiteDataLoading();
2963         break;
2965       case "sitedatamanager:sites-updated":
2966         this.toggleSiteData(true);
2967         SiteDataManager.getTotalUsage().then(
2968           this.updateTotalDataSizeLabel.bind(this)
2969         );
2970         break;
2971     }
2972   },