Bug 1465703 - Add bookmark and follow_bookmark probes for Savant Shield study; r=mak, adw draft
authorBianca Danforth <bdanforth@mozilla.com>
Wed, 06 Jun 2018 04:25:36 -0700
changeset 805863 19336dcf1332be537887760fff3e80d1de5a25c1
parent 805786 e0595117ff5bda3a63a72ad7b3b8754fec4fb4f0
child 805878 26a84bd3c58fe24c293448f2d60e413ec62c682d
push id112786
push userbdanforth@mozilla.com
push dateFri, 08 Jun 2018 16:35:42 +0000
reviewersmak, adw
bugs1465703
milestone62.0a1
Bug 1465703 - Add bookmark and follow_bookmark probes for Savant Shield study; r=mak, adw These probes will register and record (for the duration of the study only): * When a bookmark is added. * When a bookmark is removed. * When a bookmark is visited. Also moved init of study module to later in startup as an idle task to mitigate performance impact. Previously, this patch was failing the browser/base/content/test/performance/browser_startup.js test. Note for bookmark added/removed: Using the source argument, we can filter out bulk events like from Sync, import, restore, etc. Note for bookmark visited: This will also fire for the case when the user visits a page directly that happens to be bookmarked. The user doesn't have to "click" on a bookmark. MozReview-Commit-ID: EYVlTLfVLkd
browser/components/nsBrowserGlue.js
browser/modules/SavantShieldStudy.jsm
toolkit/components/telemetry/Events.yaml
--- a/browser/components/nsBrowserGlue.js
+++ b/browser/components/nsBrowserGlue.js
@@ -1059,18 +1059,16 @@ BrowserGlue.prototype = {
 
     PageActions.init();
 
     this._firstWindowTelemetry(aWindow);
     this._firstWindowLoaded();
 
     // Set the default favicon size for UI views that use the page-icon protocol.
     PlacesUtils.favicons.setDefaultIconURIPreferredSize(16 * aWindow.devicePixelRatio);
-
-    SavantShieldStudy.init();
   },
 
   _sendMediaTelemetry() {
     let win = Services.appShell.hiddenDOMWindow;
     let v = win.document.createElementNS("http://www.w3.org/1999/xhtml", "video");
     v.reportCanPlayTelemetry();
   },
 
