Bug 1383338 - fetch and run shield studies soon after UI startup r?gijs r?mythmon draft
authorRobert Helmer <rhelmer@mozilla.com>
Wed, 26 Jul 2017 10:10:47 -0700
changeset 616101 407433a9c8de9cd7ba99560c3ea5784e8fad9513
parent 615935 388d81ed93fa640f91d155f36254667c734157cf
child 639377 23e11620772abadec5e2eb8da580880069bf11da
push id70583
push userrhelmer@mozilla.com
push dateWed, 26 Jul 2017 18:15:57 +0000
reviewersgijs, mythmon
bugs1383338
milestone56.0a1
Bug 1383338 - fetch and run shield studies soon after UI startup r?gijs r?mythmon MozReview-Commit-ID: CWRQmwKplII
browser/extensions/shield-recipe-client/install.rdf.in
browser/extensions/shield-recipe-client/lib/ClientEnvironment.jsm
browser/extensions/shield-recipe-client/lib/RecipeRunner.jsm
browser/extensions/shield-recipe-client/lib/ShieldRecipeClient.jsm
browser/extensions/shield-recipe-client/test/browser/browser_ClientEnvironment.js
browser/extensions/shield-recipe-client/test/browser/browser_RecipeRunner.js
--- a/browser/extensions/shield-recipe-client/install.rdf.in
+++ b/browser/extensions/shield-recipe-client/install.rdf.in
@@ -3,17 +3,17 @@
 #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>55</em:version>
+    <em:version>55.1</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>
--- a/browser/extensions/shield-recipe-client/lib/ClientEnvironment.jsm
+++ b/browser/extensions/shield-recipe-client/lib/ClientEnvironment.jsm
@@ -192,11 +192,15 @@ this.ClientEnvironment = {
         } else {
           names.active.push(experiment.name);
         }
       }
 
       return names;
     });
 
+    XPCOMUtils.defineLazyGetter(environment, "isFirstRun", () => {
+      return Preferences.get("extensions.shield-recipe-client.first_run");
+    });
+
     return environment;
   },
 };
--- a/browser/extensions/shield-recipe-client/lib/RecipeRunner.jsm
+++ b/browser/extensions/shield-recipe-client/lib/RecipeRunner.jsm
@@ -33,28 +33,53 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 Cu.importGlobalProperties(["fetch"]);
 
 this.EXPORTED_SYMBOLS = ["RecipeRunner"];
 
 const log = LogManager.getLogger("recipe-runner");
 const prefs = Services.prefs.getBranch("extensions.shield-recipe-client.");
 const TIMER_NAME = "recipe-client-addon-run";
 const RUN_INTERVAL_PREF = "run_interval_seconds";
