diff --git a/browser/components/BrowserGlue.jsm b/browser/components/BrowserGlue.jsm index decf226c3d47..da0f0ca21375 100644 --- a/browser/components/BrowserGlue.jsm +++ b/browser/components/BrowserGlue.jsm @@ -1237,7 +1237,9 @@ BrowserGlue.prototype = { "resource:///modules/themes/dark/" ); - Normandy.init(); + if (AppConstants.MOZ_NORMANDY) { + Normandy.init(); + } SaveToPocket.init(); Services.obs.notifyObservers(null, "browser-ui-startup-complete"); diff --git a/browser/components/enterprisepolicies/tests/browser/browser_policy_disable_shield.js b/browser/components/enterprisepolicies/tests/browser/browser_policy_disable_shield.js index d1d2bd62f41a..fc4944e0b01a 100644 --- a/browser/components/enterprisepolicies/tests/browser/browser_policy_disable_shield.js +++ b/browser/components/enterprisepolicies/tests/browser/browser_policy_disable_shield.js @@ -7,17 +7,41 @@ add_task(async function test_policy_disable_shield() { const { RecipeRunner } = ChromeUtils.import( "resource://normandy/lib/RecipeRunner.jsm" ); + const { BaseAction } = ChromeUtils.import( + "resource://normandy/actions/BaseAction.jsm" + ); + const { BaseStudyAction } = ChromeUtils.import( + "resource://normandy/actions/BaseStudyAction.jsm" + ); + + const baseAction = new BaseAction(); + const baseStudyAction = new BaseStudyAction(); await SpecialPowers.pushPrefEnv({ set: [ ["app.normandy.api_url", "https://localhost/selfsupport-dummy/"], - ["datareporting.healthreport.uploadEnabled", true], + ["app.shield.optoutstudies.enabled", true], ], }); ok(RecipeRunner, "RecipeRunner exists"); + RecipeRunner.checkPrefs(); - is(RecipeRunner.enabled, true, "RecipeRunner is enabled"); + ok(RecipeRunner.enabled, "RecipeRunner is enabled"); + + baseAction._preExecution(); + is( + baseAction.state, + BaseAction.STATE_PREPARING, + "Base action is not disabled" + ); + + baseStudyAction._preExecution(); + is( + baseStudyAction.state, + BaseAction.STATE_PREPARING, + "Base study action is not disabled" + ); await setupPolicyEngineWithJson({ policies: { @@ -26,5 +50,19 @@ add_task(async function test_policy_disable_shield() { }); RecipeRunner.checkPrefs(); - is(RecipeRunner.enabled, false, "RecipeRunner is disabled"); + ok(RecipeRunner.enabled, "RecipeRunner is still enabled"); + + baseAction._preExecution(); + is( + baseAction.state, + BaseAction.STATE_PREPARING, + "Base action is not disabled" + ); + + baseStudyAction._preExecution(); + is( + baseStudyAction.state, + BaseAction.STATE_DISABLED, + "Base study action is disabled" + ); }); diff --git a/browser/components/preferences/in-content/privacy.inc.xul b/browser/components/preferences/in-content/privacy.inc.xul index 40e5ca6a9a59..ae1eca3cd38c 100644 --- a/browser/components/preferences/in-content/privacy.inc.xul +++ b/browser/components/preferences/in-content/privacy.inc.xul @@ -828,17 +828,6 @@ class="learnMore" is="text-link" data-l10n-id="collection-health-report-link"/> - - - - #endif +#ifdef MOZ_NORMANDY + + + +#endif + #ifdef MOZ_CRASHREPORTER - allowedByPolicy && - Services.prefs.getBoolPref(PREF_NORMANDY_ENABLED, false), - isDisabled: () => !allowedByPolicy, - pref: PREF_OPT_OUT_STUDIES_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() { diff --git a/browser/moz.configure b/browser/moz.configure index 3057a5040c5f..a251050feb9b 100644 --- a/browser/moz.configure +++ b/browser/moz.configure @@ -9,6 +9,7 @@ imply_option('MOZ_SERVICES_HEALTHREPORT', True) imply_option('MOZ_SERVICES_SYNC', True) imply_option('MOZ_DEDICATED_PROFILES', True) imply_option('MOZ_BLOCK_PROFILE_DOWNGRADE', True) +imply_option('MOZ_NORMANDY', True) with only_when(target_is_linux & compile_environment): option(env='MOZ_NO_PIE_COMPAT', diff --git a/mobile/android/moz.configure b/mobile/android/moz.configure index 43a28f37b7fc..4a1c056a7468 100644 --- a/mobile/android/moz.configure +++ b/mobile/android/moz.configure @@ -121,7 +121,7 @@ set_define('FENNEC_NIGHTLY', depends_if('FENNEC_NIGHTLY')(lambda _: True)) def fennec_nightly(nightly): return bool(nightly) - +imply_option('MOZ_NORMANDY', False) imply_option('MOZ_SERVICES_HEALTHREPORT', True) imply_option('MOZ_ANDROID_HISTORY', True) imply_option('--enable-small-chunk-size', True) diff --git a/old-configure.in b/old-configure.in index cb6acabf4d5d..69fe6cb4157d 100644 --- a/old-configure.in +++ b/old-configure.in @@ -3085,7 +3085,7 @@ AC_SUBST(MOZ_INCLUDE_SOURCE_INFO) dnl If we have any service that uploads data (and requires data submission dnl policy alert), set MOZ_DATA_REPORTING. dnl We need SUBST for build system and DEFINE for xul preprocessor. -if test -n "$MOZ_TELEMETRY_REPORTING" || test -n "$MOZ_SERVICES_HEALTHREPORT" || test -n "$MOZ_CRASHREPORTER"; then +if test -n "$MOZ_TELEMETRY_REPORTING" || test -n "$MOZ_SERVICES_HEALTHREPORT" || test -n "$MOZ_CRASHREPORTER" || test -n "$MOZ_NORMANDY"; then MOZ_DATA_REPORTING=1 AC_DEFINE(MOZ_DATA_REPORTING) AC_SUBST(MOZ_DATA_REPORTING) diff --git a/python/mozbuild/mozbuild/mozinfo.py b/python/mozbuild/mozbuild/mozinfo.py index 90035638333e..fa3587e6fbe0 100755 --- a/python/mozbuild/mozbuild/mozinfo.py +++ b/python/mozbuild/mozbuild/mozinfo.py @@ -78,6 +78,7 @@ def build_dict(config, env=os.environ): d['devedition'] = substs.get('MOZ_DEV_EDITION') == '1' d['pgo'] = substs.get('MOZ_PGO') == '1' d['crashreporter'] = bool(substs.get('MOZ_CRASHREPORTER')) + d['normandy'] = substs.get('MOZ_NORMANDY') == '1' d['datareporting'] = bool(substs.get('MOZ_DATA_REPORTING')) d['healthreport'] = substs.get('MOZ_SERVICES_HEALTHREPORT') == '1' d['sync'] = substs.get('MOZ_SERVICES_SYNC') == '1' diff --git a/toolkit/components/normandy/Normandy.jsm b/toolkit/components/normandy/Normandy.jsm index 16bcd41be2fe..51c6e4a5e867 100644 --- a/toolkit/components/normandy/Normandy.jsm +++ b/toolkit/components/normandy/Normandy.jsm @@ -14,6 +14,7 @@ XPCOMUtils.defineLazyModuleGetters(this, { AddonStudies: "resource://normandy/lib/AddonStudies.jsm", CleanupManager: "resource://normandy/lib/CleanupManager.jsm", LogManager: "resource://normandy/lib/LogManager.jsm", + NormandyMigrations: "resource://normandy/NormandyMigrations.jsm", PreferenceExperiments: "resource://normandy/lib/PreferenceExperiments.jsm", PreferenceRollouts: "resource://normandy/lib/PreferenceRollouts.jsm", RecipeRunner: "resource://normandy/lib/RecipeRunner.jsm", @@ -28,7 +29,6 @@ const BOOTSTRAP_LOGGER_NAME = "app.normandy.bootstrap"; const SHIELD_INIT_NOTIFICATION = "shield-init-complete"; const PREF_PREFIX = "app.normandy"; -const LEGACY_PREF_PREFIX = "extensions.shield-recipe-client"; const STARTUP_EXPERIMENT_PREFS_BRANCH = `${PREF_PREFIX}.startupExperimentPrefs.`; const STARTUP_ROLLOUT_PREFS_BRANCH = `${PREF_PREFIX}.startupRolloutPrefs.`; const PREF_LOGGING_LEVEL = `${PREF_PREFIX}.logging.level`; @@ -44,7 +44,7 @@ var Normandy = { init() { // Initialization that needs to happen before the first paint on startup. - this.migrateShieldPrefs(); + NormandyMigrations.applyAll(); this.rolloutPrefsChanged = this.applyStartupPrefs( STARTUP_ROLLOUT_PREFS_BRANCH ); @@ -129,57 +129,6 @@ var Normandy = { } }, - migrateShieldPrefs() { - const legacyBranch = Services.prefs.getBranch(LEGACY_PREF_PREFIX + "."); - const newBranch = Services.prefs.getBranch(PREF_PREFIX + "."); - - for (const prefName of legacyBranch.getChildList("")) { - const legacyPrefType = legacyBranch.getPrefType(prefName); - const newPrefType = newBranch.getPrefType(prefName); - - // If new preference exists and is not the same as the legacy pref, skip it - if ( - newPrefType !== Services.prefs.PREF_INVALID && - newPrefType !== legacyPrefType - ) { - log.error( - `Error migrating normandy pref ${prefName}; pref type does not match.` - ); - continue; - } - - // Now move the value over. If it matches the default, this will be a no-op - switch (legacyPrefType) { - case Services.prefs.PREF_STRING: - newBranch.setCharPref(prefName, legacyBranch.getCharPref(prefName)); - break; - - case Services.prefs.PREF_INT: - newBranch.setIntPref(prefName, legacyBranch.getIntPref(prefName)); - break; - - case Services.prefs.PREF_BOOL: - newBranch.setBoolPref(prefName, legacyBranch.getBoolPref(prefName)); - break; - - case Services.prefs.PREF_INVALID: - // This should never happen. - log.error( - `Error migrating pref ${prefName}; pref type is invalid (${legacyPrefType}).` - ); - break; - - default: - // This should never happen either. - log.error( - `Error getting startup pref ${prefName}; unknown value type ${legacyPrefType}.` - ); - } - - legacyBranch.clearUserPref(prefName); - } - }, - /** * Copy a preference subtree from one branch to another, being careful about * types, and return the values the target branch originally had. Prefs will diff --git a/toolkit/components/normandy/NormandyMigrations.jsm b/toolkit/components/normandy/NormandyMigrations.jsm new file mode 100644 index 000000000000..83c189fb0dfa --- /dev/null +++ b/toolkit/components/normandy/NormandyMigrations.jsm @@ -0,0 +1,123 @@ +/* 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"; + +const { Log } = ChromeUtils.import("resource://gre/modules/Log.jsm"); +const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm"); + +var EXPORTED_SYMBOLS = ["NormandyMigrations"]; + +const BOOTSTRAP_LOGGER_NAME = "app.normandy.bootstrap"; + +const PREF_PREFIX = "app.normandy"; +const LEGACY_PREF_PREFIX = "extensions.shield-recipe-client"; +const PREF_LOGGING_LEVEL = `${PREF_PREFIX}.logging.level`; +const PREF_MIGRATIONS_APPLIED = `${PREF_PREFIX}.migrationsApplied`; +const PREF_OPTOUTSTUDIES_ENABLED = "app.shield.optoutstudies.enabled"; + +// Logging +const log = Log.repository.getLogger(BOOTSTRAP_LOGGER_NAME); +log.addAppender(new Log.ConsoleAppender(new Log.BasicFormatter())); +log.level = Services.prefs.getIntPref(PREF_LOGGING_LEVEL, Log.Level.Warn); + +const NormandyMigrations = { + applyAll() { + let migrationsApplied = Services.prefs.getIntPref( + PREF_MIGRATIONS_APPLIED, + 0 + ); + + for (let i = migrationsApplied; i < this.migrations.length; i++) { + this.applyOne(i); + migrationsApplied++; + } + + Services.prefs.setIntPref(PREF_MIGRATIONS_APPLIED, migrationsApplied); + }, + + applyOne(id) { + this.migrations[id](); + }, + + migrations: [ + // Migration 0 + migrateShieldPrefs, + + // Migration 1 + migrateStudiesEnabledWithoutHealthReporting, + ], +}; + +function migrateShieldPrefs() { + const legacyBranch = Services.prefs.getBranch(LEGACY_PREF_PREFIX + "."); + const newBranch = Services.prefs.getBranch(PREF_PREFIX + "."); + + for (const prefName of legacyBranch.getChildList("")) { + const legacyPrefType = legacyBranch.getPrefType(prefName); + const newPrefType = newBranch.getPrefType(prefName); + + // If new preference exists and is not the same as the legacy pref, skip it + if ( + newPrefType !== Services.prefs.PREF_INVALID && + newPrefType !== legacyPrefType + ) { + log.error( + `Error migrating normandy pref ${prefName}; pref type does not match.` + ); + continue; + } + + // Now move the value over. If it matches the default, this will be a no-op + switch (legacyPrefType) { + case Services.prefs.PREF_STRING: + newBranch.setCharPref(prefName, legacyBranch.getCharPref(prefName)); + break; + + case Services.prefs.PREF_INT: + newBranch.setIntPref(prefName, legacyBranch.getIntPref(prefName)); + break; + + case Services.prefs.PREF_BOOL: + newBranch.setBoolPref(prefName, legacyBranch.getBoolPref(prefName)); + break; + + case Services.prefs.PREF_INVALID: + // This should never happen. + log.error( + `Error migrating pref ${prefName}; pref type is invalid (${legacyPrefType}).` + ); + break; + + default: + // This should never happen either. + log.error( + `Error getting startup pref ${prefName}; unknown value type ${legacyPrefType}.` + ); + } + + legacyBranch.clearUserPref(prefName); + } +} + +/** + * Migration to handle moving the studies opt-out pref from under the health + * report upload pref to an independent pref. + * + * If the pref was set to true and the health report upload pref was set + * to true then the pref should stay true. Otherwise set it to false. + */ +function migrateStudiesEnabledWithoutHealthReporting() { + const optOutStudiesEnabled = Services.prefs.getBoolPref( + PREF_OPTOUTSTUDIES_ENABLED, + false + ); + const healthReportUploadEnabled = Services.prefs.getBoolPref( + "datareporting.healthreport.uploadEnabled", + false + ); + Services.prefs.setBoolPref( + PREF_OPTOUTSTUDIES_ENABLED, + optOutStudiesEnabled && healthReportUploadEnabled + ); +} diff --git a/toolkit/components/normandy/actions/BaseStudyAction.jsm b/toolkit/components/normandy/actions/BaseStudyAction.jsm new file mode 100644 index 000000000000..778e4a53e60f --- /dev/null +++ b/toolkit/components/normandy/actions/BaseStudyAction.jsm @@ -0,0 +1,44 @@ +/* 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"; + +const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm"); +const { BaseAction } = ChromeUtils.import( + "resource://normandy/actions/BaseAction.jsm" +); + +var EXPORTED_SYMBOLS = ["BaseStudyAction"]; + +const OPT_OUT_STUDIES_ENABLED_PREF = "app.shield.optoutstudies.enabled"; + +/** + * Base class for local study actions. + * + * This should be subclassed. Subclasses must implement _run() for + * per-recipe behavior, and may implement _finalize for actions to be + * taken once after recipes are run. + * + * For actions that need to be taken once before recipes are run + * _preExecution may be overriden but the overridden method must + * call the parent method to ensure the appropriate checks occur. + * + * Other methods should be overridden with care, to maintain the life + * cycle events and error reporting implemented by this class. + */ +class BaseStudyAction extends BaseAction { + _preExecution() { + if (!Services.policies.isAllowed("Shield")) { + this.log.debug("Disabling Shield because it's blocked by policy."); + this.disable(); + } + + if (!Services.prefs.getBoolPref(OPT_OUT_STUDIES_ENABLED_PREF, true)) { + this.log.debug( + "User has opted-out of opt-out experiments, disabling action." + ); + this.disable(); + } + } +} diff --git a/toolkit/components/normandy/actions/BranchedAddonStudyAction.jsm b/toolkit/components/normandy/actions/BranchedAddonStudyAction.jsm index c9cebbfb8b2f..e59f8e3c8c72 100644 --- a/toolkit/components/normandy/actions/BranchedAddonStudyAction.jsm +++ b/toolkit/components/normandy/actions/BranchedAddonStudyAction.jsm @@ -14,8 +14,8 @@ const { XPCOMUtils } = ChromeUtils.import( "resource://gre/modules/XPCOMUtils.jsm" ); -const { BaseAction } = ChromeUtils.import( - "resource://normandy/actions/BaseAction.jsm" +const { BaseStudyAction } = ChromeUtils.import( + "resource://normandy/actions/BaseStudyAction.jsm" ); XPCOMUtils.defineLazyModuleGetters(this, { @@ -33,8 +33,6 @@ XPCOMUtils.defineLazyModuleGetters(this, { var EXPORTED_SYMBOLS = ["BranchedAddonStudyAction"]; -const OPT_OUT_STUDIES_ENABLED_PREF = "app.shield.optoutstudies.enabled"; - class AddonStudyEnrollError extends Error { /** * @param {string} studyName @@ -115,7 +113,7 @@ class AddonStudyUpdateError extends Error { } } -class BranchedAddonStudyAction extends BaseAction { +class BranchedAddonStudyAction extends BaseStudyAction { get schema() { return ActionSchemas["branched-addon-study"]; } @@ -125,23 +123,6 @@ class BranchedAddonStudyAction extends BaseAction { this.seenRecipeIds = new Set(); } - /** - * This hook is executed once before any recipes have been processed, it is - * responsible for: - * - * - Checking if the user has opted out of studies, and if so, it disables the action. - * - Setting up tracking of seen recipes, for use in _finalize. - */ - _preExecution() { - // Check opt-out preference - if (!Services.prefs.getBoolPref(OPT_OUT_STUDIES_ENABLED_PREF, true)) { - this.log.info( - "User has opted-out of opt-out experiments, disabling action." - ); - this.disable(); - } - } - /** * This hook is executed once for each recipe that currently applies to this * client. It is responsible for: diff --git a/toolkit/components/normandy/actions/PreferenceExperimentAction.jsm b/toolkit/components/normandy/actions/PreferenceExperimentAction.jsm index 96a733c822f5..8a7cd6977f7d 100644 --- a/toolkit/components/normandy/actions/PreferenceExperimentAction.jsm +++ b/toolkit/components/normandy/actions/PreferenceExperimentAction.jsm @@ -4,11 +4,8 @@ "use strict"; -const { XPCOMUtils } = ChromeUtils.import( - "resource://gre/modules/XPCOMUtils.jsm" -); -const { BaseAction } = ChromeUtils.import( - "resource://normandy/actions/BaseAction.jsm" +const { BaseStudyAction } = ChromeUtils.import( + "resource://normandy/actions/BaseStudyAction.jsm" ); ChromeUtils.defineModuleGetter( this, @@ -30,13 +27,6 @@ ChromeUtils.defineModuleGetter( "PreferenceExperiments", "resource://normandy/lib/PreferenceExperiments.jsm" ); -const SHIELD_OPT_OUT_PREF = "app.shield.optoutstudies.enabled"; -XPCOMUtils.defineLazyPreferenceGetter( - this, - "shieldOptOutPref", - SHIELD_OPT_OUT_PREF, - false -); var EXPORTED_SYMBOLS = ["PreferenceExperimentAction"]; @@ -45,7 +35,7 @@ var EXPORTED_SYMBOLS = ["PreferenceExperimentAction"]; * user to an experiment branch and modify a preference temporarily to * measure how it affects Firefox via Telemetry. */ -class PreferenceExperimentAction extends BaseAction { +class PreferenceExperimentAction extends BaseStudyAction { get schema() { return ActionSchemas["multi-preference-experiment"]; } @@ -55,15 +45,6 @@ class PreferenceExperimentAction extends BaseAction { this.seenExperimentNames = []; } - _preExecution() { - if (!shieldOptOutPref) { - this.log.info( - "User has opted out of preference experiments. Disabling this action." - ); - this.disable(); - } - } - async _run(recipe) { const { branches, diff --git a/toolkit/components/normandy/jar.mn b/toolkit/components/normandy/jar.mn index cbb75636c952..61f4c9188a0f 100644 --- a/toolkit/components/normandy/jar.mn +++ b/toolkit/components/normandy/jar.mn @@ -5,6 +5,7 @@ toolkit.jar: % resource normandy %res/normandy/ res/normandy/Normandy.jsm (./Normandy.jsm) + res/normandy/NormandyMigrations.jsm (./NormandyMigrations.jsm) res/normandy/lib/ (./lib/*) res/normandy/skin/ (./skin/*) res/normandy/actions/ (./actions/*.jsm) diff --git a/toolkit/components/normandy/lib/RecipeRunner.jsm b/toolkit/components/normandy/lib/RecipeRunner.jsm index 6107b134be26..a241cfae47b2 100644 --- a/toolkit/components/normandy/lib/RecipeRunner.jsm +++ b/toolkit/components/normandy/lib/RecipeRunner.jsm @@ -39,8 +39,6 @@ const TIMER_NAME = "recipe-client-addon-run"; const REMOTE_SETTINGS_COLLECTION = "normandy-recipes"; const PREF_CHANGED_TOPIC = "nsPref:changed"; -const TELEMETRY_ENABLED_PREF = "datareporting.healthreport.uploadEnabled"; - const PREF_PREFIX = "app.normandy"; const RUN_INTERVAL_PREF = `${PREF_PREFIX}.run_interval_seconds`; const FIRST_RUN_PREF = `${PREF_PREFIX}.first_run`; @@ -53,12 +51,7 @@ const LAZY_CLASSIFY_PREF = `${PREF_PREFIX}.experiments.lazy_classify`; // see https://searchfox.org/mozilla-central/rev/11cfa0462/toolkit/components/timermanager/UpdateTimerManager.jsm#8 const TIMER_LAST_UPDATE_PREF = `app.update.lastUpdateTime.${TIMER_NAME}`; -const PREFS_TO_WATCH = [ - RUN_INTERVAL_PREF, - TELEMETRY_ENABLED_PREF, - SHIELD_ENABLED_PREF, - API_URL_PREF, -]; +const PREFS_TO_WATCH = [RUN_INTERVAL_PREF, SHIELD_ENABLED_PREF, API_URL_PREF]; XPCOMUtils.defineLazyGetter(this, "gRemoteSettingsClient", () => { return RemoteSettings(REMOTE_SETTINGS_COLLECTION, { @@ -169,7 +162,6 @@ var RecipeRunner = { break; // explicit fall-through - case TELEMETRY_ENABLED_PREF: case SHIELD_ENABLED_PREF: case API_URL_PREF: this.checkPrefs(); @@ -187,15 +179,6 @@ var RecipeRunner = { }, checkPrefs() { - // Only run if Unified Telemetry is enabled. - if (!Services.prefs.getBoolPref(TELEMETRY_ENABLED_PREF)) { - log.debug( - "Disabling RecipeRunner because Unified Telemetry is disabled." - ); - this.disable(); - return; - } - if (!Services.prefs.getBoolPref(SHIELD_ENABLED_PREF)) { log.debug( `Disabling Shield because ${SHIELD_ENABLED_PREF} is set to false` @@ -204,12 +187,6 @@ var RecipeRunner = { return; } - if (!Services.policies.isAllowed("Shield")) { - log.debug("Disabling Shield because it's blocked by policy."); - this.disable(); - return; - } - const apiUrl = Services.prefs.getCharPref(API_URL_PREF); if (!apiUrl) { log.warn(`Disabling Shield because ${API_URL_PREF} is not set.`); diff --git a/toolkit/components/normandy/test/browser/browser.ini b/toolkit/components/normandy/test/browser/browser.ini index 1119807ad29d..9e750ba7d5d9 100644 --- a/toolkit/components/normandy/test/browser/browser.ini +++ b/toolkit/components/normandy/test/browser/browser.ini @@ -11,8 +11,6 @@ generated-files = addons/normandydriver-a-2.0.xpi head = head.js [browser_about_preferences.js] -# Skip this test when FHR/Telemetry aren't available. -skip-if = !healthreport || !telemetry [browser_about_studies.js] [browser_actions_AddonStudyAction.js] [browser_actions_BranchedAddonStudyAction.js] @@ -32,6 +30,7 @@ skip-if = (verify && (os == 'linux')) [browser_Heartbeat.js] [browser_LogManager.js] [browser_Normandy.js] +[browser_NormandyMigrations.js] [browser_PreferenceExperiments.js] [browser_PreferenceRollouts.js] [browser_RecipeRunner.js] diff --git a/toolkit/components/normandy/test/browser/browser_Normandy.js b/toolkit/components/normandy/test/browser/browser_Normandy.js index f6e61c7da235..c257d3c4e541 100644 --- a/toolkit/components/normandy/test/browser/browser_Normandy.js +++ b/toolkit/components/normandy/test/browser/browser_Normandy.js @@ -286,38 +286,3 @@ decorate_task( ok(PreferenceRollouts.init.called, "startup calls PreferenceRollouts.init"); } ); - -decorate_task(withMockPreferences, async function testPrefMigration( - mockPreferences -) { - const legacyPref = "extensions.shield-recipe-client.test"; - const migratedPref = "app.normandy.test"; - mockPreferences.set(legacyPref, 1); - - ok( - Services.prefs.prefHasUserValue(legacyPref), - "Legacy pref should have a user value before running migration" - ); - ok( - !Services.prefs.prefHasUserValue(migratedPref), - "Migrated pref should not have a user value before running migration" - ); - - Normandy.migrateShieldPrefs(); - - ok( - !Services.prefs.prefHasUserValue(legacyPref), - "Legacy pref should not have a user value after running migration" - ); - ok( - Services.prefs.prefHasUserValue(migratedPref), - "Migrated pref should have a user value after running migration" - ); - is( - Services.prefs.getIntPref(migratedPref), - 1, - "Value should have been migrated" - ); - - Services.prefs.clearUserPref(migratedPref); -}); diff --git a/toolkit/components/normandy/test/browser/browser_NormandyMigrations.js b/toolkit/components/normandy/test/browser/browser_NormandyMigrations.js new file mode 100644 index 000000000000..12b4b1c30841 --- /dev/null +++ b/toolkit/components/normandy/test/browser/browser_NormandyMigrations.js @@ -0,0 +1,101 @@ +ChromeUtils.import("resource://normandy/NormandyMigrations.jsm", this); + +decorate_task(withMockPreferences, async function testApplyMigrations( + mockPreferences +) { + const migrationsAppliedPref = "app.normandy.migrationsApplied"; + mockPreferences.set(migrationsAppliedPref, 0); + + NormandyMigrations.applyAll(); + + is( + Services.prefs.getIntPref(migrationsAppliedPref), + NormandyMigrations.migrations.length, + "All migrations should have been applied" + ); +}); + +decorate_task(withMockPreferences, async function testPrefMigration( + mockPreferences +) { + const legacyPref = "extensions.shield-recipe-client.test"; + const migratedPref = "app.normandy.test"; + mockPreferences.set(legacyPref, 1); + + ok( + Services.prefs.prefHasUserValue(legacyPref), + "Legacy pref should have a user value before running migration" + ); + ok( + !Services.prefs.prefHasUserValue(migratedPref), + "Migrated pref should not have a user value before running migration" + ); + + NormandyMigrations.applyOne(0); + + ok( + !Services.prefs.prefHasUserValue(legacyPref), + "Legacy pref should not have a user value after running migration" + ); + ok( + Services.prefs.prefHasUserValue(migratedPref), + "Migrated pref should have a user value after running migration" + ); + is( + Services.prefs.getIntPref(migratedPref), + 1, + "Value should have been migrated" + ); + + Services.prefs.clearUserPref(migratedPref); +}); + +decorate_task(withMockPreferences, async function testMigration0( + mockPreferences +) { + const studiesEnabledPref = "app.shield.optoutstudies.enabled"; + const healthReportUploadEnabledPref = + "datareporting.healthreport.uploadEnabled"; + + // Both enabled + mockPreferences.set(studiesEnabledPref, true); + mockPreferences.set(healthReportUploadEnabledPref, true); + NormandyMigrations.applyOne(1); + ok( + Services.prefs.getBoolPref(studiesEnabledPref), + "Studies should be enabled." + ); + + mockPreferences.cleanup(); + + // Telemetry disabled, studies enabled + mockPreferences.set(studiesEnabledPref, true); + mockPreferences.set(healthReportUploadEnabledPref, false); + NormandyMigrations.applyOne(1); + ok( + !Services.prefs.getBoolPref(studiesEnabledPref), + "Studies should be disabled." + ); + + mockPreferences.cleanup(); + + // Telemetry enabled, studies disabled + mockPreferences.set(studiesEnabledPref, false); + mockPreferences.set(healthReportUploadEnabledPref, true); + NormandyMigrations.applyOne(1); + ok( + !Services.prefs.getBoolPref(studiesEnabledPref), + "Studies should be disabled." + ); + + mockPreferences.cleanup(); + + // Both disabled + mockPreferences.set(studiesEnabledPref, false); + mockPreferences.set(healthReportUploadEnabledPref, false); + NormandyMigrations.applyOne(1); + ok( + !Services.prefs.getBoolPref(studiesEnabledPref), + "Studies should be disabled." + ); +}); diff --git a/toolkit/components/normandy/test/browser/browser_RecipeRunner.js b/toolkit/components/normandy/test/browser/browser_RecipeRunner.js index be45e484291c..916bdfe8ccfd 100644 --- a/toolkit/components/normandy/test/browser/browser_RecipeRunner.js +++ b/toolkit/components/normandy/test/browser/browser_RecipeRunner.js @@ -590,7 +590,6 @@ decorate_task( decorate_task( withPrefEnv({ set: [ - ["datareporting.healthreport.uploadEnabled", true], // telemetry enabled ["app.normandy.dev_mode", false], ["app.normandy.first_run", false], ["app.normandy.enabled", true], @@ -648,26 +647,6 @@ decorate_task( ); is(disableStub.callCount, 2, "Disable should not be called again"); - await SpecialPowers.pushPrefEnv({ - set: [["datareporting.healthreport.uploadEnabled", false]], - }); - is(enableStub.callCount, 3, "Enable should not be called again"); - is( - disableStub.callCount, - 3, - "RecipeRunner should disable when telemetry is disabled" - ); - - await SpecialPowers.pushPrefEnv({ - set: [["datareporting.healthreport.uploadEnabled", true]], - }); - is( - enableStub.callCount, - 4, - "RecipeRunner should re-enable when telemetry is enabled" - ); - is(disableStub.callCount, 3, "Disable should not be called again"); - is( runStub.callCount, 0, diff --git a/toolkit/components/normandy/test/browser/browser_about_preferences.js b/toolkit/components/normandy/test/browser/browser_about_preferences.js index d1fc51c7a02a..7841d32c99c2 100644 --- a/toolkit/components/normandy/test/browser/browser_about_preferences.js +++ b/toolkit/components/normandy/test/browser/browser_about_preferences.js @@ -3,7 +3,6 @@ ChromeUtils.import("resource://gre/modules/Services.jsm", this); const OPT_OUT_PREF = "app.shield.optoutstudies.enabled"; -const FHR_PREF = "datareporting.healthreport.uploadEnabled"; function withPrivacyPrefs(testFunc) { return async (...args) => @@ -46,48 +45,13 @@ decorate_task( decorate_task( withPrefEnv({ - set: [[FHR_PREF, true]], - }), - withPrivacyPrefs, - async function testEnabledOnLoad(browser) { - const checkbox = browser.contentDocument.getElementById( - "optOutStudiesEnabled" - ); - ok( - !checkbox.disabled, - "Opt-out checkbox is enabled on load when the FHR pref is true" - ); - } -); - -decorate_task( - withPrefEnv({ - set: [[FHR_PREF, false]], - }), - withPrivacyPrefs, - async function testDisabledOnLoad(browser) { - const checkbox = browser.contentDocument.getElementById( - "optOutStudiesEnabled" - ); - ok( - checkbox.disabled, - "Opt-out checkbox is disabled on load when the FHR pref is false" - ); - } -); - -decorate_task( - withPrefEnv({ - set: [[FHR_PREF, true], [OPT_OUT_PREF, true]], + set: [[OPT_OUT_PREF, true]], }), withPrivacyPrefs, async function testCheckboxes(browser) { const optOutCheckbox = browser.contentDocument.getElementById( "optOutStudiesEnabled" ); - const fhrCheckbox = browser.contentDocument.getElementById( - "submitHealthReportBox" - ); optOutCheckbox.click(); ok( @@ -99,40 +63,12 @@ decorate_task( Services.prefs.getBoolPref(OPT_OUT_PREF), "Checking the opt-out checkbox sets the pref to true." ); - - fhrCheckbox.click(); - ok( - !Services.prefs.getBoolPref(OPT_OUT_PREF), - "Unchecking the FHR checkbox sets the opt-out pref to false." - ); - ok( - optOutCheckbox.disabled, - "Unchecking the FHR checkbox disables the opt-out checkbox." - ); - ok( - !optOutCheckbox.checked, - "Unchecking the FHR checkbox unchecks the opt-out checkbox." - ); - - fhrCheckbox.click(); - ok( - Services.prefs.getBoolPref(OPT_OUT_PREF), - "Checking the FHR checkbox sets the opt-out pref to true." - ); - ok( - !optOutCheckbox.disabled, - "Checking the FHR checkbox enables the opt-out checkbox." - ); - ok( - optOutCheckbox.checked, - "Checking the FHR checkbox checks the opt-out checkbox." - ); } ); decorate_task( withPrefEnv({ - set: [[FHR_PREF, true], [OPT_OUT_PREF, true]], + set: [[OPT_OUT_PREF, true]], }), withPrivacyPrefs, async function testPrefWatchers(browser) { @@ -150,34 +86,6 @@ decorate_task( optOutCheckbox.checked, "Enabling the opt-out pref checks the opt-out checkbox." ); - - Services.prefs.setBoolPref(FHR_PREF, false); - ok( - !Services.prefs.getBoolPref(OPT_OUT_PREF), - "Disabling the FHR pref sets the opt-out pref to false." - ); - ok( - optOutCheckbox.disabled, - "Disabling the FHR pref disables the opt-out checkbox." - ); - ok( - !optOutCheckbox.checked, - "Disabling the FHR pref unchecks the opt-out checkbox." - ); - - Services.prefs.setBoolPref(FHR_PREF, true); - ok( - Services.prefs.getBoolPref(OPT_OUT_PREF), - "Enabling the FHR pref sets the opt-out pref to true." - ); - ok( - !optOutCheckbox.disabled, - "Enabling the FHR pref enables the opt-out checkbox." - ); - ok( - optOutCheckbox.checked, - "Enabling the FHR pref checks the opt-out checkbox." - ); } ); diff --git a/toolkit/components/normandy/test/browser/browser_actions_AddonStudyAction.js b/toolkit/components/normandy/test/browser/browser_actions_AddonStudyAction.js index 70539c1a63dc..684fed4812fc 100644 --- a/toolkit/components/normandy/test/browser/browser_actions_AddonStudyAction.js +++ b/toolkit/components/normandy/test/browser/browser_actions_AddonStudyAction.js @@ -30,6 +30,7 @@ function addonStudyRecipeFactory(overrides = {}) { // Test that enroll is not called if recipe is already enrolled and update does nothing // if recipe is unchanged decorate_task( + withStudiesEnabled, ensureAddonCleanup, withMockNormandyApi, AddonStudies.withStudies([addonStudyFactory()]), @@ -72,6 +73,7 @@ decorate_task( // Test that if the add-on fails to install, the database is cleaned up and the // error is correctly reported. decorate_task( + withStudiesEnabled, ensureAddonCleanup, withMockNormandyApi, withSendEventStub, @@ -109,6 +111,7 @@ decorate_task( // Ensure that the database is clean and error correctly reported if hash check fails decorate_task( + withStudiesEnabled, ensureAddonCleanup, withMockNormandyApi, withSendEventStub, @@ -149,6 +152,7 @@ decorate_task( // Ensure that the database is clean and error correctly reported if there is a metadata mismatch decorate_task( + withStudiesEnabled, ensureAddonCleanup, withMockNormandyApi, withSendEventStub, @@ -190,6 +194,7 @@ decorate_task( // Test that in the case of a study add-on conflicting with a non-study add-on, the study does not enroll decorate_task( + withStudiesEnabled, ensureAddonCleanup, withMockNormandyApi, withSendEventStub, @@ -244,6 +249,7 @@ decorate_task( // Test a successful enrollment decorate_task( + withStudiesEnabled, ensureAddonCleanup, withMockNormandyApi, withSendEventStub, @@ -333,6 +339,7 @@ decorate_task( // Test a successful update decorate_task( + withStudiesEnabled, ensureAddonCleanup, withMockNormandyApi, AddonStudies.withStudies([ @@ -412,6 +419,7 @@ decorate_task( // Test update fails when addon ID does not match decorate_task( + withStudiesEnabled, ensureAddonCleanup, withMockNormandyApi, AddonStudies.withStudies([ @@ -473,6 +481,7 @@ decorate_task( // Test update fails when original addon does not exist decorate_task( + withStudiesEnabled, ensureAddonCleanup, withMockNormandyApi, AddonStudies.withStudies([ @@ -536,6 +545,7 @@ decorate_task( // Test update fails when download fails decorate_task( + withStudiesEnabled, ensureAddonCleanup, withMockNormandyApi, AddonStudies.withStudies([ @@ -598,6 +608,7 @@ decorate_task( // Test update fails when hash check fails decorate_task( + withStudiesEnabled, ensureAddonCleanup, withMockNormandyApi, AddonStudies.withStudies([ @@ -661,6 +672,7 @@ decorate_task( // Test update fails on downgrade when study version is greater than extension version decorate_task( + withStudiesEnabled, ensureAddonCleanup, withMockNormandyApi, AddonStudies.withStudies([ @@ -723,6 +735,7 @@ decorate_task( // Test update fails when there is a version mismatch with metadata decorate_task( + withStudiesEnabled, ensureAddonCleanup, withMockNormandyApi, AddonStudies.withStudies([ @@ -799,6 +812,7 @@ decorate_task( // Test that unenrolling fails if the study doesn't exist decorate_task( + withStudiesEnabled, ensureAddonCleanup, AddonStudies.withStudies(), async function unenrollNonexistent(studies) { @@ -813,6 +827,7 @@ decorate_task( // Test that unenrolling an inactive experiment fails decorate_task( + withStudiesEnabled, ensureAddonCleanup, AddonStudies.withStudies([addonStudyFactory({ active: false })]), withSendEventStub, @@ -829,6 +844,7 @@ decorate_task( // test a successful unenrollment const testStopId = "testStop@example.com"; decorate_task( + withStudiesEnabled, ensureAddonCleanup, AddonStudies.withStudies([ addonStudyFactory({ @@ -870,6 +886,7 @@ decorate_task( // If the add-on for a study isn't installed, a warning should be logged, but the action is still successful decorate_task( + withStudiesEnabled, ensureAddonCleanup, AddonStudies.withStudies([ addonStudyFactory({ @@ -903,6 +920,7 @@ decorate_task( // Test that the action respects the study opt-out decorate_task( + withStudiesEnabled, ensureAddonCleanup, withMockNormandyApi, withSendEventStub, @@ -938,6 +956,7 @@ decorate_task( // Test that the action does not enroll paused recipes decorate_task( + withStudiesEnabled, ensureAddonCleanup, withMockNormandyApi, withSendEventStub, @@ -969,6 +988,7 @@ decorate_task( // Test that the action updates paused recipes decorate_task( + withStudiesEnabled, ensureAddonCleanup, withMockNormandyApi, AddonStudies.withStudies([ @@ -1034,6 +1054,7 @@ decorate_task( // Test that update method works for legacy studies with no hash decorate_task( + withStudiesEnabled, ensureAddonCleanup, withMockNormandyApi, AddonStudies.withStudies([ @@ -1117,6 +1138,7 @@ decorate_task( // Test that enroll is not called if recipe is already enrolled decorate_task( + withStudiesEnabled, ensureAddonCleanup, AddonStudies.withStudies([addonStudyFactory()]), async function enrollTwiceFail([study]) { diff --git a/toolkit/components/normandy/test/browser/browser_actions_BranchedAddonStudyAction.js b/toolkit/components/normandy/test/browser/browser_actions_BranchedAddonStudyAction.js index 157e76b1bfb7..fe37dffd5a76 100644 --- a/toolkit/components/normandy/test/browser/browser_actions_BranchedAddonStudyAction.js +++ b/toolkit/components/normandy/test/browser/browser_actions_BranchedAddonStudyAction.js @@ -74,6 +74,7 @@ function recipeFromStudy(study, overrides = {}) { // Test that enroll is not called if recipe is already enrolled and update does nothing // if recipe is unchanged decorate_task( + withStudiesEnabled, ensureAddonCleanup, withMockNormandyApi, AddonStudies.withStudies([branchedAddonStudyFactory()]), @@ -107,6 +108,7 @@ decorate_task( // Test that if the add-on fails to install, the database is cleaned up and the // error is correctly reported. decorate_task( + withStudiesEnabled, ensureAddonCleanup, withMockNormandyApi, withSendEventStub, @@ -147,6 +149,7 @@ decorate_task( // Ensure that the database is clean and error correctly reported if hash check fails decorate_task( + withStudiesEnabled, ensureAddonCleanup, withMockNormandyApi, withSendEventStub, @@ -184,6 +187,7 @@ decorate_task( // Ensure that the database is clean and error correctly reported if there is a metadata mismatch decorate_task( + withStudiesEnabled, ensureAddonCleanup, withMockNormandyApi, withSendEventStub, @@ -221,6 +225,7 @@ decorate_task( // Test that in the case of a study add-on conflicting with a non-study add-on, the study does not enroll decorate_task( + withStudiesEnabled, ensureAddonCleanup, withMockNormandyApi, withSendEventStub, @@ -271,6 +276,7 @@ decorate_task( // Test a successful update decorate_task( + withStudiesEnabled, ensureAddonCleanup, withMockNormandyApi, AddonStudies.withStudies([ @@ -347,6 +353,7 @@ decorate_task( // Test update fails when addon ID does not match decorate_task( + withStudiesEnabled, ensureAddonCleanup, withMockNormandyApi, AddonStudies.withStudies([ @@ -400,6 +407,7 @@ decorate_task( // Test update fails when original addon does not exist decorate_task( + withStudiesEnabled, ensureAddonCleanup, withMockNormandyApi, AddonStudies.withStudies([ @@ -455,6 +463,7 @@ decorate_task( // Test update fails when download fails decorate_task( + withStudiesEnabled, ensureAddonCleanup, withMockNormandyApi, AddonStudies.withStudies([ @@ -509,6 +518,7 @@ decorate_task( // Test update fails when hash check fails decorate_task( + withStudiesEnabled, ensureAddonCleanup, withMockNormandyApi, AddonStudies.withStudies([ @@ -564,6 +574,7 @@ decorate_task( // Test update fails on downgrade when study version is greater than extension version decorate_task( + withStudiesEnabled, ensureAddonCleanup, withMockNormandyApi, AddonStudies.withStudies([ @@ -618,6 +629,7 @@ decorate_task( // Test update fails when there is a version mismatch with metadata decorate_task( + withStudiesEnabled, ensureAddonCleanup, withMockNormandyApi, AddonStudies.withStudies([ @@ -686,6 +698,7 @@ decorate_task( // Test that unenrolling fails if the study doesn't exist decorate_task( + withStudiesEnabled, ensureAddonCleanup, AddonStudies.withStudies(), async function unenrollNonexistent(studies) { @@ -700,6 +713,7 @@ decorate_task( // Test that unenrolling an inactive experiment fails decorate_task( + withStudiesEnabled, ensureAddonCleanup, AddonStudies.withStudies([branchedAddonStudyFactory({ active: false })]), withSendEventStub, @@ -716,6 +730,7 @@ decorate_task( // test a successful unenrollment const testStopId = "testStop@example.com"; decorate_task( + withStudiesEnabled, ensureAddonCleanup, AddonStudies.withStudies([ branchedAddonStudyFactory({ @@ -769,6 +784,7 @@ decorate_task( // If the add-on for a study isn't installed, a warning should be logged, but the action is still successful decorate_task( + withStudiesEnabled, ensureAddonCleanup, AddonStudies.withStudies([ branchedAddonStudyFactory({ @@ -801,6 +817,7 @@ decorate_task( // Test that the action respects the study opt-out decorate_task( + withStudiesEnabled, ensureAddonCleanup, withMockNormandyApi, withSendEventStub, @@ -836,6 +853,7 @@ decorate_task( // Test that the action does not enroll paused recipes decorate_task( + withStudiesEnabled, ensureAddonCleanup, withMockNormandyApi, withSendEventStub, @@ -867,6 +885,7 @@ decorate_task( // Test that the action updates paused recipes decorate_task( + withStudiesEnabled, ensureAddonCleanup, withMockNormandyApi, AddonStudies.withStudies([ @@ -924,6 +943,7 @@ decorate_task( // Test that unenroll called if the study is no longer sent from the server decorate_task( + withStudiesEnabled, ensureAddonCleanup, AddonStudies.withStudies([branchedAddonStudyFactory()]), async function unenroll([study]) { @@ -942,6 +962,7 @@ decorate_task( // A test function that will be parameterized over the argument "branch" below. // Mocks the branch selector, and then tests that the user correctly gets enrolled in that branch. const successEnrollBranchedTest = decorate( + withStudiesEnabled, ensureAddonCleanup, withMockNormandyApi, withSendEventStub, @@ -1070,6 +1091,7 @@ add_task(() => successEnrollBranchedTest("b")); // If the enrolled branch no longer exists, unenroll decorate_task( + withStudiesEnabled, ensureAddonCleanup, withMockNormandyApi, AddonStudies.withStudies([branchedAddonStudyFactory()]), @@ -1142,6 +1164,7 @@ decorate_task( // Test that branches without an add-on can be enrolled and unenrolled succesfully. decorate_task( + withStudiesEnabled, ensureAddonCleanup, withMockNormandyApi, withSendEventStub, diff --git a/toolkit/components/normandy/test/browser/browser_actions_PreferenceExperimentAction.js b/toolkit/components/normandy/test/browser/browser_actions_PreferenceExperimentAction.js index bbf82b1b53d0..fecd632780bc 100644 --- a/toolkit/components/normandy/test/browser/browser_actions_PreferenceExperimentAction.js +++ b/toolkit/components/normandy/test/browser/browser_actions_PreferenceExperimentAction.js @@ -63,6 +63,7 @@ function preferenceExperimentFactory(args) { } decorate_task( + withStudiesEnabled, withStub(Uptake, "reportRecipe"), async function run_without_errors(reportRecipe) { const action = new PreferenceExperimentAction(); @@ -76,6 +77,7 @@ decorate_task( ); decorate_task( + withStudiesEnabled, withStub(Uptake, "reportRecipe"), withStub(Uptake, "reportAction"), withPrefEnv({ set: [["app.shield.optoutstudies.enabled", false]] }), @@ -86,8 +88,9 @@ decorate_task( const recipe = preferenceExperimentFactory(); await action.runRecipe(recipe); - Assert.deepEqual(action.log.info.args, [ - ["User has opted out of preference experiments. Disabling this action."], + Assert.ok(action.log.debug.args.length === 1); + Assert.deepEqual(action.log.debug.args[0], [ + "User has opted-out of opt-out experiments, disabling action.", ]); Assert.deepEqual(action.log.warn.args, [ [ @@ -97,10 +100,9 @@ decorate_task( ]); await action.finalize(); - Assert.deepEqual(action.log.debug.args, [ - [ - "Skipping post-execution hook for PreferenceExperimentAction because it is disabled.", - ], + Assert.ok(action.log.debug.args.length === 2); + Assert.deepEqual(action.log.debug.args[1], [ + "Skipping post-execution hook for PreferenceExperimentAction because it is disabled.", ]); Assert.deepEqual(reportRecipe.args, [ [recipe, Uptake.RECIPE_ACTION_DISABLED], @@ -110,6 +112,7 @@ decorate_task( ); decorate_task( + withStudiesEnabled, withStub(PreferenceExperiments, "start"), PreferenceExperiments.withMockExperiments([]), async function enroll_user_if_never_been_in_experiment(startStub) { @@ -172,6 +175,7 @@ decorate_task( ); decorate_task( + withStudiesEnabled, withStub(PreferenceExperiments, "markLastSeen"), PreferenceExperiments.withMockExperiments([{ name: "test", expired: false }]), async function markSeen_if_experiment_active(markLastSeenStub) { @@ -188,6 +192,7 @@ decorate_task( ); decorate_task( + withStudiesEnabled, withStub(PreferenceExperiments, "markLastSeen"), PreferenceExperiments.withMockExperiments([{ name: "test", expired: true }]), async function dont_markSeen_if_experiment_expired(markLastSeenStub) { @@ -204,6 +209,7 @@ decorate_task( ); decorate_task( + withStudiesEnabled, withStub(PreferenceExperiments, "start"), async function do_nothing_if_enrollment_paused(startStub) { const action = new PreferenceExperimentAction(); @@ -219,6 +225,7 @@ decorate_task( ); decorate_task( + withStudiesEnabled, withStub(PreferenceExperiments, "stop"), PreferenceExperiments.withMockExperiments([ { name: "seen", expired: false, actionName: "PreferenceExperimentAction" }, @@ -244,6 +251,7 @@ decorate_task( ); decorate_task( + withStudiesEnabled, withStub(PreferenceExperiments, "stop"), PreferenceExperiments.withMockExperiments([ { @@ -275,6 +283,7 @@ decorate_task( ); decorate_task( + withStudiesEnabled, withStub(PreferenceExperiments, "start"), withStub(Uptake, "reportRecipe"), PreferenceExperiments.withMockExperiments([ @@ -317,6 +326,7 @@ decorate_task( ); decorate_task( + withStudiesEnabled, withStub(PreferenceExperiments, "start"), PreferenceExperiments.withMockExperiments([]), async function experimentType_with_isHighPopulation_false(startStub) { @@ -333,6 +343,7 @@ decorate_task( ); decorate_task( + withStudiesEnabled, withStub(PreferenceExperiments, "start"), PreferenceExperiments.withMockExperiments([]), async function experimentType_with_isHighPopulation_true(startStub) { @@ -349,6 +360,7 @@ decorate_task( ); decorate_task( + withStudiesEnabled, withStub(Sampling, "ratioSample"), async function chooseBranch_uses_ratioSample(ratioSampleStub) { ratioSampleStub.returns(Promise.resolve(1)); @@ -388,6 +400,7 @@ decorate_task( ); decorate_task( + withStudiesEnabled, withMockPreferences, PreferenceExperiments.withMockExperiments([]), async function integration_test_enroll_and_unenroll(prefs) { diff --git a/toolkit/components/normandy/test/browser/browser_actions_SinglePreferenceExperimentAction.js b/toolkit/components/normandy/test/browser/browser_actions_SinglePreferenceExperimentAction.js index 93870aa0d5b6..5d3481c85e73 100644 --- a/toolkit/components/normandy/test/browser/browser_actions_SinglePreferenceExperimentAction.js +++ b/toolkit/components/normandy/test/browser/browser_actions_SinglePreferenceExperimentAction.js @@ -42,6 +42,7 @@ function preferenceExperimentFactory(args) { } decorate_task( + withStudiesEnabled, withStub(PreferenceExperimentAction.prototype, "_run"), PreferenceExperiments.withMockExperiments([]), async function enroll_user_if_never_been_in_experiment(runStub) { diff --git a/toolkit/components/normandy/test/browser/head.js b/toolkit/components/normandy/test/browser/head.js index c2c695a36abe..d6ba667bcb89 100644 --- a/toolkit/components/normandy/test/browser/head.js +++ b/toolkit/components/normandy/test/browser/head.js @@ -240,6 +240,19 @@ this.withPrefEnv = function(inPrefs) { }; }; +this.withStudiesEnabled = function(testFunc) { + return async function inner(...args) { + await SpecialPowers.pushPrefEnv({ + set: [["app.shield.optoutstudies.enabled", true]], + }); + try { + await testFunc(...args); + } finally { + await SpecialPowers.popPrefEnv(); + } + }; +}; + /** * Combine a list of functions right to left. The rightmost function is passed * to the preceding function as the argument; the result of this is passed to diff --git a/toolkit/modules/AppConstants.jsm b/toolkit/modules/AppConstants.jsm index 1f47c364ca26..7a46b7290cd0 100644 --- a/toolkit/modules/AppConstants.jsm +++ b/toolkit/modules/AppConstants.jsm @@ -187,6 +187,13 @@ this.AppConstants = Object.freeze({ false, #endif + MOZ_NORMANDY: +#ifdef MOZ_NORMANDY + true, +#else + false, +#endif + MOZ_MAINTENANCE_SERVICE: #ifdef MOZ_MAINTENANCE_SERVICE true, diff --git a/toolkit/moz.configure b/toolkit/moz.configure index 1637dd8575e8..4a880f99d761 100644 --- a/toolkit/moz.configure +++ b/toolkit/moz.configure @@ -617,6 +617,11 @@ project_flag('MOZ_SERVICES_HEALTHREPORT', set_for_old_configure=True, set_as_define=True) +project_flag('MOZ_NORMANDY', + help='Enable Normandy recipe runner', + set_for_old_configure=True, + set_as_define=True) + project_flag('MOZ_SERVICES_SYNC', help='Build Sync Services if required') diff --git a/tools/lint/eslint/eslint-plugin-mozilla/lib/processors/xul.js b/tools/lint/eslint/eslint-plugin-mozilla/lib/processors/xul.js index 9a6a91767602..f6adf90cf238 100644 --- a/tools/lint/eslint/eslint-plugin-mozilla/lib/processors/xul.js +++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/processors/xul.js @@ -34,6 +34,7 @@ const kIfdefStateForLinting = { MOZ_DATA_REPORTING: true, MOZ_TELEMETRY_REPORTING: true, MOZ_CRASHREPORTER: true, + MOZ_NORMANDY: true, MOZ_MAINTENANCE_SERVICE: true, HAVE_SHELL_SERVICE: true, MENUBAR_CAN_AUTOHIDE: true,