Bug 1513646 - Remove Normandy remote-action infrastructure r=Gijs
authorMichael Cooper <mcooper@mozilla.com>
Tue, 23 Apr 2019 13:23:07 +0000
changeset 470541 f40a3485926cadae4bb326767a3ff5268a1814ec
parent 470533 0ce3633f8b80b9bcb69165f6ec4dc97f19fa8f0a
child 470542 b9a9068783f7848082d2a6559974ade2a78b3e6f
push id35908
push useraciure@mozilla.com
push dateWed, 24 Apr 2019 04:28:40 +0000
treeherdermozilla-central@c9f0730a57a6 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersGijs
bugs1513646
milestone68.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1513646 - Remove Normandy remote-action infrastructure r=Gijs Differential Revision: https://phabricator.services.mozilla.com/D28227
toolkit/components/normandy/lib/ActionSandboxManager.jsm
toolkit/components/normandy/lib/ActionsManager.jsm
toolkit/components/normandy/lib/NormandyDriver.jsm
toolkit/components/normandy/lib/RecipeRunner.jsm
toolkit/components/normandy/lib/SandboxManager.jsm
toolkit/components/normandy/test/browser/browser.ini
toolkit/components/normandy/test/browser/browser_ActionSandboxManager.js
toolkit/components/normandy/test/browser/browser_ActionsManager.js
toolkit/components/normandy/test/browser/browser_ClientEnvironment.js
toolkit/components/normandy/test/browser/browser_Heartbeat.js
toolkit/components/normandy/test/browser/browser_NormandyDriver.js
toolkit/components/normandy/test/browser/browser_RecipeRunner.js
toolkit/components/normandy/test/browser/browser_Storage.js
toolkit/components/normandy/test/browser/head.js
toolkit/components/normandy/test/unit/test_SandboxManager.js
toolkit/components/normandy/test/unit/xpcshell.ini
deleted file mode 100644
--- a/toolkit/components/normandy/lib/ActionSandboxManager.jsm
+++ /dev/null
@@ -1,75 +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";
-
-const {NormandyDriver} = ChromeUtils.import("resource://normandy/lib/NormandyDriver.jsm");
-const {SandboxManager} = ChromeUtils.import("resource://normandy/lib/SandboxManager.jsm");
-
-var EXPORTED_SYMBOLS = ["ActionSandboxManager"];
-
-/**
- * An extension to SandboxManager that prepares a sandbox for executing
- * Normandy actions.
- *
- * Actions register a set of named callbacks, which this class makes available
- * for execution. This allows a single action script to define multiple,
- * independent steps that execute in isolated sandboxes.
- *
- * Callbacks are assumed to be async and must return Promises.
- */
-var ActionSandboxManager = class extends SandboxManager {
-  constructor(actionScript) {
-    super();
-
-    // Prepare the sandbox environment
-    const driver = new NormandyDriver(this);
-    this.cloneIntoGlobal("sandboxedDriver", driver, {cloneFunctions: true});
-    this.evalInSandbox(`
-      // Shim old API for registering actions
-      function registerAction(name, Action) {
-        registerAsyncCallback("action", (driver, recipe) => {
-          return new Action(driver, recipe).execute();
-        });
-      };
-
-      this.asyncCallbacks = new Map();
-      function registerAsyncCallback(name, callback) {
-        asyncCallbacks.set(name, callback);
-      }
-
-      this.window = this;
-      this.setTimeout = sandboxedDriver.setTimeout;
-      this.clearTimeout = sandboxedDriver.clearTimeout;
-    `);
-    this.evalInSandbox(actionScript);
-  }
-
-  /**
-   * Execute a callback in the sandbox with the given name. If the script does
-   * not register a callback with the given name, we log a message and return.
-   * @param {String} callbackName
-   * @param {...*} [args]
-   *   Remaining arguments are cloned into the sandbox and passed as arguments
-   *   to the callback.
-   * @resolves
-   *   The return value of the callback, cloned into the current compartment, or
-   *   undefined if a matching callback was not found.
-   * @rejects
-   *   If the sandbox rejects, an error object with the message from the sandbox
-   *   error. Due to sandbox limitations, the stack trace is not preserved.
-   */
-  async runAsyncCallback(callbackName, ...args) {
-    const callbackWasRegistered = this.evalInSandbox(`asyncCallbacks.has("${callbackName}")`);
-    if (!callbackWasRegistered) {
-      return undefined;
-    }
-
-    this.cloneIntoGlobal("callbackArgs", args);
-    const result = await this.evalInSandbox(`
-      asyncCallbacks.get("${callbackName}")(sandboxedDriver, ...callbackArgs);
-    `);
-    return Cu.cloneInto(result, {});
-  }
-};
--- a/toolkit/components/normandy/lib/ActionsManager.jsm
+++ b/toolkit/components/normandy/lib/ActionsManager.jsm
@@ -1,128 +1,52 @@
 const {XPCOMUtils} = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 const {LogManager} = ChromeUtils.import("resource://normandy/lib/LogManager.jsm");
 
 XPCOMUtils.defineLazyModuleGetters(this, {
-  ActionSandboxManager: "resource://normandy/lib/ActionSandboxManager.jsm",
   AddonStudyAction: "resource://normandy/actions/AddonStudyAction.jsm",
   ConsoleLogAction: "resource://normandy/actions/ConsoleLogAction.jsm",
-  NormandyApi: "resource://normandy/lib/NormandyApi.jsm",
   PreferenceExperimentAction: "resource://normandy/actions/PreferenceExperimentAction.jsm",
   PreferenceRollbackAction: "resource://normandy/actions/PreferenceRollbackAction.jsm",
   PreferenceRolloutAction: "resource://normandy/actions/PreferenceRolloutAction.jsm",
   ShowHeartbeatAction: "resource://normandy/actions/ShowHeartbeatAction.jsm",
   Uptake: "resource://normandy/lib/Uptake.jsm",
 });
 
 var EXPORTED_SYMBOLS = ["ActionsManager"];
 
 const log = LogManager.getLogger("recipe-runner");
 
 /**
  * A class to manage the actions that recipes can use in Normandy.
- *
- * This includes both remote and local actions. Remote actions
- * implementations are fetched from the Normandy server; their
- * lifecycles are managed by `normandy/lib/ActionSandboxManager.jsm`.
- * Local actions have their implementations packaged in the Normandy
- * client, and manage their lifecycles internally.
  */
 class ActionsManager {
   constructor() {
     this.finalized = false;
-    this.remoteActionSandboxes = {};
 
     const addonStudyAction = new AddonStudyAction();
 
     this.localActions = {
       "addon-study": addonStudyAction,
       "console-log": new ConsoleLogAction(),
       "opt-out-study": addonStudyAction, // Legacy name used for addon-study on Normandy server
       "preference-experiment": new PreferenceExperimentAction(),
       "preference-rollback": new PreferenceRollbackAction(),
       "preference-rollout": new PreferenceRolloutAction(),
       "show-heartbeat": new ShowHeartbeatAction(),
     };
   }
 
-  async fetchRemoteActions() {
-    const actions = await NormandyApi.fetchActions();
-
-    for (const action of actions) {
-      // Skip actions with local implementations
-      if (action.name in this.localActions) {
-        continue;
-      }
-
-      try {
-        const implementation = await NormandyApi.fetchImplementation(action);
-        const sandbox = new ActionSandboxManager(implementation);
-        sandbox.addHold("ActionsManager");
-        this.remoteActionSandboxes[action.name] = sandbox;
-      } catch (err) {
-        log.warn(`Could not fetch implementation for ${action.name}: ${err}`);
-
-        let status;
-        if (/NetworkError/.test(err)) {
-          status = Uptake.ACTION_NETWORK_ERROR;
-        } else {
-          status = Uptake.ACTION_SERVER_ERROR;
-        }
-        await Uptake.reportAction(action.name, status);
-      }
-    }
-
-    const actionNames = Object.keys(this.remoteActionSandboxes);
-    log.debug(`Fetched ${actionNames.length} actions from the server: ${actionNames.join(", ")}`);
-  }
-
-  async preExecution() {
-    // Local actions run pre-execution hooks implicitly
-
-    for (const [actionName, manager] of Object.entries(this.remoteActionSandboxes)) {
-      try {
-        await manager.runAsyncCallback("preExecution");
-        manager.disabled = false;
-      } catch (err) {
-        log.error(`Could not run pre-execution hook for ${actionName}:`, err.message);
-        manager.disabled = true;
-        await Uptake.reportAction(actionName, Uptake.ACTION_PRE_EXECUTION_ERROR);
-      }
-    }
-  }
-
   async runRecipe(recipe) {
     let actionName = recipe.action;
 
     if (actionName in this.localActions) {
       log.info(`Executing recipe "${recipe.name}" (action=${recipe.action})`);
       const action = this.localActions[actionName];
       await action.runRecipe(recipe);
-    } else if (actionName in this.remoteActionSandboxes) {
-      let status;
-      const manager = this.remoteActionSandboxes[recipe.action];
-
-      if (manager.disabled) {
-        log.warn(
-          `Skipping recipe ${recipe.name} because ${recipe.action} failed during pre-execution.`
-        );
-        status = Uptake.RECIPE_ACTION_DISABLED;
-      } else {
-        try {
-          log.info(`Executing recipe "${recipe.name}" (action=${recipe.action})`);
-          await manager.runAsyncCallback("action", recipe);
-          status = Uptake.RECIPE_SUCCESS;
-        } catch (e) {
-          e.message = `Could not execute recipe ${recipe.name}: ${e.message}`;
-          Cu.reportError(e);
-          status = Uptake.RECIPE_EXECUTION_ERROR;
-        }
-      }
-      await Uptake.reportRecipe(recipe, status);
     } else {
       log.error(
         `Could not execute recipe ${recipe.name}:`,
         `Action ${recipe.action} is either missing or invalid.`
       );
       await Uptake.reportRecipe(recipe, Uptake.RECIPE_INVALID_ACTION);
     }
   }
@@ -132,31 +56,10 @@ class ActionsManager {
       throw new Error("ActionsManager has already been finalized");
     }
     this.finalized = true;
 
     // Finalize local actions
     for (const action of new Set(Object.values(this.localActions))) {
       action.finalize();
     }
-
-    // Run post-execution hooks for remote actions
-    for (const [actionName, manager] of Object.entries(this.remoteActionSandboxes)) {
-      // Skip if pre-execution failed.
-      if (manager.disabled) {
-        log.info(`Skipping post-execution hook for ${actionName} due to earlier failure.`);
-        continue;
-      }
-
-      try {
-        await manager.runAsyncCallback("postExecution");
-        await Uptake.reportAction(actionName, Uptake.ACTION_SUCCESS);
-      } catch (err) {
-        log.info(`Could not run post-execution hook for ${actionName}:`, err.message);
-        await Uptake.reportAction(actionName, Uptake.ACTION_POST_EXECUTION_ERROR);
-      }
-    }
-
-    // Nuke sandboxes
-    Object.values(this.remoteActionSandboxes)
-      .forEach(manager => manager.removeHold("ActionsManager"));
   }
 }
