Bug 1352224 - Run a Telemetry Experiment on Nightly to test Flash set to Click-To-Activate by default. r=dothayer
authorFelipe Gomes <felipc@gmail.com>
Fri, 14 Apr 2017 19:36:29 -0300
changeset 121 aef9c8055a67
parent 120 dc0e815061ca
child 122 4fad28b4fb92
push id94
push userfelipc@gmail.com
push date2017-04-14 22:37 +0000
reviewersdothayer
bugs1352224
Bug 1352224 - Run a Telemetry Experiment on Nightly to test Flash set to Click-To-Activate by default. r=dothayer
experiments/flash-as-ctp-nightly55/code/bootstrap.js
experiments/flash-as-ctp-nightly55/code/install.rdf
experiments/flash-as-ctp-nightly55/manifest.json
copy from experiments/intersection-observer-nightly55/code/bootstrap.js
copy to experiments/flash-as-ctp-nightly55/code/bootstrap.js
--- a/experiments/intersection-observer-nightly55/code/bootstrap.js
+++ b/experiments/flash-as-ctp-nightly55/code/bootstrap.js
@@ -1,66 +1,319 @@
+/*
+ * This experiment will test changing Flash as Click-to-Activate
+ * by default. It will put the users in a test/control cohort,
+ * with 50% chance each.
+ *
+ * Other cohort names are:
+ *
+ * - disqualified, disqualified-prefchanged
+ *   User has non-default prefs at the start of the experiment
+ *   (leading to `disqualified`), or any of the prefs involved
+ *   in the experiment changed any while the experiment is
+ *   running (leading to `disqualified-prefchanged`).
+ *   Once in a disqualified cohort, the user will never come
+ *   back to a test/control cohort.
+ *
+ * - no-flash, blocklisted
+ *   Similar to disqualified, but will let us know the difference
+ *   between a Flash user who has non-standard prefs, vs. a user
+ *   who can't or is not running Flash.
+ *   Users in this cohort have a chance of coming back to the
+ *   "test-waiting" cohort on the next restart.
+ *
+ * - test-waiting
+ *   User was chosen for the "test" cohort, but we won't actually
+ *   start the experiment (and set them to CTA) until we're sure
+ *   they have received the appropriate blocklist from Shavar.
+ */
+
 let {classes: Cc, interfaces: Ci, utils: Cu} = Components;
 
 Cu.import("resource:///modules/experiments/Experiments.jsm");
+Cu.import("resource://gre/modules/Preferences.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/Task.jsm");
-Cu.import("resource://gre/modules/Preferences.jsm");
+
+
+const FLASH_MIME_TYPE = "application/x-shockwave-flash";
 
 var gStarted = false;
 
-const kSELF_ID = "intersection-observer-nightly55@experiments.mozilla.org";
+const kSELF_ID = "flash-as-ctp-nightly55@experiments.mozilla.org";
+
+const PREF_FLASH_CTP_MODE         = "plugin.state.flash";
+const PREF_FLASH_LISTS            = "plugins.flashBlock.enabled";
+const PREF_FLASH_FALLBACK_MODE    = "plugins.favorfallback.mode";
+const PREF_FLASH_FALLBACK_RULES   = "plugins.favorfallback.rules";
+const PREF_FLASH_HIDDEN_FROM_LIST = "plugins.navigator.hidden_ctp_plugin";
+
+const PREF_SHAVAR_LAST_UPDATED    = "browser.safebrowsing.provider.mozilla.lastupdatetime";
+const PREF_SHAVAR_NEXT_UPDATE     = "browser.safebrowsing.provider.mozilla.nextupdatetime";
+const PREF_SHAVAR_FLASH_TABLE     = "urlclassifier.flashSubDocTable";
+
 const prefs = new Preferences();
