Bug 1569330 - Disable telemetry check in Normandy recipe runner r=mythmon,nalexander

Differential Revision: https://phabricator.services.mozilla.com/D39576

--HG--
extra : moz-landing-system : lando
This commit is contained in:
rdalal 2019-08-06 23:54:34 +00:00
Родитель 9f979c6057
Коммит 43acd674ea
28 изменённых файлов: 457 добавлений и 310 удалений

Просмотреть файл

@ -1237,7 +1237,9 @@ BrowserGlue.prototype = {
"resource:///modules/themes/dark/" "resource:///modules/themes/dark/"
); );
Normandy.init(); if (AppConstants.MOZ_NORMANDY) {
Normandy.init();
}
SaveToPocket.init(); SaveToPocket.init();
Services.obs.notifyObservers(null, "browser-ui-startup-complete"); Services.obs.notifyObservers(null, "browser-ui-startup-complete");

Просмотреть файл

@ -7,17 +7,41 @@ add_task(async function test_policy_disable_shield() {
const { RecipeRunner } = ChromeUtils.import( const { RecipeRunner } = ChromeUtils.import(
"resource://normandy/lib/RecipeRunner.jsm" "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({ await SpecialPowers.pushPrefEnv({
set: [ set: [
["app.normandy.api_url", "https://localhost/selfsupport-dummy/"], ["app.normandy.api_url", "https://localhost/selfsupport-dummy/"],
["datareporting.healthreport.uploadEnabled", true], ["app.shield.optoutstudies.enabled", true],
], ],
}); });
ok(RecipeRunner, "RecipeRunner exists"); ok(RecipeRunner, "RecipeRunner exists");
RecipeRunner.checkPrefs(); 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({ await setupPolicyEngineWithJson({
policies: { policies: {
@ -26,5 +50,19 @@ add_task(async function test_policy_disable_shield() {
}); });
RecipeRunner.checkPrefs(); 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"
);
}); });

Просмотреть файл

@ -828,17 +828,6 @@
class="learnMore" is="text-link" class="learnMore" is="text-link"
data-l10n-id="collection-health-report-link"/> data-l10n-id="collection-health-report-link"/>
<vbox class="indent"> <vbox class="indent">
<hbox align="center">
<checkbox id="optOutStudiesEnabled"
class="tail-with-learn-more"
data-l10n-id="collection-studies"/>
<label id="viewShieldStudies"
href="about:studies"
useoriginprincipal="true"
class="learnMore" is="text-link"
data-l10n-id="collection-studies-link"/>
</hbox>
<hbox align="center"> <hbox align="center">
<checkbox id="addonRecommendationEnabled" <checkbox id="addonRecommendationEnabled"
class="tail-with-learn-more" class="tail-with-learn-more"
@ -855,6 +844,19 @@
data-l10n-id="collection-health-report-disabled"/> data-l10n-id="collection-health-report-disabled"/>
#endif #endif
#ifdef MOZ_NORMANDY
<hbox align="center">
<checkbox id="optOutStudiesEnabled"
class="tail-with-learn-more"
data-l10n-id="collection-studies"/>
<label id="viewShieldStudies"
href="about:studies"
useoriginprincipal="true"
class="learnMore" is="text-link"
data-l10n-id="collection-studies-link"/>
</hbox>
#endif
#ifdef MOZ_CRASHREPORTER #ifdef MOZ_CRASHREPORTER
<hbox align="center"> <hbox align="center">
<checkbox id="automaticallySubmitCrashesBox" <checkbox id="automaticallySubmitCrashesBox"

Просмотреть файл