deleted file mode 100644
--- a/toolkit/components/normandy/lib/NormandyDriver.jsm
+++ /dev/null
@@ -1,193 +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";
-
-const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
-const {Preferences} = ChromeUtils.import("resource://gre/modules/Preferences.jsm");
-const {ShellService} = ChromeUtils.import("resource:///modules/ShellService.jsm");
-const {AddonManager} = ChromeUtils.import("resource://gre/modules/AddonManager.jsm");
-const {clearTimeout, setTimeout} = ChromeUtils.import("resource://gre/modules/Timer.jsm");
-const {LogManager} = ChromeUtils.import("resource://normandy/lib/LogManager.jsm");
-const {Storage} = ChromeUtils.import("resource://normandy/lib/Storage.jsm");
-const {ClientEnvironment} = ChromeUtils.import("resource://normandy/lib/ClientEnvironment.jsm");
-const {PreferenceExperiments} = ChromeUtils.import("resource://normandy/lib/PreferenceExperiments.jsm");
-
-ChromeUtils.defineModuleGetter(
-  this, "Sampling", "resource://gre/modules/components-utils/Sampling.jsm");
-ChromeUtils.defineModuleGetter(this, "UpdateUtils", "resource://gre/modules/UpdateUtils.jsm");
-
-const {generateUUID} = Cc["@mozilla.org/uuid-generator;1"].getService(Ci.nsIUUIDGenerator);
-
-var EXPORTED_SYMBOLS = ["NormandyDriver"];
-
-const actionLog = LogManager.getLogger("normandy-driver.actions");
-
-var NormandyDriver = function(sandboxManager) {
-  if (!sandboxManager) {
-    throw new Error("sandboxManager is required");
-  }
-  const {sandbox} = sandboxManager;
-
-  return {
-    testing: false,
-
-    get locale() {
-      if (Services.locale.getAppLocaleAsLangTag) {
-        return Services.locale.getAppLocaleAsLangTag;
-      }
-
-      return Cc["@mozilla.org/chrome/chrome-registry;1"]
-        .getService(Ci.nsIXULChromeRegistry)
-        .getSelectedLocale("global");
-    },
-
-    get userId() {
-      return ClientEnvironment.userId;
-    },
-
-    log(message, level = "debug") {
-      const levels = ["debug", "info", "warn", "error"];
-      if (!levels.includes(level)) {
-        throw new Error(`Invalid log level "${level}"`);
-      }
-      actionLog[level](message);
-    },
-
-    client() {
-      const appinfo = {
-        version: Services.appinfo.version,
-        channel: UpdateUtils.getUpdateChannel(false),
-        isDefaultBrowser: ShellService.isDefaultBrowser() || null,
-        searchEngine: null,
-        syncSetup: Preferences.isSet("services.sync.username"),
-        syncDesktopDevices: Preferences.get("services.sync.clients.devices.desktop", 0),
-        syncMobileDevices: Preferences.get("services.sync.clients.devices.mobile", 0),
-        syncTotalDevices: null,
-        plugins: {},
-        doNotTrack: Preferences.get("privacy.donottrackheader.enabled", false),
-        distribution: Preferences.get("distribution.id", "default"),
-      };
-      appinfo.syncTotalDevices = appinfo.syncDesktopDevices + appinfo.syncMobileDevices;
-
-      const searchEnginePromise = new Promise(resolve => {
-        Services.search.init().then(() => {
-          appinfo.searchEngine = Services.search.defaultEngine.identifier;
-        }).finally(resolve);
-      });
-
-      const pluginsPromise = (async () => {
-        let plugins = await AddonManager.getAddonsByTypes(["plugin"]);
-        plugins.forEach(plugin => appinfo.plugins[plugin.name] = {
-          name: plugin.name,
-          description: plugin.description,
-          version: plugin.version,
-        });
-      })();
-
-      return new sandbox.Promise(resolve => {
-        Promise.all([searchEnginePromise, pluginsPromise]).then(() => {
-          resolve(Cu.cloneInto(appinfo, sandbox));
-        });
-      });
-    },
-
-    uuid() {
-      let ret = generateUUID().toString();
-      ret = ret.slice(1, ret.length - 1);
-      return ret;
-    },
-
-    createStorage(prefix) {
-      const storage = new Storage(prefix);
-
-      // Wrapped methods that we expose to the sandbox. These are documented in
-      // the driver spec in docs/dev/driver.rst.
-      const storageInterface = {};
-      for (const method of ["getItem", "setItem", "removeItem", "clear"]) {
-        storageInterface[method] = sandboxManager.wrapAsync(storage[method].bind(storage), {
-          cloneArguments: true,
-          cloneInto: true,
-        });
-      }
-
-      return sandboxManager.cloneInto(storageInterface, {cloneFunctions: true});
-    },
-
-    setTimeout(cb, time) {
-      if (typeof cb !== "function") {
-        throw new sandbox.Error(`setTimeout must be called with a function, got "${typeof cb}"`);
-      }
-      const token = setTimeout(() => {
-        cb();
-        sandboxManager.removeHold(`setTimeout-${token}`);
-      }, time);
-      sandboxManager.addHold(`setTimeout-${token}`);
-      return Cu.cloneInto(token, sandbox);
-    },
-
-    clearTimeout(token) {
-      clearTimeout(token);
-      sandboxManager.removeHold(`setTimeout-${token}`);
-    },
-
-    // Sampling
-    ratioSample: sandboxManager.wrapAsync(Sampling.ratioSample),
-
-    // Preference Experiment API
-    preferenceExperiments: {
-      start: sandboxManager.wrapAsync(
-        PreferenceExperiments.start.bind(PreferenceExperiments),
-        {cloneArguments: true}
-      ),
-      markLastSeen: sandboxManager.wrapAsync(
-        PreferenceExperiments.markLastSeen.bind(PreferenceExperiments)
-      ),
-      stop: sandboxManager.wrapAsync(PreferenceExperiments.stop.bind(PreferenceExperiments)),
-      get: sandboxManager.wrapAsync(
-        PreferenceExperiments.get.bind(PreferenceExperiments),
-        {cloneInto: true}
-      ),
-      getAllActive: sandboxManager.wrapAsync(
-        PreferenceExperiments.getAllActive.bind(PreferenceExperiments),
-        {cloneInto: true}
-      ),
-      has: sandboxManager.wrapAsync(PreferenceExperiments.has.bind(PreferenceExperiments)),
-    },
-
-    // Preference read-only API
-    preferences: {
-      getBool: wrapPrefGetter(Services.prefs.getBoolPref),
-      getInt: wrapPrefGetter(Services.prefs.getIntPref),
-      getChar: wrapPrefGetter(Services.prefs.getCharPref),
-      has(name) {
-        return Services.prefs.getPrefType(name) !== Services.prefs.PREF_INVALID;
-      },
-    },
-  };
-};
-
-/**
- * Wrap a getter form nsIPrefBranch for use in the sandbox.
- *
- * We don't want to export the getters directly in case they add parameters that
- * aren't safe for the sandbox without us noticing; wrapping helps prevent
- * passing unknown parameters.
- *
- * @param {Function} getter
- *   Function on an nsIPrefBranch that fetches a preference value.
- * @return {Function}
- */
-function wrapPrefGetter(getter) {
-  return (value, defaultValue = undefined) => {
-    // Passing undefined as the defaultValue disables throwing exceptions when
-    // the pref is missing or the type doesn't match, so we need to specifically
-    // exclude it if we don't want default value behavior.
-    const args = [value];
-    if (defaultValue !== undefined) {
-      args.push(defaultValue);
-    }
-    return getter.apply(null, args);
-  };
-}
--- a/toolkit/components/normandy/lib/RecipeRunner.jsm
+++ b/toolkit/components/normandy/lib/RecipeRunner.jsm
@@ -223,18 +223,16 @@ var RecipeRunner = {
       } else if (e instanceof NormandyApi.InvalidSignatureError) {
         status = Uptake.RUNNER_INVALID_SIGNATURE;
       }
       await Uptake.reportRunner(status);
       return;
     }
 
     const actions = new ActionsManager();
