/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this file, * You can obtain one at http://mozilla.org/MPL/2.0/. */ import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs"; import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs"; const lazy = {}; XPCOMUtils.defineLazyServiceGetters(lazy, { gCertDB: ["@mozilla.org/security/x509certdb;1", "nsIX509CertDB"], gExternalProtocolService: [ "@mozilla.org/uriloader/external-protocol-service;1", "nsIExternalProtocolService", ], gHandlerService: [ "@mozilla.org/uriloader/handler-service;1", "nsIHandlerService", ], gMIMEService: ["@mozilla.org/mime;1", "nsIMIMEService"], }); ChromeUtils.defineESModuleGetters(lazy, { AddonManager: "resource://gre/modules/AddonManager.sys.mjs", BookmarksPolicies: "resource:///modules/policies/BookmarksPolicies.sys.mjs", CustomizableUI: "resource:///modules/CustomizableUI.sys.mjs", FileUtils: "resource://gre/modules/FileUtils.sys.mjs", PdfJsDefaultPreferences: "resource://pdf.js/PdfJsDefaultPreferences.sys.mjs", ProxyPolicies: "resource:///modules/policies/ProxyPolicies.sys.mjs", UrlbarPrefs: "resource:///modules/UrlbarPrefs.sys.mjs", WebsiteFilter: "resource:///modules/policies/WebsiteFilter.sys.mjs", }); const PREF_LOGLEVEL = "browser.policies.loglevel"; const BROWSER_DOCUMENT_URL = AppConstants.BROWSER_CHROME_URL; const ABOUT_CONTRACT = "@mozilla.org/network/protocol/about;1?what="; const isXpcshell = Services.env.exists("XPCSHELL_TEST_PROFILE_DIR"); ChromeUtils.defineLazyGetter(lazy, "log", () => { let { ConsoleAPI } = ChromeUtils.importESModule( "resource://gre/modules/Console.sys.mjs" ); return new ConsoleAPI({ prefix: "Policies", // tip: set maxLogLevel to "debug" and use log.debug() to create detailed // messages during development. See LOG_LEVELS in Console.sys.mjs for details. maxLogLevel: "error", maxLogLevelPref: PREF_LOGLEVEL, }); }); /* * ============================ * = POLICIES IMPLEMENTATIONS = * ============================ * * The Policies object below is where the implementation for each policy * happens. An object for each policy should be defined, containing * callback functions that will be called by the engine. * * See the _callbacks object in EnterprisePolicies.js for the list of * possible callbacks and an explanation of each. * * Each callback will be called with two parameters: * - manager * This is the EnterprisePoliciesManager singleton object from * EnterprisePolicies.js * * - param * The parameter defined for this policy in policies-schema.json. * It will be different for each policy. It could be a boolean, * a string, an array or a complex object. All parameters have * been validated according to the schema, and no unknown * properties will be present on them. * * The callbacks will be bound to their parent policy object. */ export var Policies = { // Used for cleaning up policies. // Use the same timing that you used for setting up the policy. _cleanup: { onBeforeAddons() { if (Cu.isInAutomation || isXpcshell) { console.log("_cleanup from onBeforeAddons"); clearBlockedAboutPages(); } }, onProfileAfterChange() { if (Cu.isInAutomation || isXpcshell) { console.log("_cleanup from onProfileAfterChange"); } }, onBeforeUIStartup() { if (Cu.isInAutomation || isXpcshell) { console.log("_cleanup from onBeforeUIStartup"); } }, onAllWindowsRestored() { if (Cu.isInAutomation || isXpcshell) { console.log("_cleanup from onAllWindowsRestored"); } }, }, "3rdparty": { onBeforeAddons(manager, param) { manager.setExtensionPolicies(param.Extensions); }, }, AllowedDomainsForApps: { onBeforeAddons(manager, param) { Services.obs.addObserver(function (subject) { let channel = subject.QueryInterface(Ci.nsIHttpChannel); if (channel.URI.host.endsWith(".google.com")) { channel.setRequestHeader("X-GoogApps-Allowed-Domains", param, true); } }, "http-on-modify-request"); }, }, AllowFileSelectionDialogs: { onBeforeUIStartup(manager, param) { if (!param) { setAndLockPref("widget.disable_file_pickers", true); setAndLockPref("browser.download.useDownloadDir", true); manager.disallowFeature("filepickers"); } }, }, AppAutoUpdate: { onBeforeUIStartup(manager, param) { // Logic feels a bit reversed here, but it's correct. If AppAutoUpdate is // true, we disallow turning off auto updating, and visa versa. if (param) { manager.disallowFeature("app-auto-updates-off"); } else { manager.disallowFeature("app-auto-updates-on"); } }, }, AppUpdatePin: { validate(param) { // This is the version when pinning was introduced. Attempting to set a // pin before this will not work, because Balrog's pinning table will // never have the necessary entry. const earliestPinMajorVersion = 102; const earliestPinMinorVersion = 0; let pinParts = param.split("."); if (pinParts.length < 2) { lazy.log.error("AppUpdatePin has too few dots."); return false; } if (pinParts.length > 3) { lazy.log.error("AppUpdatePin has too many dots."); return false; } const trailingPinPart = pinParts.pop(); if (trailingPinPart != "") { lazy.log.error("AppUpdatePin does not end with a trailing dot."); return false; } const pinMajorVersionStr = pinParts.shift(); if (!pinMajorVersionStr.length) { lazy.log.error("AppUpdatePin's major version is empty."); return false; } if (!/^\d+$/.test(pinMajorVersionStr)) { lazy.log.error( "AppUpdatePin's major version contains a non-numeric character." ); return false; } if (/^0/.test(pinMajorVersionStr)) { lazy.log.error("AppUpdatePin's major version contains a leading 0."); return false; } const pinMajorVersionInt = parseInt(pinMajorVersionStr, 10); if (isNaN(pinMajorVersionInt)) { lazy.log.error( "AppUpdatePin's major version could not be parsed to an integer." ); return false; } if (pinMajorVersionInt < earliestPinMajorVersion) { lazy.log.error( `AppUpdatePin must not be earlier than '${earliestPinMajorVersion}.${earliestPinMinorVersion}.'.` ); return false; } if (pinParts.length) { const pinMinorVersionStr = pinParts.shift(); if (!pinMinorVersionStr.length) { lazy.log.error("AppUpdatePin's minor version is empty."); return false; } if (!/^\d+$/.test(pinMinorVersionStr)) { lazy.log.error( "AppUpdatePin's minor version contains a non-numeric character." ); return false; } if (/^0\d/.test(pinMinorVersionStr)) { lazy.log.error("AppUpdatePin's minor version contains a leading 0."); return false; } const pinMinorVersionInt = parseInt(pinMinorVersionStr, 10); if (isNaN(pinMinorVersionInt)) { lazy.log.error( "AppUpdatePin's minor version could not be parsed to an integer." ); return false; } if ( pinMajorVersionInt == earliestPinMajorVersion && pinMinorVersionInt < earliestPinMinorVersion ) { lazy.log.error( `AppUpdatePin must not be earlier than '${earliestPinMajorVersion}.${earliestPinMinorVersion}.'.` ); return false; } } return true; }, // No additional implementation needed here. UpdateService.sys.mjs will check // for this policy directly when determining the update URL. }, AppUpdateURL: { // No implementation needed here. UpdateService.sys.mjs will check for this // policy directly when determining the update URL. }, Authentication: { onBeforeAddons(manager, param) { // When Authentication was originally implemented, it was always // locked, so it defaults to locked. let locked = true; if ("Locked" in param) { locked = param.Locked; } if ("SPNEGO" in param) { PoliciesUtils.setDefaultPref( "network.negotiate-auth.trusted-uris", param.SPNEGO.join(", "), locked ); } if ("Delegated" in param) { PoliciesUtils.setDefaultPref( "network.negotiate-auth.delegation-uris", param.Delegated.join(", "), locked ); } if ("NTLM" in param) { PoliciesUtils.setDefaultPref( "network.automatic-ntlm-auth.trusted-uris", param.NTLM.join(", "), locked ); } if ("AllowNonFQDN" in param) { if ("NTLM" in param.AllowNonFQDN) { PoliciesUtils.setDefaultPref( "network.automatic-ntlm-auth.allow-non-fqdn", param.AllowNonFQDN.NTLM, locked ); } if ("SPNEGO" in param.AllowNonFQDN) { PoliciesUtils.setDefaultPref( "network.negotiate-auth.allow-non-fqdn", param.AllowNonFQDN.SPNEGO, locked ); } } if ("AllowProxies" in param) { if ("NTLM" in param.AllowProxies) { PoliciesUtils.setDefaultPref( "network.automatic-ntlm-auth.allow-proxies", param.AllowProxies.NTLM, locked ); } if ("SPNEGO" in param.AllowProxies) { PoliciesUtils.setDefaultPref( "network.negotiate-auth.allow-proxies", param.AllowProxies.SPNEGO, locked ); } } if ("PrivateBrowsing" in param) { PoliciesUtils.setDefaultPref( "network.auth.private-browsing-sso", param.PrivateBrowsing, locked ); } }, }, AutofillAddressEnabled: { onBeforeAddons(manager, param) { setAndLockPref("extensions.formautofill.addresses.enabled", param); }, }, AutofillCreditCardEnabled: { onBeforeAddons(manager, param) { setAndLockPref("extensions.formautofill.creditCards.enabled", param); }, }, AutoLaunchProtocolsFromOrigins: { onBeforeAddons(manager, param) { for (let info of param) { addAllowDenyPermissions( `open-protocol-handler^${info.protocol}`, info.allowed_origins ); } }, }, BackgroundAppUpdate: { onBeforeAddons(manager, param) { if (param) { manager.disallowFeature("app-background-update-off"); } else { manager.disallowFeature("app-background-update-on"); } }, }, BlockAboutAddons: { onBeforeUIStartup(manager, param) { if (param) { blockAboutPage(manager, "about:addons", true); } }, }, BlockAboutConfig: { onBeforeUIStartup(manager, param) { if (param) { blockAboutPage(manager, "about:config"); setAndLockPref("devtools.chrome.enabled", false); } }, }, BlockAboutProfiles: { onBeforeUIStartup(manager, param) { if (param) { blockAboutPage(manager, "about:profiles"); } }, }, BlockAboutSupport: { onBeforeUIStartup(manager, param) { if (param) { blockAboutPage(manager, "about:support"); manager.disallowFeature("aboutSupport"); } }, }, Bookmarks: { onAllWindowsRestored(manager, param) { lazy.BookmarksPolicies.processBookmarks(param); }, }, CaptivePortal: { onBeforeAddons(manager, param) { setAndLockPref("network.captive-portal-service.enabled", param); }, }, Certificates: { onBeforeAddons(manager, param) { if ("ImportEnterpriseRoots" in param) { setAndLockPref( "security.enterprise_roots.enabled", param.ImportEnterpriseRoots ); } if ("Install" in param) { (async () => { let dirs = []; let platform = AppConstants.platform; if (platform == "win") { dirs = [ // Ugly, but there is no official way to get %USERNAME\AppData\Roaming\Mozilla. Services.dirsvc.get("XREUSysExt", Ci.nsIFile).parent, // Even more ugly, but there is no official way to get %USERNAME\AppData\Local\Mozilla. Services.dirsvc.get("DefProfLRt", Ci.nsIFile).parent.parent, ]; } else if (platform == "macosx" || platform == "linux") { dirs = [ // These two keys are named wrong. They return the Mozilla directory. Services.dirsvc.get("XREUserNativeManifests", Ci.nsIFile), Services.dirsvc.get("XRESysNativeManifests", Ci.nsIFile), ]; } dirs.unshift(Services.dirsvc.get("XREAppDist", Ci.nsIFile)); for (let certfilename of param.Install) { let certfile; try { certfile = Cc["@mozilla.org/file/local;1"].createInstance( Ci.nsIFile ); certfile.initWithPath(certfilename); } catch (e) { for (let dir of dirs) { certfile = dir.clone(); certfile.append( platform == "linux" ? "certificates" : "Certificates" ); certfile.append(certfilename); if (certfile.exists()) { break; } } } let file; try { file = await File.createFromNsIFile(certfile); } catch (e) { lazy.log.error(`Unable to find certificate - ${certfilename}`); continue; } let reader = new FileReader(); reader.onloadend = function () { if (reader.readyState != reader.DONE) { lazy.log.error(`Unable to read certificate - ${certfile.path}`); return; } let certFile = reader.result; let certFileArray = []; for (let i = 0; i < certFile.length; i++) { certFileArray.push(certFile.charCodeAt(i)); } let cert; try { cert = lazy.gCertDB.constructX509(certFileArray); } catch (e) { lazy.log.debug( `constructX509 failed with error '${e}' - trying constructX509FromBase64.` ); try { // It might be PEM instead of DER. cert = lazy.gCertDB.constructX509FromBase64( pemToBase64(certFile) ); } catch (ex) { lazy.log.error( `Unable to add certificate - ${certfile.path}`, ex ); } } if (cert) { if ( lazy.gCertDB.isCertTrusted( cert, Ci.nsIX509Cert.CA_CERT, Ci.nsIX509CertDB.TRUSTED_SSL ) ) { // Certificate is already installed. return; } try { lazy.gCertDB.addCert(certFile, "CT,CT,"); } catch (e) { // It might be PEM instead of DER. lazy.gCertDB.addCertFromBase64( pemToBase64(certFile), "CT,CT," ); } } }; reader.readAsBinaryString(file); } })(); } }, }, Containers: { // Queried directly by ContextualIdentityService.sys.mjs }, ContentAnalysis: { onBeforeAddons(manager, param) { if ("PipePathName" in param) { setAndLockPref( "browser.contentanalysis.pipe_path_name", param.PipePathName ); } if ("AgentTimeout" in param) { if (!Number.isInteger(param.AgentTimeout)) { lazy.log.error( `Non-integer value for AgentTimeout: ${param.AgentTimeout}` ); } else { setAndLockPref( "browser.contentanalysis.agent_timeout", param.AgentTimeout ); } } if ("AllowUrlRegexList" in param) { setAndLockPref( "browser.contentanalysis.allow_url_regex_list", param.AllowUrlRegexList ); } if ("DenyUrlRegexList" in param) { setAndLockPref( "browser.contentanalysis.deny_url_regex_list", param.DenyUrlRegexList ); } if ("AgentName" in param) { setAndLockPref("browser.contentanalysis.agent_name", param.AgentName); } if ("ClientSignature" in param) { setAndLockPref( "browser.contentanalysis.client_signature", param.ClientSignature ); } let boolPrefs = [ ["IsPerUser", "is_per_user"], ["ShowBlockedResult", "show_blocked_result"], ["DefaultAllow", "default_allow"], ]; for (let pref of boolPrefs) { if (pref[0] in param) { setAndLockPref( `browser.contentanalysis.${pref[1]}`, !!param[pref[0]] ); } } if ("Enabled" in param) { let enabled = !!param.Enabled; setAndLockPref("browser.contentanalysis.enabled", enabled); let ca = Cc["@mozilla.org/contentanalysis;1"].getService( Ci.nsIContentAnalysis ); ca.isSetByEnterprisePolicy = true; } }, }, Cookies: { onBeforeUIStartup(manager, param) { addAllowDenyPermissions("cookie", param.Allow, param.Block); if (param.AllowSession) { for (let origin of param.AllowSession) { try { Services.perms.addFromPrincipal( Services.scriptSecurityManager.createContentPrincipalFromOrigin( origin ), "cookie", Ci.nsICookiePermission.ACCESS_SESSION, Ci.nsIPermissionManager.EXPIRE_POLICY ); } catch (ex) { lazy.log.error( `Unable to add cookie session permission - ${origin.href}` ); } } } if (param.Block) { const hosts = param.Block.map(url => url.hostname) .sort() .join("\n"); runOncePerModification("clearCookiesForBlockedHosts", hosts, () => { for (let blocked of param.Block) { Services.cookies.removeCookiesWithOriginAttributes( "{}", blocked.hostname ); } }); } if (param.ExpireAtSessionEnd != undefined) { lazy.log.error( "'ExpireAtSessionEnd' has been deprecated and it has no effect anymore." ); } // New Cookie Behavior option takes precendence let defaultPref = Services.prefs.getDefaultBranch(""); let newCookieBehavior = defaultPref.getIntPref( "network.cookie.cookieBehavior" ); let newCookieBehaviorPB = defaultPref.getIntPref( "network.cookie.cookieBehavior.pbmode" ); if ("Behavior" in param || "BehaviorPrivateBrowsing" in param) { let behaviors = { accept: Ci.nsICookieService.BEHAVIOR_ACCEPT, "reject-foreign": Ci.nsICookieService.BEHAVIOR_REJECT_FOREIGN, reject: Ci.nsICookieService.BEHAVIOR_REJECT, "limit-foreign": Ci.nsICookieService.BEHAVIOR_LIMIT_FOREIGN, "reject-tracker": Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER, "reject-tracker-and-partition-foreign": Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN, }; if ("Behavior" in param) { newCookieBehavior = behaviors[param.Behavior]; } if ("BehaviorPrivateBrowsing" in param) { newCookieBehaviorPB = behaviors[param.BehaviorPrivateBrowsing]; } } else { // Default, AcceptThirdParty, and RejectTracker are being // deprecated in favor of Behavior. They will continue // to be supported, though. if ( param.Default !== undefined || param.AcceptThirdParty !== undefined || param.RejectTracker !== undefined || param.Locked ) { newCookieBehavior = Ci.nsICookieService.BEHAVIOR_ACCEPT; if (param.Default !== undefined && !param.Default) { newCookieBehavior = Ci.nsICookieService.BEHAVIOR_REJECT; } else if (param.AcceptThirdParty) { if (param.AcceptThirdParty == "never") { newCookieBehavior = Ci.nsICookieService.BEHAVIOR_REJECT_FOREIGN; } else if (param.AcceptThirdParty == "from-visited") { newCookieBehavior = Ci.nsICookieService.BEHAVIOR_LIMIT_FOREIGN; } } else if (param.RejectTracker) { newCookieBehavior = Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER; } } // With the old cookie policy, we made private browsing the same. newCookieBehaviorPB = newCookieBehavior; } // We set the values no matter what just in case the policy was only used to lock. PoliciesUtils.setDefaultPref( "network.cookie.cookieBehavior", newCookieBehavior, param.Locked ); PoliciesUtils.setDefaultPref( "network.cookie.cookieBehavior.pbmode", newCookieBehaviorPB, param.Locked ); }, }, DefaultDownloadDirectory: { onBeforeAddons(manager, param) { PoliciesUtils.setDefaultPref( "browser.download.dir", replacePathVariables(param) ); }, }, DisableAccounts: { onBeforeAddons(manager, param) { if (param) { setAndLockPref("identity.fxaccounts.enabled", false); setAndLockPref("browser.aboutwelcome.enabled", false); } }, }, DisableAppUpdate: { onBeforeAddons(manager, param) { if (param) { manager.disallowFeature("appUpdate"); } }, }, DisableBuiltinPDFViewer: { onBeforeAddons(manager, param) { if (param) { setAndLockPref("pdfjs.disabled", true); } }, }, DisabledCiphers: { onBeforeAddons(manager, param) { let cipherPrefs = { TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256: "security.ssl3.ecdhe_rsa_aes_128_gcm_sha256", TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256: "security.ssl3.ecdhe_ecdsa_aes_128_gcm_sha256", TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256: "security.ssl3.ecdhe_ecdsa_chacha20_poly1305_sha256", TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256: "security.ssl3.ecdhe_rsa_chacha20_poly1305_sha256", TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384: "security.ssl3.ecdhe_ecdsa_aes_256_gcm_sha384", TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384: "security.ssl3.ecdhe_rsa_aes_256_gcm_sha384", TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA: "security.ssl3.ecdhe_rsa_aes_128_sha", TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA: "security.ssl3.ecdhe_ecdsa_aes_128_sha", TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA: "security.ssl3.ecdhe_rsa_aes_256_sha", TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA: "security.ssl3.ecdhe_ecdsa_aes_256_sha", TLS_DHE_RSA_WITH_AES_128_CBC_SHA: "security.ssl3.dhe_rsa_aes_128_sha", TLS_DHE_RSA_WITH_AES_256_CBC_SHA: "security.ssl3.dhe_rsa_aes_256_sha", TLS_RSA_WITH_AES_128_GCM_SHA256: "security.ssl3.rsa_aes_128_gcm_sha256", TLS_RSA_WITH_AES_256_GCM_SHA384: "security.ssl3.rsa_aes_256_gcm_sha384", TLS_RSA_WITH_AES_128_CBC_SHA: "security.ssl3.rsa_aes_128_sha", TLS_RSA_WITH_AES_256_CBC_SHA: "security.ssl3.rsa_aes_256_sha", TLS_RSA_WITH_3DES_EDE_CBC_SHA: "security.ssl3.deprecated.rsa_des_ede3_sha", }; for (let cipher in param) { setAndLockPref(cipherPrefs[cipher], !param[cipher]); } }, }, DisableDefaultBrowserAgent: { // The implementation of this policy is in the default browser agent itself // (/toolkit/mozapps/defaultagent); we need an entry for it here so that it // shows up in about:policies as a real policy and not as an error. }, DisableDeveloperTools: { onBeforeAddons(manager, param) { if (param) { setAndLockPref("devtools.policy.disabled", true); setAndLockPref("devtools.chrome.enabled", false); manager.disallowFeature("devtools"); blockAboutPage(manager, "about:debugging"); blockAboutPage(manager, "about:devtools-toolbox"); blockAboutPage(manager, "about:profiling"); } }, }, DisableFeedbackCommands: { onBeforeUIStartup(manager, param) { if (param) { manager.disallowFeature("feedbackCommands"); } }, }, DisableFirefoxAccounts: { onBeforeAddons(manager, param) { // If DisableAccounts is set, let it take precedence. if ("DisableAccounts" in manager.getActivePolicies()) { return; } if (param) { setAndLockPref("identity.fxaccounts.enabled", false); setAndLockPref("browser.aboutwelcome.enabled", false); } }, }, DisableFirefoxScreenshots: { onBeforeAddons(manager, param) { if (param) { setAndLockPref("extensions.screenshots.disabled", true); } }, }, DisableFirefoxStudies: { onBeforeAddons(manager, param) { if (param) { manager.disallowFeature("Shield"); setAndLockPref( "browser.newtabpage.activity-stream.asrouter.userprefs.cfr.addons", false ); setAndLockPref( "browser.newtabpage.activity-stream.asrouter.userprefs.cfr.features", false ); } }, }, DisableForgetButton: { onProfileAfterChange(manager, param) { if (param) { setAndLockPref("privacy.panicButton.enabled", false); } }, }, DisableFormHistory: { onBeforeUIStartup(manager, param) { if (param) { setAndLockPref("browser.formfill.enable", false); } }, }, DisableMasterPasswordCreation: { onBeforeUIStartup(manager, param) { if (param) { manager.disallowFeature("createMasterPassword"); } }, }, DisablePasswordReveal: { onBeforeUIStartup(manager, param) { if (param) { manager.disallowFeature("passwordReveal"); } }, }, DisablePocket: { onBeforeAddons(manager, param) { if (param) { setAndLockPref("extensions.pocket.enabled", false); } }, }, DisablePrivateBrowsing: { onBeforeAddons(manager, param) { if (param) { manager.disallowFeature("privatebrowsing"); blockAboutPage(manager, "about:privatebrowsing", true); setAndLockPref("browser.privatebrowsing.autostart", false); } }, }, DisableProfileImport: { onBeforeUIStartup(manager, param) { if (param) { manager.disallowFeature("profileImport"); setAndLockPref( "browser.newtabpage.activity-stream.migrationExpired", true ); } }, }, DisableProfileRefresh: { onBeforeUIStartup(manager, param) { if (param) { manager.disallowFeature("profileRefresh"); setAndLockPref("browser.disableResetPrompt", true); } }, }, DisableSafeMode: { onBeforeUIStartup(manager, param) { if (param) { manager.disallowFeature("safeMode"); } }, }, DisableSecurityBypass: { onBeforeUIStartup(manager, param) { if ("InvalidCertificate" in param) { setAndLockPref( "security.certerror.hideAddException", param.InvalidCertificate ); } if ("SafeBrowsing" in param) { setAndLockPref( "browser.safebrowsing.allowOverride", !param.SafeBrowsing ); } }, }, DisableSetDesktopBackground: { onBeforeUIStartup(manager, param) { if (param) { manager.disallowFeature("setDesktopBackground"); } }, }, DisableSystemAddonUpdate: { onBeforeAddons(manager, param) { if (param) { manager.disallowFeature("SysAddonUpdate"); } }, }, DisableTelemetry: { onBeforeAddons(manager, param) { if (param) { setAndLockPref("datareporting.healthreport.uploadEnabled", false); setAndLockPref("datareporting.policy.dataSubmissionEnabled", false); setAndLockPref("toolkit.telemetry.archive.enabled", false); blockAboutPage(manager, "about:telemetry"); } }, }, DisableThirdPartyModuleBlocking: { onBeforeUIStartup(manager, param) { if (param) { manager.disallowFeature("thirdPartyModuleBlocking"); } }, }, DisplayBookmarksToolbar: { onBeforeUIStartup(manager, param) { let visibility; if (typeof param === "boolean") { visibility = param ? "always" : "newtab"; } else { visibility = param; } // This policy is meant to change the default behavior, not to force it. // If this policy was already applied and the user chose to re-hide the // bookmarks toolbar, do not show it again. runOncePerModification("displayBookmarksToolbar", visibility, () => { let visibilityPref = "browser.toolbars.bookmarks.visibility"; Services.prefs.setCharPref(visibilityPref, visibility); }); }, }, DisplayMenuBar: { onBeforeUIStartup(manager, param) { let value; if ( typeof param === "boolean" || param == "default-on" || param == "default-off" ) { switch (param) { case "default-on": value = "false"; break; case "default-off": value = "true"; break; default: value = (!param).toString(); break; } // This policy is meant to change the default behavior, not to force it. // If this policy was already applied and the user chose to re-hide the // menu bar, do not show it again. runOncePerModification("displayMenuBar", value, () => { Services.xulStore.setValue( BROWSER_DOCUMENT_URL, "toolbar-menubar", "autohide", value ); }); } else { switch (param) { case "always": value = "false"; break; case "never": // Make sure Alt key doesn't show the menubar setAndLockPref("ui.key.menuAccessKeyFocuses", false); value = "true"; break; } Services.xulStore.setValue( BROWSER_DOCUMENT_URL, "toolbar-menubar", "autohide", value ); manager.disallowFeature("hideShowMenuBar"); } }, }, DNSOverHTTPS: { onBeforeAddons(manager, param) { if ("Enabled" in param) { let mode = param.Enabled ? 2 : 5; // Fallback only matters if DOH is enabled. if (param.Fallback === false) { mode = 3; } PoliciesUtils.setDefaultPref("network.trr.mode", mode, param.Locked); } if ("ProviderURL" in param) { PoliciesUtils.setDefaultPref( "network.trr.uri", param.ProviderURL.href, param.Locked ); } if ("ExcludedDomains" in param) { PoliciesUtils.setDefaultPref( "network.trr.excluded-domains", param.ExcludedDomains.join(","), param.Locked ); } }, }, DontCheckDefaultBrowser: { onBeforeUIStartup(manager, param) { setAndLockPref("browser.shell.checkDefaultBrowser", !param); }, }, DownloadDirectory: { onBeforeAddons(manager, param) { setAndLockPref("browser.download.dir", replacePathVariables(param)); // If a custom download directory is being used, just lock folder list to 2. setAndLockPref("browser.download.folderList", 2); // Per Chrome spec, user can't choose to download every time // if this is set. setAndLockPref("browser.download.useDownloadDir", true); }, }, EnableTrackingProtection: { onBeforeUIStartup(manager, param) { if (param.Value) { PoliciesUtils.setDefaultPref( "privacy.trackingprotection.enabled", true, param.Locked ); PoliciesUtils.setDefaultPref( "privacy.trackingprotection.pbmode.enabled", true, param.Locked ); } else { setAndLockPref("privacy.trackingprotection.enabled", false); setAndLockPref("privacy.trackingprotection.pbmode.enabled", false); } if ("Cryptomining" in param) { PoliciesUtils.setDefaultPref( "privacy.trackingprotection.cryptomining.enabled", param.Cryptomining, param.Locked ); } if ("Fingerprinting" in param) { PoliciesUtils.setDefaultPref( "privacy.trackingprotection.fingerprinting.enabled", param.Fingerprinting, param.Locked ); } if ("EmailTracking" in param) { PoliciesUtils.setDefaultPref( "privacy.trackingprotection.emailtracking.enabled", param.EmailTracking, param.Locked ); PoliciesUtils.setDefaultPref( "privacy.trackingprotection.emailtracking.pbmode.enabled", param.EmailTracking, param.Locked ); } if ("Exceptions" in param) { addAllowDenyPermissions("trackingprotection", param.Exceptions); } }, }, EncryptedMediaExtensions: { onBeforeAddons(manager, param) { if ("Enabled" in param) { PoliciesUtils.setDefaultPref( "media.eme.enabled", param.Enabled, param.Locked ); } }, }, ExemptDomainFileTypePairsFromFileTypeDownloadWarnings: { // This policy is handled directly in EnterprisePoliciesParent.sys.mjs // and requires no validation (It's done by the schema). }, Extensions: { onBeforeUIStartup(manager, param) { let uninstallingPromise = Promise.resolve(); if ("Uninstall" in param) { uninstallingPromise = runOncePerModification( "extensionsUninstall", JSON.stringify(param.Uninstall), async () => { // If we're uninstalling add-ons, re-run the extensionsInstall runOnce even if it hasn't // changed, which will allow add-ons to be updated. Services.prefs.clearUserPref( "browser.policies.runOncePerModification.extensionsInstall" ); let addons = await lazy.AddonManager.getAddonsByIDs( param.Uninstall ); for (let addon of addons) { if (addon) { try { await addon.uninstall(); } catch (e) { // This can fail for add-ons that can't be uninstalled. lazy.log.debug( `Add-on ID (${addon.id}) couldn't be uninstalled.` ); } } } } ); } if ("Install" in param) { runOncePerModification( "extensionsInstall", JSON.stringify(param.Install), async () => { await uninstallingPromise; for (let location of param.Install) { let uri; try { // We need to try as a file first because // Windows paths are valid URIs. // This is done for legacy support (old API) let xpiFile = new lazy.FileUtils.File(location); uri = Services.io.newFileURI(xpiFile); } catch (e) { uri = Services.io.newURI(location); } installAddonFromURL(uri.spec); } } ); } if ("Locked" in param) { for (let ID of param.Locked) { manager.disallowFeature(`uninstall-extension:${ID}`); manager.disallowFeature(`disable-extension:${ID}`); } } }, }, ExtensionSettings: { onBeforeAddons(manager, param) { try { manager.setExtensionSettings(param); } catch (e) { lazy.log.error("Invalid ExtensionSettings"); } }, async onBeforeUIStartup(manager, param) { let extensionSettings = param; let blockAllExtensions = false; if ("*" in extensionSettings) { if ( "installation_mode" in extensionSettings["*"] && extensionSettings["*"].installation_mode == "blocked" ) { blockAllExtensions = true; // Turn off discovery pane in about:addons setAndLockPref("extensions.getAddons.showPane", false); // Turn off recommendations setAndLockPref( "extensions.htmlaboutaddons.recommendations.enable", false ); // Block about:debugging blockAboutPage(manager, "about:debugging"); } if ("restricted_domains" in extensionSettings["*"]) { let restrictedDomains = Services.prefs .getCharPref("extensions.webextensions.restrictedDomains") .split(","); setAndLockPref( "extensions.webextensions.restrictedDomains", restrictedDomains .concat(extensionSettings["*"].restricted_domains) .join(",") ); } } let addons = await lazy.AddonManager.getAllAddons(); let allowedExtensions = []; for (let extensionID in extensionSettings) { if (extensionID == "*") { // Ignore global settings continue; } if ("installation_mode" in extensionSettings[extensionID]) { if ( extensionSettings[extensionID].installation_mode == "force_installed" || extensionSettings[extensionID].installation_mode == "normal_installed" ) { if (!extensionSettings[extensionID].install_url) { throw new Error(`Missing install_url for ${extensionID}`); } installAddonFromURL( extensionSettings[extensionID].install_url, extensionID, addons.find(addon => addon.id == extensionID) ); manager.disallowFeature(`uninstall-extension:${extensionID}`); if ( extensionSettings[extensionID].installation_mode == "force_installed" ) { manager.disallowFeature(`disable-extension:${extensionID}`); } allowedExtensions.push(extensionID); } else if ( extensionSettings[extensionID].installation_mode == "allowed" ) { allowedExtensions.push(extensionID); } else if ( extensionSettings[extensionID].installation_mode == "blocked" ) { if (addons.find(addon => addon.id == extensionID)) { // Can't use the addon from getActiveAddons since it doesn't have uninstall. let addon = await lazy.AddonManager.getAddonByID(extensionID); try { await addon.uninstall(); } catch (e) { // This can fail for add-ons that can't be uninstalled. lazy.log.debug( `Add-on ID (${addon.id}) couldn't be uninstalled.` ); } } } } } if (blockAllExtensions) { for (let addon of addons) { if ( addon.isSystem || addon.isBuiltin || !(addon.scope & lazy.AddonManager.SCOPE_PROFILE) ) { continue; } if (!allowedExtensions.includes(addon.id)) { try { // Can't use the addon from getActiveAddons since it doesn't have uninstall. let addonToUninstall = await lazy.AddonManager.getAddonByID( addon.id ); await addonToUninstall.uninstall(); } catch (e) { // This can fail for add-ons that can't be uninstalled. lazy.log.debug( `Add-on ID (${addon.id}) couldn't be uninstalled.` ); } } } } }, }, ExtensionUpdate: { onBeforeAddons(manager, param) { if (!param) { setAndLockPref("extensions.update.enabled", param); } }, }, FirefoxHome: { onBeforeAddons(manager, param) { if ("Search" in param) { PoliciesUtils.setDefaultPref( "browser.newtabpage.activity-stream.showSearch", param.Search, param.Locked ); } if ("TopSites" in param) { PoliciesUtils.setDefaultPref( "browser.newtabpage.activity-stream.feeds.topsites", param.TopSites, param.Locked ); } if ("SponsoredTopSites" in param) { PoliciesUtils.setDefaultPref( "browser.newtabpage.activity-stream.showSponsoredTopSites", param.SponsoredTopSites, param.Locked ); } if ("Highlights" in param) { PoliciesUtils.setDefaultPref( "browser.newtabpage.activity-stream.feeds.section.highlights", param.Highlights, param.Locked ); } if ("Pocket" in param) { PoliciesUtils.setDefaultPref( "browser.newtabpage.activity-stream.feeds.system.topstories", param.Pocket, param.Locked ); PoliciesUtils.setDefaultPref( "browser.newtabpage.activity-stream.feeds.section.topstories", param.Pocket, param.Locked ); } if ("SponsoredPocket" in param) { PoliciesUtils.setDefaultPref( "browser.newtabpage.activity-stream.showSponsored", param.SponsoredPocket, param.Locked ); } }, }, FirefoxSuggest: { onBeforeAddons(manager, param) { (async () => { await lazy.UrlbarPrefs.firefoxSuggestScenarioStartupPromise; if ("WebSuggestions" in param) { PoliciesUtils.setDefaultPref( "browser.urlbar.suggest.quicksuggest.nonsponsored", param.WebSuggestions, param.Locked ); } if ("SponsoredSuggestions" in param) { PoliciesUtils.setDefaultPref( "browser.urlbar.suggest.quicksuggest.sponsored", param.SponsoredSuggestions, param.Locked ); } if ("ImproveSuggest" in param) { PoliciesUtils.setDefaultPref( "browser.urlbar.quicksuggest.dataCollection.enabled", param.ImproveSuggest, param.Locked ); } })(); }, }, GoToIntranetSiteForSingleWordEntryInAddressBar: { onBeforeAddons(manager, param) { setAndLockPref("browser.fixup.dns_first_for_single_words", param); }, }, Handlers: { onBeforeAddons(manager, param) { if ("mimeTypes" in param) { for (let mimeType in param.mimeTypes) { let mimeInfo = param.mimeTypes[mimeType]; let realMIMEInfo = lazy.gMIMEService.getFromTypeAndExtension( mimeType, "" ); processMIMEInfo(mimeInfo, realMIMEInfo); } } if ("extensions" in param) { for (let extension in param.extensions) { let mimeInfo = param.extensions[extension]; try { let realMIMEInfo = lazy.gMIMEService.getFromTypeAndExtension( "", extension ); processMIMEInfo(mimeInfo, realMIMEInfo); } catch (e) { lazy.log.error(`Invalid file extension (${extension})`); } } } if ("schemes" in param) { for (let scheme in param.schemes) { let handlerInfo = param.schemes[scheme]; let realHandlerInfo = lazy.gExternalProtocolService.getProtocolHandlerInfo(scheme); processMIMEInfo(handlerInfo, realHandlerInfo); } } }, }, HardwareAcceleration: { onBeforeAddons(manager, param) { if (!param) { setAndLockPref("layers.acceleration.disabled", true); } }, }, Homepage: { onBeforeUIStartup(manager, param) { if ("StartPage" in param && param.StartPage == "none") { // For blank startpage, we use about:blank rather // than messing with browser.startup.page param.URL = new URL("about:blank"); } // |homepages| will be a string containing a pipe-separated ('|') list of // URLs because that is what the "Home page" section of about:preferences // (and therefore what the pref |browser.startup.homepage|) accepts. if ("URL" in param) { let homepages = param.URL.href; if (param.Additional && param.Additional.length) { homepages += "|" + param.Additional.map(url => url.href).join("|"); } PoliciesUtils.setDefaultPref( "browser.startup.homepage", homepages, param.Locked ); if (param.Locked) { setAndLockPref( "pref.browser.homepage.disable_button.current_page", true ); setAndLockPref( "pref.browser.homepage.disable_button.bookmark_page", true ); setAndLockPref( "pref.browser.homepage.disable_button.restore_default", true ); } else { // Clear out old run once modification that is no longer used. clearRunOnceModification("setHomepage"); } // If a homepage has been set via policy, show the home button if (param.URL != "about:blank") { manager.disallowFeature("removeHomeButtonByDefault"); } } if (param.StartPage) { let prefValue; switch (param.StartPage) { case "homepage": case "homepage-locked": case "none": prefValue = 1; break; case "previous-session": prefValue = 3; break; } PoliciesUtils.setDefaultPref( "browser.startup.page", prefValue, param.StartPage == "homepage-locked" ); } }, }, InstallAddonsPermission: { onBeforeUIStartup(manager, param) { if ("Allow" in param) { addAllowDenyPermissions("install", param.Allow, null); } if ("Default" in param) { setAndLockPref("xpinstall.enabled", param.Default); if (!param.Default) { blockAboutPage(manager, "about:debugging"); setAndLockPref( "browser.newtabpage.activity-stream.asrouter.userprefs.cfr.addons", false ); setAndLockPref( "browser.newtabpage.activity-stream.asrouter.userprefs.cfr.features", false ); manager.disallowFeature("xpinstall"); } } }, }, LegacyProfiles: { // Handled in nsToolkitProfileService.cpp (Windows only) }, LegacySameSiteCookieBehaviorEnabled: { onBeforeAddons(manager, param) { PoliciesUtils.setDefaultPref( "network.cookie.sameSite.laxByDefault", !param ); }, }, LegacySameSiteCookieBehaviorEnabledForDomainList: { onBeforeAddons(manager, param) { PoliciesUtils.setDefaultPref( "network.cookie.sameSite.laxByDefault.disabledHosts", param.join(",") ); }, }, LocalFileLinks: { onBeforeAddons(manager, param) { // If there are existing capabilities, lock them with the policy pref. let policyNames = Services.prefs .getCharPref("capability.policy.policynames", "") .split(" "); policyNames.push("localfilelinks_policy"); setAndLockPref("capability.policy.policynames", policyNames.join(" ")); setAndLockPref( "capability.policy.localfilelinks_policy.checkloaduri.enabled", "allAccess" ); setAndLockPref( "capability.policy.localfilelinks_policy.sites", param.join(" ") ); }, }, ManagedBookmarks: {}, ManualAppUpdateOnly: { onBeforeAddons(manager, param) { if (param) { manager.disallowFeature("autoAppUpdateChecking"); } }, }, NetworkPrediction: { onBeforeAddons(manager, param) { setAndLockPref("network.dns.disablePrefetch", !param); setAndLockPref("network.dns.disablePrefetchFromHTTPS", !param); }, }, NewTabPage: { onBeforeAddons(manager, param) { setAndLockPref("browser.newtabpage.enabled", param); }, }, NoDefaultBookmarks: { onProfileAfterChange(manager, param) { if (param) { manager.disallowFeature("defaultBookmarks"); } }, }, OfferToSaveLogins: { onBeforeUIStartup(manager, param) { setAndLockPref("signon.rememberSignons", param); setAndLockPref("services.passwordSavingEnabled", param); }, }, OfferToSaveLoginsDefault: { onBeforeUIStartup(manager, param) { let policies = Services.policies.getActivePolicies(); if ("OfferToSaveLogins" in policies) { lazy.log.error( `OfferToSaveLoginsDefault ignored because OfferToSaveLogins is present.` ); } else { PoliciesUtils.setDefaultPref("signon.rememberSignons", param); } }, }, OverrideFirstRunPage: { onProfileAfterChange(manager, param) { let url = param ? param : ""; setAndLockPref("startup.homepage_welcome_url", url); setAndLockPref("browser.aboutwelcome.enabled", false); }, }, OverridePostUpdatePage: { onProfileAfterChange(manager, param) { let url = param ? param.href : ""; setAndLockPref("startup.homepage_override_url", url); // The pref startup.homepage_override_url is only used // as a fallback when the update.xml file hasn't provided // a specific post-update URL. manager.disallowFeature("postUpdateCustomPage"); }, }, PasswordManagerEnabled: { onBeforeUIStartup(manager, param) { if (!param) { blockAboutPage(manager, "about:logins", true); setAndLockPref("pref.privacy.disable_button.view_passwords", true); } setAndLockPref("signon.rememberSignons", param); }, }, PasswordManagerExceptions: { onBeforeUIStartup(manager, param) { addAllowDenyPermissions("login-saving", null, param); }, }, PDFjs: { onBeforeAddons(manager, param) { if ("Enabled" in param) { setAndLockPref("pdfjs.disabled", !param.Enabled); } if ("EnablePermissions" in param) { setAndLockPref("pdfjs.enablePermissions", param.EnablePermissions); } }, }, Permissions: { onBeforeUIStartup(manager, param) { if (param.Camera) { addAllowDenyPermissions( "camera", param.Camera.Allow, param.Camera.Block ); setDefaultPermission("camera", param.Camera); } if (param.Microphone) { addAllowDenyPermissions( "microphone", param.Microphone.Allow, param.Microphone.Block ); setDefaultPermission("microphone", param.Microphone); } if (param.Autoplay) { addAllowDenyPermissions( "autoplay-media", param.Autoplay.Allow, param.Autoplay.Block ); if ("Default" in param.Autoplay) { let prefValue; switch (param.Autoplay.Default) { case "allow-audio-video": prefValue = 0; break; case "block-audio": prefValue = 1; break; case "block-audio-video": prefValue = 5; break; } PoliciesUtils.setDefaultPref( "media.autoplay.default", prefValue, param.Autoplay.Locked ); } } if (param.Location) { addAllowDenyPermissions( "geo", param.Location.Allow, param.Location.Block ); setDefaultPermission("geo", param.Location); } if (param.Notifications) { addAllowDenyPermissions( "desktop-notification", param.Notifications.Allow, param.Notifications.Block ); setDefaultPermission("desktop-notification", param.Notifications); } if ("VirtualReality" in param) { addAllowDenyPermissions( "xr", param.VirtualReality.Allow, param.VirtualReality.Block ); setDefaultPermission("xr", param.VirtualReality); } }, }, PictureInPicture: { onBeforeAddons(manager, param) { if ("Enabled" in param) { PoliciesUtils.setDefaultPref( "media.videocontrols.picture-in-picture.video-toggle.enabled", param.Enabled ); } if (param.Locked) { Services.prefs.lockPref( "media.videocontrols.picture-in-picture.video-toggle.enabled" ); } }, }, PopupBlocking: { onBeforeUIStartup(manager, param) { addAllowDenyPermissions("popup", param.Allow, null); if (param.Locked) { let blockValue = true; if (param.Default !== undefined && !param.Default) { blockValue = false; } setAndLockPref("dom.disable_open_during_load", blockValue); } else if (param.Default !== undefined) { PoliciesUtils.setDefaultPref( "dom.disable_open_during_load", !!param.Default ); } }, }, Preferences: { onBeforeAddons(manager, param) { let allowedPrefixes = [ "accessibility.", "alerts.", "app.update.", "browser.", "datareporting.policy.", "dom.", "extensions.", "general.autoScroll", "general.smoothScroll", "geo.", "gfx.", "intl.", "keyword.enabled", "layers.", "layout.", "media.", "network.", "pdfjs.", "places.", "pref.", "print.", "privacy.userContext.enabled", "privacy.userContext.ui.enabled", "signon.", "spellchecker.", "toolkit.legacyUserProfileCustomizations.stylesheets", "ui.", "widget.", "xpinstall.whitelist.required", ]; if (!AppConstants.MOZ_REQUIRE_SIGNING) { allowedPrefixes.push("xpinstall.signatures.required"); } const allowedSecurityPrefs = [ "security.block_fileuri_script_with_wrong_mime", "security.default_personal_cert", "security.disable_button.openCertManager", "security.disable_button.openDeviceManager", "security.insecure_connection_text.enabled", "security.insecure_connection_text.pbmode.enabled", "security.mixed_content.block_active_content", "security.osclientcerts.assume_rsa_pss_support", "security.osclientcerts.autoload", "security.OCSP.enabled", "security.OCSP.require", "security.ssl.enable_ocsp_stapling", "security.ssl.errorReporting.enabled", "security.ssl.require_safe_negotiation", "security.tls.enable_0rtt_data", "security.tls.hello_downgrade_check", "security.tls.version.enable-deprecated", "security.warn_submit_secure_to_insecure", ]; const blockedPrefs = [ "app.update.channel", "app.update.lastUpdateTime", "app.update.migrated", "browser.vpn_promo.disallowed_regions", ]; for (let preference in param) { if (blockedPrefs.includes(preference)) { lazy.log.error( `Unable to set preference ${preference}. Preference not allowed for security reasons.` ); continue; } if (preference.startsWith("security.")) { if (!allowedSecurityPrefs.includes(preference)) { lazy.log.error( `Unable to set preference ${preference}. Preference not allowed for security reasons.` ); continue; } } else if ( !allowedPrefixes.some(prefix => preference.startsWith(prefix)) ) { lazy.log.error( `Unable to set preference ${preference}. Preference not allowed for stability reasons.` ); continue; } if (typeof param[preference] != "object") { // Legacy policy preferences setAndLockPref(preference, param[preference]); } else { if (param[preference].Status == "clear") { Services.prefs.clearUserPref(preference); continue; } let prefBranch; if (param[preference].Status == "user") { prefBranch = Services.prefs; } else { prefBranch = Services.prefs.getDefaultBranch(""); } // Prefs that were previously locked should stay locked, // but policy can update the value. let prefWasLocked = Services.prefs.prefIsLocked(preference); if (prefWasLocked) { Services.prefs.unlockPref(preference); } try { let prefType = param[preference].Type || typeof param[preference].Value; switch (prefType) { case "boolean": prefBranch.setBoolPref(preference, param[preference].Value); break; case "number": if (!Number.isInteger(param[preference].Value)) { throw new Error(`Non-integer value for ${preference}`); } // Because pdfjs prefs are set async, we can't check the // default pref branch to see if they are int or bool, so we // have to get their type from PdfJsDefaultPreferences. if (preference.startsWith("pdfjs.")) { let preferenceTail = preference.replace("pdfjs.", ""); if ( preferenceTail in lazy.PdfJsDefaultPreferences && typeof lazy.PdfJsDefaultPreferences[preferenceTail] == "number" ) { prefBranch.setIntPref(preference, param[preference].Value); } else { prefBranch.setBoolPref( preference, !!param[preference].Value ); } break; } // This is ugly, but necessary. On Windows GPO and macOS // configs, booleans are converted to 0/1. In the previous // Preferences implementation, the schema took care of // automatically converting these values to booleans. // Since we allow arbitrary prefs now, we have to do // something different. See bug 1666836, 1668374, and 1872267. // We only set something as int if it was explicit in policy, // the same type as the default pref, or NOT 0/1. Otherwise // we set it as bool. if ( param[preference].Type == "number" || prefBranch.getPrefType(preference) == prefBranch.PREF_INT || ![0, 1].includes(param[preference].Value) ) { prefBranch.setIntPref(preference, param[preference].Value); } else { prefBranch.setBoolPref(preference, !!param[preference].Value); } break; case "string": prefBranch.setStringPref(preference, param[preference].Value); break; } } catch (e) { lazy.log.error( `Unable to set preference ${preference}. Probable type mismatch.` ); } if (param[preference].Status == "locked" || prefWasLocked) { Services.prefs.lockPref(preference); } } } }, }, PrimaryPassword: { onAllWindowsRestored(manager, param) { if (param) { manager.disallowFeature("removeMasterPassword"); } else { manager.disallowFeature("createMasterPassword"); } }, }, PrintingEnabled: { onBeforeUIStartup(manager, param) { setAndLockPref("print.enabled", param); }, }, PromptForDownloadLocation: { onBeforeAddons(manager, param) { setAndLockPref("browser.download.useDownloadDir", !param); }, }, Proxy: { onBeforeAddons(manager, param) { if (param.Locked) { manager.disallowFeature("changeProxySettings"); } lazy.ProxyPolicies.configureProxySettings( param, PoliciesUtils.setDefaultPref ); }, }, RequestedLocales: { onBeforeAddons(manager, param) { let requestedLocales; if (Array.isArray(param)) { requestedLocales = param; } else if (param) { requestedLocales = param.split(","); } else { requestedLocales = []; } runOncePerModification( "requestedLocales", JSON.stringify(requestedLocales), () => { Services.locale.requestedLocales = requestedLocales; } ); }, }, SanitizeOnShutdown: { onBeforeUIStartup(manager, param) { if (typeof param === "boolean") { setAndLockPref("privacy.sanitize.sanitizeOnShutdown", param); setAndLockPref("privacy.clearOnShutdown.cache", param); setAndLockPref("privacy.clearOnShutdown.cookies", param); setAndLockPref("privacy.clearOnShutdown.downloads", param); setAndLockPref("privacy.clearOnShutdown.formdata", param); setAndLockPref("privacy.clearOnShutdown.history", param); setAndLockPref("privacy.clearOnShutdown.sessions", param); setAndLockPref("privacy.clearOnShutdown.siteSettings", param); setAndLockPref("privacy.clearOnShutdown.offlineApps", param); setAndLockPref( "privacy.clearOnShutdown_v2.historyFormDataAndDownloads", param ); setAndLockPref("privacy.clearOnShutdown_v2.cookiesAndStorage", param); setAndLockPref("privacy.clearOnShutdown_v2.cache", param); setAndLockPref("privacy.clearOnShutdown_v2.siteSettings", param); } else { let locked = true; // Needed to preserve original behavior in perpetuity. let lockDefaultPrefs = true; if ("Locked" in param) { locked = param.Locked; lockDefaultPrefs = false; } PoliciesUtils.setDefaultPref( "privacy.sanitize.sanitizeOnShutdown", true, locked ); if ("Cache" in param) { PoliciesUtils.setDefaultPref( "privacy.clearOnShutdown.cache", param.Cache, locked ); PoliciesUtils.setDefaultPref( "privacy.clearOnShutdown_v2.cache", param.Cache, locked ); } else { PoliciesUtils.setDefaultPref( "privacy.clearOnShutdown.cache", false, lockDefaultPrefs ); PoliciesUtils.setDefaultPref( "privacy.clearOnShutdown_v2.cache", false, lockDefaultPrefs ); } if ("Cookies" in param) { PoliciesUtils.setDefaultPref( "privacy.clearOnShutdown.cookies", param.Cookies, locked ); // We set cookiesAndStorage to follow lock and pref // settings for cookies, and deprecate offlineApps // and sessions in the new clear on shutdown dialog - Bug 1853996 PoliciesUtils.setDefaultPref( "privacy.clearOnShutdown_v2.cookiesAndStorage", param.Cookies, locked ); } else { PoliciesUtils.setDefaultPref( "privacy.clearOnShutdown.cookies", false, lockDefaultPrefs ); PoliciesUtils.setDefaultPref( "privacy.clearOnShutdown_v2.cookiesAndStorage", false, lockDefaultPrefs ); } if ("Downloads" in param) { PoliciesUtils.setDefaultPref( "privacy.clearOnShutdown.downloads", param.Downloads, locked ); } else { PoliciesUtils.setDefaultPref( "privacy.clearOnShutdown.downloads", false, lockDefaultPrefs ); } if ("FormData" in param) { PoliciesUtils.setDefaultPref( "privacy.clearOnShutdown.formdata", param.FormData, locked ); } else { PoliciesUtils.setDefaultPref( "privacy.clearOnShutdown.formdata", false, lockDefaultPrefs ); } if ("History" in param) { PoliciesUtils.setDefaultPref( "privacy.clearOnShutdown.history", param.History, locked ); // We set historyFormDataAndDownloads to follow lock and pref // settings for history, and deprecate formdata and downloads // in the new clear on shutdown dialog - Bug 1853996 PoliciesUtils.setDefaultPref( "privacy.clearOnShutdown_v2.historyFormDataAndDownloads", param.History, locked ); } else { PoliciesUtils.setDefaultPref( "privacy.clearOnShutdown.history", false, lockDefaultPrefs ); PoliciesUtils.setDefaultPref( "privacy.clearOnShutdown_v2.historyFormDataAndDownloads", false, lockDefaultPrefs ); } if ("Sessions" in param) { PoliciesUtils.setDefaultPref( "privacy.clearOnShutdown.sessions", param.Sessions, locked ); } else { PoliciesUtils.setDefaultPref( "privacy.clearOnShutdown.sessions", false, lockDefaultPrefs ); } if ("SiteSettings" in param) { PoliciesUtils.setDefaultPref( "privacy.clearOnShutdown.siteSettings", param.SiteSettings, locked ); PoliciesUtils.setDefaultPref( "privacy.clearOnShutdown_v2.siteSettings", param.SiteSettings, locked ); } if ("OfflineApps" in param) { PoliciesUtils.setDefaultPref( "privacy.clearOnShutdown.offlineApps", param.OfflineApps, locked ); } } }, }, SearchBar: { onAllWindowsRestored(manager, param) { // This policy is meant to change the default behavior, not to force it. // If this policy was already applied and the user chose move the search // bar, don't move it again. runOncePerModification("searchInNavBar", param, () => { if (param == "separate") { lazy.CustomizableUI.addWidgetToArea( "search-container", lazy.CustomizableUI.AREA_NAVBAR, lazy.CustomizableUI.getPlacementOfWidget("urlbar-container") .position + 1 ); } else if (param == "unified") { lazy.CustomizableUI.removeWidgetFromArea("search-container"); } }); }, }, SearchEngines: { onBeforeUIStartup(manager, param) { if (param.PreventInstalls) { manager.disallowFeature("installSearchEngine", true); } }, onAllWindowsRestored(manager, param) { Services.search.init().then(async () => { // Adding of engines is handled by the SearchService in the init(). // Remove can happen after those are added - no engines are allowed // to replace the application provided engines, even if they have been // removed. if (param.Remove) { // Only rerun if the list of engine names has changed. await runOncePerModification( "removeSearchEngines", JSON.stringify(param.Remove), async function () { for (let engineName of param.Remove) { let engine = Services.search.getEngineByName(engineName); if (engine) { try { await Services.search.removeEngine(engine); } catch (ex) { lazy.log.error("Unable to remove the search engine", ex); } } } } ); } if (param.Default) { await runOncePerModification( "setDefaultSearchEngine", param.Default, async () => { let defaultEngine; try { defaultEngine = Services.search.getEngineByName(param.Default); if (!defaultEngine) { throw new Error("No engine by that name could be found"); } } catch (ex) { lazy.log.error( `Search engine lookup failed when attempting to set ` + `the default engine. Requested engine was ` + `"${param.Default}".`, ex ); } if (defaultEngine) { try { await Services.search.setDefault( defaultEngine, Ci.nsISearchService.CHANGE_REASON_ENTERPRISE ); } catch (ex) { lazy.log.error("Unable to set the default search engine", ex); } } } ); } if (param.DefaultPrivate) { await runOncePerModification( "setDefaultPrivateSearchEngine", param.DefaultPrivate, async () => { let defaultPrivateEngine; try { defaultPrivateEngine = Services.search.getEngineByName( param.DefaultPrivate ); if (!defaultPrivateEngine) { throw new Error("No engine by that name could be found"); } } catch (ex) { lazy.log.error( `Search engine lookup failed when attempting to set ` + `the default private engine. Requested engine was ` + `"${param.DefaultPrivate}".`, ex ); } if (defaultPrivateEngine) { try { await Services.search.setDefaultPrivate( defaultPrivateEngine, Ci.nsISearchService.CHANGE_REASON_ENTERPRISE ); } catch (ex) { lazy.log.error( "Unable to set the default private search engine", ex ); } } } ); } }); }, }, SearchSuggestEnabled: { onBeforeAddons(manager, param) { setAndLockPref("browser.urlbar.suggest.searches", param); setAndLockPref("browser.search.suggest.enabled", param); }, }, SecurityDevices: { onProfileAfterChange(manager, param) { let pkcs11db = Cc["@mozilla.org/security/pkcs11moduledb;1"].getService( Ci.nsIPKCS11ModuleDB ); let securityDevices; if (param.Add || param.Delete) { // We're using the new syntax. securityDevices = param.Add; if (param.Delete) { for (let deviceName of param.Delete) { try { pkcs11db.deleteModule(deviceName); } catch (e) { // Ignoring errors here since it might stick around in policy // after removing. Alternative would be to listModules and // make sure it's there before removing, but that seems // like unnecessary work. } } } } else { securityDevices = param; } if (!securityDevices) { return; } for (let deviceName in securityDevices) { let foundModule = false; for (let module of pkcs11db.listModules()) { if (module && module.libName === securityDevices[deviceName]) { foundModule = true; break; } } if (foundModule) { continue; } try { pkcs11db.addModule(deviceName, securityDevices[deviceName], 0, 0); } catch (ex) { lazy.log.error(`Unable to add security device ${deviceName}`); lazy.log.debug(ex); } } }, }, ShowHomeButton: { onBeforeAddons(manager, param) { if (param) { manager.disallowFeature("removeHomeButtonByDefault"); } }, onAllWindowsRestored(manager, param) { if (param) { let homeButtonPlacement = lazy.CustomizableUI.getPlacementOfWidget("home-button"); if (!homeButtonPlacement) { let placement = lazy.CustomizableUI.getPlacementOfWidget("forward-button"); lazy.CustomizableUI.addWidgetToArea( "home-button", lazy.CustomizableUI.AREA_NAVBAR, placement.position + 2 ); } } else { lazy.CustomizableUI.removeWidgetFromArea("home-button"); } }, }, SSLVersionMax: { onBeforeAddons(manager, param) { let tlsVersion; switch (param) { case "tls1": tlsVersion = 1; break; case "tls1.1": tlsVersion = 2; break; case "tls1.2": tlsVersion = 3; break; case "tls1.3": tlsVersion = 4; break; } setAndLockPref("security.tls.version.max", tlsVersion); }, }, SSLVersionMin: { onBeforeAddons(manager, param) { let tlsVersion; switch (param) { case "tls1": tlsVersion = 1; break; case "tls1.1": tlsVersion = 2; break; case "tls1.2": tlsVersion = 3; break; case "tls1.3": tlsVersion = 4; break; } setAndLockPref("security.tls.version.min", tlsVersion); }, }, StartDownloadsInTempDirectory: { onBeforeAddons(manager, param) { setAndLockPref("browser.download.start_downloads_in_tmp_dir", param); }, }, SupportMenu: { onProfileAfterChange(manager, param) { manager.setSupportMenu(param); }, }, TranslateEnabled: { onBeforeAddons(manager, param) { setAndLockPref("browser.translations.enable", param); }, }, UserMessaging: { onBeforeAddons(manager, param) { if ("WhatsNew" in param) { PoliciesUtils.setDefaultPref( "browser.messaging-system.whatsNewPanel.enabled", param.WhatsNew, param.Locked ); } if ("ExtensionRecommendations" in param) { PoliciesUtils.setDefaultPref( "browser.newtabpage.activity-stream.asrouter.userprefs.cfr.addons", param.ExtensionRecommendations, param.Locked ); } if ("FeatureRecommendations" in param) { PoliciesUtils.setDefaultPref( "browser.newtabpage.activity-stream.asrouter.userprefs.cfr.features", param.FeatureRecommendations, param.Locked ); PoliciesUtils.setDefaultPref( "browser.translations.panelShown", !param.FeatureRecommendations, param.Locked ); } if ("UrlbarInterventions" in param && !param.UrlbarInterventions) { manager.disallowFeature("urlbarinterventions"); } if ("SkipOnboarding" in param) { PoliciesUtils.setDefaultPref( "browser.aboutwelcome.enabled", !param.SkipOnboarding, param.Locked ); } if ("MoreFromMozilla" in param) { PoliciesUtils.setDefaultPref( "browser.preferences.moreFromMozilla", param.MoreFromMozilla, param.Locked ); } }, }, UseSystemPrintDialog: { onBeforeAddons(manager, param) { setAndLockPref("print.prefer_system_dialog", param); }, }, WebsiteFilter: { onBeforeUIStartup(manager, param) { lazy.WebsiteFilter.init(param.Block || [], param.Exceptions || []); }, }, WindowsSSO: { onBeforeAddons(manager, param) { setAndLockPref("network.http.windows-sso.enabled", param); }, }, }; /* * ==================== * = HELPER FUNCTIONS = * ==================== * * The functions below are helpers to be used by several policies. */ /** * setAndLockPref * * Sets the _default_ value of a pref, and locks it (meaning that * the default value will always be returned, independent from what * is stored as the user value). * The value is only changed in memory, and not stored to disk. * * @param {string} prefName * The pref to be changed * @param {boolean|number|string} prefValue * The value to set and lock */ export function setAndLockPref(prefName, prefValue) { PoliciesUtils.setDefaultPref(prefName, prefValue, true); } /** * setDefaultPref * * Sets the _default_ value of a pref and optionally locks it. * The value is only changed in memory, and not stored to disk. * * @param {string} prefName * The pref to be changed * @param {boolean|number|string} prefValue * The value to set * @param {boolean} locked * Optionally lock the pref */ export var PoliciesUtils = { setDefaultPref(prefName, prefValue, locked) { let prefWasLocked = Services.prefs.prefIsLocked(prefName); if (prefWasLocked) { Services.prefs.unlockPref(prefName); } let defaults = Services.prefs.getDefaultBranch(""); switch (typeof prefValue) { case "boolean": defaults.setBoolPref(prefName, prefValue); break; case "number": if (!Number.isInteger(prefValue)) { throw new Error(`Non-integer value for ${prefName}`); } // This is ugly, but necessary. On Windows GPO and macOS // configs, booleans are converted to 0/1. In the previous // Preferences implementation, the schema took care of // automatically converting these values to booleans. // Since we allow arbitrary prefs now, we have to do // something different. See bug 1666836. if ( defaults.getPrefType(prefName) == defaults.PREF_INT || ![0, 1].includes(prefValue) ) { defaults.setIntPref(prefName, prefValue); } else { defaults.setBoolPref(prefName, !!prefValue); } break; case "string": defaults.setStringPref(prefName, prefValue); break; } // Prefs can only be unlocked explicitly. // If they were locked before, they stay locked. if (locked || (prefWasLocked && locked !== false)) { Services.prefs.lockPref(prefName); } }, }; /** * setDefaultPermission * * Helper function to set preferences appropriately for the policy * * @param {string} policyName * The name of the policy to set * @param {object} policyParam * The object containing param for the policy */ function setDefaultPermission(policyName, policyParam) { if ("BlockNewRequests" in policyParam) { let prefName = "permissions.default." + policyName; if (policyParam.BlockNewRequests) { PoliciesUtils.setDefaultPref(prefName, 2, policyParam.Locked); } else { PoliciesUtils.setDefaultPref(prefName, 0, policyParam.Locked); } } } /** * addAllowDenyPermissions * * Helper function to call the permissions manager (Services.perms.addFromPrincipal) * for two arrays of URLs. * * @param {string} permissionName * The name of the permission to change * @param {Array} allowList * The list of URLs to be set as ALLOW_ACTION for the chosen permission. * @param {Array} blockList * The list of URLs to be set as DENY_ACTION for the chosen permission. */ function addAllowDenyPermissions(permissionName, allowList, blockList) { allowList = allowList || []; blockList = blockList || []; for (let origin of allowList) { try { Services.perms.addFromPrincipal( Services.scriptSecurityManager.createContentPrincipalFromOrigin(origin), permissionName, Ci.nsIPermissionManager.ALLOW_ACTION, Ci.nsIPermissionManager.EXPIRE_POLICY ); } catch (ex) { // It's possible if the origin was invalid, we'll have a string instead of an origin. lazy.log.error( `Unable to add ${permissionName} permission for ${ origin.href || origin }` ); } } for (let origin of blockList) { Services.perms.addFromPrincipal( Services.scriptSecurityManager.createContentPrincipalFromOrigin(origin), permissionName, Ci.nsIPermissionManager.DENY_ACTION, Ci.nsIPermissionManager.EXPIRE_POLICY ); } } /** * runOnce * * Helper function to run a callback only once per policy. * * @param {string} actionName * A given name which will be used to track if this callback has run. * @param {Functon} callback * The callback to run only once. */ // eslint-disable-next-line no-unused-vars export function runOnce(actionName, callback) { let prefName = `browser.policies.runonce.${actionName}`; if (Services.prefs.getBoolPref(prefName, false)) { lazy.log.debug( `Not running action ${actionName} again because it has already run.` ); return; } Services.prefs.setBoolPref(prefName, true); callback(); } /** * runOncePerModification * * Helper function similar to runOnce. The difference is that runOnce runs the * callback once when the policy is set, then never again. * runOncePerModification runs the callback once each time the policy value * changes from its previous value. * If the callback that was passed is an async function, you can await on this * function to await for the callback. * * @param {string} actionName * A given name which will be used to track if this callback has run. * This string will be part of a pref name. * @param {string} policyValue * The current value of the policy. This will be compared to previous * values given to this function to determine if the policy value has * changed. Regardless of the data type of the policy, this must be a * string. * @param {Function} callback * The callback to be run when the pref value changes * @returns {Promise} * A promise that will resolve once the callback finishes running. * */ async function runOncePerModification(actionName, policyValue, callback) { let prefName = `browser.policies.runOncePerModification.${actionName}`; let oldPolicyValue = Services.prefs.getStringPref(prefName, undefined); if (policyValue === oldPolicyValue) { lazy.log.debug( `Not running action ${actionName} again because the policy's value is unchanged` ); return Promise.resolve(); } Services.prefs.setStringPref(prefName, policyValue); return callback(); } /** * clearRunOnceModification * * Helper function that clears a runOnce policy. */ function clearRunOnceModification(actionName) { let prefName = `browser.policies.runOncePerModification.${actionName}`; Services.prefs.clearUserPref(prefName); } function replacePathVariables(path) { if (path.includes("${home}")) { return path.replace( "${home}", Services.dirsvc.get("Home", Ci.nsIFile).path ); } return path; } /** * installAddonFromURL * * Helper function that installs an addon from a URL * and verifies that the addon ID matches. */ function installAddonFromURL(url, extensionID, addon) { if ( addon && addon.sourceURI && addon.sourceURI.spec == url && !addon.sourceURI.schemeIs("file") ) { // It's the same addon, don't reinstall. return; } lazy.AddonManager.getInstallForURL(url, { telemetryInfo: { source: "enterprise-policy" }, }).then(install => { if (install.addon && install.addon.appDisabled) { lazy.log.error(`Incompatible add-on - ${install.addon.id}`); install.cancel(); return; } let listener = { /* eslint-disable-next-line no-shadow */ onDownloadEnded: install => { // Install failed, error will be reported elsewhere. if (!install.addon) { return; } if (extensionID && install.addon.id != extensionID) { lazy.log.error( `Add-on downloaded from ${url} had unexpected id (got ${install.addon.id} expected ${extensionID})` ); install.removeListener(listener); install.cancel(); } if (install.addon.appDisabled) { lazy.log.error(`Incompatible add-on - ${url}`); install.removeListener(listener); install.cancel(); } if ( addon && Services.vc.compare(addon.version, install.addon.version) == 0 ) { lazy.log.debug( "Installation cancelled because versions are the same" ); install.removeListener(listener); install.cancel(); } }, onDownloadFailed: () => { install.removeListener(listener); lazy.log.error( `Download failed - ${lazy.AddonManager.errorToString( install.error )} - ${url}` ); clearRunOnceModification("extensionsInstall"); }, onInstallFailed: () => { install.removeListener(listener); lazy.log.error( `Installation failed - ${lazy.AddonManager.errorToString( install.error )} - {url}` ); }, /* eslint-disable-next-line no-shadow */ onInstallEnded: (install, addon) => { if (addon.type == "theme") { addon.enable(); } install.removeListener(listener); lazy.log.debug(`Installation succeeded - ${url}`); }, }; // If it's a local file install, onDownloadEnded is never called. // So we call it manually, to handle some error cases. if (url.startsWith("file:")) { listener.onDownloadEnded(install); if (install.state == lazy.AddonManager.STATE_CANCELLED) { return; } } install.addListener(listener); install.install(); }); } let gBlockedAboutPages = []; function clearBlockedAboutPages() { gBlockedAboutPages = []; } function blockAboutPage(manager, feature) { addChromeURLBlocker(); gBlockedAboutPages.push(feature); try { let aboutModule = Cc[ABOUT_CONTRACT + feature.split(":")[1]].getService( Ci.nsIAboutModule ); let chromeURL = aboutModule.getChromeURI(Services.io.newURI(feature)).spec; gBlockedAboutPages.push(chromeURL); } catch (e) { // Some about pages don't have chrome URLS (compat) } } let ChromeURLBlockPolicy = { shouldLoad(contentLocation, loadInfo) { let contentType = loadInfo.externalContentPolicyType; if ( (contentLocation.scheme != "chrome" && contentLocation.scheme != "about") || (contentType != Ci.nsIContentPolicy.TYPE_DOCUMENT && contentType != Ci.nsIContentPolicy.TYPE_SUBDOCUMENT) ) { return Ci.nsIContentPolicy.ACCEPT; } let contentLocationSpec = contentLocation.spec.toLowerCase(); if ( gBlockedAboutPages.some(function (aboutPage) { return contentLocationSpec.startsWith(aboutPage.toLowerCase()); }) ) { return Ci.nsIContentPolicy.REJECT_POLICY; } return Ci.nsIContentPolicy.ACCEPT; }, shouldProcess() { return Ci.nsIContentPolicy.ACCEPT; }, classDescription: "Policy Engine Content Policy", contractID: "@mozilla-org/policy-engine-content-policy-service;1", classID: Components.ID("{ba7b9118-cabc-4845-8b26-4215d2a59ed7}"), QueryInterface: ChromeUtils.generateQI(["nsIContentPolicy"]), createInstance(iid) { return this.QueryInterface(iid); }, }; function addChromeURLBlocker() { if (Cc[ChromeURLBlockPolicy.contractID]) { return; } let registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar); registrar.registerFactory( ChromeURLBlockPolicy.classID, ChromeURLBlockPolicy.classDescription, ChromeURLBlockPolicy.contractID, ChromeURLBlockPolicy ); Services.catMan.addCategoryEntry( "content-policy", ChromeURLBlockPolicy.contractID, ChromeURLBlockPolicy.contractID, false, true ); } function pemToBase64(pem) { return pem .replace(/(.*)-----BEGIN CERTIFICATE-----/, "") .replace(/-----END CERTIFICATE-----(.*)/, "") .replace(/[\r\n]/g, ""); } function processMIMEInfo(mimeInfo, realMIMEInfo) { if ("handlers" in mimeInfo) { let firstHandler = true; for (let handler of mimeInfo.handlers) { // handler can be null which means they don't // want a preferred handler. if (handler) { let handlerApp; if ("path" in handler) { try { let file = new lazy.FileUtils.File(handler.path); handlerApp = Cc[ "@mozilla.org/uriloader/local-handler-app;1" ].createInstance(Ci.nsILocalHandlerApp); handlerApp.executable = file; } catch (ex) { lazy.log.error( `Unable to create handler executable (${handler.path})` ); continue; } } else if ("uriTemplate" in handler) { let templateURL = new URL(handler.uriTemplate); if (templateURL.protocol != "https:") { lazy.log.error( `Web handler must be https (${handler.uriTemplate})` ); continue; } if ( !templateURL.pathname.includes("%s") && !templateURL.search.includes("%s") ) { lazy.log.error( `Web handler must contain %s (${handler.uriTemplate})` ); continue; } handlerApp = Cc[ "@mozilla.org/uriloader/web-handler-app;1" ].createInstance(Ci.nsIWebHandlerApp); handlerApp.uriTemplate = handler.uriTemplate; } else { lazy.log.error("Invalid handler"); continue; } if ("name" in handler) { handlerApp.name = handler.name; } realMIMEInfo.possibleApplicationHandlers.appendElement(handlerApp); if (firstHandler) { realMIMEInfo.preferredApplicationHandler = handlerApp; } } firstHandler = false; } } if ("action" in mimeInfo) { let action = realMIMEInfo[mimeInfo.action]; if ( action == realMIMEInfo.useHelperApp && !realMIMEInfo.possibleApplicationHandlers.length ) { lazy.log.error("useHelperApp requires a handler"); return; } realMIMEInfo.preferredAction = action; } if ("ask" in mimeInfo) { realMIMEInfo.alwaysAskBeforeHandling = mimeInfo.ask; } lazy.gHandlerService.store(realMIMEInfo); }