Bug 1331705 - shield-recipe-client: Do not use an XRay-ed Promise during recipe execution, r=Gijs
☠☠ backed out by d5479d988aff ☠ ☠
authorMythmon <mcooper@mozilla.com>
Tue, 17 Jan 2017 11:27:40 -0800
changeset 463138 ce90a9d52e86c1edc7e194a23eabc1548e4dc104
parent 463137 9ca73fb60c36714d16b9a74cb36e5d80f4162f65
child 463139 9a193223d4aa501014257551ffe659e6568aef27
push id41967
push userbmo:miket@mozilla.com
push dateWed, 18 Jan 2017 15:17:32 +0000
reviewersGijs
bugs1331705
milestone53.0a1
Bug 1331705 - shield-recipe-client: Do not use an XRay-ed Promise during recipe execution, r=Gijs MozReview-Commit-ID: DEM6lUiCHnj
browser/extensions/shield-recipe-client/lib/RecipeRunner.jsm
browser/extensions/shield-recipe-client/test/browser.ini
browser/extensions/shield-recipe-client/test/browser_RecipeRunner.js
--- a/browser/extensions/shield-recipe-client/lib/RecipeRunner.jsm
+++ b/browser/extensions/shield-recipe-client/lib/RecipeRunner.jsm
@@ -123,40 +123,57 @@ this.RecipeRunner = {
   },
 
   /**
    * Execute a recipe by fetching it action and executing it.
    * @param  {Object} recipe A recipe to execute
    * @promise Resolves when the action has executed
    */
   executeRecipe: Task.async(function* (recipe, extraContext) {
-    const sandboxManager = new SandboxManager();
-    const {sandbox} = sandboxManager;
-
     const action = yield NormandyApi.fetchAction(recipe.action);
     const response = yield fetch(action.implementation_url);
 
     const actionScript = yield response.text();
-    const prepScript = `
-      var pendingAction = null;
+    yield this.executeAction(recipe, extraContext, actionScript);
+  }),
 
-      function registerAction(name, Action) {
-        let a = new Action(sandboxedDriver, sandboxedRecipe);
-        pendingAction = a.execute()
-          .catch(err => sandboxedDriver.log(err, 'error'));
+  /**
+   * Execute an action in a sandbox for a specific recipe.
+   * @param  {Object} recipe A recipe to execute
+   * @param  {Object} extraContext Extra data about the user, see NormandyDriver
+   * @param  {String} actionScript The JavaScript for the action to execute.
+   * @promise Resolves or rejects when the action has executed or failed.
+   */
+  executeAction: function(recipe, extraContext, actionScript) {
+    return new Promise((resolve, reject) => {
+      const sandboxManager = new SandboxManager();
+      const {sandbox} = sandboxManager;
+      const prepScript = `
+        function registerAction(name, Action) {
+          let a = new Action(sandboxedDriver, sandboxedRecipe);
+          a.execute()
+            .then(actionFinished)
+            .catch(err => sandboxedDriver.log(err, 'error'));
+        };
+
+        window.registerAction = registerAction;
+        window.setTimeout = sandboxedDriver.setTimeout;
+        window.clearTimeout = sandboxedDriver.clearTimeout;
+      `;
+
+      const driver = new NormandyDriver(sandboxManager, extraContext);
+      sandbox.sandboxedDriver = Cu.cloneInto(driver, sandbox, {cloneFunctions: true});
+      sandbox.sandboxedRecipe = Cu.cloneInto(recipe, sandbox);
+      sandbox.actionFinished = result => {
+        sandboxManager.removeHold("recipeExecution");
+        resolve(result);
+      };
+      sandbox.actionFailed = err => {
+        sandboxManager.removeHold("recipeExecution");
+        reject(err);
       };
 
-      window.registerAction = registerAction;
-      window.setTimeout = sandboxedDriver.setTimeout;
-      window.clearTimeout = sandboxedDriver.clearTimeout;
-    `;
-
-    const driver = new NormandyDriver(sandboxManager, extraContext);
-    sandbox.sandboxedDriver = Cu.cloneInto(driver, sandbox, {cloneFunctions: true});
-    sandbox.sandboxedRecipe = Cu.cloneInto(recipe, sandbox);
-
-    Cu.evalInSandbox(prepScript, sandbox);
-    Cu.evalInSandbox(actionScript, sandbox);
-
-    sandboxManager.addHold("recipeExecution");
-    sandbox.pendingAction.then(() => sandboxManager.removeHold("recipeExecution"));
-  }),
+      sandboxManager.addHold("recipeExecution");
+      Cu.evalInSandbox(prepScript, sandbox);
+      Cu.evalInSandbox(actionScript, sandbox);
+    });
+  },
 };
--- a/browser/extensions/shield-recipe-client/test/browser.ini
+++ b/browser/extensions/shield-recipe-client/test/browser.ini
@@ -1,8 +1,9 @@
 [browser_driver_uuids.js]
 [browser_env_expressions.js]
 [browser_EventEmitter.js]
 [browser_Storage.js]
 [browser_Heartbeat.js]
 [browser_NormandyApi.js]
   support-files =
     test_server.sjs
+[browser_RecipeRunner.js]
new file mode 100644
--- /dev/null
+++ b/browser/extensions/shield-recipe-client/test/browser_RecipeRunner.js
@@ -0,0 +1,29 @@
+"use strict";
+
+const {utils: Cu} = Components;
+Cu.import("resource://shield-recipe-client/lib/RecipeRunner.jsm", this);
+
+add_task(function*() {
+  // Test that RecipeRunner can execute a basic recipe/action.
+  const recipe = {
+    foo: "bar",
+  };
+  const actionScript = `
+    class TestAction {
+      constructor(driver, recipe) {
+        this.recipe = recipe;
+      }
+
+      execute() {
+        return new Promise(resolve => {
+          resolve(this.recipe.foo);
+        });
+      }
+    }
+
+    registerAction('test-action', TestAction);
+  `;
+
+  const result = yield RecipeRunner.executeAction(recipe, {}, actionScript);
+  is(result, "bar", "Recipe executed correctly");
+});