@ -604,7 +604,9 @@ var gPrivacyPane = {
"command", "command",
gPrivacyPane.updateSubmitHealthReport gPrivacyPane.updateSubmitHealthReport
); );
this.initOptOutStudyCheckbox(); if (AppConstants.MOZ_NORMANDY) {
this.initOptOutStudyCheckbox();
}
this.initAddonRecommendationsCheckbox(); this.initAddonRecommendationsCheckbox();
} }
this._initA11yState(); this._initA11yState();
@ -2140,14 +2142,11 @@ var gPrivacyPane = {
* handles events coming from the UI for it. * handles events coming from the UI for it.
*/ */
initOptOutStudyCheckbox(doc) { initOptOutStudyCheckbox(doc) {
const allowedByPolicy = Services.policies.isAllowed("Shield");
// The checkbox should be disabled if any of the below are true. This // The checkbox should be disabled if any of the below are true. This
// prevents the user from changing the value in the box. // prevents the user from changing the value in the box.
// //
// * the policy forbids shield // * the policy forbids shield
// * the Shield Study preference is locked // * Normandy is disabled
// * the FHR pref is false
// //
// The checkbox should match the value of the preference only if all of // The checkbox should match the value of the preference only if all of
// these are true. Otherwise, the checkbox should remain unchecked. This // these are true. Otherwise, the checkbox should remain unchecked. This
@ -2155,16 +2154,27 @@ var gPrivacyPane = {
// so showing a checkbox would be confusing. // so showing a checkbox would be confusing.
// //
// * the policy allows Shield // * the policy allows Shield
// * the FHR pref is true
// * Normandy is enabled // * Normandy is enabled
dataCollectionCheckboxHandler({
checkbox: document.getElementById("optOutStudiesEnabled"), const allowedByPolicy = Services.policies.isAllowed("Shield");
matchPref: () => const checkbox = document.getElementById("optOutStudiesEnabled");
allowedByPolicy &&
Services.prefs.getBoolPref(PREF_NORMANDY_ENABLED, false), if (
isDisabled: () => !allowedByPolicy, allowedByPolicy &&
pref: PREF_OPT_OUT_STUDIES_ENABLED, 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() { initAddonRecommendationsCheckbox() {

Просмотреть файл

@ -9,6 +9,7 @@ imply_option('MOZ_SERVICES_HEALTHREPORT', True)
imply_option('MOZ_SERVICES_SYNC', True) imply_option('MOZ_SERVICES_SYNC', True)
imply_option('MOZ_DEDICATED_PROFILES', True) imply_option('MOZ_DEDICATED_PROFILES', True)
imply_option('MOZ_BLOCK_PROFILE_DOWNGRADE', True) imply_option('MOZ_BLOCK_PROFILE_DOWNGRADE', True)
imply_option('MOZ_NORMANDY', True)
with only_when(target_is_linux & compile_environment): with only_when(target_is_linux & compile_environment):
option(env='MOZ_NO_PIE_COMPAT', option(env='MOZ_NO_PIE_COMPAT',

Просмотреть файл

@ -121,7 +121,7 @@ set_define('FENNEC_NIGHTLY', depends_if('FENNEC_NIGHTLY')(lambda _: True))
def fennec_nightly(nightly): def fennec_nightly(nightly):
return bool(nightly) return bool(nightly)
imply_option('MOZ_NORMANDY', False)
imply_option('MOZ_SERVICES_HEALTHREPORT', True) imply_option('MOZ_SERVICES_HEALTHREPORT', True)
imply_option('MOZ_ANDROID_HISTORY', True) imply_option('MOZ_ANDROID_HISTORY', True)
imply_option('--enable-small-chunk-size', True) imply_option('--enable-small-chunk-size', True)

Просмотреть файл

@ -3085,7 +3085,7 @@ AC_SUBST(MOZ_INCLUDE_SOURCE_INFO)
dnl If we have any service that uploads data (and requires data submission dnl If we have any service that uploads data (and requires data submission
dnl policy alert), set MOZ_DATA_REPORTING. dnl policy alert), set MOZ_DATA_REPORTING.
dnl We need SUBST for build system and DEFINE for xul preprocessor. 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 MOZ_DATA_REPORTING=1
AC_DEFINE(MOZ_DATA_REPORTING) AC_DEFINE(MOZ_DATA_REPORTING)
AC_SUBST(MOZ_DATA_REPORTING) AC_SUBST(MOZ_DATA_REPORTING)

Просмотреть файл

@ -78,6 +78,7 @@ def build_dict(config, env=os.environ):
d['devedition'] = substs.get('MOZ_DEV_EDITION') == '1' d['devedition'] = substs.get('MOZ_DEV_EDITION') == '1'
d['pgo'] = substs.get('MOZ_PGO') == '1' d['pgo'] = substs.get('MOZ_PGO') == '1'
d['crashreporter'] = bool(substs.get('MOZ_CRASHREPORTER')) d['crashreporter'] = bool(substs.get('MOZ_CRASHREPORTER'))
d['normandy'] = substs.get('MOZ_NORMANDY') == '1'
d['datareporting'] = bool(substs.get('MOZ_DATA_REPORTING')) d['datareporting'] = bool(substs.get('MOZ_DATA_REPORTING'))
d['healthreport'] = substs.get('MOZ_SERVICES_HEALTHREPORT') == '1' d['healthreport'] = substs.get('MOZ_SERVICES_HEALTHREPORT') == '1'
d['sync'] = substs.get('MOZ_SERVICES_SYNC') == '1' d['sync'] = substs.get('MOZ_SERVICES_SYNC') == '1'

Просмотреть файл

@ -14,6 +14,7 @@ XPCOMUtils.defineLazyModuleGetters(this, {
AddonStudies: "resource://normandy/lib/AddonStudies.jsm", AddonStudies: "resource://normandy/lib/AddonStudies.jsm",
CleanupManager: "resource://normandy/lib/CleanupManager.jsm", CleanupManager: "resource://normandy/lib/CleanupManager.jsm",
LogManager: "resource://normandy/lib/LogManager.jsm", LogManager: "resource://normandy/lib/LogManager.jsm",
NormandyMigrations: "resource://normandy/NormandyMigrations.jsm",
PreferenceExperiments: "resource://normandy/lib/PreferenceExperiments.jsm", PreferenceExperiments: "resource://normandy/lib/PreferenceExperiments.jsm",
PreferenceRollouts: "resource://normandy/lib/PreferenceRollouts.jsm", PreferenceRollouts: "resource://normandy/lib/PreferenceRollouts.jsm",
RecipeRunner: "resource://normandy/lib/RecipeRunner.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 SHIELD_INIT_NOTIFICATION = "shield-init-complete";
const PREF_PREFIX = "app.normandy"; const PREF_PREFIX = "app.normandy";
const LEGACY_PREF_PREFIX = "extensions.shield-recipe-client";
const STARTUP_EXPERIMENT_PREFS_BRANCH = `${PREF_PREFIX}.startupExperimentPrefs.`; const STARTUP_EXPERIMENT_PREFS_BRANCH = `${PREF_PREFIX}.startupExperimentPrefs.`;
const STARTUP_ROLLOUT_PREFS_BRANCH = `${PREF_PREFIX}.startupRolloutPrefs.`; const STARTUP_ROLLOUT_PREFS_BRANCH = `${PREF_PREFIX}.startupRolloutPrefs.`;
const PREF_LOGGING_LEVEL = `${PREF_PREFIX}.logging.level`; const PREF_LOGGING_LEVEL = `${PREF_PREFIX}.logging.level`;
@ -44,7 +44,7 @@ var Normandy = {
init() { init() {
// Initialization that needs to happen before the first paint on startup. // Initialization that needs to happen before the first paint on startup.
this.migrateShieldPrefs(); NormandyMigrations.applyAll();
this.rolloutPrefsChanged = this.applyStartupPrefs( this.rolloutPrefsChanged = this.applyStartupPrefs(
STARTUP_ROLLOUT_PREFS_BRANCH 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 * Copy a preference subtree from one branch to another, being careful about
* types, and return the values the target branch originally had. Prefs will * types, and return the values the target branch originally had. Prefs will

Просмотреть файл

@ -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
);
}

Просмотреть файл

@ -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();
}
}
}

Просмотреть файл

@ -14,8 +14,8 @@
const { XPCOMUtils } = ChromeUtils.import( const { XPCOMUtils } = ChromeUtils.import(
"resource://gre/modules/XPCOMUtils.jsm" "resource://gre/modules/XPCOMUtils.jsm"
); );
const { BaseAction } = ChromeUtils.import( const { BaseStudyAction } = ChromeUtils.import(
"resource://normandy/actions/BaseAction.jsm" "resource://normandy/actions/BaseStudyAction.jsm"
); );
XPCOMUtils.defineLazyModuleGetters(this, { XPCOMUtils.defineLazyModuleGetters(this, {
@ -33,8 +33,6 @@ XPCOMUtils.defineLazyModuleGetters(this, {
var EXPORTED_SYMBOLS = ["BranchedAddonStudyAction"]; var EXPORTED_SYMBOLS = ["BranchedAddonStudyAction"];
const OPT_OUT_STUDIES_ENABLED_PREF = "app.shield.optoutstudies.enabled";
class AddonStudyEnrollError extends Error { class AddonStudyEnrollError extends Error {
/** /**
* @param {string} studyName * @param {string} studyName
@ -115,7 +113,7 @@ class AddonStudyUpdateError extends Error {
} }
} }
class BranchedAddonStudyAction extends BaseAction { class BranchedAddonStudyAction extends BaseStudyAction {
get schema() { get schema() {
return ActionSchemas["branched-addon-study"]; return ActionSchemas["branched-addon-study"];
} }
@ -125,23 +123,6 @@ class BranchedAddonStudyAction extends BaseAction {
this.seenRecipeIds = new Set(); 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 * This hook is executed once for each recipe that currently applies to this
* client. It is responsible for: * client. It is responsible for:

Просмотреть файл

@ -4,11 +4,8 @@
"use strict"; "use strict";
const { XPCOMUtils } = ChromeUtils.import( const { BaseStudyAction } = ChromeUtils.import(
"resource://gre/modules/XPCOMUtils.jsm" "resource://normandy/actions/BaseStudyAction.jsm"
);
const { BaseAction } = ChromeUtils.import(
"resource://normandy/actions/BaseAction.jsm"
); );
ChromeUtils.defineModuleGetter( ChromeUtils.defineModuleGetter(
this, this,
@ -30,13 +27,6 @@ ChromeUtils.defineModuleGetter(
"PreferenceExperiments", "PreferenceExperiments",
"resource://normandy/lib/PreferenceExperiments.jsm" "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"]; var EXPORTED_SYMBOLS = ["PreferenceExperimentAction"];
@ -45,7 +35,7 @@ var EXPORTED_SYMBOLS = ["PreferenceExperimentAction"];
* user to an experiment branch and modify a preference temporarily to * user to an experiment branch and modify a preference temporarily to
* measure how it affects Firefox via Telemetry. * measure how it affects Firefox via Telemetry.
*/ */
class PreferenceExperimentAction extends BaseAction { class PreferenceExperimentAction extends BaseStudyAction {
get schema() { get schema() {
return ActionSchemas["multi-preference-experiment"]; return ActionSchemas["multi-preference-experiment"];
} }
@ -55,15 +45,6 @@ class PreferenceExperimentAction extends BaseAction {
this.seenExperimentNames = []; this.seenExperimentNames = [];
} }
_preExecution() {
if (!shieldOptOutPref) {
this.log.info(
"User has opted out of preference experiments. Disabling this action."
);
this.disable();
}
}
async _run(recipe) { async _run(recipe) {
const { const {
branches, branches,

Просмотреть файл

@ -5,6 +5,7 @@
toolkit.jar: toolkit.jar:
% resource normandy %res/normandy/ % resource normandy %res/normandy/
res/normandy/Normandy.jsm (./Normandy.jsm) res/normandy/Normandy.jsm (./Normandy.jsm)
res/normandy/NormandyMigrations.jsm (./NormandyMigrations.jsm)
res/normandy/lib/ (./lib/*) res/normandy/lib/ (./lib/*)
res/normandy/skin/ (./skin/*) res/normandy/skin/ (./skin/*)
res/normandy/actions/ (./actions/*.jsm) res/normandy/actions/ (./actions/*.jsm)

Просмотреть файл

@ -39,8 +39,6 @@ const TIMER_NAME = "recipe-client-addon-run";
const REMOTE_SETTINGS_COLLECTION = "normandy-recipes"; const REMOTE_SETTINGS_COLLECTION = "normandy-recipes";
const PREF_CHANGED_TOPIC = "nsPref:changed"; const PREF_CHANGED_TOPIC = "nsPref:changed";
const TELEMETRY_ENABLED_PREF = "datareporting.healthreport.uploadEnabled";
const PREF_PREFIX = "app.normandy"; const PREF_PREFIX = "app.normandy";
const RUN_INTERVAL_PREF = `${PREF_PREFIX}.run_interval_seconds`; const RUN_INTERVAL_PREF = `${PREF_PREFIX}.run_interval_seconds`;
const FIRST_RUN_PREF = `${PREF_PREFIX}.first_run`; 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 // 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 TIMER_LAST_UPDATE_PREF = `app.update.lastUpdateTime.${TIMER_NAME}`;
const PREFS_TO_WATCH = [ const PREFS_TO_WATCH = [RUN_INTERVAL_PREF, SHIELD_ENABLED_PREF, API_URL_PREF];
RUN_INTERVAL_PREF,
TELEMETRY_ENABLED_PREF,
SHIELD_ENABLED_PREF,
API_URL_PREF,
];
XPCOMUtils.defineLazyGetter(this, "gRemoteSettingsClient", () => { XPCOMUtils.defineLazyGetter(this, "gRemoteSettingsClient", () => {
return RemoteSettings(REMOTE_SETTINGS_COLLECTION, { return RemoteSettings(REMOTE_SETTINGS_COLLECTION, {
@ -169,7 +162,6 @@ var RecipeRunner = {
break; break;
// explicit fall-through // explicit fall-through
case TELEMETRY_ENABLED_PREF:
case SHIELD_ENABLED_PREF: case SHIELD_ENABLED_PREF:
case API_URL_PREF: case API_URL_PREF:
this.checkPrefs(); this.checkPrefs();
@ -187,15 +179,6 @@ var RecipeRunner = {
}, },
checkPrefs() { 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)) { if (!Services.prefs.getBoolPref(SHIELD_ENABLED_PREF)) {
log.debug( log.debug(
`Disabling Shield because ${SHIELD_ENABLED_PREF} is set to false` `Disabling Shield because ${SHIELD_ENABLED_PREF} is set to false`
@ -204,12 +187,6 @@ var RecipeRunner = {
return; 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); const apiUrl = Services.prefs.getCharPref(API_URL_PREF);
if (!apiUrl) { if (!apiUrl) {
log.warn(`Disabling Shield because ${API_URL_PREF} is not set.`); log.warn(`Disabling Shield because ${API_URL_PREF} is not set.`);

Просмотреть файл

@ -11,8 +11,6 @@ generated-files =
addons/normandydriver-a-2.0.xpi addons/normandydriver-a-2.0.xpi
head = head.js head = head.js
[browser_about_preferences.js] [browser_about_preferences.js]
# Skip this test when FHR/Telemetry aren't available.
skip-if = !healthreport || !telemetry
[browser_about_studies.js] [browser_about_studies.js]
[browser_actions_AddonStudyAction.js] [browser_actions_AddonStudyAction.js]
[browser_actions_BranchedAddonStudyAction.js] [browser_actions_BranchedAddonStudyAction.js]
@ -32,6 +30,7 @@ skip-if = (verify && (os == 'linux'))
[browser_Heartbeat.js] [browser_Heartbeat.js]
[browser_LogManager.js] [browser_LogManager.js]
[browser_Normandy.js] [browser_Normandy.js]
[browser_NormandyMigrations.js]
[browser_PreferenceExperiments.js] [browser_PreferenceExperiments.js]
[browser_PreferenceRollouts.js] [browser_PreferenceRollouts.js]
[browser_RecipeRunner.js] [browser_RecipeRunner.js]

Просмотреть файл

@ -286,38 +286,3 @@ decorate_task(
ok(PreferenceRollouts.init.called, "startup calls PreferenceRollouts.init"); 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);
});

Просмотреть файл

@ -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."
);
});

Просмотреть файл

@ -590,7 +590,6 @@ decorate_task(
decorate_task( decorate_task(
withPrefEnv({ withPrefEnv({
set: [ set: [
["datareporting.healthreport.uploadEnabled", true], // telemetry enabled
["app.normandy.dev_mode", false], ["app.normandy.dev_mode", false],
["app.normandy.first_run", false], ["app.normandy.first_run", false],
["app.normandy.enabled", true], ["app.normandy.enabled", true],
@ -648,26 +647,6 @@ decorate_task(
); );
is(disableStub.callCount, 2, "Disable should not be called again"); 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( is(
runStub.callCount, runStub.callCount,
0, 0,

Просмотреть файл

@ -3,7 +3,6 @@
ChromeUtils.import("resource://gre/modules/Services.jsm", this); ChromeUtils.import("resource://gre/modules/Services.jsm", this);
const OPT_OUT_PREF = "app.shield.optoutstudies.enabled"; const OPT_OUT_PREF = "app.shield.optoutstudies.enabled";
const FHR_PREF = "datareporting.healthreport.uploadEnabled";
function withPrivacyPrefs(testFunc) { function withPrivacyPrefs(testFunc) {
return async (...args) => return async (...args) =>
@ -46,48 +45,13 @@ decorate_task(
decorate_task( decorate_task(
withPrefEnv({ withPrefEnv({
set: [[FHR_PREF, true]], set: [[OPT_OUT_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]],
}), }),
withPrivacyPrefs, withPrivacyPrefs,
async function testCheckboxes(browser) { async function testCheckboxes(browser) {
const optOutCheckbox = browser.contentDocument.getElementById( const optOutCheckbox = browser.contentDocument.getElementById(
"optOutStudiesEnabled" "optOutStudiesEnabled"
); );
const fhrCheckbox = browser.contentDocument.getElementById(
"submitHealthReportBox"
);
optOutCheckbox.click(); optOutCheckbox.click();
ok( ok(
@ -99,40 +63,12 @@ decorate_task(
Services.prefs.getBoolPref(OPT_OUT_PREF), Services.prefs.getBoolPref(OPT_OUT_PREF),
"Checking the opt-out checkbox sets the pref to true." "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( decorate_task(
withPrefEnv({ withPrefEnv({
set: [[FHR_PREF, true], [OPT_OUT_PREF, true]], set: [[OPT_OUT_PREF, true]],
}), }),
withPrivacyPrefs, withPrivacyPrefs,
async function testPrefWatchers(browser) { async function testPrefWatchers(browser) {
@ -150,34 +86,6 @@ decorate_task(
optOutCheckbox.checked, optOutCheckbox.checked,
"Enabling the opt-out pref checks the opt-out checkbox." "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."
);
} }
); );

Просмотреть файл

@ -30,6 +30,7 @@ function addonStudyRecipeFactory(overrides = {}) {
// Test that enroll is not called if recipe is already enrolled and update does nothing // Test that enroll is not called if recipe is already enrolled and update does nothing
// if recipe is unchanged // if recipe is unchanged
decorate_task( decorate_task(
withStudiesEnabled,
ensureAddonCleanup, ensureAddonCleanup,
withMockNormandyApi, withMockNormandyApi,
AddonStudies.withStudies([addonStudyFactory()]), 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 // Test that if the add-on fails to install, the database is cleaned up and the
// error is correctly reported. // error is correctly reported.
decorate_task( decorate_task(
withStudiesEnabled,
ensureAddonCleanup, ensureAddonCleanup,
withMockNormandyApi, withMockNormandyApi,
withSendEventStub, withSendEventStub,
@ -109,6 +111,7 @@ decorate_task(
// Ensure that the database is clean and error correctly reported if hash check fails // Ensure that the database is clean and error correctly reported if hash check fails
decorate_task( decorate_task(
withStudiesEnabled,
ensureAddonCleanup, ensureAddonCleanup,
withMockNormandyApi, withMockNormandyApi,
withSendEventStub, withSendEventStub,
@ -149,6 +152,7 @@ decorate_task(
// Ensure that the database is clean and error correctly reported if there is a metadata mismatch // Ensure that the database is clean and error correctly reported if there is a metadata mismatch
decorate_task( decorate_task(
withStudiesEnabled,
ensureAddonCleanup, ensureAddonCleanup,
withMockNormandyApi, withMockNormandyApi,
withSendEventStub, 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 // Test that in the case of a study add-on conflicting with a non-study add-on, the study does not enroll
decorate_task( decorate_task(
withStudiesEnabled,
ensureAddonCleanup, ensureAddonCleanup,
withMockNormandyApi, withMockNormandyApi,
withSendEventStub, withSendEventStub,
@ -244,6 +249,7 @@ decorate_task(
// Test a successful enrollment // Test a successful enrollment
decorate_task( decorate_task(
withStudiesEnabled,
ensureAddonCleanup, ensureAddonCleanup,
withMockNormandyApi, withMockNormandyApi,
withSendEventStub, withSendEventStub,
@ -333,6 +339,7 @@ decorate_task(
// Test a successful update // Test a successful update
decorate_task( decorate_task(
withStudiesEnabled,
ensureAddonCleanup, ensureAddonCleanup,
withMockNormandyApi, withMockNormandyApi,
AddonStudies.withStudies([ AddonStudies.withStudies([
@ -412,6 +419,7 @@ decorate_task(
// Test update fails when addon ID does not match // Test update fails when addon ID does not match
decorate_task( decorate_task(
withStudiesEnabled,
ensureAddonCleanup, ensureAddonCleanup,
withMockNormandyApi, withMockNormandyApi,
AddonStudies.withStudies([ AddonStudies.withStudies([
@ -473,6 +481,7 @@ decorate_task(
// Test update fails when original addon does not exist // Test update fails when original addon does not exist
decorate_task( decorate_task(
withStudiesEnabled,
ensureAddonCleanup, ensureAddonCleanup,
withMockNormandyApi, withMockNormandyApi,
AddonStudies.withStudies([ AddonStudies.withStudies([
@ -536,6 +545,7 @@ decorate_task(
// Test update fails when download fails // Test update fails when download fails
decorate_task( decorate_task(
withStudiesEnabled,
ensureAddonCleanup, ensureAddonCleanup,
withMockNormandyApi, withMockNormandyApi,
AddonStudies.withStudies([ AddonStudies.withStudies([
@ -598,6 +608,7 @@ decorate_task(
// Test update fails when hash check fails // Test update fails when hash check fails
decorate_task( decorate_task(
withStudiesEnabled,
ensureAddonCleanup, ensureAddonCleanup,
withMockNormandyApi, withMockNormandyApi,
AddonStudies.withStudies([ AddonStudies.withStudies([
@ -661,6 +672,7 @@ decorate_task(
// Test update fails on downgrade when study version is greater than extension version // Test update fails on downgrade when study version is greater than extension version
decorate_task( decorate_task(
withStudiesEnabled,
ensureAddonCleanup, ensureAddonCleanup,
withMockNormandyApi, withMockNormandyApi,
AddonStudies.withStudies([ AddonStudies.withStudies([
@ -723,6 +735,7 @@ decorate_task(
// Test update fails when there is a version mismatch with metadata // Test update fails when there is a version mismatch with metadata
decorate_task( decorate_task(
withStudiesEnabled,
ensureAddonCleanup, ensureAddonCleanup,
withMockNormandyApi, withMockNormandyApi,
AddonStudies.withStudies([ AddonStudies.withStudies([
@ -799,6 +812,7 @@ decorate_task(
// Test that unenrolling fails if the study doesn't exist // Test that unenrolling fails if the study doesn't exist
decorate_task( decorate_task(
withStudiesEnabled,
ensureAddonCleanup, ensureAddonCleanup,
AddonStudies.withStudies(), AddonStudies.withStudies(),
async function unenrollNonexistent(studies) { async function unenrollNonexistent(studies) {
@ -813,6 +827,7 @@ decorate_task(
// Test that unenrolling an inactive experiment fails // Test that unenrolling an inactive experiment fails
decorate_task( decorate_task(
withStudiesEnabled,
ensureAddonCleanup, ensureAddonCleanup,
AddonStudies.withStudies([addonStudyFactory({ active: false })]), AddonStudies.withStudies([addonStudyFactory({ active: false })]),
withSendEventStub, withSendEventStub,
@ -829,6 +844,7 @@ decorate_task(
// test a successful unenrollment // test a successful unenrollment
const testStopId = "testStop@example.com"; const testStopId = "testStop@example.com";
decorate_task( decorate_task(
withStudiesEnabled,
ensureAddonCleanup, ensureAddonCleanup,
AddonStudies.withStudies([ AddonStudies.withStudies([
addonStudyFactory({ 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 // If the add-on for a study isn't installed, a warning should be logged, but the action is still successful
decorate_task( decorate_task(
withStudiesEnabled,
ensureAddonCleanup, ensureAddonCleanup,
AddonStudies.withStudies([ AddonStudies.withStudies([
addonStudyFactory({ addonStudyFactory({
@ -903,6 +920,7 @@ decorate_task(
// Test that the action respects the study opt-out // Test that the action respects the study opt-out
decorate_task( decorate_task(
withStudiesEnabled,
ensureAddonCleanup, ensureAddonCleanup,
withMockNormandyApi, withMockNormandyApi,
withSendEventStub, withSendEventStub,
@ -938,6 +956,7 @@ decorate_task(
// Test that the action does not enroll paused recipes // Test that the action does not enroll paused recipes
decorate_task( decorate_task(
withStudiesEnabled,
ensureAddonCleanup, ensureAddonCleanup,
withMockNormandyApi, withMockNormandyApi,
withSendEventStub, withSendEventStub,
@ -969,6 +988,7 @@ decorate_task(
// Test that the action updates paused recipes // Test that the action updates paused recipes
decorate_task( decorate_task(
withStudiesEnabled,
ensureAddonCleanup, ensureAddonCleanup,
withMockNormandyApi, withMockNormandyApi,
AddonStudies.withStudies([ AddonStudies.withStudies([
@ -1034,6 +1054,7 @@ decorate_task(
// Test that update method works for legacy studies with no hash // Test that update method works for legacy studies with no hash
decorate_task( decorate_task(
withStudiesEnabled,
ensureAddonCleanup, ensureAddonCleanup,
withMockNormandyApi, withMockNormandyApi,
AddonStudies.withStudies([ AddonStudies.withStudies([
@ -1117,6 +1138,7 @@ decorate_task(
// Test that enroll is not called if recipe is already enrolled // Test that enroll is not called if recipe is already enrolled
decorate_task( decorate_task(
withStudiesEnabled,
ensureAddonCleanup, ensureAddonCleanup,
AddonStudies.withStudies([addonStudyFactory()]), AddonStudies.withStudies([addonStudyFactory()]),
async function enrollTwiceFail([study]) { async function enrollTwiceFail([study]) {

Просмотреть файл

@ -74,6 +74,7 @@ function recipeFromStudy(study, overrides = {}) {
// Test that enroll is not called if recipe is already enrolled and update does nothing // Test that enroll is not called if recipe is already enrolled and update does nothing
// if recipe is unchanged // if recipe is unchanged
decorate_task( decorate_task(
withStudiesEnabled,
ensureAddonCleanup, ensureAddonCleanup,
withMockNormandyApi, withMockNormandyApi,
AddonStudies.withStudies([branchedAddonStudyFactory()]), 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 // Test that if the add-on fails to install, the database is cleaned up and the
// error is correctly reported. // error is correctly reported.
decorate_task( decorate_task(
withStudiesEnabled,
ensureAddonCleanup, ensureAddonCleanup,
withMockNormandyApi, withMockNormandyApi,
withSendEventStub, withSendEventStub,
@ -147,6 +149,7 @@ decorate_task(
// Ensure that the database is clean and error correctly reported if hash check fails // Ensure that the database is clean and error correctly reported if hash check fails
decorate_task( decorate_task(
withStudiesEnabled,
ensureAddonCleanup, ensureAddonCleanup,
withMockNormandyApi, withMockNormandyApi,
withSendEventStub, withSendEventStub,
@ -184,6 +187,7 @@ decorate_task(
// Ensure that the database is clean and error correctly reported if there is a metadata mismatch // Ensure that the database is clean and error correctly reported if there is a metadata mismatch
decorate_task( decorate_task(
withStudiesEnabled,
ensureAddonCleanup, ensureAddonCleanup,
withMockNormandyApi, withMockNormandyApi,
withSendEventStub, 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 // Test that in the case of a study add-on conflicting with a non-study add-on, the study does not enroll
decorate_task( decorate_task(
withStudiesEnabled,
ensureAddonCleanup, ensureAddonCleanup,
withMockNormandyApi, withMockNormandyApi,
withSendEventStub, withSendEventStub,
@ -271,6 +276,7 @@ decorate_task(
// Test a successful update // Test a successful update
decorate_task( decorate_task(
withStudiesEnabled,
ensureAddonCleanup, ensureAddonCleanup,
withMockNormandyApi, withMockNormandyApi,
AddonStudies.withStudies([ AddonStudies.withStudies([
@ -347,6 +353,7 @@ decorate_task(
// Test update fails when addon ID does not match // Test update fails when addon ID does not match
decorate_task( decorate_task(
withStudiesEnabled,
ensureAddonCleanup, ensureAddonCleanup,
withMockNormandyApi, withMockNormandyApi,
AddonStudies.withStudies([ AddonStudies.withStudies([
@ -400,6 +407,7 @@ decorate_task(
// Test update fails when original addon does not exist // Test update fails when original addon does not exist
decorate_task( decorate_task(
withStudiesEnabled,
ensureAddonCleanup, ensureAddonCleanup,
withMockNormandyApi, withMockNormandyApi,
AddonStudies.withStudies([ AddonStudies.withStudies([
@ -455,6 +463,7 @@ decorate_task(
// Test update fails when download fails // Test update fails when download fails
decorate_task( decorate_task(
withStudiesEnabled,
ensureAddonCleanup, ensureAddonCleanup,
withMockNormandyApi, withMockNormandyApi,
AddonStudies.withStudies([ AddonStudies.withStudies([
@ -509,6 +518,7 @@ decorate_task(
// Test update fails when hash check fails // Test update fails when hash check fails
decorate_task( decorate_task(
withStudiesEnabled,
ensureAddonCleanup, ensureAddonCleanup,
withMockNormandyApi, withMockNormandyApi,
AddonStudies.withStudies([ AddonStudies.withStudies([
@ -564,6 +574,7 @@ decorate_task(
// Test update fails on downgrade when study version is greater than extension version // Test update fails on downgrade when study version is greater than extension version
decorate_task( decorate_task(
withStudiesEnabled,
ensureAddonCleanup, ensureAddonCleanup,
withMockNormandyApi, withMockNormandyApi,
AddonStudies.withStudies([ AddonStudies.withStudies([
@ -618,6 +629,7 @@ decorate_task(
// Test update fails when there is a version mismatch with metadata // Test update fails when there is a version mismatch with metadata
decorate_task( decorate_task(
withStudiesEnabled,
ensureAddonCleanup, ensureAddonCleanup,
withMockNormandyApi, withMockNormandyApi,
AddonStudies.withStudies([ AddonStudies.withStudies([
@ -686,6 +698,7 @@ decorate_task(
// Test that unenrolling fails if the study doesn't exist // Test that unenrolling fails if the study doesn't exist
decorate_task( decorate_task(
withStudiesEnabled,
ensureAddonCleanup, ensureAddonCleanup,
AddonStudies.withStudies(), AddonStudies.withStudies(),
async function unenrollNonexistent(studies) { async function unenrollNonexistent(studies) {
@ -700,6 +713,7 @@ decorate_task(
// Test that unenrolling an inactive experiment fails // Test that unenrolling an inactive experiment fails
decorate_task( decorate_task(
withStudiesEnabled,
ensureAddonCleanup, ensureAddonCleanup,
AddonStudies.withStudies([branchedAddonStudyFactory({ active: false })]), AddonStudies.withStudies([branchedAddonStudyFactory({ active: false })]),
withSendEventStub, withSendEventStub,
@ -716,6 +730,7 @@ decorate_task(
// test a successful unenrollment // test a successful unenrollment
const testStopId = "testStop@example.com"; const testStopId = "testStop@example.com";
decorate_task( decorate_task(
withStudiesEnabled,
ensureAddonCleanup, ensureAddonCleanup,
AddonStudies.withStudies([ AddonStudies.withStudies([
branchedAddonStudyFactory({ 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 // If the add-on for a study isn't installed, a warning should be logged, but the action is still successful
decorate_task( decorate_task(
withStudiesEnabled,
ensureAddonCleanup, ensureAddonCleanup,
AddonStudies.withStudies([ AddonStudies.withStudies([
branchedAddonStudyFactory({ branchedAddonStudyFactory({
@ -801,6 +817,7 @@ decorate_task(
// Test that the action respects the study opt-out // Test that the action respects the study opt-out
decorate_task( decorate_task(
withStudiesEnabled,
ensureAddonCleanup, ensureAddonCleanup,
withMockNormandyApi, withMockNormandyApi,
withSendEventStub, withSendEventStub,
@ -836,6 +853,7 @@ decorate_task(
// Test that the action does not enroll paused recipes // Test that the action does not enroll paused recipes
decorate_task( decorate_task(
withStudiesEnabled,
ensureAddonCleanup, ensureAddonCleanup,
withMockNormandyApi, withMockNormandyApi,
withSendEventStub, withSendEventStub,
@ -867,6 +885,7 @@ decorate_task(
// Test that the action updates paused recipes // Test that the action updates paused recipes
decorate_task( decorate_task(
withStudiesEnabled,
ensureAddonCleanup, ensureAddonCleanup,
withMockNormandyApi, withMockNormandyApi,
AddonStudies.withStudies([ AddonStudies.withStudies([
@ -924,6 +943,7 @@ decorate_task(
// Test that unenroll called if the study is no longer sent from the server // Test that unenroll called if the study is no longer sent from the server
decorate_task( decorate_task(
withStudiesEnabled,
ensureAddonCleanup, ensureAddonCleanup,
AddonStudies.withStudies([branchedAddonStudyFactory()]), AddonStudies.withStudies([branchedAddonStudyFactory()]),
async function unenroll([study]) { async function unenroll([study]) {
@ -942,6 +962,7 @@ decorate_task(
// A test function that will be parameterized over the argument "branch" below. // 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. // Mocks the branch selector, and then tests that the user correctly gets enrolled in that branch.
const successEnrollBranchedTest = decorate( const successEnrollBranchedTest = decorate(
withStudiesEnabled,
ensureAddonCleanup, ensureAddonCleanup,
withMockNormandyApi, withMockNormandyApi,
withSendEventStub, withSendEventStub,
@ -1070,6 +1091,7 @@ add_task(() => successEnrollBranchedTest("b"));
// If the enrolled branch no longer exists, unenroll // If the enrolled branch no longer exists, unenroll
decorate_task( decorate_task(
withStudiesEnabled,
ensureAddonCleanup, ensureAddonCleanup,
withMockNormandyApi, withMockNormandyApi,
AddonStudies.withStudies([branchedAddonStudyFactory()]), AddonStudies.withStudies([branchedAddonStudyFactory()]),
@ -1142,6 +1164,7 @@ decorate_task(
// Test that branches without an add-on can be enrolled and unenrolled succesfully. // Test that branches without an add-on can be enrolled and unenrolled succesfully.
decorate_task( decorate_task(
withStudiesEnabled,
ensureAddonCleanup, ensureAddonCleanup,
withMockNormandyApi, withMockNormandyApi,
withSendEventStub, withSendEventStub,

Просмотреть файл

@ -63,6 +63,7 @@ function preferenceExperimentFactory(args) {
} }
decorate_task( decorate_task(
withStudiesEnabled,
withStub(Uptake, "reportRecipe"), withStub(Uptake, "reportRecipe"),
async function run_without_errors(reportRecipe) { async function run_without_errors(reportRecipe) {
const action = new PreferenceExperimentAction(); const action = new PreferenceExperimentAction();
@ -76,6 +77,7 @@ decorate_task(
); );
decorate_task( decorate_task(
withStudiesEnabled,
withStub(Uptake, "reportRecipe"), withStub(Uptake, "reportRecipe"),
withStub(Uptake, "reportAction"), withStub(Uptake, "reportAction"),
withPrefEnv({ set: [["app.shield.optoutstudies.enabled", false]] }), withPrefEnv({ set: [["app.shield.optoutstudies.enabled", false]] }),
@ -86,8 +88,9 @@ decorate_task(
const recipe = preferenceExperimentFactory(); const recipe = preferenceExperimentFactory();
await action.runRecipe(recipe); await action.runRecipe(recipe);
Assert.deepEqual(action.log.info.args, [ Assert.ok(action.log.debug.args.length === 1);
["User has opted out of preference experiments. Disabling this action."], Assert.deepEqual(action.log.debug.args[0], [
"User has opted-out of opt-out experiments, disabling action.",
]); ]);
Assert.deepEqual(action.log.warn.args, [ Assert.deepEqual(action.log.warn.args, [
[ [
@ -97,10 +100,9 @@ decorate_task(
]); ]);
await action.finalize(); await action.finalize();
Assert.deepEqual(action.log.debug.args, [ 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.", "Skipping post-execution hook for PreferenceExperimentAction because it is disabled.",
],
]); ]);
Assert.deepEqual(reportRecipe.args, [ Assert.deepEqual(reportRecipe.args, [
[recipe, Uptake.RECIPE_ACTION_DISABLED], [recipe, Uptake.RECIPE_ACTION_DISABLED],
@ -110,6 +112,7 @@ decorate_task(
); );
decorate_task( decorate_task(
withStudiesEnabled,
withStub(PreferenceExperiments, "start"), withStub(PreferenceExperiments, "start"),
PreferenceExperiments.withMockExperiments([]), PreferenceExperiments.withMockExperiments([]),
async function enroll_user_if_never_been_in_experiment(startStub) { async function enroll_user_if_never_been_in_experiment(startStub) {
@ -172,6 +175,7 @@ decorate_task(
); );
decorate_task( decorate_task(
withStudiesEnabled,
withStub(PreferenceExperiments, "markLastSeen"), withStub(PreferenceExperiments, "markLastSeen"),
PreferenceExperiments.withMockExperiments([{ name: "test", expired: false }]), PreferenceExperiments.withMockExperiments([{ name: "test", expired: false }]),
async function markSeen_if_experiment_active(markLastSeenStub) { async function markSeen_if_experiment_active(markLastSeenStub) {
@ -188,6 +192,7 @@ decorate_task(
); );
decorate_task( decorate_task(
withStudiesEnabled,
withStub(PreferenceExperiments, "markLastSeen"), withStub(PreferenceExperiments, "markLastSeen"),
PreferenceExperiments.withMockExperiments([{ name: "test", expired: true }]), PreferenceExperiments.withMockExperiments([{ name: "test", expired: true }]),
async function dont_markSeen_if_experiment_expired(markLastSeenStub) { async function dont_markSeen_if_experiment_expired(markLastSeenStub) {
@ -204,6 +209,7 @@ decorate_task(
); );
decorate_task( decorate_task(
withStudiesEnabled,
withStub(PreferenceExperiments, "start"), withStub(PreferenceExperiments, "start"),
async function do_nothing_if_enrollment_paused(startStub) { async function do_nothing_if_enrollment_paused(startStub) {
const action = new PreferenceExperimentAction(); const action = new PreferenceExperimentAction();
@ -219,6 +225,7 @@ decorate_task(
); );
decorate_task( decorate_task(
withStudiesEnabled,
withStub(PreferenceExperiments, "stop"), withStub(PreferenceExperiments, "stop"),
PreferenceExperiments.withMockExperiments([ PreferenceExperiments.withMockExperiments([
{ name: "seen", expired: false, actionName: "PreferenceExperimentAction" }, { name: "seen", expired: false, actionName: "PreferenceExperimentAction" },
@ -244,6 +251,7 @@ decorate_task(
); );
decorate_task( decorate_task(
withStudiesEnabled,
withStub(PreferenceExperiments, "stop"), withStub(PreferenceExperiments, "stop"),
PreferenceExperiments.withMockExperiments([ PreferenceExperiments.withMockExperiments([
{ {
@ -275,6 +283,7 @@ decorate_task(
); );
decorate_task( decorate_task(
withStudiesEnabled,
withStub(PreferenceExperiments, "start"), withStub(PreferenceExperiments, "start"),
withStub(Uptake, "reportRecipe"), withStub(Uptake, "reportRecipe"),
PreferenceExperiments.withMockExperiments([ PreferenceExperiments.withMockExperiments([
@ -317,6 +326,7 @@ decorate_task(
); );
decorate_task( decorate_task(
withStudiesEnabled,
withStub(PreferenceExperiments, "start"), withStub(PreferenceExperiments, "start"),
PreferenceExperiments.withMockExperiments([]), PreferenceExperiments.withMockExperiments([]),
async function experimentType_with_isHighPopulation_false(startStub) { async function experimentType_with_isHighPopulation_false(startStub) {
@ -333,6 +343,7 @@ decorate_task(
); );
decorate_task( decorate_task(
withStudiesEnabled,
withStub(PreferenceExperiments, "start"), withStub(PreferenceExperiments, "start"),
PreferenceExperiments.withMockExperiments([]), PreferenceExperiments.withMockExperiments([]),
async function experimentType_with_isHighPopulation_true(startStub) { async function experimentType_with_isHighPopulation_true(startStub) {
@ -349,6 +360,7 @@ decorate_task(
); );
decorate_task( decorate_task(
withStudiesEnabled,
withStub(Sampling, "ratioSample"), withStub(Sampling, "ratioSample"),
async function chooseBranch_uses_ratioSample(ratioSampleStub) { async function chooseBranch_uses_ratioSample(ratioSampleStub) {
ratioSampleStub.returns(Promise.resolve(1)); ratioSampleStub.returns(Promise.resolve(1));
@ -388,6 +400,7 @@ decorate_task(
); );
decorate_task( decorate_task(
withStudiesEnabled,
withMockPreferences, withMockPreferences,
PreferenceExperiments.withMockExperiments([]), PreferenceExperiments.withMockExperiments([]),
async function integration_test_enroll_and_unenroll(prefs) { async function integration_test_enroll_and_unenroll(prefs) {

Просмотреть файл

@ -42,6 +42,7 @@ function preferenceExperimentFactory(args) {
} }
decorate_task( decorate_task(
withStudiesEnabled,
withStub(PreferenceExperimentAction.prototype, "_run"), withStub(PreferenceExperimentAction.prototype, "_run"),
PreferenceExperiments.withMockExperiments([]), PreferenceExperiments.withMockExperiments([]),
async function enroll_user_if_never_been_in_experiment(runStub) { async function enroll_user_if_never_been_in_experiment(runStub) {

Просмотреть файл

@ -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 * 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 * to the preceding function as the argument; the result of this is passed to

Просмотреть файл

@ -187,6 +187,13 @@ this.AppConstants = Object.freeze({
false, false,
#endif #endif
MOZ_NORMANDY:
#ifdef MOZ_NORMANDY
true,
#else
false,
#endif
MOZ_MAINTENANCE_SERVICE: MOZ_MAINTENANCE_SERVICE:
#ifdef MOZ_MAINTENANCE_SERVICE #ifdef MOZ_MAINTENANCE_SERVICE
true, true,

Просмотреть файл

@ -617,6 +617,11 @@ project_flag('MOZ_SERVICES_HEALTHREPORT',
set_for_old_configure=True, set_for_old_configure=True,
set_as_define=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', project_flag('MOZ_SERVICES_SYNC',
help='Build Sync Services if required') help='Build Sync Services if required')

Просмотреть файл

@ -34,6 +34,7 @@ const kIfdefStateForLinting = {
MOZ_DATA_REPORTING: true, MOZ_DATA_REPORTING: true,
MOZ_TELEMETRY_REPORTING: true, MOZ_TELEMETRY_REPORTING: true,
MOZ_CRASHREPORTER: true, MOZ_CRASHREPORTER: true,
MOZ_NORMANDY: true,
MOZ_MAINTENANCE_SERVICE: true, MOZ_MAINTENANCE_SERVICE: true,
HAVE_SHELL_SERVICE: true, HAVE_SHELL_SERVICE: true,
MENUBAR_CAN_AUTOHIDE: true, MENUBAR_CAN_AUTOHIDE: true,