+const defaultPrefs = new Preferences({defaultBranch: true});
+const experiments = Experiments.instance();
 
 function startup() {
   // Seems startup() function is launched twice after install, we're
   // unsure why so far. We only want it to run once.
   if (gStarted) {
     return;
   }
   gStarted = true;
 
+  updateBranch();
+}
+
+function updateBranch() {
   Task.spawn(function*() {
-    let branch = yield Experiments.instance().getExperimentBranch(kSELF_ID);
+    let branch = yield experiments.getExperimentBranch(kSELF_ID);
+
     if (branch == null) {
-      let r = Math.random() * 2;
-      if (r < 1) {
+      let disqualified = checkHasAnyUserPref(true /* isFirstCheck */);
+
+      if (disqualified) {
+        branch = "disqualified";
+      } else if (Math.random() >= 0.5) {
+        // "Test-waiting" means this user was chosen for the test
+        // branch, but we won't tag them as proper "test" (and change
+        // their prefs) until we're sure that the Flash classification
+        // lists are received from Shavar.
+        branch = "test-waiting";
+      } else {
         branch = "control";
-      } else {
-        branch = "enabled";
-        prefs.set("dom.IntersectionObserver.enabled", true);
       }
-      yield Experiments.instance().setExperimentBranch(kSELF_ID, branch);
-    } else {
-      // Exclude the user if they've manually changed the pref
-      let pref = prefs.get("dom.IntersectionObserver.enabled");
-      let isBranchValid = true;
-      if (branch == "enabled") {
-        isBranchValid = (pref == true);
-      } else if (branch == "control") {
-        isBranchValid = (pref == false);
+
+      yield experiments.setExperimentBranch(kSELF_ID, branch);
+    }
+
+    switch (branch) {
+      case "test-waiting":
+      {
+        // User was chosen for the "test" cohort, but we won't actually
+        // start the experiment (and set them to CTA) until we're sure
+        // they have received the appropriate blocklist from Shavar.
+         let canBegin = prefs.get(PREF_FLASH_LISTS, false) &&
+                       (yield checkHasReceivedShavarUpdatedLists());
+
+        if (canBegin) {
+          branch = "test";
+          yield experiments.setExperimentBranch(kSELF_ID, branch);
+          startExperiment();
+        } else {
+          // Once we set PREF_FLASH_LISTS to true, that will trigger
+          // an update from Shavar. When that update is complete, the
+          // pref PREF_SHAVAR_LAST_UPDATED gets update. By watching that
+          // pref we have a chance of moving the user from "test-waiting"
+          // to "test" more quickly. Otherwise that would take 2 restarts.
+          prefs.observe(PREF_SHAVAR_LAST_UPDATED, function prefObserver() {
+            prefs.ignore(PREF_SHAVAR_LAST_UPDATED, prefObserver);
+            updateBranch();
+          });
+
+          // This is the only pref that we are setting as a user-pref,
+          // because setting it on every startup through the defaultPrefs
+          // might trigger a shavar list update (which is watching pref
+          // changes), and it's better to avoid that.
+          // Also, we need to set this here in the "test-waiting" phase,
+          // otherwise the urlclassifier service won't request updates
+          // to the flash-blocking tables.
+          if (prefs.get(PREF_FLASH_LISTS, false) == false) {
+            // Try to force a list update on the next restart.
+            prefs.set(PREF_SHAVAR_NEXT_UPDATE, "1");
+            prefs.set(PREF_FLASH_LISTS, true);
+          }
+        }
+        break;
       }
-      if (!isBranchValid) {
-        branch = "usermod";
-        yield Experiments.instance().setExperimentBranch(kSELF_ID, branch);
-      }
+      case "test":
+        startExperiment();
+        break;
+
+      case "no-flash":
+      case "blocklisted":
+        // Perhaps the bad running conditions are gone now:
+        let badRunningConditions = checkRunningConditions();
+        if (!badRunningConditions) {
+          branch = "test-waiting";
+          yield experiments.setExperimentBranch(kSELF_ID, branch);
+        }
+        break;
+
+      case "disqualified":
+      case "disqualified-prefchanged":
+      case "control":
+      default:
+        break;
     }
+
   }).then(
     function() {
     },
     function(e) {
       Cu.reportError("Got error during bootstrap startup: " + e);
-    });
+    }
+  );
+}
+
+function setUpExperimentPrefs(on) {
+  if (on) {
+    // Makes Flash Click-To-Play
+    defaultPrefs.set(PREF_FLASH_CTP_MODE, 1);
+    // Turn on Favor-Fallback mode for <video> elements inside <object>
+    defaultPrefs.set(PREF_FLASH_FALLBACK_MODE, "follow-ctp");
+    defaultPrefs.set(PREF_FLASH_FALLBACK_RULES, "video");
+    // Disable the pref on Nightly that hides Flash from
+    // navigator.plugins when CTP
+    defaultPrefs.set(PREF_FLASH_HIDDEN_FROM_LIST, "");
+  } else {
+    // There's no way to "reset" default prefs, so let's set
+    // them to the values we know to be the default.
+    defaultPrefs.set(PREF_FLASH_CTP_MODE, 2);
+    defaultPrefs.set(PREF_FLASH_FALLBACK_MODE, "never");
+    defaultPrefs.set(PREF_FLASH_FALLBACK_RULES, "");
+    defaultPrefs.set(PREF_FLASH_HIDDEN_FROM_LIST, "Shockwave Flash");
+
+    prefs.reset(PREF_FLASH_LISTS);
+  }
 }
 
