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
--- 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]