-    await actions.fetchRemoteActions();
-    await actions.preExecution();
 
     // Execute recipes, if we have any.
     if (recipesToRun.length === 0) {
       log.debug("No recipes to execute");
     } else {
       for (const recipe of recipesToRun) {
         await actions.runRecipe(recipe);
       }
deleted file mode 100644
--- a/toolkit/components/normandy/lib/SandboxManager.jsm
+++ /dev/null
@@ -1,118 +0,0 @@
-var EXPORTED_SYMBOLS = ["SandboxManager"];
-
-/**
- * A wrapper class with helper methods for manipulating a sandbox.
- *
- * Along with convenient utility methods, SandboxManagers maintain a list of
- * "holds", which prevent the sandbox from being nuked until all registered
- * holds are removed. This allows sandboxes to trigger async operations and
- * automatically nuke themselves when they're done.
- */
-var SandboxManager = class {
-  constructor() {
-    this._sandbox = new Cu.Sandbox(null, {
-      wantComponents: false,
-      wantGlobalProperties: ["URL", "URLSearchParams"],
-    });
-    this.holds = [];
-  }
-
-  get sandbox() {
-    if (this._sandbox) {
-      return this._sandbox;
-    }
-    throw new Error("Tried to use sandbox after it was nuked");
-  }
-
-  addHold(name) {
-    this.holds.push(name);
-  }
-
-  removeHold(name) {
-    const index = this.holds.indexOf(name);
-    if (index === -1) {
-      throw new Error(`Tried to remove non-existant hold "${name}"`);
-    }
-    this.holds.splice(index, 1);
-    this.tryCleanup();
-  }
-
-  cloneInto(value, options = {}) {
-    return Cu.cloneInto(value, this.sandbox, options);
-  }
-
-  cloneIntoGlobal(name, value, options = {}) {
-    const clonedValue = Cu.cloneInto(value, this.sandbox, options);
-    this.addGlobal(name, clonedValue);
-    return clonedValue;
-  }
-
-  addGlobal(name, value) {
-    this.sandbox[name] = value;
-  }
-
-  evalInSandbox(script) {
-    return Cu.evalInSandbox(script, this.sandbox);
-  }
-
-  tryCleanup() {
-    if (this.holds.length === 0) {
-      const sandbox = this._sandbox;
-      this._sandbox = null;
-      Cu.nukeSandbox(sandbox);
-    }
-  }
-
-  isNuked() {
-    // Do this in a promise, so other async things can resolve.
-    return new Promise((resolve, reject) => {
-      if (!this._sandbox) {
-        resolve();
-      } else {
-        reject(new Error(`Sandbox is not nuked. Holds left: ${this.holds}`));
-      }
-    });
-  }
-
-  /**
-   * Wraps a function that returns a Promise from a privileged (i.e. chrome)
-   * context and returns a Promise from this SandboxManager's sandbox. Useful
-   * for exposing privileged functions to the sandbox, since the sandbox can't
-   * access properties on privileged objects, e.g. Promise.then on a privileged
-   * Promise.
-   * @param {Function} wrappedFunction
-   * @param {Object} [options]
-   * @param {boolean} [options.cloneInto=false]
-   *   If true, the value resolved by the privileged Promise is cloned into the
-   *   sandbox before being resolved by the sandbox Promise. Without this, the
-   *   result will be Xray-wrapped.
-   * @param {boolean} [options.cloneArguments=false]
-   *   If true, the arguments passed to wrappedFunction will be cloned into the
-   *   privileged chrome context. If wrappedFunction holds a reference to any of
-   *   its arguments, you will need this to avoid losing access to the arguments
-   *   when the sandbox they originate from is nuked.
-   * @return {Function}
-   */
-  wrapAsync(wrappedFunction, options = {cloneInto: false, cloneArguments: false}) {
-    // In order for `this` to work in wrapped functions, we must return a
-    // non-arrow function, which requires saving a reference to the manager.
-    const sandboxManager = this;
-    return function(...args) {
-      return new sandboxManager.sandbox.Promise((resolve, reject) => {
-        if (options.cloneArguments) {
-          args = Cu.cloneInto(args, {});
-        }
-
-        wrappedFunction.apply(this, args).then(result => {
-          if (options.cloneInto) {
-            result = sandboxManager.cloneInto(result);
-          }
-
-          resolve(result);
-        }, err => {
-          reject(new sandboxManager.sandbox.Error(err.message, err.fileName, err.lineNumber));
-        });
-      });
-    };
-  }
-};
--- a/toolkit/components/normandy/test/browser/browser.ini
+++ b/toolkit/components/normandy/test/browser/browser.ini
@@ -13,26 +13,24 @@ head = head.js
 skip-if = !healthreport || !telemetry
 [browser_about_studies.js]
 [browser_actions_AddonStudyAction.js]
 [browser_actions_ConsoleLogAction.js]
 [browser_actions_PreferenceExperimentAction.js]
 [browser_actions_PreferenceRolloutAction.js]
 [browser_actions_PreferenceRollbackAction.js]
 [browser_actions_ShowHeartbeatAction.js]
-[browser_ActionSandboxManager.js]
 [browser_ActionsManager.js]
 [browser_AddonStudies.js]
 skip-if = (verify && (os == 'linux'))
 [browser_BaseAction.js]
 [browser_CleanupManager.js]
 [browser_ClientEnvironment.js]
 [browser_EventEmitter.js]
 [browser_Heartbeat.js]
 [browser_LogManager.js]
 [browser_Normandy.js]
-[browser_NormandyDriver.js]
 [browser_PreferenceExperiments.js]
 [browser_PreferenceRollouts.js]
 [browser_RecipeRunner.js]
 [browser_ShieldPreferences.js]
 [browser_Storage.js]
 [browser_Uptake.js]
deleted file mode 100644
--- a/toolkit/components/normandy/test/browser/browser_ActionSandboxManager.js
+++ /dev/null
@@ -1,167 +0,0 @@
-"use strict";
-
-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");
-}
-
-add_task(async function testMissingCallbackName() {
-  await withManager("1 + 1", async manager => {
-    is(
-      await manager.runAsyncCallback("missingCallback"),
-      undefined,
-      "runAsyncCallback returns undefined when given a missing callback name",
-    );
-  });
-});
-
-add_task(async function testCallback() {
-  const script = `
-    registerAsyncCallback("testCallback", async function(normandy) {
-      return 5;
-    });
-  `;
-
-  await withManager(script, async manager => {
-    const result = await manager.runAsyncCallback("testCallback");
-    is(result, 5, "runAsyncCallback executes the named callback inside the sandbox");
-  });
-});
-
-add_task(async function testArguments() {
-  const script = `
-    registerAsyncCallback("testCallback", async function(normandy, a, b) {
-      return a + b;
-    });
-  `;
-
-  await withManager(script, async manager => {
-    const result = await manager.runAsyncCallback("testCallback", 4, 6);
-    is(result, 10, "runAsyncCallback passes arguments to the callback");
-  });
-});
-
-add_task(async function testCloning() {
-  const script = `
-    registerAsyncCallback("testCallback", async function(normandy, obj) {
-      return {foo: "bar", baz: obj.baz};
-    });
-  `;
-
-  await withManager(script, async manager => {
-    const result = await manager.runAsyncCallback("testCallback", {baz: "biff"});
-
-    Assert.deepEqual(
-      result,
-      {foo: "bar", baz: "biff"},
-      (
-        "runAsyncCallback clones arguments into the sandbox and return values into the " +
-        "context it was called from"
-      ),
-    );
-  });
-});
-
-add_task(async function testError() {
-  const script = `
-    registerAsyncCallback("testCallback", async function(normandy) {
-      throw new Error("WHY")
-    });
-  `;
-
-  await withManager(script, async manager => {
-    try {
-      await manager.runAsyncCallback("testCallback");
-      ok(false, "runAsnycCallbackFromScript throws errors when raised by the sandbox");
-    } catch (err) {
-      is(err.message, "WHY", "runAsnycCallbackFromScript throws errors when raised by the sandbox");
-    }
-  });
-});
-
-add_task(async function testDriver() {
-  // The value returned by runAsyncCallback is cloned without the cloneFunctions
-  // option, so we can't inspect the driver itself since its methods will not be
-  // present. Instead, we inspect the properties on it available to the sandbox.
-  const script = `
-    registerAsyncCallback("testCallback", async function(normandy) {
-      return Object.keys(normandy);
-    });
-  `;
-
-  await withManager(script, async manager => {
-    const sandboxDriverKeys = await manager.runAsyncCallback("testCallback");
-    const referenceDriver = new NormandyDriver(manager);
-    for (const prop of Object.keys(referenceDriver)) {
-      ok(sandboxDriverKeys.includes(prop), `runAsyncCallback's driver has the "${prop}" property.`);
-    }
-  });
-});
-
-add_task(async function testGlobalObject() {
-  // Test that window is an alias for the global object, and that it
-  // has some expected functions available on it.
-  const script = `
-    window.setOnWindow = "set";
-    this.setOnGlobal = "set";
-
-    registerAsyncCallback("testCallback", async function(normandy) {
-      return {
-        setOnWindow: setOnWindow,
-        setOnGlobal: window.setOnGlobal,
-        setTimeoutExists: setTimeout !== undefined,
-        clearTimeoutExists: clearTimeout !== undefined,
-      };
-    });
-  `;
-
-  await withManager(script, async manager => {
-    const result = await manager.runAsyncCallback("testCallback");
-    Assert.deepEqual(result, {
-      setOnWindow: "set",
-      setOnGlobal: "set",
-      setTimeoutExists: true,
-      clearTimeoutExists: true,
-    }, "sandbox.window is the global object and has expected functions.");
-  });
-});
-
-add_task(async function testRegisterActionShim() {
-  const recipe = {
-    foo: "bar",
-  };
-  const script = `
-    class TestAction {
-      constructor(driver, recipe) {
-        this.driver = driver;
-        this.recipe = recipe;
-      }
-
-      execute() {
-        return new Promise(resolve => {
-          resolve({
-            foo: this.recipe.foo,
-            isDriver: "log" in this.driver,
-          });
-        });
-      }
-    }
-
-    registerAction('test-action', TestAction);
-  `;
-
-  await withManager(script, async manager => {
-    const result = await manager.runAsyncCallback("action", recipe);
-    is(result.foo, "bar", "registerAction registers an async callback for actions");
-    is(
-      result.isDriver,
-      true,
-      "registerAction passes the driver to the action class constructor",
-    );
-  });
-});
--- a/toolkit/components/normandy/test/browser/browser_ActionsManager.js
+++ b/toolkit/components/normandy/test/browser/browser_ActionsManager.js
@@ -1,227 +1,15 @@
 "use strict";
 
 ChromeUtils.import("resource://normandy/lib/ActionsManager.jsm", this);
 ChromeUtils.import("resource://normandy/lib/NormandyApi.jsm", this);
 ChromeUtils.import("resource://normandy/lib/Uptake.jsm", this);
 
