Bug 1436113 - Part 1: Move browser/extensions/shield-recipe-client to toolkit/components/normandy r=Gijs
authorMike Cooper <mcooper@mozilla.com>
Wed, 28 Feb 2018 14:55:47 -0800
changeset 762817 c174737a9d9b8654afeded5b810b6a3379279a1f
parent 762816 8fb0418948834f071dd99f6b8256b746b4a8adce
child 762818 94f5c92fc711a1e1e76ea54d52482cbbd2db0401
push id101258
push userbmo:mcooper@mozilla.com
push dateFri, 02 Mar 2018 23:48:12 +0000
reviewersGijs
bugs1436113
milestone60.0a1
Bug 1436113 - Part 1: Move browser/extensions/shield-recipe-client to toolkit/components/normandy r=Gijs MozReview-Commit-ID: LidgzhI4Z7h
browser/base/content/test/static/browser_all_files_referenced.js
browser/components/enterprisepolicies/tests/browser/browser_policy_disable_shield.js
browser/components/nsBrowserGlue.js
browser/extensions/moz.build
browser/extensions/shield-recipe-client/bootstrap.js
browser/extensions/shield-recipe-client/content/AboutPages.jsm
browser/extensions/shield-recipe-client/content/about-studies/about-studies.css
browser/extensions/shield-recipe-client/content/about-studies/about-studies.html
browser/extensions/shield-recipe-client/content/about-studies/about-studies.js
browser/extensions/shield-recipe-client/content/about-studies/common.js
browser/extensions/shield-recipe-client/content/about-studies/img/shield-logo.png
browser/extensions/shield-recipe-client/content/about-studies/shield-studies.js
browser/extensions/shield-recipe-client/content/shield-content-frame.js
browser/extensions/shield-recipe-client/content/shield-content-process.js
browser/extensions/shield-recipe-client/docs/data-collection.rst
browser/extensions/shield-recipe-client/docs/index.rst
browser/extensions/shield-recipe-client/install.rdf.in
browser/extensions/shield-recipe-client/jar.mn
browser/extensions/shield-recipe-client/lib/ActionSandboxManager.jsm
browser/extensions/shield-recipe-client/lib/AddonStudies.jsm
browser/extensions/shield-recipe-client/lib/Addons.jsm
browser/extensions/shield-recipe-client/lib/CleanupManager.jsm
browser/extensions/shield-recipe-client/lib/ClientEnvironment.jsm
browser/extensions/shield-recipe-client/lib/EventEmitter.jsm
browser/extensions/shield-recipe-client/lib/FilterExpressions.jsm
browser/extensions/shield-recipe-client/lib/Heartbeat.jsm
browser/extensions/shield-recipe-client/lib/LogManager.jsm
browser/extensions/shield-recipe-client/lib/NormandyApi.jsm
browser/extensions/shield-recipe-client/lib/NormandyDriver.jsm
browser/extensions/shield-recipe-client/lib/PreferenceExperiments.jsm
browser/extensions/shield-recipe-client/lib/PreferenceFilters.jsm
browser/extensions/shield-recipe-client/lib/RecipeRunner.jsm
browser/extensions/shield-recipe-client/lib/Sampling.jsm
browser/extensions/shield-recipe-client/lib/SandboxManager.jsm
browser/extensions/shield-recipe-client/lib/ShieldPreferences.jsm
browser/extensions/shield-recipe-client/lib/ShieldRecipeClient.jsm
browser/extensions/shield-recipe-client/lib/Storage.jsm
browser/extensions/shield-recipe-client/lib/TelemetryEvents.jsm
browser/extensions/shield-recipe-client/lib/Uptake.jsm
browser/extensions/shield-recipe-client/lib/Utils.jsm
browser/extensions/shield-recipe-client/moz.build
browser/extensions/shield-recipe-client/skin/osx/Heartbeat.css
browser/extensions/shield-recipe-client/skin/shared/Heartbeat.css
browser/extensions/shield-recipe-client/skin/shared/heartbeat-icon.svg
browser/extensions/shield-recipe-client/skin/shared/heartbeat-star-lit.svg
browser/extensions/shield-recipe-client/skin/shared/heartbeat-star-off.svg
browser/extensions/shield-recipe-client/test/.eslintrc.js
browser/extensions/shield-recipe-client/test/browser/.eslintrc.js
browser/extensions/shield-recipe-client/test/browser/action_server.sjs
browser/extensions/shield-recipe-client/test/browser/browser.ini
browser/extensions/shield-recipe-client/test/browser/browser_ActionSandboxManager.js
browser/extensions/shield-recipe-client/test/browser/browser_AddonStudies.js
browser/extensions/shield-recipe-client/test/browser/browser_Addons.js
browser/extensions/shield-recipe-client/test/browser/browser_CleanupManager.js
browser/extensions/shield-recipe-client/test/browser/browser_ClientEnvironment.js
browser/extensions/shield-recipe-client/test/browser/browser_EventEmitter.js
browser/extensions/shield-recipe-client/test/browser/browser_FilterExpressions.js
browser/extensions/shield-recipe-client/test/browser/browser_Heartbeat.js
browser/extensions/shield-recipe-client/test/browser/browser_LogManager.js
browser/extensions/shield-recipe-client/test/browser/browser_NormandyDriver.js
browser/extensions/shield-recipe-client/test/browser/browser_PreferenceExperiments.js
browser/extensions/shield-recipe-client/test/browser/browser_RecipeRunner.js
browser/extensions/shield-recipe-client/test/browser/browser_ShieldPreferences.js
browser/extensions/shield-recipe-client/test/browser/browser_ShieldRecipeClient.js
browser/extensions/shield-recipe-client/test/browser/browser_Storage.js
browser/extensions/shield-recipe-client/test/browser/browser_about_preferences.js
browser/extensions/shield-recipe-client/test/browser/browser_about_studies.js
browser/extensions/shield-recipe-client/test/browser/browser_bootstrap.js
browser/extensions/shield-recipe-client/test/browser/fixtures/addon-fixture/manifest.json
browser/extensions/shield-recipe-client/test/browser/fixtures/normandy.xpi
browser/extensions/shield-recipe-client/test/browser/head.js
browser/extensions/shield-recipe-client/test/unit/.eslintrc.js
browser/extensions/shield-recipe-client/test/unit/echo_server.sjs
browser/extensions/shield-recipe-client/test/unit/head_xpc.js
browser/extensions/shield-recipe-client/test/unit/invalid_recipe_signature_api/api/v1/index.json
browser/extensions/shield-recipe-client/test/unit/invalid_recipe_signature_api/api/v1/recipe/signed/index.json
browser/extensions/shield-recipe-client/test/unit/invalid_recipe_signature_api/normandy.content-signature.mozilla.org-20210705.dev.chain
browser/extensions/shield-recipe-client/test/unit/mock_api/api/v1/action/console-log/implementation/sha384-RGx3rydrSq53UfmW9kFcK0mQYra67XIvZvr4MhmAe--ljiiMQOtgM7Cmca48um3v
browser/extensions/shield-recipe-client/test/unit/mock_api/api/v1/action/console-log/index.json
browser/extensions/shield-recipe-client/test/unit/mock_api/api/v1/action/index.json
browser/extensions/shield-recipe-client/test/unit/mock_api/api/v1/action/opt-out-study/implementation/sha384-HM_avYcD00o27ufwU1V7PIBtiuMAXML6MMwlYrDEqDX-XzGVuOfL52RCM680JExN
browser/extensions/shield-recipe-client/test/unit/mock_api/api/v1/action/opt-out-study/index.json
browser/extensions/shield-recipe-client/test/unit/mock_api/api/v1/action/preference-experiment/implementation/sha384-KQgG38GQ7KZAb2VIB48ANQO6nBcxZoLm2ORzUviRT5nAvSywyPjZ5cJIElw6iXIt
browser/extensions/shield-recipe-client/test/unit/mock_api/api/v1/action/preference-experiment/index.json
browser/extensions/shield-recipe-client/test/unit/mock_api/api/v1/action/show-heartbeat/implementation/sha384-dEGiyKPEln8Ns5cQHzGpMIGdirSAAX0X-Kwlu-U3sJ05yNbO-ANij_a6c5SyL7G4
browser/extensions/shield-recipe-client/test/unit/mock_api/api/v1/action/show-heartbeat/index.json
browser/extensions/shield-recipe-client/test/unit/mock_api/api/v1/action/signed/index.json
browser/extensions/shield-recipe-client/test/unit/mock_api/api/v1/classify_client/index.json
browser/extensions/shield-recipe-client/test/unit/mock_api/api/v1/index.json
browser/extensions/shield-recipe-client/test/unit/mock_api/api/v1/recipe/index.json
browser/extensions/shield-recipe-client/test/unit/mock_api/api/v1/recipe/signed/index.json
browser/extensions/shield-recipe-client/test/unit/mock_api/normandy.content-signature.mozilla.org-20210705.dev.chain
browser/extensions/shield-recipe-client/test/unit/query_server.sjs
browser/extensions/shield-recipe-client/test/unit/test_NormandyApi.js
browser/extensions/shield-recipe-client/test/unit/test_Sampling.js
browser/extensions/shield-recipe-client/test/unit/test_SandboxManager.js
browser/extensions/shield-recipe-client/test/unit/test_Utils.js
browser/extensions/shield-recipe-client/test/unit/utils.js
browser/extensions/shield-recipe-client/test/unit/xpcshell.ini
browser/extensions/shield-recipe-client/vendor/LICENSE_THIRDPARTY
browser/extensions/shield-recipe-client/vendor/PropTypes.js
browser/extensions/shield-recipe-client/vendor/React.js
browser/extensions/shield-recipe-client/vendor/ReactDOM.js
browser/extensions/shield-recipe-client/vendor/classnames.js
browser/extensions/shield-recipe-client/vendor/mozjexl.js
toolkit/components/moz.build
toolkit/components/normandy/Normandy.jsm
toolkit/components/normandy/content/AboutPages.jsm
toolkit/components/normandy/content/about-studies/about-studies.css
toolkit/components/normandy/content/about-studies/about-studies.html
toolkit/components/normandy/content/about-studies/about-studies.js
toolkit/components/normandy/content/about-studies/common.js
toolkit/components/normandy/content/about-studies/img/shield-logo.png
toolkit/components/normandy/content/about-studies/shield-studies.js
toolkit/components/normandy/content/shield-content-frame.js
toolkit/components/normandy/content/shield-content-process.js
toolkit/components/normandy/docs/data-collection.rst
toolkit/components/normandy/docs/index.rst
toolkit/components/normandy/jar.mn
toolkit/components/normandy/lib/ActionSandboxManager.jsm
toolkit/components/normandy/lib/AddonStudies.jsm
toolkit/components/normandy/lib/Addons.jsm
toolkit/components/normandy/lib/CleanupManager.jsm
toolkit/components/normandy/lib/ClientEnvironment.jsm
toolkit/components/normandy/lib/EventEmitter.jsm
toolkit/components/normandy/lib/FilterExpressions.jsm
toolkit/components/normandy/lib/Heartbeat.jsm
toolkit/components/normandy/lib/LogManager.jsm
toolkit/components/normandy/lib/NormandyApi.jsm
toolkit/components/normandy/lib/NormandyDriver.jsm
toolkit/components/normandy/lib/PreferenceExperiments.jsm
toolkit/components/normandy/lib/PreferenceFilters.jsm
toolkit/components/normandy/lib/RecipeRunner.jsm
toolkit/components/normandy/lib/Sampling.jsm
toolkit/components/normandy/lib/SandboxManager.jsm
toolkit/components/normandy/lib/ShieldPreferences.jsm
toolkit/components/normandy/lib/Storage.jsm
toolkit/components/normandy/lib/TelemetryEvents.jsm
toolkit/components/normandy/lib/Uptake.jsm
toolkit/components/normandy/lib/Utils.jsm
toolkit/components/normandy/moz.build
toolkit/components/normandy/skin/osx/Heartbeat.css
toolkit/components/normandy/skin/shared/Heartbeat.css
toolkit/components/normandy/skin/shared/heartbeat-icon.svg
toolkit/components/normandy/skin/shared/heartbeat-star-lit.svg
toolkit/components/normandy/skin/shared/heartbeat-star-off.svg
toolkit/components/normandy/test/.eslintrc.js
toolkit/components/normandy/test/browser/.eslintrc.js
toolkit/components/normandy/test/browser/action_server.sjs
toolkit/components/normandy/test/browser/browser.ini
toolkit/components/normandy/test/browser/browser_ActionSandboxManager.js
toolkit/components/normandy/test/browser/browser_AddonStudies.js
toolkit/components/normandy/test/browser/browser_Addons.js
toolkit/components/normandy/test/browser/browser_CleanupManager.js
toolkit/components/normandy/test/browser/browser_ClientEnvironment.js
toolkit/components/normandy/test/browser/browser_EventEmitter.js
toolkit/components/normandy/test/browser/browser_FilterExpressions.js
toolkit/components/normandy/test/browser/browser_Heartbeat.js
toolkit/components/normandy/test/browser/browser_LogManager.js
toolkit/components/normandy/test/browser/browser_Normandy.js
toolkit/components/normandy/test/browser/browser_NormandyDriver.js
toolkit/components/normandy/test/browser/browser_PreferenceExperiments.js
toolkit/components/normandy/test/browser/browser_RecipeRunner.js
toolkit/components/normandy/test/browser/browser_ShieldPreferences.js
toolkit/components/normandy/test/browser/browser_ShieldRecipeClient.js
toolkit/components/normandy/test/browser/browser_Storage.js
toolkit/components/normandy/test/browser/browser_about_preferences.js
toolkit/components/normandy/test/browser/browser_about_studies.js
toolkit/components/normandy/test/browser/fixtures/addon-fixture/manifest.json
toolkit/components/normandy/test/browser/fixtures/normandy.xpi
toolkit/components/normandy/test/browser/head.js
toolkit/components/normandy/test/unit/.eslintrc.js
toolkit/components/normandy/test/unit/echo_server.sjs
toolkit/components/normandy/test/unit/head_xpc.js
toolkit/components/normandy/test/unit/invalid_recipe_signature_api/api/v1/index.json
toolkit/components/normandy/test/unit/invalid_recipe_signature_api/api/v1/recipe/signed/index.json
toolkit/components/normandy/test/unit/invalid_recipe_signature_api/normandy.content-signature.mozilla.org-20210705.dev.chain
toolkit/components/normandy/test/unit/mock_api/api/v1/action/console-log/implementation/sha384-RGx3rydrSq53UfmW9kFcK0mQYra67XIvZvr4MhmAe--ljiiMQOtgM7Cmca48um3v
toolkit/components/normandy/test/unit/mock_api/api/v1/action/console-log/index.json
toolkit/components/normandy/test/unit/mock_api/api/v1/action/index.json
toolkit/components/normandy/test/unit/mock_api/api/v1/action/opt-out-study/implementation/sha384-HM_avYcD00o27ufwU1V7PIBtiuMAXML6MMwlYrDEqDX-XzGVuOfL52RCM680JExN
toolkit/components/normandy/test/unit/mock_api/api/v1/action/opt-out-study/index.json
toolkit/components/normandy/test/unit/mock_api/api/v1/action/preference-experiment/implementation/sha384-KQgG38GQ7KZAb2VIB48ANQO6nBcxZoLm2ORzUviRT5nAvSywyPjZ5cJIElw6iXIt
toolkit/components/normandy/test/unit/mock_api/api/v1/action/preference-experiment/index.json
toolkit/components/normandy/test/unit/mock_api/api/v1/action/show-heartbeat/implementation/sha384-dEGiyKPEln8Ns5cQHzGpMIGdirSAAX0X-Kwlu-U3sJ05yNbO-ANij_a6c5SyL7G4
toolkit/components/normandy/test/unit/mock_api/api/v1/action/show-heartbeat/index.json
toolkit/components/normandy/test/unit/mock_api/api/v1/action/signed/index.json
toolkit/components/normandy/test/unit/mock_api/api/v1/classify_client/index.json
toolkit/components/normandy/test/unit/mock_api/api/v1/index.json
toolkit/components/normandy/test/unit/mock_api/api/v1/recipe/index.json
toolkit/components/normandy/test/unit/mock_api/api/v1/recipe/signed/index.json
toolkit/components/normandy/test/unit/mock_api/normandy.content-signature.mozilla.org-20210705.dev.chain
toolkit/components/normandy/test/unit/query_server.sjs
toolkit/components/normandy/test/unit/test_NormandyApi.js
toolkit/components/normandy/test/unit/test_Sampling.js
toolkit/components/normandy/test/unit/test_SandboxManager.js
toolkit/components/normandy/test/unit/test_Utils.js
toolkit/components/normandy/test/unit/utils.js
toolkit/components/normandy/test/unit/xpcshell.ini
toolkit/components/normandy/vendor/LICENSE_THIRDPARTY
toolkit/components/normandy/vendor/PropTypes.js
toolkit/components/normandy/vendor/React.js
toolkit/components/normandy/vendor/ReactDOM.js
toolkit/components/normandy/vendor/classnames.js
toolkit/components/normandy/vendor/mozjexl.js
--- a/browser/base/content/test/static/browser_all_files_referenced.js
+++ b/browser/base/content/test/static/browser_all_files_referenced.js
@@ -13,23 +13,21 @@ var isDevtools = SimpleTest.harnessParam
 var gExceptionPaths = [
   "chrome://browser/content/defaultthemes/",
   "chrome://browser/locale/searchplugins/",
   "resource://app/defaults/blocklists/",
   "resource://app/defaults/pinning/",
   "resource://app/defaults/preferences/",
   "resource://gre/modules/commonjs/",
   "resource://gre/defaults/pref/",
-  "resource://shield-recipe-client/node_modules/jexl/lib/",
 
   // These resources are referenced using relative paths from html files.
   "resource://payments/",
-
-  // https://github.com/mozilla/normandy/issues/577
-  "resource://shield-recipe-client/test/",
+  "resource://normandy-content/shield-content-frame.js",
+  "resource://normandy-content/shield-content-process.js",
 
   // https://github.com/mozilla/activity-stream/issues/3053
   "resource://activity-stream/data/content/tippytop/images/",
   // https://github.com/mozilla/activity-stream/issues/3758
   "resource://activity-stream/prerendered/",
 
   // browser/extensions/pdfjs/content/build/pdf.js#1999
   "resource://pdf.js/web/images/",
@@ -119,20 +117,16 @@ var whitelist = [
   {file: "resource://gre/chrome/en-US/locale/en-US/global-platform/win/intl.properties",
    platforms: ["linux", "macosx"]},
   {file: "resource://gre/chrome/en-US/locale/en-US/global-platform/win/platformKeys.properties",
    platforms: ["linux", "macosx"]},
 
   // browser/extensions/pdfjs/content/web/viewer.js#7450
   {file: "resource://pdf.js/web/debugger.js"},
 
-  // These are used in content processes. They are actually referenced.
-  {file: "resource://shield-recipe-client-content/shield-content-frame.js"},
-  {file: "resource://shield-recipe-client-content/shield-content-process.js"},
-
   // Starting from here, files in the whitelist are bugs that need fixing.
   // Bug 1339424 (wontfix?)
   {file: "chrome://browser/locale/taskbar.properties",
    platforms: ["linux", "macosx"]},
   // Bug 1356031 (only used by devtools)
   {file: "chrome://global/skin/icons/error-16.png"},
   // Bug 1348362
   {file: "chrome://global/skin/icons/warning-64.png", platforms: ["linux"]},
--- a/browser/components/enterprisepolicies/tests/browser/browser_policy_disable_shield.js
+++ b/browser/components/enterprisepolicies/tests/browser/browser_policy_disable_shield.js
@@ -1,15 +1,15 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 add_task(async function test_policy_disable_shield() {
-  const { RecipeRunner } = ChromeUtils.import("resource://shield-recipe-client/lib/RecipeRunner.jsm", {});
+  const { RecipeRunner } = ChromeUtils.import("resource://normandy/lib/RecipeRunner.jsm", {});
 
   await SpecialPowers.pushPrefEnv({ set: [["extensions.shield-recipe-client.api_url",
                                             "https://localhost/selfsupport-dummy/"],
                                           ["datareporting.healthreport.uploadEnabled",
                                             true]]});
 
   ok(RecipeRunner, "RecipeRunner exists");
   RecipeRunner.checkPrefs();
--- a/browser/components/nsBrowserGlue.js
+++ b/browser/components/nsBrowserGlue.js
@@ -105,16 +105,17 @@ XPCOMUtils.defineLazyModuleGetters(this,
   Integration: "resource://gre/modules/Integration.jsm",
   L10nRegistry: "resource://gre/modules/L10nRegistry.jsm",
   LanguagePrompt: "resource://gre/modules/LanguagePrompt.jsm",
   LightweightThemeManager: "resource://gre/modules/LightweightThemeManager.jsm",
   LoginHelper: "resource://gre/modules/LoginHelper.jsm",
   LoginManagerParent: "resource://gre/modules/LoginManagerParent.jsm",
   NetUtil: "resource://gre/modules/NetUtil.jsm",
   NewTabUtils: "resource://gre/modules/NewTabUtils.jsm",
+  Normandy: "resource://normandy/Normandy.jsm",
   OS: "resource://gre/modules/osfile.jsm",
   PageActions: "resource:///modules/PageActions.jsm",
   PageThumbs: "resource://gre/modules/PageThumbs.jsm",
   PdfJs: "resource://pdf.js/PdfJs.jsm",
   PermissionUI: "resource:///modules/PermissionUI.jsm",
   PingCentre: "resource:///modules/PingCentre.jsm",
   PlacesBackups: "resource://gre/modules/PlacesBackups.jsm",
   PlacesUtils: "resource://gre/modules/PlacesUtils.jsm",
@@ -705,16 +706,17 @@ BrowserGlue.prototype = {
       name: gBrowserBundle.GetStringFromName("darkTheme.name"),
       description: gBrowserBundle.GetStringFromName("darkTheme.description"),
       iconURL: "resource:///chrome/browser/content/browser/defaultthemes/dark.icon.svg",
       textcolor: "white",
       accentcolor: "black",
       author: vendorShortName,
     });
 
+    Normandy.init();
 
     // Initialize the default l10n resource sources for L10nRegistry.
     let locales = Services.locale.getPackagedLocales();
     const appSource = new FileSource("app", locales, "resource://app/localization/{locale}/");
     L10nRegistry.registerSource(appSource);
 
     Services.obs.notifyObservers(null, "browser-ui-startup-complete");
   },
@@ -1051,16 +1053,18 @@ BrowserGlue.prototype = {
     NewTabUtils.uninit();
     AutoCompletePopup.uninit();
     DateTimePickerHelper.uninit();
 
     // Browser errors are only collected on Nightly
     if (AppConstants.NIGHTLY_BUILD && AppConstants.MOZ_DATA_REPORTING) {
       this.browserErrorReporter.uninit();
     }
+
+    Normandy.uninit();
   },
 
   // All initial windows have opened.
   _onWindowsRestored: function BG__onWindowsRestored() {
     if (this._windowsWereRestored) {
       return;
     }
     this._windowsWereRestored = true;
--- a/browser/extensions/moz.build
+++ b/browser/extensions/moz.build
@@ -8,17 +8,16 @@ DIRS += [
     'activity-stream',
     'aushelper',
     'followonsearch',
     'formautofill',
     'onboarding',
     'pdfjs',
     'pocket',
     'screenshots',
-    'shield-recipe-client',
     'webcompat',
 ]
 
 # Only include the following system add-ons if building Aurora or Nightly
 if not CONFIG['RELEASE_OR_BETA']:
     DIRS += [
         'presentation',
     ]
deleted file mode 100644
--- a/browser/extensions/shield-recipe-client/bootstrap.js
+++ /dev/null
@@ -1,227 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-"use strict";
-
-ChromeUtils.import("resource://gre/modules/AppConstants.jsm");
-ChromeUtils.import("resource://gre/modules/Log.jsm");
-ChromeUtils.import("resource://gre/modules/Services.jsm");
-ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
-
-ChromeUtils.defineModuleGetter(this, "LogManager",
-  "resource://shield-recipe-client/lib/LogManager.jsm");
-ChromeUtils.defineModuleGetter(this, "ShieldRecipeClient",
-  "resource://shield-recipe-client/lib/ShieldRecipeClient.jsm");
-ChromeUtils.defineModuleGetter(this, "PreferenceExperiments",
-  "resource://shield-recipe-client/lib/PreferenceExperiments.jsm");
-
-// Act as both a normal bootstrap.js and a JS module so that we can test
-// startup methods without having to install/uninstall the add-on.
-var EXPORTED_SYMBOLS = ["Bootstrap"];
-
-const REASON_APP_STARTUP = 1;
-const UI_AVAILABLE_NOTIFICATION = "sessionstore-windows-restored";
-const STARTUP_EXPERIMENT_PREFS_BRANCH = "extensions.shield-recipe-client.startupExperimentPrefs.";
-const PREF_LOGGING_LEVEL = "extensions.shield-recipe-client.logging.level";
-const BOOTSTRAP_LOGGER_NAME = "extensions.shield-recipe-client.bootstrap";
-const DEFAULT_PREFS = {
-  "extensions.shield-recipe-client.api_url": "https://normandy.cdn.mozilla.net/api/v1",
-  "extensions.shield-recipe-client.dev_mode": false,
-  "extensions.shield-recipe-client.enabled": true,
-  "extensions.shield-recipe-client.startup_delay_seconds": 300,
-  "extensions.shield-recipe-client.logging.level": Log.Level.Warn,
-  "extensions.shield-recipe-client.user_id": "",
-  "extensions.shield-recipe-client.run_interval_seconds": 86400, // 24 hours
-  "extensions.shield-recipe-client.first_run": true,
-  "extensions.shield-recipe-client.shieldLearnMoreUrl": (
-    "https://support.mozilla.org/1/firefox/%VERSION%/%OS%/%LOCALE%/shield"
-  ),
-  "app.shield.optoutstudies.enabled": AppConstants.MOZ_DATA_REPORTING,
-};
-
-// 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);
-
-let studyPrefsChanged = {};
-
-var Bootstrap = {
-  initShieldPrefs(defaultPrefs) {
-    const prefBranch = Services.prefs.getDefaultBranch("");
-    for (const [name, value] of Object.entries(defaultPrefs)) {
-      switch (typeof value) {
-        case "string":
-          prefBranch.setCharPref(name, value);
-          break;
-        case "number":
-          prefBranch.setIntPref(name, value);
-          break;
-        case "boolean":
-          prefBranch.setBoolPref(name, value);
-          break;
-        default:
-          throw new Error(`Invalid default preference type ${typeof value}`);
-      }
-    }
-  },
-
-  initExperimentPrefs() {
-    studyPrefsChanged = {};
-    const defaultBranch = Services.prefs.getDefaultBranch("");
-    const experimentBranch = Services.prefs.getBranch(STARTUP_EXPERIMENT_PREFS_BRANCH);
-
-    for (const prefName of experimentBranch.getChildList("")) {
-      const experimentPrefType = experimentBranch.getPrefType(prefName);
-      const realPrefType = defaultBranch.getPrefType(prefName);
-
-      if (realPrefType !== Services.prefs.PREF_INVALID && realPrefType !== experimentPrefType) {
-        log.error(`Error setting startup pref ${prefName}; pref type does not match.`);
-        continue;
-      }
-
-      // record the value of the default branch before setting it
-      try {
-        switch (realPrefType) {
-          case Services.prefs.PREF_STRING:
-            studyPrefsChanged[prefName] = defaultBranch.getCharPref(prefName);
-            break;
-
-          case Services.prefs.PREF_INT:
-            studyPrefsChanged[prefName] = defaultBranch.getIntPref(prefName);
-            break;
-
-          case Services.prefs.PREF_BOOL:
-            studyPrefsChanged[prefName] = defaultBranch.getBoolPref(prefName);
-            break;
-
-          case Services.prefs.PREF_INVALID:
-            studyPrefsChanged[prefName] = null;
-            break;
-
-          default:
-            // This should never happen
-            log.error(`Error getting startup pref ${prefName}; unknown value type ${experimentPrefType}.`);
-        }
-      } catch (e) {
-        if (e.result === Cr.NS_ERROR_UNEXPECTED) {
-          // There is a value for the pref on the user branch but not on the default branch. This is ok.
-          studyPrefsChanged[prefName] = null;
-        } else {
-          // rethrow
-          throw e;
-        }
-      }
-
-      // now set the new default value
-      switch (experimentPrefType) {
-        case Services.prefs.PREF_STRING:
-          defaultBranch.setCharPref(prefName, experimentBranch.getCharPref(prefName));
-          break;
-
-        case Services.prefs.PREF_INT:
-          defaultBranch.setIntPref(prefName, experimentBranch.getIntPref(prefName));
-          break;
-
-        case Services.prefs.PREF_BOOL:
-          defaultBranch.setBoolPref(prefName, experimentBranch.getBoolPref(prefName));
-          break;
-
-        case Services.prefs.PREF_INVALID:
-          // This should never happen.
-          log.error(`Error setting startup pref ${prefName}; pref type is invalid (${experimentPrefType}).`);
-          break;
-
-        default:
-          // This should never happen either.
-          log.error(`Error getting startup pref ${prefName}; unknown value type ${experimentPrefType}.`);
-      }
-    }
-  },
-
-  observe(subject, topic, data) {
-    if (topic === UI_AVAILABLE_NOTIFICATION) {
-      Services.obs.removeObserver(this, UI_AVAILABLE_NOTIFICATION);
-      this.finishStartup();
-    }
-  },
-
-  install() {
-    // Nothing to do during install
-  },
-
-  startup(data, reason) {
-    // Initialization that needs to happen before the first paint on startup.
-    this.initShieldPrefs(DEFAULT_PREFS);
-    this.initExperimentPrefs();
-
-    // If the app is starting up, wait until the UI is available before finishing
-    // init.
-    if (reason === REASON_APP_STARTUP) {
-      Services.obs.addObserver(this, UI_AVAILABLE_NOTIFICATION);
-    } else {
-      this.finishStartup();
-    }
-  },
-
-  async finishStartup() {
-    await PreferenceExperiments.recordOriginalValues(studyPrefsChanged);
-    ShieldRecipeClient.startup();
-  },
-
-  async shutdown(data, reason) {
-    // Wait for async write operations during shutdown before unloading modules.
-    await ShieldRecipeClient.shutdown(reason);
-
-    // In case the observer didn't run, clean it up.
-    try {
-      Services.obs.removeObserver(this, UI_AVAILABLE_NOTIFICATION);
-    } catch (err) {
-      // It must already be removed!
-    }
-
-    // Unload add-on modules. We don't do this in ShieldRecipeClient so that
-    // modules are not unloaded accidentally during tests.
-    let modules = [
-      "lib/ActionSandboxManager.jsm",
-      "lib/Addons.jsm",
-      "lib/AddonStudies.jsm",
-      "lib/CleanupManager.jsm",
-      "lib/ClientEnvironment.jsm",
-      "lib/FilterExpressions.jsm",
-      "lib/EventEmitter.jsm",
-      "lib/Heartbeat.jsm",
-      "lib/LogManager.jsm",
-      "lib/NormandyApi.jsm",
-      "lib/NormandyDriver.jsm",
-      "lib/PreferenceExperiments.jsm",
-      "lib/RecipeRunner.jsm",
-      "lib/Sampling.jsm",
-      "lib/SandboxManager.jsm",
-      "lib/ShieldPreferences.jsm",
-      "lib/ShieldRecipeClient.jsm",
-      "lib/Storage.jsm",
-      "lib/TelemetryEvents.jsm",
-      "lib/Uptake.jsm",
-      "lib/Utils.jsm",
-    ].map(m => `resource://shield-recipe-client/${m}`);
-    modules = modules.concat([
-      "resource://shield-recipe-client-content/AboutPages.jsm",
-      "resource://shield-recipe-client-vendor/mozjexl.js",
-    ]);
-
-    for (const module of modules) {
-      log.debug(`Unloading ${module}`);
-      Cu.unload(module);
-    }
-  },
-
-  uninstall() {
-    // Do nothing
-  },
-};
-
-// Expose bootstrap methods on the global
-for (const methodName of ["install", "startup", "shutdown", "uninstall"]) {
-  this[methodName] = Bootstrap[methodName].bind(Bootstrap);
-}
deleted file mode 100644
--- a/browser/extensions/shield-recipe-client/install.rdf.in
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-
-#filter substitution
-
-<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:em="http://www.mozilla.org/2004/em-rdf#">
-  <Description about="urn:mozilla:install-manifest">
-    <em:id>shield-recipe-client@mozilla.org</em:id>
-    <em:type>2</em:type>
-    <em:bootstrap>true</em:bootstrap>
-    <em:unpack>false</em:unpack>
-    <em:version>83</em:version>
-    <em:name>Shield Recipe Client</em:name>
-    <em:description>Client to download and run recipes for SHIELD, Heartbeat, etc.</em:description>
-    <em:multiprocessCompatible>true</em:multiprocessCompatible>
-
-    <em:targetApplication>
-      <Description>
-        <em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id>
-        <em:minVersion>@MOZ_APP_VERSION@</em:minVersion>
-        <em:maxVersion>@MOZ_APP_MAXVERSION@</em:maxVersion>
-      </Description>
-    </em:targetApplication>
-  </Description>
-</RDF>
deleted file mode 100644
--- a/browser/extensions/shield-recipe-client/lib/ShieldRecipeClient.jsm
+++ /dev/null
@@ -1,86 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-"use strict";
-
-ChromeUtils.import("resource://gre/modules/Services.jsm");
-ChromeUtils.import("resource://gre/modules/Log.jsm");
-ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
-
-ChromeUtils.defineModuleGetter(this, "LogManager",
-  "resource://shield-recipe-client/lib/LogManager.jsm");
-ChromeUtils.defineModuleGetter(this, "RecipeRunner",
-  "resource://shield-recipe-client/lib/RecipeRunner.jsm");
-ChromeUtils.defineModuleGetter(this, "CleanupManager",
-  "resource://shield-recipe-client/lib/CleanupManager.jsm");
-ChromeUtils.defineModuleGetter(this, "PreferenceExperiments",
-  "resource://shield-recipe-client/lib/PreferenceExperiments.jsm");
-ChromeUtils.defineModuleGetter(this, "AboutPages",
-  "resource://shield-recipe-client-content/AboutPages.jsm");
-ChromeUtils.defineModuleGetter(this, "ShieldPreferences",
-  "resource://shield-recipe-client/lib/ShieldPreferences.jsm");
-ChromeUtils.defineModuleGetter(this, "AddonStudies",
-  "resource://shield-recipe-client/lib/AddonStudies.jsm");
-ChromeUtils.defineModuleGetter(this, "TelemetryEvents",
-  "resource://shield-recipe-client/lib/TelemetryEvents.jsm");
-
-var EXPORTED_SYMBOLS = ["ShieldRecipeClient"];
-
-const PREF_LOGGING_LEVEL = "extensions.shield-recipe-client.logging.level";
-const SHIELD_INIT_NOTIFICATION = "shield-init-complete";
-
-let log = null;
-
-/**
- * Handles startup and shutdown of the entire add-on. Bootsrap.js defers to this
- * module for most tasks so that we can more easily test startup and shutdown
- * (bootstrap.js is difficult to import in tests).
- */
-var ShieldRecipeClient = {
-  async startup() {
-    // Setup logging and listen for changes to logging prefs
-    LogManager.configure(Services.prefs.getIntPref(PREF_LOGGING_LEVEL));
-    Services.prefs.addObserver(PREF_LOGGING_LEVEL, LogManager.configure);
-    CleanupManager.addCleanupHandler(
-      () => Services.prefs.removeObserver(PREF_LOGGING_LEVEL, LogManager.configure),
-    );
-    log = LogManager.getLogger("bootstrap");
-
-    try {
-      TelemetryEvents.init();
-    } catch (err) {
-      log.error("Failed to initialize telemetry events:", err);
-    }
-
-    try {
-      await AboutPages.init();
-    } catch (err) {
-      log.error("Failed to initialize about pages:", err);
-    }
-
-    try {
-      await AddonStudies.init();
-    } catch (err) {
-      log.error("Failed to initialize addon studies:", err);
-    }
-
-    try {
-      await PreferenceExperiments.init();
-    } catch (err) {
-      log.error("Failed to initialize preference experiments:", err);
-    }
-
-    try {
-      ShieldPreferences.init();
-    } catch (err) {
-      log.error("Failed to initialize preferences UI:", err);
-    }
-
-    await RecipeRunner.init();
-    Services.obs.notifyObservers(null, SHIELD_INIT_NOTIFICATION);
-  },
-
-  async shutdown(reason) {
-    await CleanupManager.cleanup();
-  },
-};
--- a/toolkit/components/moz.build
+++ b/toolkit/components/moz.build
@@ -108,8 +108,11 @@ if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'wind
 if CONFIG['MOZ_WIDGET_TOOLKIT'] != 'android':
     EXTRA_COMPONENTS += [
         'nsDefaultCLH.js',
         'nsDefaultCLH.manifest',
     ]
 
 if CONFIG['NIGHTLY_BUILD'] and CONFIG['MOZ_BUILD_APP'] == 'browser':
     DIRS += ['payments']
+
+if CONFIG['MOZ_BUILD_APP'] == 'browser':
+    DIRS += ['normandy']
new file mode 100644
--- /dev/null
+++ b/toolkit/components/normandy/Normandy.jsm
@@ -0,0 +1,171 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+ChromeUtils.import("resource://gre/modules/AppConstants.jsm");
+ChromeUtils.import("resource://gre/modules/Log.jsm");
+ChromeUtils.import("resource://gre/modules/Services.jsm");
+ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+ChromeUtils.defineModuleGetter(this, "LogManager",
+  "resource://normandy/lib/LogManager.jsm");
+ChromeUtils.defineModuleGetter(this, "ShieldRecipeClient",
+  "resource://normandy/lib/ShieldRecipeClient.jsm");
+ChromeUtils.defineModuleGetter(this, "PreferenceExperiments",
+  "resource://normandy/lib/PreferenceExperiments.jsm");
+
+var EXPORTED_SYMBOLS = ["Normandy"];
+
+const UI_AVAILABLE_NOTIFICATION = "sessionstore-windows-restored";
+const STARTUP_EXPERIMENT_PREFS_BRANCH = "extensions.shield-recipe-client.startupExperimentPrefs.";
+const PREF_LOGGING_LEVEL = "extensions.shield-recipe-client.logging.level";
+const BOOTSTRAP_LOGGER_NAME = "extensions.shield-recipe-client.bootstrap";
+const DEFAULT_PREFS = {
+  "extensions.shield-recipe-client.api_url": "https://normandy.cdn.mozilla.net/api/v1",
+  "extensions.shield-recipe-client.dev_mode": false,
+  "extensions.shield-recipe-client.enabled": true,
+  "extensions.shield-recipe-client.startup_delay_seconds": 300,
+  "extensions.shield-recipe-client.logging.level": Log.Level.Warn,
+  "extensions.shield-recipe-client.user_id": "",
+  "extensions.shield-recipe-client.run_interval_seconds": 86400, // 24 hours
+  "extensions.shield-recipe-client.first_run": true,
+  "extensions.shield-recipe-client.shieldLearnMoreUrl": (
+    "https://support.mozilla.org/1/firefox/%VERSION%/%OS%/%LOCALE%/shield"
+  ),
+  "app.shield.optoutstudies.enabled": AppConstants.MOZ_DATA_REPORTING,
+};
+
+// 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);
+
+let studyPrefsChanged = {};
+
+var Normandy = {
+  init() {
+    // Initialization that needs to happen before the first paint on startup.
+    this.initShieldPrefs(DEFAULT_PREFS);
+    this.initExperimentPrefs();
+
+    // Wait until the UI is available before finishing initialization.
+    Services.obs.addObserver(this, UI_AVAILABLE_NOTIFICATION);
+  },
+
+  observe(subject, topic, data) {
+    if (topic === UI_AVAILABLE_NOTIFICATION) {
+      Services.obs.removeObserver(this, UI_AVAILABLE_NOTIFICATION);
+      this.finishInit();
+    }
+  },
+
+  async finishInit() {
+    await PreferenceExperiments.recordOriginalValues(studyPrefsChanged);
+    ShieldRecipeClient.startup();
+  },
+
+  async uninit() {
+    // Wait for async write operations during shutdown before unloading modules.
+    await ShieldRecipeClient.shutdown();
+
+    // In case the observer didn't run, clean it up.
+    try {
+      Services.obs.removeObserver(this, UI_AVAILABLE_NOTIFICATION);
+    } catch (err) {
+      // It must already be removed!
+    }
+  },
+
+  initShieldPrefs(defaultPrefs) {
+    const prefBranch = Services.prefs.getDefaultBranch("");
+    for (const [name, value] of Object.entries(defaultPrefs)) {
+      switch (typeof value) {
+        case "string":
+          prefBranch.setCharPref(name, value);
+          break;
+        case "number":
+          prefBranch.setIntPref(name, value);
+          break;
+        case "boolean":
+          prefBranch.setBoolPref(name, value);
+          break;
+        default:
+          throw new Error(`Invalid default preference type ${typeof value}`);
+      }
+    }
+  },
+
+  initExperimentPrefs() {
+    studyPrefsChanged = {};
+    const defaultBranch = Services.prefs.getDefaultBranch("");
+    const experimentBranch = Services.prefs.getBranch(STARTUP_EXPERIMENT_PREFS_BRANCH);
+
+    for (const prefName of experimentBranch.getChildList("")) {
+      const experimentPrefType = experimentBranch.getPrefType(prefName);
+      const realPrefType = defaultBranch.getPrefType(prefName);
+
+      if (realPrefType !== Services.prefs.PREF_INVALID && realPrefType !== experimentPrefType) {
+        log.error(`Error setting startup pref ${prefName}; pref type does not match.`);
+        continue;
+      }
+
+      // record the value of the default branch before setting it
+      try {
+        switch (realPrefType) {
+          case Services.prefs.PREF_STRING:
+            studyPrefsChanged[prefName] = defaultBranch.getCharPref(prefName);
+            break;
+
+          case Services.prefs.PREF_INT:
+            studyPrefsChanged[prefName] = defaultBranch.getIntPref(prefName);
+            break;
+
+          case Services.prefs.PREF_BOOL:
+            studyPrefsChanged[prefName] = defaultBranch.getBoolPref(prefName);
+            break;
+
+          case Services.prefs.PREF_INVALID:
+            studyPrefsChanged[prefName] = null;
+            break;
+
+          default:
+            // This should never happen
+            log.error(`Error getting startup pref ${prefName}; unknown value type ${experimentPrefType}.`);
+        }
+      } catch (e) {
+        if (e.result === Cr.NS_ERROR_UNEXPECTED) {
+          // There is a value for the pref on the user branch but not on the default branch. This is ok.
+          studyPrefsChanged[prefName] = null;
+        } else {
+          // rethrow
+          throw e;
+        }
+      }
+
+      // now set the new default value
+      switch (experimentPrefType) {
+        case Services.prefs.PREF_STRING:
+          defaultBranch.setCharPref(prefName, experimentBranch.getCharPref(prefName));
+          break;
+
+        case Services.prefs.PREF_INT:
+          defaultBranch.setIntPref(prefName, experimentBranch.getIntPref(prefName));
+          break;
+
+        case Services.prefs.PREF_BOOL:
+          defaultBranch.setBoolPref(prefName, experimentBranch.getBoolPref(prefName));
+          break;
+
+        case Services.prefs.PREF_INVALID:
+          // This should never happen.
+          log.error(`Error setting startup pref ${prefName}; pref type is invalid (${experimentPrefType}).`);
+          break;
+
+        default:
+          // This should never happen either.
+          log.error(`Error getting startup pref ${prefName}; unknown value type ${experimentPrefType}.`);
+      }
+    }
+  },
+};
rename from browser/extensions/shield-recipe-client/content/AboutPages.jsm
rename to toolkit/components/normandy/content/AboutPages.jsm
--- a/browser/extensions/shield-recipe-client/content/AboutPages.jsm
+++ b/toolkit/components/normandy/content/AboutPages.jsm
@@ -3,40 +3,40 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";
 
 const Cm = Components.manager;
 ChromeUtils.import("resource://gre/modules/Services.jsm");
 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 
 ChromeUtils.defineModuleGetter(
-  this, "CleanupManager", "resource://shield-recipe-client/lib/CleanupManager.jsm",
+  this, "CleanupManager", "resource://normandy/lib/CleanupManager.jsm",
 );
 ChromeUtils.defineModuleGetter(
-  this, "AddonStudies", "resource://shield-recipe-client/lib/AddonStudies.jsm",
+  this, "AddonStudies", "resource://normandy/lib/AddonStudies.jsm",
 );
 ChromeUtils.defineModuleGetter(
-  this, "RecipeRunner", "resource://shield-recipe-client/lib/RecipeRunner.jsm",
+  this, "RecipeRunner", "resource://normandy/lib/RecipeRunner.jsm",
 );
 
 var EXPORTED_SYMBOLS = ["AboutPages"];
 
 const SHIELD_LEARN_MORE_URL_PREF = "extensions.shield-recipe-client.shieldLearnMoreUrl";
 
 // Due to bug 1051238 frame scripts are cached forever, so we can't update them
 // as a restartless add-on. The Math.random() is the work around for this.
 const PROCESS_SCRIPT = (
-  `resource://shield-recipe-client-content/shield-content-process.js?${Math.random()}`
+  `resource://normandy-content/shield-content-process.js?${Math.random()}`
 );
 const FRAME_SCRIPT = (
-  `resource://shield-recipe-client-content/shield-content-frame.js?${Math.random()}`
+  `resource://normandy-content/shield-content-frame.js?${Math.random()}`
 );
 
 /**
- * Class for managing an about: page that Shield provides. Adapted from
+ * Class for managing an about: page that Normandy provides. Adapted from
  * browser/extensions/pocket/content/AboutPocket.jsm.
  *
  * @implements nsIFactory
  * @implements nsIAboutModule
  */
 class AboutPage {
   constructor({chromeUrl, aboutHost, classId, description, uriFlags}) {
     this.chromeUrl = chromeUrl;
@@ -121,17 +121,17 @@ var AboutPages = {
 
 /**
  * about:studies page for displaying in-progress and past Shield studies.
  * @type {AboutPage}
  * @implements {nsIMessageListener}
  */
 XPCOMUtils.defineLazyGetter(this.AboutPages, "aboutStudies", () => {
   const aboutStudies = new AboutPage({
-    chromeUrl: "resource://shield-recipe-client-content/about-studies/about-studies.html",
+    chromeUrl: "resource://normandy-content/about-studies/about-studies.html",
     aboutHost: "studies",
     classId: "{6ab96943-a163-482c-9622-4faedc0e827f}",
     description: "Shield Study Listing",
     uriFlags: (
       Ci.nsIAboutModule.ALLOW_SCRIPT
       | Ci.nsIAboutModule.URI_SAFE_FOR_UNTRUSTED_CONTENT
       | Ci.nsIAboutModule.URI_MUST_LOAD_IN_CHILD
     ),
@@ -141,25 +141,27 @@ XPCOMUtils.defineLazyGetter(this.AboutPa
   Object.assign(aboutStudies, {
     /**
      * Register listeners for messages from the content processes.
      */
     registerParentListeners() {
       Services.mm.addMessageListener("Shield:GetStudyList", this);
       Services.mm.addMessageListener("Shield:RemoveStudy", this);
       Services.mm.addMessageListener("Shield:OpenDataPreferences", this);
+      Services.mm.addMessageListener("Shield:GetStudiesEnabled", this);
     },
 
     /**
      * Unregister listeners for messages from the content process.
      */
     unregisterParentListeners() {
       Services.mm.removeMessageListener("Shield:GetStudyList", this);
       Services.mm.removeMessageListener("Shield:RemoveStudy", this);
       Services.mm.removeMessageListener("Shield:OpenDataPreferences", this);
+      Services.mm.removeMessageListener("Shield:GetStudiesEnabled", this);
     },
 
     /**
      * Dispatch messages from the content process to the appropriate handler.
      * @param {Object} message
      *   See the nsIMessageListener documentation for details about this object.
      */
     receiveMessage(message) {
@@ -168,16 +170,19 @@ XPCOMUtils.defineLazyGetter(this.AboutPa
           this.sendStudyList(message.target);
           break;
         case "Shield:RemoveStudy":
           this.removeStudy(message.data.recipeId, message.data.reason);
           break;
         case "Shield:OpenDataPreferences":
           this.openDataPreferences();
           break;
+        case "Shield:GetStudiesEnabled":
+          this.sendStudiesEnabled(message.target);
+          break;
       }
     },
 
     /**
      * Fetch a list of studies from storage and send it to the process that
      * requested them.
      * @param {<browser>} target
      *   XUL <browser> element for the tab containing the about:studies page
@@ -190,16 +195,38 @@ XPCOMUtils.defineLazyGetter(this.AboutPa
         });
       } catch (err) {
         // The child process might be gone, so no need to throw here.
         Cu.reportError(err);
       }
     },
 
     /**
+     * Get if studies are enabled and send it to the process that
+     * requested them. This has to be in the parent process, since
+     * RecipeRunner is stateful, and can't be interacted with from
+     * content processes safely.
+     *
+     * @param {<browser>} target
+     *   XUL <browser> element for the tab containing the about:studies page
+     *   that requested a study list.
+     */
+    sendStudiesEnabled(target) {
+      RecipeRunner.checkPrefs();
+      try {
+        target.messageManager.sendAsyncMessage("Shield:ReceiveStudiesEnabled", {
+          studiesEnabled: RecipeRunner.enabled,
+        });
+      } catch (err) {
+        // The child process might be gone, so no need to throw here.
+        Cu.reportError(err);
+      }
+    },
+
+    /**
      * Disable an active study and remove its add-on.
      * @param {String} studyName
      */
     async removeStudy(recipeId, reason) {
       await AddonStudies.stop(recipeId, reason);
 
       // Update any open tabs with the new study list now that it has changed.
       Services.mm.broadcastAsyncMessage("Shield:ReceiveStudyList", {
rename from browser/extensions/shield-recipe-client/content/about-studies/about-studies.css
rename to toolkit/components/normandy/content/about-studies/about-studies.css
rename from browser/extensions/shield-recipe-client/content/about-studies/about-studies.html
rename to toolkit/components/normandy/content/about-studies/about-studies.html
--- a/browser/extensions/shield-recipe-client/content/about-studies/about-studies.html
+++ b/toolkit/components/normandy/content/about-studies/about-studies.html
@@ -3,21 +3,21 @@
    - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
 <!DOCTYPE html>
 <html>
   <head>
     <meta charset="utf-8">
     <title>about:studies</title>
     <link rel="stylesheet" href="chrome://global/skin/global.css">
     <link rel="stylesheet" href="chrome://global/skin/in-content/common.css">
-    <link rel="stylesheet" href="resource://shield-recipe-client-content/about-studies/about-studies.css">
+    <link rel="stylesheet" href="resource://normandy-content/about-studies/about-studies.css">
   </head>
   <body>
     <div id="app"></div>
-    <script src="resource://shield-recipe-client-vendor/React.js"></script>
-    <script src="resource://shield-recipe-client-vendor/ReactDOM.js"></script>
-    <script src="resource://shield-recipe-client-vendor/PropTypes.js"></script>
-    <script src="resource://shield-recipe-client-vendor/classnames.js"></script>
-    <script src="resource://shield-recipe-client-content/about-studies/common.js"></script>
-    <script src="resource://shield-recipe-client-content/about-studies/shield-studies.js"></script>
-    <script src="resource://shield-recipe-client-content/about-studies/about-studies.js"></script>
+    <script src="resource://normandy-vendor/React.js"></script>
+    <script src="resource://normandy-vendor/ReactDOM.js"></script>
+    <script src="resource://normandy-vendor/PropTypes.js"></script>
+    <script src="resource://normandy-vendor/classnames.js"></script>
+    <script src="resource://normandy-content/about-studies/common.js"></script>
+    <script src="resource://normandy-content/about-studies/shield-studies.js"></script>
+    <script src="resource://normandy-content/about-studies/about-studies.js"></script>
   </body>
 </html>
rename from browser/extensions/shield-recipe-client/content/about-studies/about-studies.js
rename to toolkit/components/normandy/content/about-studies/about-studies.js
--- a/browser/extensions/shield-recipe-client/content/about-studies/about-studies.js
+++ b/toolkit/components/normandy/content/about-studies/about-studies.js
@@ -10,17 +10,17 @@
  *
  * Pages will appear in the sidebar in the order they are defined here. If the
  * URL doesn't contain a hash, the first page will be displayed in the content area.
  */
 const PAGES = new Map([
   ["shieldStudies", {
     name: "Shield Studies",
     component: ShieldStudies,
-    icon: "resource://shield-recipe-client-content/about-studies/img/shield-logo.png",
+    icon: "resource://normandy-content/about-studies/img/shield-logo.png",
   }],
 ]);
 
 /**
  * Handle basic layout and routing within about:studies.
  */
 class AboutStudies extends React.Component {
   constructor(props) {
rename from browser/extensions/shield-recipe-client/content/about-studies/common.js
rename to toolkit/components/normandy/content/about-studies/common.js
--- a/browser/extensions/shield-recipe-client/content/about-studies/common.js
+++ b/toolkit/components/normandy/content/about-studies/common.js
@@ -45,17 +45,17 @@ window.FxButton = class FxButton extends
 window.FxButton.propTypes = {
   children: PropTypes.node,
 };
 
 /**
  * Wrapper class for a value that is provided by the frame script.
  *
  * Emits a "GetRemoteValue:{name}" page event on load to fetch the initial
- * value, and listens for "ReceiveRemoveValue:{name}" page callbacks to receive
+ * value, and listens for "ReceiveRemoteValue:{name}" page callbacks to receive
  * the value when it updates.
  *
  * @example
  * const myRemoteValue = new RemoteValue("MyValue", 5);
  * class MyComponent extends React.Component {
  *   constructor(props) {
  *     super(props);
  *     this.state = {
rename from browser/extensions/shield-recipe-client/content/about-studies/img/shield-logo.png
rename to toolkit/components/normandy/content/about-studies/img/shield-logo.png
rename from browser/extensions/shield-recipe-client/content/about-studies/shield-studies.js
rename to toolkit/components/normandy/content/about-studies/shield-studies.js
rename from browser/extensions/shield-recipe-client/content/shield-content-frame.js
rename to toolkit/components/normandy/content/shield-content-frame.js
--- a/browser/extensions/shield-recipe-client/content/shield-content-frame.js
+++ b/toolkit/components/normandy/content/shield-content-frame.js
@@ -15,17 +15,17 @@
 
 /* global content addMessageListener removeMessageListener sendAsyncMessage */
 
 ChromeUtils.import("resource://gre/modules/Services.jsm");
 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 
 const frameGlobal = {};
 ChromeUtils.defineModuleGetter(
-  frameGlobal, "AboutPages", "resource://shield-recipe-client-content/AboutPages.jsm",
+  frameGlobal, "AboutPages", "resource://normandy-content/AboutPages.jsm",
 );
 
 /**
  * Handles incoming events from the parent process and about:studies.
  * @implements nsIMessageListener
  * @implements EventListener
  */
 class ShieldFrameListener {
@@ -34,41 +34,39 @@ class ShieldFrameListener {
     if (!this.ensureTrustedOrigin()) {
       return;
     }
 
     // We waited until after we received an event to register message listeners
     // in order to save resources for tabs that don't ever load about:studies.
     addMessageListener("Shield:ShuttingDown", this);
     addMessageListener("Shield:ReceiveStudyList", this);
+    addMessageListener("Shield:ReceiveStudiesEnabled", this);
 
     switch (event.detail.action) {
       // Actions that require the parent process
       case "GetRemoteValue:StudyList":
         sendAsyncMessage("Shield:GetStudyList");
         break;
       case "RemoveStudy":
         sendAsyncMessage("Shield:RemoveStudy", event.detail.data);
         break;
+      case "GetRemoteValue:StudiesEnabled":
+        sendAsyncMessage("Shield:GetStudiesEnabled");
+        break;
+      case "NavigateToDataPreferences":
+        sendAsyncMessage("Shield:OpenDataPreferences");
+        break;
       // Actions that can be performed in the content process
       case "GetRemoteValue:ShieldLearnMoreHref":
         this.triggerPageCallback(
           "ReceiveRemoteValue:ShieldLearnMoreHref",
           frameGlobal.AboutPages.aboutStudies.getShieldLearnMoreHref()
         );
         break;
-      case "GetRemoteValue:StudiesEnabled":
-        this.triggerPageCallback(
-          "ReceiveRemoteValue:StudiesEnabled",
-          frameGlobal.AboutPages.aboutStudies.getStudiesEnabled()
-        );
-        break;
-      case "NavigateToDataPreferences":
-        sendAsyncMessage("Shield:OpenDataPreferences");
-        break;
     }
   }
 
   /**
    * Check that the current webpage's origin is about:studies.
    * @return {Boolean}
    */
   ensureTrustedOrigin() {
@@ -80,16 +78,19 @@ class ShieldFrameListener {
    * @param {Object} message
    *   See the nsIMessageListener docs.
    */
   receiveMessage(message) {
     switch (message.name) {
       case "Shield:ReceiveStudyList":
         this.triggerPageCallback("ReceiveRemoteValue:StudyList", message.data.studies);
         break;
+      case "Shield:ReceiveStudiesEnabled":
+        this.triggerPageCallback("ReceiveRemoteValue:StudiesEnabled", message.data.studiesEnabled);
+        break;
       case "Shield:ShuttingDown":
         this.onShutdown();
         break;
     }
   }
 
   /**
    * Trigger an event to communicate with the unprivileged about: page.
rename from browser/extensions/shield-recipe-client/content/shield-content-process.js
rename to toolkit/components/normandy/content/shield-content-process.js
--- a/browser/extensions/shield-recipe-client/content/shield-content-process.js
+++ b/toolkit/components/normandy/content/shield-content-process.js
@@ -8,31 +8,31 @@
  * from the add-on before un-registering them.
  *
  * This file is loaded as a process script. It is executed once for each
  * process, including the parent one.
  */
 
 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 ChromeUtils.import("resource://gre/modules/Services.jsm");
-ChromeUtils.import("resource://shield-recipe-client-content/AboutPages.jsm");
+ChromeUtils.import("resource://normandy-content/AboutPages.jsm");
 
 class ShieldChildListener {
   onStartup() {
     Services.cpmm.addMessageListener("Shield:ShuttingDown", this, true);
     AboutPages.aboutStudies.register();
   }
 
   onShutdown() {
     AboutPages.aboutStudies.unregister();
     Services.cpmm.removeMessageListener("Shield:ShuttingDown", this);
 
     // Unload AboutPages.jsm in case the add-on is reinstalled and we need to
     // load a new version of it.
-    Cu.unload("resource://shield-recipe-client-content/AboutPages.jsm");
+    Cu.unload("resource://normandy-content/AboutPages.jsm");
   }
 
   receiveMessage(message) {
     switch (message.name) {
       case "Shield:ShuttingDown":
         this.onShutdown();
         break;
     }
rename from browser/extensions/shield-recipe-client/docs/data-collection.rst
rename to toolkit/components/normandy/docs/data-collection.rst
rename from browser/extensions/shield-recipe-client/docs/index.rst
rename to toolkit/components/normandy/docs/index.rst
rename from browser/extensions/shield-recipe-client/jar.mn
rename to toolkit/components/normandy/jar.mn
--- a/browser/extensions/shield-recipe-client/jar.mn
+++ b/toolkit/components/normandy/jar.mn
@@ -1,13 +1,15 @@
 # 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/.
 
-[features/shield-recipe-client@mozilla.org] chrome.jar:
-% resource shield-recipe-client %
-  lib/ (./lib/*)
-  data/ (./data/*)
-  skin/  (skin/*)
-% resource shield-recipe-client-content %content/ contentaccessible=yes
-  content/ (./content/*)
-% resource shield-recipe-client-vendor %vendor/ contentaccessible=yes
-  vendor/ (./vendor/*)
+toolkit.jar:
+% resource normandy %res/normandy/
+  res/normandy/Normandy.jsm (./Normandy.jsm)
+  res/normandy/lib/ (./lib/*)
+  res/normandy/skin/  (./skin/*)
+
+% resource normandy-content %res/normandy/content/ contentaccessible=yes
+  res/normandy/content/ (./content/*)
+
+% resource normandy-vendor %res/normandy/vendor/ contentaccessible=yes
+  res/normandy/vendor/ (./vendor/*)
rename from browser/extensions/shield-recipe-client/lib/ActionSandboxManager.jsm
rename to toolkit/components/normandy/lib/ActionSandboxManager.jsm
--- a/browser/extensions/shield-recipe-client/lib/ActionSandboxManager.jsm
+++ b/toolkit/components/normandy/lib/ActionSandboxManager.jsm
@@ -1,17 +1,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
-ChromeUtils.import("resource://shield-recipe-client/lib/NormandyDriver.jsm");
-ChromeUtils.import("resource://shield-recipe-client/lib/SandboxManager.jsm");
-ChromeUtils.import("resource://shield-recipe-client/lib/LogManager.jsm");
+ChromeUtils.import("resource://normandy/lib/NormandyDriver.jsm");
+ChromeUtils.import("resource://normandy/lib/SandboxManager.jsm");
+ChromeUtils.import("resource://normandy/lib/LogManager.jsm");
 
 var EXPORTED_SYMBOLS = ["ActionSandboxManager"];
 
 const log = LogManager.getLogger("recipe-sandbox-manager");
 
 /**
  * An extension to SandboxManager that prepares a sandbox for executing
  * Normandy actions.
rename from browser/extensions/shield-recipe-client/lib/AddonStudies.jsm
rename to toolkit/components/normandy/lib/AddonStudies.jsm
--- a/browser/extensions/shield-recipe-client/lib/AddonStudies.jsm
+++ b/toolkit/components/normandy/lib/AddonStudies.jsm
@@ -28,22 +28,22 @@
 
 ChromeUtils.import("resource://gre/modules/osfile.jsm");
 ChromeUtils.import("resource://gre/modules/Services.jsm");
 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 
 ChromeUtils.defineModuleGetter(this, "FileUtils", "resource://gre/modules/FileUtils.jsm");
 ChromeUtils.defineModuleGetter(this, "IndexedDB", "resource://gre/modules/IndexedDB.jsm");
 ChromeUtils.defineModuleGetter(this, "AddonManager", "resource://gre/modules/AddonManager.jsm");
-ChromeUtils.defineModuleGetter(this, "Addons", "resource://shield-recipe-client/lib/Addons.jsm");
+ChromeUtils.defineModuleGetter(this, "Addons", "resource://normandy/lib/Addons.jsm");
 ChromeUtils.defineModuleGetter(
-  this, "CleanupManager", "resource://shield-recipe-client/lib/CleanupManager.jsm"
+  this, "CleanupManager", "resource://normandy/lib/CleanupManager.jsm"
 );
-ChromeUtils.defineModuleGetter(this, "LogManager", "resource://shield-recipe-client/lib/LogManager.jsm");
-ChromeUtils.defineModuleGetter(this, "TelemetryEvents", "resource://shield-recipe-client/lib/TelemetryEvents.jsm");
+ChromeUtils.defineModuleGetter(this, "LogManager", "resource://normandy/lib/LogManager.jsm");
+ChromeUtils.defineModuleGetter(this, "TelemetryEvents", "resource://normandy/lib/TelemetryEvents.jsm");
 
 Cu.importGlobalProperties(["fetch"]); /* globals fetch */
 
 var EXPORTED_SYMBOLS = ["AddonStudies"];
 
 const DB_NAME = "shield";
 const STORE_NAME = "addon-studies";
 const DB_OPTIONS = {
rename from browser/extensions/shield-recipe-client/lib/Addons.jsm
rename to toolkit/components/normandy/lib/Addons.jsm
--- a/browser/extensions/shield-recipe-client/lib/Addons.jsm
+++ b/toolkit/components/normandy/lib/Addons.jsm
@@ -4,17 +4,17 @@
 
 "use strict";
 
 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 
 ChromeUtils.defineModuleGetter(this, "AddonManager", "resource://gre/modules/AddonManager.jsm");
 ChromeUtils.defineModuleGetter(this, "Extension", "resource://gre/modules/Extension.jsm");
 ChromeUtils.defineModuleGetter(
-  this, "CleanupManager", "resource://shield-recipe-client/lib/CleanupManager.jsm"
+  this, "CleanupManager", "resource://normandy/lib/CleanupManager.jsm"
 );
 
 var EXPORTED_SYMBOLS = ["Addons"];
 
 /**
  * SafeAddons store info about an add-on. They are single-depth
  * objects to simplify cloning, and have no methods so they are safe
  * to pass to sandboxes and filter expressions.
rename from browser/extensions/shield-recipe-client/lib/CleanupManager.jsm
rename to toolkit/components/normandy/lib/CleanupManager.jsm
rename from browser/extensions/shield-recipe-client/lib/ClientEnvironment.jsm
rename to toolkit/components/normandy/lib/ClientEnvironment.jsm
--- a/browser/extensions/shield-recipe-client/lib/ClientEnvironment.jsm
+++ b/toolkit/components/normandy/lib/ClientEnvironment.jsm
@@ -7,24 +7,24 @@
 ChromeUtils.import("resource://gre/modules/Preferences.jsm");
 ChromeUtils.import("resource://gre/modules/Services.jsm");
 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 
 ChromeUtils.defineModuleGetter(this, "ShellService", "resource:///modules/ShellService.jsm");
 ChromeUtils.defineModuleGetter(this, "AddonManager", "resource://gre/modules/AddonManager.jsm");
 ChromeUtils.defineModuleGetter(this, "TelemetryArchive", "resource://gre/modules/TelemetryArchive.jsm");
 ChromeUtils.defineModuleGetter(this, "UpdateUtils", "resource://gre/modules/UpdateUtils.jsm");
-ChromeUtils.defineModuleGetter(this, "NormandyApi", "resource://shield-recipe-client/lib/NormandyApi.jsm");
+ChromeUtils.defineModuleGetter(this, "NormandyApi", "resource://normandy/lib/NormandyApi.jsm");
 ChromeUtils.defineModuleGetter(
     this,
     "PreferenceExperiments",
-    "resource://shield-recipe-client/lib/PreferenceExperiments.jsm"
+    "resource://normandy/lib/PreferenceExperiments.jsm"
 );
-ChromeUtils.defineModuleGetter(this, "Utils", "resource://shield-recipe-client/lib/Utils.jsm");
-ChromeUtils.defineModuleGetter(this, "Addons", "resource://shield-recipe-client/lib/Addons.jsm");
+ChromeUtils.defineModuleGetter(this, "Utils", "resource://normandy/lib/Utils.jsm");
+ChromeUtils.defineModuleGetter(this, "Addons", "resource://normandy/lib/Addons.jsm");
 
 const {generateUUID} = Cc["@mozilla.org/uuid-generator;1"].getService(Ci.nsIUUIDGenerator);
 
 var EXPORTED_SYMBOLS = ["ClientEnvironment"];
 
 // Cached API request for client attributes that are determined by the Normandy
 // service.
 let _classifyRequest = null;
rename from browser/extensions/shield-recipe-client/lib/EventEmitter.jsm
rename to toolkit/components/normandy/lib/EventEmitter.jsm
--- a/browser/extensions/shield-recipe-client/lib/EventEmitter.jsm
+++ b/toolkit/components/normandy/lib/EventEmitter.jsm
@@ -1,14 +1,14 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";
 
-ChromeUtils.import("resource://shield-recipe-client/lib/LogManager.jsm");
+ChromeUtils.import("resource://normandy/lib/LogManager.jsm");
 
 var EXPORTED_SYMBOLS = ["EventEmitter"];
 
 const log = LogManager.getLogger("event-emitter");
 
 var EventEmitter = function(sandboxManager) {
   const listeners = {};
 
rename from browser/extensions/shield-recipe-client/lib/FilterExpressions.jsm
rename to toolkit/components/normandy/lib/FilterExpressions.jsm
--- a/browser/extensions/shield-recipe-client/lib/FilterExpressions.jsm
+++ b/toolkit/components/normandy/lib/FilterExpressions.jsm
@@ -1,19 +1,19 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
-ChromeUtils.import("resource://shield-recipe-client/lib/Sampling.jsm");
-ChromeUtils.import("resource://shield-recipe-client/lib/PreferenceFilters.jsm");
+ChromeUtils.import("resource://normandy/lib/Sampling.jsm");
+ChromeUtils.import("resource://normandy/lib/PreferenceFilters.jsm");
 
-ChromeUtils.defineModuleGetter(this, "mozjexl", "resource://shield-recipe-client-vendor/mozjexl.js");
+ChromeUtils.defineModuleGetter(this, "mozjexl", "resource://normandy-vendor/mozjexl.js");
 
 var EXPORTED_SYMBOLS = ["FilterExpressions"];
 
 XPCOMUtils.defineLazyGetter(this, "jexl", () => {
   const jexl = new mozjexl.Jexl();
   jexl.addTransforms({
     date: dateString => new Date(dateString),
     stableSample: Sampling.stableSample,
rename from browser/extensions/shield-recipe-client/lib/Heartbeat.jsm
rename to toolkit/components/normandy/lib/Heartbeat.jsm
--- a/browser/extensions/shield-recipe-client/lib/Heartbeat.jsm
+++ b/toolkit/components/normandy/lib/Heartbeat.jsm
@@ -4,28 +4,28 @@
 
 "use strict";
 
 ChromeUtils.import("resource://gre/modules/AppConstants.jsm");
 ChromeUtils.import("resource://gre/modules/Preferences.jsm");
 ChromeUtils.import("resource://gre/modules/Services.jsm");
 ChromeUtils.import("resource://gre/modules/TelemetryController.jsm");
 ChromeUtils.import("resource://gre/modules/Timer.jsm");
-ChromeUtils.import("resource://shield-recipe-client/lib/CleanupManager.jsm");
-ChromeUtils.import("resource://shield-recipe-client/lib/EventEmitter.jsm");
-ChromeUtils.import("resource://shield-recipe-client/lib/LogManager.jsm");
+ChromeUtils.import("resource://normandy/lib/CleanupManager.jsm");
+ChromeUtils.import("resource://normandy/lib/EventEmitter.jsm");
+ChromeUtils.import("resource://normandy/lib/LogManager.jsm");
 
 Cu.importGlobalProperties(["URL"]); /* globals URL */
 
 var EXPORTED_SYMBOLS = ["Heartbeat"];
 
 const PREF_SURVEY_DURATION = "browser.uitour.surveyDuration";
 const NOTIFICATION_TIME = 3000;
-const HEARTBEAT_CSS_URI = Services.io.newURI("resource://shield-recipe-client/skin/shared/Heartbeat.css");
-const HEARTBEAT_CSS_URI_OSX = Services.io.newURI("resource://shield-recipe-client/skin/osx/Heartbeat.css");
+const HEARTBEAT_CSS_URI = Services.io.newURI("resource://normandy/skin/shared/Heartbeat.css");
+const HEARTBEAT_CSS_URI_OSX = Services.io.newURI("resource://normandy/skin/osx/Heartbeat.css");
 
 const log = LogManager.getLogger("heartbeat");
 const windowsWithInjectedCss = new WeakSet();
 let anyWindowsWithInjectedCss = false;
 
 // Add cleanup handler for CSS injected into windows by Heartbeat
 CleanupManager.addCleanupHandler(() => {
   if (anyWindowsWithInjectedCss) {
@@ -152,17 +152,17 @@ var Heartbeat = class {
         },
       }];
     }
 
     this.notificationBox = this.chromeWindow.document.querySelector("#high-priority-global-notificationbox");
     this.notice = this.notificationBox.appendNotification(
       this.options.message,
       "heartbeat-" + this.options.flowId,
-      "resource://shield-recipe-client/skin/shared/heartbeat-icon.svg",
+      "resource://normandy/skin/shared/heartbeat-icon.svg",
       this.notificationBox.PRIORITY_INFO_HIGH,
       this.buttons,
       eventType => {
         if (eventType !== "removed") {
           return;
         }
         this.maybeNotifyHeartbeat("NotificationClosed");
       }
rename from browser/extensions/shield-recipe-client/lib/LogManager.jsm
rename to toolkit/components/normandy/lib/LogManager.jsm
rename from browser/extensions/shield-recipe-client/lib/NormandyApi.jsm
rename to toolkit/components/normandy/lib/NormandyApi.jsm
--- a/browser/extensions/shield-recipe-client/lib/NormandyApi.jsm
+++ b/toolkit/components/normandy/lib/NormandyApi.jsm
@@ -1,17 +1,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 ChromeUtils.import("resource://gre/modules/Services.jsm");
 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
-ChromeUtils.import("resource://shield-recipe-client/lib/LogManager.jsm");
+ChromeUtils.import("resource://normandy/lib/LogManager.jsm");
 
 ChromeUtils.defineModuleGetter(
   this, "CanonicalJSON", "resource://gre/modules/CanonicalJSON.jsm");
 
 Cu.importGlobalProperties(["fetch", "URL"]); /* globals fetch, URL */
 
 var EXPORTED_SYMBOLS = ["NormandyApi"];
 
rename from browser/extensions/shield-recipe-client/lib/NormandyDriver.jsm
rename to toolkit/components/normandy/lib/NormandyDriver.jsm
--- a/browser/extensions/shield-recipe-client/lib/NormandyDriver.jsm
+++ b/toolkit/components/normandy/lib/NormandyDriver.jsm
@@ -5,28 +5,28 @@
 "use strict";
 
 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 ChromeUtils.import("resource://gre/modules/Services.jsm");
 ChromeUtils.import("resource://gre/modules/Preferences.jsm");
 ChromeUtils.import("resource:///modules/ShellService.jsm");
 ChromeUtils.import("resource://gre/modules/AddonManager.jsm");
 ChromeUtils.import("resource://gre/modules/Timer.jsm");
-ChromeUtils.import("resource://shield-recipe-client/lib/Addons.jsm");
-ChromeUtils.import("resource://shield-recipe-client/lib/LogManager.jsm");
-ChromeUtils.import("resource://shield-recipe-client/lib/Storage.jsm");
-ChromeUtils.import("resource://shield-recipe-client/lib/Heartbeat.jsm");
-ChromeUtils.import("resource://shield-recipe-client/lib/FilterExpressions.jsm");
-ChromeUtils.import("resource://shield-recipe-client/lib/ClientEnvironment.jsm");
-ChromeUtils.import("resource://shield-recipe-client/lib/PreferenceExperiments.jsm");
-ChromeUtils.import("resource://shield-recipe-client/lib/Sampling.jsm");
+ChromeUtils.import("resource://normandy/lib/Addons.jsm");
+ChromeUtils.import("resource://normandy/lib/LogManager.jsm");
+ChromeUtils.import("resource://normandy/lib/Storage.jsm");
+ChromeUtils.import("resource://normandy/lib/Heartbeat.jsm");
+ChromeUtils.import("resource://normandy/lib/FilterExpressions.jsm");
+ChromeUtils.import("resource://normandy/lib/ClientEnvironment.jsm");
+ChromeUtils.import("resource://normandy/lib/PreferenceExperiments.jsm");
+ChromeUtils.import("resource://normandy/lib/Sampling.jsm");
 
 ChromeUtils.defineModuleGetter(this, "UpdateUtils", "resource://gre/modules/UpdateUtils.jsm");
 ChromeUtils.defineModuleGetter(
-  this, "AddonStudies", "resource://shield-recipe-client/lib/AddonStudies.jsm");
+  this, "AddonStudies", "resource://normandy/lib/AddonStudies.jsm");
 
 const {generateUUID} = Cc["@mozilla.org/uuid-generator;1"].getService(Ci.nsIUUIDGenerator);
 
 var EXPORTED_SYMBOLS = ["NormandyDriver"];
 
 const log = LogManager.getLogger("normandy-driver");
 const actionLog = LogManager.getLogger("normandy-driver.actions");
 
rename from browser/extensions/shield-recipe-client/lib/PreferenceExperiments.jsm
rename to toolkit/components/normandy/lib/PreferenceExperiments.jsm
--- a/browser/extensions/shield-recipe-client/lib/PreferenceExperiments.jsm
+++ b/toolkit/components/normandy/lib/PreferenceExperiments.jsm
@@ -50,22 +50,22 @@
  *   The type to report to Telemetry's experiment marker API.
  */
 
 "use strict";
 
 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 
 ChromeUtils.defineModuleGetter(this, "Services", "resource://gre/modules/Services.jsm");
-ChromeUtils.defineModuleGetter(this, "CleanupManager", "resource://shield-recipe-client/lib/CleanupManager.jsm");
+ChromeUtils.defineModuleGetter(this, "CleanupManager", "resource://normandy/lib/CleanupManager.jsm");
 ChromeUtils.defineModuleGetter(this, "JSONFile", "resource://gre/modules/JSONFile.jsm");
 ChromeUtils.defineModuleGetter(this, "OS", "resource://gre/modules/osfile.jsm");
-ChromeUtils.defineModuleGetter(this, "LogManager", "resource://shield-recipe-client/lib/LogManager.jsm");
+ChromeUtils.defineModuleGetter(this, "LogManager", "resource://normandy/lib/LogManager.jsm");
 ChromeUtils.defineModuleGetter(this, "TelemetryEnvironment", "resource://gre/modules/TelemetryEnvironment.jsm");
-ChromeUtils.defineModuleGetter(this, "TelemetryEvents", "resource://shield-recipe-client/lib/TelemetryEvents.jsm");
+ChromeUtils.defineModuleGetter(this, "TelemetryEvents", "resource://normandy/lib/TelemetryEvents.jsm");
 
 var EXPORTED_SYMBOLS = ["PreferenceExperiments"];
 
 const EXPERIMENT_FILE = "shield-preference-experiments.json";
 const STARTUP_EXPERIMENT_PREFS_BRANCH = "extensions.shield-recipe-client.startupExperimentPrefs.";
 
 const MAX_EXPERIMENT_TYPE_LENGTH = 20; // enforced by TelemetryEnvironment
 const EXPERIMENT_TYPE_PREFIX = "normandy-";
rename from browser/extensions/shield-recipe-client/lib/PreferenceFilters.jsm
rename to toolkit/components/normandy/lib/PreferenceFilters.jsm
rename from browser/extensions/shield-recipe-client/lib/RecipeRunner.jsm
rename to toolkit/components/normandy/lib/RecipeRunner.jsm
--- a/browser/extensions/shield-recipe-client/lib/RecipeRunner.jsm
+++ b/toolkit/components/normandy/lib/RecipeRunner.jsm
@@ -1,42 +1,42 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 ChromeUtils.import("resource://gre/modules/Services.jsm");
 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
-ChromeUtils.import("resource://shield-recipe-client/lib/LogManager.jsm");
+ChromeUtils.import("resource://normandy/lib/LogManager.jsm");
 
 XPCOMUtils.defineLazyServiceGetter(this, "timerManager",
                                    "@mozilla.org/updates/timer-manager;1",
                                    "nsIUpdateTimerManager");
 ChromeUtils.defineModuleGetter(this, "Preferences", "resource://gre/modules/Preferences.jsm");
 ChromeUtils.defineModuleGetter(this, "Storage",
-                               "resource://shield-recipe-client/lib/Storage.jsm");
+                               "resource://normandy/lib/Storage.jsm");
 ChromeUtils.defineModuleGetter(this, "NormandyDriver",
-                               "resource://shield-recipe-client/lib/NormandyDriver.jsm");
+                               "resource://normandy/lib/NormandyDriver.jsm");
 ChromeUtils.defineModuleGetter(this, "FilterExpressions",
-                               "resource://shield-recipe-client/lib/FilterExpressions.jsm");
+                               "resource://normandy/lib/FilterExpressions.jsm");
 ChromeUtils.defineModuleGetter(this, "NormandyApi",
-                               "resource://shield-recipe-client/lib/NormandyApi.jsm");
+                               "resource://normandy/lib/NormandyApi.jsm");
 ChromeUtils.defineModuleGetter(this, "SandboxManager",
-                               "resource://shield-recipe-client/lib/SandboxManager.jsm");
+                               "resource://normandy/lib/SandboxManager.jsm");
 ChromeUtils.defineModuleGetter(this, "ClientEnvironment",
-                               "resource://shield-recipe-client/lib/ClientEnvironment.jsm");
+                               "resource://normandy/lib/ClientEnvironment.jsm");
 ChromeUtils.defineModuleGetter(this, "CleanupManager",
-                               "resource://shield-recipe-client/lib/CleanupManager.jsm");
+                               "resource://normandy/lib/CleanupManager.jsm");
 ChromeUtils.defineModuleGetter(this, "ActionSandboxManager",
-                               "resource://shield-recipe-client/lib/ActionSandboxManager.jsm");
+                               "resource://normandy/lib/ActionSandboxManager.jsm");
 ChromeUtils.defineModuleGetter(this, "AddonStudies",
-                               "resource://shield-recipe-client/lib/AddonStudies.jsm");
+                               "resource://normandy/lib/AddonStudies.jsm");
 ChromeUtils.defineModuleGetter(this, "Uptake",
-                               "resource://shield-recipe-client/lib/Uptake.jsm");
+                               "resource://normandy/lib/Uptake.jsm");
 
 Cu.importGlobalProperties(["fetch"]);
 
 var EXPORTED_SYMBOLS = ["RecipeRunner"];
 
 const log = LogManager.getLogger("recipe-runner");
 const TIMER_NAME = "recipe-client-addon-run";
 const PREF_CHANGED_TOPIC = "nsPref:changed";
rename from browser/extensions/shield-recipe-client/lib/Sampling.jsm
rename to toolkit/components/normandy/lib/Sampling.jsm
rename from browser/extensions/shield-recipe-client/lib/SandboxManager.jsm
rename to toolkit/components/normandy/lib/SandboxManager.jsm
rename from browser/extensions/shield-recipe-client/lib/ShieldPreferences.jsm
rename to toolkit/components/normandy/lib/ShieldPreferences.jsm
--- a/browser/extensions/shield-recipe-client/lib/ShieldPreferences.jsm
+++ b/toolkit/components/normandy/lib/ShieldPreferences.jsm
@@ -5,20 +5,20 @@
 
 ChromeUtils.import("resource://gre/modules/Services.jsm");
 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 
 ChromeUtils.defineModuleGetter(
   this, "AppConstants", "resource://gre/modules/AppConstants.jsm"
 );
 ChromeUtils.defineModuleGetter(
-  this, "AddonStudies", "resource://shield-recipe-client/lib/AddonStudies.jsm"
+  this, "AddonStudies", "resource://normandy/lib/AddonStudies.jsm"
 );
 ChromeUtils.defineModuleGetter(
-  this, "CleanupManager", "resource://shield-recipe-client/lib/CleanupManager.jsm"
+  this, "CleanupManager", "resource://normandy/lib/CleanupManager.jsm"
 );
 
 var EXPORTED_SYMBOLS = ["ShieldPreferences"];
 
 const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
 const NS_PREFBRANCH_PREFCHANGE_TOPIC_ID = "nsPref:changed"; // from modules/libpref/nsIPrefBranch.idl
 const FHR_UPLOAD_ENABLED_PREF = "datareporting.healthreport.uploadEnabled";
 const OPT_OUT_STUDIES_ENABLED_PREF = "app.shield.optoutstudies.enabled";
rename from browser/extensions/shield-recipe-client/lib/Storage.jsm
rename to toolkit/components/normandy/lib/Storage.jsm
rename from browser/extensions/shield-recipe-client/lib/TelemetryEvents.jsm
rename to toolkit/components/normandy/lib/TelemetryEvents.jsm
rename from browser/extensions/shield-recipe-client/lib/Uptake.jsm
rename to toolkit/components/normandy/lib/Uptake.jsm
rename from browser/extensions/shield-recipe-client/lib/Utils.jsm
rename to toolkit/components/normandy/lib/Utils.jsm
--- a/browser/extensions/shield-recipe-client/lib/Utils.jsm
+++ b/toolkit/components/normandy/lib/Utils.jsm
@@ -1,15 +1,15 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
-ChromeUtils.import("resource://shield-recipe-client/lib/LogManager.jsm");
+ChromeUtils.import("resource://normandy/lib/LogManager.jsm");
 
 var EXPORTED_SYMBOLS = ["Utils"];
 
 const log = LogManager.getLogger("utils");
 
 var Utils = {
   /**
    * Convert an array of objects to an object. Each item is keyed by the value
rename from browser/extensions/shield-recipe-client/moz.build
rename to toolkit/components/normandy/moz.build
--- a/browser/extensions/shield-recipe-client/moz.build
+++ b/toolkit/components/normandy/moz.build
@@ -1,24 +1,15 @@
 # -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # 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/.
 
-DEFINES['MOZ_APP_VERSION'] = CONFIG['MOZ_APP_VERSION']
-DEFINES['MOZ_APP_MAXVERSION'] = CONFIG['MOZ_APP_MAXVERSION']
-
-FINAL_TARGET_FILES.features['shield-recipe-client@mozilla.org'] += [
-  'bootstrap.js',
-]
-
-FINAL_TARGET_PP_FILES.features['shield-recipe-client@mozilla.org'] += [
-  'install.rdf.in'
-]
-
-BROWSER_CHROME_MANIFESTS += ['test/browser/browser.ini']
-
-XPCSHELL_TESTS_MANIFESTS += ['test/unit/xpcshell.ini']
+with Files('**'):
+    BUG_COMPONENT = ('Firefox', 'Normandy Client')
 
 JAR_MANIFESTS += ['jar.mn']
 
-SPHINX_TREES['shield-recipe-client'] = 'docs'
+SPHINX_TREES['normandy'] = 'docs'
+
+BROWSER_CHROME_MANIFESTS += ['test/browser/browser.ini']
+XPCSHELL_TESTS_MANIFESTS += ['test/unit/xpcshell.ini']
rename from browser/extensions/shield-recipe-client/skin/osx/Heartbeat.css
rename to toolkit/components/normandy/skin/osx/Heartbeat.css
rename from browser/extensions/shield-recipe-client/skin/shared/Heartbeat.css
rename to toolkit/components/normandy/skin/shared/Heartbeat.css
--- a/browser/extensions/shield-recipe-client/skin/shared/Heartbeat.css
+++ b/toolkit/components/normandy/skin/shared/Heartbeat.css
@@ -109,19 +109,19 @@ notification.heartbeat {
   -moz-box-ordinal-group: 3;
 }
 
 .heartbeat > #star-rating-container > #star2 {
   -moz-box-ordinal-group: 2;
 }
 
 .heartbeat > #star-rating-container > .star-x {
-  background: url("resource://shield-recipe-client/skin/shared/heartbeat-star-off.svg");
+  background: url("resource://normandy/skin/shared/heartbeat-star-off.svg");
   cursor: pointer;
   height: 16px;
   margin-inline-end: 4px !important; /* Overrides the margin-inline-end for all platforms defined in the .plain class */
   width: 16px;
 }
 
 .heartbeat > #star-rating-container > .star-x:hover,
 .heartbeat > #star-rating-container > .star-x:hover ~ .star-x {
-  background: url("resource://shield-recipe-client/skin/shared/heartbeat-star-lit.svg");
+  background: url("resource://normandy/skin/shared/heartbeat-star-lit.svg");
 }
rename from browser/extensions/shield-recipe-client/skin/shared/heartbeat-icon.svg
rename to toolkit/components/normandy/skin/shared/heartbeat-icon.svg
rename from browser/extensions/shield-recipe-client/skin/shared/heartbeat-star-lit.svg
rename to toolkit/components/normandy/skin/shared/heartbeat-star-lit.svg
rename from browser/extensions/shield-recipe-client/skin/shared/heartbeat-star-off.svg
rename to toolkit/components/normandy/skin/shared/heartbeat-star-off.svg
rename from browser/extensions/shield-recipe-client/test/.eslintrc.js
rename to toolkit/components/normandy/test/.eslintrc.js
rename from browser/extensions/shield-recipe-client/test/browser/.eslintrc.js
rename to toolkit/components/normandy/test/browser/.eslintrc.js
rename from browser/extensions/shield-recipe-client/test/browser/action_server.sjs
rename to toolkit/components/normandy/test/browser/action_server.sjs
rename from browser/extensions/shield-recipe-client/test/browser/browser.ini
rename to toolkit/components/normandy/test/browser/browser.ini
--- a/browser/extensions/shield-recipe-client/test/browser/browser.ini
+++ b/toolkit/components/normandy/test/browser/browser.ini
@@ -1,25 +1,25 @@
 [DEFAULT]
 support-files =
   action_server.sjs
   fixtures/normandy.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_ActionSandboxManager.js]
 [browser_Addons.js]
 [browser_AddonStudies.js]
-[browser_bootstrap.js]
 [browser_CleanupManager.js]
-[browser_NormandyDriver.js]
+[browser_ClientEnvironment.js]
+[browser_EventEmitter.js]
 [browser_FilterExpressions.js]
-[browser_EventEmitter.js]
-[browser_Storage.js]
 [browser_Heartbeat.js]
-[browser_RecipeRunner.js]
 [browser_LogManager.js]
-[browser_ClientEnvironment.js]
-[browser_ShieldRecipeClient.js]
-[browser_ShieldPreferences.js]
+[browser_Normandy.js]
+[browser_NormandyDriver.js]
 [browser_PreferenceExperiments.js]
-[browser_about_studies.js]
-[browser_about_preferences.js]
-# Skip this test when FHR/Telemetry aren't available.
-skip-if = !healthreport || !telemetry
+[browser_RecipeRunner.js]
+[browser_ShieldPreferences.js]
+[browser_ShieldRecipeClient.js]
+[browser_Storage.js]
rename from browser/extensions/shield-recipe-client/test/browser/browser_ActionSandboxManager.js
rename to toolkit/components/normandy/test/browser/browser_ActionSandboxManager.js
--- a/browser/extensions/shield-recipe-client/test/browser/browser_ActionSandboxManager.js
+++ b/toolkit/components/normandy/test/browser/browser_ActionSandboxManager.js
@@ -1,12 +1,12 @@
 "use strict";
 
-ChromeUtils.import("resource://shield-recipe-client/lib/ActionSandboxManager.jsm", this);
-ChromeUtils.import("resource://shield-recipe-client/lib/NormandyDriver.jsm", this);
+ChromeUtils.import("resource://normandy/lib/ActionSandboxManager.jsm", this);
+ChromeUtils.import("resource://normandy/lib/NormandyDriver.jsm", this);
 
 async function withManager(script, testFunction) {
   const manager = new ActionSandboxManager(script);
   manager.addHold("testing");
   await testFunction(manager);
   manager.removeHold("testing");
 }
 
rename from browser/extensions/shield-recipe-client/test/browser/browser_AddonStudies.js
rename to toolkit/components/normandy/test/browser/browser_AddonStudies.js
--- a/browser/extensions/shield-recipe-client/test/browser/browser_AddonStudies.js
+++ b/toolkit/components/normandy/test/browser/browser_AddonStudies.js
@@ -1,16 +1,16 @@
 "use strict";
 
 ChromeUtils.import("resource://gre/modules/IndexedDB.jsm", this);
 ChromeUtils.import("resource://testing-common/TestUtils.jsm", this);
 ChromeUtils.import("resource://testing-common/AddonTestUtils.jsm", this);
-ChromeUtils.import("resource://shield-recipe-client/lib/Addons.jsm", this);
-ChromeUtils.import("resource://shield-recipe-client/lib/AddonStudies.jsm", this);
-ChromeUtils.import("resource://shield-recipe-client/lib/TelemetryEvents.jsm", this);
+ChromeUtils.import("resource://normandy/lib/Addons.jsm", this);
+ChromeUtils.import("resource://normandy/lib/AddonStudies.jsm", this);
+ChromeUtils.import("resource://normandy/lib/TelemetryEvents.jsm", this);
 
 // Initialize test utils
 AddonTestUtils.initMochitest(this);
 
 let _startArgsFactoryId = 1;
 function startArgsFactory(args) {
   return Object.assign({
     recipeId: _startArgsFactoryId++,
rename from browser/extensions/shield-recipe-client/test/browser/browser_Addons.js
rename to toolkit/components/normandy/test/browser/browser_Addons.js
--- a/browser/extensions/shield-recipe-client/test/browser/browser_Addons.js
+++ b/toolkit/components/normandy/test/browser/browser_Addons.js
@@ -1,13 +1,13 @@
 "use strict";
 
 ChromeUtils.import("resource://testing-common/AddonTestUtils.jsm", this);
 ChromeUtils.import("resource://gre/modules/Services.jsm", this);
-ChromeUtils.import("resource://shield-recipe-client/lib/Addons.jsm", this);
+ChromeUtils.import("resource://normandy/lib/Addons.jsm", this);
 
 // Initialize test utils
 AddonTestUtils.initMochitest(this);
 
 const testInstallId = "testInstallUpdate@example.com";
 decorate_task(
   withInstalledWebExtension({version: "1.0", id: testInstallId}),
   withWebExtension({version: "2.0", id: testInstallId}),
rename from browser/extensions/shield-recipe-client/test/browser/browser_CleanupManager.js
rename to toolkit/components/normandy/test/browser/browser_CleanupManager.js
--- a/browser/extensions/shield-recipe-client/test/browser/browser_CleanupManager.js
+++ b/toolkit/components/normandy/test/browser/browser_CleanupManager.js
@@ -1,11 +1,11 @@
 "use strict";
 
-ChromeUtils.import("resource://shield-recipe-client/lib/CleanupManager.jsm", this); /* global CleanupManagerClass */
+ChromeUtils.import("resource://normandy/lib/CleanupManager.jsm", this); /* global CleanupManagerClass */
 
 add_task(async function testCleanupManager() {
   const spy1 = sinon.spy();
   const spy2 = sinon.spy();
   const spy3 = sinon.spy();
 
   const manager = new CleanupManager.constructor();
   manager.addCleanupHandler(spy1);
rename from browser/extensions/shield-recipe-client/test/browser/browser_ClientEnvironment.js
rename to toolkit/components/normandy/test/browser/browser_ClientEnvironment.js
--- a/browser/extensions/shield-recipe-client/test/browser/browser_ClientEnvironment.js
+++ b/toolkit/components/normandy/test/browser/browser_ClientEnvironment.js
@@ -1,16 +1,16 @@
 "use strict";
 
 ChromeUtils.import("resource://gre/modules/Services.jsm", this);
 ChromeUtils.import("resource://gre/modules/TelemetryController.jsm", this);
 ChromeUtils.import("resource://gre/modules/AddonManager.jsm", this);
 ChromeUtils.import("resource://testing-common/AddonTestUtils.jsm", this);
-ChromeUtils.import("resource://shield-recipe-client/lib/ClientEnvironment.jsm", this);
-ChromeUtils.import("resource://shield-recipe-client/lib/PreferenceExperiments.jsm", this);
+ChromeUtils.import("resource://normandy/lib/ClientEnvironment.jsm", this);
+ChromeUtils.import("resource://normandy/lib/PreferenceExperiments.jsm", this);
 
 
 add_task(async function testTelemetry() {
   // setup
   await SpecialPowers.pushPrefEnv({set: [["privacy.reduceTimerPrecision", true]]});
 
   await TelemetryController.submitExternalPing("testfoo", {foo: 1});
   await TelemetryController.submitExternalPing("testbar", {bar: 2});
rename from browser/extensions/shield-recipe-client/test/browser/browser_EventEmitter.js
rename to toolkit/components/normandy/test/browser/browser_EventEmitter.js
--- a/browser/extensions/shield-recipe-client/test/browser/browser_EventEmitter.js
+++ b/toolkit/components/normandy/test/browser/browser_EventEmitter.js
@@ -1,12 +1,12 @@
 "use strict";
 
 ChromeUtils.import("resource://gre/modules/Services.jsm", this);
-ChromeUtils.import("resource://shield-recipe-client/lib/EventEmitter.jsm", this);
+ChromeUtils.import("resource://normandy/lib/EventEmitter.jsm", this);
 
 const evidence = {
   a: 0,
   b: 0,
   c: 0,
   log: "",
 };
 
rename from browser/extensions/shield-recipe-client/test/browser/browser_FilterExpressions.js
rename to toolkit/components/normandy/test/browser/browser_FilterExpressions.js
--- a/browser/extensions/shield-recipe-client/test/browser/browser_FilterExpressions.js
+++ b/toolkit/components/normandy/test/browser/browser_FilterExpressions.js
@@ -1,11 +1,11 @@
 "use strict";
 
-ChromeUtils.import("resource://shield-recipe-client/lib/FilterExpressions.jsm", this);
+ChromeUtils.import("resource://normandy/lib/FilterExpressions.jsm", this);
 
 // Basic JEXL tests
 add_task(async function() {
   let val;
   // Test that basic expressions work
   val = await FilterExpressions.eval("2+2");
   is(val, 4, "basic expression works");
 
rename from browser/extensions/shield-recipe-client/test/browser/browser_Heartbeat.js
rename to toolkit/components/normandy/test/browser/browser_Heartbeat.js
--- a/browser/extensions/shield-recipe-client/test/browser/browser_Heartbeat.js
+++ b/toolkit/components/normandy/test/browser/browser_Heartbeat.js
@@ -1,13 +1,13 @@
 "use strict";
 
 ChromeUtils.import("resource://gre/modules/Services.jsm", this);
-ChromeUtils.import("resource://shield-recipe-client/lib/Heartbeat.jsm", this);
-ChromeUtils.import("resource://shield-recipe-client/lib/SandboxManager.jsm", this);
+ChromeUtils.import("resource://normandy/lib/Heartbeat.jsm", this);
+ChromeUtils.import("resource://normandy/lib/SandboxManager.jsm", this);
 
 /**
  * Assert an array is in non-descending order, and that every element is a number
  */
 function assertOrdered(arr) {
   for (let i = 0; i < arr.length; i++) {
     Assert.equal(typeof arr[i], "number", `element ${i} is type "number"`);
   }
rename from browser/extensions/shield-recipe-client/test/browser/browser_LogManager.js
rename to toolkit/components/normandy/test/browser/browser_LogManager.js
--- a/browser/extensions/shield-recipe-client/test/browser/browser_LogManager.js
+++ b/toolkit/components/normandy/test/browser/browser_LogManager.js
@@ -1,12 +1,12 @@
 "use strict";
 
 ChromeUtils.import("resource://gre/modules/Log.jsm", this);
-ChromeUtils.import("resource://shield-recipe-client/lib/LogManager.jsm", this);
+ChromeUtils.import("resource://normandy/lib/LogManager.jsm", this);
 
 add_task(async function() {
   // Ensure that configuring the logger affects all generated loggers.
   const firstLogger = LogManager.getLogger("first");
   LogManager.configure(5);
   const secondLogger = LogManager.getLogger("second");
   is(firstLogger.level, 5, "First logger level inherited from root logger.");
   is(secondLogger.level, 5, "Second logger level inherited from root logger.");
rename from browser/extensions/shield-recipe-client/test/browser/browser_bootstrap.js
rename to toolkit/components/normandy/test/browser/browser_Normandy.js
--- a/browser/extensions/shield-recipe-client/test/browser/browser_bootstrap.js
+++ b/toolkit/components/normandy/test/browser/browser_Normandy.js
@@ -1,60 +1,42 @@
 "use strict";
 
-ChromeUtils.import("resource://shield-recipe-client/lib/ShieldRecipeClient.jsm", this);
-ChromeUtils.import("resource://shield-recipe-client/lib/PreferenceExperiments.jsm", this);
-
-// We can't import bootstrap.js directly since it isn't in the jar manifest, but
-// we can use Addon.getResourceURI to get a path to the file and import using
-// that instead.
-ChromeUtils.import("resource://gre/modules/AddonManager.jsm", this);
-const bootstrapPromise = AddonManager.getAddonByID("shield-recipe-client@mozilla.org").then(addon => {
-  const bootstrapUri = addon.getResourceURI("bootstrap.js");
-  const {Bootstrap} = ChromeUtils.import(bootstrapUri.spec, {});
-  return Bootstrap;
-});
-
-// Use a decorator to get around getAddonByID being async.
-function withBootstrap(testFunction) {
-  return async function wrappedTestFunction(...args) {
-    const Bootstrap = await bootstrapPromise;
-    return testFunction(...args, Bootstrap);
-  };
-}
+ChromeUtils.import("resource://normandy/Normandy.jsm", this);
+ChromeUtils.import("resource://normandy/lib/ShieldRecipeClient.jsm", this);
+ChromeUtils.import("resource://normandy/lib/PreferenceExperiments.jsm", this);
 
 const initPref1 = "test.initShieldPrefs1";
 const initPref2 = "test.initShieldPrefs2";
 const initPref3 = "test.initShieldPrefs3";
 
 const experimentPref1 = "test.initExperimentPrefs1";
 const experimentPref2 = "test.initExperimentPrefs2";
 const experimentPref3 = "test.initExperimentPrefs3";
 const experimentPref4 = "test.initExperimentPrefs4";
 
 decorate_task(
-  withBootstrap,
-  async function testInitShieldPrefs(Bootstrap) {
+  async function testInitShieldPrefs() {
     const defaultBranch = Services.prefs.getDefaultBranch("");
 
     const prefDefaults = {
       [initPref1]: true,
       [initPref2]: 2,
       [initPref3]: "string",
     };
 
     for (const pref of Object.keys(prefDefaults)) {
       is(
         defaultBranch.getPrefType(pref),
         defaultBranch.PREF_INVALID,
         `Pref ${pref} don't exist before being initialized.`,
       );
     }
 
-    Bootstrap.initShieldPrefs(prefDefaults);
+    Normandy.initShieldPrefs(prefDefaults);
 
     ok(
       defaultBranch.getBoolPref(initPref1),
       `Pref ${initPref1} has a default value after being initialized.`,
     );
     is(
       defaultBranch.getIntPref(initPref2),
       2,
@@ -73,46 +55,44 @@ decorate_task(
       );
     }
 
     defaultBranch.deleteBranch("test.");
   },
 );
 
 decorate_task(
-  withBootstrap,
-  async function testInitShieldPrefsError(Bootstrap) {
+  async function testInitShieldPrefsError() {
     Assert.throws(
-      () => Bootstrap.initShieldPrefs({"test.prefTypeError": new Date()}),
+      () => Normandy.initShieldPrefs({"test.prefTypeError": new Date()}),
       "initShieldPrefs throws when given an invalid type for the pref value.",
     );
   },
 );
 
 decorate_task(
   withPrefEnv({
     set: [
       [`extensions.shield-recipe-client.startupExperimentPrefs.${experimentPref1}`, true],
       [`extensions.shield-recipe-client.startupExperimentPrefs.${experimentPref2}`, 2],
       [`extensions.shield-recipe-client.startupExperimentPrefs.${experimentPref3}`, "string"],
     ],
     clear: [[experimentPref1], [experimentPref2], [experimentPref3]],
   }),
-  withBootstrap,
-  async function testInitExperimentPrefs(Bootstrap) {
+  async function testInitExperimentPrefs() {
     const defaultBranch = Services.prefs.getDefaultBranch("");
     for (const pref of [experimentPref1, experimentPref2, experimentPref3]) {
       is(
         defaultBranch.getPrefType(pref),
         defaultBranch.PREF_INVALID,
         `Pref ${pref} don't exist before being initialized.`,
       );
     }
 
-    Bootstrap.initExperimentPrefs();
+    Normandy.initExperimentPrefs();
 
     ok(
       defaultBranch.getBoolPref(experimentPref1),
       `Pref ${experimentPref1} has a default value after being initialized.`,
     );
     is(
       defaultBranch.getIntPref(experimentPref2),
       2,
@@ -134,83 +114,60 @@ decorate_task(
 );
 
 decorate_task(
   withPrefEnv({
     set: [
       ["extensions.shield-recipe-client.startupExperimentPrefs.test.existingPref", "experiment"],
     ],
   }),
-  withBootstrap,
-  async function testInitExperimentPrefsExisting(Bootstrap) {
+  async function testInitExperimentPrefsExisting() {
     const defaultBranch = Services.prefs.getDefaultBranch("");
     defaultBranch.setCharPref("test.existingPref", "default");
-    Bootstrap.initExperimentPrefs();
+    Normandy.initExperimentPrefs();
     is(
       defaultBranch.getCharPref("test.existingPref"),
       "experiment",
       "initExperimentPrefs overwrites the default values of existing preferences.",
     );
   },
 );
 
 decorate_task(
   withPrefEnv({
     set: [
       ["extensions.shield-recipe-client.startupExperimentPrefs.test.mismatchPref", "experiment"],
     ],
   }),
-  withBootstrap,
-  async function testInitExperimentPrefsMismatch(Bootstrap) {
+  async function testInitExperimentPrefsMismatch() {
     const defaultBranch = Services.prefs.getDefaultBranch("");
     defaultBranch.setIntPref("test.mismatchPref", 2);
-    Bootstrap.initExperimentPrefs();
+    Normandy.initExperimentPrefs();
     is(
       defaultBranch.getPrefType("test.mismatchPref"),
       Services.prefs.PREF_INT,
       "initExperimentPrefs skips prefs that don't match the existing default value's type.",
     );
   },
 );
 
 decorate_task(
-  withBootstrap,
-  async function testStartupDelayed(Bootstrap) {
-    const finishStartupStub = sinon.stub(Bootstrap, "finishStartup");
-    try {
-      Bootstrap.startup(undefined, 1); // 1 == APP_STARTUP
-      ok(
-        !finishStartupStub.called,
-        "When started at app startup, do not call ShieldRecipeClient.startup immediately.",
-      );
+  withStub(Normandy, "finishInit"),
+  async function testStartupDelayed(finishInitStub) {
+    Normandy.init();
+    ok(
+      !finishInitStub.called,
+      "When initialized, do not call finishInit immediately.",
+    );
 
-      Bootstrap.observe(null, "sessionstore-windows-restored");
-      ok(
-        finishStartupStub.called,
-        "Once the sessionstore-windows-restored event is observed, call ShieldRecipeClient.startup.",
-      );
-    } finally {
-      finishStartupStub.restore();
-    }
-  },
-);
-
-decorate_task(
-  withBootstrap,
-  async function testStartupDelayed(Bootstrap) {
-    const finishStartupStub = sinon.stub(Bootstrap, "finishStartup");
-    try {
-      Bootstrap.startup(undefined, 3); // 3 == ADDON_ENABLED
-      ok(
-        finishStartupStub.called,
-        "When the add-on is enabled outside app startup, call ShieldRecipeClient.startup immediately.",
-      );
-    } finally {
-      finishStartupStub.restore();
-    }
+    Normandy.observe(null, "sessionstore-windows-restored");
+    ok(
+      finishInitStub.called,
+      "Once the sessionstore-windows-restored event is observed, finishInit should be called.",
+    );
   },
 );
 
 // During startup, preferences that are changed for experiments should
 // be record by calling PreferenceExperiments.recordOriginalValues.
 decorate_task(
   withPrefEnv({
     set: [
@@ -222,49 +179,47 @@ decorate_task(
     clear: [
       [experimentPref1],
       [experimentPref2],
       [experimentPref3],
       [experimentPref4],
       ["extensions.shield-recipe-client.startupExperimentPrefs.existingPref"],
     ],
   }),
-  withBootstrap,
   withStub(PreferenceExperiments, "recordOriginalValues"),
-  async function testInitExperimentPrefs(Bootstrap, recordOriginalValuesStub) {
+  async function testInitExperimentPrefs(recordOriginalValuesStub) {
     const defaultBranch = Services.prefs.getDefaultBranch("");
 
     defaultBranch.setBoolPref(experimentPref1, false);
     defaultBranch.setIntPref(experimentPref2, 1);
     defaultBranch.setCharPref(experimentPref3, "original string");
     // experimentPref4 is left unset
 
-    Bootstrap.initExperimentPrefs();
-    await Bootstrap.finishStartup();
+    Normandy.initExperimentPrefs();
+    await Normandy.finishInit();
 
     Assert.deepEqual(
       recordOriginalValuesStub.getCall(0).args,
       [{
         [experimentPref1]: false,
         [experimentPref2]: 1,
         [experimentPref3]: "original string",
         [experimentPref4]: null,  // null because it was not initially set.
       }],
-      "finishStartup should record original values of the prefs initExperimentPrefs changed",
+      "finishInit should record original values of the prefs initExperimentPrefs changed",
     );
   },
 );
 
 // Test that startup prefs are handled correctly when there is a value on the user branch but not the default branch.
 decorate_task(
   withPrefEnv({
     set: [
       ["extensions.shield-recipe-client.startupExperimentPrefs.testing.does-not-exist", "foo"],
       ["testing.does-not-exist", "foo"],
     ],
   }),
-  withBootstrap,
   withStub(PreferenceExperiments, "recordOriginalValues"),
-  async function testInitExperimentPrefsNoDefaultValue(Bootstrap) {
-    Bootstrap.initExperimentPrefs();
+  async function testInitExperimentPrefsNoDefaultValue() {
+    Normandy.initExperimentPrefs();
     ok(true, "initExperimentPrefs should not throw for non-existant prefs");
   },
 );
rename from browser/extensions/shield-recipe-client/test/browser/browser_NormandyDriver.js
rename to toolkit/components/normandy/test/browser/browser_NormandyDriver.js
--- a/browser/extensions/shield-recipe-client/test/browser/browser_NormandyDriver.js
+++ b/toolkit/components/normandy/test/browser/browser_NormandyDriver.js
@@ -1,15 +1,15 @@
 "use strict";
 
 ChromeUtils.import("resource://gre/modules/AppConstants.jsm");
 ChromeUtils.import("resource://testing-common/AddonTestUtils.jsm", this);
-ChromeUtils.import("resource://shield-recipe-client/lib/AddonStudies.jsm", this);
-ChromeUtils.import("resource://shield-recipe-client/lib/NormandyDriver.jsm", this);
-ChromeUtils.import("resource://shield-recipe-client/lib/PreferenceExperiments.jsm", this);
+ChromeUtils.import("resource://normandy/lib/AddonStudies.jsm", this);
+ChromeUtils.import("resource://normandy/lib/NormandyDriver.jsm", this);
+ChromeUtils.import("resource://normandy/lib/PreferenceExperiments.jsm", this);
 
 add_task(withDriver(Assert, async function uuids(driver) {
   // Test that it is a UUID
   const uuid1 = driver.uuid();
   ok(UUID_REGEX.test(uuid1), "valid uuid format");
 
   // Test that UUIDs are different each time
   const uuid2 = driver.uuid();
rename from browser/extensions/shield-recipe-client/test/browser/browser_PreferenceExperiments.js
rename to toolkit/components/normandy/test/browser/browser_PreferenceExperiments.js
--- a/browser/extensions/shield-recipe-client/test/browser/browser_PreferenceExperiments.js
+++ b/toolkit/components/normandy/test/browser/browser_PreferenceExperiments.js
@@ -1,15 +1,15 @@
 "use strict";
 
 ChromeUtils.import("resource://gre/modules/Preferences.jsm", this);
 ChromeUtils.import("resource://gre/modules/TelemetryEnvironment.jsm", this);
-ChromeUtils.import("resource://shield-recipe-client/lib/PreferenceExperiments.jsm", this);
-ChromeUtils.import("resource://shield-recipe-client/lib/CleanupManager.jsm", this);
-ChromeUtils.import("resource://shield-recipe-client/lib/TelemetryEvents.jsm", this);
+ChromeUtils.import("resource://normandy/lib/PreferenceExperiments.jsm", this);
+ChromeUtils.import("resource://normandy/lib/CleanupManager.jsm", this);
+ChromeUtils.import("resource://normandy/lib/TelemetryEvents.jsm", this);
 
 // Save ourselves some typing
 const {withMockExperiments} = PreferenceExperiments;
 const DefaultPreferences = new Preferences({defaultBranch: true});
 const startupPrefs = "extensions.shield-recipe-client.startupExperimentPrefs";
 
 function experimentFactory(attrs) {
   return Object.assign({
rename from browser/extensions/shield-recipe-client/test/browser/browser_RecipeRunner.js
rename to toolkit/components/normandy/test/browser/browser_RecipeRunner.js
--- a/browser/extensions/shield-recipe-client/test/browser/browser_RecipeRunner.js
+++ b/toolkit/components/normandy/test/browser/browser_RecipeRunner.js
@@ -1,18 +1,18 @@
 "use strict";
 
 ChromeUtils.import("resource://testing-common/TestUtils.jsm", this);
-ChromeUtils.import("resource://shield-recipe-client/lib/RecipeRunner.jsm", this);
-ChromeUtils.import("resource://shield-recipe-client/lib/ClientEnvironment.jsm", this);
-ChromeUtils.import("resource://shield-recipe-client/lib/CleanupManager.jsm", this);
-ChromeUtils.import("resource://shield-recipe-client/lib/NormandyApi.jsm", this);
-ChromeUtils.import("resource://shield-recipe-client/lib/ActionSandboxManager.jsm", this);
-ChromeUtils.import("resource://shield-recipe-client/lib/AddonStudies.jsm", this);
-ChromeUtils.import("resource://shield-recipe-client/lib/Uptake.jsm", this);
+ChromeUtils.import("resource://normandy/lib/RecipeRunner.jsm", this);
+ChromeUtils.import("resource://normandy/lib/ClientEnvironment.jsm", this);
+ChromeUtils.import("resource://normandy/lib/CleanupManager.jsm", this);
+ChromeUtils.import("resource://normandy/lib/NormandyApi.jsm", this);
+ChromeUtils.import("resource://normandy/lib/ActionSandboxManager.jsm", this);
+ChromeUtils.import("resource://normandy/lib/AddonStudies.jsm", this);
+ChromeUtils.import("resource://normandy/lib/Uptake.jsm", this);
 
 add_task(async function getFilterContext() {
   const recipe = {id: 17, arguments: {foo: "bar"}, unrelated: false};
   const context = RecipeRunner.getFilterContext(recipe);
 
   // Test for expected properties in the filter expression context.
   const expectedNormandyKeys = [
     "channel",
rename from browser/extensions/shield-recipe-client/test/browser/browser_ShieldPreferences.js
rename to toolkit/components/normandy/test/browser/browser_ShieldPreferences.js
--- a/browser/extensions/shield-recipe-client/test/browser/browser_ShieldPreferences.js
+++ b/toolkit/components/normandy/test/browser/browser_ShieldPreferences.js
@@ -1,12 +1,12 @@
 "use strict";
 
 ChromeUtils.import("resource://gre/modules/Services.jsm", this);
-ChromeUtils.import("resource://shield-recipe-client/lib/AddonStudies.jsm", this);
+ChromeUtils.import("resource://normandy/lib/AddonStudies.jsm", this);
 
 const OPT_OUT_PREF = "app.shield.optoutstudies.enabled";
 
 decorate_task(
   withMockPreferences,
   AddonStudies.withStudies([
     studyFactory({active: true}),
     studyFactory({active: true}),
rename from browser/extensions/shield-recipe-client/test/browser/browser_ShieldRecipeClient.js
rename to toolkit/components/normandy/test/browser/browser_ShieldRecipeClient.js
--- a/browser/extensions/shield-recipe-client/test/browser/browser_ShieldRecipeClient.js
+++ b/toolkit/components/normandy/test/browser/browser_ShieldRecipeClient.js
@@ -1,16 +1,16 @@
 "use strict";
 
-ChromeUtils.import("resource://shield-recipe-client/lib/ShieldRecipeClient.jsm", this);
-ChromeUtils.import("resource://shield-recipe-client/lib/RecipeRunner.jsm", this);
-ChromeUtils.import("resource://shield-recipe-client/lib/PreferenceExperiments.jsm", this);
-ChromeUtils.import("resource://shield-recipe-client-content/AboutPages.jsm", this);
-ChromeUtils.import("resource://shield-recipe-client/lib/AddonStudies.jsm", this);
-ChromeUtils.import("resource://shield-recipe-client/lib/TelemetryEvents.jsm", this);
+ChromeUtils.import("resource://normandy-content/AboutPages.jsm", this);
+ChromeUtils.import("resource://normandy/lib/AddonStudies.jsm", this);
+ChromeUtils.import("resource://normandy/lib/PreferenceExperiments.jsm", this);
+ChromeUtils.import("resource://normandy/lib/RecipeRunner.jsm", this);
+ChromeUtils.import("resource://normandy/lib/ShieldRecipeClient.jsm", this);
+ChromeUtils.import("resource://normandy/lib/TelemetryEvents.jsm", this);
 
 function withStubInits(testFunction) {
   return decorate(
     withStub(AboutPages, "init"),
     withStub(AddonStudies, "init"),
     withStub(PreferenceExperiments, "init"),
     withStub(RecipeRunner, "init"),
     withStub(TelemetryEvents, "init"),
rename from browser/extensions/shield-recipe-client/test/browser/browser_Storage.js
rename to toolkit/components/normandy/test/browser/browser_Storage.js
--- a/browser/extensions/shield-recipe-client/test/browser/browser_Storage.js
+++ b/toolkit/components/normandy/test/browser/browser_Storage.js
@@ -1,12 +1,12 @@
 "use strict";
 
-ChromeUtils.import("resource://shield-recipe-client/lib/Storage.jsm", this);
-ChromeUtils.import("resource://shield-recipe-client/lib/SandboxManager.jsm", this);
+ChromeUtils.import("resource://normandy/lib/Storage.jsm", this);
+ChromeUtils.import("resource://normandy/lib/SandboxManager.jsm", this);
 
 add_task(async function() {
   const store1 = new Storage("prefix1");
   const store2 = new Storage("prefix2");
 
   // Make sure values return null before being set
   Assert.equal(await store1.getItem("key"), null);
   Assert.equal(await store2.getItem("key"), null);
rename from browser/extensions/shield-recipe-client/test/browser/browser_about_preferences.js
rename to toolkit/components/normandy/test/browser/browser_about_preferences.js
rename from browser/extensions/shield-recipe-client/test/browser/browser_about_studies.js
rename to toolkit/components/normandy/test/browser/browser_about_studies.js
--- a/browser/extensions/shield-recipe-client/test/browser/browser_about_studies.js
+++ b/toolkit/components/normandy/test/browser/browser_about_studies.js
@@ -1,16 +1,16 @@
 "use strict";
 
-ChromeUtils.import("resource://shield-recipe-client/lib/AddonStudies.jsm", this);
-ChromeUtils.import("resource://shield-recipe-client/lib/RecipeRunner.jsm", this);
-ChromeUtils.import("resource://shield-recipe-client-content/AboutPages.jsm", this);
+ChromeUtils.import("resource://normandy/lib/AddonStudies.jsm", this);
+ChromeUtils.import("resource://normandy/lib/RecipeRunner.jsm", this);
+ChromeUtils.import("resource://normandy-content/AboutPages.jsm", this);
 
 function withAboutStudies(testFunc) {
-  return async (...args) => (
+return async (...args) => (
     BrowserTestUtils.withNewTab("about:studies", async browser => (
       testFunc(...args, browser)
     ))
   );
 }
 
 decorate_task(
   withAboutStudies,
rename from browser/extensions/shield-recipe-client/test/browser/fixtures/addon-fixture/manifest.json
rename to toolkit/components/normandy/test/browser/fixtures/addon-fixture/manifest.json
rename from browser/extensions/shield-recipe-client/test/browser/fixtures/normandy.xpi
rename to toolkit/components/normandy/test/browser/fixtures/normandy.xpi
rename from browser/extensions/shield-recipe-client/test/browser/head.js
rename to toolkit/components/normandy/test/browser/head.js
--- a/browser/extensions/shield-recipe-client/test/browser/head.js
+++ b/toolkit/components/normandy/test/browser/head.js
@@ -1,17 +1,18 @@
 ChromeUtils.import("resource://gre/modules/Preferences.jsm", this);
 ChromeUtils.import("resource://testing-common/AddonTestUtils.jsm", this);
 ChromeUtils.import("resource://testing-common/TestUtils.jsm", this);
-ChromeUtils.import("resource://shield-recipe-client/lib/Addons.jsm", this);
-ChromeUtils.import("resource://shield-recipe-client/lib/SandboxManager.jsm", this);
-ChromeUtils.import("resource://shield-recipe-client/lib/NormandyDriver.jsm", this);
-ChromeUtils.import("resource://shield-recipe-client/lib/NormandyApi.jsm", this);
-ChromeUtils.import("resource://shield-recipe-client/lib/TelemetryEvents.jsm", this);
-ChromeUtils.import("resource://shield-recipe-client/lib/Utils.jsm", this);
+ChromeUtils.import("resource://normandy-content/AboutPages.jsm", this);
+ChromeUtils.import("resource://normandy/lib/Addons.jsm", this);
+ChromeUtils.import("resource://normandy/lib/SandboxManager.jsm", this);
+ChromeUtils.import("resource://normandy/lib/NormandyDriver.jsm", this);
+ChromeUtils.import("resource://normandy/lib/NormandyApi.jsm", this);
+ChromeUtils.import("resource://normandy/lib/TelemetryEvents.jsm", this);
+ChromeUtils.import("resource://normandy/lib/Utils.jsm", this);
 
 // Load mocking/stubbing library, sinon
 // docs: http://sinonjs.org/docs/
 /* global sinon */
 Services.scriptloader.loadSubScript("resource://testing-common/sinon-2.3.2.js");
 
 // Make sinon assertions fail in a way that mochitest understands
 sinon.assert.fail = function(message) {
rename from browser/extensions/shield-recipe-client/test/unit/.eslintrc.js
rename to toolkit/components/normandy/test/unit/.eslintrc.js
rename from browser/extensions/shield-recipe-client/test/unit/echo_server.sjs
rename to toolkit/components/normandy/test/unit/echo_server.sjs
rename from browser/extensions/shield-recipe-client/test/unit/head_xpc.js
rename to toolkit/components/normandy/test/unit/head_xpc.js
--- a/browser/extensions/shield-recipe-client/test/unit/head_xpc.js
+++ b/toolkit/components/normandy/test/unit/head_xpc.js
@@ -1,26 +1,12 @@
 "use strict";
 
 ChromeUtils.import("resource://gre/modules/Services.jsm");
 
-// Load our bootstrap extension manifest so we can access our chrome/resource URIs.
-// Cargo culted from formautofill system add-on
-const EXTENSION_ID = "shield-recipe-client@mozilla.org";
-let extensionDir = Services.dirsvc.get("GreD", Ci.nsIFile);
-extensionDir.append("browser");
-extensionDir.append("features");
-extensionDir.append(EXTENSION_ID);
-// If the unpacked extension doesn't exist, use the packed version.
-if (!extensionDir.exists()) {
-  extensionDir = extensionDir.parent;
-  extensionDir.append(EXTENSION_ID + ".xpi");
-}
-Components.manager.addBootstrappedManifestLocation(extensionDir);
-
 // ================================================
 // Load mocking/stubbing library, sinon
 // docs: http://sinonjs.org/releases/v2.3.2/
 /* exported sinon */
 ChromeUtils.import("resource://gre/modules/Timer.jsm");
 Services.scriptloader.loadSubScript("resource://testing-common/sinon-2.3.2.js", this);
 /* globals sinon */
 // ================================================
rename from browser/extensions/shield-recipe-client/test/unit/invalid_recipe_signature_api/api/v1/index.json
rename to toolkit/components/normandy/test/unit/invalid_recipe_signature_api/api/v1/index.json
rename from browser/extensions/shield-recipe-client/test/unit/invalid_recipe_signature_api/api/v1/recipe/signed/index.json
rename to toolkit/components/normandy/test/unit/invalid_recipe_signature_api/api/v1/recipe/signed/index.json
rename from browser/extensions/shield-recipe-client/test/unit/invalid_recipe_signature_api/normandy.content-signature.mozilla.org-20210705.dev.chain
rename to toolkit/components/normandy/test/unit/invalid_recipe_signature_api/normandy.content-signature.mozilla.org-20210705.dev.chain
rename from browser/extensions/shield-recipe-client/test/unit/mock_api/api/v1/action/console-log/implementation/sha384-RGx3rydrSq53UfmW9kFcK0mQYra67XIvZvr4MhmAe--ljiiMQOtgM7Cmca48um3v
rename to toolkit/components/normandy/test/unit/mock_api/api/v1/action/console-log/implementation/sha384-RGx3rydrSq53UfmW9kFcK0mQYra67XIvZvr4MhmAe--ljiiMQOtgM7Cmca48um3v
rename from browser/extensions/shield-recipe-client/test/unit/mock_api/api/v1/action/console-log/index.json
rename to toolkit/components/normandy/test/unit/mock_api/api/v1/action/console-log/index.json
rename from browser/extensions/shield-recipe-client/test/unit/mock_api/api/v1/action/index.json
rename to toolkit/components/normandy/test/unit/mock_api/api/v1/action/index.json
rename from browser/extensions/shield-recipe-client/test/unit/mock_api/api/v1/action/opt-out-study/implementation/sha384-HM_avYcD00o27ufwU1V7PIBtiuMAXML6MMwlYrDEqDX-XzGVuOfL52RCM680JExN
rename to toolkit/components/normandy/test/unit/mock_api/api/v1/action/opt-out-study/implementation/sha384-HM_avYcD00o27ufwU1V7PIBtiuMAXML6MMwlYrDEqDX-XzGVuOfL52RCM680JExN
rename from browser/extensions/shield-recipe-client/test/unit/mock_api/api/v1/action/opt-out-study/index.json
rename to toolkit/components/normandy/test/unit/mock_api/api/v1/action/opt-out-study/index.json
rename from browser/extensions/shield-recipe-client/test/unit/mock_api/api/v1/action/preference-experiment/implementation/sha384-KQgG38GQ7KZAb2VIB48ANQO6nBcxZoLm2ORzUviRT5nAvSywyPjZ5cJIElw6iXIt
rename to toolkit/components/normandy/test/unit/mock_api/api/v1/action/preference-experiment/implementation/sha384-KQgG38GQ7KZAb2VIB48ANQO6nBcxZoLm2ORzUviRT5nAvSywyPjZ5cJIElw6iXIt
rename from browser/extensions/shield-recipe-client/test/unit/mock_api/api/v1/action/preference-experiment/index.json
rename to toolkit/components/normandy/test/unit/mock_api/api/v1/action/preference-experiment/index.json
rename from browser/extensions/shield-recipe-client/test/unit/mock_api/api/v1/action/show-heartbeat/implementation/sha384-dEGiyKPEln8Ns5cQHzGpMIGdirSAAX0X-Kwlu-U3sJ05yNbO-ANij_a6c5SyL7G4
rename to toolkit/components/normandy/test/unit/mock_api/api/v1/action/show-heartbeat/implementation/sha384-dEGiyKPEln8Ns5cQHzGpMIGdirSAAX0X-Kwlu-U3sJ05yNbO-ANij_a6c5SyL7G4
rename from browser/extensions/shield-recipe-client/test/unit/mock_api/api/v1/action/show-heartbeat/index.json
rename to toolkit/components/normandy/test/unit/mock_api/api/v1/action/show-heartbeat/index.json
rename from browser/extensions/shield-recipe-client/test/unit/mock_api/api/v1/action/signed/index.json
rename to toolkit/components/normandy/test/unit/mock_api/api/v1/action/signed/index.json
rename from browser/extensions/shield-recipe-client/test/unit/mock_api/api/v1/classify_client/index.json
rename to toolkit/components/normandy/test/unit/mock_api/api/v1/classify_client/index.json
rename from browser/extensions/shield-recipe-client/test/unit/mock_api/api/v1/index.json
rename to toolkit/components/normandy/test/unit/mock_api/api/v1/index.json
rename from browser/extensions/shield-recipe-client/test/unit/mock_api/api/v1/recipe/index.json
rename to toolkit/components/normandy/test/unit/mock_api/api/v1/recipe/index.json
rename from browser/extensions/shield-recipe-client/test/unit/mock_api/api/v1/recipe/signed/index.json
rename to toolkit/components/normandy/test/unit/mock_api/api/v1/recipe/signed/index.json
rename from browser/extensions/shield-recipe-client/test/unit/mock_api/normandy.content-signature.mozilla.org-20210705.dev.chain
rename to toolkit/components/normandy/test/unit/mock_api/normandy.content-signature.mozilla.org-20210705.dev.chain
rename from browser/extensions/shield-recipe-client/test/unit/query_server.sjs
rename to toolkit/components/normandy/test/unit/query_server.sjs
rename from browser/extensions/shield-recipe-client/test/unit/test_NormandyApi.js
rename to toolkit/components/normandy/test/unit/test_NormandyApi.js
--- a/browser/extensions/shield-recipe-client/test/unit/test_NormandyApi.js
+++ b/toolkit/components/normandy/test/unit/test_NormandyApi.js
@@ -1,16 +1,16 @@
 /* globals sinon */
 "use strict";
 
 ChromeUtils.import("resource://gre/modules/Services.jsm");
 ChromeUtils.import("resource://testing-common/httpd.js");
 ChromeUtils.import("resource://gre/modules/CanonicalJSON.jsm", this);
 ChromeUtils.import("resource://gre/modules/osfile.jsm", this);
-ChromeUtils.import("resource://shield-recipe-client/lib/NormandyApi.jsm", this);
+ChromeUtils.import("resource://normandy/lib/NormandyApi.jsm", this);
 
 load("utils.js"); /* globals withMockPreferences */
 
 class MockResponse {
   constructor(content) {
     this.content = content;
   }
 
rename from browser/extensions/shield-recipe-client/test/unit/test_Sampling.js
rename to toolkit/components/normandy/test/unit/test_Sampling.js
--- a/browser/extensions/shield-recipe-client/test/unit/test_Sampling.js
+++ b/toolkit/components/normandy/test/unit/test_Sampling.js
@@ -1,11 +1,11 @@
 "use strict";
 
-ChromeUtils.import("resource://shield-recipe-client/lib/Sampling.jsm", this);
+ChromeUtils.import("resource://normandy/lib/Sampling.jsm", this);
 
 add_task(async function testStableSample() {
   // Absolute samples
   equal(await Sampling.stableSample("test", 1), true, "stableSample returns true for 100% sample");
   equal(await Sampling.stableSample("test", 0), false, "stableSample returns false for 0% sample");
 
   // Known samples. The numbers are nonces to make the tests pass
   equal(await Sampling.stableSample("test-0", 0.5), true, "stableSample returns true for known matching sample");
rename from browser/extensions/shield-recipe-client/test/unit/test_SandboxManager.js
rename to toolkit/components/normandy/test/unit/test_SandboxManager.js
--- a/browser/extensions/shield-recipe-client/test/unit/test_SandboxManager.js
+++ b/toolkit/components/normandy/test/unit/test_SandboxManager.js
@@ -1,11 +1,11 @@
 "use strict";
 
-ChromeUtils.import("resource://shield-recipe-client/lib/SandboxManager.jsm");
+ChromeUtils.import("resource://normandy/lib/SandboxManager.jsm");
 
 // wrapAsync should wrap privileged Promises with Promises that are usable by
 // the sandbox.
 add_task(function* () {
   const manager = new SandboxManager();
   manager.addHold("testing");
 
   manager.cloneIntoGlobal("driver", {
rename from browser/extensions/shield-recipe-client/test/unit/test_Utils.js
rename to toolkit/components/normandy/test/unit/test_Utils.js
--- a/browser/extensions/shield-recipe-client/test/unit/test_Utils.js
+++ b/toolkit/components/normandy/test/unit/test_Utils.js
@@ -1,11 +1,11 @@
 "use strict";
 
-ChromeUtils.import("resource://shield-recipe-client/lib/Utils.jsm");
+ChromeUtils.import("resource://normandy/lib/Utils.jsm");
 
 add_task(async function testKeyBy() {
   const list = [];
   deepEqual(Utils.keyBy(list, "foo"), {});
 
   const foo = {name: "foo", value: 1};
   list.push(foo);
   deepEqual(Utils.keyBy(list, "name"), {foo});
rename from browser/extensions/shield-recipe-client/test/unit/utils.js
rename to toolkit/components/normandy/test/unit/utils.js
rename from browser/extensions/shield-recipe-client/test/unit/xpcshell.ini
rename to toolkit/components/normandy/test/unit/xpcshell.ini
rename from browser/extensions/shield-recipe-client/vendor/LICENSE_THIRDPARTY
rename to toolkit/components/normandy/vendor/LICENSE_THIRDPARTY
rename from browser/extensions/shield-recipe-client/vendor/PropTypes.js
rename to toolkit/components/normandy/vendor/PropTypes.js
rename from browser/extensions/shield-recipe-client/vendor/React.js
rename to toolkit/components/normandy/vendor/React.js
rename from browser/extensions/shield-recipe-client/vendor/ReactDOM.js
rename to toolkit/components/normandy/vendor/ReactDOM.js
rename from browser/extensions/shield-recipe-client/vendor/classnames.js
rename to toolkit/components/normandy/vendor/classnames.js
rename from browser/extensions/shield-recipe-client/vendor/mozjexl.js
rename to toolkit/components/normandy/vendor/mozjexl.js