Bug 1462725 - Add study expiration and testing override pref; r=rhelmer draft
authorBianca Danforth <bdanforth@mozilla.com>
Sat, 26 May 2018 16:09:55 -0700
changeset 803324 d8f6dcf3105968a3cc2381b28fd277c207e446e1
parent 803323 f26a1112e2cadb3d0ec016cef2fd375e2dcdf5dd
child 803325 950900b42e9320b21a07f8e18cf3826796c4c1d7
push id112069
push userbdanforth@mozilla.com
push dateSun, 03 Jun 2018 04:54:10 +0000
reviewersrhelmer
bugs1462725
milestone62.0a1
Bug 1462725 - Add study expiration and testing override pref; r=rhelmer The study will shutdown on its own after a specified period of time. The testing override pref allows for changing the study duration from the default, which is 4 weeks. MozReview-Commit-ID: GEWpCaJX3Mr
browser/modules/SavantShieldStudy.jsm
--- a/browser/modules/SavantShieldStudy.jsm
+++ b/browser/modules/SavantShieldStudy.jsm
@@ -26,25 +26,28 @@ XPCOMUtils.defineLazyGetter(this, "log",
   return new ConsoleAPI(consoleOptions);
 });
 
 class SavantShieldStudyClass {
   constructor() {
     this.STUDY_PREF = "shield.savant.enabled";
     this.STUDY_TELEMETRY_CATEGORY = "savant";
     this.ALWAYS_PRIVATE_BROWSING_PREF = "browser.privatebrowsing.autostart";
+    this.STUDY_DURATION_OVERRIDE_PREF = "shield.savant.duration_override";
+    this.STUDY_EXPIRATION_DATE_PREF = "shield.savant.expiration_date";
+    // ms = 'x' weeks * 7 days/week * 24 hours/day * 60 minutes/hour
+    // * 60 seconds/minute * 1000 milliseconds/second
+    this.DEFAULT_STUDY_DURATION_MS = 4 * 7 * 24 * 60 * 60 * 1000;
   }
 
   async init() {
     this.TELEMETRY_CLIENT_ID = await ClientID.getClientID();
     this.TelemetryEvents = new TelemetryEvents(this.STUDY_TELEMETRY_CATEGORY,
                                               this.TELEMETRY_CLIENT_ID);
 
-    // TODO check expiration, add study duration override pref
-
     // check the pref in case Normandy flipped it on before we could add the pref listener
     this.shouldCollect = Services.prefs.getBoolPref(this.STUDY_PREF);
     if (this.shouldCollect) {
       this.startupStudy();
     }
     Services.prefs.addObserver(this.STUDY_PREF, this);
   }
 
@@ -57,53 +60,115 @@ class SavantShieldStudyClass {
       } else {
         // The pref has been turned off
         this.endStudy("study-disabled");
       }
     }
   }
 
   startupStudy() {
+    // enable before any possible calls to endStudy, since it sends an 'end_study' event
+    this.TelemetryEvents.enableCollection();
+
     if (!this.isEligible()) {
       this.endStudy("ineligible");
       return;
     }
 
-    this.TelemetryEvents.enableCollection();
+    this.initStudyDuration();
+
+    if (this.isStudyExpired()) {
+      log.debug("Study expired in between this and the previous session.");
+      this.endStudy("expired");
+    }
   }
 
   isEligible() {
     const isAlwaysPrivateBrowsing = Services.prefs.getBoolPref(this.ALWAYS_PRIVATE_BROWSING_PREF);
     if (isAlwaysPrivateBrowsing) {
       return false;
     }
 
     return true;
   }
 
+  initStudyDuration() {
+    if (Services.prefs.getStringPref(this.STUDY_EXPIRATION_DATE_PREF, "")) {
+      return;
+    }
+    Services.prefs.setStringPref(
+      this.STUDY_EXPIRATION_DATE_PREF,
+      this.getExpirationDateString()
+    );
+  }
+
+  getDurationFromPref() {
+    return Services.prefs.getIntPref(this.STUDY_DURATION_OVERRIDE_PREF, 0);
+  }
+
+  getExpirationDateString() {
+    const now = Date.now();
+    const studyDurationInMs =
+    this.getDurationFromPref()
+      || this.DEFAULT_STUDY_DURATION_MS;
+    const expirationDateInt = now + studyDurationInMs;
+    return new Date(expirationDateInt).toISOString();
+  }
+
+  isStudyExpired() {
+    const expirationDateInt =
+      Date.parse(Services.prefs.getStringPref(
+        this.STUDY_EXPIRATION_DATE_PREF,
+        this.getExpirationDateString()
+      ));
+
+    if (isNaN(expirationDateInt)) {
+      log.error(
+        `The value for the preference ${this.STUDY_EXPIRATION_DATE_PREF} is invalid.`
+      );
+      return false;
+    }
+
+    if (Date.now() > expirationDateInt) {
+      return true;
+    }
+    return false;
+  }
+
 
   sendEvent(method, object, value, extra) {
     this.TelemetryEvents.sendEvent(method, object, value, extra);
   }
 
   endStudy(reason) {
     log.debug(`Ending the study due to reason: ${ reason }`);
-    this.TelemetryEvents.disableCollection();
+    const isStudyEnding = true;
     // Services.telemetry.recordEvent(this.STUDY_TELEMETRY_CATEGORY, "end_study", reason);
-    this.uninit();
-    // Study pref needs to persist between restarts, so only reset on endStudy
+    this.TelemetryEvents.disableCollection();
+    this.uninit(isStudyEnding);
+    // These prefs needs to persist between restarts, so only reset on endStudy
     Services.prefs.clearUserPref(this.STUDY_PREF);
+    Services.prefs.clearUserPref(this.STUDY_EXPIRATION_DATE_PREF);
   }
 
   // Called on every Firefox shutdown and endStudy
-  uninit() {
-    // TODO: clear study expiration override pref and remove its listener
+  uninit(isStudyEnding = false) {
+    // if just shutting down, check for expiration, so the endStudy event can
+    // be sent along with this session's main ping.
+    if (!isStudyEnding && this.isStudyExpired()) {
+      log.debug("Study expired during this session.");
+      this.endStudy("expired");
+      return;
+    }
+
     Services.prefs.removeObserver(this.ALWAYS_PRIVATE_BROWSING_PREF, this);
     Services.prefs.removeObserver(this.STUDY_PREF, this);
+    Services.prefs.removeObserver(this.STUDY_DURATION_OVERRIDE_PREF, this);
     Services.prefs.clearUserPref(PREF_LOG_LEVEL);
+    Services.prefs.clearUserPref(this.STUDY_DURATION_OVERRIDE_PREF);
   }
 
   async getFlowID(str) {
     return this.TelemetryEvents.getFlowID(str);
   }
 }
 
 const SavantShieldStudy = new SavantShieldStudyClass();