-// It should only fetch implementations for actions that don't exist locally
-decorate_task(
-  withStub(NormandyApi, "fetchActions"),
-  withStub(NormandyApi, "fetchImplementation"),
-  async function(fetchActionsStub, fetchImplementationStub) {
-    const remoteAction = {name: "remote-action"};
-    const localAction = {name: "local-action"};
-    fetchActionsStub.resolves([remoteAction, localAction]);
-    fetchImplementationStub.callsFake(async () => "");
-
-    const manager = new ActionsManager();
-    manager.localActions = {"local-action": {}};
-    await manager.fetchRemoteActions();
-
-    is(fetchActionsStub.callCount, 1, "action metadata should be fetched");
-    Assert.deepEqual(
-      fetchImplementationStub.getCall(0).args,
-      [remoteAction],
-      "only the remote action's implementation should be fetched",
-    );
-  },
-);
-
-// Test life cycle methods for remote actions
-decorate_task(
-  withStub(Uptake, "reportAction"),
-  withStub(Uptake, "reportRecipe"),
-  async function(reportActionStub, reportRecipeStub) {
-    let manager = new ActionsManager();
-    const recipe = {id: 1, action: "test-remote-action-used"};
-
-    const sandboxManagerUsed = {
-      removeHold: sinon.stub(),
-      runAsyncCallback: sinon.stub(),
-    };
-    const sandboxManagerUnused = {
-      removeHold: sinon.stub(),
-      runAsyncCallback: sinon.stub(),
-    };
-    manager.remoteActionSandboxes = {
-      "test-remote-action-used": sandboxManagerUsed,
-      "test-remote-action-unused": sandboxManagerUnused,
-    };
-    manager.localActions = {};
-
-    await manager.preExecution();
-    await manager.runRecipe(recipe);
-    await manager.finalize();
-
-    Assert.deepEqual(
-      sandboxManagerUsed.runAsyncCallback.args,
-      [
-        ["preExecution"],
-        ["action", recipe],
-        ["postExecution"],
-      ],
-      "The expected life cycle events should be called on the used sandbox action manager",
-    );
-    Assert.deepEqual(
-      sandboxManagerUnused.runAsyncCallback.args,
-      [
-        ["preExecution"],
-        ["postExecution"],
-      ],
-      "The expected life cycle events should be called on the unused sandbox action manager",
-    );
-    Assert.deepEqual(
-      sandboxManagerUsed.removeHold.args,
-      [["ActionsManager"]],
-      "ActionsManager should remove holds on the sandbox managers during finalize.",
-    );
-    Assert.deepEqual(
-      sandboxManagerUnused.removeHold.args,
-      [["ActionsManager"]],
-      "ActionsManager should remove holds on the sandbox managers during finalize.",
-    );
-
-    Assert.deepEqual(reportActionStub.args, [
-      ["test-remote-action-used", Uptake.ACTION_SUCCESS],
-      ["test-remote-action-unused", Uptake.ACTION_SUCCESS],
-    ]);
-    Assert.deepEqual(reportRecipeStub.args, [[recipe, Uptake.RECIPE_SUCCESS]]);
-  },
-);
-
-// Test life cycle for remote action that fails in pre-step
-decorate_task(
-  withStub(Uptake, "reportAction"),
-  withStub(Uptake, "reportRecipe"),
-  async function(reportActionStub, reportRecipeStub) {
-    let manager = new ActionsManager();
-    const recipe = {id: 1, action: "test-remote-action-broken"};
-
-    const sandboxManagerBroken = {
-      removeHold: sinon.stub(),
-      runAsyncCallback: sinon.stub().callsFake(callbackName => {
-        if (callbackName === "preExecution") {
-          throw new Error("mock preExecution failure");
-        }
-      }),
-    };
-    manager.remoteActionSandboxes = {
-      "test-remote-action-broken": sandboxManagerBroken,
-    };
-    manager.localActions = {};
-
-    await manager.preExecution();
-    await manager.runRecipe(recipe);
-    await manager.finalize();
-
-    Assert.deepEqual(
-      sandboxManagerBroken.runAsyncCallback.args,
-      [["preExecution"]],
-      "No async callbacks should be called after preExecution fails",
-    );
-    Assert.deepEqual(
-      sandboxManagerBroken.removeHold.args,
-      [["ActionsManager"]],
-      "sandbox holds should still be removed after a failure",
-    );
-
-    Assert.deepEqual(reportActionStub.args, [
-      ["test-remote-action-broken", Uptake.ACTION_PRE_EXECUTION_ERROR],
-    ]);
-    Assert.deepEqual(reportRecipeStub.args, [[recipe, Uptake.RECIPE_ACTION_DISABLED]]);
-  },
-);
-
-// Test life cycle for remote action that fails on a recipe-step
-decorate_task(
-  withStub(Uptake, "reportAction"),
-  withStub(Uptake, "reportRecipe"),
-  async function(reportActionStub, reportRecipeStub) {
-    let manager = new ActionsManager();
-    const recipe = {id: 1, action: "test-remote-action-broken"};
-
-    const sandboxManagerBroken = {
-      removeHold: sinon.stub(),
-      runAsyncCallback: sinon.stub().callsFake(callbackName => {
-        if (callbackName === "action") {
-          throw new Error("mock action failure");
-        }
-      }),
-    };
-    manager.remoteActionSandboxes = {
-      "test-remote-action-broken": sandboxManagerBroken,
-    };
-    manager.localActions = {};
-
-    await manager.preExecution();
-    await manager.runRecipe(recipe);
-    await manager.finalize();
-
-    Assert.deepEqual(
-      sandboxManagerBroken.runAsyncCallback.args,
-      [["preExecution"], ["action", recipe], ["postExecution"]],
-      "postExecution callback should still be called after action callback fails",
-    );
-    Assert.deepEqual(
-      sandboxManagerBroken.removeHold.args,
-      [["ActionsManager"]],
-      "sandbox holds should still be removed after a recipe failure",
-    );
-
-    Assert.deepEqual(reportActionStub.args, [["test-remote-action-broken", Uptake.ACTION_SUCCESS]]);
-    Assert.deepEqual(reportRecipeStub.args, [[recipe, Uptake.RECIPE_EXECUTION_ERROR]]);
-  },
-);
-
-// Test life cycle for remote action that fails in post-step
-decorate_task(
-  withStub(Uptake, "reportAction"),
-  withStub(Uptake, "reportRecipe"),
-  async function(reportActionStub, reportRecipeStub) {
-    let manager = new ActionsManager();
-    const recipe = {id: 1, action: "test-remote-action-broken"};
-
-    const sandboxManagerBroken = {
-      removeHold: sinon.stub(),
-      runAsyncCallback: sinon.stub().callsFake(callbackName => {
-        if (callbackName === "postExecution") {
-          throw new Error("mock postExecution failure");
-        }
-      }),
-    };
-    manager.remoteActionSandboxes = {
-      "test-remote-action-broken": sandboxManagerBroken,
-    };
-    manager.localActions = {};
-
-    await manager.preExecution();
-    await manager.runRecipe(recipe);
-    await manager.finalize();
-
-    Assert.deepEqual(
-      sandboxManagerBroken.runAsyncCallback.args,
-      [["preExecution"], ["action", recipe], ["postExecution"]],
-      "All callbacks should be executed",
-    );
-    Assert.deepEqual(
-      sandboxManagerBroken.removeHold.args,
-      [["ActionsManager"]],
-      "sandbox holds should still be removed after a failure",
-    );
-
-    Assert.deepEqual(reportRecipeStub.args, [[recipe, Uptake.RECIPE_SUCCESS]]);
-    Assert.deepEqual(reportActionStub.args, [
-      ["test-remote-action-broken", Uptake.ACTION_POST_EXECUTION_ERROR],
-    ]);
-  },
-);
-
-// Test life cycle methods for local actions
+// Test life cycle methods for actions
 decorate_task(
   async function(reportActionStub, Stub) {
     let manager = new ActionsManager();
     const recipe = {id: 1, action: "test-local-action-used"};
 
     let actionUsed = {
       runRecipe: sinon.stub(),
       finalize: sinon.stub(),
@@ -229,85 +17,19 @@ decorate_task(
     let actionUnused = {
       runRecipe: sinon.stub(),
       finalize: sinon.stub(),
     };
     manager.localActions = {
       "test-local-action-used": actionUsed,
       "test-local-action-unused": actionUnused,
     };
-    manager.remoteActionSandboxes = {};
 
-    await manager.preExecution();
     await manager.runRecipe(recipe);
     await manager.finalize();
 
     Assert.deepEqual(actionUsed.runRecipe.args, [[recipe]], "used action should be called with the recipe");
     ok(actionUsed.finalize.calledOnce, "finalize should be called on used action");
     Assert.deepEqual(actionUnused.runRecipe.args, [], "unused action should not be called with the recipe");
     ok(actionUnused.finalize.calledOnce, "finalize should be called on the unused action");
-
-    // Uptake telemetry is handled by actions directly, so doesn't
-    // need to be tested for local action handling here.
   },
 );
 
-// Likewise, error handling is dealt with internal to actions as well,
-// so doesn't need to be tested as a part of ActionsManager.
-
-// Test fetch remote actions
-decorate_task(
-  withStub(NormandyApi, "fetchActions"),
-  withStub(NormandyApi, "fetchImplementation"),
-  withStub(Uptake, "reportAction"),
-  async function(fetchActionsStub, fetchImplementationStub, reportActionStub) {
-    fetchActionsStub.callsFake(async () => [
-      {name: "remoteAction"},
-      {name: "missingImpl"},
-      {name: "migratedAction"},
-    ]);
-    fetchImplementationStub.callsFake(async ({ name }) => {
-      switch (name) {
-        case "remoteAction":
-          return "window.scriptRan = true";
-        case "missingImpl":
-          throw new Error(`Could not fetch implementation for ${name}: test error`);
-        case "migratedAction":
-          return "// this shouldn't be requested";
-        default:
-          throw new Error(`Could not fetch implementation for ${name}: unexpected action`);
-      }
-    });
-
-    const manager = new ActionsManager();
-    manager.localActions = {
-      migratedAction: {finalize: sinon.stub()},
-    };
-
-    await manager.fetchRemoteActions();
-
-    Assert.deepEqual(
-      Object.keys(manager.remoteActionSandboxes),
-      ["remoteAction"],
-      "remote action should have been loaded",
-    );
-
-    Assert.deepEqual(
-      fetchImplementationStub.args,
-      [[{name: "remoteAction"}], [{name: "missingImpl"}]],
-      "all remote actions should be requested",
-    );
-
-    Assert.deepEqual(
-      reportActionStub.args,
-      [["missingImpl", Uptake.ACTION_SERVER_ERROR]],
-      "Missing implementation should be reported via Uptake",
-    );
-
-    ok(
-      await manager.remoteActionSandboxes.remoteAction.evalInSandbox("window.scriptRan"),
-      "Implementations should be run in the sandbox",
-    );
-
-    // clean up sandboxes made by fetchRemoteActions
-    manager.finalize();
-  },
-);
--- a/toolkit/components/normandy/test/browser/browser_ClientEnvironment.js
+++ b/toolkit/components/normandy/test/browser/browser_ClientEnvironment.js
@@ -97,34 +97,12 @@ add_task(async function testExperiments(
     experiments.expired,
     ["expired"],
     "experiments.expired returns all expired experiment names",
   );
 
   getAll.restore();
 });
 
-add_task(withDriver(Assert, async function testAddonsInContext(driver) {
-  // Create before install so that the listener is added before startup completes.
-  const startupPromise = AddonTestUtils.promiseWebExtensionStartup("normandydriver@example.com");
-  const addonInstall = await AddonManager.getInstallForURL(TEST_XPI_URL);
-  await addonInstall.install();
-  const addonId = addonInstall.addon.id;
-  await startupPromise;
-
-  const addons = await ClientEnvironment.addons;
-  Assert.deepEqual(addons[addonId], {
-    id: [addonId],
-    name: "normandy_fixture",
-    version: "1.0",
-    installDate: addons[addonId].installDate,
-    isActive: true,
-    type: "extension",
-  }, "addons should be available in context");
-
-  const addon = await AddonManager.getAddonByID(addonId);
-  await addon.uninstall();
-}));
-
 add_task(async function isFirstRun() {
   await SpecialPowers.pushPrefEnv({set: [["app.normandy.first_run", true]]});
   ok(ClientEnvironment.isFirstRun, "isFirstRun is read from preferences");
 });
--- a/toolkit/components/normandy/test/browser/browser_Heartbeat.js
+++ b/toolkit/components/normandy/test/browser/browser_Heartbeat.js
@@ -1,13 +1,12 @@
 "use strict";
 
 ChromeUtils.import("resource://gre/modules/Services.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"`);
   }
@@ -63,21 +62,16 @@ function assertTelemetrySent(hb, eventNa
       events.push(Date.now());
 
       assertOrdered(events);
       resolve();
     });
   });
 }
 
-
-const sandboxManager = new SandboxManager();
-sandboxManager.addHold("test running");
-
-
 // Several of the behaviors of heartbeat prompt are mutually exclusive, so checks are broken up
 // into three batches.
 
 /* Batch #1 - General UI, Stars, and telemetry data */
 add_task(async function() {
   const targetWindow = Services.wm.getMostRecentWindow("navigator:browser");
   const notificationBox = targetWindow.gHighPriorityNotificationBox;
 
@@ -176,18 +170,8 @@ add_task(async function() {
     message: "test",
   });
 
   const telemetrySentPromise = assertTelemetrySent(hb, ["offeredTS", "windowClosedTS"]);
   // triggers sending ping to normandy
   await BrowserTestUtils.closeWindow(targetWindow);
   await telemetrySentPromise;
 });
-
-
-// Cleanup
-add_task(async function() {
-  // Make sure the sandbox is clean.
-  sandboxManager.removeHold("test running");
-  await sandboxManager.isNuked()
-    .then(() => ok(true, "sandbox is nuked"))
-    .catch(e => ok(false, "sandbox is nuked", e));
-});
deleted file mode 100644
--- a/toolkit/components/normandy/test/browser/browser_NormandyDriver.js
+++ /dev/null
@@ -1,215 +0,0 @@
-"use strict";
-
-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();
-  isnot(uuid1, uuid2, "uuids are unique");
-}));
-
-add_task(withDriver(Assert, async function userId(driver) {
-  // Test that userId is a UUID
-  ok(UUID_REGEX.test(driver.userId), "userId is a uuid");
-}));
-
-add_task(withDriver(Assert, async function syncDeviceCounts(driver) {
-  let client = await driver.client();
-  is(client.syncMobileDevices, 0, "syncMobileDevices defaults to zero");
-  is(client.syncDesktopDevices, 0, "syncDesktopDevices defaults to zero");
-  is(client.syncTotalDevices, 0, "syncTotalDevices defaults to zero");
-
-  await SpecialPowers.pushPrefEnv({
-    set: [
-      ["services.sync.clients.devices.mobile", 5],
-      ["services.sync.clients.devices.desktop", 4],
-    ],
-  });
-
-  client = await driver.client();
-  is(client.syncMobileDevices, 5, "syncMobileDevices is read when set");
-  is(client.syncDesktopDevices, 4, "syncDesktopDevices is read when set");
-  is(client.syncTotalDevices, 9, "syncTotalDevices is read when set");
-}));
-
-add_task(withDriver(Assert, async function distribution(driver) {
-  let client = await driver.client();
-  is(client.distribution, "default", "distribution has a default value");
-
-  await SpecialPowers.pushPrefEnv({set: [["distribution.id", "funnelcake"]]});
-  client = await driver.client();
-  is(client.distribution, "funnelcake", "distribution is read from preferences");
-}));
-
-decorate_task(
-  withSandboxManager(Assert),
-  async function testCreateStorage(sandboxManager) {
-    const driver = new NormandyDriver(sandboxManager);
-    sandboxManager.cloneIntoGlobal("driver", driver, {cloneFunctions: true});
-
-    // Assertion helpers
-    sandboxManager.addGlobal("is", is);
-    sandboxManager.addGlobal("deepEqual", (...args) => Assert.deepEqual(...args));
-
-    await sandboxManager.evalInSandbox(`
-      (async function sandboxTest() {
-        const store = driver.createStorage("testprefix");
-        const otherStore = driver.createStorage("othertestprefix");
-        await store.clear();
-        await otherStore.clear();
-
-        await store.setItem("willremove", 7);
-        await otherStore.setItem("willremove", 4);
-        is(await store.getItem("willremove"), 7, "createStorage stores sandbox values");
-        is(
-          await otherStore.getItem("willremove"),
-          4,
-          "values are not shared between createStorage stores",
-        );
-
-        const deepValue = {"foo": ["bar", "baz"]};
-        await store.setItem("deepValue", deepValue);
-        deepEqual(await store.getItem("deepValue"), deepValue, "createStorage clones stored values");
-
-        await store.removeItem("willremove");
-        is(await store.getItem("willremove"), null, "createStorage removes items");
-
-        is('prefix' in store, false, "createStorage doesn't expose non-whitelist attributes");
-      })();
-    `);
-  }
-);
-
-decorate_task(
-  withPrefEnv({
-    set: [
-      ["test.char", "a string"],
-      ["test.int", 5],
-      ["test.bool", true],
-    ],
-  }),
-  withSandboxManager(Assert, async function testPreferences(sandboxManager) {
-    const driver = new NormandyDriver(sandboxManager);
-    sandboxManager.cloneIntoGlobal("driver", driver, {cloneFunctions: true});
-
-    // Assertion helpers
-    sandboxManager.addGlobal("is", is);
-    sandboxManager.addGlobal("ok", ok);
-    sandboxManager.addGlobal("assertThrows", Assert.throws.bind(Assert));
-
-    await sandboxManager.evalInSandbox(`
-      (async function sandboxTest() {
-        ok(
-          driver.preferences.getBool("test.bool"),
-          "preferences.getBool can retrieve boolean preferences."
-        );
-        is(
-          driver.preferences.getInt("test.int"),
-          5,
-          "preferences.getInt can retrieve integer preferences."
-        );
-        is(
-          driver.preferences.getChar("test.char"),
-          "a string",
-          "preferences.getChar can retrieve string preferences."
-        );
-        assertThrows(
-          () => driver.preferences.getChar("test.int"),
-          "preferences.getChar throws when retreiving a non-string preference."
-        );
-        assertThrows(
-          () => driver.preferences.getInt("test.bool"),
-          "preferences.getInt throws when retreiving a non-integer preference."
-        );
-        assertThrows(
-          () => driver.preferences.getBool("test.char"),
-          "preferences.getBool throws when retreiving a non-boolean preference."
-        );
-        assertThrows(
-          () => driver.preferences.getChar("test.does.not.exist"),
-          "preferences.getChar throws when retreiving a non-existant preference."
-        );
-        assertThrows(
-          () => driver.preferences.getInt("test.does.not.exist"),
-          "preferences.getInt throws when retreiving a non-existant preference."
-        );
-        assertThrows(
-          () => driver.preferences.getBool("test.does.not.exist"),
-          "preferences.getBool throws when retreiving a non-existant preference."
-        );
-        ok(
-          driver.preferences.getBool("test.does.not.exist", true),
-          "preferences.getBool returns a default value if the preference doesn't exist."
-        );
-        is(
-          driver.preferences.getInt("test.does.not.exist", 7),
-          7,
-          "preferences.getInt returns a default value if the preference doesn't exist."
-        );
-        is(
-          driver.preferences.getChar("test.does.not.exist", "default"),
-          "default",
-          "preferences.getChar returns a default value if the preference doesn't exist."
-        );
-        ok(
-          driver.preferences.has("test.char"),
-          "preferences.has returns true if the given preference exists."
-        );
-        ok(
-          !driver.preferences.has("test.does.not.exist"),
-          "preferences.has returns false if the given preference does not exist."
-        );
-      })();
-    `);
-  })
-);
-
-decorate_task(
-  withSandboxManager(Assert),
-  withMockPreferences,
-  PreferenceExperiments.withMockExperiments(),
-  async function testPreferenceStudies(sandboxManager) {
-    const driver = new NormandyDriver(sandboxManager);
-    sandboxManager.cloneIntoGlobal("driver", driver, {cloneFunctions: true});
-
-    // Assertion helpers
-    sandboxManager.addGlobal("is", is);
-    sandboxManager.addGlobal("ok", ok);
-
-    await sandboxManager.evalInSandbox(`
-      (async function sandboxTest() {
-        const studyName = "preftest";
-        let hasStudy = await driver.preferenceExperiments.has(studyName);
-        ok(!hasStudy, "preferenceExperiments.has returns false if the study hasn't been started yet.");
-
-        await driver.preferenceExperiments.start({
-          name: studyName,
-          branch: "control",
-          preferenceName: "test.pref",
-          preferenceValue: true,
-          preferenceBranchType: "user",
-          preferenceType: "boolean",
-        });
-        hasStudy = await driver.preferenceExperiments.has(studyName);
-        ok(hasStudy, "preferenceExperiments.has returns true after the study has been started.");
-
-        let study = await driver.preferenceExperiments.get(studyName);
-        is(
-          study.branch,
-          "control",
-          "preferenceExperiments.get fetches studies from within a sandbox."
-        );
-        ok(!study.expired, "Studies are marked as active after being started by the driver.");
-
-        await driver.preferenceExperiments.stop(studyName);
-        study = await driver.preferenceExperiments.get(studyName);
-        ok(study.expired, "Studies are marked as inactive after being stopped by the driver.");
-      })();
-    `);
-  }
-);
--- a/toolkit/components/normandy/test/browser/browser_RecipeRunner.js
+++ b/toolkit/components/normandy/test/browser/browser_RecipeRunner.js
@@ -1,17 +1,16 @@
 "use strict";
 
 ChromeUtils.import("resource://testing-common/TestUtils.jsm", this);
 ChromeUtils.import("resource://gre/modules/components-utils/FilterExpressions.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/ActionsManager.jsm", this);
 ChromeUtils.import("resource://normandy/lib/AddonStudies.jsm", this);
 ChromeUtils.import("resource://normandy/lib/Uptake.jsm", this);
 
 const { RemoteSettings } = ChromeUtils.import("resource://services-settings/remote-settings.js");
 
 add_task(async function getFilterContext() {
   const recipe = {id: 17, arguments: {foo: "bar"}, unrelated: false};
@@ -123,66 +122,40 @@ decorate_task(
     ]});
     getStub.reset();
     ok(!getStub.called, "getClientClassification hasn't been called");
     await RecipeRunner.run();
     ok(!getStub.called, "getClientClassification was not called eagerly");
   }
 );
 
-/**
- * Mocks RecipeRunner.loadActionSandboxManagers for testing run.
- */
-async function withMockActionSandboxManagers(actions, testFunction) {
-  const managers = {};
-  for (const action of actions) {
-    const manager = new ActionSandboxManager("");
-    manager.addHold("testing");
-    managers[action.name] = manager;
-    sinon.stub(managers[action.name], "runAsyncCallback");
-  }
-
-  await testFunction(managers);
-
-  for (const manager of Object.values(managers)) {
-    manager.removeHold("testing");
-    await manager.isNuked();
-  }
-}
-
 decorate_task(
   withStub(Uptake, "reportRunner"),
   withStub(NormandyApi, "fetchRecipes"),
-  withStub(ActionsManager.prototype, "fetchRemoteActions"),
-  withStub(ActionsManager.prototype, "preExecution"),
   withStub(ActionsManager.prototype, "runRecipe"),
   withStub(ActionsManager.prototype, "finalize"),
   withStub(Uptake, "reportRecipe"),
   async function testRun(
     reportRunnerStub,
     fetchRecipesStub,
-    fetchRemoteActionsStub,
-    preExecutionStub,
     runRecipeStub,
     finalizeStub,
     reportRecipeStub,
   ) {
     const runRecipeReturn = Promise.resolve();
     const runRecipeReturnThen = sinon.spy(runRecipeReturn, "then");
     runRecipeStub.returns(runRecipeReturn);
 
     const matchRecipe = {id: "match", action: "matchAction", filter_expression: "true"};
     const noMatchRecipe = {id: "noMatch", action: "noMatchAction", filter_expression: "false"};
     const missingRecipe = {id: "missing", action: "missingAction", filter_expression: "true"};
     fetchRecipesStub.callsFake(async () => [matchRecipe, noMatchRecipe, missingRecipe]);
 
     await RecipeRunner.run();
 
-    ok(fetchRemoteActionsStub.calledOnce, "remote actions should be fetched");
-    ok(preExecutionStub.calledOnce, "pre-execution hooks should be run");
     Assert.deepEqual(
       runRecipeStub.args,
       [[matchRecipe], [missingRecipe]],
       "recipe with matching filters should be executed",
     );
     ok(runRecipeReturnThen.called, "the run method should be used asyncronously");
 
     // Test uptake reporting
@@ -202,23 +175,21 @@ decorate_task(
 decorate_task(
   withPrefEnv({
     set: [
       ["features.normandy-remote-settings.enabled", true],
     ],
   }),
   withStub(NormandyApi, "verifyObjectSignature"),
   withStub(ActionsManager.prototype, "runRecipe"),
-  withStub(ActionsManager.prototype, "fetchRemoteActions"),
   withStub(ActionsManager.prototype, "finalize"),
   withStub(Uptake, "reportRecipe"),
   async function testReadFromRemoteSettings(
     verifyObjectSignatureStub,
     runRecipeStub,
-    fetchRemoteActionsStub,
     finalizeStub,
     reportRecipeStub,
   ) {
     const matchRecipe =  { name: "match", action: "matchAction", filter_expression: "true" };
     const noMatchRecipe = { name: "noMatch", action: "noMatchAction", filter_expression: "false" };
     const missingRecipe = { name: "missing", action: "missingAction", filter_expression: "true" };
 
     const rsCollection = await RecipeRunner._remoteSettingsClientForTesting.openCollection();
@@ -285,41 +256,34 @@ decorate_task(
     );
   }
 );
 
 decorate_task(
   withMockNormandyApi,
   async function testRunFetchFail(mockApi) {
     const reportRunner = sinon.stub(Uptake, "reportRunner");
-
-    const action = {name: "action"};
-    mockApi.actions = [action];
     mockApi.fetchRecipes.rejects(new Error("Signature not valid"));
 
-    await withMockActionSandboxManagers(mockApi.actions, async managers => {
-      const manager = managers.action;
-      await RecipeRunner.run();
+    await RecipeRunner.run();
 
-      // If the recipe fetch failed, do not run anything.
-      sinon.assert.notCalled(manager.runAsyncCallback);
-      sinon.assert.calledWith(reportRunner, Uptake.RUNNER_SERVER_ERROR);
+    // If the recipe fetch failed, report a server error
+    sinon.assert.calledWith(reportRunner, Uptake.RUNNER_SERVER_ERROR);
 
-      // Test that network errors report a specific uptake error
-      reportRunner.reset();
-      mockApi.fetchRecipes.rejects(new Error("NetworkError: The system was down"));
-      await RecipeRunner.run();
-      sinon.assert.calledWith(reportRunner, Uptake.RUNNER_NETWORK_ERROR);
+    // Test that network errors report a specific uptake error
+    reportRunner.reset();
+    mockApi.fetchRecipes.rejects(new Error("NetworkError: The system was down"));
+    await RecipeRunner.run();
+    sinon.assert.calledWith(reportRunner, Uptake.RUNNER_NETWORK_ERROR);
 
-      // Test that signature issues report a specific uptake error
-      reportRunner.reset();
-      mockApi.fetchRecipes.rejects(new NormandyApi.InvalidSignatureError("Signature fail"));
-      await RecipeRunner.run();
-      sinon.assert.calledWith(reportRunner, Uptake.RUNNER_INVALID_SIGNATURE);
-    });
+    // Test that signature issues report a specific uptake error
+    reportRunner.reset();
+    mockApi.fetchRecipes.rejects(new NormandyApi.InvalidSignatureError("Signature fail"));
+    await RecipeRunner.run();
+    sinon.assert.calledWith(reportRunner, Uptake.RUNNER_INVALID_SIGNATURE);
 
     reportRunner.restore();
   }
 );
 
 // test init() in dev mode
 decorate_task(
   withPrefEnv({
--- a/toolkit/components/normandy/test/browser/browser_Storage.js
+++ b/toolkit/components/normandy/test/browser/browser_Storage.js
@@ -1,12 +1,11 @@
 "use strict";
 
 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);
--- a/toolkit/components/normandy/test/browser/head.js
+++ b/toolkit/components/normandy/test/browser/head.js
@@ -1,14 +1,12 @@
 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://normandy-content/AboutPages.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.defineModuleGetter(this, "TelemetryTestUtils",
                                "resource://testing-common/TelemetryTestUtils.jsm");
 
 const CryptoHash = Components.Constructor("@mozilla.org/security/hash;1",
                                           "nsICryptoHash", "initWithString");
 const FileInputStream = Components.Constructor("@mozilla.org/network/file-input-stream;1",
@@ -95,40 +93,16 @@ this.withInstalledWebExtension = functio
             ok(expectUninstall, "Add-on should not be unexpectedly uninstalled during test");
           }
         }
       }
     );
   };
 };
 
-this.withSandboxManager = function(Assert) {
-  return function wrapper(testFunction) {
-    return async function wrappedTestFunction(...args) {
-      const sandboxManager = new SandboxManager();
-      sandboxManager.addHold("test running");
-
-      await testFunction(...args, sandboxManager);
-
-      sandboxManager.removeHold("test running");
-      await sandboxManager.isNuked()
-        .then(() => Assert.ok(true, "sandbox is nuked"))
-        .catch(e => Assert.ok(false, "sandbox is nuked", e));
-    };
-  };
-};
-
-this.withDriver = function(Assert, testFunction) {
-  return withSandboxManager(Assert)(async function inner(...args) {
-    const sandboxManager = args[args.length - 1];
-    const driver = new NormandyDriver(sandboxManager);
-    await testFunction(driver, ...args);
-  });
-};
-
 this.withMockNormandyApi = function(testFunction) {
   return async function inner(...args) {
     const mockApi = {actions: [], recipes: [], implementations: {}, extensionDetails: {}};
 
     // Use callsFake instead of resolves so that the current values in mockApi are used.
     mockApi.fetchActions = sinon.stub(NormandyApi, "fetchActions").callsFake(async () => mockApi.actions);
     mockApi.fetchRecipes = sinon.stub(NormandyApi, "fetchRecipes").callsFake(async () => mockApi.recipes);
     mockApi.fetchImplementation = sinon.stub(NormandyApi, "fetchImplementation").callsFake(
deleted file mode 100644
--- a/toolkit/components/normandy/test/unit/test_SandboxManager.js
+++ /dev/null
@@ -1,110 +0,0 @@
-"use strict";
-
-const {SandboxManager} = ChromeUtils.import("resource://normandy/lib/SandboxManager.jsm");
-
-// wrapAsync should wrap privileged Promises with Promises that are usable by
-// the sandbox.
-add_task(async function() {
-  const manager = new SandboxManager();
-  manager.addHold("testing");
-
-  manager.cloneIntoGlobal("driver", {
-    async privileged() {
-      return "privileged";
-    },
-    wrapped: manager.wrapAsync(async function() {
-      return "wrapped";
-    }),
-    aValue: "aValue",
-    wrappedThis: manager.wrapAsync(async function() {
-      return this.aValue;
-    }),
-  }, {cloneFunctions: true});
-
-  // Assertion helpers
-  manager.addGlobal("ok", ok);
-  manager.addGlobal("equal", equal);
-
-  const sandboxResult = await new Promise(resolve => {
-    manager.addGlobal("resolve", result => resolve(result));
-    manager.evalInSandbox(`
-      // Unwrapped privileged promises are not accessible in the sandbox
-      try {
-        const privilegedResult = driver.privileged().then(() => false);
-        ok(false, "The sandbox could not use a privileged Promise");
-      } catch (err) { }
-
-      // Wrapped functions return promises that the sandbox can access.
-      const wrappedResult = driver.wrapped();
-      ok("then" in wrappedResult);
-
-      // Resolve the Promise around the sandbox with the wrapped result to test
-      // that the Promise in the sandbox works.
-      wrappedResult.then(resolve);
-    `);
-  });
-  equal(sandboxResult, "wrapped", "wrapAsync methods return Promises that work in the sandbox");
-
-  await manager.evalInSandbox(`
-    (async function sandboxTest() {
-      equal(
-        await driver.wrappedThis(),
-        "aValue",
-        "wrapAsync preserves the behavior of the this keyword",
-      );
-    })();
-  `);
-
-  manager.removeHold("testing");
-});
-
-// wrapAsync cloning options
-add_task(async function() {
-  const manager = new SandboxManager();
-  manager.addHold("testing");
-
-  // clonedArgument stores the argument passed to cloned(), which we use to test
-  // that arguments from within the sandbox are cloned outside.
-  let clonedArgument = null;
-  manager.cloneIntoGlobal("driver", {
-    uncloned: manager.wrapAsync(async function() {
-      return {value: "uncloned"};
-    }),
-    cloned: manager.wrapAsync(async function(argument) {
-      clonedArgument = argument;
-      return {value: "cloned"};
-    }, {cloneInto: true, cloneArguments: true}),
-  }, {cloneFunctions: true});
-
-  // Assertion helpers
-  manager.addGlobal("ok", ok);
-  manager.addGlobal("deepEqual", deepEqual);
-
-  await new Promise(resolve => {
-    manager.addGlobal("resolve", resolve);
-    manager.evalInSandbox(`
-      (async function() {
-        // The uncloned return value should be privileged and inaccesible.
-        const uncloned = await driver.uncloned();
-        ok(!("value" in uncloned), "The sandbox could not use an uncloned return value");
-
-        // The cloned return value should be usable.
-        deepEqual(
-          await driver.cloned({value: "insidesandbox"}),
-          {value: "cloned"},
-          "The sandbox could use the cloned return value",
-        );
-      })().then(resolve);
-    `);
-  });
-
-  // Removing the hold nukes the sandbox. Afterwards, because cloned() has the
-  // cloneArguments option, the clonedArgument variable should still be
-  // accessible.
-  manager.removeHold("testing");
-  deepEqual(
-    clonedArgument,
-    {value: "insidesandbox"},
-    "cloneArguments allowed an argument from within the sandbox to persist after it was nuked",
-  );
-});
--- a/toolkit/components/normandy/test/unit/xpcshell.ini
+++ b/toolkit/components/normandy/test/unit/xpcshell.ini
@@ -6,9 +6,8 @@ support-files =
   invalid_recipe_signature_api/**
   query_server.sjs
   echo_server.sjs
   utils.js
 tags = normandy
 
 [test_addon_unenroll.js]
 [test_NormandyApi.js]
-[test_SandboxManager.js]