Bug 1465703 - Add bookmark and follow_bookmark probes for Savant Shield study; r=adw
authorBianca Danforth <bdanforth@mozilla.com>
Wed, 06 Jun 2018 04:25:36 -0700
changeset 421969 7deab88709c76a757173f029e3a2479d6517f620
parent 421968 dd482a0086c4841b03a4a57c18bce19197df1fb2
child 421970 2b65a8f996222369279cc2152ecaa7eaee93898a
push id34113
push userbtara@mozilla.com
push dateSat, 09 Jun 2018 12:05:46 +0000
treeherdermozilla-central@21bc698a4d9c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersadw
bugs1465703
milestone62.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1465703 - Add bookmark and follow_bookmark probes for Savant Shield study; r=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]