-function shutdown(reason) {
-  if (reason == ADDON_DISABLE || reason == ADDON_UNINSTALL) {
-    // when the add-on is being disabled/uninstalled
-    prefs.reset("dom.IntersectionObserver.enabled");
+function checkHasAnyUserPref(isFirstCheck) {
+
+  // We have to make an expection for the PREF_FLASH_LISTS
+  // since that one is set through user-prefs (for reasons
+  // explained above).
+  // If this is not a recheck, i.e., this is the first run
+  // of the experiment, this pref will have its default
+  // value of false. If we're running the experiment already,
+  // and we're just listening for user-made changes, then
+  // the expected value will be true.
+  let expectedFlashListsPrefValue = (isFirstCheck)
+                                    ? false
+                                    : true;
+
+  return prefs.isSet(PREF_FLASH_CTP_MODE) ||
+         prefs.isSet(PREF_FLASH_FALLBACK_RULES) ||
+         prefs.isSet(PREF_FLASH_FALLBACK_MODE) ||
+         prefs.isSet(PREF_FLASH_HIDDEN_FROM_LIST) ||
+         prefs.isSet(PREF_SHAVAR_FLASH_TABLE) ||
+         (prefs.get(PREF_FLASH_LISTS, false) != expectedFlashListsPrefValue);
+}
+
+
+function checkRunningConditions() {
+  let pluginHost = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost);
+  let pluginTag;
+
+  try {
+    pluginTag = pluginHost.getPluginTagForType(FLASH_MIME_TYPE);
+  } catch (e) {
+    return "no-flash";
+  }
+
+  if (pluginTag.blocklisted) {
+    return "blocklisted";
   }
 }
 
-function install() {
-  // no-op
+function checkHasReceivedShavarUpdatedLists() {
+  let classifier = Cc['@mozilla.org/url-classifier/dbservice;1']
+                     .getService(Ci.nsIURIClassifier);
+
+  let { NetUtil } = Cu.import("resource://gre/modules/NetUtil.jsm", {});
+  let table = Services.prefs.getCharPref(PREF_SHAVAR_FLASH_TABLE);
+  let uri = NetUtil.newURI("https://nightly.flashstudy.example.com");
+
+  return new Promise((resolve) => {
+     classifier.asyncClassifyLocalWithTables(uri, table,
+      function(errorCode, list, provider, prefix) {
+        let found = (errorCode == 0) && (list != "");
+        resolve(found);
+      });
+  });
+}
+
+function toggleCTPDropdownHandler(on) {
+  if (on) {
+    Services.obs.addObserver(hideFlashCTPDropdown, "EM-loaded", false);
+  } else {
+    Services.obs.removeObserver(hideFlashCTPDropdown, "EM-loaded");
+  }
 }
 
-function uninstall() {
-  prefs.reset("dom.IntersectionObserver.enabled");
+function hideFlashCTPDropdown(win) {
+  let winUtils = win.QueryInterface(Ci.nsIInterfaceRequestor)
+                    .getInterface(Ci.nsIDOMWindowUtils);
+
+  winUtils.loadSheetUsingURIString(`data:text/css;charset=utf-8,
+    .addon-view[name="Shockwave Flash"] .status-control-wrapper
+    {
+      display: none;
+    }`
+    , winUtils.AUTHOR_SHEET);
+};
+
+
+function startExperiment() {
+  let badRunningConditions = checkRunningConditions();
+  if (badRunningConditions) {
+    let branch = badRunningConditions;
+    experiments.setExperimentBranch(kSELF_ID, branch).then(
+      null, Cu.reportError);
+    return;
+  }
+
+  setUpExperimentPrefs(true);
+  toggleCTPDropdownHandler(true);
+  watchPrefChanges(true);
+}
+
+function stopExperiment() {
+  watchPrefChanges(false);
+  setUpExperimentPrefs(false);
+  toggleCTPDropdownHandler(false);
 }
+
+function recheckPrefs() {
+  if (checkHasAnyUserPref(false /* isFirstCheck */)) {
+    Task.spawn(function*() {
+      yield experiments.setExperimentBranch(kSELF_ID, "disqualified-prefchanged");
+      stopExperiment();
+    }).then(null, function(e) { Cu.reportError(e); })
+  }
+}
+
+function watchPrefChanges(on) {
+  if (on) {
+    prefs.observe(PREF_FLASH_CTP_MODE, recheckPrefs);
+    prefs.observe(PREF_FLASH_LISTS, recheckPrefs);
+    prefs.observe(PREF_FLASH_FALLBACK_MODE, recheckPrefs);
+    prefs.observe(PREF_FLASH_FALLBACK_RULES, recheckPrefs);
+    prefs.observe(PREF_FLASH_HIDDEN_FROM_LIST, recheckPrefs);
+  } else {
+    try {
+      // These prefs might not being observed if startExperiment()
+      // was not called (e.g., because it is still in test-waiting
+      // phase), but shutdown/uninstall got called.
+      prefs.ignore(PREF_FLASH_CTP_MODE, recheckPrefs);
+      prefs.ignore(PREF_FLASH_LISTS, recheckPrefs);
+      prefs.ignore(PREF_FLASH_FALLBACK_MODE, recheckPrefs);
+      prefs.ignore(PREF_FLASH_FALLBACK_RULES, recheckPrefs);
+      prefs.ignore(PREF_FLASH_HIDDEN_FROM_LIST, recheckPrefs);
+    } catch (e) {}
+  }
+}
+
+function install(a,b) {
+}
+
+function uninstall(a,b) {
+  stopExperiment();
+}
+
+function shutdown(a, reason) {
+  if (reason == ADDON_DISABLE || reason == ADDON_UNINSTALL) {
+    // When the add-on is being disabled/uninstalled.
+    stopExperiment();
+  }
+}
copy from experiments/intersection-observer-nightly55/code/install.rdf
copy to experiments/flash-as-ctp-nightly55/code/install.rdf
--- a/experiments/intersection-observer-nightly55/code/install.rdf
+++ b/experiments/flash-as-ctp-nightly55/code/install.rdf
@@ -1,24 +1,24 @@
 <?xml version="1.0" encoding="utf-8"?>
 <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>intersection-observer-nightly55@experiments.mozilla.org</em:id>
+    <em:id>flash-as-ctp-nightly55@experiments.mozilla.org</em:id>
     <em:version>1.0.0</em:version>
     <em:type>128</em:type>
     <em:bootstrap>true</em:bootstrap>
     <em:unpack>false</em:unpack>
 
     <!-- Firefox -->
     <em:targetApplication>
       <Description>
         <em:id>{c8cb8c64-f556-11e6-bc64-92361f002671}</em:id>
         <em:minVersion>55.0a1</em:minVersion>
-        <em:maxVersion>55.0a1</em:maxVersion>
+        <em:maxVersion>56.0a1</em:maxVersion>
       </Description>
     </em:targetApplication>
 
     <!-- Front End MetaData -->
-    <em:name>Intersection Observer API</em:name>
-    <em:description>Measures the impact of enabling the Intersection Observer API in Firefox</em:description>
-    <em:aboutURL>https://bugzilla.mozilla.org/show_bug.cgi?id=1341259</em:aboutURL>
+    <em:name>Flash as Click-To-Activate</em:name>
+    <em:description>Measuring the impact of setting Flash as Click-To-Activate by default</em:description>
+    <em:aboutURL>https://bugzilla.mozilla.org/show_bug.cgi?id=1352224</em:aboutURL>
   </Description>
 </RDF>
copy from experiments/intersection-observer-nightly55/manifest.json
copy to experiments/flash-as-ctp-nightly55/manifest.json
--- a/experiments/intersection-observer-nightly55/manifest.json
+++ b/experiments/flash-as-ctp-nightly55/manifest.json
@@ -1,17 +1,19 @@
 {
   "publish"     : true,
   "priority"    : 2,
-  "name"        : "Intersection Observer API",
-  "description" : "Measuring the impact of enabling the Intersection Observer API in Firefox.",
-  "info"        : "<p><a href=\"https://bugzilla.mozilla.org/show_bug.cgi?id=1341259\">Related bug</a></p>",
+  "name"        : "Flash as Click-To-Activate",
+  "description" : "Measuring the impact of setting Flash as Click-To-Activate by default",
+  "info"        : "<p><a href=\"https://bugzilla.mozilla.org/show_bug.cgi?id=1352224\">Related bug</a></p>",
   "manifest"    : {
-    "id"               : "intersection-observer-nightly55@experiments.mozilla.org",
-    "startTime"        : 1490659200,
-    "endTime"          : 1491264000,
-    "maxActiveSeconds" : 604800,
+    "id"               : "flash-as-ctp-nightly55@experiments.mozilla.org",
+    "startTime"        : 1492052400,
+    "endTime"          : 1492916400,
+    "maxActiveSeconds" : 864000,
     "appName"          : ["Firefox"],
     "channel"          : ["nightly"],
+    "minBuildID"       : "20170331030216",
     "minVersion"       : "55.0a1",
-    "maxVersion"       : "55.0a1"
+    "maxVersion"       : "55.0a1",
+    "sample"           : 0.25
   }
 }