/* 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/. */ "use strict"; ChromeUtils.import("resource://gre/modules/Services.jsm"); ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm"); ChromeUtils.defineModuleGetter( this, "AppConstants", "resource://gre/modules/AppConstants.jsm" ); ChromeUtils.defineModuleGetter( this, "AddonStudies", "resource://normandy/lib/AddonStudies.jsm" ); ChromeUtils.defineModuleGetter( this, "CleanupManager", "resource://normandy/lib/CleanupManager.jsm" ); var EXPORTED_SYMBOLS = ["ShieldPreferences"]; const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"; const NS_PREFBRANCH_PREFCHANGE_TOPIC_ID = "nsPref:changed"; // from modules/libpref/nsIPrefBranch.idl const FHR_UPLOAD_ENABLED_PREF = "datareporting.healthreport.uploadEnabled"; const OPT_OUT_STUDIES_ENABLED_PREF = "app.shield.optoutstudies.enabled"; /** * Handles Shield-specific preferences, including their UI. */ var ShieldPreferences = { init() { // If the FHR pref was disabled since our last run, disable opt-out as well. if (!Services.prefs.getBoolPref(FHR_UPLOAD_ENABLED_PREF)) { Services.prefs.setBoolPref(OPT_OUT_STUDIES_ENABLED_PREF, false); } // Watch for changes to the FHR pref Services.prefs.addObserver(FHR_UPLOAD_ENABLED_PREF, this); CleanupManager.addCleanupHandler(() => { Services.prefs.removeObserver(FHR_UPLOAD_ENABLED_PREF, this); }); // Watch for changes to the Opt-out pref Services.prefs.addObserver(OPT_OUT_STUDIES_ENABLED_PREF, this); CleanupManager.addCleanupHandler(() => { Services.prefs.removeObserver(OPT_OUT_STUDIES_ENABLED_PREF, this); }); // Disabled outside of en-* locales temporarily (bug 1377192). // Disabled when MOZ_DATA_REPORTING is false since the FHR UI is also hidden // when data reporting is false. if (AppConstants.MOZ_DATA_REPORTING && Services.locale.getAppLocaleAsLangTag().startsWith("en")) { Services.obs.addObserver(this, "privacy-pane-loaded"); CleanupManager.addCleanupHandler(() => { Services.obs.removeObserver(this, "privacy-pane-loaded"); }); } }, observe(subject, topic, data) { switch (topic) { // Add the opt-out-study checkbox to the Privacy preferences when it is shown. case "privacy-pane-loaded": this.injectOptOutStudyCheckbox(subject.document); break; case NS_PREFBRANCH_PREFCHANGE_TOPIC_ID: this.observePrefChange(data); break; } }, async observePrefChange(prefName) { let prefValue; switch (prefName) { // If the FHR pref changes, set the opt-out-study pref to the value it is changing to. case FHR_UPLOAD_ENABLED_PREF: { prefValue = Services.prefs.getBoolPref(FHR_UPLOAD_ENABLED_PREF); Services.prefs.setBoolPref(OPT_OUT_STUDIES_ENABLED_PREF, prefValue); break; } // If the opt-out pref changes to be false, disable all current studies. case OPT_OUT_STUDIES_ENABLED_PREF: { prefValue = Services.prefs.getBoolPref(OPT_OUT_STUDIES_ENABLED_PREF); if (!prefValue) { for (const study of await AddonStudies.getAll()) { if (study.active) { await AddonStudies.stop(study.recipeId, "general-opt-out"); } } } break; } } }, /** * Injects the opt-out-study preference checkbox into about:preferences and * handles events coming from the UI for it. */ injectOptOutStudyCheckbox(doc) { const container = doc.createElementNS(XUL_NS, "vbox"); container.classList.add("indent"); const hContainer = doc.createElementNS(XUL_NS, "hbox"); hContainer.setAttribute("align", "center"); container.appendChild(hContainer); const checkbox = doc.createElementNS(XUL_NS, "checkbox"); checkbox.setAttribute("id", "optOutStudiesEnabled"); checkbox.setAttribute("class", "tail-with-learn-more"); checkbox.setAttribute("label", "Allow Firefox to install and run studies"); let allowedByPolicy = Services.policies.isAllowed("Shield"); if (allowedByPolicy) { // If Shield is not allowed by policy, don't tie this checkbox to the preference, // so that the checkbox remains unchecked. // Otherwise, it would be grayed out but still checked, which looks confusing // because it appears it's enabled with no way to disable it. checkbox.setAttribute("preference", OPT_OUT_STUDIES_ENABLED_PREF); } hContainer.appendChild(checkbox); const viewStudies = doc.createElementNS(XUL_NS, "label"); viewStudies.setAttribute("id", "viewShieldStudies"); viewStudies.setAttribute("href", "about:studies"); viewStudies.setAttribute("useoriginprincipal", true); viewStudies.textContent = "View Firefox Studies"; viewStudies.classList.add("learnMore", "text-link"); hContainer.appendChild(viewStudies); // Preference instances for prefs that we need to monitor while the page is open. doc.defaultView.Preferences.add({ id: OPT_OUT_STUDIES_ENABLED_PREF, type: "bool" }); // Weirdly, FHR doesn't have a Preference instance on the page, so we create it. const fhrPref = doc.defaultView.Preferences.add({ id: FHR_UPLOAD_ENABLED_PREF, type: "bool" }); function onChangeFHRPref() { let isDisabled = Services.prefs.prefIsLocked(FHR_UPLOAD_ENABLED_PREF) || !AppConstants.MOZ_TELEMETRY_REPORTING || !Services.prefs.getBoolPref(FHR_UPLOAD_ENABLED_PREF) || !allowedByPolicy; // We can't use checkbox.disabled here because the XBL binding may not be present, // in which case setting the property won't work properly. if (isDisabled) { checkbox.setAttribute("disabled", "true"); } else { checkbox.removeAttribute("disabled"); } } fhrPref.on("change", onChangeFHRPref); onChangeFHRPref(); doc.defaultView.addEventListener("unload", () => fhrPref.off("change", onChangeFHRPref), { once: true }); // Actually inject the elements we've created. const parent = doc.getElementById("submitHealthReportBox").closest("description"); parent.appendChild(container); }, };