tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

privacy.js (174195B)


      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/. */
      4 
      5 /* import-globals-from extensionControlled.js */
      6 /* import-globals-from preferences.js */
      7 
      8 const PREF_UPLOAD_ENABLED = "datareporting.healthreport.uploadEnabled";
      9 
     10 const TRACKING_PROTECTION_KEY = "websites.trackingProtectionMode";
     11 const TRACKING_PROTECTION_PREFS = [
     12  "privacy.trackingprotection.enabled",
     13  "privacy.trackingprotection.pbmode.enabled",
     14 ];
     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",
     22  "privacy.trackingprotection.emailtracking.enabled",
     23  "privacy.trackingprotection.emailtracking.pbmode.enabled",
     24  "privacy.fingerprintingProtection",
     25  "privacy.fingerprintingProtection.pbmode",
     26  "privacy.trackingprotection.allow_list.baseline.enabled",
     27  "privacy.trackingprotection.allow_list.convenience.enabled",
     28 ];
     29 
     30 const PREF_OPT_OUT_STUDIES_ENABLED = "app.shield.optoutstudies.enabled";
     31 const PREF_NORMANDY_ENABLED = "app.normandy.enabled";
     32 
     33 const PREF_ADDON_RECOMMENDATIONS_ENABLED = "browser.discovery.enabled";
     34 
     35 const PREF_PASSWORD_GENERATION_AVAILABLE = "signon.generation.available";
     36 const { BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN } = Ci.nsICookieService;
     37 
     38 const PASSWORD_MANAGER_PREF_ID = "services.passwordSavingEnabled";
     39 
     40 ChromeUtils.defineLazyGetter(this, "AlertsServiceDND", function () {
     41  try {
     42    let alertsService = Cc["@mozilla.org/alerts-service;1"]
     43      .getService(Ci.nsIAlertsService)
     44      .QueryInterface(Ci.nsIAlertsDoNotDisturb);
     45    // This will throw if manualDoNotDisturb isn't implemented.
     46    alertsService.manualDoNotDisturb;
     47    return alertsService;
     48  } catch (ex) {
     49    return undefined;
     50  }
     51 });
     52 
     53 ChromeUtils.defineLazyGetter(lazy, "AboutLoginsL10n", () => {
     54  return new Localization(["branding/brand.ftl", "browser/aboutLogins.ftl"]);
     55 });
     56 
     57 ChromeUtils.defineLazyGetter(lazy, "gParentalControlsService", () =>
     58  "@mozilla.org/parental-controls-service;1" in Cc
     59    ? Cc["@mozilla.org/parental-controls-service;1"].getService(
     60        Ci.nsIParentalControlsService
     61      )
     62    : null
     63 );
     64 
     65 XPCOMUtils.defineLazyScriptGetter(
     66  this,
     67  ["OnionServicesAuthPreferences"],
     68  "chrome://browser/content/onionservices/authPreferences.js"
     69 );
     70 
     71 // TODO: module import via ChromeUtils.defineModuleGetter
     72 XPCOMUtils.defineLazyScriptGetter(
     73  this,
     74  ["SecurityLevelPreferences"],
     75  "chrome://browser/content/securitylevel/securityLevel.js"
     76 );
     77 
     78 XPCOMUtils.defineLazyServiceGetter(
     79  lazy,
     80  "TrackingDBService",
     81  "@mozilla.org/tracking-db-service;1",
     82  Ci.nsITrackingDBService
     83 );
     84 
     85 XPCOMUtils.defineLazyPreferenceGetter(
     86  this,
     87  "gIsFirstPartyIsolated",
     88  "privacy.firstparty.isolate",
     89  false
     90 );
     91 
     92 XPCOMUtils.defineLazyPreferenceGetter(
     93  this,
     94  "useOldClearHistoryDialog",
     95  "privacy.sanitize.useOldClearHistoryDialog",
     96  false
     97 );
     98 
     99 ChromeUtils.defineESModuleGetters(this, {
    100  AppUpdater: "resource://gre/modules/AppUpdater.sys.mjs",
    101  DoHConfigController: "moz-src:///toolkit/components/doh/DoHConfig.sys.mjs",
    102  Sanitizer: "resource:///modules/Sanitizer.sys.mjs",
    103  SelectableProfileService:
    104    "resource:///modules/profiles/SelectableProfileService.sys.mjs",
    105 });
    106 
    107 const SANITIZE_ON_SHUTDOWN_MAPPINGS = {
    108  history: "privacy.clearOnShutdown.history",
    109  downloads: "privacy.clearOnShutdown.downloads",
    110  formdata: "privacy.clearOnShutdown.formdata",
    111  sessions: "privacy.clearOnShutdown.sessions",
    112  siteSettings: "privacy.clearOnShutdown.siteSettings",
    113  cookies: "privacy.clearOnShutdown.cookies",
    114  cache: "privacy.clearOnShutdown.cache",
    115  offlineApps: "privacy.clearOnShutdown.offlineApps",
    116 };
    117 
    118 /*
    119 * Prefs that are unique to sanitizeOnShutdown and are not shared
    120 * with the deleteOnClose mechanism like privacy.clearOnShutdown.cookies, -cache and -offlineApps
    121 */
    122 const SANITIZE_ON_SHUTDOWN_PREFS_ONLY = [
    123  "privacy.clearOnShutdown.history",
    124  "privacy.clearOnShutdown.downloads",
    125  "privacy.clearOnShutdown.sessions",
    126  "privacy.clearOnShutdown.formdata",
    127  "privacy.clearOnShutdown.siteSettings",
    128 ];
    129 
    130 const SANITIZE_ON_SHUTDOWN_PREFS_ONLY_V2 = [
    131  "privacy.clearOnShutdown_v2.browsingHistoryAndDownloads",
    132  "privacy.clearOnShutdown_v2.siteSettings",
    133 ];
    134 
    135 const SECURITY_PRIVACY_STATUS_CARD_ENABLED =
    136  Services.prefs.getBoolPref("browser.settings-redesign.enabled", false) ||
    137  Services.prefs.getBoolPref(
    138    "browser.settings-redesign.securityPrivacyStatus.enabled",
    139    false
    140  );
    141 
    142 Preferences.addAll([
    143  // Content blocking / Tracking Protection
    144  { id: "privacy.trackingprotection.enabled", type: "bool" },
    145  { id: "privacy.trackingprotection.pbmode.enabled", type: "bool" },
    146  { id: "privacy.trackingprotection.fingerprinting.enabled", type: "bool" },
    147  { id: "privacy.trackingprotection.cryptomining.enabled", type: "bool" },
    148  { id: "privacy.trackingprotection.emailtracking.enabled", type: "bool" },
    149  {
    150    id: "privacy.trackingprotection.emailtracking.pbmode.enabled",
    151    type: "bool",
    152  },
    153  {
    154    id: "privacy.trackingprotection.allow_list.baseline.enabled",
    155    type: "bool",
    156  },
    157  {
    158    id: "privacy.trackingprotection.allow_list.convenience.enabled",
    159    type: "bool",
    160  },
    161 
    162  // Fingerprinting Protection
    163  { id: "privacy.fingerprintingProtection", type: "bool" },
    164  { id: "privacy.fingerprintingProtection.pbmode", type: "bool" },
    165 
    166  // Resist Fingerprinting
    167  { id: "privacy.resistFingerprinting", type: "bool" },
    168  { id: "privacy.resistFingerprinting.pbmode", type: "bool" },
    169 
    170  // Social tracking
    171  { id: "privacy.trackingprotection.socialtracking.enabled", type: "bool" },
    172  { id: "privacy.socialtracking.block_cookies.enabled", type: "bool" },
    173 
    174  // Tracker list
    175  { id: "urlclassifier.trackingTable", type: "string" },
    176 
    177  // Button prefs
    178  { id: "pref.privacy.disable_button.cookie_exceptions", type: "bool" },
    179  {
    180    id: "pref.privacy.disable_button.tracking_protection_exceptions",
    181    type: "bool",
    182  },
    183 
    184  // History
    185  { id: "places.history.enabled", type: "bool" },
    186  { id: "browser.formfill.enable", type: "bool" },
    187  { id: "privacy.history.custom", type: "bool" },
    188 
    189  // Cookies
    190  { id: "network.cookie.cookieBehavior", type: "int" },
    191  { id: "network.cookie.blockFutureCookies", type: "bool" },
    192  // Content blocking category
    193  { id: "browser.contentblocking.category", type: "string" },
    194  { id: "browser.contentblocking.features.strict", type: "string" },
    195 
    196  // Clear Private Data
    197  { id: "privacy.sanitize.sanitizeOnShutdown", type: "bool" },
    198  { id: "privacy.sanitize.timeSpan", type: "int" },
    199  { id: "privacy.clearOnShutdown.cookies", type: "bool" },
    200  { id: "privacy.clearOnShutdown_v2.cookiesAndStorage", type: "bool" },
    201  { id: "privacy.clearOnShutdown.cache", type: "bool" },
    202  { id: "privacy.clearOnShutdown_v2.cache", type: "bool" },
    203  { id: "privacy.clearOnShutdown.offlineApps", type: "bool" },
    204  { id: "privacy.clearOnShutdown.history", type: "bool" },
    205  {
    206    id: "privacy.clearOnShutdown_v2.browsingHistoryAndDownloads",
    207    type: "bool",
    208  },
    209  { id: "privacy.clearOnShutdown.downloads", type: "bool" },
    210  { id: "privacy.clearOnShutdown.sessions", type: "bool" },
    211  { id: "privacy.clearOnShutdown.formdata", type: "bool" },
    212  { id: "privacy.clearOnShutdown.siteSettings", type: "bool" },
    213  { id: "privacy.clearOnShutdown_v2.siteSettings", type: "bool" },
    214 
    215  // Do not track and Global Privacy Control
    216  { id: "privacy.donottrackheader.enabled", type: "bool" },
    217  { id: "privacy.globalprivacycontrol.functionality.enabled", type: "bool" },
    218  { id: "privacy.globalprivacycontrol.enabled", type: "bool" },
    219  {
    220    id: "browser.preferences.config_warning.donottrackheader.dismissed",
    221    type: "bool",
    222  },
    223 
    224  // Firefox VPN
    225  { id: "browser.ipProtection.enabled", type: "bool" },
    226  { id: "browser.ipProtection.features.siteExceptions", type: "bool" },
    227  { id: "browser.ipProtection.features.autoStart", type: "bool" },
    228  { id: "browser.ipProtection.autoStartEnabled", type: "bool" },
    229  { id: "browser.ipProtection.autoStartPrivateEnabled", type: "bool" },
    230 
    231  // Media
    232  { id: "media.autoplay.default", type: "int" },
    233 
    234  // Popups
    235  { id: "dom.disable_open_during_load", type: "bool" },
    236 
    237  // Passwords
    238  { id: "signon.rememberSignons", type: "bool" },
    239  { id: "signon.generation.enabled", type: "bool" },
    240  { id: "signon.autofillForms", type: "bool" },
    241  { id: "signon.management.page.breach-alerts.enabled", type: "bool" },
    242  { id: "signon.firefoxRelay.feature", type: "string" },
    243 
    244  // Buttons
    245  { id: "pref.privacy.disable_button.view_passwords", type: "bool" },
    246  { id: "pref.privacy.disable_button.view_passwords_exceptions", type: "bool" },
    247 
    248  /* Certificates tab
    249   * security.default_personal_cert
    250   *   - a string:
    251   *       "Select Automatically"   select a certificate automatically when a site
    252   *                                requests one
    253   *       "Ask Every Time"         present a dialog to the user so he can select
    254   *                                the certificate to use on a site which
    255   *                                requests one
    256   */
    257  { id: "security.default_personal_cert", type: "string" },
    258 
    259  { id: "security.disable_button.openCertManager", type: "bool" },
    260 
    261  { id: "security.disable_button.openDeviceManager", type: "bool" },
    262 
    263  { id: "security.enterprise_roots.enabled", type: "bool" },
    264 
    265  // Add-ons, malware, phishing
    266  { id: "xpinstall.whitelist.required", type: "bool" },
    267 
    268  { id: "browser.safebrowsing.malware.enabled", type: "bool" },
    269  { id: "browser.safebrowsing.phishing.enabled", type: "bool" },
    270 
    271  { id: "browser.safebrowsing.downloads.enabled", type: "bool" },
    272 
    273  { id: "urlclassifier.malwareTable", type: "string" },
    274 
    275  {
    276    id: "browser.safebrowsing.downloads.remote.block_potentially_unwanted",
    277    type: "bool",
    278  },
    279  { id: "browser.safebrowsing.downloads.remote.block_uncommon", type: "bool" },
    280 
    281  // First-Party Isolation
    282  { id: "privacy.firstparty.isolate", type: "bool" },
    283 
    284  // HTTPS-Only
    285  { id: "dom.security.https_only_mode", type: "bool" },
    286  { id: "dom.security.https_only_mode_pbm", type: "bool" },
    287  { id: "dom.security.https_first", type: "bool" },
    288  { id: "dom.security.https_first_pbm", type: "bool" },
    289 
    290  // Windows SSO
    291  { id: "network.http.windows-sso.enabled", type: "bool" },
    292 
    293  // Cookie Banner Handling
    294  { id: "cookiebanners.ui.desktop.enabled", type: "bool" },
    295  { id: "cookiebanners.service.mode.privateBrowsing", type: "int" },
    296 
    297  // DoH
    298  { id: "network.trr.mode", type: "int" },
    299  { id: "network.trr.uri", type: "string" },
    300  { id: "network.trr.default_provider_uri", type: "string" },
    301  { id: "network.trr.custom_uri", type: "string" },
    302  { id: "network.trr_ui.fallback_was_checked", type: "bool" },
    303  { id: "doh-rollout.disable-heuristics", type: "bool" },
    304 
    305  // Local Network Access
    306  { id: "network.lna.blocking", type: "bool" },
    307 
    308  // Permissions
    309  { id: "media.setsinkid.enabled", type: "bool" },
    310 ]);
    311 
    312 if (SECURITY_PRIVACY_STATUS_CARD_ENABLED) {
    313  Preferences.addAll([
    314    // Security and Privacy Warnings
    315    { id: "browser.preferences.config_warning.dismissAll", type: "bool" },
    316    { id: "privacy.ui.status_card.testing.show_issue", type: "bool" },
    317    {
    318      id: "browser.preferences.config_warning.warningTest.dismissed",
    319      type: "bool",
    320    },
    321    {
    322      id: "browser.preferences.config_warning.warningAllowFingerprinters.dismissed",
    323      type: "bool",
    324    },
    325    {
    326      id: "browser.preferences.config_warning.warningThirdPartyCookies.dismissed",
    327      type: "bool",
    328    },
    329    {
    330      id: "browser.preferences.config_warning.warningPasswordManager.dismissed",
    331      type: "bool",
    332    },
    333    {
    334      id: "browser.preferences.config_warning.warningPopupBlocker.dismissed",
    335      type: "bool",
    336    },
    337    {
    338      id: "browser.preferences.config_warning.warningExtensionInstall.dismissed",
    339      type: "bool",
    340    },
    341    {
    342      id: "browser.preferences.config_warning.warningSafeBrowsing.dismissed",
    343      type: "bool",
    344    },
    345    {
    346      id: "browser.preferences.config_warning.warningDoH.dismissed",
    347      type: "bool",
    348    },
    349    {
    350      id: "browser.preferences.config_warning.warningECH.dismissed",
    351      type: "bool",
    352    },
    353    {
    354      id: "browser.preferences.config_warning.warningCT.dismissed",
    355      type: "bool",
    356    },
    357    {
    358      id: "browser.preferences.config_warning.warningCRLite.dismissed",
    359      type: "bool",
    360    },
    361    {
    362      id: "browser.preferences.config_warning.warningCertificatePinning.dismissed",
    363      type: "bool",
    364    },
    365    {
    366      id: "browser.preferences.config_warning.warningTLSMin.dismissed",
    367      type: "bool",
    368    },
    369    {
    370      id: "browser.preferences.config_warning.warningTLSMax.dismissed",
    371      type: "bool",
    372    },
    373    {
    374      id: "browser.preferences.config_warning.warningProxyAutodetection.dismissed",
    375      type: "bool",
    376    },
    377    {
    378      id: "browser.preferences.config_warning.warningPrivilegedConstraint.dismissed",
    379      type: "bool",
    380    },
    381    {
    382      id: "browser.preferences.config_warning.warningProcessSandbox.dismissed",
    383      type: "bool",
    384    },
    385    {
    386      id: "browser.preferences.config_warning.warningContentResourceURI.dismissed",
    387      type: "bool",
    388    },
    389    {
    390      id: "browser.preferences.config_warning.warningWorkerMIME.dismissed",
    391      type: "bool",
    392    },
    393    {
    394      id: "browser.preferences.config_warning.warningTopLevelDataURI.dismissed",
    395      type: "bool",
    396    },
    397    {
    398      id: "browser.preferences.config_warning.warningActiveMixedContent.dismissed",
    399      type: "bool",
    400    },
    401    {
    402      id: "browser.preferences.config_warning.warningInnerHTMLltgt.dismissed",
    403      type: "bool",
    404    },
    405    {
    406      id: "browser.preferences.config_warning.warningFileURIOrigin.dismissed",
    407      type: "bool",
    408    },
    409    {
    410      id: "services.passwordSavingEnabled",
    411      type: "bool",
    412    },
    413    {
    414      id: "network.dns.echconfig.enabled",
    415      type: "bool",
    416    },
    417    {
    418      id: "network.dns.http3_echconfig.enabled",
    419      type: "bool",
    420    },
    421    {
    422      id: "security.pki.certificate_transparency.mode",
    423      type: "int",
    424    },
    425    {
    426      id: "security.pki.crlite_mode",
    427      type: "int",
    428    },
    429    {
    430      id: "security.cert_pinning.enforcement_level",
    431      type: "int",
    432    },
    433    {
    434      id: "security.tls.version.min",
    435      type: "int",
    436    },
    437    {
    438      id: "security.tls.version.fallback-limit",
    439      type: "int",
    440    },
    441    {
    442      id: "security.tls.version.enable-deprecated",
    443      type: "bool",
    444    },
    445    {
    446      id: "security.tls.version.max",
    447      type: "int",
    448    },
    449    {
    450      id: "network.proxy.type",
    451      type: "int",
    452    },
    453    {
    454      id: "security.all_resource_uri_content_accessible",
    455      type: "bool",
    456    },
    457    {
    458      id: "security.block_Worker_with_wrong_mime",
    459      type: "bool",
    460    },
    461    {
    462      id: "security.data_uri.block_toplevel_data_uri_navigations",
    463      type: "bool",
    464    },
    465    {
    466      id: "security.mixed_content.block_active_content",
    467      type: "bool",
    468    },
    469    {
    470      id: "dom.security.html_serialization_escape_lt_gt",
    471      type: "bool",
    472    },
    473    {
    474      id: "security.fileuri.strict_origin_policy",
    475      type: "bool",
    476    },
    477    {
    478      id: "dom.security.skip_html_fragment_assertion",
    479      type: "bool",
    480    },
    481    {
    482      id: "security.browser_xhtml_csp.enabled",
    483      type: "bool",
    484    },
    485    {
    486      id: "security.allow_unsafe_dangerous_privileged_evil_eval",
    487      type: "bool",
    488    },
    489    {
    490      id: "security.allow_eval_in_parent_process",
    491      type: "bool",
    492    },
    493    {
    494      id: "security.allow_eval_with_system_principal",
    495      type: "bool",
    496    },
    497    {
    498      id: "security.allow_unsafe_parent_loads",
    499      type: "bool",
    500    },
    501    {
    502      id: "security.allow_parent_unrestricted_js_loads",
    503      type: "bool",
    504    },
    505    {
    506      id: "dom.security.skip_remote_script_assertion_in_system_priv_context",
    507      type: "bool",
    508    },
    509    {
    510      id: "security.sandbox.content.mac.disconnect-windowserver",
    511      type: "bool",
    512    },
    513    {
    514      id: "security.sandbox.content.write_path_whitelist",
    515      type: "string",
    516    },
    517    {
    518      id: "security.sandbox.content.read_path_whitelist",
    519      type: "string",
    520    },
    521    {
    522      id: "security.sandbox.content.syscall_whitelist",
    523      type: "string",
    524    },
    525    {
    526      id: "security.sandbox.content.level",
    527      type: "int",
    528    },
    529    {
    530      id: "security.sandbox.socket.process.level",
    531      type: "int",
    532    },
    533    {
    534      id: "security.sandbox.gpu.level",
    535      type: "int",
    536    },
    537    {
    538      id: "security.sandbox.content.win32k-disable",
    539      type: "bool",
    540    },
    541    {
    542      id: "security.sandbox.gmp.win32k-disable",
    543      type: "bool",
    544    },
    545    {
    546      id: "security.sandbox.gmp.acg.enabled",
    547      type: "bool",
    548    },
    549    {
    550      id: "security.sandbox.socket.win32k-disable",
    551      type: "bool",
    552    },
    553    {
    554      id: "security.sandbox.rdd.shadow-stack.enabled",
    555      type: "bool",
    556    },
    557    {
    558      id: "security.sandbox.socket.shadow-stack.enabled",
    559      type: "bool",
    560    },
    561    {
    562      id: "security.sandbox.gpu.shadow-stack.enabled",
    563      type: "bool",
    564    },
    565    {
    566      id: "security.sandbox.gmp.shadow-stack.enabled",
    567      type: "bool",
    568    },
    569    {
    570      id: "security.sandbox.utility-wmf-cdm.lpac.enabled",
    571      type: "bool",
    572    },
    573    {
    574      id: "security.sandbox.rdd.acg.enabled",
    575      type: "bool",
    576    },
    577    {
    578      id: "security.sandbox.utility-wmf.acg.enabled",
    579      type: "bool",
    580    },
    581  ]);
    582 
    583  Preferences.addSetting({
    584    id: "etpStrictEnabled",
    585    pref: "browser.contentblocking.category",
    586    get: prefValue => prefValue == "strict",
    587  });
    588  Preferences.addSetting(
    589    /** @type {{ cachedValue: number, loadTrackerCount: (emitChange: SettingEmitChange) => Promise<void> } & SettingConfig} */ ({
    590      id: "trackerCount",
    591      cachedValue: null,
    592      async loadTrackerCount(emitChange) {
    593        const now = Date.now();
    594        const aMonthAgo = new Date(now - 30 * 24 * 60 * 60 * 1000);
    595        /** @type {{ getResultByName: (_: string) => number }[]} */
    596        const events = await lazy.TrackingDBService.getEventsByDateRange(
    597          now,
    598          aMonthAgo
    599        );
    600 
    601        const total = events.reduce((acc, day) => {
    602          return acc + day.getResultByName("count");
    603        }, 0);
    604        this.cachedValue = total;
    605        emitChange();
    606      },
    607      setup(emitChange) {
    608        this.loadTrackerCount(emitChange);
    609      },
    610      get() {
    611        return this.cachedValue;
    612      },
    613    })
    614  );
    615  Preferences.addSetting(
    616    /** @type {{ cachedValue: any } & SettingConfig} */ ({
    617      id: "appUpdateStatus",
    618      cachedValue: AppUpdater.STATUS.NO_UPDATER,
    619      setup(emitChange) {
    620        if (AppConstants.MOZ_UPDATER && !gIsPackagedApp) {
    621          let appUpdater = new AppUpdater();
    622          /**
    623           * @param {number} status
    624           * @param {any[]} _args
    625           */
    626          let listener = (status, ..._args) => {
    627            this.cachedValue = status;
    628            emitChange();
    629          };
    630          appUpdater.addListener(listener);
    631          appUpdater.check();
    632          return () => {
    633            appUpdater.removeListener(listener);
    634            appUpdater.stop();
    635          };
    636        }
    637        return () => {};
    638      },
    639      get() {
    640        return this.cachedValue;
    641      },
    642      set(value) {
    643        this.cachedValue = value;
    644      },
    645    })
    646  );
    647 }
    648 
    649 Preferences.addSetting({
    650  id: "savePasswords",
    651  pref: "signon.rememberSignons",
    652  controllingExtensionInfo: {
    653    storeId: "services.passwordSavingEnabled",
    654    l10nId: "extension-controlling-password-saving",
    655  },
    656 });
    657 
    658 Preferences.addSetting({
    659  id: "managePasswordExceptions",
    660  onUserClick: () => {
    661    gPrivacyPane.showPasswordExceptions();
    662  },
    663 });
    664 
    665 Preferences.addSetting({
    666  id: "fillUsernameAndPasswords",
    667  pref: "signon.autofillForms",
    668 });
    669 
    670 Preferences.addSetting({
    671  id: "suggestStrongPasswords",
    672  pref: "signon.generation.enabled",
    673  visible: () => Services.prefs.getBoolPref("signon.generation.available"),
    674 });
    675 
    676 Preferences.addSetting({
    677  id: "requireOSAuthForPasswords",
    678  visible: () => OSKeyStore.canReauth(),
    679  get: () => LoginHelper.getOSAuthEnabled(),
    680  async set(checked) {
    681    const [messageText, captionText] = await Promise.all([
    682      lazy.AboutLoginsL10n.formatValue("about-logins-os-auth-dialog-message"),
    683      lazy.AboutLoginsL10n.formatValue("about-logins-os-auth-dialog-caption"),
    684    ]);
    685 
    686    await LoginHelper.trySetOSAuthEnabled(
    687      window,
    688      checked,
    689      messageText,
    690      captionText
    691    );
    692 
    693    // Trigger change event to keep checkbox UI in sync with pref value
    694    Services.obs.notifyObservers(null, "PasswordsOSAuthEnabledChange");
    695  },
    696  setup: emitChange => {
    697    Services.obs.addObserver(emitChange, "PasswordsOSAuthEnabledChange");
    698    return () =>
    699      Services.obs.removeObserver(emitChange, "PasswordsOSAuthEnabledChange");
    700  },
    701 });
    702 
    703 Preferences.addSetting({
    704  id: "allowWindowSSO",
    705  pref: "network.http.windows-sso.enabled",
    706  visible: () => AppConstants.platform === "win",
    707 });
    708 
    709 Preferences.addSetting({
    710  id: "manageSavedPasswords",
    711  onUserClick: ({ target }) => {
    712    target.ownerGlobal.gPrivacyPane.showPasswords();
    713  },
    714 });
    715 
    716 Preferences.addSetting({
    717  id: "additionalProtectionsGroup",
    718 });
    719 
    720 Preferences.addSetting({
    721  id: "primaryPasswordNotSet",
    722  setup(emitChange) {
    723    const topic = "passwordmgr-primary-pw-changed";
    724    Services.obs.addObserver(emitChange, topic);
    725    return () => Services.obs.removeObserver(emitChange, topic);
    726  },
    727  visible: () => {
    728    return !LoginHelper.isPrimaryPasswordSet();
    729  },
    730 });
    731 
    732 Preferences.addSetting({
    733  id: "usePrimaryPassword",
    734  deps: ["primaryPasswordNotSet"],
    735 });
    736 
    737 Preferences.addSetting({
    738  id: "addPrimaryPassword",
    739  deps: ["primaryPasswordNotSet"],
    740  onUserClick: ({ target }) => {
    741    target.ownerGlobal.gPrivacyPane.changeMasterPassword();
    742  },
    743  disabled: () => {
    744    return !Services.policies.isAllowed("createMasterPassword");
    745  },
    746 });
    747 
    748 Preferences.addSetting({
    749  id: "primaryPasswordSet",
    750  setup(emitChange) {
    751    const topic = "passwordmgr-primary-pw-changed";
    752    Services.obs.addObserver(emitChange, topic);
    753    return () => Services.obs.removeObserver(emitChange, topic);
    754  },
    755  visible: () => {
    756    return LoginHelper.isPrimaryPasswordSet();
    757  },
    758 });
    759 
    760 Preferences.addSetting({
    761  id: "statusPrimaryPassword",
    762  deps: ["primaryPasswordSet"],
    763  onUserClick: e => {
    764    if (e.target.localName == "moz-button") {
    765      e.target.ownerGlobal.gPrivacyPane._removeMasterPassword();
    766    }
    767  },
    768  getControlConfig(config) {
    769    config.options[0].controlAttrs = {
    770      ...config.options[0].controlAttrs,
    771      ...(!Services.policies.isAllowed("removeMasterPassword")
    772        ? { disabled: "" }
    773        : {}),
    774    };
    775    return config;
    776  },
    777 });
    778 
    779 Preferences.addSetting({
    780  id: "changePrimaryPassword",
    781  deps: ["primaryPasswordSet"],
    782  onUserClick: ({ target }) => {
    783    target.ownerGlobal.gPrivacyPane.changeMasterPassword();
    784  },
    785 });
    786 
    787 Preferences.addSetting({
    788  id: "breachAlerts",
    789  pref: "signon.management.page.breach-alerts.enabled",
    790 });
    791 
    792 /**
    793 * This class is used to create Settings that are used to warn the user about
    794 * potential misconfigurations. It should be passed into Preferences.addSetting
    795 * to create the Preference for a <moz-box-item> because it creates
    796 * separate members on pref.config
    797 *
    798 * @implements {SettingConfig}
    799 */
    800 class WarningSettingConfig {
    801  /**
    802   * This callback type specifies the most important part of a WarningSettingConfig: how to know
    803   * when to warn.
    804   *
    805   * @callback problematicCallback
    806   * @param {WarningSettingConfig} self - this is a Setting config created by the constructor below,
    807   * that has been `setup` and not yet cleaned up. Its prefMapping is setup into its properties.
    808   * @returns {boolean} Should this Setting show a warning to the user if not yet dismissed?
    809   */
    810 
    811  /**
    812   *
    813   * @param {string} id - The unique setting ID for the setting created by this config
    814   * @param {{[key: string]: string}} prefMapping - A map from member name (to be used in the
    815   * `problematic` arg's arg) to pref string, containing all of the preferences this Setting
    816   * relies upon. On setup, this object will create properties for each entry here, where the
    817   * value is the result of Preferences.get(key).
    818   * @param {problematicCallback} problematic - How we determine whether or not to show this
    819   * setting initially
    820   * @param {boolean} isDismissable - A boolean indicating whether or not we should support dismissing
    821   * this setting
    822   */
    823  constructor(id, prefMapping, problematic, isDismissable) {
    824    this.id = id;
    825    this.prefMapping = prefMapping;
    826    if (isDismissable) {
    827      this.dismissedPrefId = `browser.preferences.config_warning.${this.id}.dismissed`;
    828      this.prefMapping.dismissed = this.dismissedPrefId;
    829      this.dismissAllPrefId = `browser.preferences.config_warning.dismissAll`;
    830      this.prefMapping.dismissAll = this.dismissAllPrefId;
    831    }
    832    this.problematic = problematic;
    833  }
    834 
    835  /**
    836   * This item in a warning moz-box-group should be visible if the `problematic` argument
    837   * from the constructor says we should, and it isn't hidden.
    838   *
    839   * @returns {boolean} Whether or not to show this configuration as a warning to the user
    840   */
    841  visible() {
    842    return (
    843      !this.dismissAll?.value &&
    844      !this.dismissed?.value &&
    845      this.problematic(this)
    846    );
    847  }
    848 
    849  /**
    850   * This resets all of the preferernces in the `prefMapping` from the constructor that have
    851   * user-specified values. This includes the dismiss pref as well.
    852   */
    853  reset() {
    854    for (let getter of Object.keys(this.prefMapping)) {
    855      if (this[getter].hasUserValue) {
    856        this[getter].reset();
    857      }
    858    }
    859  }
    860 
    861  /**
    862   * When invoked, this sets a pref that persistently hides this setting. See visible().
    863   */
    864  dismiss() {
    865    if (this.dismissed) {
    866      this.dismissed.value = true;
    867    }
    868  }
    869 
    870  /**
    871   * This initializes the Setting created with this config, starting listeners for all dependent
    872   * Preferences and providing a cleanup callback to remove them
    873   *
    874   * @param {() => any} emitChange - a callback to be invoked any time that the Setting created
    875   * with this config is changed
    876   * @returns {() => any} a function that cleans up the state from this Setting, namely pref change listeners.
    877   */
    878  setup(emitChange) {
    879    for (let [getter, prefId] of Object.entries(this.prefMapping)) {
    880      this[getter] = Preferences.get(prefId);
    881      this[getter].on("change", emitChange);
    882    }
    883    return () => {
    884      for (let getter of Object.keys(this.prefMapping)) {
    885        this[getter].off(emitChange);
    886      }
    887    };
    888  }
    889 
    890  /**
    891   * Setting helper to handle clicks of our warning. They may be a "reset" or
    892   * "dismiss" action depending on the target, and those callbacks are defined
    893   * in this class.
    894   *
    895   * @param {PointerEvent} event - The event for the user click
    896   */
    897  onUserClick(event) {
    898    switch (event.target.id) {
    899      case "reset": {
    900        this.reset();
    901        Glean.securityPreferencesWarnings.warningFixed.record();
    902        break;
    903      }
    904      case "dismiss": {
    905        this.dismiss();
    906        Glean.securityPreferencesWarnings.warningDismissed.record();
    907        break;
    908      }
    909    }
    910  }
    911 }
    912 
    913 if (SECURITY_PRIVACY_STATUS_CARD_ENABLED) {
    914  Preferences.addSetting(
    915    new WarningSettingConfig(
    916      "warningTest",
    917      {
    918        showIssue: "privacy.ui.status_card.testing.show_issue",
    919      },
    920      ({ showIssue }) => showIssue.hasUserValue && !showIssue.locked,
    921      true
    922    )
    923  );
    924 
    925  Preferences.addSetting(
    926    new WarningSettingConfig(
    927      "warningAllowFingerprinters",
    928      {
    929        fingerprintingEnabled:
    930          "privacy.trackingprotection.fingerprinting.enabled",
    931      },
    932      ({ fingerprintingEnabled }) =>
    933        !fingerprintingEnabled.value && !fingerprintingEnabled.locked,
    934      true
    935    )
    936  );
    937 
    938  Preferences.addSetting(
    939    new WarningSettingConfig(
    940      "warningThirdPartyCookies",
    941      {
    942        cookieBehavior: "network.cookie.cookieBehavior",
    943      },
    944      ({ cookieBehavior }) =>
    945        (cookieBehavior.value == 0 ||
    946          cookieBehavior.value == 3 ||
    947          cookieBehavior.value == 4) &&
    948        !cookieBehavior.locked,
    949      true
    950    )
    951  );
    952 
    953  Preferences.addSetting(
    954    new WarningSettingConfig(
    955      "warningPasswordManager",
    956      {
    957        enabled: "signon.rememberSignons",
    958        extentionAllows: "services.passwordSavingEnabled",
    959      },
    960      ({ enabled, extentionAllows }) =>
    961        !enabled.value && !enabled.locked && !extentionAllows.value,
    962      true
    963    )
    964  );
    965 
    966  Preferences.addSetting(
    967    new WarningSettingConfig(
    968      "warningPopupBlocker",
    969      {
    970        enabled: "dom.disable_open_during_load",
    971      },
    972      ({ enabled }) => !enabled.value && !enabled.locked,
    973      true
    974    )
    975  );
    976 
    977  Preferences.addSetting(
    978    new WarningSettingConfig(
    979      "warningExtensionInstall",
    980      {
    981        blockInstalls: "xpinstall.whitelist.required",
    982      },
    983      ({ blockInstalls }) => !blockInstalls.value && !blockInstalls.locked,
    984      true
    985    )
    986  );
    987 
    988  Preferences.addSetting(
    989    new WarningSettingConfig(
    990      "warningSafeBrowsing",
    991      {
    992        malware: "browser.safebrowsing.malware.enabled",
    993        phishing: "browser.safebrowsing.phishing.enabled",
    994        downloads: "browser.safebrowsing.downloads.enabled",
    995        unwantedDownloads:
    996          "browser.safebrowsing.downloads.remote.block_potentially_unwanted",
    997        uncommonDownloads:
    998          "browser.safebrowsing.downloads.remote.block_potentially_unwanted",
    999      },
   1000      ({
   1001        malware,
   1002        phishing,
   1003        downloads,
   1004        unwantedDownloads,
   1005        uncommonDownloads,
   1006      }) =>
   1007        (!malware.value && !malware.locked) ||
   1008        (!phishing.value && !phishing.locked) ||
   1009        (!downloads.value && !downloads.locked) ||
   1010        (!unwantedDownloads.value && !unwantedDownloads.locked) ||
   1011        (!uncommonDownloads.value && !uncommonDownloads.locked),
   1012      true
   1013    )
   1014  );
   1015 
   1016  Preferences.addSetting(
   1017    new WarningSettingConfig(
   1018      "warningDoH",
   1019      {
   1020        dohMode: "network.trr.mode",
   1021      },
   1022      ({ dohMode }) => dohMode.value == 5 && !dohMode.locked,
   1023      true
   1024    )
   1025  );
   1026 
   1027  Preferences.addSetting(
   1028    new WarningSettingConfig(
   1029      "warningECH",
   1030      {
   1031        echEnabled: "network.dns.echconfig.enabled",
   1032        https3echEnabled: "network.dns.http3_echconfig.enabled",
   1033      },
   1034      ({ echEnabled, https3echEnabled }) =>
   1035        (!echEnabled.value && !echEnabled.locked) ||
   1036        (!https3echEnabled.value && !https3echEnabled.locked),
   1037      true
   1038    )
   1039  );
   1040 
   1041  Preferences.addSetting(
   1042    new WarningSettingConfig(
   1043      "warningCT",
   1044      {
   1045        ctMode: "security.pki.certificate_transparency.mode",
   1046      },
   1047      ({ ctMode }) => ctMode.value != 2 && !ctMode.locked,
   1048      true
   1049    )
   1050  );
   1051 
   1052  Preferences.addSetting(
   1053    new WarningSettingConfig(
   1054      "warningCRLite",
   1055      {
   1056        crliteMode: "security.pki.crlite_mode",
   1057      },
   1058      ({ crliteMode }) => crliteMode.value != 2 && !crliteMode.locked,
   1059      true
   1060    )
   1061  );
   1062 
   1063  Preferences.addSetting(
   1064    new WarningSettingConfig(
   1065      "warningCertificatePinning",
   1066      {
   1067        pinningLevel: "security.cert_pinning.enforcement_level",
   1068      },
   1069      ({ pinningLevel }) => pinningLevel.value < 1 && !pinningLevel.locked,
   1070      true
   1071    )
   1072  );
   1073 
   1074  Preferences.addSetting(
   1075    new WarningSettingConfig(
   1076      "warningTLSMin",
   1077      {
   1078        tlsMin: "security.tls.version.min",
   1079        enableDeprecated: "security.tls.version.enable-deprecated",
   1080        fallbackLimit: "security.tls.version.fallback-limit",
   1081      },
   1082      ({ tlsMin, enableDeprecated, fallbackLimit }) =>
   1083        (tlsMin.value < 3 && !tlsMin.locked) ||
   1084        (enableDeprecated.value && !tlsMin.locked) ||
   1085        (fallbackLimit.value < 4 && !tlsMin.locked),
   1086      true
   1087    )
   1088  );
   1089 
   1090  Preferences.addSetting(
   1091    new WarningSettingConfig(
   1092      "warningTLSMax",
   1093      {
   1094        tlsMax: "security.tls.version.max",
   1095      },
   1096      ({ tlsMax }) => tlsMax.value < 4 && !tlsMax.locked,
   1097      true
   1098    )
   1099  );
   1100 
   1101  Preferences.addSetting(
   1102    new WarningSettingConfig(
   1103      "warningProxyAutodetection",
   1104      {
   1105        proxyType: "network.proxy.type",
   1106      },
   1107      ({ proxyType }) => proxyType.value == 2 && !proxyType.locked,
   1108      true
   1109    )
   1110  );
   1111 
   1112  Preferences.addSetting(
   1113    new WarningSettingConfig(
   1114      "warningContentResourceURI",
   1115      {
   1116        contentResourceURIAccessible:
   1117          "security.all_resource_uri_content_accessible",
   1118      },
   1119      ({ contentResourceURIAccessible }) =>
   1120        contentResourceURIAccessible.value &&
   1121        !contentResourceURIAccessible.locked,
   1122      true
   1123    )
   1124  );
   1125 
   1126  Preferences.addSetting(
   1127    new WarningSettingConfig(
   1128      "warningWorkerMIME",
   1129      {
   1130        workerMimeTypeBlock: "security.block_Worker_with_wrong_mime",
   1131      },
   1132      ({ workerMimeTypeBlock }) =>
   1133        !workerMimeTypeBlock.value && !workerMimeTypeBlock.locked,
   1134      true
   1135    )
   1136  );
   1137 
   1138  Preferences.addSetting(
   1139    new WarningSettingConfig(
   1140      "warningTopLevelDataURI",
   1141      {
   1142        blockNav: "security.data_uri.block_toplevel_data_uri_navigations",
   1143      },
   1144      ({ blockNav }) => !blockNav.value && !blockNav.locked,
   1145      true
   1146    )
   1147  );
   1148 
   1149  Preferences.addSetting(
   1150    new WarningSettingConfig(
   1151      "warningActiveMixedContent",
   1152      {
   1153        blockedMixedContent: "security.mixed_content.block_active_content",
   1154      },
   1155      ({ blockedMixedContent }) =>
   1156        !blockedMixedContent.value && !blockedMixedContent.locked,
   1157      true
   1158    )
   1159  );
   1160 
   1161  Preferences.addSetting(
   1162    new WarningSettingConfig(
   1163      "warningInnerHTMLltgt",
   1164      {
   1165        escapeLtGt: "dom.security.html_serialization_escape_lt_gt",
   1166      },
   1167      ({ escapeLtGt }) => !escapeLtGt.value && !escapeLtGt.locked,
   1168      true
   1169    )
   1170  );
   1171 
   1172  Preferences.addSetting(
   1173    new WarningSettingConfig(
   1174      "warningFileURIOrigin",
   1175      {
   1176        fileURIStrictOrigin: "security.fileuri.strict_origin_policy",
   1177      },
   1178      ({ fileURIStrictOrigin }) =>
   1179        !fileURIStrictOrigin.value && !fileURIStrictOrigin.locked,
   1180      true
   1181    )
   1182  );
   1183 
   1184  Preferences.addSetting(
   1185    new WarningSettingConfig(
   1186      "warningPrivilegedConstraint",
   1187      {
   1188        shfa: "dom.security.skip_html_fragment_assertion",
   1189        xhtmlcsp: "security.browser_xhtml_csp.enabled",
   1190        allowUDPEE: "security.allow_unsafe_dangerous_privileged_evil_eval",
   1191        allowEvalInParent: "security.allow_eval_in_parent_process",
   1192        allowEvalBySystem: "security.allow_eval_with_system_principal",
   1193        allowUnsafeParentLoads: "security.allow_unsafe_parent_loads",
   1194        allowParentUnrestrictedJSLoads:
   1195          "security.allow_parent_unrestricted_js_loads",
   1196        skipRemoteScriptAssertionInSystem:
   1197          "dom.security.skip_remote_script_assertion_in_system_priv_context",
   1198      },
   1199      ({
   1200        shfa,
   1201        xhtmlcsp,
   1202        allowUDPEE,
   1203        allowEvalInParent,
   1204        allowEvalBySystem,
   1205        allowUnsafeParentLoads,
   1206        allowParentUnrestrictedJSLoads,
   1207        skipRemoteScriptAssertionInSystem,
   1208      }) =>
   1209        (!xhtmlcsp.value && !xhtmlcsp.locked) ||
   1210        (shfa.value && !shfa.locked) ||
   1211        (allowUDPEE.value && !allowUDPEE.locked) ||
   1212        (allowEvalInParent.value && !allowEvalInParent.locked) ||
   1213        (allowEvalBySystem.value && !allowEvalBySystem.locked) ||
   1214        (allowUnsafeParentLoads.value && !allowUnsafeParentLoads.locked) ||
   1215        (allowParentUnrestrictedJSLoads.value &&
   1216          !allowParentUnrestrictedJSLoads.locked) ||
   1217        (skipRemoteScriptAssertionInSystem.value &&
   1218          !skipRemoteScriptAssertionInSystem.locked),
   1219      true
   1220    )
   1221  );
   1222 
   1223  Preferences.addSetting(
   1224    new WarningSettingConfig(
   1225      "warningProcessSandbox",
   1226      {
   1227        macNoWindowServer:
   1228          "security.sandbox.content.mac.disconnect-windowserver",
   1229        contentWriteWhitelist: "security.sandbox.content.write_path_whitelist",
   1230        contentReadWhitelist: "security.sandbox.content.read_path_whitelist",
   1231        contentSyscallWhitelist: "security.sandbox.content.syscall_whitelist",
   1232        contentSandboxLevel: "security.sandbox.content.level",
   1233        socketSandboxLevel: "security.sandbox.socket.process.level",
   1234        gpuSandboxLevel: "security.sandbox.gpu.level",
   1235        content32kDisable: "security.sandbox.content.win32k-disable",
   1236        gmp32kDisable: "security.sandbox.gmp.win32k-disable",
   1237        gmpACGEnable: "security.sandbox.gmp.acg.enabled",
   1238        socket32kDisable: "security.sandbox.socket.win32k-disable",
   1239        rddShadowStackEnabled: "security.sandbox.rdd.shadow-stack.enabled",
   1240        socketShadowStackEnabled:
   1241          "security.sandbox.socket.shadow-stack.enabled",
   1242        gpuShadowStackEnabled: "security.sandbox.gpu.shadow-stack.enabled",
   1243        gmpShadowStackEnabled: "security.sandbox.gmp.shadow-stack.enabled",
   1244        utilityWmfCdmLpacEnabled:
   1245          "security.sandbox.utility-wmf-cdm.lpac.enabled",
   1246        rddACGEnabled: "security.sandbox.rdd.acg.enabled",
   1247        utilityWmfACGEnabled: "security.sandbox.utility-wmf.acg.enabled",
   1248      },
   1249      ({
   1250        macNoWindowServer,
   1251        contentWriteWhitelist,
   1252        contentReadWhitelist,
   1253        contentSyscallWhitelist,
   1254        contentSandboxLevel,
   1255        socketSandboxLevel,
   1256        gpuSandboxLevel,
   1257        content32kDisable,
   1258        gmp32kDisable,
   1259        gmpACGEnable,
   1260        socket32kDisable,
   1261        rddShadowStackEnabled,
   1262        socketShadowStackEnabled,
   1263        gpuShadowStackEnabled,
   1264        gmpShadowStackEnabled,
   1265        utilityWmfCdmLpacEnabled,
   1266        rddACGEnabled,
   1267        utilityWmfACGEnabled,
   1268      }) =>
   1269        (macNoWindowServer.hasUserValue && !macNoWindowServer.locked) ||
   1270        (contentWriteWhitelist.hasUserValue && !contentWriteWhitelist.locked) ||
   1271        (contentReadWhitelist.hasUserValue && !contentReadWhitelist.locked) ||
   1272        (contentSyscallWhitelist.hasUserValue &&
   1273          !contentSyscallWhitelist.locked) ||
   1274        (contentSandboxLevel.hasUserValue && !contentSandboxLevel.locked) ||
   1275        (socketSandboxLevel.hasUserValue && !socketSandboxLevel.locked) ||
   1276        (gpuSandboxLevel.hasUserValue && !gpuSandboxLevel.locked) ||
   1277        (content32kDisable.hasUserValue && !content32kDisable.locked) ||
   1278        (gmp32kDisable.hasUserValue && !gmp32kDisable.locked) ||
   1279        (gmpACGEnable.hasUserValue && !gmpACGEnable.locked) ||
   1280        (socket32kDisable.hasUserValue && !socket32kDisable.locked) ||
   1281        (rddShadowStackEnabled.hasUserValue && !rddShadowStackEnabled.locked) ||
   1282        (socketShadowStackEnabled.hasUserValue &&
   1283          !socketShadowStackEnabled.locked) ||
   1284        (gpuShadowStackEnabled.hasUserValue && !gpuShadowStackEnabled.locked) ||
   1285        (gmpShadowStackEnabled.hasUserValue && !gmpShadowStackEnabled.locked) ||
   1286        (utilityWmfCdmLpacEnabled.hasUserValue &&
   1287          !utilityWmfCdmLpacEnabled.locked) ||
   1288        (rddACGEnabled.hasUserValue && !rddACGEnabled.locked) ||
   1289        (utilityWmfACGEnabled.hasUserValue && !utilityWmfACGEnabled.locked),
   1290 
   1291      true
   1292    )
   1293  );
   1294 
   1295  /** @type {SettingControlConfig[]} */
   1296  const SECURITY_WARNINGS = [
   1297    {
   1298      l10nId: "security-privacy-issue-warning-test",
   1299      id: "warningTest",
   1300    },
   1301    {
   1302      l10nId: "security-privacy-issue-warning-fingerprinters",
   1303      id: "warningAllowFingerprinters",
   1304    },
   1305    {
   1306      l10nId: "security-privacy-issue-warning-third-party-cookies",
   1307      id: "warningThirdPartyCookies",
   1308    },
   1309    {
   1310      l10nId: "security-privacy-issue-warning-password-manager",
   1311      id: "warningPasswordManager",
   1312    },
   1313    {
   1314      l10nId: "security-privacy-issue-warning-popup-blocker",
   1315      id: "warningPopupBlocker",
   1316    },
   1317    {
   1318      l10nId: "security-privacy-issue-warning-extension-install",
   1319      id: "warningExtensionInstall",
   1320    },
   1321    {
   1322      l10nId: "security-privacy-issue-warning-safe-browsing",
   1323      id: "warningSafeBrowsing",
   1324    },
   1325    {
   1326      l10nId: "security-privacy-issue-warning-doh",
   1327      id: "warningDoH",
   1328    },
   1329    {
   1330      l10nId: "security-privacy-issue-warning-ech",
   1331      id: "warningECH",
   1332    },
   1333    {
   1334      l10nId: "security-privacy-issue-warning-ct",
   1335      id: "warningCT",
   1336    },
   1337    {
   1338      l10nId: "security-privacy-issue-warning-crlite",
   1339      id: "warningCRLite",
   1340    },
   1341    {
   1342      l10nId: "security-privacy-issue-warning-certificate-pinning",
   1343      id: "warningCertificatePinning",
   1344    },
   1345    {
   1346      l10nId: "security-privacy-issue-warning-tlsmin",
   1347      id: "warningTLSMin",
   1348    },
   1349    {
   1350      l10nId: "security-privacy-issue-warning-tlsmax",
   1351      id: "warningTLSMax",
   1352    },
   1353    {
   1354      l10nId: "security-privacy-issue-warning-proxy-autodetection",
   1355      id: "warningProxyAutodetection",
   1356    },
   1357    {
   1358      l10nId: "security-privacy-issue-warning-content-resource-uri",
   1359      id: "warningContentResourceURI",
   1360    },
   1361    {
   1362      l10nId: "security-privacy-issue-warning-worker-mime",
   1363      id: "warningWorkerMIME",
   1364    },
   1365    {
   1366      l10nId: "security-privacy-issue-warning-top-level-data-uri",
   1367      id: "warningTopLevelDataURI",
   1368    },
   1369    {
   1370      l10nId: "security-privacy-issue-warning-active-mixed-content",
   1371      id: "warningActiveMixedContent",
   1372    },
   1373    {
   1374      l10nId: "security-privacy-issue-warning-inner-html-ltgt",
   1375      id: "warningInnerHTMLltgt",
   1376    },
   1377    {
   1378      l10nId: "security-privacy-issue-warning-file-uri-origin",
   1379      id: "warningFileURIOrigin",
   1380    },
   1381    {
   1382      l10nId: "security-privacy-issue-warning-privileged-constraint",
   1383      id: "warningPrivilegedConstraint",
   1384    },
   1385    {
   1386      l10nId: "security-privacy-issue-warning-process-sandbox",
   1387      id: "warningProcessSandbox",
   1388    },
   1389  ];
   1390 
   1391  Preferences.addSetting(
   1392    /** @type {{ makeSecurityWarningItems: () => SettingControlConfig[] } & SettingConfig} */ ({
   1393      id: "securityWarningsGroup",
   1394      makeSecurityWarningItems() {
   1395        return SECURITY_WARNINGS.map(({ id, l10nId }) => ({
   1396          id,
   1397          l10nId,
   1398          control: "moz-box-item",
   1399          options: [
   1400            {
   1401              control: "moz-button",
   1402              l10nId: "issue-card-reset-button",
   1403              controlAttrs: { slot: "actions", size: "small", id: "reset" },
   1404            },
   1405            {
   1406              control: "moz-button",
   1407              l10nId: "issue-card-dismiss-button",
   1408              controlAttrs: {
   1409                slot: "actions",
   1410                size: "small",
   1411                iconsrc: "chrome://global/skin/icons/close.svg",
   1412                id: "dismiss",
   1413              },
   1414            },
   1415          ],
   1416        }));
   1417      },
   1418      getControlConfig(config) {
   1419        if (!config.items) {
   1420          return { ...config, items: this.makeSecurityWarningItems() };
   1421        }
   1422        return config;
   1423      },
   1424    })
   1425  );
   1426 
   1427  Preferences.addSetting({
   1428    id: "privacyCard",
   1429    deps: [
   1430      "appUpdateStatus",
   1431      "trackerCount",
   1432      "etpStrictEnabled",
   1433      ...SECURITY_WARNINGS.map(warning => warning.id),
   1434    ],
   1435  });
   1436 
   1437  Preferences.addSetting({
   1438    id: "warningCard",
   1439    deps: SECURITY_WARNINGS.map(warning => warning.id),
   1440    visible: deps => {
   1441      const count = Object.values(deps).filter(
   1442        depSetting => depSetting.visible
   1443      ).length;
   1444      if (!this._telemetrySent) {
   1445        Glean.securityPreferencesWarnings.warningsShown.record({ count });
   1446        this._telemetrySent = true;
   1447      }
   1448      return count > 0;
   1449    },
   1450  });
   1451 }
   1452 
   1453 Preferences.addSetting({
   1454  id: "ipProtectionVisible",
   1455  pref: "browser.ipProtection.enabled",
   1456 });
   1457 Preferences.addSetting({
   1458  id: "ipProtectionSiteExceptionsFeatureEnabled",
   1459  pref: "browser.ipProtection.features.siteExceptions",
   1460 });
   1461 Preferences.addSetting({
   1462  id: "ipProtectionExceptions",
   1463  deps: ["ipProtectionVisible", "ipProtectionSiteExceptionsFeatureEnabled"],
   1464  visible: ({
   1465    ipProtectionVisible,
   1466    ipProtectionSiteExceptionsFeatureEnabled,
   1467  }) =>
   1468    ipProtectionVisible.value && ipProtectionSiteExceptionsFeatureEnabled.value,
   1469 });
   1470 
   1471 Preferences.addSetting({
   1472  id: "ipProtectionExceptionAllListButton",
   1473  deps: ["ipProtectionVisible", "ipProtectionSiteExceptionsFeatureEnabled"],
   1474  setup(emitChange) {
   1475    let permObserver = {
   1476      observe(subject, topic, _data) {
   1477        if (subject && topic === "perm-changed") {
   1478          let permission = subject.QueryInterface(Ci.nsIPermission);
   1479          if (permission.type === "ipp-vpn") {
   1480            emitChange();
   1481          }
   1482        }
   1483      },
   1484    };
   1485    Services.obs.addObserver(permObserver, "perm-changed");
   1486    return () => {
   1487      Services.obs.removeObserver(permObserver, "perm-changed");
   1488    };
   1489  },
   1490  visible: ({
   1491    ipProtectionVisible,
   1492    ipProtectionSiteExceptionsFeatureEnabled,
   1493  }) =>
   1494    ipProtectionVisible.value && ipProtectionSiteExceptionsFeatureEnabled.value,
   1495  onUserClick() {
   1496    let params = {
   1497      addVisible: true,
   1498      hideStatusColumn: true,
   1499      prefilledHost: "",
   1500      permissionType: "ipp-vpn",
   1501      capabilityFilter: Ci.nsIPermissionManager.DENY_ACTION,
   1502    };
   1503 
   1504    gSubDialog.open(
   1505      "chrome://browser/content/preferences/dialogs/permissions.xhtml",
   1506      { features: "resizable=yes" },
   1507      params
   1508    );
   1509  },
   1510  getControlConfig(config) {
   1511    let l10nId = "ip-protection-site-exceptions-all-sites-button";
   1512 
   1513    let savedExceptions = Services.perms.getAllByTypes(["ipp-vpn"]);
   1514    let numberOfExclusions = savedExceptions.filter(
   1515      perm => perm.capability === Ci.nsIPermissionManager.DENY_ACTION
   1516    ).length;
   1517 
   1518    let l10nArgs = {
   1519      count: numberOfExclusions,
   1520    };
   1521 
   1522    return {
   1523      ...config,
   1524      l10nId,
   1525      l10nArgs,
   1526    };
   1527  },
   1528 });
   1529 Preferences.addSetting({
   1530  id: "ipProtectionAutoStartFeatureEnabled",
   1531  pref: "browser.ipProtection.features.autoStart",
   1532  get: prefVal => prefVal,
   1533 });
   1534 Preferences.addSetting({
   1535  id: "ipProtectionAutoStart",
   1536  deps: ["ipProtectionVisible", "ipProtectionAutoStartFeatureEnabled"],
   1537  visible: ({ ipProtectionVisible, ipProtectionAutoStartFeatureEnabled }) =>
   1538    ipProtectionVisible.value && ipProtectionAutoStartFeatureEnabled.value,
   1539 });
   1540 Preferences.addSetting({
   1541  id: "ipProtectionAutoStartCheckbox",
   1542  pref: "browser.ipProtection.autoStartEnabled",
   1543  deps: ["ipProtectionVisible", "ipProtectionAutoStart"],
   1544  visible: ({ ipProtectionVisible }) => ipProtectionVisible.value,
   1545 });
   1546 Preferences.addSetting({
   1547  id: "ipProtectionAutoStartPrivateCheckbox",
   1548  pref: "browser.ipProtection.autoStartPrivateEnabled",
   1549  deps: ["ipProtectionVisible", "ipProtectionAutoStart"],
   1550  visible: ({ ipProtectionVisible }) => ipProtectionVisible.value,
   1551 });
   1552 Preferences.addSetting({
   1553  id: "ipProtectionAdditionalLinks",
   1554  deps: ["ipProtectionVisible"],
   1555  visible: ({ ipProtectionVisible }) => ipProtectionVisible.value,
   1556 });
   1557 
   1558 // Study opt out
   1559 if (AppConstants.MOZ_DATA_REPORTING) {
   1560  Preferences.addAll([
   1561    // Preference instances for prefs that we need to monitor while the page is open.
   1562    { id: PREF_OPT_OUT_STUDIES_ENABLED, type: "bool" },
   1563    { id: PREF_ADDON_RECOMMENDATIONS_ENABLED, type: "bool" },
   1564    { id: PREF_UPLOAD_ENABLED, type: "bool" },
   1565    { id: "datareporting.usage.uploadEnabled", type: "bool" },
   1566    { id: "dom.private-attribution.submission.enabled", type: "bool" },
   1567  ]);
   1568 }
   1569 // Privacy segmentation section
   1570 Preferences.add({
   1571  id: "browser.dataFeatureRecommendations.enabled",
   1572  type: "bool",
   1573 });
   1574 
   1575 // Data Choices tab
   1576 if (AppConstants.MOZ_CRASHREPORTER) {
   1577  Preferences.add({
   1578    id: "browser.crashReports.unsubmittedCheck.autoSubmit2",
   1579    type: "bool",
   1580  });
   1581 }
   1582 
   1583 Preferences.addSetting({
   1584  id: "gpcFunctionalityEnabled",
   1585  pref: "privacy.globalprivacycontrol.functionality.enabled",
   1586 });
   1587 Preferences.addSetting({
   1588  id: "gpcEnabled",
   1589  pref: "privacy.globalprivacycontrol.enabled",
   1590  deps: ["gpcFunctionalityEnabled"],
   1591  visible: ({ gpcFunctionalityEnabled }) => {
   1592    return gpcFunctionalityEnabled.value;
   1593  },
   1594 });
   1595 Preferences.addSetting({
   1596  id: "relayFeature",
   1597  pref: "signon.firefoxRelay.feature",
   1598 });
   1599 Preferences.addSetting({
   1600  id: "relayIntegration",
   1601  deps: ["savePasswords", "relayFeature"],
   1602  visible: () => {
   1603    return FirefoxRelay.isAvailable;
   1604  },
   1605  disabled: ({ savePasswords, relayFeature }) => {
   1606    return !savePasswords.value || relayFeature.pref.locked;
   1607  },
   1608  get() {
   1609    return FirefoxRelay.isAvailable && !FirefoxRelay.isDisabled;
   1610  },
   1611  set(checked) {
   1612    if (checked) {
   1613      FirefoxRelay.markAsAvailable();
   1614    } else {
   1615      FirefoxRelay.markAsDisabled();
   1616    }
   1617  },
   1618  onUserChange(checked) {
   1619    if (checked) {
   1620      Glean.relayIntegration.enabledPrefChange.record();
   1621    } else {
   1622      Glean.relayIntegration.disabledPrefChange.record();
   1623    }
   1624  },
   1625 });
   1626 Preferences.addSetting({
   1627  id: "dntHeaderEnabled",
   1628  pref: "privacy.donottrackheader.enabled",
   1629 });
   1630 Preferences.addSetting({
   1631  id: "dntRemoval",
   1632  pref: "browser.preferences.config_warning.donottrackheader.dismissed",
   1633  deps: ["dntHeaderEnabled"],
   1634  visible: ({ dntHeaderEnabled }, setting) => {
   1635    return dntHeaderEnabled.value && !setting.value;
   1636  },
   1637  onUserClick: (event, _deps, setting) => {
   1638    let dismissButton = event.target?.shadowRoot?.querySelector(".close");
   1639    if (
   1640      dismissButton?.shadowRoot &&
   1641      event.originalTarget &&
   1642      dismissButton.shadowRoot.contains(event.originalTarget)
   1643    ) {
   1644      setting.value = true;
   1645    }
   1646  },
   1647 });
   1648 
   1649 Preferences.addSetting({
   1650  id: "httpsOnlyEnabled",
   1651  pref: "dom.security.https_only_mode",
   1652 });
   1653 Preferences.addSetting({
   1654  id: "httpsOnlyEnabledPBM",
   1655  pref: "dom.security.https_only_mode_pbm",
   1656 });
   1657 Preferences.addSetting({
   1658  id: "httpsOnlyRadioGroup",
   1659  deps: ["httpsOnlyEnabled", "httpsOnlyEnabledPBM"],
   1660  get: (_value, deps) => {
   1661    if (deps.httpsOnlyEnabled.value) {
   1662      return "enabled";
   1663    }
   1664    if (deps.httpsOnlyEnabledPBM.value) {
   1665      return "privateOnly";
   1666    }
   1667    return "disabled";
   1668  },
   1669  set: (value, deps) => {
   1670    if (value == "enabled") {
   1671      deps.httpsOnlyEnabled.value = true;
   1672      deps.httpsOnlyEnabledPBM.value = false;
   1673    } else if (value == "privateOnly") {
   1674      deps.httpsOnlyEnabled.value = false;
   1675      deps.httpsOnlyEnabledPBM.value = true;
   1676    } else if (value == "disabled") {
   1677      deps.httpsOnlyEnabled.value = false;
   1678      deps.httpsOnlyEnabledPBM.value = false;
   1679    }
   1680  },
   1681  disabled: deps => {
   1682    return deps.httpsOnlyEnabled.locked || deps.httpsOnlyEnabledPBM.locked;
   1683  },
   1684 });
   1685 Preferences.addSetting({
   1686  id: "httpsFirstEnabled",
   1687  pref: "dom.security.https_first",
   1688 });
   1689 Preferences.addSetting({
   1690  id: "httpsFirstEnabledPBM",
   1691  pref: "dom.security.https_first_pbm",
   1692 });
   1693 Preferences.addSetting({
   1694  id: "httpsOnlyExceptionButton",
   1695  deps: [
   1696    "httpsOnlyEnabled",
   1697    "httpsOnlyEnabledPBM",
   1698    "httpsFirstEnabled",
   1699    "httpsFirstEnabledPBM",
   1700  ],
   1701  disabled: deps => {
   1702    return (
   1703      !deps.httpsOnlyEnabled.value &&
   1704      !deps.httpsOnlyEnabledPBM.value &&
   1705      !deps.httpsFirstEnabled.value &&
   1706      !deps.httpsFirstEnabledPBM.value
   1707    );
   1708  },
   1709  onUserClick: () => {
   1710    gPrivacyPane.showHttpsOnlyModeExceptions();
   1711  },
   1712 });
   1713 
   1714 Preferences.addSetting({
   1715  id: "enableSafeBrowsingPhishing",
   1716  pref: "browser.safebrowsing.phishing.enabled",
   1717 });
   1718 Preferences.addSetting({
   1719  id: "enableSafeBrowsingMalware",
   1720  pref: "browser.safebrowsing.malware.enabled",
   1721 });
   1722 Preferences.addSetting({
   1723  id: "enableSafeBrowsing",
   1724  deps: ["enableSafeBrowsingPhishing", "enableSafeBrowsingMalware"],
   1725  get: (_value, deps) => {
   1726    return (
   1727      deps.enableSafeBrowsingPhishing.value &&
   1728      deps.enableSafeBrowsingMalware.value
   1729    );
   1730  },
   1731  set: (value, deps) => {
   1732    deps.enableSafeBrowsingPhishing.value = value;
   1733    deps.enableSafeBrowsingMalware.value = value;
   1734  },
   1735  disabled: deps => {
   1736    return (
   1737      deps.enableSafeBrowsingPhishing.locked ||
   1738      deps.enableSafeBrowsingMalware.locked
   1739    );
   1740  },
   1741 });
   1742 Preferences.addSetting({
   1743  id: "blockDownloads",
   1744  pref: "browser.safebrowsing.downloads.enabled",
   1745  deps: ["enableSafeBrowsing"],
   1746  disabled: (deps, self) => {
   1747    return !deps.enableSafeBrowsing.value || self.locked;
   1748  },
   1749 });
   1750 Preferences.addSetting({
   1751  id: "malwareTable",
   1752  pref: "urlclassifier.malwareTable",
   1753 });
   1754 Preferences.addSetting({
   1755  id: "blockUncommonDownloads",
   1756  pref: "browser.safebrowsing.downloads.remote.block_uncommon",
   1757 });
   1758 Preferences.addSetting({
   1759  id: "blockUnwantedDownloads",
   1760  pref: "browser.safebrowsing.downloads.remote.block_potentially_unwanted",
   1761 });
   1762 Preferences.addSetting({
   1763  id: "blockUncommonUnwanted",
   1764  deps: [
   1765    "enableSafeBrowsing",
   1766    "blockDownloads",
   1767    "blockUncommonDownloads",
   1768    "blockUnwantedDownloads",
   1769  ],
   1770  get: (_value, deps) => {
   1771    return (
   1772      deps.blockUncommonDownloads.value && deps.blockUnwantedDownloads.value
   1773    );
   1774  },
   1775  set: (value, deps) => {
   1776    deps.blockUncommonDownloads.value = value;
   1777    deps.blockUnwantedDownloads.value = value;
   1778 
   1779    let malwareTable = Preferences.get("urlclassifier.malwareTable");
   1780    let malware = /** @type {string} */ (malwareTable.value)
   1781      .split(",")
   1782      .filter(
   1783        x =>
   1784          x !== "goog-unwanted-proto" &&
   1785          x !== "goog-unwanted-shavar" &&
   1786          x !== "moztest-unwanted-simple"
   1787      );
   1788 
   1789    if (value) {
   1790      if (malware.includes("goog-malware-shavar")) {
   1791        malware.push("goog-unwanted-shavar");
   1792      } else {
   1793        malware.push("goog-unwanted-proto");
   1794      }
   1795      malware.push("moztest-unwanted-simple");
   1796    }
   1797 
   1798    // sort alphabetically to keep the pref consistent
   1799    malware.sort();
   1800    malwareTable.value = malware.join(",");
   1801 
   1802    // Force an update after changing the malware table.
   1803    listManager.forceUpdates(malwareTable.value);
   1804  },
   1805  disabled: deps => {
   1806    return (
   1807      !deps.enableSafeBrowsing.value ||
   1808      !deps.blockDownloads.value ||
   1809      deps.blockUncommonDownloads.locked ||
   1810      deps.blockUnwantedDownloads.locked
   1811    );
   1812  },
   1813 });
   1814 Preferences.addSetting({
   1815  id: "manageDataSettingsGroup",
   1816 });
   1817 Preferences.addSetting(
   1818  /** @type {{ isUpdatingSites: boolean, usage: { value: number, unit: string } | void } & SettingConfig} */ ({
   1819    id: "siteDataSize",
   1820    usage: null,
   1821    isUpdatingSites: false,
   1822    setup(emitChange) {
   1823      let onUsageChanged = async () => {
   1824        let [siteDataUsage, cacheUsage] = await Promise.all([
   1825          SiteDataManager.getTotalUsage(),
   1826          SiteDataManager.getCacheSize(),
   1827        ]);
   1828        let totalUsage = siteDataUsage + cacheUsage;
   1829        let [value, unit] = DownloadUtils.convertByteUnits(totalUsage);
   1830        this.usage = { value, unit };
   1831 
   1832        this.isUpdatingSites = false;
   1833        emitChange();
   1834      };
   1835 
   1836      let onUpdatingSites = () => {
   1837        this.isUpdatingSites = true;
   1838        emitChange();
   1839      };
   1840 
   1841      Services.obs.addObserver(onUsageChanged, "sitedatamanager:sites-updated");
   1842      Services.obs.addObserver(
   1843        onUpdatingSites,
   1844        "sitedatamanager:updating-sites"
   1845      );
   1846 
   1847      return () => {
   1848        Services.obs.removeObserver(
   1849          onUsageChanged,
   1850          "sitedatamanager:sites-updated"
   1851        );
   1852        Services.obs.removeObserver(
   1853          onUpdatingSites,
   1854          "sitedatamanager:updating-sites"
   1855        );
   1856      };
   1857    },
   1858    getControlConfig(config) {
   1859      if (this.isUpdatingSites || !this.usage) {
   1860        // Data not retrieved yet, show a loading state.
   1861        return {
   1862          ...config,
   1863          l10nId: "sitedata-total-size-calculating",
   1864        };
   1865      }
   1866 
   1867      let { value, unit } = this.usage;
   1868      return {
   1869        ...config,
   1870        l10nId: "sitedata-total-size2",
   1871        l10nArgs: {
   1872          value,
   1873          unit,
   1874        },
   1875      };
   1876    },
   1877  })
   1878 );
   1879 
   1880 Preferences.addSetting({
   1881  id: "deleteOnCloseInfo",
   1882  deps: ["privateBrowsingAutoStart"],
   1883  visible({ privateBrowsingAutoStart }) {
   1884    return privateBrowsingAutoStart.value;
   1885  },
   1886 });
   1887 
   1888 Preferences.addSetting(
   1889  /** @type {{ isUpdatingSites: boolean } & SettingConfig} */ ({
   1890    id: "clearSiteDataButton",
   1891    isUpdatingSites: false,
   1892    setup(emitChange) {
   1893      let onSitesUpdated = async () => {
   1894        this.isUpdatingSites = false;
   1895        emitChange();
   1896      };
   1897 
   1898      let onUpdatingSites = () => {
   1899        this.isUpdatingSites = true;
   1900        emitChange();
   1901      };
   1902 
   1903      Services.obs.addObserver(onSitesUpdated, "sitedatamanager:sites-updated");
   1904      Services.obs.addObserver(
   1905        onUpdatingSites,
   1906        "sitedatamanager:updating-sites"
   1907      );
   1908 
   1909      return () => {
   1910        Services.obs.removeObserver(
   1911          onSitesUpdated,
   1912          "sitedatamanager:sites-updated"
   1913        );
   1914        Services.obs.removeObserver(
   1915          onUpdatingSites,
   1916          "sitedatamanager:updating-sites"
   1917        );
   1918      };
   1919    },
   1920    onUserClick() {
   1921      let uri;
   1922      if (useOldClearHistoryDialog) {
   1923        uri =
   1924          "chrome://browser/content/preferences/dialogs/clearSiteData.xhtml";
   1925      } else {
   1926        uri = "chrome://browser/content/sanitize_v2.xhtml";
   1927      }
   1928 
   1929      gSubDialog.open(
   1930        uri,
   1931        {
   1932          features: "resizable=no",
   1933        },
   1934        {
   1935          mode: "clearSiteData",
   1936        }
   1937      );
   1938    },
   1939    disabled() {
   1940      return this.isUpdatingSites;
   1941    },
   1942  })
   1943 );
   1944 Preferences.addSetting(
   1945  /** @type {{ isUpdatingSites: boolean } & SettingConfig} */ ({
   1946    id: "siteDataSettings",
   1947    isUpdatingSites: false,
   1948    setup(emitChange) {
   1949      let onSitesUpdated = async () => {
   1950        this.isUpdatingSites = false;
   1951        emitChange();
   1952      };
   1953 
   1954      let onUpdatingSites = () => {
   1955        this.isUpdatingSites = true;
   1956        emitChange();
   1957      };
   1958 
   1959      Services.obs.addObserver(onSitesUpdated, "sitedatamanager:sites-updated");
   1960      Services.obs.addObserver(
   1961        onUpdatingSites,
   1962        "sitedatamanager:updating-sites"
   1963      );
   1964 
   1965      return () => {
   1966        Services.obs.removeObserver(
   1967          onSitesUpdated,
   1968          "sitedatamanager:sites-updated"
   1969        );
   1970        Services.obs.removeObserver(
   1971          onUpdatingSites,
   1972          "sitedatamanager:updating-sites"
   1973        );
   1974      };
   1975    },
   1976    onUserClick() {
   1977      gSubDialog.open(
   1978        "chrome://browser/content/preferences/dialogs/siteDataSettings.xhtml"
   1979      );
   1980    },
   1981    disabled() {
   1982      return this.isUpdatingSites;
   1983    },
   1984  })
   1985 );
   1986 Preferences.addSetting({
   1987  id: "cookieExceptions",
   1988  onUserClick() {
   1989    gSubDialog.open(
   1990      "chrome://browser/content/preferences/dialogs/permissions.xhtml",
   1991      {},
   1992      {
   1993        blockVisible: true,
   1994        sessionVisible: true,
   1995        allowVisible: true,
   1996        prefilledHost: "",
   1997        permissionType: "cookie",
   1998      }
   1999    );
   2000  },
   2001 });
   2002 
   2003 function isCookiesAndStorageClearingOnShutdown() {
   2004  // We have to branch between the old clear on shutdown prefs and new prefs after the clear history revamp (Bug 1853996)
   2005  // Once the old dialog is deprecated, we can remove these branches.
   2006  if (useOldClearHistoryDialog) {
   2007    return (
   2008      Preferences.get("privacy.sanitize.sanitizeOnShutdown").value &&
   2009      Preferences.get("privacy.clearOnShutdown.cookies").value &&
   2010      Preferences.get("privacy.clearOnShutdown.cache").value &&
   2011      Preferences.get("privacy.clearOnShutdown.offlineApps").value
   2012    );
   2013  }
   2014  return (
   2015    Preferences.get("privacy.sanitize.sanitizeOnShutdown").value &&
   2016    Preferences.get("privacy.clearOnShutdown_v2.cookiesAndStorage").value &&
   2017    Preferences.get("privacy.clearOnShutdown_v2.cache").value
   2018  );
   2019 }
   2020 
   2021 /*
   2022 * Unsets cleaning prefs that do not belong to DeleteOnClose
   2023 */
   2024 function resetCleaningPrefs() {
   2025  let sanitizeOnShutdownPrefsArray = useOldClearHistoryDialog
   2026    ? SANITIZE_ON_SHUTDOWN_PREFS_ONLY
   2027    : SANITIZE_ON_SHUTDOWN_PREFS_ONLY_V2;
   2028 
   2029  return sanitizeOnShutdownPrefsArray.forEach(
   2030    pref => (Preferences.get(pref).value = false)
   2031  );
   2032 }
   2033 
   2034 Preferences.addSetting({
   2035  id: "clearOnCloseCookies",
   2036  pref: useOldClearHistoryDialog
   2037    ? "privacy.clearOnShutdown.cookies"
   2038    : "privacy.clearOnShutdown_v2.cookiesAndStorage",
   2039 });
   2040 Preferences.addSetting({
   2041  id: "clearOnCloseCache",
   2042  pref: useOldClearHistoryDialog
   2043    ? "privacy.clearOnShutdown.cache"
   2044    : "privacy.clearOnShutdown_v2.cache",
   2045 });
   2046 Preferences.addSetting({
   2047  id: "clearOnCloseStorage",
   2048  pref: useOldClearHistoryDialog
   2049    ? "privacy.clearOnShutdown.offlineApps"
   2050    : "privacy.clearOnShutdown_v2.cookiesAndStorage",
   2051 });
   2052 Preferences.addSetting({
   2053  id: "sanitizeOnShutdown",
   2054  pref: "privacy.sanitize.sanitizeOnShutdown",
   2055 });
   2056 Preferences.addSetting({
   2057  id: "historyModeCustom",
   2058  pref: "privacy.history.custom",
   2059 });
   2060 Preferences.addSetting({
   2061  id: "cookieBehavior",
   2062  pref: "network.cookie.cookieBehavior",
   2063 });
   2064 Preferences.addSetting({
   2065  id: "deleteOnClose",
   2066  deps: [
   2067    "clearOnCloseCookies",
   2068    "clearOnCloseCache",
   2069    "clearOnCloseStorage",
   2070    "sanitizeOnShutdown",
   2071    "privateBrowsingAutoStart",
   2072    "cookieBehavior",
   2073    "alwaysClear",
   2074  ],
   2075  setup() {
   2076    // Make sure to do the migration for the clear history dialog before implementing logic for delete on close
   2077    // This needs to be done to make sure the migration is done before any pref changes are made to avoid unintentionally
   2078    // overwriting prefs
   2079    Sanitizer.maybeMigratePrefs("clearOnShutdown");
   2080  },
   2081  disabled({ privateBrowsingAutoStart, cookieBehavior }) {
   2082    return (
   2083      privateBrowsingAutoStart.value ||
   2084      cookieBehavior.value == Ci.nsICookieService.BEHAVIOR_REJECT
   2085    );
   2086  },
   2087  get(_, { privateBrowsingAutoStart }) {
   2088    return (
   2089      isCookiesAndStorageClearingOnShutdown() || privateBrowsingAutoStart.value
   2090    );
   2091  },
   2092  set(
   2093    value,
   2094    {
   2095      clearOnCloseCookies,
   2096      clearOnCloseCache,
   2097      clearOnCloseStorage,
   2098      sanitizeOnShutdown,
   2099    }
   2100  ) {
   2101    clearOnCloseCookies.value = value;
   2102    clearOnCloseCache.value = value;
   2103    clearOnCloseStorage.value = value;
   2104 
   2105    // Sync the cleaning prefs with the deleteOnClose box.
   2106 
   2107    // Forget the current pref selection if sanitizeOnShutdown is disabled,
   2108    // to not over clear when it gets enabled by the sync mechanism
   2109    if (!sanitizeOnShutdown.value) {
   2110      resetCleaningPrefs();
   2111    }
   2112    // If no other cleaning category is selected, sanitizeOnShutdown gets synced with deleteOnClose
   2113    sanitizeOnShutdown.value =
   2114      gPrivacyPane._isCustomCleaningPrefPresent() || value;
   2115  },
   2116 });
   2117 
   2118 Preferences.addSetting({
   2119  id: "historyModeCustom",
   2120  pref: "privacy.history.custom",
   2121 });
   2122 Preferences.addSetting({
   2123  id: "historyEnabled",
   2124  pref: "places.history.enabled",
   2125 });
   2126 Preferences.addSetting({
   2127  id: "formFillEnabled",
   2128  pref: "browser.formfill.enable",
   2129 });
   2130 
   2131 // Store this on the window so tests can suppress the prompt.
   2132 window._shouldPromptForRestartPBM = true;
   2133 async function onChangePrivateBrowsingAutoStart(value, revertFn) {
   2134  if (!window._shouldPromptForRestartPBM) {
   2135    return false;
   2136  }
   2137 
   2138  // The PBM autostart pref has changed so we need to prompt for restart.
   2139  let buttonIndex = await confirmRestartPrompt(value, 1, true, false);
   2140 
   2141  // User accepts, restart the browser.
   2142  if (buttonIndex == CONFIRM_RESTART_PROMPT_RESTART_NOW) {
   2143    Services.startup.quit(
   2144      Ci.nsIAppStartup.eAttemptQuit | Ci.nsIAppStartup.eRestart
   2145    );
   2146    return false;
   2147  }
   2148 
   2149  // Don't prompt for the revert operation itself.
   2150  window._shouldPromptForRestartPBM = false;
   2151  revertFn();
   2152  window._shouldPromptForRestartPBM = true;
   2153 
   2154  // User cancels, do nothing. The caller will clean up the pref change.
   2155  return true;
   2156 }
   2157 
   2158 Preferences.addSetting({
   2159  id: "historyMode",
   2160  deps: [
   2161    "historyModeCustom",
   2162    "privateBrowsingAutoStart",
   2163    "historyEnabled",
   2164    "formFillEnabled",
   2165    "sanitizeOnShutdown",
   2166  ],
   2167  get(
   2168    _,
   2169    {
   2170      historyModeCustom,
   2171      privateBrowsingAutoStart,
   2172      historyEnabled,
   2173      formFillEnabled,
   2174      sanitizeOnShutdown,
   2175    }
   2176  ) {
   2177    if (historyModeCustom.value) {
   2178      return "custom";
   2179    }
   2180 
   2181    if (privateBrowsingAutoStart.value) {
   2182      return "dontremember";
   2183    }
   2184 
   2185    if (
   2186      historyEnabled.value &&
   2187      formFillEnabled.value &&
   2188      !sanitizeOnShutdown.value
   2189    ) {
   2190      return "remember";
   2191    }
   2192 
   2193    return "custom";
   2194  },
   2195  set(
   2196    value,
   2197    {
   2198      historyModeCustom,
   2199      privateBrowsingAutoStart,
   2200      historyEnabled,
   2201      formFillEnabled,
   2202      sanitizeOnShutdown,
   2203    }
   2204  ) {
   2205    let lastHistoryModeCustom = historyModeCustom.value;
   2206    let lastHistoryEnabled = historyEnabled.value;
   2207    let lastFormFillEnabled = formFillEnabled.value;
   2208    let lastSanitizeOnShutdown = sanitizeOnShutdown.value;
   2209    let lastPrivateBrowsingAutoStart = privateBrowsingAutoStart.value;
   2210 
   2211    historyModeCustom.value = value == "custom";
   2212 
   2213    if (value == "remember") {
   2214      historyEnabled.value = true;
   2215      formFillEnabled.value = true;
   2216      sanitizeOnShutdown.value = false;
   2217      privateBrowsingAutoStart.value = false;
   2218    } else if (value == "dontremember") {
   2219      privateBrowsingAutoStart.value = true;
   2220    }
   2221 
   2222    if (privateBrowsingAutoStart.value !== lastPrivateBrowsingAutoStart) {
   2223      // The PBM autostart pref has changed so we need to prompt for restart.
   2224      onChangePrivateBrowsingAutoStart(privateBrowsingAutoStart.value, () => {
   2225        // User cancelled the action, revert the change.
   2226        // Simply reverting the setting value itself is not enough, because a
   2227        // state transition to "custom" does not override any of the sub-prefs.
   2228        // We need to update them all manually.
   2229        historyModeCustom.value = lastHistoryModeCustom;
   2230        historyEnabled.value = lastHistoryEnabled;
   2231        formFillEnabled.value = lastFormFillEnabled;
   2232        sanitizeOnShutdown.value = lastSanitizeOnShutdown;
   2233        privateBrowsingAutoStart.value = lastPrivateBrowsingAutoStart;
   2234      });
   2235    }
   2236  },
   2237  disabled({ privateBrowsingAutoStart }) {
   2238    // Disable history dropdown if PBM autostart is locked on.
   2239    return privateBrowsingAutoStart.locked && privateBrowsingAutoStart.value;
   2240  },
   2241  getControlConfig(config, { privateBrowsingAutoStart }, setting) {
   2242    let l10nId = undefined;
   2243    if (!srdSectionEnabled("history2")) {
   2244      if (setting.value == "remember") {
   2245        l10nId = "history-remember-description3";
   2246      } else if (setting.value == "dontremember") {
   2247        l10nId = "history-dontremember-description3";
   2248      } else if (setting.value == "custom") {
   2249        l10nId = "history-custom-description3";
   2250      }
   2251    }
   2252 
   2253    let dontRememberOption = config.options.find(
   2254      opt => opt.value == "dontremember"
   2255    );
   2256 
   2257    // If PBM is unavailable hide the "Never remember history" option.
   2258    dontRememberOption.hidden = !PrivateBrowsingUtils.enabled;
   2259 
   2260    // If the PBM autostart pref is locked disable the "Never remember history"
   2261    // option.
   2262    dontRememberOption.disabled =
   2263      privateBrowsingAutoStart.locked && !privateBrowsingAutoStart.value;
   2264 
   2265    return {
   2266      ...config,
   2267      l10nId,
   2268    };
   2269  },
   2270 });
   2271 
   2272 Preferences.addSetting({
   2273  id: "customHistoryButton",
   2274  onUserClick(e) {
   2275    e.preventDefault();
   2276    gotoPref("paneHistory");
   2277  },
   2278 });
   2279 
   2280 Preferences.addSetting({
   2281  id: "privateBrowsingAutoStart",
   2282  pref: "browser.privatebrowsing.autostart",
   2283  deps: ["historyMode"],
   2284  onUserChange(value, _, setting) {
   2285    onChangePrivateBrowsingAutoStart(value, () => {
   2286      // User cancelled the action, revert the setting.
   2287      setting.value = !value;
   2288    });
   2289  },
   2290  visible({ historyMode }) {
   2291    return PrivateBrowsingUtils.enabled && historyMode.value == "custom";
   2292  },
   2293 });
   2294 Preferences.addSetting({
   2295  id: "rememberHistory",
   2296  pref: "places.history.enabled",
   2297  deps: ["historyMode", "privateBrowsingAutoStart"],
   2298  visible({ historyMode }) {
   2299    return historyMode.value == "custom";
   2300  },
   2301  disabled({ privateBrowsingAutoStart }) {
   2302    return privateBrowsingAutoStart.value;
   2303  },
   2304 });
   2305 Preferences.addSetting({
   2306  id: "rememberForms",
   2307  pref: "browser.formfill.enable",
   2308  deps: ["historyMode", "privateBrowsingAutoStart"],
   2309  visible({ historyMode }) {
   2310    return historyMode.value == "custom";
   2311  },
   2312  disabled({ privateBrowsingAutoStart }) {
   2313    return privateBrowsingAutoStart.value;
   2314  },
   2315 });
   2316 Preferences.addSetting({
   2317  id: "alwaysClear",
   2318  pref: "privacy.sanitize.sanitizeOnShutdown",
   2319  deps: ["historyMode", "privateBrowsingAutoStart"],
   2320  visible({ historyMode }) {
   2321    return historyMode.value == "custom";
   2322  },
   2323  disabled({ privateBrowsingAutoStart }) {
   2324    return privateBrowsingAutoStart.value;
   2325  },
   2326 });
   2327 
   2328 Preferences.addSetting({
   2329  id: "clearDataSettings",
   2330  deps: ["historyMode", "alwaysClear"],
   2331  visible({ historyMode }) {
   2332    return historyMode.value == "custom";
   2333  },
   2334  disabled({ alwaysClear }) {
   2335    return !alwaysClear.value || alwaysClear.disabled;
   2336  },
   2337  onUserClick() {
   2338    let dialogFile = useOldClearHistoryDialog
   2339      ? "chrome://browser/content/preferences/dialogs/sanitize.xhtml"
   2340      : "chrome://browser/content/sanitize_v2.xhtml";
   2341 
   2342    gSubDialog.open(
   2343      dialogFile,
   2344      {
   2345        features: "resizable=no",
   2346      },
   2347      {
   2348        mode: "clearOnShutdown",
   2349      }
   2350    );
   2351  },
   2352 });
   2353 
   2354 Preferences.addSetting({
   2355  id: "clearHistoryButton",
   2356  deps: ["historyMode"],
   2357  onUserClick(_, { historyMode }) {
   2358    gPrivacyPane.clearPrivateDataNow(historyMode.value == "dontremember");
   2359  },
   2360 });
   2361 
   2362 Preferences.addSetting({
   2363  id: "certificateButtonGroup",
   2364 });
   2365 Preferences.addSetting({
   2366  id: "disableOpenCertManager",
   2367  pref: "security.disable_button.openCertManager",
   2368 });
   2369 Preferences.addSetting({
   2370  id: "disableOpenDeviceManager",
   2371  pref: "security.disable_button.openDeviceManager",
   2372 });
   2373 Preferences.addSetting({
   2374  id: "viewCertificatesButton",
   2375  deps: ["disableOpenCertManager"],
   2376  disabled: deps => {
   2377    return deps.disableOpenCertManager.value;
   2378  },
   2379  onUserClick: () => {
   2380    gPrivacyPane.showCertificates();
   2381  },
   2382 });
   2383 Preferences.addSetting({
   2384  id: "viewSecurityDevicesButton",
   2385  deps: ["disableOpenDeviceManager"],
   2386  disabled: deps => {
   2387    return deps.disableOpenDeviceManager.value;
   2388  },
   2389  onUserClick: () => {
   2390    gPrivacyPane.showSecurityDevices();
   2391  },
   2392 });
   2393 Preferences.addSetting({
   2394  id: "certEnableThirdPartyToggle",
   2395  pref: "security.enterprise_roots.enabled",
   2396  visible: () => {
   2397    // Third-party certificate import is only implemented for Windows and Mac,
   2398    // and we should not expose this as a user-configurable setting if there's
   2399    // an enterprise policy controlling it (either to enable _or_ disable it).
   2400    return (
   2401      (AppConstants.platform == "win" || AppConstants.platform == "macosx") &&
   2402      typeof Services.policies.getActivePolicies()?.Certificates
   2403        ?.ImportEnterpriseRoots == "undefined" &&
   2404      !AppConstants.BASE_BROWSER_VERSION
   2405    );
   2406  },
   2407 });
   2408 
   2409 Preferences.addSetting({
   2410  id: "permissionBox",
   2411 });
   2412 Preferences.addSetting({
   2413  id: "popupPolicy",
   2414  pref: "dom.disable_open_during_load",
   2415 });
   2416 Preferences.addSetting({
   2417  id: "popupPolicyButton",
   2418  deps: ["popupPolicy"],
   2419  onUserClick: () => gPrivacyPane.showPopupExceptions(),
   2420  disabled: ({ popupPolicy }) => {
   2421    return !popupPolicy.value || popupPolicy.locked;
   2422  },
   2423 });
   2424 Preferences.addSetting({
   2425  id: "warnAddonInstall",
   2426  pref: "xpinstall.whitelist.required",
   2427 });
   2428 Preferences.addSetting({
   2429  id: "addonExceptions",
   2430  deps: ["warnAddonInstall"],
   2431  onUserClick: () => gPrivacyPane.showAddonExceptions(),
   2432  disabled: ({ warnAddonInstall }) => {
   2433    return !warnAddonInstall.value || warnAddonInstall.locked;
   2434  },
   2435 });
   2436 Preferences.addSetting({
   2437  id: "notificationsDoNotDisturb",
   2438  get: () => {
   2439    return AlertsServiceDND?.manualDoNotDisturb ?? false;
   2440  },
   2441  set: value => {
   2442    if (AlertsServiceDND) {
   2443      AlertsServiceDND.manualDoNotDisturb = value;
   2444    }
   2445  },
   2446  visible: () => {
   2447    return AlertsServiceDND != undefined;
   2448  },
   2449 });
   2450 Preferences.addSetting({
   2451  id: "locationSettingsButton",
   2452  onUserClick: () => gPrivacyPane.showLocationExceptions(),
   2453 });
   2454 Preferences.addSetting({
   2455  id: "cameraSettingsButton",
   2456  onUserClick: () => gPrivacyPane.showCameraExceptions(),
   2457 });
   2458 Preferences.addSetting({
   2459  id: "enabledLNA",
   2460  pref: "network.lna.blocking",
   2461 });
   2462 Preferences.addSetting({
   2463  id: "localNetworkSettingsButton",
   2464  onUserClick: () => gPrivacyPane.showLocalNetworkExceptions(),
   2465  deps: ["enabledLNA"],
   2466  visible: deps => {
   2467    return deps.enabledLNA.value;
   2468  },
   2469 });
   2470 Preferences.addSetting({
   2471  id: "localHostSettingsButton",
   2472  onUserClick: () => gPrivacyPane.showLocalHostExceptions(),
   2473  deps: ["enabledLNA"],
   2474  visible: deps => {
   2475    return deps.enabledLNA.value;
   2476  },
   2477 });
   2478 Preferences.addSetting({
   2479  id: "microphoneSettingsButton",
   2480  onUserClick: () => gPrivacyPane.showMicrophoneExceptions(),
   2481 });
   2482 Preferences.addSetting({
   2483  id: "enabledSpeakerControl",
   2484  pref: "media.setsinkid.enabled",
   2485 });
   2486 Preferences.addSetting({
   2487  id: "speakerSettingsButton",
   2488  onUserClick: () => gPrivacyPane.showSpeakerExceptions(),
   2489  deps: ["enabledSpeakerControl"],
   2490  visible: ({ enabledSpeakerControl }) => {
   2491    return enabledSpeakerControl.value;
   2492  },
   2493 });
   2494 Preferences.addSetting({
   2495  id: "notificationSettingsButton",
   2496  onUserClick: () => gPrivacyPane.showNotificationExceptions(),
   2497 });
   2498 Preferences.addSetting({
   2499  id: "autoplaySettingsButton",
   2500  onUserClick: () => gPrivacyPane.showAutoplayMediaExceptions(),
   2501 });
   2502 Preferences.addSetting({
   2503  id: "xrSettingsButton",
   2504  onUserClick: () => gPrivacyPane.showXRExceptions(),
   2505 });
   2506 
   2507 Preferences.addSetting({
   2508  id: "dohBox",
   2509 });
   2510 
   2511 Preferences.addSetting({
   2512  id: "dohAdvancedButton",
   2513  onUserClick(e) {
   2514    e.preventDefault();
   2515    gotoPref("paneDnsOverHttps");
   2516  },
   2517 });
   2518 
   2519 Preferences.addSetting({
   2520  id: "dohExceptionsButton",
   2521  onUserClick: () => gPrivacyPane.showDoHExceptions(),
   2522 });
   2523 
   2524 Preferences.addSetting({
   2525  id: "dohMode",
   2526  pref: "network.trr.mode",
   2527  setup(emitChange) {
   2528    Services.obs.addObserver(emitChange, "network:trr-mode-changed");
   2529    Services.obs.addObserver(emitChange, "network:trr-confirmation");
   2530    return () => {
   2531      Services.obs.removeObserver(emitChange, "network:trr-mode-changed");
   2532      Services.obs.removeObserver(emitChange, "network:trr-confirmation");
   2533    };
   2534  },
   2535 });
   2536 
   2537 Preferences.addSetting({
   2538  id: "dohURL",
   2539  pref: "network.trr.uri",
   2540  setup(emitChange) {
   2541    Services.obs.addObserver(emitChange, "network:trr-uri-changed");
   2542    Services.obs.addObserver(emitChange, "network:trr-confirmation");
   2543    return () => {
   2544      Services.obs.removeObserver(emitChange, "network:trr-uri-changed");
   2545      Services.obs.removeObserver(emitChange, "network:trr-confirmation");
   2546    };
   2547  },
   2548 });
   2549 
   2550 Preferences.addSetting({
   2551  id: "dohDefaultURL",
   2552  pref: "network.trr.default_provider_uri",
   2553 });
   2554 
   2555 Preferences.addSetting({
   2556  id: "dohDisableHeuristics",
   2557  pref: "doh-rollout.disable-heuristics",
   2558 });
   2559 
   2560 Preferences.addSetting({
   2561  id: "dohModeBoxItem",
   2562  deps: ["dohMode"],
   2563  getControlConfig: (config, deps) => {
   2564    let l10nId = "preferences-doh-overview-off";
   2565    if (deps.dohMode.value == Ci.nsIDNSService.MODE_NATIVEONLY) {
   2566      l10nId = "preferences-doh-overview-default";
   2567    } else if (
   2568      deps.dohMode.value == Ci.nsIDNSService.MODE_TRRFIRST ||
   2569      deps.dohMode.value == Ci.nsIDNSService.MODE_TRRONLY
   2570    ) {
   2571      l10nId = "preferences-doh-overview-custom";
   2572    }
   2573    return {
   2574      ...config,
   2575      l10nId,
   2576    };
   2577  },
   2578 });
   2579 
   2580 Preferences.addSetting({
   2581  id: "dohStatusBox",
   2582  deps: ["dohMode", "dohURL"],
   2583  getControlConfig: config => {
   2584    let l10nId = "preferences-doh-status-item-off";
   2585    let l10nArgs = {};
   2586    let supportPage = "";
   2587    let controlAttrs = { type: "info" };
   2588 
   2589    let trrURI = Services.dns.currentTrrURI;
   2590    let hostname = URL.parse(trrURI)?.hostname;
   2591 
   2592    let name = hostname || trrURI;
   2593    let nameFound = false;
   2594    let steering = false;
   2595    for (let resolver of DoHConfigController.currentConfig.providerList) {
   2596      if (resolver.uri == trrURI) {
   2597        name = resolver.UIName || name;
   2598        nameFound = true;
   2599        break;
   2600      }
   2601    }
   2602    if (!nameFound) {
   2603      for (let resolver of DoHConfigController.currentConfig.providerSteering
   2604        .providerList) {
   2605        if (resolver.uri == trrURI) {
   2606          steering = true;
   2607          name = resolver.UIName || name;
   2608          break;
   2609        }
   2610      }
   2611    }
   2612 
   2613    let mode = Services.dns.currentTrrMode;
   2614    if (
   2615      (mode == Ci.nsIDNSService.MODE_TRRFIRST ||
   2616        mode == Ci.nsIDNSService.MODE_TRRONLY) &&
   2617      lazy.gParentalControlsService?.parentalControlsEnabled
   2618    ) {
   2619      l10nId = "preferences-doh-status-item-not-active";
   2620      supportPage = "doh-status";
   2621      l10nArgs = {
   2622        reason: Services.dns.getTRRSkipReasonName(
   2623          Ci.nsITRRSkipReason.TRR_PARENTAL_CONTROL
   2624        ),
   2625        name,
   2626      };
   2627    } else {
   2628      let confirmationState = Services.dns.currentTrrConfirmationState;
   2629      if (
   2630        mode != Ci.nsIDNSService.MODE_TRRFIRST &&
   2631        mode != Ci.nsIDNSService.MODE_TRRONLY
   2632      ) {
   2633        l10nId = "preferences-doh-status-item-off";
   2634      } else if (
   2635        confirmationState == Ci.nsIDNSService.CONFIRM_TRYING_OK ||
   2636        confirmationState == Ci.nsIDNSService.CONFIRM_OK ||
   2637        confirmationState == Ci.nsIDNSService.CONFIRM_DISABLED
   2638      ) {
   2639        if (steering) {
   2640          l10nId = "preferences-doh-status-item-active-local";
   2641          controlAttrs = { type: "success" };
   2642        } else {
   2643          l10nId = "preferences-doh-status-item-active";
   2644          controlAttrs = { type: "success" };
   2645        }
   2646      } else if (steering) {
   2647        l10nId = "preferences-doh-status-item-not-active-local";
   2648        supportPage = "doh-status";
   2649        controlAttrs = { type: "warning" };
   2650      } else {
   2651        l10nId = "preferences-doh-status-item-not-active";
   2652        supportPage = "doh-status";
   2653        controlAttrs = { type: "warning" };
   2654      }
   2655 
   2656      let confirmationStatus = Services.dns.lastConfirmationStatus;
   2657      if (confirmationStatus != Cr.NS_OK) {
   2658        l10nArgs = {
   2659          reason: ChromeUtils.getXPCOMErrorName(confirmationStatus),
   2660          name,
   2661        };
   2662      } else {
   2663        l10nArgs = {
   2664          reason: Services.dns.getTRRSkipReasonName(
   2665            Services.dns.lastConfirmationSkipReason
   2666          ),
   2667          name,
   2668        };
   2669        if (
   2670          Services.dns.lastConfirmationSkipReason ==
   2671            Ci.nsITRRSkipReason.TRR_BAD_URL ||
   2672          !name
   2673        ) {
   2674          l10nId = "preferences-doh-status-item-not-active-bad-url";
   2675          supportPage = "doh-status";
   2676          controlAttrs = { type: "warning" };
   2677        }
   2678      }
   2679    }
   2680 
   2681    return {
   2682      ...config,
   2683      l10nId,
   2684      l10nArgs,
   2685      supportPage,
   2686      controlAttrs,
   2687    };
   2688  },
   2689 });
   2690 
   2691 Preferences.addSetting({
   2692  id: "dohRadioGroup",
   2693  // These deps are complicated:
   2694  // this radio group, along with dohFallbackIfCustom controls the mode and URL.
   2695  // Therefore, we set dohMode and dohURL as deps here. This is a smell, but needed
   2696  // for the mismatch of control-to-pref.
   2697  deps: ["dohFallbackIfCustom", "dohMode", "dohURL"],
   2698  onUserChange: (val, deps) => {
   2699    let value = null;
   2700    if (val == "default") {
   2701      value = "dohDefaultRadio";
   2702    } else if (val == "off") {
   2703      value = "dohOffRadio";
   2704    } else if (val == "custom" && deps.dohFallbackIfCustom.value) {
   2705      value = "dohEnabledRadio";
   2706    } else if (val == "custom" && !deps.dohFallbackIfCustom.value) {
   2707      value = "dohStrictRadio";
   2708    }
   2709    if (value) {
   2710      Glean.securityDohSettings.modeChangedButton.record({
   2711        value,
   2712      });
   2713    }
   2714  },
   2715  get: (_val, deps) => {
   2716    switch (deps.dohMode.value) {
   2717      case Ci.nsIDNSService.MODE_NATIVEONLY:
   2718        return "default";
   2719      case Ci.nsIDNSService.MODE_TRRFIRST:
   2720      case Ci.nsIDNSService.MODE_TRRONLY:
   2721        return "custom";
   2722      case Ci.nsIDNSService.MODE_TRROFF:
   2723      case Ci.nsIDNSService.MODE_RESERVED1:
   2724      case Ci.nsIDNSService.MODE_RESERVED4:
   2725      default:
   2726        return "off";
   2727    }
   2728  },
   2729  set: (val, deps) => {
   2730    if (val == "custom") {
   2731      if (deps.dohFallbackIfCustom.value) {
   2732        deps.dohMode.value = Ci.nsIDNSService.MODE_TRRFIRST;
   2733      } else {
   2734        deps.dohMode.value = Ci.nsIDNSService.MODE_TRRONLY;
   2735      }
   2736    } else if (val == "off") {
   2737      deps.dohMode.value = Ci.nsIDNSService.MODE_TRROFF;
   2738    } else {
   2739      deps.dohMode.value = Ci.nsIDNSService.MODE_NATIVEONLY;
   2740    }
   2741 
   2742    // When the mode is set to 0 we need to clear the URI so
   2743    // doh-rollout can kick in.
   2744    if (deps.dohMode.value == Ci.nsIDNSService.MODE_NATIVEONLY) {
   2745      deps.dohURL.pref.value = undefined;
   2746      Services.prefs.clearUserPref("doh-rollout.disable-heuristics");
   2747    }
   2748 
   2749    // Bug 1861285
   2750    // When the mode is set to 2 or 3, we need to check if network.trr.uri is a empty string.
   2751    // In this case, we need to update network.trr.uri to default to fallbackProviderURI.
   2752    // This occurs when the mode is previously set to 0 (Default Protection).
   2753    if (
   2754      deps.dohMode.value == Ci.nsIDNSService.MODE_TRRFIRST ||
   2755      deps.dohMode.value == Ci.nsIDNSService.MODE_TRRONLY
   2756    ) {
   2757      if (!deps.dohURL.value) {
   2758        deps.dohURL.value =
   2759          DoHConfigController.currentConfig.fallbackProviderURI;
   2760      }
   2761    }
   2762 
   2763    // Bug 1900672
   2764    // When the mode is set to 5, clear the pref to ensure that
   2765    // network.trr.uri is set to fallbackProviderURIwhen the mode is set to 2 or 3 afterwards
   2766    if (deps.dohMode.value == Ci.nsIDNSService.MODE_TRROFF) {
   2767      deps.dohURL.pref.value = undefined;
   2768    }
   2769  },
   2770 });
   2771 
   2772 Preferences.addSetting({
   2773  id: "dohFallbackIfCustom",
   2774  pref: "network.trr_ui.fallback_was_checked",
   2775  // These deps are complicated:
   2776  // this checkbox, along with dohRadioGroup controls the mode and URL.
   2777  // Therefore, we set dohMode as a dep here. This is a smell, but needed
   2778  // for the mismatch of control-to-pref.
   2779  deps: ["dohMode"],
   2780  onUserChange: val => {
   2781    if (val) {
   2782      Glean.securityDohSettings.modeChangedButton.record({
   2783        value: "dohEnabledRadio",
   2784      });
   2785    } else {
   2786      Glean.securityDohSettings.modeChangedButton.record({
   2787        value: "dohStrictRadio",
   2788      });
   2789    }
   2790  },
   2791  get: (val, deps) => {
   2792    // If we are in a custom mode, we need to get the value from the Setting
   2793    if (deps.dohMode.value == Ci.nsIDNSService.MODE_TRRFIRST) {
   2794      return true;
   2795    }
   2796    if (deps.dohMode.value == Ci.nsIDNSService.MODE_TRRONLY) {
   2797      return false;
   2798    }
   2799 
   2800    // Propagate the preference otherwise
   2801    return val;
   2802  },
   2803  set: (val, deps) => {
   2804    // Toggle the preference that controls the setting if are in a custom mode
   2805    // This should be the only case where the checkbox is enabled, but we can be
   2806    // careful and test.
   2807    if (deps.dohMode.value == Ci.nsIDNSService.MODE_TRRFIRST && !val) {
   2808      deps.dohMode.value = Ci.nsIDNSService.MODE_TRRONLY;
   2809    } else if (deps.dohMode.value == Ci.nsIDNSService.MODE_TRRONLY && val) {
   2810      deps.dohMode.value = Ci.nsIDNSService.MODE_TRRFIRST;
   2811    }
   2812    // Propagate to the real preference
   2813    return val;
   2814  },
   2815 });
   2816 
   2817 Preferences.addSetting({
   2818  id: "dohCustomProvider",
   2819  deps: ["dohProviderSelect", "dohURL"],
   2820  _value: null,
   2821  visible: deps => {
   2822    return deps.dohProviderSelect.value == "custom";
   2823  },
   2824  get(_val, deps) {
   2825    if (this._value === null) {
   2826      return deps.dohURL.value;
   2827    }
   2828    return this._value;
   2829  },
   2830  set(val, deps) {
   2831    this._value = val;
   2832    if (val == "") {
   2833      val = " ";
   2834    }
   2835    deps.dohURL.value = val;
   2836  },
   2837 });
   2838 
   2839 Preferences.addSetting({
   2840  id: "dohProviderSelect",
   2841  deps: ["dohURL", "dohDefaultURL"],
   2842  _custom: false,
   2843  onUserChange: value => {
   2844    Glean.securityDohSettings.providerChoiceValue.record({
   2845      value,
   2846    });
   2847  },
   2848  getControlConfig(config, deps) {
   2849    let options = [];
   2850 
   2851    let resolvers = DoHConfigController.currentConfig.providerList;
   2852    // if there's no default, we'll hold its position with an empty string
   2853    let defaultURI = DoHConfigController.currentConfig.fallbackProviderURI;
   2854    let defaultFound = resolvers.some(p => p.uri == defaultURI);
   2855    if (!defaultFound && defaultURI) {
   2856      // the default value for the pref isn't included in the resolvers list
   2857      // so we'll make a stub for it. Without an id, we'll have to use the url as the label
   2858      resolvers.unshift({ uri: defaultURI });
   2859    }
   2860    let currentURI = deps.dohURL.value;
   2861    if (currentURI && !resolvers.some(p => p.uri == currentURI)) {
   2862      this._custom = true;
   2863    }
   2864 
   2865    options = resolvers.map(resolver => {
   2866      let option = {
   2867        value: resolver.uri,
   2868        l10nArgs: {
   2869          name: resolver.UIName || resolver.uri,
   2870        },
   2871      };
   2872      if (resolver.uri == defaultURI) {
   2873        option.l10nId = "connection-dns-over-https-url-item-default";
   2874      } else {
   2875        option.l10nId = "connection-dns-over-https-url-item";
   2876      }
   2877      return option;
   2878    });
   2879    options.push({
   2880      value: "custom",
   2881      l10nId: "connection-dns-over-https-url-custom",
   2882    });
   2883 
   2884    return {
   2885      options,
   2886      ...config,
   2887    };
   2888  },
   2889  get(_val, deps) {
   2890    if (this._custom) {
   2891      return "custom";
   2892    }
   2893    let currentURI = deps.dohURL.value;
   2894    if (!currentURI) {
   2895      currentURI = deps.dohDefaultURL.value;
   2896    }
   2897    return currentURI;
   2898  },
   2899  set(val, deps, setting) {
   2900    if (val != "custom") {
   2901      this._custom = false;
   2902      deps.dohURL.value = val;
   2903    } else {
   2904      this._custom = true;
   2905    }
   2906    setting.emit("change");
   2907    return val;
   2908  },
   2909 });
   2910 
   2911 function shouldDisableETPCategoryControls() {
   2912  let policy = Services.policies.getActivePolicies();
   2913  return policy?.EnableTrackingProtection?.Locked || policy?.Cookies?.Locked;
   2914 }
   2915 
   2916 Preferences.addSetting({
   2917  id: "contentBlockingCategory",
   2918  pref: "browser.contentblocking.category",
   2919 });
   2920 
   2921 // We need a separate setting for the radio group for custom disable behavior.
   2922 // Setter and getter simply write to the pref.
   2923 Preferences.addSetting({
   2924  id: "contentBlockingCategoryRadioGroup",
   2925  deps: ["contentBlockingCategory"],
   2926  get(_, { contentBlockingCategory }) {
   2927    return contentBlockingCategory.value;
   2928  },
   2929  set(value, { contentBlockingCategory }) {
   2930    contentBlockingCategory.value = value;
   2931  },
   2932  getControlConfig(config, _, setting) {
   2933    if (!shouldDisableETPCategoryControls()) {
   2934      return config;
   2935    }
   2936 
   2937    let { options } = config;
   2938 
   2939    // If ETP level is set to custom keep the radio button enabled so the "customize" button works even when the category selection itself is locked.
   2940    for (let option of options) {
   2941      option.disabled =
   2942        option.id != "etpLevelCustom" || setting.value != "custom";
   2943    }
   2944 
   2945    return config;
   2946  },
   2947 });
   2948 
   2949 Preferences.addSetting({
   2950  id: "etpStatusBoxGroup",
   2951 });
   2952 
   2953 Preferences.addSetting({
   2954  id: "etpStatusItem",
   2955  deps: ["contentBlockingCategory"],
   2956  getControlConfig(config, { contentBlockingCategory }) {
   2957    // Display a different description and label depending on the content blocking category (= ETP level).
   2958    let categoryToL10nId = {
   2959      standard: "preferences-etp-level-standard",
   2960      strict: "preferences-etp-level-strict",
   2961      custom: "preferences-etp-level-custom",
   2962    };
   2963 
   2964    return {
   2965      ...config,
   2966      l10nId:
   2967        categoryToL10nId[contentBlockingCategory.value] ??
   2968        "preferences-etp-level-standard",
   2969    };
   2970  },
   2971 });
   2972 
   2973 Preferences.addSetting({
   2974  id: "etpStatusAdvancedButton",
   2975  onUserClick(e) {
   2976    e.preventDefault();
   2977    gotoPref("etp");
   2978  },
   2979 });
   2980 
   2981 Preferences.addSetting({
   2982  id: "protectionsDashboardLink",
   2983 });
   2984 
   2985 Preferences.addSetting({
   2986  id: "etpBannerEl",
   2987 });
   2988 
   2989 Preferences.addSetting({
   2990  id: "etpAllowListBaselineEnabled",
   2991  pref: "privacy.trackingprotection.allow_list.baseline.enabled",
   2992  deps: ["contentBlockingCategory"],
   2993  visible({ contentBlockingCategory }) {
   2994    return contentBlockingCategory.value == "strict";
   2995  },
   2996  onUserChange(value, _deps, setting) {
   2997    gPrivacyPane.onBaselineAllowListSettingChange(value, setting);
   2998  },
   2999 });
   3000 
   3001 Preferences.addSetting({
   3002  id: "etpAllowListConvenienceEnabled",
   3003  pref: "privacy.trackingprotection.allow_list.convenience.enabled",
   3004  onUserChange() {
   3005    gPrivacyPane.maybeNotifyUserToReload();
   3006  },
   3007 });
   3008 
   3009 Preferences.addSetting({
   3010  id: "etpCustomizeButton",
   3011  onUserClick(e) {
   3012    e.preventDefault();
   3013    gotoPref("etpCustomize");
   3014  },
   3015 });
   3016 
   3017 Preferences.addSetting({
   3018  id: "reloadTabsHint",
   3019  _showHint: false,
   3020  set(value, _, setting) {
   3021    this._showHint = value;
   3022    setting.emit("change");
   3023  },
   3024  get() {
   3025    return this._showHint;
   3026  },
   3027  visible(_, setting) {
   3028    return setting.value;
   3029  },
   3030  onUserClick() {
   3031    gPrivacyPane.reloadAllOtherTabs();
   3032  },
   3033 });
   3034 
   3035 Preferences.addSetting({
   3036  id: "resistFingerprinting",
   3037  pref: "privacy.resistFingerprinting",
   3038 });
   3039 
   3040 Preferences.addSetting({
   3041  id: "resistFingerprintingPBM",
   3042  pref: "privacy.resistFingerprinting.pbmode",
   3043 });
   3044 
   3045 Preferences.addSetting({
   3046  id: "rfpWarning",
   3047  deps: ["resistFingerprinting", "resistFingerprintingPBM"],
   3048  visible({ resistFingerprinting, resistFingerprintingPBM }) {
   3049    return resistFingerprinting.value || resistFingerprintingPBM.value;
   3050  },
   3051 });
   3052 
   3053 Preferences.addSetting({
   3054  id: "etpLevelWarning",
   3055  deps: ["contentBlockingCategory"],
   3056  visible({ contentBlockingCategory }) {
   3057    return contentBlockingCategory.value != "standard";
   3058  },
   3059 });
   3060 
   3061 Preferences.addSetting({
   3062  id: "etpManageExceptionsButton",
   3063  onUserClick() {
   3064    let params = {
   3065      permissionType: "trackingprotection",
   3066      disableETPVisible: true,
   3067      prefilledHost: "",
   3068      hideStatusColumn: true,
   3069    };
   3070    gSubDialog.open(
   3071      "chrome://browser/content/preferences/dialogs/permissions.xhtml",
   3072      undefined,
   3073      params
   3074    );
   3075  },
   3076 });
   3077 
   3078 Preferences.addSetting({
   3079  id: "etpResetButtonGroup",
   3080 });
   3081 
   3082 Preferences.addSetting({
   3083  id: "etpResetStandardButton",
   3084  deps: ["contentBlockingCategory"],
   3085  onUserClick(_, { contentBlockingCategory }) {
   3086    contentBlockingCategory.value = "standard";
   3087  },
   3088  disabled({ contentBlockingCategory }) {
   3089    return (
   3090      contentBlockingCategory.value == "standard" ||
   3091      shouldDisableETPCategoryControls()
   3092    );
   3093  },
   3094 });
   3095 
   3096 Preferences.addSetting({
   3097  id: "etpResetStrictButton",
   3098  deps: ["contentBlockingCategory"],
   3099  onUserClick(_, { contentBlockingCategory }) {
   3100    contentBlockingCategory.value = "strict";
   3101  },
   3102  disabled({ contentBlockingCategory }) {
   3103    return (
   3104      contentBlockingCategory.value == "strict" ||
   3105      shouldDisableETPCategoryControls()
   3106    );
   3107  },
   3108 });
   3109 
   3110 Preferences.addSetting({
   3111  id: "etpAllowListBaselineEnabledCustom",
   3112  pref: "privacy.trackingprotection.allow_list.baseline.enabled",
   3113  onUserChange(value, _deps, setting) {
   3114    gPrivacyPane.onBaselineAllowListSettingChange(value, setting);
   3115  },
   3116 });
   3117 
   3118 Preferences.addSetting({
   3119  id: "etpAllowListConvenienceEnabledCustom",
   3120  pref: "privacy.trackingprotection.allow_list.convenience.enabled",
   3121  onUserChange() {
   3122    gPrivacyPane.maybeNotifyUserToReload();
   3123  },
   3124 });
   3125 
   3126 Preferences.addSetting({
   3127  id: "etpCustomCookiesEnabled",
   3128  deps: ["cookieBehavior"],
   3129  disabled: ({ cookieBehavior }) => {
   3130    return cookieBehavior.locked;
   3131  },
   3132  get(_, { cookieBehavior }) {
   3133    return cookieBehavior.value != Ci.nsICookieService.BEHAVIOR_ACCEPT;
   3134  },
   3135  set(value, { cookieBehavior }) {
   3136    if (!value) {
   3137      cookieBehavior.value = Ci.nsICookieService.BEHAVIOR_ACCEPT;
   3138    } else {
   3139      // When the user enabled cookie blocking, set the cookie behavior to the default.
   3140      cookieBehavior.value = cookieBehavior.pref.defaultValue;
   3141    }
   3142  },
   3143 });
   3144 
   3145 Preferences.addSetting({
   3146  id: "trackingProtectionEnabled",
   3147  pref: "privacy.trackingprotection.enabled",
   3148 });
   3149 
   3150 Preferences.addSetting({
   3151  id: "trackingProtectionEnabledPBM",
   3152  pref: "privacy.trackingprotection.pbmode.enabled",
   3153 });
   3154 
   3155 Preferences.addSetting({
   3156  id: "etpCustomTrackingProtectionEnabledContext",
   3157  deps: ["trackingProtectionEnabled", "trackingProtectionEnabledPBM"],
   3158  get(_, { trackingProtectionEnabled, trackingProtectionEnabledPBM }) {
   3159    if (trackingProtectionEnabled.value && trackingProtectionEnabledPBM.value) {
   3160      return "all";
   3161    } else if (trackingProtectionEnabledPBM) {
   3162      return "pbmOnly";
   3163    }
   3164    return null;
   3165  },
   3166  set(value, { trackingProtectionEnabled, trackingProtectionEnabledPBM }) {
   3167    if (value == "all") {
   3168      trackingProtectionEnabled.value = true;
   3169      trackingProtectionEnabledPBM.value = true;
   3170    } else if (value == "pbmOnly") {
   3171      trackingProtectionEnabled.value = false;
   3172      trackingProtectionEnabledPBM.value = true;
   3173    }
   3174  },
   3175 });
   3176 
   3177 Preferences.addSetting({
   3178  id: "etpCustomTrackingProtectionEnabled",
   3179  deps: ["trackingProtectionEnabled", "trackingProtectionEnabledPBM"],
   3180  disabled: ({ trackingProtectionEnabled, trackingProtectionEnabledPBM }) => {
   3181    return (
   3182      trackingProtectionEnabled.locked || trackingProtectionEnabledPBM.locked
   3183    );
   3184  },
   3185  get(_, { trackingProtectionEnabled, trackingProtectionEnabledPBM }) {
   3186    return (
   3187      trackingProtectionEnabled.value || trackingProtectionEnabledPBM.value
   3188    );
   3189  },
   3190  set(value, { trackingProtectionEnabled, trackingProtectionEnabledPBM }) {
   3191    if (value) {
   3192      trackingProtectionEnabled.value = false;
   3193      trackingProtectionEnabledPBM.value = true;
   3194    } else {
   3195      trackingProtectionEnabled.value = false;
   3196      trackingProtectionEnabledPBM.value = false;
   3197    }
   3198  },
   3199 });
   3200 
   3201 Preferences.addSetting({
   3202  id: "etpCustomCryptominingProtectionEnabled",
   3203  pref: "privacy.trackingprotection.cryptomining.enabled",
   3204 });
   3205 
   3206 Preferences.addSetting({
   3207  id: "etpCustomKnownFingerprintingProtectionEnabled",
   3208  pref: "privacy.trackingprotection.fingerprinting.enabled",
   3209 });
   3210 
   3211 Preferences.addSetting({
   3212  id: "etpCustomFingerprintingProtectionEnabled",
   3213  pref: "privacy.fingerprintingProtection",
   3214 });
   3215 
   3216 Preferences.addSetting({
   3217  id: "etpCustomFingerprintingProtectionEnabledPBM",
   3218  pref: "privacy.fingerprintingProtection.pbmode",
   3219 });
   3220 
   3221 Preferences.addSetting({
   3222  id: "etpCustomSuspectFingerprintingProtectionEnabled",
   3223  deps: [
   3224    "etpCustomFingerprintingProtectionEnabled",
   3225    "etpCustomFingerprintingProtectionEnabledPBM",
   3226  ],
   3227  disabled({
   3228    etpCustomFingerprintingProtectionEnabled,
   3229    etpCustomFingerprintingProtectionEnabledPBM,
   3230  }) {
   3231    return (
   3232      etpCustomFingerprintingProtectionEnabled.locked ||
   3233      etpCustomFingerprintingProtectionEnabledPBM.locked
   3234    );
   3235  },
   3236  get(
   3237    _,
   3238    {
   3239      etpCustomFingerprintingProtectionEnabled,
   3240      etpCustomFingerprintingProtectionEnabledPBM,
   3241    }
   3242  ) {
   3243    return (
   3244      etpCustomFingerprintingProtectionEnabled.value ||
   3245      etpCustomFingerprintingProtectionEnabledPBM.value
   3246    );
   3247  },
   3248  set(
   3249    value,
   3250    {
   3251      etpCustomFingerprintingProtectionEnabled,
   3252      etpCustomFingerprintingProtectionEnabledPBM,
   3253    }
   3254  ) {
   3255    if (value) {
   3256      etpCustomFingerprintingProtectionEnabled.value = false;
   3257      etpCustomFingerprintingProtectionEnabledPBM.value = true;
   3258    } else {
   3259      etpCustomFingerprintingProtectionEnabled.value = false;
   3260      etpCustomFingerprintingProtectionEnabledPBM.value = false;
   3261    }
   3262  },
   3263 });
   3264 
   3265 Preferences.addSetting({
   3266  id: "etpCustomSuspectFingerprintingProtectionEnabledContext",
   3267  deps: [
   3268    "etpCustomFingerprintingProtectionEnabled",
   3269    "etpCustomFingerprintingProtectionEnabledPBM",
   3270  ],
   3271  get(
   3272    _,
   3273    {
   3274      etpCustomFingerprintingProtectionEnabled,
   3275      etpCustomFingerprintingProtectionEnabledPBM,
   3276    }
   3277  ) {
   3278    if (
   3279      etpCustomFingerprintingProtectionEnabled.value &&
   3280      etpCustomFingerprintingProtectionEnabledPBM.value
   3281    ) {
   3282      return "all";
   3283    } else if (etpCustomFingerprintingProtectionEnabledPBM) {
   3284      return "pbmOnly";
   3285    }
   3286    return null;
   3287  },
   3288  set(
   3289    value,
   3290    {
   3291      etpCustomFingerprintingProtectionEnabled,
   3292      etpCustomFingerprintingProtectionEnabledPBM,
   3293    }
   3294  ) {
   3295    if (value == "all") {
   3296      etpCustomFingerprintingProtectionEnabled.value = true;
   3297      etpCustomFingerprintingProtectionEnabledPBM.value = true;
   3298    } else if (value == "pbmOnly") {
   3299      etpCustomFingerprintingProtectionEnabled.value = false;
   3300      etpCustomFingerprintingProtectionEnabledPBM.value = true;
   3301    }
   3302  },
   3303 });
   3304 
   3305 function setEventListener(aId, aEventType, aCallback) {
   3306  document
   3307    .getElementById(aId)
   3308    .addEventListener(aEventType, aCallback.bind(gPrivacyPane));
   3309 }
   3310 
   3311 function setSyncFromPrefListener(aId, aCallback) {
   3312  Preferences.addSyncFromPrefListener(document.getElementById(aId), aCallback);
   3313 }
   3314 
   3315 function setSyncToPrefListener(aId, aCallback) {
   3316  Preferences.addSyncToPrefListener(document.getElementById(aId), aCallback);
   3317 }
   3318 
   3319 function dataCollectionCheckboxHandler({
   3320  checkbox,
   3321  pref,
   3322  matchPref = () => true,
   3323  isDisabled = () => false,
   3324 }) {
   3325  function updateCheckbox() {
   3326    let collectionEnabled = Services.prefs.getBoolPref(
   3327      PREF_UPLOAD_ENABLED,
   3328      false
   3329    );
   3330 
   3331    if (collectionEnabled && matchPref()) {
   3332      checkbox.toggleAttribute(
   3333        "checked",
   3334        Services.prefs.getBoolPref(pref, false)
   3335      );
   3336      checkbox.setAttribute("preference", pref);
   3337    } else {
   3338      checkbox.removeAttribute("preference");
   3339      checkbox.removeAttribute("checked");
   3340    }
   3341 
   3342    checkbox.disabled =
   3343      !collectionEnabled || Services.prefs.prefIsLocked(pref) || isDisabled();
   3344  }
   3345 
   3346  Preferences.get(PREF_UPLOAD_ENABLED).on("change", updateCheckbox);
   3347  updateCheckbox();
   3348 }
   3349 
   3350 // Sets the "Learn how" SUMO link in the Strict/Custom options of Content Blocking.
   3351 function setUpContentBlockingWarnings() {
   3352  document.getElementById("fpiIncompatibilityWarning").hidden =
   3353    !gIsFirstPartyIsolated;
   3354 
   3355  document.getElementById("rfpIncompatibilityWarning").hidden =
   3356    !Preferences.get("privacy.resistFingerprinting").value &&
   3357    !Preferences.get("privacy.resistFingerprinting.pbmode").value;
   3358 }
   3359 
   3360 function initTCPStandardSection() {
   3361  let cookieBehaviorPref = Preferences.get("network.cookie.cookieBehavior");
   3362  let updateTCPSectionVisibilityState = () => {
   3363    document.getElementById("etpStandardTCPBox").hidden =
   3364      cookieBehaviorPref.value !=
   3365      Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN;
   3366  };
   3367 
   3368  cookieBehaviorPref.on("change", updateTCPSectionVisibilityState);
   3369 
   3370  updateTCPSectionVisibilityState();
   3371 }
   3372 
   3373 var gPrivacyPane = {
   3374  _pane: null,
   3375 
   3376  /**
   3377   * Show the Security Level UI
   3378   */
   3379  _initSecurityLevel() {
   3380    SecurityLevelPreferences.init();
   3381    window.addEventListener("unload", () => SecurityLevelPreferences.uninit(), {
   3382      once: true,
   3383    });
   3384  },
   3385 
   3386  /**
   3387   * Whether the prompt to restart Firefox should appear when changing the autostart pref.
   3388   */
   3389  _shouldPromptForRestart: true,
   3390 
   3391  /**
   3392   * Update the tracking protection UI to deal with extension control.
   3393   */
   3394  _updateTrackingProtectionUI() {
   3395    let cBPrefisLocked = CONTENT_BLOCKING_PREFS.some(pref =>
   3396      Services.prefs.prefIsLocked(pref)
   3397    );
   3398    let tPPrefisLocked = TRACKING_PROTECTION_PREFS.some(pref =>
   3399      Services.prefs.prefIsLocked(pref)
   3400    );
   3401 
   3402    function setInputsDisabledState(isControlled) {
   3403      let tpDisabled = tPPrefisLocked || isControlled;
   3404      let disabled = cBPrefisLocked || isControlled;
   3405      let tpCheckbox = document.getElementById(
   3406        "contentBlockingTrackingProtectionCheckbox"
   3407      );
   3408      // Only enable the TP menu if Detect All Trackers is enabled.
   3409      document.getElementById("trackingProtectionMenu").disabled =
   3410        tpDisabled || !tpCheckbox.checked;
   3411      tpCheckbox.disabled = tpDisabled;
   3412 
   3413      document.getElementById("standardRadio").disabled = disabled;
   3414      document.getElementById("strictRadio").disabled = disabled;
   3415      document
   3416        .getElementById("contentBlockingOptionStrict")
   3417        .classList.toggle("disabled", disabled);
   3418      document
   3419        .getElementById("contentBlockingOptionStandard")
   3420        .classList.toggle("disabled", disabled);
   3421      let arrowButtons = document.querySelectorAll("button.arrowhead");
   3422      for (let button of arrowButtons) {
   3423        button.disabled = disabled;
   3424      }
   3425 
   3426      // Notify observers that the TP UI has been updated.
   3427      // This is needed since our tests need to be notified about the
   3428      // trackingProtectionMenu element getting disabled/enabled at the right time.
   3429      Services.obs.notifyObservers(window, "privacy-pane-tp-ui-updated");
   3430    }
   3431 
   3432    if (shouldDisableETPCategoryControls()) {
   3433      setInputsDisabledState(true);
   3434    }
   3435    if (tPPrefisLocked) {
   3436      // An extension can't control this setting if either pref is locked.
   3437      hideControllingExtension(TRACKING_PROTECTION_KEY);
   3438      setInputsDisabledState(false);
   3439    } else {
   3440      handleControllingExtension(
   3441        PREF_SETTING_TYPE,
   3442        TRACKING_PROTECTION_KEY
   3443      ).then(setInputsDisabledState);
   3444    }
   3445  },
   3446 
   3447  /**
   3448   * Set up handlers for showing and hiding controlling extension info
   3449   * for tracking protection.
   3450   */
   3451  _initTrackingProtectionExtensionControl() {
   3452    setEventListener(
   3453      "contentBlockingDisableTrackingProtectionExtension",
   3454      "command",
   3455      makeDisableControllingExtension(
   3456        PREF_SETTING_TYPE,
   3457        TRACKING_PROTECTION_KEY
   3458      )
   3459    );
   3460 
   3461    let trackingProtectionObserver = {
   3462      observe() {
   3463        gPrivacyPane._updateTrackingProtectionUI();
   3464      },
   3465    };
   3466 
   3467    for (let pref of TRACKING_PROTECTION_PREFS) {
   3468      Services.prefs.addObserver(pref, trackingProtectionObserver);
   3469    }
   3470    window.addEventListener("unload", () => {
   3471      for (let pref of TRACKING_PROTECTION_PREFS) {
   3472        Services.prefs.removeObserver(pref, trackingProtectionObserver);
   3473      }
   3474    });
   3475  },
   3476 
   3477  /**
   3478   * Ensure the tracking protection exception list is migrated before the privacy
   3479   * preferences UI is shown.
   3480   * If the migration has already been run, this is a no-op.
   3481   */
   3482  _ensureTrackingProtectionExceptionListMigration() {
   3483    // Let's check the migration pref here as well to avoid the extra xpcom call
   3484    // for the common case where we've already migrated.
   3485    if (
   3486      Services.prefs.getBoolPref(
   3487        "privacy.trackingprotection.allow_list.hasMigratedCategoryPrefs",
   3488        false
   3489      )
   3490    ) {
   3491      return;
   3492    }
   3493 
   3494    let exceptionListService = Cc[
   3495      "@mozilla.org/url-classifier/exception-list-service;1"
   3496    ].getService(Ci.nsIUrlClassifierExceptionListService);
   3497 
   3498    exceptionListService.maybeMigrateCategoryPrefs();
   3499  },
   3500 
   3501  get dnsOverHttpsResolvers() {
   3502    let providers = DoHConfigController.currentConfig.providerList;
   3503    // if there's no default, we'll hold its position with an empty string
   3504    let defaultURI = DoHConfigController.currentConfig.fallbackProviderURI;
   3505    let defaultIndex = providers.findIndex(p => p.uri == defaultURI);
   3506    if (defaultIndex == -1 && defaultURI) {
   3507      // the default value for the pref isn't included in the resolvers list
   3508      // so we'll make a stub for it. Without an id, we'll have to use the url as the label
   3509      providers.unshift({ uri: defaultURI });
   3510    }
   3511    return providers;
   3512  },
   3513 
   3514  updateDoHResolverList(mode) {
   3515    let resolvers = this.dnsOverHttpsResolvers;
   3516    let currentURI = Preferences.get("network.trr.uri").value;
   3517    if (!currentURI) {
   3518      currentURI = Preferences.get("network.trr.default_provider_uri").value;
   3519    }
   3520    let menu = document.getElementById(`${mode}ResolverChoices`);
   3521 
   3522    let selectedIndex = currentURI
   3523      ? resolvers.findIndex(r => r.uri == currentURI)
   3524      : 0;
   3525    if (selectedIndex == -1) {
   3526      // select the last "Custom" item
   3527      selectedIndex = menu.itemCount - 1;
   3528    }
   3529    menu.selectedIndex = selectedIndex;
   3530 
   3531    let customInput = document.getElementById(`${mode}InputField`);
   3532    customInput.hidden = menu.value != "custom";
   3533  },
   3534 
   3535  populateDoHResolverList(mode) {
   3536    let resolvers = this.dnsOverHttpsResolvers;
   3537    let defaultURI = DoHConfigController.currentConfig.fallbackProviderURI;
   3538    let menu = document.getElementById(`${mode}ResolverChoices`);
   3539 
   3540    // populate the DNS-Over-HTTPS resolver list
   3541    menu.removeAllItems();
   3542    for (let resolver of resolvers) {
   3543      let item = menu.appendItem(undefined, resolver.uri);
   3544      if (resolver.uri == defaultURI) {
   3545        document.l10n.setAttributes(
   3546          item,
   3547          "connection-dns-over-https-url-item-default",
   3548          {
   3549            name: resolver.UIName || resolver.uri,
   3550          }
   3551        );
   3552      } else {
   3553        item.label = resolver.UIName || resolver.uri;
   3554      }
   3555    }
   3556    let lastItem = menu.appendItem(undefined, "custom");
   3557    document.l10n.setAttributes(
   3558      lastItem,
   3559      "connection-dns-over-https-url-custom"
   3560    );
   3561 
   3562    // set initial selection in the resolver provider picker
   3563    this.updateDoHResolverList(mode);
   3564 
   3565    let customInput = document.getElementById(`${mode}InputField`);
   3566 
   3567    function updateURIPref() {
   3568      if (customInput.value == "") {
   3569        // Setting the pref to empty string will make it have the default
   3570        // pref value which makes us fallback to using the default TRR
   3571        // resolver in network.trr.default_provider_uri.
   3572        // If the input is empty we set it to "(space)" which is essentially
   3573        // the same.
   3574        Services.prefs.setStringPref("network.trr.uri", " ");
   3575      } else {
   3576        Services.prefs.setStringPref("network.trr.uri", customInput.value);
   3577      }
   3578    }
   3579 
   3580    menu.addEventListener("command", () => {
   3581      if (menu.value == "custom") {
   3582        customInput.hidden = false;
   3583        updateURIPref();
   3584      } else {
   3585        customInput.hidden = true;
   3586        Services.prefs.setStringPref("network.trr.uri", menu.value);
   3587      }
   3588      Glean.securityDohSettings.providerChoiceValue.record({
   3589        value: menu.value,
   3590      });
   3591 
   3592      // Update other menu too.
   3593      let otherMode = mode == "dohEnabled" ? "dohStrict" : "dohEnabled";
   3594      let otherMenu = document.getElementById(`${otherMode}ResolverChoices`);
   3595      let otherInput = document.getElementById(`${otherMode}InputField`);
   3596      otherMenu.value = menu.value;
   3597      otherInput.hidden = otherMenu.value != "custom";
   3598    });
   3599 
   3600    // Change the URL when you press ENTER in the input field it or loses focus
   3601    customInput.addEventListener("change", () => {
   3602      updateURIPref();
   3603    });
   3604  },
   3605 
   3606  async updateDoHStatus() {
   3607    let trrURI = Services.dns.currentTrrURI;
   3608    let hostname = URL.parse(trrURI)?.hostname;
   3609    if (!hostname) {
   3610      hostname = await document.l10n.formatValue("preferences-doh-bad-url");
   3611    }
   3612 
   3613    let steering = document.getElementById("dohSteeringStatus");
   3614    steering.hidden = true;
   3615 
   3616    let dohResolver = document.getElementById("dohResolver");
   3617    dohResolver.hidden = true;
   3618 
   3619    let status = document.getElementById("dohStatus");
   3620 
   3621    async function setStatus(localizedStringName, options) {
   3622      let opts = options || {};
   3623      let statusString = await document.l10n.formatValue(
   3624        localizedStringName,
   3625        opts
   3626      );
   3627      document.l10n.setAttributes(status, "preferences-doh-status", {
   3628        status: statusString,
   3629      });
   3630    }
   3631 
   3632    function computeStatus() {
   3633      let mode = Services.dns.currentTrrMode;
   3634      if (
   3635        mode == Ci.nsIDNSService.MODE_TRRFIRST ||
   3636        mode == Ci.nsIDNSService.MODE_TRRONLY
   3637      ) {
   3638        if (lazy.gParentalControlsService?.parentalControlsEnabled) {
   3639          return "preferences-doh-status-not-active";
   3640        }
   3641        let confirmationState = Services.dns.currentTrrConfirmationState;
   3642        switch (confirmationState) {
   3643          case Ci.nsIDNSService.CONFIRM_TRYING_OK:
   3644          case Ci.nsIDNSService.CONFIRM_OK:
   3645          case Ci.nsIDNSService.CONFIRM_DISABLED:
   3646            return "preferences-doh-status-active";
   3647          default:
   3648            return "preferences-doh-status-not-active";
   3649        }
   3650      }
   3651 
   3652      return "preferences-doh-status-disabled";
   3653    }
   3654 
   3655    let errReason = "";
   3656    let confirmationStatus = Services.dns.lastConfirmationStatus;
   3657    let mode = Services.dns.currentTrrMode;
   3658    if (
   3659      (mode == Ci.nsIDNSService.MODE_TRRFIRST ||
   3660        mode == Ci.nsIDNSService.MODE_TRRONLY) &&
   3661      lazy.gParentalControlsService?.parentalControlsEnabled
   3662    ) {
   3663      errReason = Services.dns.getTRRSkipReasonName(
   3664        Ci.nsITRRSkipReason.TRR_PARENTAL_CONTROL
   3665      );
   3666    } else if (confirmationStatus != Cr.NS_OK) {
   3667      errReason = ChromeUtils.getXPCOMErrorName(confirmationStatus);
   3668    } else {
   3669      errReason = Services.dns.getTRRSkipReasonName(
   3670        Services.dns.lastConfirmationSkipReason
   3671      );
   3672    }
   3673    let statusLabel = computeStatus();
   3674    // setStatus will format and set the statusLabel asynchronously.
   3675    setStatus(statusLabel, { reason: errReason });
   3676    dohResolver.hidden = statusLabel == "preferences-doh-status-disabled";
   3677 
   3678    let statusLearnMore = document.getElementById("dohStatusLearnMore");
   3679    statusLearnMore.hidden = statusLabel != "preferences-doh-status-not-active";
   3680 
   3681    // No need to set the resolver name since we're not going to show it.
   3682    if (statusLabel == "preferences-doh-status-disabled") {
   3683      return;
   3684    }
   3685 
   3686    function nameOrDomain() {
   3687      for (let resolver of DoHConfigController.currentConfig.providerList) {
   3688        if (resolver.uri == trrURI) {
   3689          return resolver.UIName || hostname || trrURI;
   3690        }
   3691      }
   3692 
   3693      // Also check if this is a steering provider.
   3694      for (let resolver of DoHConfigController.currentConfig.providerSteering
   3695        .providerList) {
   3696        if (resolver.uri == trrURI) {
   3697          steering.hidden = false;
   3698          return resolver.UIName || hostname || trrURI;
   3699        }
   3700      }
   3701 
   3702      return hostname;
   3703    }
   3704 
   3705    let resolverNameOrDomain = nameOrDomain();
   3706    document.l10n.setAttributes(dohResolver, "preferences-doh-resolver", {
   3707      name: resolverNameOrDomain,
   3708    });
   3709  },
   3710 
   3711  highlightDoHCategoryAndUpdateStatus() {
   3712    let value = Preferences.get("network.trr.mode").value;
   3713    let defaultOption = document.getElementById("dohOptionDefault");
   3714    let enabledOption = document.getElementById("dohOptionEnabled");
   3715    let strictOption = document.getElementById("dohOptionStrict");
   3716    let offOption = document.getElementById("dohOptionOff");
   3717    defaultOption.classList.remove("selected");
   3718    enabledOption.classList.remove("selected");
   3719    strictOption.classList.remove("selected");
   3720    offOption.classList.remove("selected");
   3721 
   3722    switch (value) {
   3723      case Ci.nsIDNSService.MODE_NATIVEONLY:
   3724        defaultOption.classList.add("selected");
   3725        break;
   3726      case Ci.nsIDNSService.MODE_TRRFIRST:
   3727        enabledOption.classList.add("selected");
   3728        break;
   3729      case Ci.nsIDNSService.MODE_TRRONLY:
   3730        strictOption.classList.add("selected");
   3731        break;
   3732      case Ci.nsIDNSService.MODE_TRROFF:
   3733        offOption.classList.add("selected");
   3734        break;
   3735      default:
   3736        // The pref is set to a random value.
   3737        // This shouldn't happen, but let's make sure off is selected.
   3738        offOption.classList.add("selected");
   3739        document.getElementById("dohCategoryRadioGroup").selectedIndex = 3;
   3740        break;
   3741    }
   3742 
   3743    // When the mode is set to 0 we need to clear the URI so
   3744    // doh-rollout can kick in.
   3745    if (value == Ci.nsIDNSService.MODE_NATIVEONLY) {
   3746      Services.prefs.clearUserPref("network.trr.uri");
   3747      Services.prefs.clearUserPref("doh-rollout.disable-heuristics");
   3748    }
   3749 
   3750    // Bug 1861285
   3751    // When the mode is set to 2 or 3, we need to check if network.trr.uri is a empty string.
   3752    // In this case, we need to update network.trr.uri to default to fallbackProviderURI.
   3753    // This occurs when the mode is previously set to 0 (Default Protection).
   3754    if (
   3755      value == Ci.nsIDNSService.MODE_TRRFIRST ||
   3756      value == Ci.nsIDNSService.MODE_TRRONLY
   3757    ) {
   3758      if (!Services.prefs.getStringPref("network.trr.uri")) {
   3759        Services.prefs.setStringPref(
   3760          "network.trr.uri",
   3761          DoHConfigController.currentConfig.fallbackProviderURI
   3762        );
   3763      }
   3764    }
   3765 
   3766    // Bug 1900672
   3767    // When the mode is set to 5, clear the pref to ensure that
   3768    // network.trr.uri is set to fallbackProviderURIwhen the mode is set to 2 or 3 afterwards
   3769    if (value == Ci.nsIDNSService.MODE_TRROFF) {
   3770      Services.prefs.clearUserPref("network.trr.uri");
   3771    }
   3772 
   3773    gPrivacyPane.updateDoHStatus();
   3774  },
   3775 
   3776  /**
   3777   * Init DoH corresponding prefs
   3778   */
   3779  initDoH() {
   3780    setEventListener("dohDefaultArrow", "command", this.toggleExpansion);
   3781    setEventListener("dohEnabledArrow", "command", this.toggleExpansion);
   3782    setEventListener("dohStrictArrow", "command", this.toggleExpansion);
   3783 
   3784    function modeButtonPressed(e) {
   3785      // Clicking the active mode again should not generate another event
   3786      if (
   3787        parseInt(e.target.value) == Preferences.get("network.trr.mode").value
   3788      ) {
   3789        return;
   3790      }
   3791      Glean.securityDohSettings.modeChangedButton.record({
   3792        value: e.target.id,
   3793      });
   3794    }
   3795 
   3796    setEventListener("dohDefaultRadio", "command", modeButtonPressed);
   3797    setEventListener("dohEnabledRadio", "command", modeButtonPressed);
   3798    setEventListener("dohStrictRadio", "command", modeButtonPressed);
   3799    setEventListener("dohOffRadio", "command", modeButtonPressed);
   3800 
   3801    this.populateDoHResolverList("dohEnabled");
   3802    this.populateDoHResolverList("dohStrict");
   3803 
   3804    Preferences.get("network.trr.uri").on("change", () => {
   3805      gPrivacyPane.updateDoHResolverList("dohEnabled");
   3806      gPrivacyPane.updateDoHResolverList("dohStrict");
   3807      gPrivacyPane.updateDoHStatus();
   3808    });
   3809 
   3810    // Update status box and hightlightling when the pref changes
   3811    Preferences.get("network.trr.mode").on(
   3812      "change",
   3813      gPrivacyPane.highlightDoHCategoryAndUpdateStatus
   3814    );
   3815    this.highlightDoHCategoryAndUpdateStatus();
   3816 
   3817    Services.obs.addObserver(this, "network:trr-uri-changed");
   3818    Services.obs.addObserver(this, "network:trr-mode-changed");
   3819    Services.obs.addObserver(this, "network:trr-confirmation");
   3820    let unload = () => {
   3821      Services.obs.removeObserver(this, "network:trr-uri-changed");
   3822      Services.obs.removeObserver(this, "network:trr-mode-changed");
   3823      Services.obs.removeObserver(this, "network:trr-confirmation");
   3824    };
   3825    window.addEventListener("unload", unload, { once: true });
   3826 
   3827    let uriPref = Services.prefs.getStringPref("network.trr.uri");
   3828    // If the value isn't one of the providers, we need to update the
   3829    // custom_uri pref to make sure the input box contains the correct URL.
   3830    if (uriPref && !this.dnsOverHttpsResolvers.some(e => e.uri == uriPref)) {
   3831      Services.prefs.setStringPref(
   3832        "network.trr.custom_uri",
   3833        Services.prefs.getStringPref("network.trr.uri")
   3834      );
   3835    }
   3836 
   3837    if (Services.prefs.prefIsLocked("network.trr.mode")) {
   3838      document.getElementById("dohCategoryRadioGroup").disabled = true;
   3839      Services.prefs.setStringPref("network.trr.custom_uri", uriPref);
   3840    }
   3841  },
   3842 
   3843  initWebAuthn() {
   3844    document.getElementById("openWindowsPasskeySettings").hidden =
   3845      !Services.prefs.getBoolPref(
   3846        "security.webauthn.show_ms_settings_link",
   3847        true
   3848      );
   3849  },
   3850 
   3851  /**
   3852   * Sets up the UI for the number of days of history to keep, and updates the
   3853   * label of the "Clear Now..." button.
   3854   */
   3855  init() {
   3856    initSettingGroup("nonTechnicalPrivacy");
   3857    initSettingGroup("nonTechnicalPrivacy2");
   3858    initSettingGroup("securityPrivacyStatus");
   3859    initSettingGroup("securityPrivacyWarnings");
   3860    initSettingGroup("httpsOnly");
   3861    initSettingGroup("browsingProtection");
   3862    initSettingGroup("cookiesAndSiteData");
   3863    initSettingGroup("cookiesAndSiteData2");
   3864    initSettingGroup("certificates");
   3865    initSettingGroup("ipprotection");
   3866    initSettingGroup("history");
   3867    initSettingGroup("history2");
   3868    initSettingGroup("permissions");
   3869    initSettingGroup("dnsOverHttps");
   3870    initSettingGroup("dnsOverHttpsAdvanced");
   3871    initSettingGroup("etpStatus");
   3872    initSettingGroup("etpBanner");
   3873    initSettingGroup("etpAdvanced");
   3874    initSettingGroup("etpReset");
   3875    initSettingGroup("etpCustomize");
   3876 
   3877    /* Initialize Content Blocking */
   3878    this.initContentBlocking();
   3879 
   3880    this.trackingProtectionReadPrefs();
   3881    this.fingerprintingProtectionReadPrefs();
   3882    this.networkCookieBehaviorReadPrefs();
   3883    this._initTrackingProtectionExtensionControl();
   3884    this._ensureTrackingProtectionExceptionListMigration();
   3885    this._initProfilesInfo();
   3886    OnionServicesAuthPreferences.init();
   3887    this._initSecurityLevel();
   3888 
   3889    Preferences.get("privacy.trackingprotection.enabled").on(
   3890      "change",
   3891      gPrivacyPane.trackingProtectionReadPrefs.bind(gPrivacyPane)
   3892    );
   3893    Preferences.get("privacy.trackingprotection.pbmode.enabled").on(
   3894      "change",
   3895      gPrivacyPane.trackingProtectionReadPrefs.bind(gPrivacyPane)
   3896    );
   3897 
   3898    // Watch all of the prefs that the new Cookies & Site Data UI depends on
   3899    Preferences.get("network.cookie.cookieBehavior").on(
   3900      "change",
   3901      gPrivacyPane.networkCookieBehaviorReadPrefs.bind(gPrivacyPane)
   3902    );
   3903    Preferences.get("browser.privatebrowsing.autostart").on(
   3904      "change",
   3905      gPrivacyPane.networkCookieBehaviorReadPrefs.bind(gPrivacyPane)
   3906    );
   3907    Preferences.get("privacy.firstparty.isolate").on(
   3908      "change",
   3909      gPrivacyPane.networkCookieBehaviorReadPrefs.bind(gPrivacyPane)
   3910    );
   3911 
   3912    Preferences.get("privacy.fingerprintingProtection").on(
   3913      "change",
   3914      gPrivacyPane.fingerprintingProtectionReadPrefs.bind(gPrivacyPane)
   3915    );
   3916    Preferences.get("privacy.fingerprintingProtection.pbmode").on(
   3917      "change",
   3918      gPrivacyPane.fingerprintingProtectionReadPrefs.bind(gPrivacyPane)
   3919    );
   3920 
   3921    setEventListener(
   3922      "trackingProtectionExceptions",
   3923      "command",
   3924      gPrivacyPane.showTrackingProtectionExceptions
   3925    );
   3926 
   3927    setEventListener(
   3928      "dohExceptionsButton",
   3929      "command",
   3930      gPrivacyPane.showDoHExceptions
   3931    );
   3932    setEventListener(
   3933      "passwordExceptions",
   3934      "command",
   3935      gPrivacyPane.showPasswordExceptions
   3936    );
   3937    setEventListener(
   3938      "useMasterPassword",
   3939      "command",
   3940      gPrivacyPane.updateMasterPasswordButton
   3941    );
   3942    setEventListener(
   3943      "changeMasterPassword",
   3944      "command",
   3945      gPrivacyPane.changeMasterPassword
   3946    );
   3947    setEventListener("showPasswords", "command", gPrivacyPane.showPasswords);
   3948 
   3949    this._pane = document.getElementById("panePrivacy");
   3950 
   3951    this._initPasswordGenerationUI();
   3952    this._initRelayIntegrationUI();
   3953    this._initMasterPasswordUI();
   3954    this._initOSAuthentication();
   3955 
   3956    // Init passwords settings group
   3957    initSettingGroup("passwords");
   3958 
   3959    this.initListenersForExtensionControllingPasswordManager();
   3960 
   3961    setSyncFromPrefListener("contentBlockingBlockCookiesCheckbox", () =>
   3962      this.readBlockCookies()
   3963    );
   3964    setSyncToPrefListener("contentBlockingBlockCookiesCheckbox", () =>
   3965      this.writeBlockCookies()
   3966    );
   3967    setSyncFromPrefListener("blockCookiesMenu", () =>
   3968      this.readBlockCookiesFrom()
   3969    );
   3970    setSyncToPrefListener("blockCookiesMenu", () =>
   3971      this.writeBlockCookiesFrom()
   3972    );
   3973 
   3974    setSyncFromPrefListener("savePasswords", () => this.readSavePasswords());
   3975 
   3976    this.initSiteDataControls();
   3977 
   3978    this.initCookieBannerHandling();
   3979 
   3980    this.initDataCollection();
   3981 
   3982    if (AppConstants.MOZ_DATA_REPORTING) {
   3983      this.updateSubmitHealthReportFromPref();
   3984      Preferences.get(PREF_UPLOAD_ENABLED).on(
   3985        "change",
   3986        gPrivacyPane.updateSubmitHealthReportFromPref
   3987      );
   3988      setEventListener(
   3989        "submitHealthReportBox",
   3990        "command",
   3991        gPrivacyPane.updateSubmitHealthReportToPref
   3992      );
   3993      if (AppConstants.MOZ_NORMANDY) {
   3994        this.initOptOutStudyCheckbox();
   3995      }
   3996      this.initAddonRecommendationsCheckbox();
   3997    }
   3998 
   3999    let signonBundle = document.getElementById("signonBundle");
   4000    appendSearchKeywords("showPasswords", [
   4001      signonBundle.getString("loginsDescriptionAll2"),
   4002    ]);
   4003 
   4004    setEventListener(
   4005      "contentBlockingBaselineExceptionsStrict",
   4006      "change",
   4007      gPrivacyPane.onBaselineCheckboxChange
   4008    );
   4009 
   4010    setEventListener(
   4011      "contentBlockingBaselineExceptionsCustom",
   4012      "change",
   4013      gPrivacyPane.onBaselineCheckboxChange
   4014    );
   4015 
   4016    setEventListener(
   4017      "contentBlockingConvenienceExceptionsStrict",
   4018      "change",
   4019      gPrivacyPane.maybeNotifyUserToReload
   4020    );
   4021 
   4022    setEventListener(
   4023      "contentBlockingConvenienceExceptionsCustom",
   4024      "change",
   4025      gPrivacyPane.maybeNotifyUserToReload
   4026    );
   4027 
   4028    this.initDoH();
   4029 
   4030    this.initWebAuthn();
   4031 
   4032    // Notify observers that the UI is now ready
   4033    Services.obs.notifyObservers(window, "privacy-pane-loaded");
   4034  },
   4035 
   4036  initSiteDataControls() {
   4037    SiteDataManager.updateSites();
   4038  },
   4039 
   4040  // CONTENT BLOCKING
   4041 
   4042  /**
   4043   * Initializes the content blocking section.
   4044   */
   4045  initContentBlocking() {
   4046    setEventListener(
   4047      "contentBlockingTrackingProtectionCheckbox",
   4048      "command",
   4049      this.trackingProtectionWritePrefs
   4050    );
   4051    setEventListener(
   4052      "contentBlockingTrackingProtectionCheckbox",
   4053      "command",
   4054      this._updateTrackingProtectionUI
   4055    );
   4056    setEventListener(
   4057      "contentBlockingCryptominersCheckbox",
   4058      "command",
   4059      this.updateCryptominingLists
   4060    );
   4061    setEventListener(
   4062      "contentBlockingFingerprintersCheckbox",
   4063      "command",
   4064      this.updateFingerprintingLists
   4065    );
   4066    setEventListener(
   4067      "trackingProtectionMenu",
   4068      "command",
   4069      this.trackingProtectionWritePrefs
   4070    );
   4071    setEventListener(
   4072      "contentBlockingFingerprintingProtectionCheckbox",
   4073      "command",
   4074      e => {
   4075        const extra = { checked: e.target.checked };
   4076        Glean.privacyUiFppClick.checkbox.record(extra);
   4077        this.fingerprintingProtectionWritePrefs();
   4078      }
   4079    );
   4080    setEventListener("fingerprintingProtectionMenu", "command", e => {
   4081      const extra = { value: e.target.value };
   4082      Glean.privacyUiFppClick.menu.record(extra);
   4083      this.fingerprintingProtectionWritePrefs();
   4084    });
   4085    setEventListener("standardArrow", "command", this.toggleExpansion);
   4086    setEventListener("strictArrow", "command", this.toggleExpansion);
   4087    setEventListener("customArrow", "command", this.toggleExpansion);
   4088 
   4089    Preferences.get("network.cookie.cookieBehavior").on(
   4090      "change",
   4091      gPrivacyPane.readBlockCookies.bind(gPrivacyPane)
   4092    );
   4093    Preferences.get("browser.contentblocking.category").on(
   4094      "change",
   4095      gPrivacyPane.highlightCBCategory
   4096    );
   4097 
   4098    // If any relevant content blocking pref changes, show a warning that the changes will
   4099    // not be implemented until they refresh their tabs.
   4100    for (let pref of CONTENT_BLOCKING_PREFS) {
   4101      // Skip registering change listeners for baseline and convenience allow list prefs.
   4102      // Their UI is handled in gPrivacyPane.onBaselineCheckboxChange to prevent redundant reload
   4103      // warnings when user toggles the checkboxes.
   4104      if (
   4105        pref == "privacy.trackingprotection.allow_list.baseline.enabled" ||
   4106        pref == "privacy.trackingprotection.allow_list.convenience.enabled"
   4107      ) {
   4108        continue;
   4109      }
   4110      Preferences.get(pref).on("change", gPrivacyPane.maybeNotifyUserToReload);
   4111      // If the value changes, run populateCategoryContents, since that change might have been
   4112      // triggered by a default value changing in the standard category.
   4113      Preferences.get(pref).on("change", gPrivacyPane.populateCategoryContents);
   4114    }
   4115    Preferences.get("urlclassifier.trackingTable").on(
   4116      "change",
   4117      gPrivacyPane.maybeNotifyUserToReload
   4118    );
   4119    for (let button of document.querySelectorAll(".reload-tabs-button")) {
   4120      button.addEventListener("command", gPrivacyPane.reloadAllOtherTabs);
   4121    }
   4122 
   4123    let cryptoMinersOption = document.getElementById(
   4124      "contentBlockingCryptominersOption"
   4125    );
   4126    let fingerprintersOption = document.getElementById(
   4127      "contentBlockingFingerprintersOption"
   4128    );
   4129    let trackingAndIsolateOption = document.querySelector(
   4130      "#blockCookiesMenu menuitem[value='trackers-plus-isolate']"
   4131    );
   4132    cryptoMinersOption.hidden = !Services.prefs.getBoolPref(
   4133      "browser.contentblocking.cryptomining.preferences.ui.enabled"
   4134    );
   4135    fingerprintersOption.hidden = !Services.prefs.getBoolPref(
   4136      "browser.contentblocking.fingerprinting.preferences.ui.enabled"
   4137    );
   4138    let updateTrackingAndIsolateOption = () => {
   4139      trackingAndIsolateOption.hidden =
   4140        !Services.prefs.getBoolPref(
   4141          "browser.contentblocking.reject-and-isolate-cookies.preferences.ui.enabled",
   4142          false
   4143        ) || gIsFirstPartyIsolated;
   4144    };
   4145    Preferences.get("privacy.firstparty.isolate").on(
   4146      "change",
   4147      updateTrackingAndIsolateOption
   4148    );
   4149    updateTrackingAndIsolateOption();
   4150 
   4151    Preferences.get("browser.contentblocking.features.strict").on(
   4152      "change",
   4153      this.populateCategoryContents
   4154    );
   4155    this.populateCategoryContents();
   4156    this.highlightCBCategory();
   4157    this.readBlockCookies();
   4158 
   4159    // Toggles the text "Cross-site and social media trackers" based on the
   4160    // social tracking pref. If the pref is false, the text reads
   4161    // "Cross-site trackers".
   4162    const STP_COOKIES_PREF = "privacy.socialtracking.block_cookies.enabled";
   4163    if (Services.prefs.getBoolPref(STP_COOKIES_PREF)) {
   4164      let contentBlockOptionSocialMedia = document.getElementById(
   4165        "blockCookiesSocialMedia"
   4166      );
   4167 
   4168      document.l10n.setAttributes(
   4169        contentBlockOptionSocialMedia,
   4170        "sitedata-option-block-cross-site-tracking-cookies"
   4171      );
   4172    }
   4173 
   4174    Preferences.get("privacy.resistFingerprinting").on(
   4175      "change",
   4176      setUpContentBlockingWarnings
   4177    );
   4178    Preferences.get("privacy.resistFingerprinting.pbmode").on(
   4179      "change",
   4180      setUpContentBlockingWarnings
   4181    );
   4182 
   4183    setUpContentBlockingWarnings();
   4184 
   4185    initTCPStandardSection();
   4186  },
   4187 
   4188  populateCategoryContents() {
   4189    for (let type of ["strict", "standard"]) {
   4190      let rulesArray = [];
   4191      let selector;
   4192      if (type == "strict") {
   4193        selector = "#contentBlockingOptionStrict";
   4194        rulesArray = Services.prefs
   4195          .getStringPref("browser.contentblocking.features.strict")
   4196          .split(",");
   4197        if (gIsFirstPartyIsolated) {
   4198          let idx = rulesArray.indexOf("cookieBehavior5");
   4199          if (idx != -1) {
   4200            rulesArray[idx] = "cookieBehavior4";
   4201          }
   4202        }
   4203      } else {
   4204        selector = "#contentBlockingOptionStandard";
   4205        // In standard show/hide UI items based on the default values of the relevant prefs.
   4206        let defaults = Services.prefs.getDefaultBranch("");
   4207 
   4208        let cookieBehavior = defaults.getIntPref(
   4209          "network.cookie.cookieBehavior"
   4210        );
   4211        switch (cookieBehavior) {
   4212          case Ci.nsICookieService.BEHAVIOR_ACCEPT:
   4213            rulesArray.push("cookieBehavior0");
   4214            break;
   4215          case Ci.nsICookieService.BEHAVIOR_REJECT_FOREIGN:
   4216            rulesArray.push("cookieBehavior1");
   4217            break;
   4218          case Ci.nsICookieService.BEHAVIOR_REJECT:
   4219            rulesArray.push("cookieBehavior2");
   4220            break;
   4221          case Ci.nsICookieService.BEHAVIOR_LIMIT_FOREIGN:
   4222            rulesArray.push("cookieBehavior3");
   4223            break;
   4224          case Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER:
   4225            rulesArray.push("cookieBehavior4");
   4226            break;
   4227          case BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN:
   4228            rulesArray.push(
   4229              gIsFirstPartyIsolated ? "cookieBehavior4" : "cookieBehavior5"
   4230            );
   4231            break;
   4232        }
   4233        let cookieBehaviorPBM = defaults.getIntPref(
   4234          "network.cookie.cookieBehavior.pbmode"
   4235        );
   4236        switch (cookieBehaviorPBM) {
   4237          case Ci.nsICookieService.BEHAVIOR_ACCEPT:
   4238            rulesArray.push("cookieBehaviorPBM0");
   4239            break;
   4240          case Ci.nsICookieService.BEHAVIOR_REJECT_FOREIGN:
   4241            rulesArray.push("cookieBehaviorPBM1");
   4242            break;
   4243          case Ci.nsICookieService.BEHAVIOR_REJECT:
   4244            rulesArray.push("cookieBehaviorPBM2");
   4245            break;
   4246          case Ci.nsICookieService.BEHAVIOR_LIMIT_FOREIGN:
   4247            rulesArray.push("cookieBehaviorPBM3");
   4248            break;
   4249          case Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER:
   4250            rulesArray.push("cookieBehaviorPBM4");
   4251            break;
   4252          case BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN:
   4253            rulesArray.push(
   4254              gIsFirstPartyIsolated
   4255                ? "cookieBehaviorPBM4"
   4256                : "cookieBehaviorPBM5"
   4257            );
   4258            break;
   4259        }
   4260        rulesArray.push(
   4261          defaults.getBoolPref(
   4262            "privacy.trackingprotection.cryptomining.enabled"
   4263          )
   4264            ? "cryptoTP"
   4265            : "-cryptoTP"
   4266        );
   4267        rulesArray.push(
   4268          defaults.getBoolPref(
   4269            "privacy.trackingprotection.fingerprinting.enabled"
   4270          )
   4271            ? "fp"
   4272            : "-fp"
   4273        );
   4274        rulesArray.push(
   4275          Services.prefs.getBoolPref(
   4276            "privacy.socialtracking.block_cookies.enabled"
   4277          )
   4278            ? "stp"
   4279            : "-stp"
   4280        );
   4281        rulesArray.push(
   4282          defaults.getBoolPref("privacy.trackingprotection.enabled")
   4283            ? "tp"
   4284            : "-tp"
   4285        );
   4286        rulesArray.push(
   4287          defaults.getBoolPref("privacy.trackingprotection.pbmode.enabled")
   4288            ? "tpPrivate"
   4289            : "-tpPrivate"
   4290        );
   4291      }
   4292 
   4293      // Hide all cookie options first, until we learn which one should be showing.
   4294      document.querySelector(selector + " .all-cookies-option").hidden = true;
   4295      document.querySelector(selector + " .unvisited-cookies-option").hidden =
   4296        true;
   4297      document.querySelector(selector + " .cross-site-cookies-option").hidden =
   4298        true;
   4299      document.querySelector(
   4300        selector + " .third-party-tracking-cookies-option"
   4301      ).hidden = true;
   4302      document.querySelector(
   4303        selector + " .all-third-party-cookies-private-windows-option"
   4304      ).hidden = true;
   4305      document.querySelector(
   4306        selector + " .all-third-party-cookies-option"
   4307      ).hidden = true;
   4308      document.querySelector(selector + " .social-media-option").hidden = true;
   4309 
   4310      for (let item of rulesArray) {
   4311        // Note "cookieBehavior0", will result in no UI changes, so is not listed here.
   4312        switch (item) {
   4313          case "tp":
   4314            document.querySelector(selector + " .trackers-option").hidden =
   4315              false;
   4316            break;
   4317          case "-tp":
   4318            document.querySelector(selector + " .trackers-option").hidden =
   4319              true;
   4320            break;
   4321          case "tpPrivate":
   4322            document.querySelector(selector + " .pb-trackers-option").hidden =
   4323              false;
   4324            break;
   4325          case "-tpPrivate":
   4326            document.querySelector(selector + " .pb-trackers-option").hidden =
   4327              true;
   4328            break;
   4329          case "fp":
   4330            document.querySelector(
   4331              selector + " .fingerprinters-option"
   4332            ).hidden = false;
   4333            break;
   4334          case "-fp":
   4335            document.querySelector(
   4336              selector + " .fingerprinters-option"
   4337            ).hidden = true;
   4338            break;
   4339          case "cryptoTP":
   4340            document.querySelector(selector + " .cryptominers-option").hidden =
   4341              false;
   4342            break;
   4343          case "-cryptoTP":
   4344            document.querySelector(selector + " .cryptominers-option").hidden =
   4345              true;
   4346            break;
   4347          case "stp": {
   4348            // Store social tracking cookies pref
   4349            const STP_COOKIES_PREF =
   4350              "privacy.socialtracking.block_cookies.enabled";
   4351 
   4352            if (Services.prefs.getBoolPref(STP_COOKIES_PREF)) {
   4353              document.querySelector(
   4354                selector + " .social-media-option"
   4355              ).hidden = false;
   4356            }
   4357            break;
   4358          }
   4359          case "-stp":
   4360            // Store social tracking cookies pref
   4361            document.querySelector(selector + " .social-media-option").hidden =
   4362              true;
   4363            break;
   4364          case "cookieBehavior1":
   4365            document.querySelector(
   4366              selector + " .all-third-party-cookies-option"
   4367            ).hidden = false;
   4368            break;
   4369          case "cookieBehavior2":
   4370            document.querySelector(selector + " .all-cookies-option").hidden =
   4371              false;
   4372            break;
   4373          case "cookieBehavior3":
   4374            document.querySelector(
   4375              selector + " .unvisited-cookies-option"
   4376            ).hidden = false;
   4377            break;
   4378          case "cookieBehavior4":
   4379            document.querySelector(
   4380              selector + " .third-party-tracking-cookies-option"
   4381            ).hidden = false;
   4382            break;
   4383          case "cookieBehavior5":
   4384            document.querySelector(
   4385              selector + " .cross-site-cookies-option"
   4386            ).hidden = false;
   4387            break;
   4388          case "cookieBehaviorPBM5":
   4389            // We only need to show the cookie option for private windows if the
   4390            // cookieBehaviors are different between regular windows and private
   4391            // windows.
   4392            if (!rulesArray.includes("cookieBehavior5")) {
   4393              document.querySelector(
   4394                selector + " .all-third-party-cookies-private-windows-option"
   4395              ).hidden = false;
   4396            }
   4397            break;
   4398        }
   4399      }
   4400      // Hide the "tracking protection in private browsing" list item
   4401      // if the "tracking protection enabled in all windows" list item is showing.
   4402      if (!document.querySelector(selector + " .trackers-option").hidden) {
   4403        document.querySelector(selector + " .pb-trackers-option").hidden = true;
   4404      }
   4405    }
   4406  },
   4407 
   4408  highlightCBCategory() {
   4409    let value = Preferences.get("browser.contentblocking.category").value;
   4410    let standardEl = document.getElementById("contentBlockingOptionStandard");
   4411    let strictEl = document.getElementById("contentBlockingOptionStrict");
   4412    let customEl = document.getElementById("contentBlockingOptionCustom");
   4413    standardEl.classList.remove("selected");
   4414    strictEl.classList.remove("selected");
   4415    customEl.classList.remove("selected");
   4416 
   4417    switch (value) {
   4418      case "strict":
   4419        strictEl.classList.add("selected");
   4420        break;
   4421      case "custom":
   4422        customEl.classList.add("selected");
   4423        break;
   4424      case "standard":
   4425      /* fall through */
   4426      default:
   4427        standardEl.classList.add("selected");
   4428        break;
   4429    }
   4430  },
   4431 
   4432  updateCryptominingLists() {
   4433    let listPrefs = [
   4434      "urlclassifier.features.cryptomining.blacklistTables",
   4435      "urlclassifier.features.cryptomining.whitelistTables",
   4436    ];
   4437 
   4438    let listValue = listPrefs
   4439      .map(l => Services.prefs.getStringPref(l))
   4440      .join(",");
   4441    listManager.forceUpdates(listValue);
   4442  },
   4443 
   4444  updateFingerprintingLists() {
   4445    let listPrefs = [
   4446      "urlclassifier.features.fingerprinting.blacklistTables",
   4447      "urlclassifier.features.fingerprinting.whitelistTables",
   4448    ];
   4449 
   4450    let listValue = listPrefs
   4451      .map(l => Services.prefs.getStringPref(l))
   4452      .join(",");
   4453    listManager.forceUpdates(listValue);
   4454  },
   4455 
   4456  // TRACKING PROTECTION MODE
   4457 
   4458  /**
   4459   * Selects the right item of the Tracking Protection menulist and checkbox.
   4460   */
   4461  trackingProtectionReadPrefs() {
   4462    let enabledPref = Preferences.get("privacy.trackingprotection.enabled");
   4463    let pbmPref = Preferences.get("privacy.trackingprotection.pbmode.enabled");
   4464    let tpMenu = document.getElementById("trackingProtectionMenu");
   4465    let tpCheckbox = document.getElementById(
   4466      "contentBlockingTrackingProtectionCheckbox"
   4467    );
   4468 
   4469    this._updateTrackingProtectionUI();
   4470 
   4471    // Global enable takes precedence over enabled in Private Browsing.
   4472    if (enabledPref.value) {
   4473      tpMenu.value = "always";
   4474      tpCheckbox.checked = true;
   4475    } else if (pbmPref.value) {
   4476      tpMenu.value = "private";
   4477      tpCheckbox.checked = true;
   4478    } else {
   4479      tpMenu.value = "never";
   4480      tpCheckbox.checked = false;
   4481    }
   4482  },
   4483 
   4484  /**
   4485   * Selects the right item of the Fingerprinting Protection menulist and
   4486   * checkbox.
   4487   */
   4488  fingerprintingProtectionReadPrefs() {
   4489    let enabledPref = Preferences.get("privacy.fingerprintingProtection");
   4490    let pbmPref = Preferences.get("privacy.fingerprintingProtection.pbmode");
   4491    let fppMenu = document.getElementById("fingerprintingProtectionMenu");
   4492    let fppCheckbox = document.getElementById(
   4493      "contentBlockingFingerprintingProtectionCheckbox"
   4494    );
   4495 
   4496    // Global enable takes precedence over enabled in Private Browsing.
   4497    if (enabledPref.value) {
   4498      fppMenu.value = "always";
   4499      fppCheckbox.checked = true;
   4500    } else if (pbmPref.value) {
   4501      fppMenu.value = "private";
   4502      fppCheckbox.checked = true;
   4503    } else {
   4504      fppMenu.value = "never";
   4505      fppCheckbox.checked = false;
   4506    }
   4507    fppMenu.disabled = !fppCheckbox.checked || enabledPref.locked;
   4508    fppCheckbox.disabled = enabledPref.locked;
   4509  },
   4510 
   4511  /**
   4512   * Selects the right items of the new Cookies & Site Data UI.
   4513   */
   4514  networkCookieBehaviorReadPrefs() {
   4515    let behavior = Services.cookies.getCookieBehavior(false);
   4516    let blockCookiesMenu = document.getElementById("blockCookiesMenu");
   4517    let blockCookies = behavior != Ci.nsICookieService.BEHAVIOR_ACCEPT;
   4518    let cookieBehaviorLocked = Services.prefs.prefIsLocked(
   4519      "network.cookie.cookieBehavior"
   4520    );
   4521    let blockCookiesControlsDisabled = !blockCookies || cookieBehaviorLocked;
   4522    blockCookiesMenu.disabled = blockCookiesControlsDisabled;
   4523 
   4524    switch (behavior) {
   4525      case Ci.nsICookieService.BEHAVIOR_ACCEPT:
   4526        break;
   4527      case Ci.nsICookieService.BEHAVIOR_REJECT_FOREIGN:
   4528        blockCookiesMenu.value = "all-third-parties";
   4529        break;
   4530      case Ci.nsICookieService.BEHAVIOR_REJECT:
   4531        blockCookiesMenu.value = "always";
   4532        break;
   4533      case Ci.nsICookieService.BEHAVIOR_LIMIT_FOREIGN:
   4534        blockCookiesMenu.value = "unvisited";
   4535        break;
   4536      case Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER:
   4537        blockCookiesMenu.value = "trackers";
   4538        break;
   4539      case BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN:
   4540        blockCookiesMenu.value = "trackers-plus-isolate";
   4541        break;
   4542    }
   4543  },
   4544 
   4545  /**
   4546   * Sets the pref values based on the selected item of the radiogroup.
   4547   */
   4548  trackingProtectionWritePrefs() {
   4549    let enabledPref = Preferences.get("privacy.trackingprotection.enabled");
   4550    let pbmPref = Preferences.get("privacy.trackingprotection.pbmode.enabled");
   4551    let stpPref = Preferences.get(
   4552      "privacy.trackingprotection.socialtracking.enabled"
   4553    );
   4554    let stpCookiePref = Preferences.get(
   4555      "privacy.socialtracking.block_cookies.enabled"
   4556    );
   4557    // Currently, we don't expose the email tracking protection setting on our
   4558    // privacy UI. Instead, we use the existing tracking protection checkbox to
   4559    // control the email tracking protection.
   4560    let emailTPPref = Preferences.get(
   4561      "privacy.trackingprotection.emailtracking.enabled"
   4562    );
   4563    let emailTPPBMPref = Preferences.get(
   4564      "privacy.trackingprotection.emailtracking.pbmode.enabled"
   4565    );
   4566    let tpMenu = document.getElementById("trackingProtectionMenu");
   4567    let tpCheckbox = document.getElementById(
   4568      "contentBlockingTrackingProtectionCheckbox"
   4569    );
   4570 
   4571    let value;
   4572    if (tpCheckbox.checked) {
   4573      if (tpMenu.value == "never") {
   4574        tpMenu.value = "private";
   4575      }
   4576      value = tpMenu.value;
   4577    } else {
   4578      tpMenu.value = "never";
   4579      value = "never";
   4580    }
   4581 
   4582    switch (value) {
   4583      case "always":
   4584        enabledPref.value = true;
   4585        pbmPref.value = true;
   4586        emailTPPref.value = true;
   4587        emailTPPBMPref.value = true;
   4588        if (stpCookiePref.value) {
   4589          stpPref.value = true;
   4590        }
   4591        break;
   4592      case "private":
   4593        enabledPref.value = false;
   4594        pbmPref.value = true;
   4595        emailTPPref.value = false;
   4596        emailTPPBMPref.value = true;
   4597        if (stpCookiePref.value) {
   4598          stpPref.value = false;
   4599        }
   4600        break;
   4601      case "never":
   4602        enabledPref.value = false;
   4603        pbmPref.value = false;
   4604        emailTPPref.value = false;
   4605        emailTPPBMPref.value = false;
   4606        if (stpCookiePref.value) {
   4607          stpPref.value = false;
   4608        }
   4609        break;
   4610    }
   4611  },
   4612 
   4613  fingerprintingProtectionWritePrefs() {
   4614    let enabledPref = Preferences.get("privacy.fingerprintingProtection");
   4615    let pbmPref = Preferences.get("privacy.fingerprintingProtection.pbmode");
   4616    let fppMenu = document.getElementById("fingerprintingProtectionMenu");
   4617    let fppCheckbox = document.getElementById(
   4618      "contentBlockingFingerprintingProtectionCheckbox"
   4619    );
   4620 
   4621    let value;
   4622    if (fppCheckbox.checked) {
   4623      if (fppMenu.value == "never") {
   4624        fppMenu.value = "private";
   4625      }
   4626      value = fppMenu.value;
   4627    } else {
   4628      fppMenu.value = "never";
   4629      value = "never";
   4630    }
   4631 
   4632    fppMenu.disabled = !fppCheckbox.checked;
   4633 
   4634    switch (value) {
   4635      case "always":
   4636        enabledPref.value = true;
   4637        pbmPref.value = true;
   4638        break;
   4639      case "private":
   4640        enabledPref.value = false;
   4641        pbmPref.value = true;
   4642        break;
   4643      case "never":
   4644        enabledPref.value = false;
   4645        pbmPref.value = false;
   4646        break;
   4647    }
   4648  },
   4649 
   4650  toggleExpansion(e) {
   4651    let carat = e.target;
   4652    carat.classList.toggle("up");
   4653    carat.closest(".privacy-detailedoption").classList.toggle("expanded");
   4654    carat.setAttribute(
   4655      "aria-expanded",
   4656      carat.getAttribute("aria-expanded") === "false"
   4657    );
   4658  },
   4659 
   4660  // CLEAR PRIVATE DATA
   4661 
   4662  /*
   4663   * Preferences:
   4664   *
   4665   * privacy.sanitize.sanitizeOnShutdown
   4666   * - true if the user's private data is cleared on startup according to the
   4667   *   Clear Private Data settings, false otherwise
   4668   */
   4669 
   4670  /**
   4671   * Displays the Clear Private Data settings dialog.
   4672   */
   4673  showClearPrivateDataSettings() {
   4674    let dialogFile = useOldClearHistoryDialog
   4675      ? "chrome://browser/content/preferences/dialogs/sanitize.xhtml"
   4676      : "chrome://browser/content/sanitize_v2.xhtml";
   4677 
   4678    gSubDialog.open(
   4679      dialogFile,
   4680      {
   4681        features: "resizable=no",
   4682      },
   4683      {
   4684        mode: "clearOnShutdown",
   4685      }
   4686    );
   4687  },
   4688 
   4689  /**
   4690   * Displays a dialog from which individual parts of private data may be
   4691   * cleared.
   4692   */
   4693  clearPrivateDataNow(aClearEverything) {
   4694    var ts = Preferences.get("privacy.sanitize.timeSpan");
   4695    var timeSpanOrig = ts.value;
   4696 
   4697    if (aClearEverything) {
   4698      ts.value = 0;
   4699    }
   4700 
   4701    // Bug 1856418 We intend to remove the old dialog box
   4702    let dialogFile = useOldClearHistoryDialog
   4703      ? "chrome://browser/content/sanitize.xhtml"
   4704      : "chrome://browser/content/sanitize_v2.xhtml";
   4705 
   4706    gSubDialog.open(dialogFile, {
   4707      features: "resizable=no",
   4708      closingCallback: () => {
   4709        // reset the timeSpan pref
   4710        if (aClearEverything) {
   4711          ts.value = timeSpanOrig;
   4712        }
   4713 
   4714        Services.obs.notifyObservers(null, "clear-private-data");
   4715      },
   4716    });
   4717  },
   4718 
   4719  /*
   4720   Checks if the user set cleaning prefs that do not belong to DeleteOnClose
   4721   */
   4722  _isCustomCleaningPrefPresent() {
   4723    let sanitizeOnShutdownPrefsArray = useOldClearHistoryDialog
   4724      ? SANITIZE_ON_SHUTDOWN_PREFS_ONLY
   4725      : SANITIZE_ON_SHUTDOWN_PREFS_ONLY_V2;
   4726 
   4727    return sanitizeOnShutdownPrefsArray.some(
   4728      pref => Preferences.get(pref).value
   4729    );
   4730  },
   4731 
   4732  /**
   4733   * Displays fine-grained, per-site preferences for tracking protection.
   4734   */
   4735  showTrackingProtectionExceptions() {
   4736    let params = {
   4737      permissionType: "trackingprotection",
   4738      disableETPVisible: true,
   4739      prefilledHost: "",
   4740      hideStatusColumn: true,
   4741    };
   4742    gSubDialog.open(
   4743      "chrome://browser/content/preferences/dialogs/permissions.xhtml",
   4744      undefined,
   4745      params
   4746    );
   4747  },
   4748 
   4749  // COOKIES AND SITE DATA
   4750 
   4751  /*
   4752   * Preferences:
   4753   *
   4754   * network.cookie.cookieBehavior
   4755   * - determines how the browser should handle cookies:
   4756   *     0   means enable all cookies
   4757   *     1   means reject all third party cookies
   4758   *     2   means disable all cookies
   4759   *     3   means reject third party cookies unless at least one is already set for the eTLD
   4760   *     4   means reject all trackers
   4761   *     5   means reject all trackers and partition third-party cookies
   4762   *         see netwerk/cookie/src/CookieService.cpp for details
   4763   */
   4764 
   4765  /**
   4766   * Reads the network.cookie.cookieBehavior preference value and
   4767   * enables/disables the "blockCookiesMenu" menulist accordingly.
   4768   */
   4769  readBlockCookies() {
   4770    let bcControl = document.getElementById("blockCookiesMenu");
   4771    bcControl.disabled =
   4772      Services.cookies.getCookieBehavior(false) ==
   4773      Ci.nsICookieService.BEHAVIOR_ACCEPT;
   4774  },
   4775 
   4776  /**
   4777   * Updates the "accept third party cookies" menu based on whether the
   4778   * "contentBlockingBlockCookiesCheckbox" checkbox is checked.
   4779   */
   4780  writeBlockCookies() {
   4781    let block = document.getElementById("contentBlockingBlockCookiesCheckbox");
   4782    let blockCookiesMenu = document.getElementById("blockCookiesMenu");
   4783 
   4784    if (block.checked) {
   4785      // Automatically select 'third-party trackers' as the default.
   4786      blockCookiesMenu.selectedIndex = 0;
   4787      return this.writeBlockCookiesFrom();
   4788    }
   4789    return Ci.nsICookieService.BEHAVIOR_ACCEPT;
   4790  },
   4791 
   4792  readBlockCookiesFrom() {
   4793    switch (Services.cookies.getCookieBehavior(false)) {
   4794      case Ci.nsICookieService.BEHAVIOR_REJECT_FOREIGN:
   4795        return "all-third-parties";
   4796      case Ci.nsICookieService.BEHAVIOR_REJECT:
   4797        return "always";
   4798      case Ci.nsICookieService.BEHAVIOR_LIMIT_FOREIGN:
   4799        return "unvisited";
   4800      case Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER:
   4801        return "trackers";
   4802      case BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN:
   4803        return "trackers-plus-isolate";
   4804      default:
   4805        return undefined;
   4806    }
   4807  },
   4808 
   4809  writeBlockCookiesFrom() {
   4810    let block = document.getElementById("blockCookiesMenu").selectedItem;
   4811    switch (block.value) {
   4812      case "trackers":
   4813        return Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER;
   4814      case "unvisited":
   4815        return Ci.nsICookieService.BEHAVIOR_LIMIT_FOREIGN;
   4816      case "always":
   4817        return Ci.nsICookieService.BEHAVIOR_REJECT;
   4818      case "all-third-parties":
   4819        return Ci.nsICookieService.BEHAVIOR_REJECT_FOREIGN;
   4820      case "trackers-plus-isolate":
   4821        return Ci.nsICookieService
   4822          .BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN;
   4823      default:
   4824        return undefined;
   4825    }
   4826  },
   4827 
   4828  /**
   4829   * Discard the browsers of all tabs in all windows. Pinned tabs, as
   4830   * well as tabs for which discarding doesn't succeed (e.g. selected
   4831   * tabs, tabs with beforeunload listeners), are reloaded.
   4832   */
   4833  reloadAllOtherTabs() {
   4834    let ourTab = window.browsingContext.topChromeWindow.gBrowser.selectedTab;
   4835    BrowserWindowTracker.orderedWindows.forEach(win => {
   4836      let otherGBrowser = win.gBrowser;
   4837      for (let tab of otherGBrowser.tabs) {
   4838        if (tab == ourTab) {
   4839          // Don't reload our preferences tab.
   4840          continue;
   4841        }
   4842 
   4843        if (tab.pinned || tab.selected) {
   4844          otherGBrowser.reloadTab(tab);
   4845        } else {
   4846          otherGBrowser.discardBrowser(tab);
   4847        }
   4848      }
   4849    });
   4850 
   4851    for (let notification of document.querySelectorAll(".reload-tabs")) {
   4852      notification.hidden = true;
   4853    }
   4854 
   4855    Preferences.getSetting("reloadTabsHint").value = false;
   4856  },
   4857 
   4858  /**
   4859   * If there are more tabs than just the preferences tab, show a warning to the user that
   4860   * they need to reload their tabs to apply the setting.
   4861   */
   4862  maybeNotifyUserToReload() {
   4863    let shouldShow = false;
   4864    if (window.BrowserWindowTracker.orderedWindows.length > 1) {
   4865      shouldShow = true;
   4866    } else {
   4867      let tabbrowser = window.browsingContext.topChromeWindow.gBrowser;
   4868      if (tabbrowser.tabs.length > 1) {
   4869        shouldShow = true;
   4870      }
   4871    }
   4872    if (shouldShow) {
   4873      for (let notification of document.querySelectorAll(".reload-tabs")) {
   4874        notification.hidden = false;
   4875      }
   4876    }
   4877 
   4878    Preferences.getSetting("reloadTabsHint").value = true;
   4879  },
   4880 
   4881  /**
   4882   * Displays per-site preferences for HTTPS-Only Mode exceptions.
   4883   */
   4884  showHttpsOnlyModeExceptions() {
   4885    var params = {
   4886      blockVisible: false,
   4887      sessionVisible: true,
   4888      allowVisible: false,
   4889      prefilledHost: "",
   4890      permissionType: "https-only-load-insecure",
   4891      forcedHTTP: true,
   4892    };
   4893    gSubDialog.open(
   4894      "chrome://browser/content/preferences/dialogs/permissions.xhtml",
   4895      undefined,
   4896      params
   4897    );
   4898  },
   4899 
   4900  showDoHExceptions() {
   4901    gSubDialog.open(
   4902      "chrome://browser/content/preferences/dialogs/dohExceptions.xhtml",
   4903      undefined
   4904    );
   4905  },
   4906 
   4907  /**
   4908   * Initializes the cookie banner handling subgroup on the privacy pane.
   4909   *
   4910   * This UI is shown if the "cookiebanners.ui.desktop.enabled" pref is true.
   4911   *
   4912   * The cookie banner handling checkbox reflects the cookie banner feature
   4913   * state. It is enabled when the service enabled via the
   4914   * cookiebanners.service.mode pref. If detection-only mode is enabled the
   4915   * checkbox is unchecked, since in this mode no banners are handled. It is
   4916   * only used for detection for banners which means we may prompt the user to
   4917   * enable the feature via other UI surfaces such as the onboarding doorhanger.
   4918   *
   4919   * If the user checks the checkbox, the pref value is set to
   4920   * nsICookieBannerService.MODE_REJECT_OR_ACCEPT.
   4921   *
   4922   * If the user unchecks the checkbox, the mode pref value is set to
   4923   * nsICookieBannerService.MODE_DISABLED.
   4924   *
   4925   * Advanced users can choose other int-valued modes via about:config.
   4926   */
   4927  initCookieBannerHandling() {
   4928    setSyncFromPrefListener("handleCookieBanners", () =>
   4929      this.readCookieBannerMode()
   4930    );
   4931    setSyncToPrefListener("handleCookieBanners", () =>
   4932      this.writeCookieBannerMode()
   4933    );
   4934 
   4935    let preference = Preferences.get("cookiebanners.ui.desktop.enabled");
   4936    preference.on("change", () => this.updateCookieBannerHandlingVisibility());
   4937 
   4938    this.updateCookieBannerHandlingVisibility();
   4939  },
   4940 
   4941  /**
   4942   * Reads the cookiebanners.service.mode.privateBrowsing pref,
   4943   * interpreting the multiple modes as a true/false value
   4944   */
   4945  readCookieBannerMode() {
   4946    return (
   4947      Preferences.get("cookiebanners.service.mode.privateBrowsing").value !=
   4948      Ci.nsICookieBannerService.MODE_DISABLED
   4949    );
   4950  },
   4951 
   4952  /**
   4953   * Translates user clicks on the cookie banner handling checkbox to the
   4954   * corresponding integer-valued cookie banner mode preference.
   4955   */
   4956  writeCookieBannerMode() {
   4957    let checkbox = document.getElementById("handleCookieBanners");
   4958    if (!checkbox.checked) {
   4959      /* because we removed UI control for the non-PBM pref, disabling it here
   4960         provides an off-ramp for profiles where it had previously been enabled from the UI */
   4961      Services.prefs.setIntPref(
   4962        "cookiebanners.service.mode",
   4963        Ci.nsICookieBannerService.MODE_DISABLED
   4964      );
   4965      return Ci.nsICookieBannerService.MODE_DISABLED;
   4966    }
   4967    return Ci.nsICookieBannerService.MODE_REJECT;
   4968  },
   4969 
   4970  /**
   4971   * Shows or hides the cookie banner handling section based on the value of
   4972   * the "cookiebanners.ui.desktop.enabled" pref.
   4973   */
   4974  updateCookieBannerHandlingVisibility() {
   4975    let groupbox = document.getElementById("cookieBannerHandlingGroup");
   4976    let isEnabled = Preferences.get("cookiebanners.ui.desktop.enabled").value;
   4977 
   4978    // Because the top-level pane showing code unsets the hidden attribute, we
   4979    // manually hide the section when cookie banner handling is preffed off.
   4980    if (isEnabled) {
   4981      groupbox.removeAttribute("style");
   4982    } else {
   4983      groupbox.setAttribute("style", "display: none !important");
   4984    }
   4985  },
   4986 
   4987  // GEOLOCATION
   4988 
   4989  /**
   4990   * Displays the location exceptions dialog where specific site location
   4991   * preferences can be set.
   4992   */
   4993  showLocationExceptions() {
   4994    let params = { permissionType: "geo" };
   4995 
   4996    gSubDialog.open(
   4997      "chrome://browser/content/preferences/dialogs/sitePermissions.xhtml",
   4998      { features: "resizable=yes" },
   4999      params
   5000    );
   5001  },
   5002 
   5003  // LOCALHOST
   5004 
   5005  /**
   5006   * Displays the localhost exceptions dialog where specific site localhost
   5007   * preferences can be set.
   5008   */
   5009  showLocalHostExceptions() {
   5010    let params = { permissionType: "localhost" };
   5011 
   5012    gSubDialog.open(
   5013      "chrome://browser/content/preferences/dialogs/sitePermissions.xhtml",
   5014      { features: "resizable=yes" },
   5015      params
   5016    );
   5017  },
   5018 
   5019  // LOCAL-NETWORK
   5020 
   5021  /**
   5022   * Displays the local network exceptions dialog where specific site local network
   5023   * preferences can be set.
   5024   */
   5025  showLocalNetworkExceptions() {
   5026    let params = { permissionType: "local-network" };
   5027 
   5028    gSubDialog.open(
   5029      "chrome://browser/content/preferences/dialogs/sitePermissions.xhtml",
   5030      { features: "resizable=yes" },
   5031      params
   5032    );
   5033  },
   5034 
   5035  // XR
   5036 
   5037  /**
   5038   * Displays the XR exceptions dialog where specific site XR
   5039   * preferences can be set.
   5040   */
   5041  showXRExceptions() {
   5042    let params = { permissionType: "xr" };
   5043 
   5044    gSubDialog.open(
   5045      "chrome://browser/content/preferences/dialogs/sitePermissions.xhtml",
   5046      { features: "resizable=yes" },
   5047      params
   5048    );
   5049  },
   5050 
   5051  // CAMERA
   5052 
   5053  /**
   5054   * Displays the camera exceptions dialog where specific site camera
   5055   * preferences can be set.
   5056   */
   5057  showCameraExceptions() {
   5058    let params = { permissionType: "camera" };
   5059 
   5060    gSubDialog.open(
   5061      "chrome://browser/content/preferences/dialogs/sitePermissions.xhtml",
   5062      { features: "resizable=yes" },
   5063      params
   5064    );
   5065  },
   5066 
   5067  // MICROPHONE
   5068 
   5069  /**
   5070   * Displays the microphone exceptions dialog where specific site microphone
   5071   * preferences can be set.
   5072   */
   5073  showMicrophoneExceptions() {
   5074    let params = { permissionType: "microphone" };
   5075 
   5076    gSubDialog.open(
   5077      "chrome://browser/content/preferences/dialogs/sitePermissions.xhtml",
   5078      { features: "resizable=yes" },
   5079      params
   5080    );
   5081  },
   5082 
   5083  // SPEAKER
   5084 
   5085  /**
   5086   * Displays the speaker exceptions dialog where specific site speaker
   5087   * preferences can be set.
   5088   */
   5089  showSpeakerExceptions() {
   5090    let params = { permissionType: "speaker" };
   5091 
   5092    gSubDialog.open(
   5093      "chrome://browser/content/preferences/dialogs/sitePermissions.xhtml",
   5094      { features: "resizable=yes" },
   5095      params
   5096    );
   5097  },
   5098 
   5099  // NOTIFICATIONS
   5100 
   5101  /**
   5102   * Displays the notifications exceptions dialog where specific site notification
   5103   * preferences can be set.
   5104   */
   5105  showNotificationExceptions() {
   5106    let params = { permissionType: "desktop-notification" };
   5107 
   5108    gSubDialog.open(
   5109      "chrome://browser/content/preferences/dialogs/sitePermissions.xhtml",
   5110      { features: "resizable=yes" },
   5111      params
   5112    );
   5113  },
   5114 
   5115  // MEDIA
   5116 
   5117  showAutoplayMediaExceptions() {
   5118    var params = { permissionType: "autoplay-media" };
   5119 
   5120    gSubDialog.open(
   5121      "chrome://browser/content/preferences/dialogs/sitePermissions.xhtml",
   5122      { features: "resizable=yes" },
   5123      params
   5124    );
   5125  },
   5126 
   5127  // POP-UPS
   5128 
   5129  /**
   5130   * Displays the popup exceptions dialog where specific site popup preferences
   5131   * can be set.
   5132   */
   5133  showPopupExceptions() {
   5134    var params = {
   5135      blockVisible: false,
   5136      sessionVisible: false,
   5137      allowVisible: true,
   5138      prefilledHost: "",
   5139      permissionType: "popup",
   5140    };
   5141 
   5142    gSubDialog.open(
   5143      "chrome://browser/content/preferences/dialogs/permissions.xhtml",
   5144      { features: "resizable=yes" },
   5145      params
   5146    );
   5147  },
   5148 
   5149  // UTILITY FUNCTIONS
   5150 
   5151  /**
   5152   * Utility function to enable/disable the button specified by aButtonID based
   5153   * on the value of the Boolean preference specified by aPreferenceID.
   5154   */
   5155  updateButtons(aButtonID, aPreferenceID) {
   5156    var button = document.getElementById(aButtonID);
   5157    var preference = Preferences.get(aPreferenceID);
   5158    button.disabled = !preference.value || preference.locked;
   5159    return undefined;
   5160  },
   5161 
   5162  // BEGIN UI CODE
   5163 
   5164  /*
   5165   * Preferences:
   5166   *
   5167   * dom.disable_open_during_load
   5168   * - true if popups are blocked by default, false otherwise
   5169   */
   5170 
   5171  // POP-UPS
   5172 
   5173  /**
   5174   * Displays a dialog in which the user can view and modify the list of sites
   5175   * where passwords are never saved.
   5176   */
   5177  showPasswordExceptions() {
   5178    var params = {
   5179      blockVisible: true,
   5180      sessionVisible: false,
   5181      allowVisible: false,
   5182      hideStatusColumn: true,
   5183      prefilledHost: "",
   5184      permissionType: "login-saving",
   5185    };
   5186 
   5187    gSubDialog.open(
   5188      "chrome://browser/content/preferences/dialogs/permissions.xhtml",
   5189      undefined,
   5190      params
   5191    );
   5192  },
   5193 
   5194  /**
   5195   * Initializes master password UI: the "use master password" checkbox, selects
   5196   * the master password button to show, and enables/disables it as necessary.
   5197   * The master password is controlled by various bits of NSS functionality, so
   5198   * the UI for it can't be controlled by the normal preference bindings.
   5199   */
   5200  _initMasterPasswordUI() {
   5201    var noMP = !LoginHelper.isPrimaryPasswordSet();
   5202 
   5203    var button = document.getElementById("changeMasterPassword");
   5204    button.disabled = noMP;
   5205 
   5206    var checkbox = document.getElementById("useMasterPassword");
   5207    checkbox.checked = !noMP;
   5208    checkbox.disabled =
   5209      (noMP && !Services.policies.isAllowed("createMasterPassword")) ||
   5210      (!noMP && !Services.policies.isAllowed("removeMasterPassword"));
   5211  },
   5212 
   5213  /**
   5214   * Enables/disables the master password button depending on the state of the
   5215   * "use master password" checkbox, and prompts for master password removal if
   5216   * one is set.
   5217   */
   5218  async updateMasterPasswordButton() {
   5219    var checkbox = document.getElementById("useMasterPassword");
   5220    var button = document.getElementById("changeMasterPassword");
   5221    button.disabled = !checkbox.checked;
   5222 
   5223    // unchecking the checkbox should try to immediately remove the master
   5224    // password, because it's impossible to non-destructively remove the master
   5225    // password used to encrypt all the passwords without providing it (by
   5226    // design), and it would be extremely odd to pop up that dialog when the
   5227    // user closes the prefwindow and saves his settings
   5228    if (!checkbox.checked) {
   5229      await this._removeMasterPassword();
   5230    } else {
   5231      await this.changeMasterPassword();
   5232    }
   5233 
   5234    this._initMasterPasswordUI();
   5235  },
   5236 
   5237  /**
   5238   * Displays the "remove master password" dialog to allow the user to remove
   5239   * the current master password.  When the dialog is dismissed, master password
   5240   * UI is automatically updated.
   5241   */
   5242  async _removeMasterPassword() {
   5243    var secmodDB = Cc["@mozilla.org/security/pkcs11moduledb;1"].getService(
   5244      Ci.nsIPKCS11ModuleDB
   5245    );
   5246    if (secmodDB.isFIPSEnabled) {
   5247      let title = document.getElementById("fips-title").textContent;
   5248      let desc = document.getElementById("fips-desc").textContent;
   5249      Services.prompt.alert(window, title, desc);
   5250      this._initMasterPasswordUI();
   5251    } else {
   5252      gSubDialog.open("chrome://mozapps/content/preferences/removemp.xhtml", {
   5253        closingCallback: () => {
   5254          Services.obs.notifyObservers(null, "passwordmgr-primary-pw-changed");
   5255          this._initMasterPasswordUI();
   5256        },
   5257      });
   5258    }
   5259  },
   5260 
   5261  /**
   5262   * Displays a dialog in which the primary password may be changed.
   5263   */
   5264  async changeMasterPassword() {
   5265    // Require OS authentication before the user can set a Primary Password.
   5266    // OS reauthenticate functionality is not available on Linux yet (bug 1527745)
   5267    if (!LoginHelper.isPrimaryPasswordSet() && LoginHelper.getOSAuthEnabled()) {
   5268      // Uses primary-password-os-auth-dialog-message-win and
   5269      // primary-password-os-auth-dialog-message-macosx via concatenation:
   5270      let messageId =
   5271        "primary-password-os-auth-dialog-message-" + AppConstants.platform;
   5272      let [messageText, captionText] = await document.l10n.formatMessages([
   5273        {
   5274          id: messageId,
   5275        },
   5276        {
   5277          id: "master-password-os-auth-dialog-caption",
   5278        },
   5279      ]);
   5280      let win = Services.wm.getMostRecentBrowserWindow();
   5281 
   5282      // Note on Glean collection: because OSKeyStore.ensureLoggedIn() is not wrapped in
   5283      // verifyOSAuth(), it will be documenting "success" for unsupported platforms
   5284      // and won't record "fail_error", only "fail_user_canceled"
   5285      let loggedIn = await OSKeyStore.ensureLoggedIn(
   5286        messageText.value,
   5287        captionText.value,
   5288        win,
   5289        false
   5290      );
   5291 
   5292      const result = loggedIn.authenticated ? "success" : "fail_user_canceled";
   5293      Glean.pwmgr.promptShownOsReauth.record({
   5294        trigger: "toggle_pref_primary_password",
   5295        result,
   5296      });
   5297 
   5298      if (!loggedIn.authenticated) {
   5299        return;
   5300      }
   5301    }
   5302 
   5303    gSubDialog.open("chrome://mozapps/content/preferences/changemp.xhtml", {
   5304      features: "resizable=no",
   5305      closingCallback: () => {
   5306        Services.obs.notifyObservers(null, "passwordmgr-primary-pw-changed");
   5307        this._initMasterPasswordUI();
   5308      },
   5309    });
   5310  },
   5311 
   5312  /**
   5313   * Set up the initial state for the password generation UI.
   5314   * It will be hidden unless the .available pref is true
   5315   */
   5316  _initPasswordGenerationUI() {
   5317    // we don't watch the .available pref for runtime changes
   5318    let prefValue = Services.prefs.getBoolPref(
   5319      PREF_PASSWORD_GENERATION_AVAILABLE,
   5320      false
   5321    );
   5322    document.getElementById("generatePasswordsBox").hidden = !prefValue;
   5323  },
   5324 
   5325  toggleRelayIntegration() {
   5326    const checkbox = document.getElementById("relayIntegration");
   5327    if (checkbox.checked) {
   5328      FirefoxRelay.markAsAvailable();
   5329      Glean.relayIntegration.enabledPrefChange.record();
   5330    } else {
   5331      FirefoxRelay.markAsDisabled();
   5332      Glean.relayIntegration.disabledPrefChange.record();
   5333    }
   5334  },
   5335 
   5336  _updateRelayIntegrationUI() {
   5337    // In Base Browser, we always hide the integration checkbox since
   5338    // FirefoxRelay should remain disabled.
   5339    // See tor-browser#43109 and tor-browser#42814.
   5340    // NOTE: FirefoxRelay.isAvailable will be true whenever
   5341    // FirefoxRelay.isDisabled is true.
   5342    document.getElementById("relayIntegrationBox").hidden = true;
   5343    document.getElementById("relayIntegration").checked =
   5344      FirefoxRelay.isAvailable && !FirefoxRelay.isDisabled;
   5345  },
   5346 
   5347  _initRelayIntegrationUI() {
   5348    document
   5349      .getElementById("relayIntegrationLearnMoreLink")
   5350      .setAttribute("href", FirefoxRelay.learnMoreUrl);
   5351 
   5352    setEventListener(
   5353      "relayIntegration",
   5354      "command",
   5355      gPrivacyPane.toggleRelayIntegration.bind(gPrivacyPane)
   5356    );
   5357    Preferences.get("signon.firefoxRelay.feature").on(
   5358      "change",
   5359      gPrivacyPane._updateRelayIntegrationUI.bind(gPrivacyPane)
   5360    );
   5361 
   5362    this._updateRelayIntegrationUI();
   5363  },
   5364 
   5365  async _toggleOSAuth() {
   5366    let osReauthCheckbox = document.getElementById("osReauthCheckbox");
   5367 
   5368    const messageText = await lazy.AboutLoginsL10n.formatValue(
   5369      "about-logins-os-auth-dialog-message"
   5370    );
   5371    const captionText = await lazy.AboutLoginsL10n.formatValue(
   5372      "about-logins-os-auth-dialog-caption"
   5373    );
   5374    let win =
   5375      osReauthCheckbox.ownerGlobal.docShell.chromeEventHandler.ownerGlobal;
   5376 
   5377    // Calling OSKeyStore.ensureLoggedIn() instead of LoginHelper.verifyOSAuth()
   5378    // since we want to authenticate user each time this setting is changed.
   5379 
   5380    // Note on Glean collection: because OSKeyStore.ensureLoggedIn() is not wrapped in
   5381    // verifyOSAuth(), it will be documenting "success" for unsupported platforms
   5382    // and won't record "fail_error", only "fail_user_canceled"
   5383    let isAuthorized = (
   5384      await OSKeyStore.ensureLoggedIn(messageText, captionText, win, false)
   5385    ).authenticated;
   5386 
   5387    Glean.pwmgr.promptShownOsReauth.record({
   5388      trigger: "toggle_pref_os_auth",
   5389      result: isAuthorized ? "success" : "fail_user_canceled",
   5390    });
   5391 
   5392    if (!isAuthorized) {
   5393      osReauthCheckbox.checked = !osReauthCheckbox.checked;
   5394      return;
   5395    }
   5396 
   5397    // If osReauthCheckbox is checked enable osauth.
   5398    LoginHelper.setOSAuthEnabled(osReauthCheckbox.checked);
   5399 
   5400    Glean.pwmgr.requireOsReauthToggle.record({
   5401      toggle_state: osReauthCheckbox.checked,
   5402    });
   5403  },
   5404 
   5405  _initOSAuthentication() {
   5406    let osReauthCheckbox = document.getElementById("osReauthCheckbox");
   5407    if (
   5408      !OSKeyStore.canReauth() ||
   5409      Services.prefs.getBoolPref("security.nocertdb", false)
   5410    ) {
   5411      osReauthCheckbox.hidden = true;
   5412      return;
   5413    }
   5414 
   5415    osReauthCheckbox.toggleAttribute("checked", LoginHelper.getOSAuthEnabled());
   5416 
   5417    setEventListener(
   5418      "osReauthCheckbox",
   5419      "command",
   5420      gPrivacyPane._toggleOSAuth.bind(gPrivacyPane)
   5421    );
   5422  },
   5423 
   5424  /**
   5425   * Shows the sites where the user has saved passwords and the associated login
   5426   * information.
   5427   */
   5428  showPasswords() {
   5429    let loginManager = window.windowGlobalChild.getActor("LoginManager");
   5430    loginManager.sendAsyncMessage("PasswordManager:OpenPreferences", {
   5431      entryPoint: "Preferences",
   5432    });
   5433  },
   5434 
   5435  /**
   5436   * Enables/disables dependent controls related to password saving
   5437   * When password saving is not enabled, we need to also disable the password generation checkbox
   5438   * The Exceptions button is used to configure sites where passwords are never saved.
   5439   */
   5440  readSavePasswords() {
   5441    var prefValue = Preferences.get("signon.rememberSignons").value;
   5442    document.getElementById("passwordExceptions").disabled = !prefValue;
   5443    document.getElementById("generatePasswords").disabled = !prefValue;
   5444    document.getElementById("passwordAutofillCheckbox").disabled = !prefValue;
   5445    document.getElementById("relayIntegration").disabled =
   5446      !prefValue || Services.prefs.prefIsLocked("signon.firefoxRelay.feature");
   5447    // don't override pref value in UI
   5448    return undefined;
   5449  },
   5450 
   5451  /**
   5452   * Initalizes pref listeners for the password manager.
   5453   *
   5454   * This ensures that the user is always notified if an extension is controlling the password manager.
   5455   */
   5456  initListenersForExtensionControllingPasswordManager() {
   5457    this._passwordManagerCheckbox = document.getElementById("savePasswords");
   5458    this._disableExtensionButton = document.getElementById(
   5459      "disablePasswordManagerExtension"
   5460    );
   5461 
   5462    this._disableExtensionButton.addEventListener(
   5463      "command",
   5464      makeDisableControllingExtension(
   5465        PREF_SETTING_TYPE,
   5466        PASSWORD_MANAGER_PREF_ID
   5467      )
   5468    );
   5469 
   5470    initListenersForPrefChange(
   5471      PREF_SETTING_TYPE,
   5472      PASSWORD_MANAGER_PREF_ID,
   5473      this._passwordManagerCheckbox
   5474    );
   5475  },
   5476 
   5477  /**
   5478   * Displays the exceptions lists for add-on installation warnings.
   5479   */
   5480  showAddonExceptions() {
   5481    var params = this._addonParams;
   5482 
   5483    gSubDialog.open(
   5484      "chrome://browser/content/preferences/dialogs/permissions.xhtml",
   5485      undefined,
   5486      params
   5487    );
   5488  },
   5489 
   5490  /**
   5491   * Parameters for the add-on install permissions dialog.
   5492   */
   5493  _addonParams: {
   5494    blockVisible: false,
   5495    sessionVisible: false,
   5496    allowVisible: true,
   5497    prefilledHost: "",
   5498    permissionType: "install",
   5499  },
   5500 
   5501  /**
   5502   * Displays the user's certificates and associated options.
   5503   */
   5504  showCertificates() {
   5505    gSubDialog.open("chrome://pippki/content/certManager.xhtml");
   5506  },
   5507 
   5508  /**
   5509   * Displays a dialog from which the user can manage his security devices.
   5510   */
   5511  showSecurityDevices() {
   5512    gSubDialog.open("chrome://pippki/content/device_manager.xhtml");
   5513  },
   5514 
   5515  initDataCollection() {
   5516    if (
   5517      !AppConstants.MOZ_DATA_REPORTING &&
   5518      !Services.prefs.getBoolPref(
   5519        "browser.privacySegmentation.preferences.show"
   5520      )
   5521    ) {
   5522      // Nothing to control in the data collection section, remove it.
   5523      document.getElementById("dataCollectionCategory").remove();
   5524      document.getElementById("dataCollectionGroup").remove();
   5525      return;
   5526    }
   5527 
   5528    this._setupLearnMoreLink(
   5529      "toolkit.datacollection.infoURL",
   5530      "dataCollectionPrivacyNotice"
   5531    );
   5532    this.initPrivacySegmentation();
   5533  },
   5534 
   5535  initPrivacySegmentation() {
   5536    // Section visibility
   5537    let section = document.getElementById("privacySegmentationSection");
   5538    let updatePrivacySegmentationSectionVisibilityState = () => {
   5539      section.hidden = !Services.prefs.getBoolPref(
   5540        "browser.privacySegmentation.preferences.show"
   5541      );
   5542    };
   5543 
   5544    Services.prefs.addObserver(
   5545      "browser.privacySegmentation.preferences.show",
   5546      updatePrivacySegmentationSectionVisibilityState
   5547    );
   5548 
   5549    window.addEventListener("unload", () => {
   5550      Services.prefs.removeObserver(
   5551        "browser.privacySegmentation.preferences.show",
   5552        updatePrivacySegmentationSectionVisibilityState
   5553      );
   5554    });
   5555 
   5556    updatePrivacySegmentationSectionVisibilityState();
   5557  },
   5558 
   5559  /**
   5560   * Set up or hide the Learn More links for various data collection options
   5561   */
   5562  _setupLearnMoreLink(pref, element) {
   5563    // set up the Learn More link with the correct URL
   5564    let url = Services.urlFormatter.formatURLPref(pref);
   5565    let el = document.getElementById(element);
   5566 
   5567    if (url) {
   5568      el.setAttribute("href", url);
   5569    } else {
   5570      el.hidden = true;
   5571    }
   5572  },
   5573 
   5574  /**
   5575   * Update the health report service checkbox from preference.
   5576   */
   5577  updateSubmitHealthReportFromPref() {
   5578    let checkbox = document.getElementById("submitHealthReportBox");
   5579    let telemetryContainer = document.getElementById("telemetry-container");
   5580 
   5581    // Telemetry is only sending data if MOZ_TELEMETRY_REPORTING is defined.
   5582    // We still want to display the preferences panel if that's not the case, but
   5583    // we want it to be disabled and unchecked.
   5584    if (
   5585      Services.prefs.prefIsLocked(PREF_UPLOAD_ENABLED) ||
   5586      !AppConstants.MOZ_TELEMETRY_REPORTING
   5587    ) {
   5588      checkbox.setAttribute("disabled", "true");
   5589      return;
   5590    }
   5591 
   5592    checkbox.checked =
   5593      Services.prefs.getBoolPref(PREF_UPLOAD_ENABLED) &&
   5594      AppConstants.MOZ_TELEMETRY_REPORTING;
   5595    telemetryContainer.hidden = checkbox.checked;
   5596  },
   5597 
   5598  /**
   5599   * Update the health report preference with state from checkbox.
   5600   */
   5601  updateSubmitHealthReportToPref() {
   5602    let checkbox = document.getElementById("submitHealthReportBox");
   5603    let telemetryContainer = document.getElementById("telemetry-container");
   5604 
   5605    Services.prefs.setBoolPref(PREF_UPLOAD_ENABLED, checkbox.checked);
   5606    telemetryContainer.hidden = checkbox.checked;
   5607  },
   5608 
   5609  /**
   5610   * Initialize the opt-out-study preference checkbox into about:preferences and
   5611   * handles events coming from the UI for it.
   5612   */
   5613  initOptOutStudyCheckbox() {
   5614    // The checkbox should be disabled if any of the below are true. This
   5615    // prevents the user from changing the value in the box.
   5616    //
   5617    // * the policy forbids shield
   5618    // * Normandy is disabled
   5619    //
   5620    // The checkbox should match the value of the preference only if all of
   5621    // these are true. Otherwise, the checkbox should remain unchecked. This
   5622    // is because in these situations, Shield studies are always disabled, and
   5623    // so showing a checkbox would be confusing.
   5624    //
   5625    // * the policy allows Shield
   5626    // * Normandy is enabled
   5627 
   5628    const allowedByPolicy = Services.policies.isAllowed("Shield");
   5629    const checkbox = document.getElementById("optOutStudiesEnabled");
   5630 
   5631    function updateCheckbox() {
   5632      if (
   5633        allowedByPolicy &&
   5634        Services.prefs.getBoolPref(PREF_UPLOAD_ENABLED, false) &&
   5635        Services.prefs.getBoolPref(PREF_NORMANDY_ENABLED, false)
   5636      ) {
   5637        checkbox.toggleAttribute(
   5638          "checked",
   5639          Services.prefs.getBoolPref(PREF_OPT_OUT_STUDIES_ENABLED, false)
   5640        );
   5641        checkbox.setAttribute("preference", PREF_OPT_OUT_STUDIES_ENABLED);
   5642        checkbox.removeAttribute("disabled");
   5643      } else {
   5644        checkbox.removeAttribute("preference");
   5645        checkbox.removeAttribute("checked");
   5646        checkbox.setAttribute("disabled", "true");
   5647      }
   5648    }
   5649    Preferences.get(PREF_UPLOAD_ENABLED).on("change", updateCheckbox);
   5650    updateCheckbox();
   5651  },
   5652 
   5653  initAddonRecommendationsCheckbox() {
   5654    // Setup the checkbox.
   5655    dataCollectionCheckboxHandler({
   5656      checkbox: document.getElementById("addonRecommendationEnabled"),
   5657      pref: PREF_ADDON_RECOMMENDATIONS_ENABLED,
   5658    });
   5659  },
   5660 
   5661  observe(aSubject, aTopic) {
   5662    switch (aTopic) {
   5663      case "network:trr-uri-changed":
   5664      case "network:trr-mode-changed":
   5665      case "network:trr-confirmation":
   5666        gPrivacyPane.updateDoHStatus();
   5667        break;
   5668    }
   5669  },
   5670 
   5671  _initProfilesInfo() {
   5672    setEventListener(
   5673      "dataCollectionViewProfiles",
   5674      "click",
   5675      gMainPane.manageProfiles
   5676    );
   5677 
   5678    let listener = () => gPrivacyPane.updateProfilesPrivacyInfo();
   5679    SelectableProfileService.on("enableChanged", listener);
   5680    window.addEventListener("unload", () =>
   5681      SelectableProfileService.off("enableChanged", listener)
   5682    );
   5683    this.updateProfilesPrivacyInfo();
   5684  },
   5685 
   5686  updateProfilesPrivacyInfo() {
   5687    let profilesInfo = document.getElementById("preferences-privacy-profiles");
   5688    profilesInfo.hidden = !SelectableProfileService.isEnabled;
   5689  },
   5690 
   5691  /**
   5692   * Handles change events on baseline and convenience exception checkboxes for content blocking preferences.
   5693   *
   5694   * - For baseline checkboxes: If the user attempts to uncheck, shows a confirmation dialog.
   5695   *   If confirmed, disables the baseline allow list preference.
   5696   * - For other cases: Toggles the checkbox and updates the corresponding preference.
   5697   *
   5698   * @param {Event} event - The change event triggered by the checkbox.
   5699   */
   5700  async onBaselineCheckboxChange(event) {
   5701    // Ignore events from nested checkboxes
   5702    if (event.target.slot === "nested") {
   5703      return;
   5704    }
   5705 
   5706    // If the user is checking the checkbox, don't show a confirmation prompt.
   5707    if (event.target.checked) {
   5708      this.maybeNotifyUserToReload();
   5709      return;
   5710    }
   5711 
   5712    const confirmed = await this._confirmBaselineAllowListDisable();
   5713 
   5714    if (confirmed) {
   5715      // User confirmed, set the checkbox to false.
   5716      event.target.checked = false;
   5717      this.maybeNotifyUserToReload();
   5718    } else {
   5719      // User cancelled, set the checkbox and the baseline pref to true.
   5720      event.target.checked = true;
   5721      Services.prefs.setBoolPref(
   5722        "privacy.trackingprotection.allow_list.baseline.enabled",
   5723        true
   5724      );
   5725    }
   5726  },
   5727 
   5728  async onBaselineAllowListSettingChange(value, setting) {
   5729    if (value) {
   5730      this.maybeNotifyUserToReload();
   5731      return;
   5732    }
   5733 
   5734    const confirmed = await this._confirmBaselineAllowListDisable();
   5735    if (confirmed) {
   5736      this.maybeNotifyUserToReload();
   5737      return;
   5738    }
   5739 
   5740    setting.value = true;
   5741  },
   5742 
   5743  async _confirmBaselineAllowListDisable() {
   5744    let [title, body, okButtonText, cancelButtonText] =
   5745      await document.l10n.formatValues([
   5746        { id: "content-blocking-baseline-uncheck-warning-dialog-title" },
   5747        { id: "content-blocking-baseline-uncheck-warning-dialog-body" },
   5748        { id: "content-blocking-baseline-uncheck-warning-dialog-ok-button" },
   5749        {
   5750          id: "content-blocking-baseline-uncheck-warning-dialog-cancel-button",
   5751        },
   5752      ]);
   5753 
   5754    let flags =
   5755      Services.prompt.BUTTON_TITLE_IS_STRING * Services.prompt.BUTTON_POS_1 +
   5756      Services.prompt.BUTTON_TITLE_IS_STRING * Services.prompt.BUTTON_POS_0 +
   5757      Services.prompt.BUTTON_POS_0_DEFAULT;
   5758 
   5759    const result = await Services.prompt.asyncConfirmEx(
   5760      window.browsingContext,
   5761      Services.prompt.MODAL_TYPE_CONTENT,
   5762      title,
   5763      body,
   5764      flags,
   5765      cancelButtonText,
   5766      okButtonText,
   5767      null,
   5768      null,
   5769      false,
   5770      {
   5771        useTitle: true,
   5772      }
   5773    );
   5774 
   5775    const propertyBag = result.QueryInterface(Ci.nsIPropertyBag2);
   5776    return propertyBag.get("buttonNumClicked") == 1;
   5777  },
   5778 };