/* 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-globals-from extensionControlled.js */ /* import-globals-from preferences.js */ const PREF_UPLOAD_ENABLED = "datareporting.healthreport.uploadEnabled"; const TRACKING_PROTECTION_KEY = "websites.trackingProtectionMode"; const TRACKING_PROTECTION_PREFS = [ "privacy.trackingprotection.enabled", "privacy.trackingprotection.pbmode.enabled", ]; const CONTENT_BLOCKING_PREFS = [ "privacy.trackingprotection.enabled", "privacy.trackingprotection.pbmode.enabled", "network.cookie.cookieBehavior", "privacy.trackingprotection.fingerprinting.enabled", "privacy.trackingprotection.cryptomining.enabled", "privacy.firstparty.isolate", "privacy.trackingprotection.emailtracking.enabled", "privacy.trackingprotection.emailtracking.pbmode.enabled", ]; const PREF_URLBAR_QUICKSUGGEST_BLOCKLIST = "browser.urlbar.quicksuggest.blockedDigests"; const PREF_URLBAR_WEATHER_USER_ENABLED = "browser.urlbar.suggest.weather"; /* * Prefs that are unique to sanitizeOnShutdown and are not shared * with the deleteOnClose mechanism like privacy.clearOnShutdown.cookies, -cache and -offlineApps */ const SANITIZE_ON_SHUTDOWN_PREFS_ONLY = [ "privacy.clearOnShutdown.history", "privacy.clearOnShutdown.downloads", "privacy.clearOnShutdown.sessions", "privacy.clearOnShutdown.formdata", "privacy.clearOnShutdown.siteSettings", ]; const PREF_OPT_OUT_STUDIES_ENABLED = "app.shield.optoutstudies.enabled"; const PREF_NORMANDY_ENABLED = "app.normandy.enabled"; const PREF_ADDON_RECOMMENDATIONS_ENABLED = "browser.discovery.enabled"; const PREF_PASSWORD_GENERATION_AVAILABLE = "signon.generation.available"; const { BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN } = Ci.nsICookieService; const PASSWORD_MANAGER_PREF_ID = "services.passwordSavingEnabled"; XPCOMUtils.defineLazyGetter(this, "AlertsServiceDND", function () { try { let alertsService = Cc["@mozilla.org/alerts-service;1"] .getService(Ci.nsIAlertsService) .QueryInterface(Ci.nsIAlertsDoNotDisturb); // This will throw if manualDoNotDisturb isn't implemented. alertsService.manualDoNotDisturb; return alertsService; } catch (ex) { return undefined; } }); XPCOMUtils.defineLazyPreferenceGetter( this, "OS_AUTH_ENABLED", "signon.management.page.os-auth.enabled", true ); XPCOMUtils.defineLazyPreferenceGetter( this, "gIsFirstPartyIsolated", "privacy.firstparty.isolate", false ); ChromeUtils.defineESModuleGetters(this, { DoHConfigController: "resource:///modules/DoHConfig.sys.mjs", }); Preferences.addAll([ // Content blocking / Tracking Protection { id: "privacy.trackingprotection.enabled", type: "bool" }, { id: "privacy.trackingprotection.pbmode.enabled", type: "bool" }, { id: "privacy.trackingprotection.fingerprinting.enabled", type: "bool" }, { id: "privacy.trackingprotection.cryptomining.enabled", type: "bool" }, { id: "privacy.trackingprotection.emailtracking.enabled", type: "bool" }, { id: "privacy.trackingprotection.emailtracking.pbmode.enabled", type: "bool", }, // Social tracking { id: "privacy.trackingprotection.socialtracking.enabled", type: "bool" }, { id: "privacy.socialtracking.block_cookies.enabled", type: "bool" }, // Tracker list { id: "urlclassifier.trackingTable", type: "string" }, // Button prefs { id: "pref.privacy.disable_button.cookie_exceptions", type: "bool" }, { id: "pref.privacy.disable_button.view_cookies", type: "bool" }, { id: "pref.privacy.disable_button.change_blocklist", type: "bool" }, { id: "pref.privacy.disable_button.tracking_protection_exceptions", type: "bool", }, // Location Bar { id: "browser.urlbar.suggest.bestmatch", type: "bool" }, { id: "browser.urlbar.suggest.bookmark", type: "bool" }, { id: "browser.urlbar.suggest.history", type: "bool" }, { id: "browser.urlbar.suggest.openpage", type: "bool" }, { id: "browser.urlbar.suggest.topsites", type: "bool" }, { id: "browser.urlbar.suggest.engines", type: "bool" }, { id: "browser.urlbar.suggest.quicksuggest.nonsponsored", type: "bool" }, { id: "browser.urlbar.suggest.quicksuggest.sponsored", type: "bool" }, { id: "browser.urlbar.quicksuggest.dataCollection.enabled", type: "bool" }, { id: PREF_URLBAR_QUICKSUGGEST_BLOCKLIST, type: "string" }, { id: PREF_URLBAR_WEATHER_USER_ENABLED, type: "bool" }, // History { id: "places.history.enabled", type: "bool" }, { id: "browser.formfill.enable", type: "bool" }, { id: "privacy.history.custom", type: "bool" }, // Cookies { id: "network.cookie.cookieBehavior", type: "int" }, { id: "network.cookie.blockFutureCookies", type: "bool" }, // Content blocking category { id: "browser.contentblocking.category", type: "string" }, { id: "browser.contentblocking.features.strict", type: "string" }, // Clear Private Data { id: "privacy.sanitize.sanitizeOnShutdown", type: "bool" }, { id: "privacy.sanitize.timeSpan", type: "int" }, { id: "privacy.clearOnShutdown.cookies", type: "bool" }, { id: "privacy.clearOnShutdown.cache", type: "bool" }, { id: "privacy.clearOnShutdown.offlineApps", type: "bool" }, { id: "privacy.clearOnShutdown.history", type: "bool" }, { id: "privacy.clearOnShutdown.downloads", type: "bool" }, { id: "privacy.clearOnShutdown.sessions", type: "bool" }, { id: "privacy.clearOnShutdown.formdata", type: "bool" }, { id: "privacy.clearOnShutdown.siteSettings", type: "bool" }, // Do not track { id: "privacy.donottrackheader.enabled", type: "bool" }, // Media { id: "media.autoplay.default", type: "int" }, // Popups { id: "dom.disable_open_during_load", type: "bool" }, // Passwords { id: "signon.rememberSignons", type: "bool" }, { id: "signon.generation.enabled", type: "bool" }, { id: "signon.autofillForms", type: "bool" }, { id: "signon.management.page.breach-alerts.enabled", type: "bool" }, { id: "signon.firefoxRelay.feature", type: "string" }, // Buttons { id: "pref.privacy.disable_button.view_passwords", type: "bool" }, { id: "pref.privacy.disable_button.view_passwords_exceptions", type: "bool" }, /* Certificates tab * security.default_personal_cert * - a string: * "Select Automatically" select a certificate automatically when a site * requests one * "Ask Every Time" present a dialog to the user so he can select * the certificate to use on a site which * requests one */ { id: "security.default_personal_cert", type: "string" }, { id: "security.disable_button.openCertManager", type: "bool" }, { id: "security.disable_button.openDeviceManager", type: "bool" }, { id: "security.OCSP.enabled", type: "int" }, // Add-ons, malware, phishing { id: "xpinstall.whitelist.required", type: "bool" }, { id: "browser.safebrowsing.malware.enabled", type: "bool" }, { id: "browser.safebrowsing.phishing.enabled", type: "bool" }, { id: "browser.safebrowsing.downloads.enabled", type: "bool" }, { id: "urlclassifier.malwareTable", type: "string" }, { id: "browser.safebrowsing.downloads.remote.block_potentially_unwanted", type: "bool", }, { id: "browser.safebrowsing.downloads.remote.block_uncommon", type: "bool" }, // First-Party Isolation { id: "privacy.firstparty.isolate", type: "bool" }, // HTTPS-Only { id: "dom.security.https_only_mode", type: "bool" }, { id: "dom.security.https_only_mode_pbm", type: "bool" }, // Windows SSO { id: "network.http.windows-sso.enabled", type: "bool" }, // Quick Actions { id: "browser.urlbar.quickactions.showPrefs", type: "bool" }, { id: "browser.urlbar.suggest.quickactions", type: "bool" }, // Cookie Banner Handling { id: "cookiebanners.ui.desktop.enabled", type: "bool" }, { id: "cookiebanners.service.mode", type: "int" }, { id: "cookiebanners.service.detectOnly", type: "bool" }, // DoH { id: "network.trr.mode", type: "int" }, { id: "network.trr.uri", type: "string" }, { id: "network.trr.default_provider_uri", type: "string" }, { id: "network.trr.custom_uri", type: "string" }, { id: "network.trr_ui.show_fallback_warning_option", type: "bool" }, { id: "network.trr.display_fallback_warning", type: "bool" }, { id: "doh-rollout.disable-heuristics", type: "bool" }, ]); // Study opt out if (AppConstants.MOZ_DATA_REPORTING) { Preferences.addAll([ // Preference instances for prefs that we need to monitor while the page is open. { id: PREF_OPT_OUT_STUDIES_ENABLED, type: "bool" }, { id: PREF_ADDON_RECOMMENDATIONS_ENABLED, type: "bool" }, { id: PREF_UPLOAD_ENABLED, type: "bool" }, ]); } // Privacy segmentation section Preferences.add({ id: "browser.dataFeatureRecommendations.enabled", type: "bool", }); // Data Choices tab if (AppConstants.MOZ_CRASHREPORTER) { Preferences.add({ id: "browser.crashReports.unsubmittedCheck.autoSubmit2", type: "bool", }); } function setEventListener(aId, aEventType, aCallback) { document .getElementById(aId) .addEventListener(aEventType, aCallback.bind(gPrivacyPane)); } function setSyncFromPrefListener(aId, aCallback) { Preferences.addSyncFromPrefListener(document.getElementById(aId), aCallback); } function setSyncToPrefListener(aId, aCallback) { Preferences.addSyncToPrefListener(document.getElementById(aId), aCallback); } function dataCollectionCheckboxHandler({ checkbox, pref, matchPref = () => true, isDisabled = () => false, }) { function updateCheckbox() { let collectionEnabled = Services.prefs.getBoolPref( PREF_UPLOAD_ENABLED, false ); if (collectionEnabled && matchPref()) { if (Services.prefs.getBoolPref(pref, false)) { checkbox.setAttribute("checked", "true"); } else { checkbox.removeAttribute("checked"); } checkbox.setAttribute("preference", pref); } else { checkbox.removeAttribute("preference"); checkbox.removeAttribute("checked"); } checkbox.disabled = !collectionEnabled || Services.prefs.prefIsLocked(pref) || isDisabled(); } Preferences.get(PREF_UPLOAD_ENABLED).on("change", updateCheckbox); updateCheckbox(); } // Sets the "Learn how" SUMO link in the Strict/Custom options of Content Blocking. function setUpContentBlockingWarnings() { document.getElementById("fpiIncompatibilityWarning").hidden = !gIsFirstPartyIsolated; } function initTCPStandardSection() { let cookieBehaviorPref = Preferences.get("network.cookie.cookieBehavior"); let updateTCPSectionVisibilityState = () => { document.getElementById("etpStandardTCPBox").hidden = cookieBehaviorPref.value != Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN; }; cookieBehaviorPref.on("change", updateTCPSectionVisibilityState); updateTCPSectionVisibilityState(); } var gPrivacyPane = { _pane: null, /** * Whether the prompt to restart Firefox should appear when changing the autostart pref. */ _shouldPromptForRestart: true, /** * Update the tracking protection UI to deal with extension control. */ _updateTrackingProtectionUI() { let cBPrefisLocked = CONTENT_BLOCKING_PREFS.some(pref => Services.prefs.prefIsLocked(pref) ); let tPPrefisLocked = TRACKING_PROTECTION_PREFS.some(pref => Services.prefs.prefIsLocked(pref) ); function setInputsDisabledState(isControlled) { let tpDisabled = tPPrefisLocked || isControlled; let disabled = cBPrefisLocked || isControlled; let tpCheckbox = document.getElementById( "contentBlockingTrackingProtectionCheckbox" ); // Only enable the TP menu if Detect All Trackers is enabled. document.getElementById("trackingProtectionMenu").disabled = tpDisabled || !tpCheckbox.checked; tpCheckbox.disabled = tpDisabled; document.getElementById("standardRadio").disabled = disabled; document.getElementById("strictRadio").disabled = disabled; document .getElementById("contentBlockingOptionStrict") .classList.toggle("disabled", disabled); document .getElementById("contentBlockingOptionStandard") .classList.toggle("disabled", disabled); let arrowButtons = document.querySelectorAll("button.arrowhead"); for (let button of arrowButtons) { button.disabled = disabled; } // Notify observers that the TP UI has been updated. // This is needed since our tests need to be notified about the // trackingProtectionMenu element getting disabled/enabled at the right time. Services.obs.notifyObservers(window, "privacy-pane-tp-ui-updated"); } let policy = Services.policies.getActivePolicies(); if ( policy && ((policy.EnableTrackingProtection && policy.EnableTrackingProtection.Locked) || (policy.Cookies && policy.Cookies.Locked)) ) { setInputsDisabledState(true); } if (tPPrefisLocked) { // An extension can't control this setting if either pref is locked. hideControllingExtension(TRACKING_PROTECTION_KEY); setInputsDisabledState(false); } else { handleControllingExtension( PREF_SETTING_TYPE, TRACKING_PROTECTION_KEY ).then(setInputsDisabledState); } }, /** * Hide the "Change Block List" link for trackers/tracking content in the * custom Content Blocking/ETP panel. By default, it will not be visible. */ _showCustomBlockList() { let prefValue = Services.prefs.getBoolPref( "browser.contentblocking.customBlockList.preferences.ui.enabled" ); if (!prefValue) { document.getElementById("changeBlockListLink").style.display = "none"; } else { setEventListener("changeBlockListLink", "click", this.showBlockLists); } }, /** * Set up handlers for showing and hiding controlling extension info * for tracking protection. */ _initTrackingProtectionExtensionControl() { setEventListener( "contentBlockingDisableTrackingProtectionExtension", "command", makeDisableControllingExtension( PREF_SETTING_TYPE, TRACKING_PROTECTION_KEY ) ); let trackingProtectionObserver = { observe(subject, topic, data) { gPrivacyPane._updateTrackingProtectionUI(); }, }; for (let pref of TRACKING_PROTECTION_PREFS) { Services.prefs.addObserver(pref, trackingProtectionObserver); } window.addEventListener("unload", () => { for (let pref of TRACKING_PROTECTION_PREFS) { Services.prefs.removeObserver(pref, trackingProtectionObserver); } }); }, _initQuickActionsSection() { let showPref = Preferences.get("browser.urlbar.quickactions.showPrefs"); let showQuickActionsGroup = () => { document.getElementById("quickActionsBox").hidden = !showPref.value; }; showPref.on("change", showQuickActionsGroup); showQuickActionsGroup(); }, syncFromHttpsOnlyPref() { let httpsOnlyOnPref = Services.prefs.getBoolPref( "dom.security.https_only_mode" ); let httpsOnlyOnPBMPref = Services.prefs.getBoolPref( "dom.security.https_only_mode_pbm" ); let httpsOnlyRadioGroup = document.getElementById("httpsOnlyRadioGroup"); let httpsOnlyExceptionButton = document.getElementById( "httpsOnlyExceptionButton" ); if (httpsOnlyOnPref) { httpsOnlyRadioGroup.value = "enabled"; httpsOnlyExceptionButton.disabled = false; } else if (httpsOnlyOnPBMPref) { httpsOnlyRadioGroup.value = "privateOnly"; httpsOnlyExceptionButton.disabled = true; } else { httpsOnlyRadioGroup.value = "disabled"; httpsOnlyExceptionButton.disabled = true; } if ( Services.prefs.prefIsLocked("dom.security.https_only_mode") || Services.prefs.prefIsLocked("dom.security.https_only_mode_pbm") ) { httpsOnlyRadioGroup.disabled = true; } }, syncToHttpsOnlyPref() { let value = document.getElementById("httpsOnlyRadioGroup").value; Services.prefs.setBoolPref( "dom.security.https_only_mode_pbm", value == "privateOnly" ); Services.prefs.setBoolPref( "dom.security.https_only_mode", value == "enabled" ); }, /** * Init HTTPS-Only mode and corresponding prefs */ initHttpsOnly() { // Set radio-value based on the pref value this.syncFromHttpsOnlyPref(); // Create event listener for when the user clicks // on one of the radio buttons setEventListener( "httpsOnlyRadioGroup", "command", this.syncToHttpsOnlyPref ); // Update radio-value when the pref changes Preferences.get("dom.security.https_only_mode").on("change", () => this.syncFromHttpsOnlyPref() ); Preferences.get("dom.security.https_only_mode_pbm").on("change", () => this.syncFromHttpsOnlyPref() ); }, get dnsOverHttpsResolvers() { let providers = DoHConfigController.currentConfig.providerList; // if there's no default, we'll hold its position with an empty string let defaultURI = DoHConfigController.currentConfig.fallbackProviderURI; let defaultIndex = providers.findIndex(p => p.uri == defaultURI); if (defaultIndex == -1 && defaultURI) { // the default value for the pref isn't included in the resolvers list // so we'll make a stub for it. Without an id, we'll have to use the url as the label providers.unshift({ uri: defaultURI }); } return providers; }, updateDoHResolverList(mode) { let resolvers = this.dnsOverHttpsResolvers; let currentURI = Preferences.get("network.trr.uri").value; if (!currentURI) { currentURI = Preferences.get("network.trr.default_provider_uri").value; } let menu = document.getElementById(`${mode}ResolverChoices`); let selectedIndex = currentURI ? resolvers.findIndex(r => r.uri == currentURI) : 0; if (selectedIndex == -1) { // select the last "Custom" item selectedIndex = menu.itemCount - 1; } menu.selectedIndex = selectedIndex; let customInput = document.getElementById(`${mode}InputField`); customInput.hidden = menu.value != "custom"; }, populateDoHResolverList(mode) { let resolvers = this.dnsOverHttpsResolvers; let defaultURI = DoHConfigController.currentConfig.fallbackProviderURI; let menu = document.getElementById(`${mode}ResolverChoices`); // populate the DNS-Over-HTTPS resolver list menu.removeAllItems(); for (let resolver of resolvers) { let item = menu.appendItem(undefined, resolver.uri); if (resolver.uri == defaultURI) { document.l10n.setAttributes( item, "connection-dns-over-https-url-item-default", { name: resolver.UIName || resolver.uri, } ); } else { item.label = resolver.UIName || resolver.uri; } } let lastItem = menu.appendItem(undefined, "custom"); document.l10n.setAttributes( lastItem, "connection-dns-over-https-url-custom" ); // set initial selection in the resolver provider picker this.updateDoHResolverList(mode); let customInput = document.getElementById(`${mode}InputField`); function updateURIPref() { if (customInput.value == "") { // Setting the pref to empty string will make it have the default // pref value which makes us fallback to using the default TRR // resolver in network.trr.default_provider_uri. // If the input is empty we set it to "(space)" which is essentially // the same. Services.prefs.setStringPref("network.trr.uri", " "); } else { Services.prefs.setStringPref("network.trr.uri", customInput.value); } } menu.addEventListener("command", () => { if (menu.value == "custom") { customInput.hidden = false; updateURIPref(); } else { customInput.hidden = true; Services.prefs.setStringPref("network.trr.uri", menu.value); } Services.telemetry.recordEvent( "security.doh.settings", "provider_choice", "value", menu.value ); // Update other menu too. let otherMode = mode == "dohEnabled" ? "dohStrict" : "dohEnabled"; let otherMenu = document.getElementById(`${otherMode}ResolverChoices`); let otherInput = document.getElementById(`${otherMode}InputField`); otherMenu.value = menu.value; otherInput.hidden = otherMenu.value != "custom"; }); // Change the URL when you press ENTER in the input field it or loses focus customInput.addEventListener("change", () => { updateURIPref(); }); }, async updateDoHStatus() { let trrURI = Services.dns.currentTrrURI; let hostname = ""; try { hostname = new URL(trrURI).hostname; } catch (e) { hostname = await document.l10n.formatValue("preferences-doh-bad-url"); } let steering = document.getElementById("dohSteeringStatus"); steering.hidden = true; let dohResolver = document.getElementById("dohResolver"); dohResolver.hidden = true; let status = document.getElementById("dohStatus"); async function setStatus(localizedStringName, options) { let opts = options || {}; let statusString = await document.l10n.formatValue( localizedStringName, opts ); document.l10n.setAttributes(status, "preferences-doh-status", { status: statusString, }); } function computeStatus() { let mode = Services.dns.currentTrrMode; let confirmationState = Services.dns.currentTrrConfirmationState; if ( mode == Ci.nsIDNSService.MODE_TRRFIRST || mode == Ci.nsIDNSService.MODE_TRRONLY ) { switch (confirmationState) { case Ci.nsIDNSService.CONFIRM_TRYING_OK: case Ci.nsIDNSService.CONFIRM_OK: case Ci.nsIDNSService.CONFIRM_DISABLED: return "preferences-doh-status-active"; default: return "preferences-doh-status-not-active"; } } return "preferences-doh-status-disabled"; } let errReason = ""; let confirmationStatus = Services.dns.lastConfirmationStatus; if (confirmationStatus != Cr.NS_OK) { errReason = ChromeUtils.getXPCOMErrorName(confirmationStatus); } else { errReason = Services.dns.getTRRSkipReasonName( Services.dns.lastConfirmationSkipReason ); } let statusLabel = computeStatus(); // setStatus will format and set the statusLabel asynchronously. setStatus(statusLabel, { reason: errReason }); dohResolver.hidden = statusLabel == "preferences-doh-status-disabled"; let statusLearnMore = document.getElementById("dohStatusLearnMore"); statusLearnMore.hidden = statusLabel != "preferences-doh-status-not-active"; // No need to set the resolver name since we're not going to show it. if (statusLabel == "preferences-doh-status-disabled") { return; } function nameOrDomain() { for (let resolver of DoHConfigController.currentConfig.providerList) { if (resolver.uri == trrURI) { return resolver.UIName || hostname || trrURI; } } // Also check if this is a steering provider. for (let resolver of DoHConfigController.currentConfig.providerSteering .providerList) { if (resolver.uri == trrURI) { steering.hidden = false; return resolver.UIName || hostname || trrURI; } } return hostname; } let resolverNameOrDomain = nameOrDomain(); document.l10n.setAttributes(dohResolver, "preferences-doh-resolver", { name: resolverNameOrDomain, }); }, highlightDoHCategoryAndUpdateStatus() { let value = Preferences.get("network.trr.mode").value; let defaultOption = document.getElementById("dohOptionDefault"); let enabledOption = document.getElementById("dohOptionEnabled"); let strictOption = document.getElementById("dohOptionStrict"); let offOption = document.getElementById("dohOptionOff"); defaultOption.classList.remove("selected"); enabledOption.classList.remove("selected"); strictOption.classList.remove("selected"); offOption.classList.remove("selected"); switch (value) { case Ci.nsIDNSService.MODE_NATIVEONLY: defaultOption.classList.add("selected"); break; case Ci.nsIDNSService.MODE_TRRFIRST: enabledOption.classList.add("selected"); break; case Ci.nsIDNSService.MODE_TRRONLY: strictOption.classList.add("selected"); break; case Ci.nsIDNSService.MODE_TRROFF: offOption.classList.add("selected"); break; default: // The pref is set to a random value. // This shouldn't happen, but let's make sure off is selected. offOption.classList.add("selected"); document.getElementById("dohCategoryRadioGroup").selectedIndex = 3; break; } // When the mode is set to 0 we need to clear the URI so // doh-rollout can kick in. if (value == Ci.nsIDNSService.MODE_NATIVEONLY) { Services.prefs.clearUserPref("network.trr.uri"); Services.prefs.clearUserPref("doh-rollout.disable-heuristics"); } gPrivacyPane.updateDoHStatus(); }, /** * Init DoH corresponding prefs */ initDoH() { Services.telemetry.setEventRecordingEnabled("security.doh.settings", true); setEventListener("dohDefaultArrow", "command", this.toggleExpansion); setEventListener("dohEnabledArrow", "command", this.toggleExpansion); setEventListener("dohStrictArrow", "command", this.toggleExpansion); function modeButtonPressed(e) { // Clicking the active mode again should not generate another event if ( parseInt(e.target.value) == Preferences.get("network.trr.mode").value ) { return; } Services.telemetry.recordEvent( "security.doh.settings", "mode_changed", "button", e.target.id ); } setEventListener("dohDefaultRadio", "command", modeButtonPressed); setEventListener("dohEnabledRadio", "command", modeButtonPressed); setEventListener("dohStrictRadio", "command", modeButtonPressed); setEventListener("dohOffRadio", "command", modeButtonPressed); function warnCheckboxClicked(e) { Services.telemetry.recordEvent( "security.doh.settings", "warn_checkbox", "checkbox", `${e.target.checked}` ); } setEventListener("dohWarnCheckbox1", "command", warnCheckboxClicked); setEventListener("dohWarnCheckbox2", "command", warnCheckboxClicked); this.populateDoHResolverList("dohEnabled"); this.populateDoHResolverList("dohStrict"); Preferences.get("network.trr.uri").on("change", () => { gPrivacyPane.updateDoHResolverList("dohEnabled"); gPrivacyPane.updateDoHResolverList("dohStrict"); gPrivacyPane.updateDoHStatus(); }); // Update status box and hightlightling when the pref changes Preferences.get("network.trr.mode").on( "change", gPrivacyPane.highlightDoHCategoryAndUpdateStatus ); this.highlightDoHCategoryAndUpdateStatus(); Services.obs.addObserver(this, "network:trr-uri-changed"); Services.obs.addObserver(this, "network:trr-mode-changed"); Services.obs.addObserver(this, "network:trr-confirmation"); let unload = () => { Services.obs.removeObserver(this, "network:trr-uri-changed"); Services.obs.removeObserver(this, "network:trr-mode-changed"); Services.obs.removeObserver(this, "network:trr-confirmation"); }; window.addEventListener("unload", unload, { once: true }); if (Preferences.get("network.trr_ui.show_fallback_warning_option").value) { document.getElementById("dohWarningBox1").hidden = false; document.getElementById("dohWarningBox2").hidden = false; } let uriPref = Services.prefs.getStringPref("network.trr.uri"); // If the value isn't one of the providers, we need to update the // custom_uri pref to make sure the input box contains the correct URL. if (uriPref && !this.dnsOverHttpsResolvers.some(e => e.uri == uriPref)) { Services.prefs.setStringPref( "network.trr.custom_uri", Services.prefs.getStringPref("network.trr.uri") ); } if (Services.prefs.prefIsLocked("network.trr.mode")) { document.getElementById("dohCategoryRadioGroup").disabled = true; Services.prefs.setStringPref("network.trr.custom_uri", uriPref); } }, /** * Sets up the UI for the number of days of history to keep, and updates the * label of the "Clear Now..." button. */ init() { this._updateSanitizeSettingsButton(); this.initDeleteOnCloseBox(); this.syncSanitizationPrefsWithDeleteOnClose(); this.initializeHistoryMode(); this.updateHistoryModePane(); this.updatePrivacyMicroControls(); this.initAutoStartPrivateBrowsingReverter(); /* Initialize Content Blocking */ this.initContentBlocking(); this._showCustomBlockList(); this.trackingProtectionReadPrefs(); this.networkCookieBehaviorReadPrefs(); this._initTrackingProtectionExtensionControl(); Services.telemetry.setEventRecordingEnabled("pwmgr", true); Preferences.get("privacy.trackingprotection.enabled").on( "change", gPrivacyPane.trackingProtectionReadPrefs.bind(gPrivacyPane) ); Preferences.get("privacy.trackingprotection.pbmode.enabled").on( "change", gPrivacyPane.trackingProtectionReadPrefs.bind(gPrivacyPane) ); // Watch all of the prefs that the new Cookies & Site Data UI depends on Preferences.get("network.cookie.cookieBehavior").on( "change", gPrivacyPane.networkCookieBehaviorReadPrefs.bind(gPrivacyPane) ); Preferences.get("browser.privatebrowsing.autostart").on( "change", gPrivacyPane.networkCookieBehaviorReadPrefs.bind(gPrivacyPane) ); Preferences.get("privacy.firstparty.isolate").on( "change", gPrivacyPane.networkCookieBehaviorReadPrefs.bind(gPrivacyPane) ); setEventListener( "trackingProtectionExceptions", "command", gPrivacyPane.showTrackingProtectionExceptions ); Preferences.get("privacy.sanitize.sanitizeOnShutdown").on( "change", gPrivacyPane._updateSanitizeSettingsButton.bind(gPrivacyPane) ); Preferences.get("browser.privatebrowsing.autostart").on( "change", gPrivacyPane.updatePrivacyMicroControls.bind(gPrivacyPane) ); setEventListener("historyMode", "command", function () { gPrivacyPane.updateHistoryModePane(); gPrivacyPane.updateHistoryModePrefs(); gPrivacyPane.updatePrivacyMicroControls(); gPrivacyPane.updateAutostart(); }); setEventListener("clearHistoryButton", "command", function () { let historyMode = document.getElementById("historyMode"); // Select "everything" in the clear history dialog if the // user has set their history mode to never remember history. gPrivacyPane.clearPrivateDataNow(historyMode.value == "dontremember"); }); setEventListener("openSearchEnginePreferences", "click", function (event) { if (event.button == 0) { gotoPref("search"); } return false; }); setEventListener( "privateBrowsingAutoStart", "command", gPrivacyPane.updateAutostart ); setEventListener( "cookieExceptions", "command", gPrivacyPane.showCookieExceptions ); setEventListener( "httpsOnlyExceptionButton", "command", gPrivacyPane.showHttpsOnlyModeExceptions ); setEventListener( "dohExceptionsButton", "command", gPrivacyPane.showDoHExceptions ); setEventListener( "clearDataSettings", "command", gPrivacyPane.showClearPrivateDataSettings ); setEventListener( "passwordExceptions", "command", gPrivacyPane.showPasswordExceptions ); setEventListener( "useMasterPassword", "command", gPrivacyPane.updateMasterPasswordButton ); setEventListener( "changeMasterPassword", "command", gPrivacyPane.changeMasterPassword ); setEventListener("showPasswords", "command", gPrivacyPane.showPasswords); setEventListener( "addonExceptions", "command", gPrivacyPane.showAddonExceptions ); setEventListener( "viewCertificatesButton", "command", gPrivacyPane.showCertificates ); setEventListener( "viewSecurityDevicesButton", "command", gPrivacyPane.showSecurityDevices ); this._pane = document.getElementById("panePrivacy"); this._initPasswordGenerationUI(); this._initRelayIntegrationUI(); this._initMasterPasswordUI(); this.initListenersForExtensionControllingPasswordManager(); this._initSafeBrowsing(); setEventListener( "autoplaySettingsButton", "command", gPrivacyPane.showAutoplayMediaExceptions ); setEventListener( "notificationSettingsButton", "command", gPrivacyPane.showNotificationExceptions ); setEventListener( "locationSettingsButton", "command", gPrivacyPane.showLocationExceptions ); setEventListener( "xrSettingsButton", "command", gPrivacyPane.showXRExceptions ); setEventListener( "cameraSettingsButton", "command", gPrivacyPane.showCameraExceptions ); setEventListener( "microphoneSettingsButton", "command", gPrivacyPane.showMicrophoneExceptions ); document.getElementById("speakerSettingsRow").hidden = !Services.prefs.getBoolPref("media.setsinkid.enabled", false); setEventListener( "speakerSettingsButton", "command", gPrivacyPane.showSpeakerExceptions ); setEventListener( "popupPolicyButton", "command", gPrivacyPane.showPopupExceptions ); setEventListener( "notificationsDoNotDisturb", "command", gPrivacyPane.toggleDoNotDisturbNotifications ); setSyncFromPrefListener("contentBlockingBlockCookiesCheckbox", () => this.readBlockCookies() ); setSyncToPrefListener("contentBlockingBlockCookiesCheckbox", () => this.writeBlockCookies() ); setSyncFromPrefListener("blockCookiesMenu", () => this.readBlockCookiesFrom() ); setSyncToPrefListener("blockCookiesMenu", () => this.writeBlockCookiesFrom() ); setSyncFromPrefListener("savePasswords", () => this.readSavePasswords()); let microControlHandler = el => this.ensurePrivacyMicroControlUncheckedWhenDisabled(el); setSyncFromPrefListener("rememberHistory", microControlHandler); setSyncFromPrefListener("rememberForms", microControlHandler); setSyncFromPrefListener("alwaysClear", microControlHandler); setSyncFromPrefListener("popupPolicy", () => this.updateButtons("popupPolicyButton", "dom.disable_open_during_load") ); setSyncFromPrefListener("warnAddonInstall", () => this.readWarnAddonInstall() ); setSyncFromPrefListener("enableOCSP", () => this.readEnableOCSP()); setSyncToPrefListener("enableOCSP", () => this.writeEnableOCSP()); if (AlertsServiceDND) { let notificationsDoNotDisturbBox = document.getElementById( "notificationsDoNotDisturbBox" ); notificationsDoNotDisturbBox.removeAttribute("hidden"); let checkbox = document.getElementById("notificationsDoNotDisturb"); document.l10n.setAttributes(checkbox, "permissions-notification-pause"); if (AlertsServiceDND.manualDoNotDisturb) { let notificationsDoNotDisturb = document.getElementById( "notificationsDoNotDisturb" ); notificationsDoNotDisturb.setAttribute("checked", true); } } this._initAddressBar(); this.initSiteDataControls(); setEventListener( "clearSiteDataButton", "command", gPrivacyPane.clearSiteData ); setEventListener( "siteDataSettings", "command", gPrivacyPane.showSiteDataSettings ); this.initCookieBannerHandling(); this.initDataCollection(); if (AppConstants.MOZ_DATA_REPORTING) { if (AppConstants.MOZ_CRASHREPORTER) { this.initSubmitCrashes(); } this.initSubmitHealthReport(); setEventListener( "submitHealthReportBox", "command", gPrivacyPane.updateSubmitHealthReport ); setEventListener( "telemetryDataDeletionLearnMore", "click", gPrivacyPane.showDataDeletion ); if (AppConstants.MOZ_NORMANDY) { this.initOptOutStudyCheckbox(); } this.initAddonRecommendationsCheckbox(); } let signonBundle = document.getElementById("signonBundle"); let pkiBundle = document.getElementById("pkiBundle"); appendSearchKeywords("showPasswords", [ signonBundle.getString("loginsDescriptionAll2"), ]); appendSearchKeywords("viewSecurityDevicesButton", [ pkiBundle.getString("enable_fips"), ]); if (!PrivateBrowsingUtils.enabled) { document.getElementById("privateBrowsingAutoStart").hidden = true; document.querySelector("menuitem[value='dontremember']").hidden = true; } /* init HTTPS-Only mode */ this.initHttpsOnly(); this.initDoH(); // Notify observers that the UI is now ready Services.obs.notifyObservers(window, "privacy-pane-loaded"); }, initSiteDataControls() { Services.obs.addObserver(this, "sitedatamanager:sites-updated"); Services.obs.addObserver(this, "sitedatamanager:updating-sites"); let unload = () => { window.removeEventListener("unload", unload); Services.obs.removeObserver(this, "sitedatamanager:sites-updated"); Services.obs.removeObserver(this, "sitedatamanager:updating-sites"); }; window.addEventListener("unload", unload); SiteDataManager.updateSites(); }, // CONTENT BLOCKING /** * Initializes the content blocking section. */ initContentBlocking() { setEventListener( "contentBlockingTrackingProtectionCheckbox", "command", this.trackingProtectionWritePrefs ); setEventListener( "contentBlockingTrackingProtectionCheckbox", "command", this._updateTrackingProtectionUI ); setEventListener( "contentBlockingCryptominersCheckbox", "command", this.updateCryptominingLists ); setEventListener( "contentBlockingFingerprintersCheckbox", "command", this.updateFingerprintingLists ); setEventListener( "trackingProtectionMenu", "command", this.trackingProtectionWritePrefs ); setEventListener("standardArrow", "command", this.toggleExpansion); setEventListener("strictArrow", "command", this.toggleExpansion); setEventListener("customArrow", "command", this.toggleExpansion); Preferences.get("network.cookie.cookieBehavior").on( "change", gPrivacyPane.readBlockCookies.bind(gPrivacyPane) ); Preferences.get("browser.contentblocking.category").on( "change", gPrivacyPane.highlightCBCategory ); // If any relevant content blocking pref changes, show a warning that the changes will // not be implemented until they refresh their tabs. for (let pref of CONTENT_BLOCKING_PREFS) { Preferences.get(pref).on("change", gPrivacyPane.maybeNotifyUserToReload); // If the value changes, run populateCategoryContents, since that change might have been // triggered by a default value changing in the standard category. Preferences.get(pref).on("change", gPrivacyPane.populateCategoryContents); } Preferences.get("urlclassifier.trackingTable").on( "change", gPrivacyPane.maybeNotifyUserToReload ); for (let button of document.querySelectorAll(".reload-tabs-button")) { button.addEventListener("command", gPrivacyPane.reloadAllOtherTabs); } let cryptoMinersOption = document.getElementById( "contentBlockingCryptominersOption" ); let fingerprintersOption = document.getElementById( "contentBlockingFingerprintersOption" ); let trackingAndIsolateOption = document.querySelector( "#blockCookiesMenu menuitem[value='trackers-plus-isolate']" ); cryptoMinersOption.hidden = !Services.prefs.getBoolPref( "browser.contentblocking.cryptomining.preferences.ui.enabled" ); fingerprintersOption.hidden = !Services.prefs.getBoolPref( "browser.contentblocking.fingerprinting.preferences.ui.enabled" ); let updateTrackingAndIsolateOption = () => { trackingAndIsolateOption.hidden = !Services.prefs.getBoolPref( "browser.contentblocking.reject-and-isolate-cookies.preferences.ui.enabled", false ) || gIsFirstPartyIsolated; }; Preferences.get("privacy.firstparty.isolate").on( "change", updateTrackingAndIsolateOption ); updateTrackingAndIsolateOption(); Preferences.get("browser.contentblocking.features.strict").on( "change", this.populateCategoryContents ); this.populateCategoryContents(); this.highlightCBCategory(); this.readBlockCookies(); // Toggles the text "Cross-site and social media trackers" based on the // social tracking pref. If the pref is false, the text reads // "Cross-site trackers". const STP_COOKIES_PREF = "privacy.socialtracking.block_cookies.enabled"; if (Services.prefs.getBoolPref(STP_COOKIES_PREF)) { let contentBlockOptionSocialMedia = document.getElementById( "blockCookiesSocialMedia" ); document.l10n.setAttributes( contentBlockOptionSocialMedia, "sitedata-option-block-cross-site-tracking-cookies" ); } setUpContentBlockingWarnings(); initTCPStandardSection(); }, populateCategoryContents() { for (let type of ["strict", "standard"]) { let rulesArray = []; let selector; if (type == "strict") { selector = "#contentBlockingOptionStrict"; rulesArray = Services.prefs .getStringPref("browser.contentblocking.features.strict") .split(","); if (gIsFirstPartyIsolated) { let idx = rulesArray.indexOf("cookieBehavior5"); if (idx != -1) { rulesArray[idx] = "cookieBehavior4"; } } } else { selector = "#contentBlockingOptionStandard"; // In standard show/hide UI items based on the default values of the relevant prefs. let defaults = Services.prefs.getDefaultBranch(""); let cookieBehavior = defaults.getIntPref( "network.cookie.cookieBehavior" ); switch (cookieBehavior) { case Ci.nsICookieService.BEHAVIOR_ACCEPT: rulesArray.push("cookieBehavior0"); break; case Ci.nsICookieService.BEHAVIOR_REJECT_FOREIGN: rulesArray.push("cookieBehavior1"); break; case Ci.nsICookieService.BEHAVIOR_REJECT: rulesArray.push("cookieBehavior2"); break; case Ci.nsICookieService.BEHAVIOR_LIMIT_FOREIGN: rulesArray.push("cookieBehavior3"); break; case Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER: rulesArray.push("cookieBehavior4"); break; case BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN: rulesArray.push( gIsFirstPartyIsolated ? "cookieBehavior4" : "cookieBehavior5" ); break; } let cookieBehaviorPBM = defaults.getIntPref( "network.cookie.cookieBehavior.pbmode" ); switch (cookieBehaviorPBM) { case Ci.nsICookieService.BEHAVIOR_ACCEPT: rulesArray.push("cookieBehaviorPBM0"); break; case Ci.nsICookieService.BEHAVIOR_REJECT_FOREIGN: rulesArray.push("cookieBehaviorPBM1"); break; case Ci.nsICookieService.BEHAVIOR_REJECT: rulesArray.push("cookieBehaviorPBM2"); break; case Ci.nsICookieService.BEHAVIOR_LIMIT_FOREIGN: rulesArray.push("cookieBehaviorPBM3"); break; case Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER: rulesArray.push("cookieBehaviorPBM4"); break; case BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN: rulesArray.push( gIsFirstPartyIsolated ? "cookieBehaviorPBM4" : "cookieBehaviorPBM5" ); break; } rulesArray.push( defaults.getBoolPref( "privacy.trackingprotection.cryptomining.enabled" ) ? "cm" : "-cm" ); rulesArray.push( defaults.getBoolPref( "privacy.trackingprotection.fingerprinting.enabled" ) ? "fp" : "-fp" ); rulesArray.push( Services.prefs.getBoolPref( "privacy.socialtracking.block_cookies.enabled" ) ? "stp" : "-stp" ); rulesArray.push( defaults.getBoolPref("privacy.trackingprotection.enabled") ? "tp" : "-tp" ); rulesArray.push( defaults.getBoolPref("privacy.trackingprotection.pbmode.enabled") ? "tpPrivate" : "-tpPrivate" ); } // Hide all cookie options first, until we learn which one should be showing. document.querySelector(selector + " .all-cookies-option").hidden = true; document.querySelector( selector + " .unvisited-cookies-option" ).hidden = true; document.querySelector( selector + " .cross-site-cookies-option" ).hidden = true; document.querySelector( selector + " .third-party-tracking-cookies-option" ).hidden = true; document.querySelector( selector + " .all-third-party-cookies-private-windows-option" ).hidden = true; document.querySelector( selector + " .all-third-party-cookies-option" ).hidden = true; document.querySelector(selector + " .social-media-option").hidden = true; for (let item of rulesArray) { // Note "cookieBehavior0", will result in no UI changes, so is not listed here. switch (item) { case "tp": document.querySelector( selector + " .trackers-option" ).hidden = false; break; case "-tp": document.querySelector( selector + " .trackers-option" ).hidden = true; break; case "tpPrivate": document.querySelector( selector + " .pb-trackers-option" ).hidden = false; break; case "-tpPrivate": document.querySelector( selector + " .pb-trackers-option" ).hidden = true; break; case "fp": document.querySelector( selector + " .fingerprinters-option" ).hidden = false; break; case "-fp": document.querySelector( selector + " .fingerprinters-option" ).hidden = true; break; case "cm": document.querySelector( selector + " .cryptominers-option" ).hidden = false; break; case "-cm": document.querySelector( selector + " .cryptominers-option" ).hidden = true; break; case "stp": // Store social tracking cookies pref const STP_COOKIES_PREF = "privacy.socialtracking.block_cookies.enabled"; if (Services.prefs.getBoolPref(STP_COOKIES_PREF)) { document.querySelector( selector + " .social-media-option" ).hidden = false; } break; case "-stp": // Store social tracking cookies pref document.querySelector( selector + " .social-media-option" ).hidden = true; break; case "cookieBehavior1": document.querySelector( selector + " .all-third-party-cookies-option" ).hidden = false; break; case "cookieBehavior2": document.querySelector( selector + " .all-cookies-option" ).hidden = false; break; case "cookieBehavior3": document.querySelector( selector + " .unvisited-cookies-option" ).hidden = false; break; case "cookieBehavior4": document.querySelector( selector + " .third-party-tracking-cookies-option" ).hidden = false; break; case "cookieBehavior5": document.querySelector( selector + " .cross-site-cookies-option" ).hidden = false; break; case "cookieBehaviorPBM5": // We only need to show the cookie option for private windows if the // cookieBehaviors are different between regular windows and private // windows. if (!rulesArray.includes("cookieBehavior5")) { document.querySelector( selector + " .all-third-party-cookies-private-windows-option" ).hidden = false; } break; } } // Hide the "tracking protection in private browsing" list item // if the "tracking protection enabled in all windows" list item is showing. if (!document.querySelector(selector + " .trackers-option").hidden) { document.querySelector(selector + " .pb-trackers-option").hidden = true; } } }, highlightCBCategory() { let value = Preferences.get("browser.contentblocking.category").value; let standardEl = document.getElementById("contentBlockingOptionStandard"); let strictEl = document.getElementById("contentBlockingOptionStrict"); let customEl = document.getElementById("contentBlockingOptionCustom"); standardEl.classList.remove("selected"); strictEl.classList.remove("selected"); customEl.classList.remove("selected"); switch (value) { case "strict": strictEl.classList.add("selected"); break; case "custom": customEl.classList.add("selected"); break; case "standard": /* fall through */ default: standardEl.classList.add("selected"); break; } }, updateCryptominingLists() { let listPrefs = [ "urlclassifier.features.cryptomining.blacklistTables", "urlclassifier.features.cryptomining.whitelistTables", ]; let listValue = listPrefs .map(l => Services.prefs.getStringPref(l)) .join(","); listManager.forceUpdates(listValue); }, updateFingerprintingLists() { let listPrefs = [ "urlclassifier.features.fingerprinting.blacklistTables", "urlclassifier.features.fingerprinting.whitelistTables", ]; let listValue = listPrefs .map(l => Services.prefs.getStringPref(l)) .join(","); listManager.forceUpdates(listValue); }, // TRACKING PROTECTION MODE /** * Selects the right item of the Tracking Protection menulist and checkbox. */ trackingProtectionReadPrefs() { let enabledPref = Preferences.get("privacy.trackingprotection.enabled"); let pbmPref = Preferences.get("privacy.trackingprotection.pbmode.enabled"); let tpMenu = document.getElementById("trackingProtectionMenu"); let tpCheckbox = document.getElementById( "contentBlockingTrackingProtectionCheckbox" ); this._updateTrackingProtectionUI(); // Global enable takes precedence over enabled in Private Browsing. if (enabledPref.value) { tpMenu.value = "always"; tpCheckbox.checked = true; } else if (pbmPref.value) { tpMenu.value = "private"; tpCheckbox.checked = true; } else { tpMenu.value = "never"; tpCheckbox.checked = false; } }, /** * Selects the right items of the new Cookies & Site Data UI. */ networkCookieBehaviorReadPrefs() { let behavior = Services.cookies.getCookieBehavior(false); let blockCookiesMenu = document.getElementById("blockCookiesMenu"); let deleteOnCloseCheckbox = document.getElementById("deleteOnClose"); let deleteOnCloseNote = document.getElementById("deleteOnCloseNote"); let blockCookies = behavior != Ci.nsICookieService.BEHAVIOR_ACCEPT; let cookieBehaviorLocked = Services.prefs.prefIsLocked( "network.cookie.cookieBehavior" ); let blockCookiesControlsDisabled = !blockCookies || cookieBehaviorLocked; blockCookiesMenu.disabled = blockCookiesControlsDisabled; let completelyBlockCookies = behavior == Ci.nsICookieService.BEHAVIOR_REJECT; let privateBrowsing = Preferences.get( "browser.privatebrowsing.autostart" ).value; deleteOnCloseCheckbox.disabled = privateBrowsing || completelyBlockCookies; deleteOnCloseNote.hidden = !privateBrowsing; switch (behavior) { case Ci.nsICookieService.BEHAVIOR_ACCEPT: break; case Ci.nsICookieService.BEHAVIOR_REJECT_FOREIGN: blockCookiesMenu.value = "all-third-parties"; break; case Ci.nsICookieService.BEHAVIOR_REJECT: blockCookiesMenu.value = "always"; break; case Ci.nsICookieService.BEHAVIOR_LIMIT_FOREIGN: blockCookiesMenu.value = "unvisited"; break; case Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER: blockCookiesMenu.value = "trackers"; break; case BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN: blockCookiesMenu.value = "trackers-plus-isolate"; break; } }, /** * Sets the pref values based on the selected item of the radiogroup. */ trackingProtectionWritePrefs() { let enabledPref = Preferences.get("privacy.trackingprotection.enabled"); let pbmPref = Preferences.get("privacy.trackingprotection.pbmode.enabled"); let stpPref = Preferences.get( "privacy.trackingprotection.socialtracking.enabled" ); let stpCookiePref = Preferences.get( "privacy.socialtracking.block_cookies.enabled" ); // Currently, we don't expose the email tracking protection setting on our // privacy UI. Instead, we use the existing tracking protection checkbox to // control the email tracking protection. let emailTPPref = Preferences.get( "privacy.trackingprotection.emailtracking.enabled" ); let emailTPPBMPref = Preferences.get( "privacy.trackingprotection.emailtracking.pbmode.enabled" ); let tpMenu = document.getElementById("trackingProtectionMenu"); let tpCheckbox = document.getElementById( "contentBlockingTrackingProtectionCheckbox" ); let value; if (tpCheckbox.checked) { if (tpMenu.value == "never") { tpMenu.value = "private"; } value = tpMenu.value; } else { tpMenu.value = "never"; value = "never"; } switch (value) { case "always": enabledPref.value = true; pbmPref.value = true; emailTPPref.value = true; emailTPPBMPref.value = true; if (stpCookiePref.value) { stpPref.value = true; } break; case "private": enabledPref.value = false; pbmPref.value = true; emailTPPref.value = false; emailTPPBMPref.value = true; if (stpCookiePref.value) { stpPref.value = false; } break; case "never": enabledPref.value = false; pbmPref.value = false; emailTPPref.value = false; emailTPPBMPref.value = false; if (stpCookiePref.value) { stpPref.value = false; } break; } }, toggleExpansion(e) { let carat = e.target; carat.classList.toggle("up"); carat.closest(".privacy-detailedoption").classList.toggle("expanded"); carat.setAttribute( "aria-expanded", carat.getAttribute("aria-expanded") === "false" ); }, // HISTORY MODE /** * The list of preferences which affect the initial history mode settings. * If the auto start private browsing mode pref is active, the initial * history mode would be set to "Don't remember anything". * If ALL of these preferences are set to the values that correspond * to keeping some part of history, and the auto-start * private browsing mode is not active, the initial history mode would be * set to "Remember everything". * Otherwise, the initial history mode would be set to "Custom". * * Extensions adding their own preferences can set values here if needed. */ prefsForKeepingHistory: { "places.history.enabled": true, // History is enabled "browser.formfill.enable": true, // Form information is saved "privacy.sanitize.sanitizeOnShutdown": false, // Private date is NOT cleared on shutdown }, /** * The list of control IDs which are dependent on the auto-start private * browsing setting, such that in "Custom" mode they would be disabled if * the auto-start private browsing checkbox is checked, and enabled otherwise. * * Extensions adding their own controls can append their IDs to this array if needed. */ dependentControls: [ "rememberHistory", "rememberForms", "alwaysClear", "clearDataSettings", ], /** * Check whether preferences values are set to keep history * * @param aPrefs an array of pref names to check for * @returns boolean true if all of the prefs are set to keep history, * false otherwise */ _checkHistoryValues(aPrefs) { for (let pref of Object.keys(aPrefs)) { if (Preferences.get(pref).value != aPrefs[pref]) { return false; } } return true; }, /** * Initialize the history mode menulist based on the privacy preferences */ initializeHistoryMode() { let mode; let getVal = aPref => Preferences.get(aPref).value; if (getVal("privacy.history.custom")) { mode = "custom"; } else if (this._checkHistoryValues(this.prefsForKeepingHistory)) { if (getVal("browser.privatebrowsing.autostart")) { mode = "dontremember"; } else { mode = "remember"; } } else { mode = "custom"; } document.getElementById("historyMode").value = mode; }, /** * Update the selected pane based on the history mode menulist */ updateHistoryModePane() { let selectedIndex = -1; switch (document.getElementById("historyMode").value) { case "remember": selectedIndex = 0; break; case "dontremember": selectedIndex = 1; break; case "custom": selectedIndex = 2; break; } document.getElementById("historyPane").selectedIndex = selectedIndex; Preferences.get("privacy.history.custom").value = selectedIndex == 2; }, /** * Update the private browsing auto-start pref and the history mode * micro-management prefs based on the history mode menulist */ updateHistoryModePrefs() { let pref = Preferences.get("browser.privatebrowsing.autostart"); switch (document.getElementById("historyMode").value) { case "remember": if (pref.value) { pref.value = false; } // select the remember history option if needed Preferences.get("places.history.enabled").value = true; // select the remember forms history option Preferences.get("browser.formfill.enable").value = true; // select the clear on close option Preferences.get("privacy.sanitize.sanitizeOnShutdown").value = false; break; case "dontremember": if (!pref.value) { pref.value = true; } break; } }, /** * Update the privacy micro-management controls based on the * value of the private browsing auto-start preference. */ updatePrivacyMicroControls() { let clearDataSettings = document.getElementById("clearDataSettings"); if (document.getElementById("historyMode").value == "custom") { let disabled = Preferences.get("browser.privatebrowsing.autostart").value; this.dependentControls.forEach(aElement => { let control = document.getElementById(aElement); let preferenceId = control.getAttribute("preference"); if (!preferenceId) { let dependentControlId = control.getAttribute("control"); if (dependentControlId) { let dependentControl = document.getElementById(dependentControlId); preferenceId = dependentControl.getAttribute("preference"); } } let preference = preferenceId ? Preferences.get(preferenceId) : {}; control.disabled = disabled || preference.locked; if (control != clearDataSettings) { this.ensurePrivacyMicroControlUncheckedWhenDisabled(control); } }); clearDataSettings.removeAttribute("hidden"); if (!disabled) { // adjust the Settings button for sanitizeOnShutdown this._updateSanitizeSettingsButton(); } } else { clearDataSettings.hidden = true; } }, ensurePrivacyMicroControlUncheckedWhenDisabled(el) { if (Preferences.get("browser.privatebrowsing.autostart").value) { // Set checked to false when called from updatePrivacyMicroControls el.checked = false; // return false for the onsyncfrompreference case: return false; } return undefined; // tell preferencesBindings to assign the 'right' value. }, // CLEAR PRIVATE DATA /* * Preferences: * * privacy.sanitize.sanitizeOnShutdown * - true if the user's private data is cleared on startup according to the * Clear Private Data settings, false otherwise */ /** * Displays the Clear Private Data settings dialog. */ showClearPrivateDataSettings() { gSubDialog.open( "chrome://browser/content/preferences/dialogs/sanitize.xhtml", { features: "resizable=no" } ); }, /** * Displays a dialog from which individual parts of private data may be * cleared. */ clearPrivateDataNow(aClearEverything) { var ts = Preferences.get("privacy.sanitize.timeSpan"); var timeSpanOrig = ts.value; if (aClearEverything) { ts.value = 0; } gSubDialog.open("chrome://browser/content/sanitize.xhtml", { features: "resizable=no", closingCallback: () => { // reset the timeSpan pref if (aClearEverything) { ts.value = timeSpanOrig; } Services.obs.notifyObservers(null, "clear-private-data"); }, }); }, /* * On loading the page, assigns the state to the deleteOnClose checkbox that fits the pref selection */ initDeleteOnCloseBox() { let deleteOnCloseBox = document.getElementById("deleteOnClose"); deleteOnCloseBox.checked = (Preferences.get("privacy.sanitize.sanitizeOnShutdown").value && Preferences.get("privacy.clearOnShutdown.cookies").value && Preferences.get("privacy.clearOnShutdown.cache").value && Preferences.get("privacy.clearOnShutdown.offlineApps").value) || Preferences.get("browser.privatebrowsing.autostart").value; }, /* * Keeps the state of the deleteOnClose checkbox in sync with the pref selection */ syncSanitizationPrefsWithDeleteOnClose() { let deleteOnCloseBox = document.getElementById("deleteOnClose"); let historyMode = Preferences.get("privacy.history.custom"); let sanitizeOnShutdownPref = Preferences.get( "privacy.sanitize.sanitizeOnShutdown" ); // ClearOnClose cleaning categories let cookiePref = Preferences.get("privacy.clearOnShutdown.cookies"); let cachePref = Preferences.get("privacy.clearOnShutdown.cache"); let offlineAppsPref = Preferences.get( "privacy.clearOnShutdown.offlineApps" ); // Sync the cleaning prefs with the deleteOnClose box deleteOnCloseBox.addEventListener("command", () => { let { checked } = deleteOnCloseBox; cookiePref.value = checked; cachePref.value = checked; offlineAppsPref.value = checked; // Forget the current pref selection if sanitizeOnShutdown is disabled, // to not over clear when it gets enabled by the sync mechanism if (!sanitizeOnShutdownPref.value) { this._resetCleaningPrefs(); } // If no other cleaning category is selected, sanitizeOnShutdown gets synced with deleteOnClose sanitizeOnShutdownPref.value = this._isCustomCleaningPrefPresent() || checked; // Update the view of the history settings if (checked && !historyMode.value) { historyMode.value = "custom"; this.initializeHistoryMode(); this.updateHistoryModePane(); this.updatePrivacyMicroControls(); } }); cookiePref.on("change", this._onSanitizePrefChangeSyncClearOnClose); cachePref.on("change", this._onSanitizePrefChangeSyncClearOnClose); offlineAppsPref.on("change", this._onSanitizePrefChangeSyncClearOnClose); sanitizeOnShutdownPref.on( "change", this._onSanitizePrefChangeSyncClearOnClose ); }, /* * Sync the deleteOnClose box to its cleaning prefs */ _onSanitizePrefChangeSyncClearOnClose() { let deleteOnCloseBox = document.getElementById("deleteOnClose"); deleteOnCloseBox.checked = Preferences.get("privacy.clearOnShutdown.cookies").value && Preferences.get("privacy.clearOnShutdown.cache").value && Preferences.get("privacy.clearOnShutdown.offlineApps").value && Preferences.get("privacy.sanitize.sanitizeOnShutdown").value; }, /* * Unsets cleaning prefs that do not belong to DeleteOnClose */ _resetCleaningPrefs() { SANITIZE_ON_SHUTDOWN_PREFS_ONLY.forEach( pref => (Preferences.get(pref).value = false) ); }, /* Checks if the user set cleaning prefs that do not belong to DeleteOnClose */ _isCustomCleaningPrefPresent() { return SANITIZE_ON_SHUTDOWN_PREFS_ONLY.some( pref => Preferences.get(pref).value ); }, /** * Enables or disables the "Settings..." button depending * on the privacy.sanitize.sanitizeOnShutdown preference value */ _updateSanitizeSettingsButton() { var settingsButton = document.getElementById("clearDataSettings"); var sanitizeOnShutdownPref = Preferences.get( "privacy.sanitize.sanitizeOnShutdown" ); settingsButton.disabled = !sanitizeOnShutdownPref.value; }, toggleDoNotDisturbNotifications(event) { AlertsServiceDND.manualDoNotDisturb = event.target.checked; }, // PRIVATE BROWSING /** * Initialize the starting state for the auto-start private browsing mode pref reverter. */ initAutoStartPrivateBrowsingReverter() { // We determine the mode in initializeHistoryMode, which is guaranteed to have been // called before now, so this is up-to-date. let mode = document.getElementById("historyMode"); this._lastMode = mode.selectedIndex; // The value of the autostart pref, on the other hand, is gotten from Preferences, // which updates the DOM asynchronously, so we can't rely on the DOM. Get it directly // from the prefs. this._lastCheckState = Preferences.get( "browser.privatebrowsing.autostart" ).value; }, _lastMode: null, _lastCheckState: null, async updateAutostart() { let mode = document.getElementById("historyMode"); let autoStart = document.getElementById("privateBrowsingAutoStart"); let pref = Preferences.get("browser.privatebrowsing.autostart"); if ( (mode.value == "custom" && this._lastCheckState == autoStart.checked) || (mode.value == "remember" && !this._lastCheckState) || (mode.value == "dontremember" && this._lastCheckState) ) { // These are all no-op changes, so we don't need to prompt. this._lastMode = mode.selectedIndex; this._lastCheckState = autoStart.hasAttribute("checked"); return; } if (!this._shouldPromptForRestart) { // We're performing a revert. Just let it happen. return; } let buttonIndex = await confirmRestartPrompt( autoStart.checked, 1, true, false ); if (buttonIndex == CONFIRM_RESTART_PROMPT_RESTART_NOW) { pref.value = autoStart.hasAttribute("checked"); Services.startup.quit( Ci.nsIAppStartup.eAttemptQuit | Ci.nsIAppStartup.eRestart ); return; } this._shouldPromptForRestart = false; if (this._lastCheckState) { autoStart.checked = "checked"; } else { autoStart.removeAttribute("checked"); } pref.value = autoStart.hasAttribute("checked"); mode.selectedIndex = this._lastMode; mode.doCommand(); this._shouldPromptForRestart = true; }, /** * Displays fine-grained, per-site preferences for tracking protection. */ showTrackingProtectionExceptions() { let params = { permissionType: "trackingprotection", disableETPVisible: true, prefilledHost: "", hideStatusColumn: true, }; gSubDialog.open( "chrome://browser/content/preferences/dialogs/permissions.xhtml", undefined, params ); }, /** * Displays the available block lists for tracking protection. */ showBlockLists() { gSubDialog.open( "chrome://browser/content/preferences/dialogs/blocklists.xhtml" ); }, // COOKIES AND SITE DATA /* * Preferences: * * network.cookie.cookieBehavior * - determines how the browser should handle cookies: * 0 means enable all cookies * 1 means reject all third party cookies * 2 means disable all cookies * 3 means reject third party cookies unless at least one is already set for the eTLD * 4 means reject all trackers * 5 means reject all trackers and partition third-party cookies * see netwerk/cookie/src/CookieService.cpp for details */ /** * Reads the network.cookie.cookieBehavior preference value and * enables/disables the "blockCookiesMenu" menulist accordingly. */ readBlockCookies() { let bcControl = document.getElementById("blockCookiesMenu"); bcControl.disabled = Services.cookies.getCookieBehavior(false) == Ci.nsICookieService.BEHAVIOR_ACCEPT; }, /** * Updates the "accept third party cookies" menu based on whether the * "contentBlockingBlockCookiesCheckbox" checkbox is checked. */ writeBlockCookies() { let block = document.getElementById("contentBlockingBlockCookiesCheckbox"); let blockCookiesMenu = document.getElementById("blockCookiesMenu"); if (block.checked) { // Automatically select 'third-party trackers' as the default. blockCookiesMenu.selectedIndex = 0; return this.writeBlockCookiesFrom(); } return Ci.nsICookieService.BEHAVIOR_ACCEPT; }, readBlockCookiesFrom() { switch (Services.cookies.getCookieBehavior(false)) { case Ci.nsICookieService.BEHAVIOR_REJECT_FOREIGN: return "all-third-parties"; case Ci.nsICookieService.BEHAVIOR_REJECT: return "always"; case Ci.nsICookieService.BEHAVIOR_LIMIT_FOREIGN: return "unvisited"; case Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER: return "trackers"; case BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN: return "trackers-plus-isolate"; default: return undefined; } }, writeBlockCookiesFrom() { let block = document.getElementById("blockCookiesMenu").selectedItem; switch (block.value) { case "trackers": return Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER; case "unvisited": return Ci.nsICookieService.BEHAVIOR_LIMIT_FOREIGN; case "always": return Ci.nsICookieService.BEHAVIOR_REJECT; case "all-third-parties": return Ci.nsICookieService.BEHAVIOR_REJECT_FOREIGN; case "trackers-plus-isolate": return Ci.nsICookieService .BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN; default: return undefined; } }, /** * Discard the browsers of all tabs in all windows. Pinned tabs, as * well as tabs for which discarding doesn't succeed (e.g. selected * tabs, tabs with beforeunload listeners), are reloaded. */ reloadAllOtherTabs() { let ourTab = BrowserWindowTracker.getTopWindow().gBrowser.selectedTab; BrowserWindowTracker.orderedWindows.forEach(win => { let otherGBrowser = win.gBrowser; for (let tab of otherGBrowser.tabs) { if (tab == ourTab) { // Don't reload our preferences tab. continue; } if (tab.pinned || tab.selected) { otherGBrowser.reloadTab(tab); } else { otherGBrowser.discardBrowser(tab); } } }); for (let notification of document.querySelectorAll(".reload-tabs")) { notification.hidden = true; } }, /** * If there are more tabs than just the preferences tab, show a warning to the user that * they need to reload their tabs to apply the setting. */ maybeNotifyUserToReload() { let shouldShow = false; if (window.BrowserWindowTracker.orderedWindows.length > 1) { shouldShow = true; } else { let tabbrowser = window.BrowserWindowTracker.getTopWindow().gBrowser; if (tabbrowser.tabs.length > 1) { shouldShow = true; } } if (shouldShow) { for (let notification of document.querySelectorAll(".reload-tabs")) { notification.hidden = false; } } }, /** * Displays fine-grained, per-site preferences for cookies. */ showCookieExceptions() { var params = { blockVisible: true, sessionVisible: true, allowVisible: true, prefilledHost: "", permissionType: "cookie", }; gSubDialog.open( "chrome://browser/content/preferences/dialogs/permissions.xhtml", undefined, params ); }, /** * Displays per-site preferences for HTTPS-Only Mode exceptions. */ showHttpsOnlyModeExceptions() { var params = { blockVisible: false, sessionVisible: true, allowVisible: false, prefilledHost: "", permissionType: "https-only-load-insecure", }; gSubDialog.open( "chrome://browser/content/preferences/dialogs/permissions.xhtml", undefined, params ); }, showDoHExceptions() { gSubDialog.open( "chrome://browser/content/preferences/dialogs/dohExceptions.xhtml", undefined ); }, showSiteDataSettings() { gSubDialog.open( "chrome://browser/content/preferences/dialogs/siteDataSettings.xhtml" ); }, toggleSiteData(shouldShow) { let clearButton = document.getElementById("clearSiteDataButton"); let settingsButton = document.getElementById("siteDataSettings"); clearButton.disabled = !shouldShow; settingsButton.disabled = !shouldShow; }, showSiteDataLoading() { let totalSiteDataSizeLabel = document.getElementById("totalSiteDataSize"); document.l10n.setAttributes( totalSiteDataSizeLabel, "sitedata-total-size-calculating" ); }, updateTotalDataSizeLabel(siteDataUsage) { SiteDataManager.getCacheSize().then(function (cacheUsage) { let totalSiteDataSizeLabel = document.getElementById("totalSiteDataSize"); let totalUsage = siteDataUsage + cacheUsage; let [value, unit] = DownloadUtils.convertByteUnits(totalUsage); document.l10n.setAttributes( totalSiteDataSizeLabel, "sitedata-total-size", { value, unit, } ); }); }, clearSiteData() { gSubDialog.open( "chrome://browser/content/preferences/dialogs/clearSiteData.xhtml" ); }, /** * Initializes the cookie banner handling subgroup on the privacy pane. * * This UI is shown if the "cookiebanners.ui.desktop.enabled" pref is true. * * The cookie banner handling checkbox reflects the cookie banner feature * state. It is enabled when the service enabled via the * cookiebanners.service.mode pref. If detection-only mode is enabled the * checkbox is unchecked, since in this mode no banners are handled. It is * only used for detection for banners which means we may prompt the user to * enable the feature via other UI surfaces such as the onboarding doorhanger. * * If the user checks the checkbox, the pref value is set to * nsICookieBannerService.MODE_REJECT_OR_ACCEPT. * * If the user unchecks the checkbox, the mode pref value is set to * nsICookieBannerService.MODE_DISABLED. * * Advanced users can choose other int-valued modes via about:config. */ initCookieBannerHandling() { setSyncFromPrefListener("handleCookieBanners", () => this.readCookieBannerMode() ); setSyncToPrefListener("handleCookieBanners", () => this.writeCookieBannerMode() ); let preference = Preferences.get("cookiebanners.ui.desktop.enabled"); preference.on("change", () => this.updateCookieBannerHandlingVisibility()); this.updateCookieBannerHandlingVisibility(); }, /** * Reads the cookiebanners.service.mode and detectOnly preference value and * updates the cookie banner handling checkbox accordingly. */ readCookieBannerMode() { if (Preferences.get("cookiebanners.service.detectOnly").value) { return false; } return ( Preferences.get("cookiebanners.service.mode").value != Ci.nsICookieBannerService.MODE_DISABLED ); }, /** * Translates user clicks on the cookie banner handling checkbox to the * corresponding integer-valued cookie banner mode preference. */ writeCookieBannerMode() { let checkbox = document.getElementById("handleCookieBanners"); let mode; if (checkbox.checked) { mode = Ci.nsICookieBannerService.MODE_REJECT; // Also unset the detect-only mode pref, just in case the user enabled // the feature via about:preferences, not the onboarding doorhanger. Services.prefs.setBoolPref("cookiebanners.service.detectOnly", false); } else { mode = Ci.nsICookieBannerService.MODE_DISABLED; } /** * There is a second service.mode pref for private browsing, * but for now we want it always be the same as service.mode * more info: https://bugzilla.mozilla.org/show_bug.cgi?id=1817201 */ Services.prefs.setIntPref( "cookiebanners.service.mode.privateBrowsing", mode ); return mode; }, /** * Shows or hides the cookie banner handling section based on the value of * the "cookiebanners.ui.desktop.enabled" pref. */ updateCookieBannerHandlingVisibility() { let groupbox = document.getElementById("cookieBannerHandlingGroup"); let isEnabled = Preferences.get("cookiebanners.ui.desktop.enabled").value; // Because the top-level pane showing code unsets the hidden attribute, we // manually hide the section when cookie banner handling is preffed off. if (isEnabled) { groupbox.removeAttribute("style"); } else { groupbox.setAttribute("style", "display: none !important"); } }, // ADDRESS BAR /** * Initializes the address bar section. */ _initAddressBar() { // Update the Firefox Suggest section when its Nimbus config changes. let onNimbus = () => this._updateFirefoxSuggestSection(); NimbusFeatures.urlbar.onUpdate(onNimbus); window.addEventListener("unload", () => { NimbusFeatures.urlbar.offUpdate(onNimbus); }); // The Firefox Suggest info box potentially needs updating when any of the // toggles change. let infoBoxPrefs = [ "browser.urlbar.suggest.quicksuggest.nonsponsored", "browser.urlbar.suggest.quicksuggest.sponsored", "browser.urlbar.quicksuggest.dataCollection.enabled", ]; for (let pref of infoBoxPrefs) { Preferences.get(pref).on("change", () => this._updateFirefoxSuggestInfoBox() ); } this._updateFirefoxSuggestSection(true); this._initQuickActionsSection(); }, /** * Updates the Firefox Suggest section (in the address bar section) depending * on whether the user is enrolled in a Firefox Suggest rollout. * * @param {boolean} [onInit] * Pass true when calling this when initializing the pane. */ _updateFirefoxSuggestSection(onInit = false) { // Show the best match checkbox container as appropriate. document.getElementById("firefoxSuggestBestMatchContainer").hidden = !UrlbarPrefs.get("bestMatchEnabled"); let container = document.getElementById("firefoxSuggestContainer"); if (UrlbarPrefs.get("quickSuggestEnabled")) { // Update the l10n IDs of text elements. let l10nIdByElementId = { locationBarGroupHeader: "addressbar-header-firefox-suggest", locationBarSuggestionLabel: "addressbar-suggest-firefox-suggest", }; for (let [elementId, l10nId] of Object.entries(l10nIdByElementId)) { let element = document.getElementById(elementId); element.dataset.l10nIdOriginal ??= element.dataset.l10nId; element.dataset.l10nId = l10nId; } // Add the extraMargin class to the engine-prefs link. document .getElementById("openSearchEnginePreferences") .classList.add("extraMargin"); // Show the container. this._updateFirefoxSuggestInfoBox(); this._updateDismissedSuggestionsStatus(); Preferences.get(PREF_URLBAR_QUICKSUGGEST_BLOCKLIST).on("change", () => this._updateDismissedSuggestionsStatus() ); Preferences.get(PREF_URLBAR_WEATHER_USER_ENABLED).on("change", () => this._updateDismissedSuggestionsStatus() ); setEventListener("restoreDismissedSuggestions", "command", () => this.restoreDismissedSuggestions() ); container.removeAttribute("hidden"); } else if (!onInit) { // Firefox Suggest is not enabled. This is the default, so to avoid // accidentally messing anything up, only modify the doc if we're being // called due to a change in the rollout-enabled status (!onInit). container.setAttribute("hidden", "true"); let elementIds = ["locationBarGroupHeader", "locationBarSuggestionLabel"]; for (let id of elementIds) { let element = document.getElementById(id); element.dataset.l10nId = element.dataset.l10nIdOriginal; delete element.dataset.l10nIdOriginal; document.l10n.translateElements([element]); } document .getElementById("openSearchEnginePreferences") .classList.remove("extraMargin"); } }, /** * Updates the Firefox Suggest info box (in the address bar section) depending * on the states of the Firefox Suggest toggles. */ _updateFirefoxSuggestInfoBox() { let nonsponsored = Preferences.get( "browser.urlbar.suggest.quicksuggest.nonsponsored" ).value; let sponsored = Preferences.get( "browser.urlbar.suggest.quicksuggest.sponsored" ).value; let dataCollection = Preferences.get( "browser.urlbar.quicksuggest.dataCollection.enabled" ).value; // Get the l10n ID of the appropriate text based on the values of the three // prefs. let l10nId; if (nonsponsored && sponsored && dataCollection) { l10nId = "addressbar-firefox-suggest-info-all"; } else if (nonsponsored && sponsored && !dataCollection) { l10nId = "addressbar-firefox-suggest-info-nonsponsored-sponsored"; } else if (nonsponsored && !sponsored && dataCollection) { l10nId = "addressbar-firefox-suggest-info-nonsponsored-data"; } else if (nonsponsored && !sponsored && !dataCollection) { l10nId = "addressbar-firefox-suggest-info-nonsponsored"; } else if (!nonsponsored && sponsored && dataCollection) { l10nId = "addressbar-firefox-suggest-info-sponsored-data"; } else if (!nonsponsored && sponsored && !dataCollection) { l10nId = "addressbar-firefox-suggest-info-sponsored"; } else if (!nonsponsored && !sponsored && dataCollection) { l10nId = "addressbar-firefox-suggest-info-data"; } let instance = (this._firefoxSuggestInfoBoxInstance = {}); let infoBox = document.getElementById("firefoxSuggestInfoBox"); if (!l10nId) { infoBox.hidden = true; } else { let infoText = document.getElementById("firefoxSuggestInfoText"); infoText.dataset.l10nId = l10nId; // If the info box is currently hidden and we unhide it immediately, it // will show its old text until the new text is asyncly fetched and shown. // That's ugly, so wait for the fetch to finish before unhiding it. document.l10n.translateElements([infoText]).then(() => { if (instance == this._firefoxSuggestInfoBoxInstance) { infoBox.hidden = false; } }); } }, /** * Enables/disables the "Restore" button for dismissed Firefox Suggest * suggestions. */ _updateDismissedSuggestionsStatus() { document.getElementById("restoreDismissedSuggestions").disabled = !Services.prefs.prefHasUserValue(PREF_URLBAR_QUICKSUGGEST_BLOCKLIST) && !( Services.prefs.prefHasUserValue(PREF_URLBAR_WEATHER_USER_ENABLED) && !Services.prefs.getBoolPref(PREF_URLBAR_WEATHER_USER_ENABLED) ); }, /** * Restores Firefox Suggest suggestions dismissed by the user. */ restoreDismissedSuggestions() { Services.prefs.clearUserPref(PREF_URLBAR_QUICKSUGGEST_BLOCKLIST); Services.prefs.clearUserPref(PREF_URLBAR_WEATHER_USER_ENABLED); }, // GEOLOCATION /** * Displays the location exceptions dialog where specific site location * preferences can be set. */ showLocationExceptions() { let params = { permissionType: "geo" }; gSubDialog.open( "chrome://browser/content/preferences/dialogs/sitePermissions.xhtml", { features: "resizable=yes" }, params ); }, // XR /** * Displays the XR exceptions dialog where specific site XR * preferences can be set. */ showXRExceptions() { let params = { permissionType: "xr" }; gSubDialog.open( "chrome://browser/content/preferences/dialogs/sitePermissions.xhtml", { features: "resizable=yes" }, params ); }, // CAMERA /** * Displays the camera exceptions dialog where specific site camera * preferences can be set. */ showCameraExceptions() { let params = { permissionType: "camera" }; gSubDialog.open( "chrome://browser/content/preferences/dialogs/sitePermissions.xhtml", { features: "resizable=yes" }, params ); }, // MICROPHONE /** * Displays the microphone exceptions dialog where specific site microphone * preferences can be set. */ showMicrophoneExceptions() { let params = { permissionType: "microphone" }; gSubDialog.open( "chrome://browser/content/preferences/dialogs/sitePermissions.xhtml", { features: "resizable=yes" }, params ); }, // SPEAKER /** * Displays the speaker exceptions dialog where specific site speaker * preferences can be set. */ showSpeakerExceptions() { let params = { permissionType: "speaker" }; gSubDialog.open( "chrome://browser/content/preferences/dialogs/sitePermissions.xhtml", { features: "resizable=yes" }, params ); }, // NOTIFICATIONS /** * Displays the notifications exceptions dialog where specific site notification * preferences can be set. */ showNotificationExceptions() { let params = { permissionType: "desktop-notification" }; gSubDialog.open( "chrome://browser/content/preferences/dialogs/sitePermissions.xhtml", { features: "resizable=yes" }, params ); }, // MEDIA showAutoplayMediaExceptions() { var params = { permissionType: "autoplay-media" }; gSubDialog.open( "chrome://browser/content/preferences/dialogs/sitePermissions.xhtml", { features: "resizable=yes" }, params ); }, // POP-UPS /** * Displays the popup exceptions dialog where specific site popup preferences * can be set. */ showPopupExceptions() { var params = { blockVisible: false, sessionVisible: false, allowVisible: true, prefilledHost: "", permissionType: "popup", }; gSubDialog.open( "chrome://browser/content/preferences/dialogs/permissions.xhtml", { features: "resizable=yes" }, params ); }, // UTILITY FUNCTIONS /** * Utility function to enable/disable the button specified by aButtonID based * on the value of the Boolean preference specified by aPreferenceID. */ updateButtons(aButtonID, aPreferenceID) { var button = document.getElementById(aButtonID); var preference = Preferences.get(aPreferenceID); button.disabled = !preference.value || preference.locked; return undefined; }, // BEGIN UI CODE /* * Preferences: * * dom.disable_open_during_load * - true if popups are blocked by default, false otherwise */ // POP-UPS /** * Displays a dialog in which the user can view and modify the list of sites * where passwords are never saved. */ showPasswordExceptions() { var params = { blockVisible: true, sessionVisible: false, allowVisible: false, hideStatusColumn: true, prefilledHost: "", permissionType: "login-saving", }; gSubDialog.open( "chrome://browser/content/preferences/dialogs/permissions.xhtml", undefined, params ); }, /** * Initializes master password UI: the "use master password" checkbox, selects * the master password button to show, and enables/disables it as necessary. * The master password is controlled by various bits of NSS functionality, so * the UI for it can't be controlled by the normal preference bindings. */ _initMasterPasswordUI() { var noMP = !LoginHelper.isPrimaryPasswordSet(); var button = document.getElementById("changeMasterPassword"); button.disabled = noMP; var checkbox = document.getElementById("useMasterPassword"); checkbox.checked = !noMP; checkbox.disabled = (noMP && !Services.policies.isAllowed("createMasterPassword")) || (!noMP && !Services.policies.isAllowed("removeMasterPassword")); }, /** * Enables/disables the master password button depending on the state of the * "use master password" checkbox, and prompts for master password removal if * one is set. */ async updateMasterPasswordButton() { var checkbox = document.getElementById("useMasterPassword"); var button = document.getElementById("changeMasterPassword"); button.disabled = !checkbox.checked; // unchecking the checkbox should try to immediately remove the master // password, because it's impossible to non-destructively remove the master // password used to encrypt all the passwords without providing it (by // design), and it would be extremely odd to pop up that dialog when the // user closes the prefwindow and saves his settings if (!checkbox.checked) { await this._removeMasterPassword(); } else { await this.changeMasterPassword(); } this._initMasterPasswordUI(); }, /** * Displays the "remove master password" dialog to allow the user to remove * the current master password. When the dialog is dismissed, master password * UI is automatically updated. */ async _removeMasterPassword() { var secmodDB = Cc["@mozilla.org/security/pkcs11moduledb;1"].getService( Ci.nsIPKCS11ModuleDB ); if (secmodDB.isFIPSEnabled) { let title = document.getElementById("fips-title").textContent; let desc = document.getElementById("fips-desc").textContent; Services.prompt.alert(window, title, desc); this._initMasterPasswordUI(); } else { gSubDialog.open("chrome://mozapps/content/preferences/removemp.xhtml", { closingCallback: this._initMasterPasswordUI.bind(this), }); } }, /** * Displays a dialog in which the primary password may be changed. */ async changeMasterPassword() { // Require OS authentication before the user can set a Primary Password. // OS reauthenticate functionality is not available on Linux yet (bug 1527745) if ( !LoginHelper.isPrimaryPasswordSet() && OS_AUTH_ENABLED && OSKeyStore.canReauth() ) { // Uses primary-password-os-auth-dialog-message-win and // primary-password-os-auth-dialog-message-macosx via concatenation: let messageId = "primary-password-os-auth-dialog-message-" + AppConstants.platform; let [messageText, captionText] = await document.l10n.formatMessages([ { id: messageId, }, { id: "master-password-os-auth-dialog-caption", }, ]); let win = Services.wm.getMostRecentBrowserWindow(); let loggedIn = await OSKeyStore.ensureLoggedIn( messageText.value, captionText.value, win, false ); if (!loggedIn.authenticated) { return; } } gSubDialog.open("chrome://mozapps/content/preferences/changemp.xhtml", { features: "resizable=no", closingCallback: this._initMasterPasswordUI.bind(this), }); }, /** * Set up the initial state for the password generation UI. * It will be hidden unless the .available pref is true */ _initPasswordGenerationUI() { // we don't watch the .available pref for runtime changes let prefValue = Services.prefs.getBoolPref( PREF_PASSWORD_GENERATION_AVAILABLE, false ); document.getElementById("generatePasswordsBox").hidden = !prefValue; }, toggleRelayIntegration() { const checkbox = document.getElementById("relayIntegration"); if (checkbox.checked) { FirefoxRelay.markAsAvailable(); FirefoxRelayTelemetry.recordRelayPrefEvent("enabled"); } else { FirefoxRelay.markAsDisabled(); FirefoxRelayTelemetry.recordRelayPrefEvent("disabled"); } }, _updateRelayIntegrationUI() { document.getElementById("relayIntegrationBox").hidden = !FirefoxRelay.isAvailable; document.getElementById("relayIntegration").checked = FirefoxRelay.isAvailable && !FirefoxRelay.isDisabled; }, _initRelayIntegrationUI() { document .getElementById("relayIntegrationLearnMoreLink") .setAttribute("href", FirefoxRelay.learnMoreUrl); setEventListener( "relayIntegration", "command", gPrivacyPane.toggleRelayIntegration.bind(gPrivacyPane) ); Preferences.get("signon.firefoxRelay.feature").on( "change", gPrivacyPane._updateRelayIntegrationUI.bind(gPrivacyPane) ); this._updateRelayIntegrationUI(); }, /** * Shows the sites where the user has saved passwords and the associated login * information. */ showPasswords() { let loginManager = window.windowGlobalChild.getActor("LoginManager"); loginManager.sendAsyncMessage("PasswordManager:OpenPreferences", { entryPoint: "preferences", }); }, /** * Enables/disables dependent controls related to password saving * When password saving is not enabled, we need to also disable the password generation checkbox * The Exceptions button is used to configure sites where passwords are never saved. */ readSavePasswords() { var prefValue = Preferences.get("signon.rememberSignons").value; document.getElementById("passwordExceptions").disabled = !prefValue; document.getElementById("generatePasswords").disabled = !prefValue; document.getElementById("passwordAutofillCheckbox").disabled = !prefValue; document.getElementById("relayIntegration").disabled = !prefValue || Services.prefs.prefIsLocked("signon.firefoxRelay.feature"); // don't override pref value in UI return undefined; }, /** * Initalizes pref listeners for the password manager. * * This ensures that the user is always notified if an extension is controlling the password manager. */ initListenersForExtensionControllingPasswordManager() { this._passwordManagerCheckbox = document.getElementById("savePasswords"); this._disableExtensionButton = document.getElementById( "disablePasswordManagerExtension" ); this._disableExtensionButton.addEventListener( "command", makeDisableControllingExtension( PREF_SETTING_TYPE, PASSWORD_MANAGER_PREF_ID ) ); initListenersForPrefChange( PREF_SETTING_TYPE, PASSWORD_MANAGER_PREF_ID, this._passwordManagerCheckbox ); }, /** * Enables/disables the add-ons Exceptions button depending on whether * or not add-on installation warnings are displayed. */ readWarnAddonInstall() { var warn = Preferences.get("xpinstall.whitelist.required"); var exceptions = document.getElementById("addonExceptions"); exceptions.disabled = !warn.value || warn.locked; // don't override the preference value return undefined; }, _initSafeBrowsing() { let enableSafeBrowsing = document.getElementById("enableSafeBrowsing"); let blockDownloads = document.getElementById("blockDownloads"); let blockUncommonUnwanted = document.getElementById( "blockUncommonUnwanted" ); let safeBrowsingPhishingPref = Preferences.get( "browser.safebrowsing.phishing.enabled" ); let safeBrowsingMalwarePref = Preferences.get( "browser.safebrowsing.malware.enabled" ); let blockDownloadsPref = Preferences.get( "browser.safebrowsing.downloads.enabled" ); let malwareTable = Preferences.get("urlclassifier.malwareTable"); let blockUnwantedPref = Preferences.get( "browser.safebrowsing.downloads.remote.block_potentially_unwanted" ); let blockUncommonPref = Preferences.get( "browser.safebrowsing.downloads.remote.block_uncommon" ); enableSafeBrowsing.addEventListener("command", function () { safeBrowsingPhishingPref.value = enableSafeBrowsing.checked; safeBrowsingMalwarePref.value = enableSafeBrowsing.checked; blockDownloads.disabled = !enableSafeBrowsing.checked || blockDownloadsPref.locked; blockUncommonUnwanted.disabled = !blockDownloads.checked || !enableSafeBrowsing.checked || blockUnwantedPref.locked || blockUncommonPref.locked; }); blockDownloads.addEventListener("command", function () { blockDownloadsPref.value = blockDownloads.checked; blockUncommonUnwanted.disabled = !blockDownloads.checked || blockUnwantedPref.locked || blockUncommonPref.locked; }); blockUncommonUnwanted.addEventListener("command", function () { blockUnwantedPref.value = blockUncommonUnwanted.checked; blockUncommonPref.value = blockUncommonUnwanted.checked; let malware = malwareTable.value .split(",") .filter( x => x !== "goog-unwanted-proto" && x !== "goog-unwanted-shavar" && x !== "moztest-unwanted-simple" ); if (blockUncommonUnwanted.checked) { if (malware.includes("goog-malware-shavar")) { malware.push("goog-unwanted-shavar"); } else { malware.push("goog-unwanted-proto"); } malware.push("moztest-unwanted-simple"); } // sort alphabetically to keep the pref consistent malware.sort(); malwareTable.value = malware.join(","); // Force an update after changing the malware table. listManager.forceUpdates(malwareTable.value); }); // set initial values enableSafeBrowsing.checked = safeBrowsingPhishingPref.value && safeBrowsingMalwarePref.value; if (!enableSafeBrowsing.checked) { blockDownloads.setAttribute("disabled", "true"); blockUncommonUnwanted.setAttribute("disabled", "true"); } blockDownloads.checked = blockDownloadsPref.value; if (!blockDownloadsPref.value) { blockUncommonUnwanted.setAttribute("disabled", "true"); } blockUncommonUnwanted.checked = blockUnwantedPref.value && blockUncommonPref.value; if (safeBrowsingPhishingPref.locked || safeBrowsingMalwarePref.locked) { enableSafeBrowsing.disabled = true; } if (blockDownloadsPref.locked) { blockDownloads.disabled = true; } if (blockUnwantedPref.locked || blockUncommonPref.locked) { blockUncommonUnwanted.disabled = true; } }, /** * Displays the exceptions lists for add-on installation warnings. */ showAddonExceptions() { var params = this._addonParams; gSubDialog.open( "chrome://browser/content/preferences/dialogs/permissions.xhtml", undefined, params ); }, /** * Parameters for the add-on install permissions dialog. */ _addonParams: { blockVisible: false, sessionVisible: false, allowVisible: true, prefilledHost: "", permissionType: "install", }, /** * readEnableOCSP is used by the preferences UI to determine whether or not * the checkbox for OCSP fetching should be checked (it returns true if it * should be checked and false otherwise). The about:config preference * "security.OCSP.enabled" is an integer rather than a boolean, so it can't be * directly mapped from {true,false} to {checked,unchecked}. The possible * values for "security.OCSP.enabled" are: * 0: fetching is disabled * 1: fetch for all certificates * 2: fetch only for EV certificates * Hence, if "security.OCSP.enabled" is non-zero, the checkbox should be * checked. Otherwise, it should be unchecked. */ readEnableOCSP() { var preference = Preferences.get("security.OCSP.enabled"); // This is the case if the preference is the default value. if (preference.value === undefined) { return true; } return preference.value != 0; }, /** * writeEnableOCSP is used by the preferences UI to map the checked/unchecked * state of the OCSP fetching checkbox to the value that the preference * "security.OCSP.enabled" should be set to (it returns that value). See the * readEnableOCSP documentation for more background. We unfortunately don't * have enough information to map from {true,false} to all possible values for * "security.OCSP.enabled", but a reasonable alternative is to map from * {true,false} to {,0}. That is, if the box is checked, * "security.OCSP.enabled" will be set to whatever default it should be, given * the platform and channel. If the box is unchecked, the preference will be * set to 0. Obviously this won't work if the default is 0, so we will have to * revisit this if we ever set it to 0. */ writeEnableOCSP() { var checkbox = document.getElementById("enableOCSP"); var defaults = Services.prefs.getDefaultBranch(null); var defaultValue = defaults.getIntPref("security.OCSP.enabled"); return checkbox.checked ? defaultValue : 0; }, /** * Displays the user's certificates and associated options. */ showCertificates() { gSubDialog.open("chrome://pippki/content/certManager.xhtml"); }, /** * Displays a dialog from which the user can manage his security devices. */ showSecurityDevices() { gSubDialog.open("chrome://pippki/content/device_manager.xhtml"); }, /** * Displays the learn more health report page when a user opts out of data collection. */ showDataDeletion() { let url = Services.urlFormatter.formatURLPref("app.support.baseURL") + "telemetry-clientid"; window.open(url, "_blank"); }, initDataCollection() { if ( !AppConstants.MOZ_DATA_REPORTING && !NimbusFeatures.majorRelease2022.getVariable( "feltPrivacyShowPreferencesSection" ) ) { // Nothing to control in the data collection section, remove it. document.getElementById("dataCollectionCategory").remove(); document.getElementById("dataCollectionGroup").remove(); return; } this._setupLearnMoreLink( "toolkit.datacollection.infoURL", "dataCollectionPrivacyNotice" ); this.initPrivacySegmentation(); }, initSubmitCrashes() { this._setupLearnMoreLink( "toolkit.crashreporter.infoURL", "crashReporterLearnMore" ); setEventListener("crashReporterLabel", "click", function (event) { if (event.target.localName == "a") { return; } const checkboxId = event.target.getAttribute("for"); document.getElementById(checkboxId).click(); }); }, initPrivacySegmentation() { // Section visibility let section = document.getElementById("privacySegmentationSection"); let updatePrivacySegmentationSectionVisibilityState = () => { section.hidden = !NimbusFeatures.majorRelease2022.getVariable( "feltPrivacyShowPreferencesSection" ); }; NimbusFeatures.majorRelease2022.onUpdate( updatePrivacySegmentationSectionVisibilityState ); window.addEventListener("unload", () => { NimbusFeatures.majorRelease2022.offUpdate( updatePrivacySegmentationSectionVisibilityState ); }); updatePrivacySegmentationSectionVisibilityState(); }, /** * Set up or hide the Learn More links for various data collection options */ _setupLearnMoreLink(pref, element) { // set up the Learn More link with the correct URL let url = Services.urlFormatter.formatURLPref(pref); let el = document.getElementById(element); if (url) { el.setAttribute("href", url); } else { el.hidden = true; } }, /** * Initialize the health report service reference and checkbox. */ initSubmitHealthReport() { this._setupLearnMoreLink( "datareporting.healthreport.infoURL", "FHRLearnMore" ); let checkbox = document.getElementById("submitHealthReportBox"); // Telemetry is only sending data if MOZ_TELEMETRY_REPORTING is defined. // We still want to display the preferences panel if that's not the case, but // we want it to be disabled and unchecked. if ( Services.prefs.prefIsLocked(PREF_UPLOAD_ENABLED) || !AppConstants.MOZ_TELEMETRY_REPORTING ) { checkbox.setAttribute("disabled", "true"); return; } checkbox.checked = Services.prefs.getBoolPref(PREF_UPLOAD_ENABLED) && AppConstants.MOZ_TELEMETRY_REPORTING; }, /** * Update the health report preference with state from checkbox. */ updateSubmitHealthReport() { let checkbox = document.getElementById("submitHealthReportBox"); let telemetryContainer = document.getElementById("telemetry-container"); Services.prefs.setBoolPref(PREF_UPLOAD_ENABLED, checkbox.checked); telemetryContainer.hidden = checkbox.checked; }, /** * Initialize the opt-out-study preference checkbox into about:preferences and * handles events coming from the UI for it. */ initOptOutStudyCheckbox(doc) { // The checkbox should be disabled if any of the below are true. This // prevents the user from changing the value in the box. // // * the policy forbids shield // * Normandy is disabled // // The checkbox should match the value of the preference only if all of // these are true. Otherwise, the checkbox should remain unchecked. This // is because in these situations, Shield studies are always disabled, and // so showing a checkbox would be confusing. // // * the policy allows Shield // * Normandy is enabled const allowedByPolicy = Services.policies.isAllowed("Shield"); const checkbox = document.getElementById("optOutStudiesEnabled"); if ( allowedByPolicy && Services.prefs.getBoolPref(PREF_NORMANDY_ENABLED, false) ) { if (Services.prefs.getBoolPref(PREF_OPT_OUT_STUDIES_ENABLED, false)) { checkbox.setAttribute("checked", "true"); } else { checkbox.removeAttribute("checked"); } checkbox.setAttribute("preference", PREF_OPT_OUT_STUDIES_ENABLED); checkbox.removeAttribute("disabled"); } else { checkbox.removeAttribute("preference"); checkbox.removeAttribute("checked"); checkbox.setAttribute("disabled", "true"); } }, initAddonRecommendationsCheckbox() { // Setup the checkbox. dataCollectionCheckboxHandler({ checkbox: document.getElementById("addonRecommendationEnabled"), pref: PREF_ADDON_RECOMMENDATIONS_ENABLED, }); }, observe(aSubject, aTopic, aData) { switch (aTopic) { case "sitedatamanager:updating-sites": // While updating, we want to disable this section and display loading message until updated this.toggleSiteData(false); this.showSiteDataLoading(); break; case "sitedatamanager:sites-updated": this.toggleSiteData(true); SiteDataManager.getTotalUsage().then( this.updateTotalDataSizeLabel.bind(this) ); break; case "network:trr-uri-changed": case "network:trr-mode-changed": case "network:trr-confirmation": gPrivacyPane.updateDoHStatus(); break; } }, };