@@ -1272,16 +1270,20 @@ BrowserGlue.prototype = {
 
     Services.tm.idleDispatchToMainThread(() => {
       LanguagePrompt.init();
     });
 
     Services.tm.idleDispatchToMainThread(() => {
       Blocklist.loadBlocklistAsync();
     });
+
+    Services.tm.idleDispatchToMainThread(() => {
+      SavantShieldStudy.init();
+    });
   },
 
   /**
    * Use this function as an entry point to schedule tasks that need
    * to run once per session, at any arbitrary point in time.
    * This function will be called from an idle observer. Check the value of
    * LATE_TASKS_IDLE_TIME_SEC to see the current value for this idle
    * observer.
--- a/browser/modules/SavantShieldStudy.jsm
+++ b/browser/modules/SavantShieldStudy.jsm
@@ -5,17 +5,18 @@
 "use strict";
 
 var EXPORTED_SYMBOLS = ["SavantShieldStudy"];
 
 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 ChromeUtils.import("resource://gre/modules/Services.jsm");
 
 XPCOMUtils.defineLazyModuleGetters(this, {
-  AddonManager: "resource://gre/modules/AddonManager.jsm"
+  AddonManager: "resource://gre/modules/AddonManager.jsm",
+  PlacesUtils: "resource://gre/modules/PlacesUtils.jsm"
 });
 
 // See LOG_LEVELS in Console.jsm. Examples: "all", "info", "warn", & "error".
 const PREF_LOG_LEVEL = "shield.savant.loglevel";
 
 // Create a new instance of the ConsoleAPI so we can control the maxLogLevel with a pref.
 XPCOMUtils.defineLazyGetter(this, "log", () => {
   let ConsoleAPI = ChromeUtils.import("resource://gre/modules/Console.jsm", {}).ConsoleAPI;
@@ -36,16 +37,17 @@ class SavantShieldStudyClass {
     // 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;
   }
 
   init() {
     this.TelemetryEvents = new TelemetryEvents(this.STUDY_TELEMETRY_CATEGORY);
     this.AddonListener = new AddonListener(this.STUDY_TELEMETRY_CATEGORY);
+    this.BookmarkObserver = new BookmarkObserver(this.STUDY_TELEMETRY_CATEGORY);
 
     // 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);
   }
@@ -75,16 +77,17 @@ class SavantShieldStudyClass {
     this.initStudyDuration();
 
     if (this.isStudyExpired()) {
       log.debug("Study expired in between this and the previous session.");
       this.endStudy("expired");
     }
 
     this.AddonListener.init();
+    this.BookmarkObserver.init();
   }
 
   isEligible() {
     const isAlwaysPrivateBrowsing = Services.prefs.getBoolPref(this.ALWAYS_PRIVATE_BROWSING_PREF);
     if (isAlwaysPrivateBrowsing) {
       return false;
     }
 
@@ -152,16 +155,18 @@ class SavantShieldStudyClass {
     // be sent along with this session's main ping.
     if (!isStudyEnding && this.isStudyExpired()) {
       log.debug("Study expired during this session.");
       this.endStudy("expired");
       return;
     }
 
     this.AddonListener.uninit();
+    this.BookmarkObserver.uninit();
+
     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);
   }
 }
 
@@ -243,9 +248,74 @@ class AddonListener {
     AddonManager.removeAddonListener(this.listener);
   }
 
   uninit() {
     this.removeListeners();
   }
 }
 
+class BookmarkObserver {
+  constructor(studyCategory) {
+    this.STUDY_TELEMETRY_CATEGORY = studyCategory;
+    // there are two probes: bookmark and follow_bookmark
+    this.METHOD_1 = "bookmark";
+    this.EXTRA_SUBCATEGORY_1 = "feature";
+    this.METHOD_2 = "follow_bookmark";
+    this.EXTRA_SUBCATEGORY_2 = "navigation";
+    this.TYPE_BOOKMARK = Ci.nsINavBookmarksService.TYPE_BOOKMARK;
+    // Ignore "fake" bookmarks created for bookmark tags
+    this.skipTags = true;
+  }
+
+  init() {
+    this.addObservers();
+  }
+
+  addObservers() {
+    PlacesUtils.bookmarks.addObserver(this);
+  }
+
+  onItemAdded(itemID, parentID, index, itemType, uri, title, dateAdded, guid, parentGUID, source) {
+    this.handleBookmarkSaveRemove(itemType, uri, source, "save");
+  }
+
+  onItemRemoved(itemID, parentID, index, itemType, uri, guid, parentGUID, source) {
+    this.handleBookmarkSaveRemove(itemType, uri, source, "remove");
+  }
+
+  handleItemAddRemove(itemType, uri, source, event) {
+    /*
+    * "place:query" uris are used to create containers like Most Visited or
+    * Recently Bookmarked. These are added as default bookmarks.
+    */
+    if (itemType === this.TYPE_BOOKMARK && !uri.schemeIs("place")
+      && source === PlacesUtils.bookmarks.SOURCE_DEFAULT) {
+      const isBookmarkProbe = true;
+      this.recordEvent(event, isBookmarkProbe);
+    }
+  }
+
+  // This observer is only fired for TYPE_BOOKMARK items.
+  onItemVisited(itemID, visitID, time, transitionType, uri, parentID, guid, parentGUID) {
+    const isBookmarkProbe = false;
+    this.recordEvent("open", isBookmarkProbe);
+  }
+
+  recordEvent(event, isBookmarkProbe) {
+    const method = isBookmarkProbe ? this.METHOD_1 : this.METHOD_2;
+    const subcategory = isBookmarkProbe ? this.EXTRA_SUBCATEGORY_1 : this.EXTRA_SUBCATEGORY_2;
+    Services.telemetry.recordEvent(this.STUDY_TELEMETRY_CATEGORY, method, event, null,
+                                  {
+                                    subcategory
+                                  });
+  }
+
+  removeObservers() {
+    PlacesUtils.bookmarks.removeObserver(this);
+  }
+
+  uninit() {
+    this.removeObservers();
+  }
+}
+
 const SavantShieldStudy = new SavantShieldStudyClass();
--- a/toolkit/components/telemetry/Events.yaml
+++ b/toolkit/components/telemetry/Events.yaml
@@ -143,16 +143,42 @@ savant:
       The value field records the addon ID for the event.
     bug_numbers: [1457226, 1465707]
     notification_emails:
       - "bdanforth@mozilla.com"
       - "shong@mozilla.com"
     expiry_version: "65"
     extra_keys:
       subcategory: The broad event category for this probe. E.g. navigation
+  bookmark:
+    objects: ["save", "remove"]
+    release_channel_collection: opt-out
+    record_in_processes: ["main"]
+    description: >
+      This is recorded any time a bookmark is saved or removed.
+    bug_numbers: [1457226, 1465703]
+    notification_emails:
+      - "bdanforth@mozilla.com"
+      - "shong@mozilla.com"
+    expiry_version: "65"
+    extra_keys:
+      subcategory: The broad event category for this probe. E.g. navigation
+  follow_bookmark:
+    objects: ["open"]
+    release_channel_collection: opt-out
+    record_in_processes: ["main"]
+    description: >
+      This is recorded any time a bookmark is visited.
+    bug_numbers: [1457226, 1465703]
+    notification_emails:
+      - "bdanforth@mozilla.com"
+      - "shong@mozilla.com"
+    expiry_version: "65"
+    extra_keys:
+      subcategory: The broad event category for this probe. E.g. navigation
 
 # This category contains event entries used for Telemetry tests.
 # They will not be sent out with any pings.
 telemetry.test:
   test:
     methods: ["test1", "test2"]
     objects: ["object1", "object2"]
     bug_numbers: [1286606]