tor-browser

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

Policies.sys.mjs (107759B)


      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 { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
      6 
      7 import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";
      8 
      9 const lazy = {};
     10 
     11 XPCOMUtils.defineLazyServiceGetters(lazy, {
     12  gCertDB: ["@mozilla.org/security/x509certdb;1", Ci.nsIX509CertDB],
     13  gExternalProtocolService: [
     14    "@mozilla.org/uriloader/external-protocol-service;1",
     15    Ci.nsIExternalProtocolService,
     16  ],
     17  gHandlerService: [
     18    "@mozilla.org/uriloader/handler-service;1",
     19    Ci.nsIHandlerService,
     20  ],
     21  gMIMEService: ["@mozilla.org/mime;1", Ci.nsIMIMEService],
     22 });
     23 
     24 ChromeUtils.defineESModuleGetters(lazy, {
     25  AddonManager: "resource://gre/modules/AddonManager.sys.mjs",
     26  BookmarksPolicies: "resource:///modules/policies/BookmarksPolicies.sys.mjs",
     27  CustomizableUI:
     28    "moz-src:///browser/components/customizableui/CustomizableUI.sys.mjs",
     29  FileUtils: "resource://gre/modules/FileUtils.sys.mjs",
     30  ProxyPolicies: "resource:///modules/policies/ProxyPolicies.sys.mjs",
     31  QuickSuggest: "moz-src:///browser/components/urlbar/QuickSuggest.sys.mjs",
     32  WebsiteFilter: "resource:///modules/policies/WebsiteFilter.sys.mjs",
     33 });
     34 
     35 const PREF_LOGLEVEL = "browser.policies.loglevel";
     36 const BROWSER_DOCUMENT_URL = AppConstants.BROWSER_CHROME_URL;
     37 const ABOUT_CONTRACT = "@mozilla.org/network/protocol/about;1?what=";
     38 
     39 const isXpcshell = Services.env.exists("XPCSHELL_TEST_PROFILE_DIR");
     40 
     41 ChromeUtils.defineLazyGetter(lazy, "log", () => {
     42  let { ConsoleAPI } = ChromeUtils.importESModule(
     43    "resource://gre/modules/Console.sys.mjs"
     44  );
     45  return new ConsoleAPI({
     46    prefix: "Policies",
     47    // tip: set maxLogLevel to "debug" and use log.debug() to create detailed
     48    // messages during development. See LOG_LEVELS in Console.sys.mjs for details.
     49    maxLogLevel: "error",
     50    maxLogLevelPref: PREF_LOGLEVEL,
     51  });
     52 });
     53 
     54 /*
     55 * ============================
     56 * = POLICIES IMPLEMENTATIONS =
     57 * ============================
     58 *
     59 * The Policies object below is where the implementation for each policy
     60 * happens. An object for each policy should be defined, containing
     61 * callback functions that will be called by the engine.
     62 *
     63 * See the _callbacks object in EnterprisePoliciesParent.sys.mjs for the list of
     64 * possible callbacks and an explanation of each.
     65 *
     66 * Each callback will be called with two parameters:
     67 * - manager
     68 *   This is the EnterprisePoliciesManager singleton object from
     69 *   EnterprisePoliciesParent.sys.mjs
     70 *
     71 * - param
     72 *   The parameter defined for this policy in policies-schema.json.
     73 *   It will be different for each policy. It could be a boolean,
     74 *   a string, an array or a complex object. All parameters have
     75 *   been validated according to the schema, and no unknown
     76 *   properties will be present on them.
     77 *
     78 * The callbacks will be bound to their parent policy object.
     79 */
     80 export var Policies = {
     81  // Used for cleaning up policies.
     82  // Use the same timing that you used for setting up the policy.
     83  _cleanup: {
     84    onBeforeAddons() {
     85      if (Cu.isInAutomation || isXpcshell) {
     86        console.log("_cleanup from onBeforeAddons");
     87        clearBlockedAboutPages();
     88      }
     89    },
     90    onProfileAfterChange() {
     91      if (Cu.isInAutomation || isXpcshell) {
     92        console.log("_cleanup from onProfileAfterChange");
     93      }
     94    },
     95    onBeforeUIStartup() {
     96      if (Cu.isInAutomation || isXpcshell) {
     97        console.log("_cleanup from onBeforeUIStartup");
     98      }
     99    },
    100    onAllWindowsRestored() {
    101      if (Cu.isInAutomation || isXpcshell) {
    102        console.log("_cleanup from onAllWindowsRestored");
    103      }
    104    },
    105  },
    106 
    107  "3rdparty": {
    108    onBeforeAddons(manager, param) {
    109      manager.setExtensionPolicies(param.Extensions);
    110    },
    111  },
    112 
    113  AllowedDomainsForApps: {
    114    onBeforeAddons(manager, param) {
    115      Services.obs.addObserver(function (subject) {
    116        let channel = subject.QueryInterface(Ci.nsIHttpChannel);
    117        if (channel.URI.host.endsWith(".google.com")) {
    118          channel.setRequestHeader("X-GoogApps-Allowed-Domains", param, true);
    119        }
    120      }, "http-on-modify-request");
    121    },
    122  },
    123 
    124  AllowFileSelectionDialogs: {
    125    onBeforeUIStartup(manager, param) {
    126      if (!param) {
    127        setAndLockPref("widget.disable_file_pickers", true);
    128        setAndLockPref("browser.download.useDownloadDir", true);
    129        manager.disallowFeature("filepickers");
    130      }
    131    },
    132  },
    133 
    134  AppAutoUpdate: {
    135    onBeforeUIStartup(manager, param) {
    136      // Logic feels a bit reversed here, but it's correct. If AppAutoUpdate is
    137      // true, we disallow turning off auto updating, and visa versa.
    138      if (param) {
    139        manager.disallowFeature("app-auto-updates-off");
    140      } else {
    141        manager.disallowFeature("app-auto-updates-on");
    142      }
    143    },
    144  },
    145 
    146  AppUpdatePin: {
    147    validate(param) {
    148      // This is the version when pinning was introduced. Attempting to set a
    149      // pin before this will not work, because Balrog's pinning table will
    150      // never have the necessary entry.
    151      const earliestPinMajorVersion = 102;
    152      const earliestPinMinorVersion = 0;
    153 
    154      let pinParts = param.split(".");
    155 
    156      if (pinParts.length < 2) {
    157        lazy.log.error("AppUpdatePin has too few dots.");
    158        return false;
    159      }
    160      if (pinParts.length > 3) {
    161        lazy.log.error("AppUpdatePin has too many dots.");
    162        return false;
    163      }
    164 
    165      const trailingPinPart = pinParts.pop();
    166      if (trailingPinPart != "") {
    167        lazy.log.error("AppUpdatePin does not end with a trailing dot.");
    168        return false;
    169      }
    170 
    171      const pinMajorVersionStr = pinParts.shift();
    172      if (!pinMajorVersionStr.length) {
    173        lazy.log.error("AppUpdatePin's major version is empty.");
    174        return false;
    175      }
    176      if (!/^\d+$/.test(pinMajorVersionStr)) {
    177        lazy.log.error(
    178          "AppUpdatePin's major version contains a non-numeric character."
    179        );
    180        return false;
    181      }
    182      if (/^0/.test(pinMajorVersionStr)) {
    183        lazy.log.error("AppUpdatePin's major version contains a leading 0.");
    184        return false;
    185      }
    186      const pinMajorVersionInt = parseInt(pinMajorVersionStr, 10);
    187      if (isNaN(pinMajorVersionInt)) {
    188        lazy.log.error(
    189          "AppUpdatePin's major version could not be parsed to an integer."
    190        );
    191        return false;
    192      }
    193      if (pinMajorVersionInt < earliestPinMajorVersion) {
    194        lazy.log.error(
    195          `AppUpdatePin must not be earlier than '${earliestPinMajorVersion}.${earliestPinMinorVersion}.'.`
    196        );
    197        return false;
    198      }
    199 
    200      if (pinParts.length) {
    201        const pinMinorVersionStr = pinParts.shift();
    202        if (!pinMinorVersionStr.length) {
    203          lazy.log.error("AppUpdatePin's minor version is empty.");
    204          return false;
    205        }
    206        if (!/^\d+$/.test(pinMinorVersionStr)) {
    207          lazy.log.error(
    208            "AppUpdatePin's minor version contains a non-numeric character."
    209          );
    210          return false;
    211        }
    212        if (/^0\d/.test(pinMinorVersionStr)) {
    213          lazy.log.error("AppUpdatePin's minor version contains a leading 0.");
    214          return false;
    215        }
    216        const pinMinorVersionInt = parseInt(pinMinorVersionStr, 10);
    217        if (isNaN(pinMinorVersionInt)) {
    218          lazy.log.error(
    219            "AppUpdatePin's minor version could not be parsed to an integer."
    220          );
    221          return false;
    222        }
    223        if (
    224          pinMajorVersionInt == earliestPinMajorVersion &&
    225          pinMinorVersionInt < earliestPinMinorVersion
    226        ) {
    227          lazy.log.error(
    228            `AppUpdatePin must not be earlier than '${earliestPinMajorVersion}.${earliestPinMinorVersion}.'.`
    229          );
    230          return false;
    231        }
    232      }
    233 
    234      return true;
    235    },
    236    // No additional implementation needed here. UpdateService.sys.mjs will check
    237    // for this policy directly when determining the update URL.
    238  },
    239 
    240  AppUpdateURL: {
    241    // No implementation needed here. UpdateService.sys.mjs will check for this
    242    // policy directly when determining the update URL.
    243  },
    244 
    245  Authentication: {
    246    onBeforeAddons(manager, param) {
    247      // When Authentication was originally implemented, it was always
    248      // locked, so it defaults to locked.
    249      let locked = true;
    250      if ("Locked" in param) {
    251        locked = param.Locked;
    252      }
    253      if ("SPNEGO" in param) {
    254        PoliciesUtils.setDefaultPref(
    255          "network.negotiate-auth.trusted-uris",
    256          param.SPNEGO.join(", "),
    257          locked
    258        );
    259      }
    260      if ("Delegated" in param) {
    261        PoliciesUtils.setDefaultPref(
    262          "network.negotiate-auth.delegation-uris",
    263          param.Delegated.join(", "),
    264          locked
    265        );
    266      }
    267      if ("NTLM" in param) {
    268        PoliciesUtils.setDefaultPref(
    269          "network.automatic-ntlm-auth.trusted-uris",
    270          param.NTLM.join(", "),
    271          locked
    272        );
    273      }
    274      if ("AllowNonFQDN" in param) {
    275        if ("NTLM" in param.AllowNonFQDN) {
    276          PoliciesUtils.setDefaultPref(
    277            "network.automatic-ntlm-auth.allow-non-fqdn",
    278            param.AllowNonFQDN.NTLM,
    279            locked
    280          );
    281        }
    282        if ("SPNEGO" in param.AllowNonFQDN) {
    283          PoliciesUtils.setDefaultPref(
    284            "network.negotiate-auth.allow-non-fqdn",
    285            param.AllowNonFQDN.SPNEGO,
    286            locked
    287          );
    288        }
    289      }
    290      if ("AllowProxies" in param) {
    291        if ("NTLM" in param.AllowProxies) {
    292          PoliciesUtils.setDefaultPref(
    293            "network.automatic-ntlm-auth.allow-proxies",
    294            param.AllowProxies.NTLM,
    295            locked
    296          );
    297        }
    298        if ("SPNEGO" in param.AllowProxies) {
    299          PoliciesUtils.setDefaultPref(
    300            "network.negotiate-auth.allow-proxies",
    301            param.AllowProxies.SPNEGO,
    302            locked
    303          );
    304        }
    305      }
    306      if ("PrivateBrowsing" in param) {
    307        PoliciesUtils.setDefaultPref(
    308          "network.auth.private-browsing-sso",
    309          param.PrivateBrowsing,
    310          locked
    311        );
    312      }
    313    },
    314  },
    315 
    316  AutofillAddressEnabled: {
    317    onBeforeAddons(manager, param) {
    318      setAndLockPref("extensions.formautofill.addresses.enabled", param);
    319    },
    320  },
    321 
    322  AutofillCreditCardEnabled: {
    323    onBeforeAddons(manager, param) {
    324      setAndLockPref("extensions.formautofill.creditCards.enabled", param);
    325    },
    326  },
    327 
    328  AutoLaunchProtocolsFromOrigins: {
    329    onBeforeAddons(manager, param) {
    330      for (let info of param) {
    331        addAllowDenyPermissions(
    332          `open-protocol-handler^${info.protocol}`,
    333          info.allowed_origins
    334        );
    335      }
    336    },
    337  },
    338 
    339  BackgroundAppUpdate: {
    340    onBeforeAddons(manager, param) {
    341      if (param) {
    342        manager.disallowFeature("app-background-update-off");
    343      } else {
    344        manager.disallowFeature("app-background-update-on");
    345      }
    346    },
    347  },
    348 
    349  BlockAboutAddons: {
    350    onBeforeUIStartup(manager, param) {
    351      if (param) {
    352        blockAboutPage(manager, "about:addons", true);
    353      }
    354    },
    355  },
    356 
    357  BlockAboutConfig: {
    358    onBeforeUIStartup(manager, param) {
    359      if (param) {
    360        blockAboutPage(manager, "about:config");
    361        setAndLockPref("devtools.chrome.enabled", false);
    362      }
    363    },
    364  },
    365 
    366  BlockAboutProfiles: {
    367    onBeforeAddons(manager, param) {
    368      if (param) {
    369        manager.disallowFeature("profileManagement");
    370      }
    371    },
    372    onBeforeUIStartup(manager, param) {
    373      if (param) {
    374        blockAboutPage(manager, "about:profiles");
    375        blockAboutPage(manager, "about:profilemanager");
    376        blockAboutPage(manager, "about:editprofile");
    377        blockAboutPage(manager, "about:deleteprofile");
    378        blockAboutPage(manager, "about:newprofile");
    379      }
    380    },
    381  },
    382 
    383  BlockAboutSupport: {
    384    onBeforeUIStartup(manager, param) {
    385      if (param) {
    386        blockAboutPage(manager, "about:support");
    387        manager.disallowFeature("aboutSupport");
    388      }
    389    },
    390  },
    391 
    392  Bookmarks: {
    393    onAllWindowsRestored(manager, param) {
    394      lazy.BookmarksPolicies.processBookmarks(param);
    395    },
    396  },
    397 
    398  BrowserDataBackup: {
    399    onBeforeUIStartup(manager, param) {
    400      if (typeof param === "boolean") {
    401        setAndLockPref("browser.backup.enabled", param);
    402        setAndLockPref("browser.backup.archive.enabled", param);
    403        setAndLockPref("browser.backup.restore.enabled", param);
    404      } else {
    405        const hasBackup = "AllowBackup" in param;
    406        const hasRestore = "AllowRestore" in param;
    407        let serviceValue;
    408 
    409        if (hasBackup && hasRestore) {
    410          // both present but could be set to false
    411          serviceValue = param.AllowBackup || param.AllowRestore;
    412        } else if (hasBackup && param.AllowBackup) {
    413          // only AllowBackup is true
    414          serviceValue = true;
    415        } else if (hasRestore && param.AllowRestore) {
    416          // only AllowRestore is true
    417          serviceValue = true;
    418        }
    419 
    420        if (serviceValue !== undefined) {
    421          PoliciesUtils.setDefaultPref(
    422            "browser.backup.enabled",
    423            serviceValue,
    424            true
    425          );
    426        }
    427 
    428        if (hasBackup) {
    429          PoliciesUtils.setDefaultPref(
    430            "browser.backup.archive.enabled",
    431            param.AllowBackup,
    432            true
    433          );
    434        }
    435 
    436        if (hasRestore) {
    437          PoliciesUtils.setDefaultPref(
    438            "browser.backup.restore.enabled",
    439            param.AllowRestore,
    440            true
    441          );
    442        }
    443      }
    444    },
    445  },
    446 
    447  CaptivePortal: {
    448    onBeforeAddons(manager, param) {
    449      setAndLockPref("network.captive-portal-service.enabled", param);
    450    },
    451  },
    452 
    453  Certificates: {
    454    onBeforeAddons(manager, param) {
    455      if ("ImportEnterpriseRoots" in param) {
    456        setAndLockPref(
    457          "security.enterprise_roots.enabled",
    458          param.ImportEnterpriseRoots
    459        );
    460      }
    461      if ("Install" in param) {
    462        (async () => {
    463          let dirs = [];
    464          let platform = AppConstants.platform;
    465          if (platform == "win") {
    466            dirs = [
    467              // Ugly, but there is no official way to get %USERNAME\AppData\Roaming\Mozilla.
    468              Services.dirsvc.get("XREUSysExt", Ci.nsIFile).parent,
    469              // Even more ugly, but there is no official way to get %USERNAME\AppData\Local\Mozilla.
    470              Services.dirsvc.get("DefProfLRt", Ci.nsIFile).parent.parent,
    471            ];
    472          } else if (platform == "macosx" || platform == "linux") {
    473            dirs = [
    474              // These two keys are named wrong. They return the Mozilla directory.
    475              Services.dirsvc.get("XREUserNativeManifests", Ci.nsIFile),
    476              Services.dirsvc.get("XRESysNativeManifests", Ci.nsIFile),
    477            ];
    478          }
    479          dirs.unshift(Services.dirsvc.get("XREAppDist", Ci.nsIFile));
    480          for (let certfilename of param.Install) {
    481            let certfile;
    482            try {
    483              certfile = Cc["@mozilla.org/file/local;1"].createInstance(
    484                Ci.nsIFile
    485              );
    486              certfile.initWithPath(certfilename);
    487            } catch (e) {
    488              for (let dir of dirs) {
    489                certfile = dir.clone();
    490                certfile.append(
    491                  platform == "linux" ? "certificates" : "Certificates"
    492                );
    493                certfile.append(certfilename);
    494                if (certfile.exists()) {
    495                  break;
    496                }
    497              }
    498            }
    499            let file;
    500            try {
    501              file = await File.createFromNsIFile(certfile);
    502            } catch (e) {
    503              lazy.log.error(`Unable to find certificate - ${certfilename}`);
    504              continue;
    505            }
    506            let reader = new FileReader();
    507            reader.onloadend = function () {
    508              if (reader.readyState != reader.DONE) {
    509                lazy.log.error(`Unable to read certificate - ${certfile.path}`);
    510                return;
    511              }
    512              let certFile = reader.result;
    513              let certFileArray = [];
    514              for (let i = 0; i < certFile.length; i++) {
    515                certFileArray.push(certFile.charCodeAt(i));
    516              }
    517              let cert;
    518              try {
    519                cert = lazy.gCertDB.constructX509(certFileArray);
    520              } catch (e) {
    521                lazy.log.debug(
    522                  `constructX509 failed with error '${e}' - trying constructX509FromBase64.`
    523                );
    524                try {
    525                  // It might be PEM instead of DER.
    526                  cert = lazy.gCertDB.constructX509FromBase64(
    527                    pemToBase64(certFile)
    528                  );
    529                } catch (ex) {
    530                  lazy.log.error(
    531                    `Unable to add certificate - ${certfile.path}`,
    532                    ex
    533                  );
    534                }
    535              }
    536              if (cert) {
    537                if (
    538                  lazy.gCertDB.isCertTrusted(
    539                    cert,
    540                    Ci.nsIX509Cert.CA_CERT,
    541                    Ci.nsIX509CertDB.TRUSTED_SSL
    542                  )
    543                ) {
    544                  // Certificate is already installed.
    545                  return;
    546                }
    547                try {
    548                  lazy.gCertDB.addCert(certFile, "CT,CT,");
    549                } catch (e) {
    550                  // It might be PEM instead of DER.
    551                  lazy.gCertDB.addCertFromBase64(
    552                    pemToBase64(certFile),
    553                    "CT,CT,"
    554                  );
    555                }
    556              }
    557            };
    558            reader.readAsBinaryString(file);
    559          }
    560        })();
    561      }
    562    },
    563  },
    564 
    565  Containers: {
    566    // Queried directly by ContextualIdentityService.sys.mjs
    567  },
    568 
    569  ContentAnalysis: {
    570    onBeforeAddons(manager, param) {
    571      // For security reasons, all of the Content Analysis related prefs should be locked in
    572      // this method, even if the values aren't specified in Enterprise Policies.
    573      setPrefIfPresentAndLock(
    574        param,
    575        "PipePathName",
    576        "browser.contentanalysis.pipe_path_name"
    577      );
    578      if ("AgentTimeout" in param) {
    579        if (!Number.isInteger(param.AgentTimeout)) {
    580          lazy.log.error(
    581            `Non-integer value for AgentTimeout: ${param.AgentTimeout}`
    582          );
    583        } else {
    584          setAndLockPref(
    585            "browser.contentanalysis.agent_timeout",
    586            param.AgentTimeout
    587          );
    588        }
    589      } else {
    590        Services.prefs.lockPref("browser.contentanalysis.agent_timeout");
    591      }
    592      setPrefIfPresentAndLock(
    593        param,
    594        "AllowUrlRegexList",
    595        "browser.contentanalysis.allow_url_regex_list"
    596      );
    597      setPrefIfPresentAndLock(
    598        param,
    599        "DenyUrlRegexList",
    600        "browser.contentanalysis.deny_url_regex_list"
    601      );
    602      setPrefIfPresentAndLock(
    603        param,
    604        "AgentName",
    605        "browser.contentanalysis.agent_name"
    606      );
    607      setPrefIfPresentAndLock(
    608        param,
    609        "ClientSignature",
    610        "browser.contentanalysis.client_signature"
    611      );
    612      setPrefIfPresentAndLock(
    613        param,
    614        "MaxConnectionsCount",
    615        "browser.contentanalysis.max_connections"
    616      );
    617      let resultPrefs = [
    618        ["DefaultResult", "default_result"],
    619        ["TimeoutResult", "timeout_result"],
    620      ];
    621      for (let pref of resultPrefs) {
    622        if (pref[0] in param) {
    623          if (
    624            !Number.isInteger(param[pref[0]]) ||
    625            param[pref[0]] < 0 ||
    626            param[pref[0]] > 2
    627          ) {
    628            lazy.log.error(
    629              `Non-integer or out of range value for ${pref[0]}: ${param[pref[0]]}`
    630            );
    631            Services.prefs.lockPref(`browser.contentanalysis.${pref[1]}`);
    632          } else {
    633            setAndLockPref(
    634              `browser.contentanalysis.${pref[1]}`,
    635              param[pref[0]]
    636            );
    637          }
    638        } else {
    639          Services.prefs.lockPref(`browser.contentanalysis.${pref[1]}`);
    640        }
    641      }
    642      let boolPrefs = [
    643        ["IsPerUser", "is_per_user"],
    644        ["ShowBlockedResult", "show_blocked_result"],
    645        ["BypassForSameTabOperations", "bypass_for_same_tab_operations"],
    646      ];
    647      for (let pref of boolPrefs) {
    648        if (pref[0] in param) {
    649          setAndLockPref(
    650            `browser.contentanalysis.${pref[1]}`,
    651            !!param[pref[0]]
    652          );
    653        } else {
    654          Services.prefs.lockPref(`browser.contentanalysis.${pref[1]}`);
    655        }
    656      }
    657      let interceptionPointPrefs = [
    658        ["Clipboard", "clipboard"],
    659        ["Download", "download"],
    660        ["DragAndDrop", "drag_and_drop"],
    661        ["FileUpload", "file_upload"],
    662        ["Print", "print"],
    663      ];
    664      if ("InterceptionPoints" in param) {
    665        for (let pref of interceptionPointPrefs) {
    666          let value = true;
    667          if (pref[0] in param.InterceptionPoints) {
    668            if ("Enabled" in param.InterceptionPoints[pref[0]]) {
    669              value = !!param.InterceptionPoints[pref[0]].Enabled;
    670            }
    671          }
    672          setAndLockPref(
    673            `browser.contentanalysis.interception_point.${pref[1]}.enabled`,
    674            value
    675          );
    676        }
    677      } else {
    678        for (let pref of interceptionPointPrefs) {
    679          Services.prefs.lockPref(
    680            `browser.contentanalysis.interception_point.${pref[1]}.enabled`
    681          );
    682        }
    683      }
    684      let plainTextOnlyPrefs = [
    685        ["Clipboard", "clipboard"],
    686        ["DragAndDrop", "drag_and_drop"],
    687      ];
    688      if ("InterceptionPoints" in param) {
    689        for (let pref of plainTextOnlyPrefs) {
    690          // Need to set and lock this value even if the enterprise
    691          // policy isn't set so users can't change it
    692          let value = true;
    693          if ("InterceptionPoints" in param) {
    694            if (pref[0] in param.InterceptionPoints) {
    695              if ("PlainTextOnly" in param.InterceptionPoints[pref[0]]) {
    696                value = !!param.InterceptionPoints[pref[0]].PlainTextOnly;
    697              }
    698            }
    699          }
    700          setAndLockPref(
    701            `browser.contentanalysis.interception_point.${pref[1]}.plain_text_only`,
    702            value
    703          );
    704        }
    705      } else {
    706        for (let pref of plainTextOnlyPrefs) {
    707          Services.prefs.lockPref(
    708            `browser.contentanalysis.interception_point.${pref[1]}.plain_text_only`
    709          );
    710        }
    711      }
    712      if ("Enabled" in param) {
    713        let enabled = !!param.Enabled;
    714        setAndLockPref("browser.contentanalysis.enabled", enabled);
    715        let ca = Cc["@mozilla.org/contentanalysis;1"].getService(
    716          Ci.nsIContentAnalysis
    717        );
    718        ca.isSetByEnterprisePolicy = true;
    719      } else {
    720        // Probably not strictly necessary, but let's lock everything
    721        // to be consistent.
    722        Services.prefs.lockPref("browser.contentanalysis.enabled");
    723      }
    724    },
    725  },
    726 
    727  Cookies: {
    728    onBeforeUIStartup(manager, param) {
    729      addAllowDenyPermissions("cookie", param.Allow, param.Block);
    730 
    731      if (param.AllowSession) {
    732        for (let origin of param.AllowSession) {
    733          try {
    734            Services.perms.addFromPrincipal(
    735              Services.scriptSecurityManager.createContentPrincipalFromOrigin(
    736                origin
    737              ),
    738              "cookie",
    739              Ci.nsICookiePermission.ACCESS_SESSION,
    740              Ci.nsIPermissionManager.EXPIRE_POLICY
    741            );
    742          } catch (ex) {
    743            lazy.log.error(
    744              `Unable to add cookie session permission - ${origin.href}`
    745            );
    746          }
    747        }
    748      }
    749 
    750      if (param.Block) {
    751        const hosts = param.Block.map(url => url.hostname)
    752          .sort()
    753          .join("\n");
    754        runOncePerModification("clearCookiesForBlockedHosts", hosts, () => {
    755          for (let blocked of param.Block) {
    756            Services.cookies.removeCookiesWithOriginAttributes(
    757              "{}",
    758              blocked.hostname
    759            );
    760          }
    761        });
    762      }
    763 
    764      if (param.ExpireAtSessionEnd != undefined) {
    765        lazy.log.error(
    766          "'ExpireAtSessionEnd' has been deprecated and it has no effect anymore."
    767        );
    768      }
    769 
    770      // New Cookie Behavior option takes precendence
    771      let defaultPref = Services.prefs.getDefaultBranch("");
    772      let newCookieBehavior = defaultPref.getIntPref(
    773        "network.cookie.cookieBehavior"
    774      );
    775      let newCookieBehaviorPB = defaultPref.getIntPref(
    776        "network.cookie.cookieBehavior.pbmode"
    777      );
    778      if ("Behavior" in param || "BehaviorPrivateBrowsing" in param) {
    779        let behaviors = {
    780          accept: Ci.nsICookieService.BEHAVIOR_ACCEPT,
    781          "reject-foreign": Ci.nsICookieService.BEHAVIOR_REJECT_FOREIGN,
    782          reject: Ci.nsICookieService.BEHAVIOR_REJECT,
    783          "limit-foreign": Ci.nsICookieService.BEHAVIOR_LIMIT_FOREIGN,
    784          "reject-tracker": Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER,
    785          "reject-tracker-and-partition-foreign":
    786            Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN,
    787        };
    788        if ("Behavior" in param) {
    789          newCookieBehavior = behaviors[param.Behavior];
    790        }
    791        if ("BehaviorPrivateBrowsing" in param) {
    792          newCookieBehaviorPB = behaviors[param.BehaviorPrivateBrowsing];
    793        }
    794      } else {
    795        // Default, AcceptThirdParty, and RejectTracker are being
    796        // deprecated in favor of Behavior. They will continue
    797        // to be supported, though.
    798        if (
    799          param.Default !== undefined ||
    800          param.AcceptThirdParty !== undefined ||
    801          param.RejectTracker !== undefined ||
    802          param.Locked
    803        ) {
    804          newCookieBehavior = Ci.nsICookieService.BEHAVIOR_ACCEPT;
    805          if (param.Default !== undefined && !param.Default) {
    806            newCookieBehavior = Ci.nsICookieService.BEHAVIOR_REJECT;
    807          } else if (param.AcceptThirdParty) {
    808            if (param.AcceptThirdParty == "never") {
    809              newCookieBehavior = Ci.nsICookieService.BEHAVIOR_REJECT_FOREIGN;
    810            } else if (param.AcceptThirdParty == "from-visited") {
    811              newCookieBehavior = Ci.nsICookieService.BEHAVIOR_LIMIT_FOREIGN;
    812            }
    813          } else if (param.RejectTracker) {
    814            newCookieBehavior = Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER;
    815          }
    816        }
    817        // With the old cookie policy, we made private browsing the same.
    818        newCookieBehaviorPB = newCookieBehavior;
    819      }
    820      // We set the values no matter what just in case the policy was only used to lock.
    821      PoliciesUtils.setDefaultPref(
    822        "network.cookie.cookieBehavior",
    823        newCookieBehavior,
    824        param.Locked
    825      );
    826      PoliciesUtils.setDefaultPref(
    827        "network.cookie.cookieBehavior.pbmode",
    828        newCookieBehaviorPB,
    829        param.Locked
    830      );
    831    },
    832  },
    833 
    834  DefaultDownloadDirectory: {
    835    onBeforeAddons(manager, param) {
    836      PoliciesUtils.setDefaultPref(
    837        "browser.download.dir",
    838        replacePathVariables(param)
    839      );
    840    },
    841  },
    842 
    843  DisableAccounts: {
    844    onBeforeAddons(manager, param) {
    845      if (param) {
    846        setAndLockPref("identity.fxaccounts.enabled", false);
    847        setAndLockPref("browser.aboutwelcome.enabled", false);
    848      }
    849    },
    850  },
    851 
    852  DisableAppUpdate: {
    853    onBeforeAddons(manager, param) {
    854      if (param) {
    855        manager.disallowFeature("appUpdate");
    856      }
    857    },
    858  },
    859 
    860  DisableBuiltinPDFViewer: {
    861    onBeforeAddons(manager, param) {
    862      let policies = Services.policies.getActivePolicies();
    863      if (
    864        policies.Handlers?.mimeTypes?.["application/pdf"] ||
    865        policies.Handlers?.extensions?.pdf
    866      ) {
    867        // If there is an existing Handlers policy modifying PDF behavior,
    868        // don't do anything.
    869        return;
    870      }
    871      let pdfMIMEInfo = lazy.gMIMEService.getFromTypeAndExtension(
    872        "application/pdf",
    873        "pdf"
    874      );
    875      let mimeInfo = {
    876        action: param ? "useSystemDefault" : "handleInternally",
    877      };
    878      processMIMEInfo(mimeInfo, pdfMIMEInfo);
    879    },
    880  },
    881 
    882  DisabledCiphers: {
    883    onBeforeAddons(manager, param) {
    884      let cipherPrefs = {
    885        TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256:
    886          "security.ssl3.ecdhe_rsa_aes_128_gcm_sha256",
    887        TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256:
    888          "security.ssl3.ecdhe_ecdsa_aes_128_gcm_sha256",
    889        TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256:
    890          "security.ssl3.ecdhe_ecdsa_chacha20_poly1305_sha256",
    891        TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256:
    892          "security.ssl3.ecdhe_rsa_chacha20_poly1305_sha256",
    893        TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384:
    894          "security.ssl3.ecdhe_ecdsa_aes_256_gcm_sha384",
    895        TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384:
    896          "security.ssl3.ecdhe_rsa_aes_256_gcm_sha384",
    897        TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA:
    898          "security.ssl3.ecdhe_rsa_aes_128_sha",
    899        TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA:
    900          "security.ssl3.ecdhe_ecdsa_aes_128_sha",
    901        TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA:
    902          "security.ssl3.ecdhe_rsa_aes_256_sha",
    903        TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA:
    904          "security.ssl3.ecdhe_ecdsa_aes_256_sha",
    905        TLS_DHE_RSA_WITH_AES_128_CBC_SHA: "security.ssl3.dhe_rsa_aes_128_sha",
    906        TLS_DHE_RSA_WITH_AES_256_CBC_SHA: "security.ssl3.dhe_rsa_aes_256_sha",
    907        TLS_RSA_WITH_AES_128_GCM_SHA256: "security.ssl3.rsa_aes_128_gcm_sha256",
    908        TLS_RSA_WITH_AES_256_GCM_SHA384: "security.ssl3.rsa_aes_256_gcm_sha384",
    909        TLS_RSA_WITH_AES_128_CBC_SHA: "security.ssl3.rsa_aes_128_sha",
    910        TLS_RSA_WITH_AES_256_CBC_SHA: "security.ssl3.rsa_aes_256_sha",
    911        TLS_RSA_WITH_3DES_EDE_CBC_SHA:
    912          "security.ssl3.deprecated.rsa_des_ede3_sha",
    913        TLS_CHACHA20_POLY1305_SHA256: "security.tls13.chacha20_poly1305_sha256",
    914        TLS_AES_128_GCM_SHA256: "security.tls13.aes_128_gcm_sha256",
    915        TLS_AES_256_GCM_SHA384: "security.tls13.aes_256_gcm_sha384",
    916      };
    917 
    918      for (let cipher in param) {
    919        setAndLockPref(cipherPrefs[cipher], !param[cipher]);
    920      }
    921    },
    922  },
    923 
    924  DisableDefaultBrowserAgent: {
    925    // The implementation of this policy is in the default browser agent itself
    926    // (/toolkit/mozapps/defaultagent); we need an entry for it here so that it
    927    // shows up in about:policies as a real policy and not as an error.
    928  },
    929 
    930  DisableDeveloperTools: {
    931    onBeforeAddons(manager, param) {
    932      if (param) {
    933        setAndLockPref("devtools.policy.disabled", true);
    934        setAndLockPref("devtools.chrome.enabled", false);
    935 
    936        manager.disallowFeature("devtools");
    937        blockAboutPage(manager, "about:debugging");
    938        blockAboutPage(manager, "about:devtools-toolbox");
    939        blockAboutPage(manager, "about:profiling");
    940      }
    941    },
    942  },
    943 
    944  DisableEncryptedClientHello: {
    945    onBeforeAddons(manager, param) {
    946      if (param) {
    947        setAndLockPref("network.dns.echconfig.enabled", false);
    948        setAndLockPref("network.dns.http3_echconfig.enabled", false);
    949      }
    950    },
    951  },
    952 
    953  DisableFeedbackCommands: {
    954    onBeforeUIStartup(manager, param) {
    955      if (param) {
    956        manager.disallowFeature("feedbackCommands");
    957      }
    958    },
    959  },
    960 
    961  DisableFirefoxAccounts: {
    962    onBeforeAddons(manager, param) {
    963      // If DisableAccounts is set, let it take precedence.
    964      if ("DisableAccounts" in manager.getActivePolicies()) {
    965        return;
    966      }
    967 
    968      if (param) {
    969        setAndLockPref("identity.fxaccounts.enabled", false);
    970        setAndLockPref("browser.aboutwelcome.enabled", false);
    971      }
    972    },
    973  },
    974 
    975  DisableFirefoxScreenshots: {
    976    onBeforeUIStartup(manager, param) {
    977      if (param) {
    978        setAndLockPref("screenshots.browser.component.enabled", false);
    979      }
    980    },
    981  },
    982 
    983  DisableFirefoxStudies: {
    984    onBeforeAddons(manager, param) {
    985      if (param) {
    986        manager.disallowFeature("Shield");
    987        setAndLockPref(
    988          "browser.newtabpage.activity-stream.asrouter.userprefs.cfr.addons",
    989          false
    990        );
    991        setAndLockPref(
    992          "browser.newtabpage.activity-stream.asrouter.userprefs.cfr.features",
    993          false
    994        );
    995      }
    996    },
    997  },
    998 
    999  DisableForgetButton: {
   1000    onProfileAfterChange(manager, param) {
   1001      if (param) {
   1002        setAndLockPref("privacy.panicButton.enabled", false);
   1003      }
   1004    },
   1005  },
   1006 
   1007  DisableFormHistory: {
   1008    onBeforeUIStartup(manager, param) {
   1009      if (param) {
   1010        setAndLockPref("browser.formfill.enable", false);
   1011      }
   1012    },
   1013  },
   1014 
   1015  DisableMasterPasswordCreation: {
   1016    onBeforeUIStartup(manager, param) {
   1017      if (param) {
   1018        manager.disallowFeature("createMasterPassword");
   1019      }
   1020    },
   1021  },
   1022 
   1023  DisablePasswordReveal: {
   1024    onBeforeUIStartup(manager, param) {
   1025      if (param) {
   1026        manager.disallowFeature("passwordReveal");
   1027      }
   1028    },
   1029  },
   1030 
   1031  DisablePrivateBrowsing: {
   1032    onBeforeAddons(manager, param) {
   1033      if (param) {
   1034        manager.disallowFeature("privatebrowsing");
   1035        blockAboutPage(manager, "about:privatebrowsing", true);
   1036        setAndLockPref("browser.privatebrowsing.autostart", false);
   1037      }
   1038    },
   1039  },
   1040 
   1041  DisableProfileImport: {
   1042    onBeforeUIStartup(manager, param) {
   1043      if (param) {
   1044        manager.disallowFeature("profileImport");
   1045        setAndLockPref(
   1046          "browser.newtabpage.activity-stream.migrationExpired",
   1047          true
   1048        );
   1049      }
   1050    },
   1051  },
   1052 
   1053  DisableProfileRefresh: {
   1054    onBeforeUIStartup(manager, param) {
   1055      if (param) {
   1056        manager.disallowFeature("profileRefresh");
   1057        setAndLockPref("browser.disableResetPrompt", true);
   1058      }
   1059    },
   1060  },
   1061 
   1062  DisableSafeMode: {
   1063    onBeforeUIStartup(manager, param) {
   1064      if (param) {
   1065        manager.disallowFeature("safeMode");
   1066      }
   1067    },
   1068  },
   1069 
   1070  DisableSecurityBypass: {
   1071    onBeforeUIStartup(manager, param) {
   1072      if ("InvalidCertificate" in param) {
   1073        setAndLockPref(
   1074          "security.certerror.hideAddException",
   1075          param.InvalidCertificate
   1076        );
   1077      }
   1078 
   1079      if ("SafeBrowsing" in param) {
   1080        setAndLockPref(
   1081          "browser.safebrowsing.allowOverride",
   1082          !param.SafeBrowsing
   1083        );
   1084      }
   1085    },
   1086  },
   1087 
   1088  DisableSetDesktopBackground: {
   1089    onBeforeUIStartup(manager, param) {
   1090      if (param) {
   1091        manager.disallowFeature("setDesktopBackground");
   1092      }
   1093    },
   1094  },
   1095 
   1096  DisableSystemAddonUpdate: {
   1097    onBeforeAddons(manager, param) {
   1098      if (param) {
   1099        manager.disallowFeature("SysAddonUpdate");
   1100      }
   1101    },
   1102  },
   1103 
   1104  DisableTelemetry: {
   1105    onBeforeAddons(manager, param) {
   1106      if (param) {
   1107        setAndLockPref("datareporting.healthreport.uploadEnabled", false);
   1108        setAndLockPref("datareporting.policy.dataSubmissionEnabled", false);
   1109        setAndLockPref("toolkit.telemetry.archive.enabled", false);
   1110        setAndLockPref("datareporting.usage.uploadEnabled", false);
   1111        blockAboutPage(manager, "about:telemetry");
   1112      }
   1113    },
   1114  },
   1115 
   1116  DisableThirdPartyModuleBlocking: {
   1117    onBeforeUIStartup(manager, param) {
   1118      if (param) {
   1119        manager.disallowFeature("thirdPartyModuleBlocking");
   1120      }
   1121    },
   1122  },
   1123 
   1124  DisplayBookmarksToolbar: {
   1125    onBeforeUIStartup(manager, param) {
   1126      let visibility;
   1127      if (typeof param === "boolean") {
   1128        visibility = param ? "always" : "newtab";
   1129      } else {
   1130        visibility = param;
   1131      }
   1132      // This policy is meant to change the default behavior, not to force it.
   1133      // If this policy was already applied and the user chose to re-hide the
   1134      // bookmarks toolbar, do not show it again.
   1135      runOncePerModification("displayBookmarksToolbar", visibility, () => {
   1136        let visibilityPref = "browser.toolbars.bookmarks.visibility";
   1137        Services.prefs.setCharPref(visibilityPref, visibility);
   1138      });
   1139    },
   1140  },
   1141 
   1142  DisplayMenuBar: {
   1143    onBeforeUIStartup(manager, param) {
   1144      let value;
   1145      if (
   1146        typeof param === "boolean" ||
   1147        param == "default-on" ||
   1148        param == "default-off"
   1149      ) {
   1150        switch (param) {
   1151          case "default-on":
   1152            value = false;
   1153            break;
   1154          case "default-off":
   1155            value = true;
   1156            break;
   1157          default:
   1158            value = !param;
   1159            break;
   1160        }
   1161        // This policy is meant to change the default behavior, not to force it.
   1162        // If this policy was already applied and the user chose to re-hide the
   1163        // menu bar, do not show it again.
   1164        runOncePerModification("displayMenuBar", value, () => {
   1165          Services.xulStore.setValue(
   1166            BROWSER_DOCUMENT_URL,
   1167            "toolbar-menubar",
   1168            "autohide",
   1169            value ? "" : "-moz-missing\n"
   1170          );
   1171        });
   1172      } else {
   1173        switch (param) {
   1174          case "always":
   1175            value = false;
   1176            break;
   1177          case "never":
   1178            // Make sure Alt key doesn't show the menubar
   1179            setAndLockPref("ui.key.menuAccessKeyFocuses", false);
   1180            value = true;
   1181            break;
   1182        }
   1183        Services.xulStore.setValue(
   1184          BROWSER_DOCUMENT_URL,
   1185          "toolbar-menubar",
   1186          "autohide",
   1187          value ? "" : "-moz-missing\n"
   1188        );
   1189        manager.disallowFeature("hideShowMenuBar");
   1190      }
   1191    },
   1192  },
   1193 
   1194  DNSOverHTTPS: {
   1195    onBeforeAddons(manager, param) {
   1196      if ("Enabled" in param) {
   1197        let mode = param.Enabled ? 2 : 5;
   1198        // Fallback only matters if DOH is enabled.
   1199        if (param.Fallback === false) {
   1200          mode = 3;
   1201        }
   1202        PoliciesUtils.setDefaultPref("network.trr.mode", mode, param.Locked);
   1203      }
   1204      if ("ProviderURL" in param) {
   1205        PoliciesUtils.setDefaultPref(
   1206          "network.trr.uri",
   1207          param.ProviderURL.href,
   1208          param.Locked
   1209        );
   1210      }
   1211      if ("ExcludedDomains" in param) {
   1212        PoliciesUtils.setDefaultPref(
   1213          "network.trr.excluded-domains",
   1214          param.ExcludedDomains.join(","),
   1215          param.Locked
   1216        );
   1217      }
   1218    },
   1219  },
   1220 
   1221  DontCheckDefaultBrowser: {
   1222    onBeforeUIStartup(manager, param) {
   1223      setAndLockPref("browser.shell.checkDefaultBrowser", !param);
   1224    },
   1225  },
   1226 
   1227  DownloadDirectory: {
   1228    onBeforeAddons(manager, param) {
   1229      setAndLockPref("browser.download.dir", replacePathVariables(param));
   1230      // If a custom download directory is being used, just lock folder list to 2.
   1231      setAndLockPref("browser.download.folderList", 2);
   1232      // Per Chrome spec, user can't choose to download every time
   1233      // if this is set.
   1234      setAndLockPref("browser.download.useDownloadDir", true);
   1235    },
   1236  },
   1237 
   1238  EnableTrackingProtection: {
   1239    onAllWindowsRestored(manager, param) {
   1240      if (param.Category) {
   1241        // browser.contentblocking.category only works as a default pref if
   1242        // it is locked.
   1243        PoliciesUtils.setDefaultPref(
   1244          "browser.contentblocking.category",
   1245          param.Category,
   1246          true
   1247        );
   1248        let { ContentBlockingPrefs } = ChromeUtils.importESModule(
   1249          "moz-src:///browser/components/protections/ContentBlockingPrefs.sys.mjs"
   1250        );
   1251        // These are always locked because they would reset at
   1252        // startup anyway.
   1253        ContentBlockingPrefs.setPrefsToCategory(
   1254          param.Category,
   1255          true, // locked
   1256          false // preserveAllowListSettings
   1257        );
   1258        ContentBlockingPrefs.matchCBCategory();
   1259        // We don't want to lock the new exceptions UI unless
   1260        // that policy was explicitly set.
   1261        if (param.Category == "strict" && !param.Locked) {
   1262          Services.prefs.unlockPref(
   1263            "privacy.trackingprotection.allow_list.baseline.enabled"
   1264          );
   1265          Services.prefs.unlockPref(
   1266            "privacy.trackingprotection.allow_list.convenience.enabled"
   1267          );
   1268        }
   1269      }
   1270    },
   1271    onBeforeUIStartup(manager, param) {
   1272      if ("Exceptions" in param) {
   1273        addAllowDenyPermissions("trackingprotection", param.Exceptions);
   1274      }
   1275      if ("BaselineExceptions" in param) {
   1276        PoliciesUtils.setDefaultPref(
   1277          "privacy.trackingprotection.allow_list.baseline.enabled",
   1278          param.BaselineExceptions,
   1279          param.Locked
   1280        );
   1281      }
   1282      if ("ConvenienceExceptions" in param) {
   1283        PoliciesUtils.setDefaultPref(
   1284          "privacy.trackingprotection.allow_list.convenience.enabled",
   1285          param.ConvenienceExceptions,
   1286          param.Locked
   1287        );
   1288      }
   1289      if (param.Category) {
   1290        // If a category is set, we ignore everything except exceptions
   1291        // and the allow lists.
   1292        return;
   1293      }
   1294      if (param.Value) {
   1295        PoliciesUtils.setDefaultPref(
   1296          "privacy.trackingprotection.enabled",
   1297          true,
   1298          param.Locked
   1299        );
   1300        PoliciesUtils.setDefaultPref(
   1301          "privacy.trackingprotection.pbmode.enabled",
   1302          true,
   1303          param.Locked
   1304        );
   1305      } else {
   1306        setAndLockPref("privacy.trackingprotection.enabled", false);
   1307        setAndLockPref("privacy.trackingprotection.pbmode.enabled", false);
   1308      }
   1309      if ("Cryptomining" in param) {
   1310        PoliciesUtils.setDefaultPref(
   1311          "privacy.trackingprotection.cryptomining.enabled",
   1312          param.Cryptomining,
   1313          param.Locked
   1314        );
   1315      }
   1316      if ("HarmfulAddon" in param) {
   1317        PoliciesUtils.setDefaultPref(
   1318          "privacy.trackingprotection.harmfuladdon.enabled",
   1319          param.HarmfulAddon,
   1320          param.Locked
   1321        );
   1322      }
   1323      if ("Fingerprinting" in param) {
   1324        PoliciesUtils.setDefaultPref(
   1325          "privacy.trackingprotection.fingerprinting.enabled",
   1326          param.Fingerprinting,
   1327          param.Locked
   1328        );
   1329      }
   1330      if ("EmailTracking" in param) {
   1331        PoliciesUtils.setDefaultPref(
   1332          "privacy.trackingprotection.emailtracking.enabled",
   1333          param.EmailTracking,
   1334          param.Locked
   1335        );
   1336        PoliciesUtils.setDefaultPref(
   1337          "privacy.trackingprotection.emailtracking.pbmode.enabled",
   1338          param.EmailTracking,
   1339          param.Locked
   1340        );
   1341      }
   1342      if ("SuspectedFingerprinting" in param) {
   1343        PoliciesUtils.setDefaultPref(
   1344          "privacy.fingerprintingProtection",
   1345          param.SuspectedFingerprinting,
   1346          param.Locked
   1347        );
   1348        PoliciesUtils.setDefaultPref(
   1349          "privacy.fingerprintingProtection.pbmode",
   1350          param.SuspectedFingerprinting,
   1351          param.Locked
   1352        );
   1353      }
   1354    },
   1355  },
   1356 
   1357  EncryptedMediaExtensions: {
   1358    onBeforeAddons(manager, param) {
   1359      if ("Enabled" in param) {
   1360        PoliciesUtils.setDefaultPref(
   1361          "media.eme.enabled",
   1362          param.Enabled,
   1363          param.Locked
   1364        );
   1365      }
   1366    },
   1367  },
   1368 
   1369  ExemptDomainFileTypePairsFromFileTypeDownloadWarnings: {
   1370    // This policy is handled directly in EnterprisePoliciesParent.sys.mjs
   1371    // and requires no validation (It's done by the schema).
   1372  },
   1373 
   1374  Extensions: {
   1375    onBeforeUIStartup(manager, param) {
   1376      let uninstallingPromise = Promise.resolve();
   1377      if ("Uninstall" in param) {
   1378        uninstallingPromise = runOncePerModification(
   1379          "extensionsUninstall",
   1380          JSON.stringify(param.Uninstall),
   1381          async () => {
   1382            // If we're uninstalling add-ons, re-run the extensionsInstall runOnce even if it hasn't
   1383            // changed, which will allow add-ons to be updated.
   1384            Services.prefs.clearUserPref(
   1385              "browser.policies.runOncePerModification.extensionsInstall"
   1386            );
   1387            let addons = await lazy.AddonManager.getAddonsByIDs(
   1388              param.Uninstall
   1389            );
   1390            for (let addon of addons) {
   1391              if (addon) {
   1392                try {
   1393                  await addon.uninstall();
   1394                } catch (e) {
   1395                  // This can fail for add-ons that can't be uninstalled.
   1396                  lazy.log.debug(
   1397                    `Add-on ID (${addon.id}) couldn't be uninstalled.`
   1398                  );
   1399                }
   1400              }
   1401            }
   1402          }
   1403        );
   1404      }
   1405      if ("Install" in param) {
   1406        runOncePerModification(
   1407          "extensionsInstall",
   1408          JSON.stringify(param.Install),
   1409          async () => {
   1410            await uninstallingPromise;
   1411            for (let location of param.Install) {
   1412              let uri;
   1413              try {
   1414                // We need to try as a file first because
   1415                // Windows paths are valid URIs.
   1416                // This is done for legacy support (old API)
   1417                let xpiFile = new lazy.FileUtils.File(location);
   1418                uri = Services.io.newFileURI(xpiFile);
   1419              } catch (e) {
   1420                uri = Services.io.newURI(location);
   1421              }
   1422              installAddonFromURL(uri.spec);
   1423            }
   1424          }
   1425        );
   1426      }
   1427      if ("Locked" in param) {
   1428        for (let ID of param.Locked) {
   1429          manager.disallowFeature(`uninstall-extension:${ID}`);
   1430          manager.disallowFeature(`disable-extension:${ID}`);
   1431        }
   1432      }
   1433    },
   1434  },
   1435 
   1436  ExtensionSettings: {
   1437    onBeforeAddons(manager, param) {
   1438      try {
   1439        manager.setExtensionSettings(param);
   1440      } catch (e) {
   1441        lazy.log.error("Invalid ExtensionSettings");
   1442      }
   1443    },
   1444    async onBeforeUIStartup(manager, param) {
   1445      let extensionSettings = param;
   1446      let blockAllExtensions = false;
   1447      if ("*" in extensionSettings) {
   1448        if (
   1449          "installation_mode" in extensionSettings["*"] &&
   1450          extensionSettings["*"].installation_mode == "blocked"
   1451        ) {
   1452          blockAllExtensions = true;
   1453          // Turn off discovery pane in about:addons
   1454          setAndLockPref("extensions.getAddons.showPane", false);
   1455          // Turn off recommendations
   1456          setAndLockPref(
   1457            "extensions.htmlaboutaddons.recommendations.enabled",
   1458            false
   1459          );
   1460          manager.disallowFeature("installTemporaryAddon");
   1461        }
   1462        if ("restricted_domains" in extensionSettings["*"]) {
   1463          let restrictedDomains = Services.prefs
   1464            .getCharPref("extensions.webextensions.restrictedDomains")
   1465            .split(",");
   1466          setAndLockPref(
   1467            "extensions.webextensions.restrictedDomains",
   1468            restrictedDomains
   1469              .concat(extensionSettings["*"].restricted_domains)
   1470              .join(",")
   1471          );
   1472        }
   1473      }
   1474      let addons = await lazy.AddonManager.getAllAddons();
   1475      let allowedExtensions = [];
   1476      for (let extensionID in extensionSettings) {
   1477        if (extensionID == "*") {
   1478          // Ignore global settings
   1479          continue;
   1480        }
   1481        if ("installation_mode" in extensionSettings[extensionID]) {
   1482          if (
   1483            extensionSettings[extensionID].installation_mode ==
   1484              "force_installed" ||
   1485            extensionSettings[extensionID].installation_mode ==
   1486              "normal_installed"
   1487          ) {
   1488            if (!extensionSettings[extensionID].install_url) {
   1489              throw new Error(`Missing install_url for ${extensionID}`);
   1490            }
   1491            installAddonFromURL(
   1492              extensionSettings[extensionID].install_url,
   1493              extensionID,
   1494              addons.find(addon => addon.id == extensionID)
   1495            );
   1496            manager.disallowFeature(`uninstall-extension:${extensionID}`);
   1497            if (
   1498              extensionSettings[extensionID].installation_mode ==
   1499              "force_installed"
   1500            ) {
   1501              manager.disallowFeature(`disable-extension:${extensionID}`);
   1502            }
   1503            allowedExtensions.push(extensionID);
   1504          } else if (
   1505            extensionSettings[extensionID].installation_mode == "allowed"
   1506          ) {
   1507            allowedExtensions.push(extensionID);
   1508          } else if (
   1509            extensionSettings[extensionID].installation_mode == "blocked"
   1510          ) {
   1511            if (addons.find(addon => addon.id == extensionID)) {
   1512              // Can't use the addon from getActiveAddons since it doesn't have uninstall.
   1513              let addon = await lazy.AddonManager.getAddonByID(extensionID);
   1514              try {
   1515                await addon.uninstall();
   1516              } catch (e) {
   1517                // This can fail for add-ons that can't be uninstalled.
   1518                lazy.log.debug(
   1519                  `Add-on ID (${addon.id}) couldn't be uninstalled.`
   1520                );
   1521              }
   1522            }
   1523          }
   1524        }
   1525      }
   1526      if (blockAllExtensions) {
   1527        for (let addon of addons) {
   1528          if (
   1529            addon.isSystem ||
   1530            addon.isBuiltin ||
   1531            !(addon.scope & lazy.AddonManager.SCOPE_PROFILE)
   1532          ) {
   1533            continue;
   1534          }
   1535          if (!allowedExtensions.includes(addon.id)) {
   1536            try {
   1537              // Can't use the addon from getActiveAddons since it doesn't have uninstall.
   1538              let addonToUninstall = await lazy.AddonManager.getAddonByID(
   1539                addon.id
   1540              );
   1541              await addonToUninstall.uninstall();
   1542            } catch (e) {
   1543              // This can fail for add-ons that can't be uninstalled.
   1544              lazy.log.debug(
   1545                `Add-on ID (${addon.id}) couldn't be uninstalled.`
   1546              );
   1547            }
   1548          }
   1549        }
   1550      }
   1551    },
   1552  },
   1553 
   1554  ExtensionUpdate: {
   1555    onBeforeAddons(manager, param) {
   1556      if (!param) {
   1557        setAndLockPref("extensions.update.enabled", param);
   1558      }
   1559    },
   1560  },
   1561 
   1562  FirefoxHome: {
   1563    onBeforeAddons(manager, param) {
   1564      if ("Search" in param) {
   1565        PoliciesUtils.setDefaultPref(
   1566          "browser.newtabpage.activity-stream.showSearch",
   1567          param.Search,
   1568          param.Locked
   1569        );
   1570      }
   1571      if ("TopSites" in param) {
   1572        PoliciesUtils.setDefaultPref(
   1573          "browser.newtabpage.activity-stream.feeds.topsites",
   1574          param.TopSites,
   1575          param.Locked
   1576        );
   1577      }
   1578      if ("SponsoredTopSites" in param) {
   1579        PoliciesUtils.setDefaultPref(
   1580          "browser.newtabpage.activity-stream.showSponsoredTopSites",
   1581          param.SponsoredTopSites,
   1582          param.Locked
   1583        );
   1584      }
   1585      if ("Highlights" in param) {
   1586        PoliciesUtils.setDefaultPref(
   1587          "browser.newtabpage.activity-stream.feeds.section.highlights",
   1588          param.Highlights,
   1589          param.Locked
   1590        );
   1591      }
   1592      if ("Pocket" in param) {
   1593        PoliciesUtils.setDefaultPref(
   1594          "browser.newtabpage.activity-stream.feeds.system.topstories",
   1595          param.Pocket,
   1596          param.Locked
   1597        );
   1598        PoliciesUtils.setDefaultPref(
   1599          "browser.newtabpage.activity-stream.feeds.section.topstories",
   1600          param.Pocket,
   1601          param.Locked
   1602        );
   1603      }
   1604      if ("Stories" in param) {
   1605        PoliciesUtils.setDefaultPref(
   1606          "browser.newtabpage.activity-stream.feeds.system.topstories",
   1607          param.Stories,
   1608          param.Locked
   1609        );
   1610        PoliciesUtils.setDefaultPref(
   1611          "browser.newtabpage.activity-stream.feeds.section.topstories",
   1612          param.Stories,
   1613          param.Locked
   1614        );
   1615      }
   1616      if ("SponsoredPocket" in param) {
   1617        PoliciesUtils.setDefaultPref(
   1618          "browser.newtabpage.activity-stream.showSponsored",
   1619          param.SponsoredPocket,
   1620          param.Locked
   1621        );
   1622      }
   1623      if ("SponsoredStories" in param) {
   1624        PoliciesUtils.setDefaultPref(
   1625          "browser.newtabpage.activity-stream.showSponsored",
   1626          param.SponsoredStories,
   1627          param.Locked
   1628        );
   1629      }
   1630    },
   1631  },
   1632 
   1633  FirefoxSuggest: {
   1634    onBeforeAddons(manager, param) {
   1635      (async () => {
   1636        await lazy.QuickSuggest.initPromise;
   1637        if ("WebSuggestions" in param) {
   1638          PoliciesUtils.setDefaultPref(
   1639            "browser.urlbar.suggest.quicksuggest.all",
   1640            param.WebSuggestions,
   1641            param.Locked
   1642          );
   1643        }
   1644        if ("SponsoredSuggestions" in param) {
   1645          PoliciesUtils.setDefaultPref(
   1646            "browser.urlbar.suggest.quicksuggest.sponsored",
   1647            param.SponsoredSuggestions,
   1648            param.Locked
   1649          );
   1650        }
   1651        // `ImproveSuggest` is deprecated and replaced with `OnlineEnabled`.
   1652        if ("OnlineEnabled" in param || "ImproveSuggest" in param) {
   1653          PoliciesUtils.setDefaultPref(
   1654            "browser.urlbar.quicksuggest.online.enabled",
   1655            param.OnlineEnabled ?? param.ImproveSuggest,
   1656            param.Locked
   1657          );
   1658        }
   1659      })();
   1660    },
   1661  },
   1662 
   1663  GenerativeAI: {
   1664    onBeforeAddons(manager, param) {
   1665      const defaultValue = "Enabled" in param ? param.Enabled : undefined;
   1666 
   1667      const features = [
   1668        ["Chatbot", ["browser.ml.chat.enabled", "browser.ml.chat.page"]],
   1669        ["LinkPreviews", ["browser.ml.linkPreview.optin"]],
   1670        ["TabGroups", ["browser.tabs.groups.smart.userEnabled"]],
   1671      ];
   1672 
   1673      for (const [key, prefs] of features) {
   1674        const value = key in param ? param[key] : defaultValue;
   1675        if (value !== undefined) {
   1676          for (const pref of prefs) {
   1677            PoliciesUtils.setDefaultPref(pref, value, param.Locked);
   1678          }
   1679        }
   1680      }
   1681    },
   1682  },
   1683 
   1684  GoToIntranetSiteForSingleWordEntryInAddressBar: {
   1685    onBeforeAddons(manager, param) {
   1686      setAndLockPref("browser.fixup.dns_first_for_single_words", param);
   1687    },
   1688  },
   1689 
   1690  Handlers: {
   1691    onBeforeAddons(manager, param) {
   1692      if ("mimeTypes" in param) {
   1693        for (let mimeType in param.mimeTypes) {
   1694          let mimeInfo = param.mimeTypes[mimeType];
   1695          let realMIMEInfo = lazy.gMIMEService.getFromTypeAndExtension(
   1696            mimeType,
   1697            ""
   1698          );
   1699          processMIMEInfo(mimeInfo, realMIMEInfo);
   1700        }
   1701      }
   1702      if ("extensions" in param) {
   1703        for (let extension in param.extensions) {
   1704          let mimeInfo = param.extensions[extension];
   1705          try {
   1706            let realMIMEInfo = lazy.gMIMEService.getFromTypeAndExtension(
   1707              "",
   1708              extension
   1709            );
   1710            processMIMEInfo(mimeInfo, realMIMEInfo);
   1711          } catch (e) {
   1712            lazy.log.error(`Invalid file extension (${extension})`);
   1713          }
   1714        }
   1715      }
   1716      if ("schemes" in param) {
   1717        for (let scheme in param.schemes) {
   1718          let handlerInfo = param.schemes[scheme];
   1719          let realHandlerInfo =
   1720            lazy.gExternalProtocolService.getProtocolHandlerInfo(scheme);
   1721          processMIMEInfo(handlerInfo, realHandlerInfo);
   1722        }
   1723      }
   1724    },
   1725  },
   1726 
   1727  HardwareAcceleration: {
   1728    onBeforeAddons(manager, param) {
   1729      if (!param) {
   1730        setAndLockPref("layers.acceleration.disabled", true);
   1731      }
   1732    },
   1733  },
   1734 
   1735  Homepage: {
   1736    onBeforeUIStartup(manager, param) {
   1737      if ("StartPage" in param && param.StartPage == "none") {
   1738        // For blank startpage, we use about:blank rather
   1739        // than messing with browser.startup.page
   1740        param.URL = new URL("about:blank");
   1741      }
   1742      // |homepages| will be a string containing a pipe-separated ('|') list of
   1743      // URLs because that is what the "Home page" section of about:preferences
   1744      // (and therefore what the pref |browser.startup.homepage|) accepts.
   1745      if ("URL" in param) {
   1746        let homepages = param.URL.href;
   1747        if (param.Additional && param.Additional.length) {
   1748          homepages += "|" + param.Additional.map(url => url.href).join("|");
   1749        }
   1750        PoliciesUtils.setDefaultPref(
   1751          "browser.startup.homepage",
   1752          homepages,
   1753          param.Locked
   1754        );
   1755        if (param.Locked) {
   1756          setAndLockPref(
   1757            "pref.browser.homepage.disable_button.current_page",
   1758            true
   1759          );
   1760          setAndLockPref(
   1761            "pref.browser.homepage.disable_button.bookmark_page",
   1762            true
   1763          );
   1764          setAndLockPref(
   1765            "pref.browser.homepage.disable_button.restore_default",
   1766            true
   1767          );
   1768        } else {
   1769          // Clear out old run once modification that is no longer used.
   1770          clearRunOnceModification("setHomepage");
   1771        }
   1772        // If a homepage has been set via policy, show the home button
   1773        if (param.URL != "about:blank") {
   1774          manager.disallowFeature("removeHomeButtonByDefault");
   1775        }
   1776      }
   1777      if (param.StartPage) {
   1778        let prefValue;
   1779        switch (param.StartPage) {
   1780          case "homepage":
   1781          case "homepage-locked":
   1782          case "none":
   1783            prefValue = 1;
   1784            break;
   1785          case "previous-session":
   1786            prefValue = 3;
   1787            break;
   1788        }
   1789        PoliciesUtils.setDefaultPref(
   1790          "browser.startup.page",
   1791          prefValue,
   1792          param.StartPage == "homepage-locked"
   1793        );
   1794      }
   1795    },
   1796  },
   1797 
   1798  HttpAllowlist: {
   1799    onBeforeAddons(manager, param) {
   1800      addAllowDenyPermissions("https-only-load-insecure", param);
   1801    },
   1802  },
   1803 
   1804  HttpsOnlyMode: {
   1805    onBeforeAddons(manager, param) {
   1806      switch (param) {
   1807        case "disallowed":
   1808          setAndLockPref("dom.security.https_only_mode", false);
   1809          break;
   1810        case "enabled":
   1811          PoliciesUtils.setDefaultPref("dom.security.https_only_mode", true);
   1812          break;
   1813        case "force_enabled":
   1814          setAndLockPref("dom.security.https_only_mode", true);
   1815          break;
   1816        case "allowed":
   1817          // The default case.
   1818          break;
   1819      }
   1820    },
   1821  },
   1822 
   1823  InstallAddonsPermission: {
   1824    onBeforeUIStartup(manager, param) {
   1825      if ("Allow" in param) {
   1826        addAllowDenyPermissions("install", param.Allow, null);
   1827      }
   1828      if ("Default" in param) {
   1829        setAndLockPref("xpinstall.enabled", param.Default);
   1830        if (!param.Default) {
   1831          manager.disallowFeature("installTemporaryAddon");
   1832          setAndLockPref(
   1833            "browser.newtabpage.activity-stream.asrouter.userprefs.cfr.addons",
   1834            false
   1835          );
   1836          setAndLockPref(
   1837            "browser.newtabpage.activity-stream.asrouter.userprefs.cfr.features",
   1838            false
   1839          );
   1840          manager.disallowFeature("xpinstall");
   1841        }
   1842      }
   1843    },
   1844  },
   1845 
   1846  LegacyProfiles: {
   1847    // Handled in nsToolkitProfileService.cpp (Windows only)
   1848  },
   1849 
   1850  LegacySameSiteCookieBehaviorEnabled: {
   1851    onBeforeAddons(manager, param) {
   1852      PoliciesUtils.setDefaultPref(
   1853        "network.cookie.sameSite.laxByDefault",
   1854        !param
   1855      );
   1856    },
   1857  },
   1858 
   1859  LegacySameSiteCookieBehaviorEnabledForDomainList: {
   1860    onBeforeAddons(manager, param) {
   1861      PoliciesUtils.setDefaultPref(
   1862        "network.cookie.sameSite.laxByDefault.disabledHosts",
   1863        param.join(",")
   1864      );
   1865    },
   1866  },
   1867 
   1868  LocalFileLinks: {
   1869    onBeforeAddons(manager, param) {
   1870      // If there are existing capabilities, lock them with the policy pref.
   1871      let policyNames = Services.prefs
   1872        .getCharPref("capability.policy.policynames", "")
   1873        .split(" ");
   1874      policyNames.push("localfilelinks_policy");
   1875      setAndLockPref("capability.policy.policynames", policyNames.join(" "));
   1876      setAndLockPref(
   1877        "capability.policy.localfilelinks_policy.checkloaduri.enabled",
   1878        "allAccess"
   1879      );
   1880      setAndLockPref(
   1881        "capability.policy.localfilelinks_policy.sites",
   1882        param.join(" ")
   1883      );
   1884    },
   1885  },
   1886 
   1887  LocalNetworkAccess: {
   1888    onBeforeAddons(manager, param) {
   1889      // Only process if "Enabled" is explicitly specified
   1890      if ("Enabled" in param) {
   1891        PoliciesUtils.setDefaultPref(
   1892          "network.lna.enabled",
   1893          param.Enabled,
   1894          param.Locked
   1895        );
   1896 
   1897        if (param.Enabled === false) {
   1898          // If LNA is explicitly disabled, disable other features too
   1899          PoliciesUtils.setDefaultPref(
   1900            "network.lna.block_trackers",
   1901            false,
   1902            param.Locked
   1903          );
   1904          PoliciesUtils.setDefaultPref(
   1905            "network.lna.blocking",
   1906            false,
   1907            param.Locked
   1908          );
   1909        } else {
   1910          // LNA is enabled - handle fine-grained controls
   1911          // For backward compatibility, default to true if not specified
   1912          let blockTrackers =
   1913            "BlockTrackers" in param ? param.BlockTrackers : true;
   1914          let enablePrompting =
   1915            "EnablePrompting" in param ? param.EnablePrompting : true;
   1916 
   1917          PoliciesUtils.setDefaultPref(
   1918            "network.lna.block_trackers",
   1919            blockTrackers,
   1920            param.Locked
   1921          );
   1922          PoliciesUtils.setDefaultPref(
   1923            "network.lna.blocking",
   1924            enablePrompting,
   1925            param.Locked
   1926          );
   1927        }
   1928      }
   1929 
   1930      // Handle SkipDomains separately (can be set independently of Enabled)
   1931      if ("SkipDomains" in param && Array.isArray(param.SkipDomains)) {
   1932        let skipDomainsValue = param.SkipDomains.join(",");
   1933        PoliciesUtils.setDefaultPref(
   1934          "network.lna.skip-domains",
   1935          skipDomainsValue,
   1936          param.Locked
   1937        );
   1938      }
   1939    },
   1940  },
   1941 
   1942  ManagedBookmarks: {},
   1943 
   1944  ManualAppUpdateOnly: {
   1945    onBeforeAddons(manager, param) {
   1946      if (param) {
   1947        manager.disallowFeature("autoAppUpdateChecking");
   1948      }
   1949    },
   1950  },
   1951 
   1952  MicrosoftEntraSSO: {
   1953    onBeforeAddons(manager, param) {
   1954      setAndLockPref("network.http.microsoft-entra-sso.enabled", param);
   1955    },
   1956  },
   1957 
   1958  NetworkPrediction: {
   1959    onBeforeAddons(manager, param) {
   1960      setAndLockPref("network.dns.disablePrefetch", !param);
   1961      setAndLockPref("network.dns.disablePrefetchFromHTTPS", !param);
   1962    },
   1963  },
   1964 
   1965  NewTabPage: {
   1966    onBeforeAddons(manager, param) {
   1967      setAndLockPref("browser.newtabpage.enabled", param);
   1968    },
   1969  },
   1970 
   1971  NoDefaultBookmarks: {
   1972    onProfileAfterChange(manager, param) {
   1973      if (param) {
   1974        manager.disallowFeature("defaultBookmarks");
   1975      }
   1976    },
   1977  },
   1978 
   1979  OfferToSaveLogins: {
   1980    onBeforeUIStartup(manager, param) {
   1981      setAndLockPref("signon.rememberSignons", param);
   1982      setAndLockPref("services.passwordSavingEnabled", param);
   1983    },
   1984  },
   1985 
   1986  OfferToSaveLoginsDefault: {
   1987    onBeforeUIStartup(manager, param) {
   1988      let policies = Services.policies.getActivePolicies();
   1989      if ("OfferToSaveLogins" in policies) {
   1990        lazy.log.error(
   1991          `OfferToSaveLoginsDefault ignored because OfferToSaveLogins is present.`
   1992        );
   1993      } else {
   1994        PoliciesUtils.setDefaultPref("signon.rememberSignons", param);
   1995      }
   1996    },
   1997  },
   1998 
   1999  OverrideFirstRunPage: {
   2000    onProfileAfterChange(manager, param) {
   2001      let url = param ? param : "";
   2002      setAndLockPref("startup.homepage_welcome_url", url);
   2003      setAndLockPref("browser.aboutwelcome.enabled", false);
   2004    },
   2005  },
   2006 
   2007  OverridePostUpdatePage: {
   2008    onProfileAfterChange(manager, param) {
   2009      let url = param ? param.href : "";
   2010      setAndLockPref("startup.homepage_override_url", url);
   2011      // The pref startup.homepage_override_url is only used
   2012      // as a fallback when the update.xml file hasn't provided
   2013      // a specific post-update URL.
   2014      manager.disallowFeature("postUpdateCustomPage");
   2015    },
   2016  },
   2017 
   2018  PasswordManagerEnabled: {
   2019    onBeforeUIStartup(manager, param) {
   2020      if (!param) {
   2021        blockAboutPage(manager, "about:logins", true);
   2022        setAndLockPref("pref.privacy.disable_button.view_passwords", true);
   2023      }
   2024      setAndLockPref("signon.rememberSignons", param);
   2025    },
   2026  },
   2027 
   2028  PasswordManagerExceptions: {
   2029    onBeforeUIStartup(manager, param) {
   2030      addAllowDenyPermissions("login-saving", null, param);
   2031    },
   2032  },
   2033 
   2034  PDFjs: {
   2035    onBeforeAddons(manager, param) {
   2036      if ("Enabled" in param) {
   2037        setAndLockPref("pdfjs.disabled", !param.Enabled);
   2038      }
   2039      if ("EnablePermissions" in param) {
   2040        setAndLockPref("pdfjs.enablePermissions", param.EnablePermissions);
   2041      }
   2042    },
   2043  },
   2044 
   2045  Permissions: {
   2046    onBeforeUIStartup(manager, param) {
   2047      if (param.Camera) {
   2048        addAllowDenyPermissions(
   2049          "camera",
   2050          param.Camera.Allow,
   2051          param.Camera.Block
   2052        );
   2053        setDefaultPermission("camera", param.Camera);
   2054      }
   2055 
   2056      if (param.Microphone) {
   2057        addAllowDenyPermissions(
   2058          "microphone",
   2059          param.Microphone.Allow,
   2060          param.Microphone.Block
   2061        );
   2062        setDefaultPermission("microphone", param.Microphone);
   2063      }
   2064 
   2065      if (param.Autoplay) {
   2066        addAllowDenyPermissions(
   2067          "autoplay-media",
   2068          param.Autoplay.Allow,
   2069          param.Autoplay.Block
   2070        );
   2071        if ("Default" in param.Autoplay) {
   2072          let prefValue;
   2073          switch (param.Autoplay.Default) {
   2074            case "allow-audio-video":
   2075              prefValue = 0;
   2076              break;
   2077            case "block-audio":
   2078              prefValue = 1;
   2079              break;
   2080            case "block-audio-video":
   2081              prefValue = 5;
   2082              break;
   2083          }
   2084          PoliciesUtils.setDefaultPref(
   2085            "media.autoplay.default",
   2086            prefValue,
   2087            param.Autoplay.Locked
   2088          );
   2089        }
   2090      }
   2091 
   2092      if (param.Location) {
   2093        addAllowDenyPermissions(
   2094          "geo",
   2095          param.Location.Allow,
   2096          param.Location.Block
   2097        );
   2098        setDefaultPermission("geo", param.Location);
   2099      }
   2100 
   2101      if (param.Notifications) {
   2102        addAllowDenyPermissions(
   2103          "desktop-notification",
   2104          param.Notifications.Allow,
   2105          param.Notifications.Block
   2106        );
   2107        setDefaultPermission("desktop-notification", param.Notifications);
   2108      }
   2109 
   2110      if ("VirtualReality" in param) {
   2111        addAllowDenyPermissions(
   2112          "xr",
   2113          param.VirtualReality.Allow,
   2114          param.VirtualReality.Block
   2115        );
   2116        setDefaultPermission("xr", param.VirtualReality);
   2117      }
   2118 
   2119      if ("ScreenShare" in param) {
   2120        addAllowDenyPermissions(
   2121          "screen",
   2122          param.ScreenShare.Allow,
   2123          param.ScreenShare.Block
   2124        );
   2125        setDefaultPermission("screen", param.ScreenShare);
   2126      }
   2127    },
   2128  },
   2129 
   2130  PictureInPicture: {
   2131    onBeforeAddons(manager, param) {
   2132      if ("Enabled" in param) {
   2133        PoliciesUtils.setDefaultPref(
   2134          "media.videocontrols.picture-in-picture.video-toggle.enabled",
   2135          param.Enabled
   2136        );
   2137      }
   2138      if (param.Locked) {
   2139        Services.prefs.lockPref(
   2140          "media.videocontrols.picture-in-picture.video-toggle.enabled"
   2141        );
   2142      }
   2143    },
   2144  },
   2145 
   2146  PopupBlocking: {
   2147    onBeforeUIStartup(manager, param) {
   2148      addAllowDenyPermissions("popup", param.Allow, null);
   2149 
   2150      if (param.Locked) {
   2151        let blockValue = true;
   2152        if (param.Default !== undefined && !param.Default) {
   2153          blockValue = false;
   2154        }
   2155        setAndLockPref("dom.disable_open_during_load", blockValue);
   2156      } else if (param.Default !== undefined) {
   2157        PoliciesUtils.setDefaultPref(
   2158          "dom.disable_open_during_load",
   2159          !!param.Default
   2160        );
   2161      }
   2162    },
   2163  },
   2164 
   2165  PostQuantumKeyAgreementEnabled: {
   2166    onBeforeAddons(manager, param) {
   2167      setAndLockPref("network.http.http3.enable_kyber", param);
   2168      setAndLockPref("security.tls.enable_kyber", param);
   2169      setAndLockPref("media.webrtc.enable_pq_hybrid_kex", param);
   2170    },
   2171  },
   2172 
   2173  Preferences: {
   2174    onBeforeAddons(manager, param) {
   2175      let allowedPrefixes = [
   2176        "accessibility.",
   2177        "alerts.",
   2178        "app.update.",
   2179        "browser.",
   2180        "datareporting.policy.",
   2181        "dom.",
   2182        "extensions.",
   2183        "general.autoScroll",
   2184        "general.smoothScroll",
   2185        "geo.",
   2186        "gfx.",
   2187        "identity.fxaccounts.toolbar.",
   2188        "intl.",
   2189        "keyword.enabled",
   2190        "layers.",
   2191        "layout.",
   2192        "mathml.disabled",
   2193        "media.",
   2194        "network.",
   2195        "pdfjs.",
   2196        "places.",
   2197        "pref.",
   2198        "print.",
   2199        "privacy.baselineFingerprintingProtection",
   2200        "privacy.fingerprintingProtection",
   2201        "privacy.globalprivacycontrol.enabled",
   2202        "privacy.userContext.enabled",
   2203        "privacy.userContext.ui.enabled",
   2204        "signon.",
   2205        "spellchecker.",
   2206        "svg.context-properties.content.enabled",
   2207        "svg.disabled",
   2208        "toolkit.legacyUserProfileCustomizations.stylesheets",
   2209        "ui.",
   2210        "webgl.disabled",
   2211        "webgl.force-enabled",
   2212        "widget.",
   2213        "xpinstall.enabled",
   2214        "xpinstall.whitelist.required",
   2215      ];
   2216      if (!AppConstants.MOZ_REQUIRE_SIGNING) {
   2217        allowedPrefixes.push("xpinstall.signatures.required");
   2218      }
   2219      const allowedSecurityPrefs = [
   2220        "security.block_fileuri_script_with_wrong_mime",
   2221        "security.csp.reporting.enabled",
   2222        "security.default_personal_cert",
   2223        "security.disable_button.openCertManager",
   2224        "security.disable_button.openDeviceManager",
   2225        "security.insecure_connection_text.enabled",
   2226        "security.insecure_connection_text.pbmode.enabled",
   2227        "security.mixed_content.block_active_content",
   2228        "security.mixed_content.block_display_content",
   2229        "security.mixed_content.upgrade_display_content",
   2230        "security.osclientcerts.autoload",
   2231        "security.OCSP.enabled",
   2232        "security.OCSP.require",
   2233        "security.pki.certificate_transparency.disable_for_hosts",
   2234        "security.pki.certificate_transparency.disable_for_spki_hashes",
   2235        "security.pki.certificate_transparency.mode",
   2236        "security.ssl.enable_ocsp_stapling",
   2237        "security.ssl.errorReporting.enabled",
   2238        "security.ssl.require_safe_negotiation",
   2239        "security.tls.enable_0rtt_data",
   2240        "security.tls.hello_downgrade_check",
   2241        "security.tls.version.enable-deprecated",
   2242        "security.warn_submit_secure_to_insecure",
   2243        "security.webauthn.always_allow_direct_attestation",
   2244      ];
   2245      const blockedPrefs = [
   2246        "app.update.channel",
   2247        "app.update.lastUpdateTime",
   2248        "app.update.migrated",
   2249        "browser.vpn_promo.disallowed_regions",
   2250      ];
   2251 
   2252      for (let preference in param) {
   2253        if (blockedPrefs.includes(preference)) {
   2254          lazy.log.error(
   2255            `Unable to set preference ${preference}. Preference not allowed for security reasons.`
   2256          );
   2257          continue;
   2258        }
   2259        if (preference.startsWith("security.")) {
   2260          if (!allowedSecurityPrefs.includes(preference)) {
   2261            lazy.log.error(
   2262              `Unable to set preference ${preference}. Preference not allowed for security reasons.`
   2263            );
   2264            continue;
   2265          }
   2266        } else if (
   2267          !allowedPrefixes.some(prefix => preference.startsWith(prefix))
   2268        ) {
   2269          lazy.log.error(
   2270            `Unable to set preference ${preference}. Preference not allowed for stability reasons.`
   2271          );
   2272          continue;
   2273        }
   2274        if (typeof param[preference] != "object") {
   2275          // Legacy policy preferences
   2276          setAndLockPref(preference, param[preference]);
   2277        } else {
   2278          if (param[preference].Status == "clear") {
   2279            Services.prefs.clearUserPref(preference);
   2280            continue;
   2281          }
   2282 
   2283          let prefBranch;
   2284          if (param[preference].Status == "user") {
   2285            prefBranch = Services.prefs;
   2286          } else {
   2287            prefBranch = Services.prefs.getDefaultBranch("");
   2288          }
   2289 
   2290          // Prefs that were previously locked should stay locked,
   2291          // but policy can update the value.
   2292          let prefWasLocked = Services.prefs.prefIsLocked(preference);
   2293          if (prefWasLocked) {
   2294            Services.prefs.unlockPref(preference);
   2295          }
   2296          try {
   2297            let prefType =
   2298              param[preference].Type || typeof param[preference].Value;
   2299            switch (prefType) {
   2300              case "boolean":
   2301                prefBranch.setBoolPref(preference, param[preference].Value);
   2302                break;
   2303 
   2304              case "number":
   2305                if (!Number.isInteger(param[preference].Value)) {
   2306                  throw new Error(`Non-integer value for ${preference}`);
   2307                }
   2308 
   2309                // This is ugly, but necessary. On Windows GPO and macOS
   2310                // configs, booleans are converted to 0/1. In the previous
   2311                // Preferences implementation, the schema took care of
   2312                // automatically converting these values to booleans.
   2313                // Since we allow arbitrary prefs now, we have to do
   2314                // something different. See bug 1666836, 1668374, and 1872267.
   2315 
   2316                // We only set something as int if it was explicit in policy,
   2317                // the same type as the default pref, or NOT 0/1. Otherwise
   2318                // we set it as bool.
   2319                if (
   2320                  param[preference].Type == "number" ||
   2321                  prefBranch.getPrefType(preference) == prefBranch.PREF_INT ||
   2322                  ![0, 1].includes(param[preference].Value)
   2323                ) {
   2324                  prefBranch.setIntPref(preference, param[preference].Value);
   2325                } else {
   2326                  prefBranch.setBoolPref(preference, !!param[preference].Value);
   2327                }
   2328                break;
   2329 
   2330              case "string":
   2331                prefBranch.setStringPref(preference, param[preference].Value);
   2332                break;
   2333            }
   2334          } catch (e) {
   2335            lazy.log.error(
   2336              `Unable to set preference ${preference}. Probable type mismatch.`
   2337            );
   2338          }
   2339 
   2340          if (param[preference].Status == "locked" || prefWasLocked) {
   2341            Services.prefs.lockPref(preference);
   2342          }
   2343        }
   2344      }
   2345    },
   2346  },
   2347 
   2348  PrimaryPassword: {
   2349    onAllWindowsRestored(manager, param) {
   2350      if (param) {
   2351        manager.disallowFeature("removeMasterPassword");
   2352      } else {
   2353        manager.disallowFeature("createMasterPassword");
   2354      }
   2355    },
   2356  },
   2357 
   2358  PrintingEnabled: {
   2359    onBeforeUIStartup(manager, param) {
   2360      setAndLockPref("print.enabled", param);
   2361    },
   2362  },
   2363 
   2364  PrivateBrowsingModeAvailability: {
   2365    onBeforeAddons(manager, param) {
   2366      switch (param) {
   2367        // Private Browsing mode disabled
   2368        case 1:
   2369          manager.disallowFeature("privatebrowsing");
   2370          blockAboutPage(manager, "about:privatebrowsing", true);
   2371          setAndLockPref("browser.privatebrowsing.autostart", false);
   2372          break;
   2373        // Private Browsing mode forced
   2374        case 2:
   2375          setAndLockPref("browser.privatebrowsing.autostart", true);
   2376          break;
   2377        // Private Browsing mode available
   2378        case 0:
   2379          break;
   2380      }
   2381    },
   2382  },
   2383 
   2384  PromptForDownloadLocation: {
   2385    onBeforeAddons(manager, param) {
   2386      setAndLockPref("browser.download.useDownloadDir", !param);
   2387    },
   2388  },
   2389 
   2390  Proxy: {
   2391    onBeforeAddons(manager, param) {
   2392      if (param.Locked) {
   2393        manager.disallowFeature("changeProxySettings");
   2394      }
   2395      lazy.ProxyPolicies.configureProxySettings(
   2396        param,
   2397        PoliciesUtils.setDefaultPref
   2398      );
   2399    },
   2400  },
   2401 
   2402  RequestedLocales: {
   2403    onBeforeAddons(manager, param) {
   2404      let requestedLocales;
   2405      if (Array.isArray(param)) {
   2406        requestedLocales = param;
   2407      } else if (param) {
   2408        requestedLocales = param.split(",");
   2409      } else {
   2410        requestedLocales = [];
   2411      }
   2412      runOncePerModification(
   2413        "requestedLocales",
   2414        JSON.stringify(requestedLocales),
   2415        () => {
   2416          Services.locale.requestedLocales = requestedLocales;
   2417        }
   2418      );
   2419    },
   2420  },
   2421 
   2422  SanitizeOnShutdown: {
   2423    onBeforeUIStartup(manager, param) {
   2424      if (typeof param === "boolean") {
   2425        setAndLockPref("privacy.sanitize.sanitizeOnShutdown", param);
   2426        setAndLockPref("privacy.clearOnShutdown.cache", param);
   2427        setAndLockPref("privacy.clearOnShutdown.cookies", param);
   2428        setAndLockPref("privacy.clearOnShutdown.downloads", param);
   2429        setAndLockPref("privacy.clearOnShutdown.formdata", param);
   2430        setAndLockPref("privacy.clearOnShutdown.history", param);
   2431        setAndLockPref("privacy.clearOnShutdown.sessions", param);
   2432        setAndLockPref("privacy.clearOnShutdown.siteSettings", param);
   2433        setAndLockPref("privacy.clearOnShutdown.offlineApps", param);
   2434        setAndLockPref(
   2435          "privacy.clearOnShutdown_v2.browsingHistoryAndDownloads",
   2436          param
   2437        );
   2438        setAndLockPref("privacy.clearOnShutdown_v2.cookiesAndStorage", param);
   2439        setAndLockPref("privacy.clearOnShutdown_v2.cache", param);
   2440        setAndLockPref("privacy.clearOnShutdown_v2.siteSettings", param);
   2441        setAndLockPref("privacy.clearOnShutdown_v2.formdata", param);
   2442      } else {
   2443        let locked = true;
   2444        // Needed to preserve original behavior in perpetuity.
   2445        let lockDefaultPrefs = true;
   2446        if ("Locked" in param) {
   2447          locked = param.Locked;
   2448          lockDefaultPrefs = false;
   2449        }
   2450        PoliciesUtils.setDefaultPref(
   2451          "privacy.sanitize.sanitizeOnShutdown",
   2452          true,
   2453          locked
   2454        );
   2455        if ("Cache" in param) {
   2456          PoliciesUtils.setDefaultPref(
   2457            "privacy.clearOnShutdown.cache",
   2458            param.Cache,
   2459            locked
   2460          );
   2461          PoliciesUtils.setDefaultPref(
   2462            "privacy.clearOnShutdown_v2.cache",
   2463            param.Cache,
   2464            locked
   2465          );
   2466        } else {
   2467          PoliciesUtils.setDefaultPref(
   2468            "privacy.clearOnShutdown.cache",
   2469            false,
   2470            lockDefaultPrefs
   2471          );
   2472          PoliciesUtils.setDefaultPref(
   2473            "privacy.clearOnShutdown_v2.cache",
   2474            false,
   2475            lockDefaultPrefs
   2476          );
   2477        }
   2478        if ("Cookies" in param) {
   2479          PoliciesUtils.setDefaultPref(
   2480            "privacy.clearOnShutdown.cookies",
   2481            param.Cookies,
   2482            locked
   2483          );
   2484 
   2485          // We set cookiesAndStorage to follow lock and pref
   2486          // settings for cookies, and deprecate offlineApps
   2487          // and sessions in the new clear on shutdown dialog - Bug 1853996
   2488          PoliciesUtils.setDefaultPref(
   2489            "privacy.clearOnShutdown_v2.cookiesAndStorage",
   2490            param.Cookies,
   2491            locked
   2492          );
   2493        } else {
   2494          PoliciesUtils.setDefaultPref(
   2495            "privacy.clearOnShutdown.cookies",
   2496            false,
   2497            lockDefaultPrefs
   2498          );
   2499          PoliciesUtils.setDefaultPref(
   2500            "privacy.clearOnShutdown_v2.cookiesAndStorage",
   2501            false,
   2502            lockDefaultPrefs
   2503          );
   2504        }
   2505        if ("Downloads" in param) {
   2506          PoliciesUtils.setDefaultPref(
   2507            "privacy.clearOnShutdown.downloads",
   2508            param.Downloads,
   2509            locked
   2510          );
   2511        } else {
   2512          PoliciesUtils.setDefaultPref(
   2513            "privacy.clearOnShutdown.downloads",
   2514            false,
   2515            lockDefaultPrefs
   2516          );
   2517        }
   2518        if ("FormData" in param) {
   2519          PoliciesUtils.setDefaultPref(
   2520            "privacy.clearOnShutdown.formdata",
   2521            param.FormData,
   2522            locked
   2523          );
   2524 
   2525          PoliciesUtils.setDefaultPref(
   2526            "privacy.clearOnShutdown_v2.formdata",
   2527            param.FormData,
   2528            locked
   2529          );
   2530        } else {
   2531          PoliciesUtils.setDefaultPref(
   2532            "privacy.clearOnShutdown.formdata",
   2533            false,
   2534            lockDefaultPrefs
   2535          );
   2536 
   2537          PoliciesUtils.setDefaultPref(
   2538            "privacy.clearOnShutdown_v2.formdata",
   2539            false,
   2540            lockDefaultPrefs
   2541          );
   2542        }
   2543        if ("History" in param) {
   2544          PoliciesUtils.setDefaultPref(
   2545            "privacy.clearOnShutdown.history",
   2546            param.History,
   2547            locked
   2548          );
   2549 
   2550          // We set browsingHistoryAndDownloads to follow lock and pref
   2551          // settings for history, and deprecate downloads
   2552          // in the new clear on shutdown dialog - Bug 1853996
   2553          PoliciesUtils.setDefaultPref(
   2554            "privacy.clearOnShutdown_v2.browsingHistoryAndDownloads",
   2555            param.History,
   2556            locked
   2557          );
   2558        } else {
   2559          PoliciesUtils.setDefaultPref(
   2560            "privacy.clearOnShutdown.history",
   2561            false,
   2562            lockDefaultPrefs
   2563          );
   2564          PoliciesUtils.setDefaultPref(
   2565            "privacy.clearOnShutdown_v2.browsingHistoryAndDownloads",
   2566            false,
   2567            lockDefaultPrefs
   2568          );
   2569        }
   2570        if ("Sessions" in param) {
   2571          PoliciesUtils.setDefaultPref(
   2572            "privacy.clearOnShutdown.sessions",
   2573            param.Sessions,
   2574            locked
   2575          );
   2576        } else {
   2577          PoliciesUtils.setDefaultPref(
   2578            "privacy.clearOnShutdown.sessions",
   2579            false,
   2580            lockDefaultPrefs
   2581          );
   2582        }
   2583        if ("SiteSettings" in param) {
   2584          PoliciesUtils.setDefaultPref(
   2585            "privacy.clearOnShutdown.siteSettings",
   2586            param.SiteSettings,
   2587            locked
   2588          );
   2589          PoliciesUtils.setDefaultPref(
   2590            "privacy.clearOnShutdown_v2.siteSettings",
   2591            param.SiteSettings,
   2592            locked
   2593          );
   2594        }
   2595        if ("OfflineApps" in param) {
   2596          PoliciesUtils.setDefaultPref(
   2597            "privacy.clearOnShutdown.offlineApps",
   2598            param.OfflineApps,
   2599            locked
   2600          );
   2601        }
   2602      }
   2603    },
   2604  },
   2605 
   2606  SearchBar: {
   2607    onAllWindowsRestored(manager, param) {
   2608      // This policy is meant to change the default behavior, not to force it.
   2609      // If this policy was already applied and the user chose move the search
   2610      // bar, don't move it again.
   2611      runOncePerModification("searchInNavBar", param, () => {
   2612        if (param == "separate") {
   2613          lazy.CustomizableUI.addWidgetToArea(
   2614            "search-container",
   2615            lazy.CustomizableUI.AREA_NAVBAR,
   2616            lazy.CustomizableUI.getPlacementOfWidget("urlbar-container")
   2617              .position + 1
   2618          );
   2619        } else if (param == "unified") {
   2620          lazy.CustomizableUI.removeWidgetFromArea("search-container");
   2621        }
   2622      });
   2623    },
   2624  },
   2625 
   2626  SearchEngines: {
   2627    onBeforeUIStartup(manager, param) {
   2628      if (param.PreventInstalls) {
   2629        manager.disallowFeature("installSearchEngine", true);
   2630      }
   2631    },
   2632    onAllWindowsRestored(manager, param) {
   2633      Services.search.init().then(async () => {
   2634        // Adding of engines is handled by the SearchService in the init().
   2635        // Remove can happen after those are added - no engines are allowed
   2636        // to replace the application provided engines, even if they have been
   2637        // removed.
   2638        if (param.Remove) {
   2639          // Only rerun if the list of engine names has changed.
   2640          await runOncePerModification(
   2641            "removeSearchEngines",
   2642            JSON.stringify(param.Remove),
   2643            async function () {
   2644              for (let engineName of param.Remove) {
   2645                let engine = Services.search.getEngineByName(engineName);
   2646                if (engine) {
   2647                  try {
   2648                    await Services.search.removeEngine(
   2649                      engine,
   2650                      Ci.nsISearchService.CHANGE_REASON_ENTERPRISE
   2651                    );
   2652                  } catch (ex) {
   2653                    lazy.log.error("Unable to remove the search engine", ex);
   2654                  }
   2655                }
   2656              }
   2657            }
   2658          );
   2659        }
   2660        if (param.Default) {
   2661          await runOncePerModification(
   2662            "setDefaultSearchEngine",
   2663            param.Default,
   2664            async () => {
   2665              let defaultEngine;
   2666              try {
   2667                defaultEngine = Services.search.getEngineByName(param.Default);
   2668                if (!defaultEngine) {
   2669                  throw new Error("No engine by that name could be found");
   2670                }
   2671              } catch (ex) {
   2672                lazy.log.error(
   2673                  `Search engine lookup failed when attempting to set ` +
   2674                    `the default engine. Requested engine was ` +
   2675                    `"${param.Default}".`,
   2676                  ex
   2677                );
   2678              }
   2679              if (defaultEngine) {
   2680                try {
   2681                  await Services.search.setDefault(
   2682                    defaultEngine,
   2683                    Ci.nsISearchService.CHANGE_REASON_ENTERPRISE
   2684                  );
   2685                } catch (ex) {
   2686                  lazy.log.error("Unable to set the default search engine", ex);
   2687                }
   2688              }
   2689            }
   2690          );
   2691        }
   2692        if (param.DefaultPrivate) {
   2693          await runOncePerModification(
   2694            "setDefaultPrivateSearchEngine",
   2695            param.DefaultPrivate,
   2696            async () => {
   2697              let defaultPrivateEngine;
   2698              try {
   2699                defaultPrivateEngine = Services.search.getEngineByName(
   2700                  param.DefaultPrivate
   2701                );
   2702                if (!defaultPrivateEngine) {
   2703                  throw new Error("No engine by that name could be found");
   2704                }
   2705              } catch (ex) {
   2706                lazy.log.error(
   2707                  `Search engine lookup failed when attempting to set ` +
   2708                    `the default private engine. Requested engine was ` +
   2709                    `"${param.DefaultPrivate}".`,
   2710                  ex
   2711                );
   2712              }
   2713              if (defaultPrivateEngine) {
   2714                try {
   2715                  await Services.search.setDefaultPrivate(
   2716                    defaultPrivateEngine,
   2717                    Ci.nsISearchService.CHANGE_REASON_ENTERPRISE
   2718                  );
   2719                } catch (ex) {
   2720                  lazy.log.error(
   2721                    "Unable to set the default private search engine",
   2722                    ex
   2723                  );
   2724                }
   2725              }
   2726            }
   2727          );
   2728        }
   2729      });
   2730    },
   2731  },
   2732 
   2733  SearchSuggestEnabled: {
   2734    onBeforeAddons(manager, param) {
   2735      setAndLockPref("browser.urlbar.suggest.searches", param);
   2736      setAndLockPref("browser.search.suggest.enabled", param);
   2737    },
   2738  },
   2739 
   2740  SecurityDevices: {
   2741    onProfileAfterChange(manager, param) {
   2742      let pkcs11db = Cc["@mozilla.org/security/pkcs11moduledb;1"].getService(
   2743        Ci.nsIPKCS11ModuleDB
   2744      );
   2745      let securityDevices;
   2746      if (param.Add || param.Delete) {
   2747        // We're using the new syntax.
   2748        securityDevices = param.Add;
   2749        if (param.Delete) {
   2750          for (let deviceName of param.Delete) {
   2751            try {
   2752              pkcs11db.deleteModule(deviceName);
   2753            } catch (e) {
   2754              // Ignoring errors here since it might stick around in policy
   2755              // after removing. Alternative would be to listModules and
   2756              // make sure it's there before removing, but that seems
   2757              // like unnecessary work.
   2758            }
   2759          }
   2760        }
   2761      } else {
   2762        securityDevices = param;
   2763      }
   2764      if (!securityDevices) {
   2765        return;
   2766      }
   2767      for (let deviceName in securityDevices) {
   2768        let foundModule = false;
   2769        for (let module of pkcs11db.listModules()) {
   2770          if (module && module.libName === securityDevices[deviceName]) {
   2771            foundModule = true;
   2772            break;
   2773          }
   2774        }
   2775        if (foundModule) {
   2776          continue;
   2777        }
   2778        try {
   2779          pkcs11db.addModule(deviceName, securityDevices[deviceName], 0, 0);
   2780        } catch (ex) {
   2781          lazy.log.error(`Unable to add security device ${deviceName}`);
   2782          lazy.log.debug(ex);
   2783        }
   2784      }
   2785    },
   2786  },
   2787 
   2788  ShowHomeButton: {
   2789    onBeforeAddons(manager, param) {
   2790      if (param) {
   2791        manager.disallowFeature("removeHomeButtonByDefault");
   2792      }
   2793    },
   2794    onAllWindowsRestored(manager, param) {
   2795      if (param) {
   2796        let homeButtonPlacement =
   2797          lazy.CustomizableUI.getPlacementOfWidget("home-button");
   2798        if (!homeButtonPlacement) {
   2799          let placement =
   2800            lazy.CustomizableUI.getPlacementOfWidget("forward-button");
   2801          lazy.CustomizableUI.addWidgetToArea(
   2802            "home-button",
   2803            lazy.CustomizableUI.AREA_NAVBAR,
   2804            placement.position + 2
   2805          );
   2806        }
   2807      } else {
   2808        lazy.CustomizableUI.removeWidgetFromArea("home-button");
   2809      }
   2810    },
   2811  },
   2812 
   2813  SkipTermsOfUse: {
   2814    onBeforeAddons(manager, param) {
   2815      if (param) {
   2816        setAndLockPref("termsofuse.acceptedVersion", 999);
   2817        setAndLockPref("termsofuse.acceptedDate", Date.now().toString());
   2818      }
   2819    },
   2820  },
   2821 
   2822  SSLVersionMax: {
   2823    onBeforeAddons(manager, param) {
   2824      let tlsVersion;
   2825      switch (param) {
   2826        case "tls1":
   2827          tlsVersion = 1;
   2828          break;
   2829        case "tls1.1":
   2830          tlsVersion = 2;
   2831          break;
   2832        case "tls1.2":
   2833          tlsVersion = 3;
   2834          break;
   2835        case "tls1.3":
   2836          tlsVersion = 4;
   2837          break;
   2838      }
   2839      setAndLockPref("security.tls.version.max", tlsVersion);
   2840    },
   2841  },
   2842 
   2843  SSLVersionMin: {
   2844    onBeforeAddons(manager, param) {
   2845      let tlsVersion;
   2846      switch (param) {
   2847        case "tls1":
   2848          tlsVersion = 1;
   2849          break;
   2850        case "tls1.1":
   2851          tlsVersion = 2;
   2852          break;
   2853        case "tls1.2":
   2854          tlsVersion = 3;
   2855          break;
   2856        case "tls1.3":
   2857          tlsVersion = 4;
   2858          break;
   2859      }
   2860      setAndLockPref("security.tls.version.min", tlsVersion);
   2861    },
   2862  },
   2863 
   2864  StartDownloadsInTempDirectory: {
   2865    onBeforeAddons(manager, param) {
   2866      setAndLockPref("browser.download.start_downloads_in_tmp_dir", param);
   2867    },
   2868  },
   2869 
   2870  SupportMenu: {
   2871    onProfileAfterChange(manager, param) {
   2872      manager.setSupportMenu(param);
   2873    },
   2874  },
   2875 
   2876  TranslateEnabled: {
   2877    onBeforeAddons(manager, param) {
   2878      setAndLockPref("browser.translations.enable", param);
   2879    },
   2880  },
   2881 
   2882  UserMessaging: {
   2883    onBeforeAddons(manager, param) {
   2884      if ("ExtensionRecommendations" in param) {
   2885        PoliciesUtils.setDefaultPref(
   2886          "browser.newtabpage.activity-stream.asrouter.userprefs.cfr.addons",
   2887          param.ExtensionRecommendations,
   2888          param.Locked
   2889        );
   2890      }
   2891      if ("FeatureRecommendations" in param) {
   2892        PoliciesUtils.setDefaultPref(
   2893          "browser.newtabpage.activity-stream.asrouter.userprefs.cfr.features",
   2894          param.FeatureRecommendations,
   2895          param.Locked
   2896        );
   2897 
   2898        // We use the mostRecentTargetLanguages pref to control the
   2899        // translations panel intro. Setting a language value simulates a
   2900        // first translation, which skips the intro panel for users with
   2901        // FeatureRecommendations disabled.
   2902        const topWebPreferredLanguage = Services.locale.acceptLanguages
   2903          .split(",")[0]
   2904          .trim();
   2905 
   2906        const preferredLanguage = topWebPreferredLanguage.length
   2907          ? topWebPreferredLanguage
   2908          : Services.locale.appLocaleAsBCP47;
   2909 
   2910        PoliciesUtils.setDefaultPref(
   2911          "browser.translations.mostRecentTargetLanguages",
   2912          param.FeatureRecommendations ? "" : preferredLanguage,
   2913          param.Locked
   2914        );
   2915      }
   2916      if ("UrlbarInterventions" in param && !param.UrlbarInterventions) {
   2917        manager.disallowFeature("urlbarinterventions");
   2918      }
   2919      if ("SkipOnboarding" in param) {
   2920        PoliciesUtils.setDefaultPref(
   2921          "browser.aboutwelcome.enabled",
   2922          !param.SkipOnboarding,
   2923          param.Locked
   2924        );
   2925      }
   2926      if ("MoreFromMozilla" in param) {
   2927        PoliciesUtils.setDefaultPref(
   2928          "browser.preferences.moreFromMozilla",
   2929          param.MoreFromMozilla,
   2930          param.Locked
   2931        );
   2932      }
   2933      if ("FirefoxLabs" in param && !param.FirefoxLabs) {
   2934        manager.disallowFeature("FirefoxLabs");
   2935      }
   2936    },
   2937  },
   2938 
   2939  UseSystemPrintDialog: {
   2940    onBeforeAddons(manager, param) {
   2941      setAndLockPref("print.prefer_system_dialog", param);
   2942    },
   2943  },
   2944 
   2945  VisualSearchEnabled: {
   2946    onBeforeAddons(manager, param) {
   2947      setAndLockPref("browser.search.visualSearch.featureGate", param);
   2948    },
   2949  },
   2950 
   2951  WebsiteFilter: {
   2952    onBeforeUIStartup(manager, param) {
   2953      lazy.WebsiteFilter.init(param.Block || [], param.Exceptions || []);
   2954    },
   2955  },
   2956 
   2957  WindowsSSO: {
   2958    onBeforeAddons(manager, param) {
   2959      setAndLockPref("network.http.windows-sso.enabled", param);
   2960    },
   2961  },
   2962 };
   2963 
   2964 /*
   2965 * ====================
   2966 * = HELPER FUNCTIONS =
   2967 * ====================
   2968 *
   2969 * The functions below are helpers to be used by several policies.
   2970 */
   2971 
   2972 /**
   2973 * setAndLockPref
   2974 *
   2975 * Sets the _default_ value of a pref, and locks it (meaning that
   2976 * the default value will always be returned, independent from what
   2977 * is stored as the user value).
   2978 * The value is only changed in memory, and not stored to disk.
   2979 *
   2980 * @param {string} prefName
   2981 *        The pref to be changed
   2982 * @param {boolean|number|string} prefValue
   2983 *        The value to set and lock
   2984 */
   2985 export function setAndLockPref(prefName, prefValue) {
   2986  PoliciesUtils.setDefaultPref(prefName, prefValue, true);
   2987 }
   2988 
   2989 /**
   2990 *
   2991 * setPrefIfPresentAndLock
   2992 *
   2993 * Sets the pref to the value param[paramKey] if that exists. Either
   2994 * way, the pref is locked.
   2995 *
   2996 * @param {object} param
   2997 *        Object with pref values
   2998 * @param {string} paramKey
   2999 *        The key to look up the value in param
   3000 * @param {string} prefName
   3001 *        The pref to be changed
   3002 */
   3003 function setPrefIfPresentAndLock(param, paramKey, prefName) {
   3004  if (paramKey in param) {
   3005    setAndLockPref(prefName, param[paramKey]);
   3006  } else {
   3007    Services.prefs.lockPref(prefName);
   3008  }
   3009 }
   3010 
   3011 /**
   3012 * setDefaultPref
   3013 *
   3014 * Sets the _default_ value of a pref and optionally locks it.
   3015 * The value is only changed in memory, and not stored to disk.
   3016 *
   3017 * @param {string} prefName
   3018 *        The pref to be changed
   3019 * @param {boolean|number|string} prefValue
   3020 *        The value to set
   3021 * @param {boolean} locked
   3022 *        Optionally lock the pref
   3023 */
   3024 
   3025 export var PoliciesUtils = {
   3026  setDefaultPref(prefName, prefValue, locked) {
   3027    let prefWasLocked = Services.prefs.prefIsLocked(prefName);
   3028    if (prefWasLocked) {
   3029      Services.prefs.unlockPref(prefName);
   3030    }
   3031 
   3032    let defaults = Services.prefs.getDefaultBranch("");
   3033 
   3034    switch (typeof prefValue) {
   3035      case "boolean":
   3036        defaults.setBoolPref(prefName, prefValue);
   3037        break;
   3038 
   3039      case "number":
   3040        if (!Number.isInteger(prefValue)) {
   3041          throw new Error(`Non-integer value for ${prefName}`);
   3042        }
   3043 
   3044        // This is ugly, but necessary. On Windows GPO and macOS
   3045        // configs, booleans are converted to 0/1. In the previous
   3046        // Preferences implementation, the schema took care of
   3047        // automatically converting these values to booleans.
   3048        // Since we allow arbitrary prefs now, we have to do
   3049        // something different. See bug 1666836.
   3050        if (
   3051          defaults.getPrefType(prefName) == defaults.PREF_INT ||
   3052          ![0, 1].includes(prefValue)
   3053        ) {
   3054          defaults.setIntPref(prefName, prefValue);
   3055        } else {
   3056          defaults.setBoolPref(prefName, !!prefValue);
   3057        }
   3058        break;
   3059 
   3060      case "string":
   3061        defaults.setStringPref(prefName, prefValue);
   3062        break;
   3063    }
   3064 
   3065    // Prefs can only be unlocked explicitly.
   3066    // If they were locked before, they stay locked.
   3067    if (locked || (prefWasLocked && locked !== false)) {
   3068      Services.prefs.lockPref(prefName);
   3069    }
   3070  },
   3071 };
   3072 
   3073 /**
   3074 * setDefaultPermission
   3075 *
   3076 * Helper function to set preferences appropriately for the policy
   3077 *
   3078 * @param {string} policyName
   3079 *        The name of the policy to set
   3080 * @param {object} policyParam
   3081 *        The object containing param for the policy
   3082 */
   3083 function setDefaultPermission(policyName, policyParam) {
   3084  if ("BlockNewRequests" in policyParam) {
   3085    let prefName = "permissions.default." + policyName;
   3086 
   3087    if (policyParam.BlockNewRequests) {
   3088      PoliciesUtils.setDefaultPref(prefName, 2, policyParam.Locked);
   3089    } else {
   3090      PoliciesUtils.setDefaultPref(prefName, 0, policyParam.Locked);
   3091    }
   3092  }
   3093 }
   3094 
   3095 /**
   3096 * addAllowDenyPermissions
   3097 *
   3098 * Helper function to call the permissions manager (Services.perms.addFromPrincipal)
   3099 * for two arrays of URLs.
   3100 *
   3101 * @param {string} permissionName
   3102 *        The name of the permission to change
   3103 * @param {Array} allowList
   3104 *        The list of URLs to be set as ALLOW_ACTION for the chosen permission.
   3105 * @param {Array} blockList
   3106 *        The list of URLs to be set as DENY_ACTION for the chosen permission.
   3107 */
   3108 function addAllowDenyPermissions(permissionName, allowList, blockList) {
   3109  allowList = allowList || [];
   3110  blockList = blockList || [];
   3111 
   3112  for (let origin of allowList) {
   3113    try {
   3114      Services.perms.addFromPrincipal(
   3115        Services.scriptSecurityManager.createContentPrincipalFromOrigin(origin),
   3116        permissionName,
   3117        Ci.nsIPermissionManager.ALLOW_ACTION,
   3118        Ci.nsIPermissionManager.EXPIRE_POLICY
   3119      );
   3120    } catch (ex) {
   3121      // It's possible if the origin was invalid, we'll have a string instead of an origin.
   3122      lazy.log.error(
   3123        `Unable to add ${permissionName} permission for ${
   3124          origin.href || origin
   3125        }`
   3126      );
   3127    }
   3128  }
   3129 
   3130  for (let origin of blockList) {
   3131    Services.perms.addFromPrincipal(
   3132      Services.scriptSecurityManager.createContentPrincipalFromOrigin(origin),
   3133      permissionName,
   3134      Ci.nsIPermissionManager.DENY_ACTION,
   3135      Ci.nsIPermissionManager.EXPIRE_POLICY
   3136    );
   3137  }
   3138 }
   3139 
   3140 /**
   3141 * runOnce
   3142 *
   3143 * Helper function to run a callback only once per policy.
   3144 *
   3145 * @param {string} actionName
   3146 *        A given name which will be used to track if this callback has run.
   3147 * @param {Functon} callback
   3148 *        The callback to run only once.
   3149 */
   3150 // eslint-disable-next-line no-unused-vars
   3151 export function runOnce(actionName, callback) {
   3152  let prefName = `browser.policies.runonce.${actionName}`;
   3153  if (Services.prefs.getBoolPref(prefName, false)) {
   3154    lazy.log.debug(
   3155      `Not running action ${actionName} again because it has already run.`
   3156    );
   3157    return;
   3158  }
   3159  Services.prefs.setBoolPref(prefName, true);
   3160  callback();
   3161 }
   3162 
   3163 /**
   3164 * runOncePerModification
   3165 *
   3166 * Helper function similar to runOnce. The difference is that runOnce runs the
   3167 * callback once when the policy is set, then never again.
   3168 * runOncePerModification runs the callback once each time the policy value
   3169 * changes from its previous value.
   3170 * If the callback that was passed is an async function, you can await on this
   3171 * function to await for the callback.
   3172 *
   3173 * @param {string} actionName
   3174 *        A given name which will be used to track if this callback has run.
   3175 *        This string will be part of a pref name.
   3176 * @param {string} policyValue
   3177 *        The current value of the policy. This will be compared to previous
   3178 *        values given to this function to determine if the policy value has
   3179 *        changed. Regardless of the data type of the policy, this must be a
   3180 *        string.
   3181 * @param {Function} callback
   3182 *        The callback to be run when the pref value changes
   3183 * @returns {Promise}
   3184 *        A promise that will resolve once the callback finishes running.
   3185 */
   3186 async function runOncePerModification(actionName, policyValue, callback) {
   3187  // Stringify the value so that it matches what we'd get from getStringPref.
   3188  policyValue = policyValue + "";
   3189  let prefName = `browser.policies.runOncePerModification.${actionName}`;
   3190  let oldPolicyValue = Services.prefs.getStringPref(prefName, undefined);
   3191  if (policyValue === oldPolicyValue) {
   3192    lazy.log.debug(
   3193      `Not running action ${actionName} again because the policy's value is unchanged`
   3194    );
   3195    return Promise.resolve();
   3196  }
   3197  Services.prefs.setStringPref(prefName, policyValue);
   3198  return callback();
   3199 }
   3200 
   3201 /**
   3202 * clearRunOnceModification
   3203 *
   3204 * Helper function that clears a runOnce policy.
   3205 */
   3206 function clearRunOnceModification(actionName) {
   3207  let prefName = `browser.policies.runOncePerModification.${actionName}`;
   3208  Services.prefs.clearUserPref(prefName);
   3209 }
   3210 
   3211 function replacePathVariables(path) {
   3212  if (path.includes("${home}")) {
   3213    return path.replace(
   3214      "${home}",
   3215      Services.dirsvc.get("Home", Ci.nsIFile).path
   3216    );
   3217  }
   3218  return path;
   3219 }
   3220 
   3221 /**
   3222 * installAddonFromURL
   3223 *
   3224 * Helper function that installs an addon from a URL
   3225 * and verifies that the addon ID matches.
   3226 */
   3227 function installAddonFromURL(url, extensionID, addon) {
   3228  if (
   3229    addon &&
   3230    addon.sourceURI &&
   3231    addon.sourceURI.spec == url &&
   3232    !addon.sourceURI.schemeIs("file")
   3233  ) {
   3234    // It's the same addon, don't reinstall.
   3235    return;
   3236  }
   3237  lazy.AddonManager.getInstallForURL(url, {
   3238    telemetryInfo: { source: "enterprise-policy" },
   3239  }).then(install => {
   3240    if (install.addon && install.addon.appDisabled) {
   3241      lazy.log.error(`Incompatible add-on - ${install.addon.id}`);
   3242      install.cancel();
   3243      return;
   3244    }
   3245    let listener = {
   3246      /* eslint-disable-next-line no-shadow */
   3247      onDownloadEnded: install => {
   3248        // Install failed, error will be reported elsewhere.
   3249        if (!install.addon) {
   3250          return;
   3251        }
   3252        if (extensionID && install.addon.id != extensionID) {
   3253          lazy.log.error(
   3254            `Add-on downloaded from ${url} had unexpected id (got ${install.addon.id} expected ${extensionID})`
   3255          );
   3256          install.removeListener(listener);
   3257          install.cancel();
   3258        }
   3259        if (install.addon.appDisabled) {
   3260          lazy.log.error(`Incompatible add-on - ${url}`);
   3261          install.removeListener(listener);
   3262          install.cancel();
   3263        }
   3264        if (
   3265          addon &&
   3266          Services.vc.compare(addon.version, install.addon.version) == 0
   3267        ) {
   3268          lazy.log.debug(
   3269            "Installation cancelled because versions are the same"
   3270          );
   3271          install.removeListener(listener);
   3272          install.cancel();
   3273        }
   3274      },
   3275      onDownloadFailed: () => {
   3276        install.removeListener(listener);
   3277        lazy.log.error(
   3278          `Download failed - ${lazy.AddonManager.errorToString(
   3279            install.error
   3280          )} - ${url}`
   3281        );
   3282        clearRunOnceModification("extensionsInstall");
   3283      },
   3284      onInstallFailed: () => {
   3285        install.removeListener(listener);
   3286        lazy.log.error(
   3287          `Installation failed - ${lazy.AddonManager.errorToString(
   3288            install.error
   3289          )} - ${url}`
   3290        );
   3291      },
   3292      /* eslint-disable-next-line no-shadow */
   3293      onInstallEnded: (install, addon) => {
   3294        if (addon.type == "theme") {
   3295          addon.enable();
   3296        }
   3297        install.removeListener(listener);
   3298        lazy.log.debug(`Installation succeeded - ${url}`);
   3299      },
   3300    };
   3301    // If it's a local file install, onDownloadEnded is never called.
   3302    // So we call it manually, to handle some error cases.
   3303    if (url.startsWith("file:")) {
   3304      listener.onDownloadEnded(install);
   3305      if (install.state == lazy.AddonManager.STATE_CANCELLED) {
   3306        return;
   3307      }
   3308    }
   3309    install.addListener(listener);
   3310    install.install();
   3311  });
   3312 }
   3313 
   3314 let gBlockedAboutPages = [];
   3315 
   3316 function clearBlockedAboutPages() {
   3317  gBlockedAboutPages = [];
   3318 }
   3319 
   3320 function blockAboutPage(manager, feature) {
   3321  addChromeURLBlocker();
   3322  gBlockedAboutPages.push(feature);
   3323 
   3324  try {
   3325    let aboutModule = Cc[ABOUT_CONTRACT + feature.split(":")[1]].getService(
   3326      Ci.nsIAboutModule
   3327    );
   3328    let chromeURL = aboutModule.getChromeURI(Services.io.newURI(feature)).spec;
   3329    gBlockedAboutPages.push(chromeURL);
   3330  } catch (e) {
   3331    // Some about pages don't have chrome URLS (compat)
   3332  }
   3333 }
   3334 
   3335 let ChromeURLBlockPolicy = {
   3336  shouldLoad(contentLocation, loadInfo) {
   3337    let contentType = loadInfo.externalContentPolicyType;
   3338    if (
   3339      (contentLocation.scheme != "chrome" &&
   3340        contentLocation.scheme != "about") ||
   3341      (contentType != Ci.nsIContentPolicy.TYPE_DOCUMENT &&
   3342        contentType != Ci.nsIContentPolicy.TYPE_SUBDOCUMENT)
   3343    ) {
   3344      return Ci.nsIContentPolicy.ACCEPT;
   3345    }
   3346    let contentLocationSpec = contentLocation.spec.toLowerCase();
   3347    if (
   3348      gBlockedAboutPages.some(function (aboutPage) {
   3349        return contentLocationSpec.startsWith(aboutPage.toLowerCase());
   3350      })
   3351    ) {
   3352      return Ci.nsIContentPolicy.REJECT_POLICY;
   3353    }
   3354    return Ci.nsIContentPolicy.ACCEPT;
   3355  },
   3356  shouldProcess() {
   3357    return Ci.nsIContentPolicy.ACCEPT;
   3358  },
   3359  classDescription: "Policy Engine Content Policy",
   3360  contractID: "@mozilla-org/policy-engine-content-policy-service;1",
   3361  classID: Components.ID("{ba7b9118-cabc-4845-8b26-4215d2a59ed7}"),
   3362  QueryInterface: ChromeUtils.generateQI(["nsIContentPolicy"]),
   3363  createInstance(iid) {
   3364    return this.QueryInterface(iid);
   3365  },
   3366 };
   3367 
   3368 function addChromeURLBlocker() {
   3369  if (Cc[ChromeURLBlockPolicy.contractID]) {
   3370    return;
   3371  }
   3372 
   3373  let registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
   3374  registrar.registerFactory(
   3375    ChromeURLBlockPolicy.classID,
   3376    ChromeURLBlockPolicy.classDescription,
   3377    ChromeURLBlockPolicy.contractID,
   3378    ChromeURLBlockPolicy
   3379  );
   3380 
   3381  Services.catMan.addCategoryEntry(
   3382    "content-policy",
   3383    ChromeURLBlockPolicy.contractID,
   3384    ChromeURLBlockPolicy.contractID,
   3385    false,
   3386    true
   3387  );
   3388 }
   3389 
   3390 function pemToBase64(pem) {
   3391  return pem
   3392    .replace(/(.*)-----BEGIN CERTIFICATE-----/, "")
   3393    .replace(/-----END CERTIFICATE-----(.*)/, "")
   3394    .replace(/[\r\n]/g, "");
   3395 }
   3396 
   3397 function processMIMEInfo(mimeInfo, realMIMEInfo) {
   3398  if ("handlers" in mimeInfo) {
   3399    let firstHandler = true;
   3400    for (let handler of mimeInfo.handlers) {
   3401      // handler can be null which means they don't
   3402      // want a preferred handler.
   3403      if (handler) {
   3404        let handlerApp;
   3405        if ("path" in handler) {
   3406          try {
   3407            let file = new lazy.FileUtils.File(handler.path);
   3408            handlerApp = Cc[
   3409              "@mozilla.org/uriloader/local-handler-app;1"
   3410            ].createInstance(Ci.nsILocalHandlerApp);
   3411            handlerApp.executable = file;
   3412          } catch (ex) {
   3413            lazy.log.error(
   3414              `Unable to create handler executable (${handler.path})`
   3415            );
   3416            continue;
   3417          }
   3418        } else if ("uriTemplate" in handler) {
   3419          let templateURL = new URL(handler.uriTemplate);
   3420          if (templateURL.protocol != "https:") {
   3421            lazy.log.error(
   3422              `Web handler must be https (${handler.uriTemplate})`
   3423            );
   3424            continue;
   3425          }
   3426          if (
   3427            !templateURL.pathname.includes("%s") &&
   3428            !templateURL.search.includes("%s")
   3429          ) {
   3430            lazy.log.error(
   3431              `Web handler must contain %s (${handler.uriTemplate})`
   3432            );
   3433            continue;
   3434          }
   3435          handlerApp = Cc[
   3436            "@mozilla.org/uriloader/web-handler-app;1"
   3437          ].createInstance(Ci.nsIWebHandlerApp);
   3438          handlerApp.uriTemplate = handler.uriTemplate;
   3439        } else {
   3440          lazy.log.error("Invalid handler");
   3441          continue;
   3442        }
   3443        if ("name" in handler) {
   3444          handlerApp.name = handler.name;
   3445        }
   3446        realMIMEInfo.possibleApplicationHandlers.appendElement(handlerApp);
   3447        if (firstHandler) {
   3448          realMIMEInfo.preferredApplicationHandler = handlerApp;
   3449        }
   3450      }
   3451      firstHandler = false;
   3452    }
   3453  }
   3454  if ("action" in mimeInfo) {
   3455    let action = realMIMEInfo[mimeInfo.action];
   3456    if (
   3457      action == realMIMEInfo.useHelperApp &&
   3458      !realMIMEInfo.possibleApplicationHandlers.length
   3459    ) {
   3460      lazy.log.error("useHelperApp requires a handler");
   3461      return;
   3462    }
   3463    realMIMEInfo.preferredAction = action;
   3464  }
   3465  if ("ask" in mimeInfo) {
   3466    realMIMEInfo.alwaysAskBeforeHandling = mimeInfo.ask;
   3467  }
   3468  lazy.gHandlerService.store(realMIMEInfo);
   3469 }