+const FIRST_RUN_PREF = "first_run";
+const UI_AVAILABLE_NOTIFICATION = "sessionstore-windows-restored";
+const SHIELD_INIT_NOTIFICATION = "shield-init-complete";
 
 this.RecipeRunner = {
   init() {
     if (!this.checkPrefs()) {
       return;
     }
 
     if (prefs.getBoolPref("dev_mode")) {
       // Run right now in dev mode
       this.run();
     }
 
+    if (prefs.getBoolPref(FIRST_RUN_PREF)) {
+      // Run once immediately after the UI is available. Do this before adding the
+      // timer so we can't end up racing it.
+      const observer = {
+        observe: (subject, topic, data) => {
+          Services.obs.removeObserver(observer, UI_AVAILABLE_NOTIFICATION);
+
+          this.run();
+          this.registerTimer();
+          prefs.setBoolPref(FIRST_RUN_PREF, false);
+
+          Services.obs.notifyObservers(null, SHIELD_INIT_NOTIFICATION);
+        },
+      };
+      Services.obs.addObserver(observer, UI_AVAILABLE_NOTIFICATION);
+      CleanupManager.addCleanupHandler(() => Services.obs.removeObserver(observer, UI_AVAILABLE_NOTIFICATION));
+    } else {
+      this.registerTimer();
+    }
+  },
+
+  registerTimer() {
     this.updateRunInterval();
     CleanupManager.addCleanupHandler(() => timerManager.unregisterTimer(TIMER_NAME));
 
     // Watch for the run interval to change, and re-register the timer with the new value
     prefs.addObserver(RUN_INTERVAL_PREF, this);
     CleanupManager.addCleanupHandler(() => prefs.removeObserver(RUN_INTERVAL_PREF, this));
   },
 
--- a/browser/extensions/shield-recipe-client/lib/ShieldRecipeClient.jsm
+++ b/browser/extensions/shield-recipe-client/lib/ShieldRecipeClient.jsm
@@ -34,16 +34,17 @@ const PREF_BRANCH = "extensions.shield-r
 const DEFAULT_PREFS = {
   api_url: "https://normandy.cdn.mozilla.net/api/v1",
   dev_mode: false,
   enabled: true,
   startup_delay_seconds: 300,
   "logging.level": Log.Level.Warn,
   user_id: "",
   run_interval_seconds: 86400, // 24 hours
+  first_run: true,
 };
 const PREF_DEV_MODE = "extensions.shield-recipe-client.dev_mode";
 const PREF_SELF_SUPPORT_ENABLED = "browser.selfsupport.enabled";
 const PREF_LOGGING_LEVEL = PREF_BRANCH + "logging.level";
 
 let log = null;
 
 /**
--- a/browser/extensions/shield-recipe-client/test/browser/browser_ClientEnvironment.js
+++ b/browser/extensions/shield-recipe-client/test/browser/browser_ClientEnvironment.js
@@ -107,8 +107,18 @@ add_task(async function testExperiments(
   Assert.deepEqual(
     experiments.expired,
     ["expired"],
     "experiments.expired returns all expired experiment names",
   );
 
   getAll.restore();
 });
+
+add_task(async function isFirstRun() {
+  let environment = ClientEnvironment.getEnvironment();
+
+  // isFirstRun is read from a preference
+  await SpecialPowers.pushPrefEnv({set: [["extensions.shield-recipe-client.first_run", true]]});
+  environment = ClientEnvironment.getEnvironment();
+  ok(environment.isFirstRun, "isFirstRun is read from preferences");
+});
+
--- a/browser/extensions/shield-recipe-client/test/browser/browser_RecipeRunner.js
+++ b/browser/extensions/shield-recipe-client/test/browser/browser_RecipeRunner.js
@@ -195,28 +195,40 @@ add_task(withMockNormandyApi(async funct
 }));
 
 add_task(async function testStartup() {
   const runStub = sinon.stub(RecipeRunner, "run");
   const addCleanupHandlerStub = sinon.stub(CleanupManager, "addCleanupHandler");
   const updateRunIntervalStub = sinon.stub(RecipeRunner, "updateRunInterval");
 
   // in dev mode
-  await SpecialPowers.pushPrefEnv({set: [["extensions.shield-recipe-client.dev_mode", true]]});
+  await SpecialPowers.pushPrefEnv({
+    set: [
+      ["extensions.shield-recipe-client.dev_mode", true],
+      ["extensions.shield-recipe-client.first_run", false],
+    ],
+  });
+
   RecipeRunner.init();
   ok(runStub.called, "RecipeRunner.run is called immediately when in dev mode");
   ok(addCleanupHandlerStub.called, "A cleanup function is registered when in dev mode");
   ok(updateRunIntervalStub.called, "A timer is registered when in dev mode");
 
   runStub.reset();
   addCleanupHandlerStub.reset();
   updateRunIntervalStub.reset();
 
   // not in dev mode
-  await SpecialPowers.pushPrefEnv({set: [["extensions.shield-recipe-client.dev_mode", false]]});
+  await SpecialPowers.pushPrefEnv({
+    set: [
+      ["extensions.shield-recipe-client.dev_mode", false],
+      ["extensions.shield-recipe-client.first_run", false],
+    ],
+  });
+
   RecipeRunner.init();
   ok(!runStub.called, "RecipeRunner.run is not called immediately when not in dev mode");
   ok(addCleanupHandlerStub.called, "A cleanup function is registered when not in dev mode");
   ok(updateRunIntervalStub.called, "A timer is registered when not in dev mode");
 
   runStub.restore();
   addCleanupHandlerStub.restore();
   updateRunIntervalStub.restore();