Bug 1500147 - Part 3: Record add-on manager telemetry for page/browser actions r=rpl
authorMark Striemer <mstriemer@mozilla.com>
Thu, 07 Feb 2019 16:43:02 +0000
changeset 458274 286129ec63de146b55a3ef0292c6a927a0f4f698
parent 458273 0a5e45e5783401a625ea582968567be21b1b978c
child 458275 91eea845d54eb42a7a54327a652f79681c17a570
push id35522
push usernbeleuzu@mozilla.com
push dateSat, 09 Feb 2019 03:34:29 +0000
treeherdermozilla-central@4e56ef85817a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersrpl
bugs1500147
milestone67.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 1500147 - Part 3: Record add-on manager telemetry for page/browser actions r=rpl Differential Revision: https://phabricator.services.mozilla.com/D18003
browser/base/content/browser-pageActions.js
browser/base/content/browser.js
browser/components/extensions/test/browser/browser_ext_browserAction_contextMenu.js
browser/modules/test/browser/browser_PageActions.js
--- a/browser/base/content/browser-pageActions.js
+++ b/browser/base/content/browser-pageActions.js
@@ -870,16 +870,21 @@ var BrowserPageActions = {
   openAboutAddonsForContextAction() {
     if (!this._contextAction) {
       return;
     }
     let action = this._contextAction;
     this._contextAction = null;
 
     PageActions.logTelemetry("managed", action);
+    AMTelemetry.recordActionEvent({
+      object: "pageAction",
+      action: "manage",
+      extra: {addonId: action.extensionID},
+    });
 
     let viewID = "addons://detail/" + encodeURIComponent(action.extensionID);
     window.BrowserOpenAddonsMgr(viewID);
   },
 
   _contextAction: null,
 
   /**
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -7,16 +7,17 @@ var {XPCOMUtils} = ChromeUtils.import("r
 var {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
 var {AppConstants} = ChromeUtils.import("resource://gre/modules/AppConstants.jsm");
 ChromeUtils.import("resource://gre/modules/NotificationDB.jsm");
 
 // lazy module getters
 
 XPCOMUtils.defineLazyModuleGetters(this, {
   AddonManager: "resource://gre/modules/AddonManager.jsm",
+  AMTelemetry: "resource://gre/modules/AddonManager.jsm",
   BrowserUsageTelemetry: "resource:///modules/BrowserUsageTelemetry.jsm",
   BrowserUtils: "resource://gre/modules/BrowserUtils.jsm",
   BrowserWindowTracker: "resource:///modules/BrowserWindowTracker.jsm",
   CFRPageActions: "resource://activity-stream/lib/CFRPageActions.jsm",
   CharsetMenu: "resource://gre/modules/CharsetMenu.jsm",
   Color: "resource://gre/modules/Color.jsm",
   ContentSearch: "resource:///modules/ContentSearch.jsm",
   ContextualIdentityService: "resource://gre/modules/ContextualIdentityService.jsm",
@@ -6369,26 +6370,37 @@ var ToolbarContextMenu = {
     let title = getFormattedString("webext.remove.confirmation.title", [name]);
     let message = getFormattedString("webext.remove.confirmation.message", [name, brand]);
     let btnTitle = getString("webext.remove.confirmation.button");
     let {BUTTON_TITLE_IS_STRING: titleString, BUTTON_TITLE_CANCEL: titleCancel,
          BUTTON_POS_0, BUTTON_POS_1, confirmEx} = Services.prompt;
     let btnFlags = BUTTON_POS_0 * titleString + BUTTON_POS_1 * titleCancel;
     let response = confirmEx(null, title, message, btnFlags, btnTitle, null, null, null,
                              {value: 0});
+    AMTelemetry.recordActionEvent({
+      object: "browserAction",
+      action: "uninstall",
+      value: response ? "cancelled" : "accepted",
+      extra: {addonId: addon.id},
+    });
     if (response == 0) {
       addon.uninstall();
     }
   },
 
   openAboutAddonsForContextAction(popup) {
     let id = this._getExtensionId(popup);
     if (id) {
       let viewID = "addons://detail/" + encodeURIComponent(id);
       BrowserOpenAddonsMgr(viewID);
+      AMTelemetry.recordActionEvent({
+        object: "browserAction",
+        action: "manage",
+        extra: {addonId: id},
+      });
     }
   },
 };
 
 var gPageStyleMenu = {
   // This maps from a <browser> element (or, more specifically, a
   // browser's permanentKey) to an Object that contains the most recent
   // information about the browser content's stylesheets. That Object
--- a/browser/components/extensions/test/browser/browser_ext_browserAction_contextMenu.js
+++ b/browser/components/extensions/test/browser/browser_ext_browserAction_contextMenu.js
@@ -39,16 +39,42 @@ let contextMenuItems = {
   "context-viewsource": "",
   "context-viewinfo": "disabled",
   "inspect-separator": "hidden",
   "context-inspect": "hidden",
   "context-inspect-a11y": "hidden",
   "context-bookmarkpage": "hidden",
 };
 
+const TELEMETRY_CATEGORY = "addonsManager";
+const TELEMETRY_METHODS = new Set(["action", "link", "view"]);
+const type = "extension";
+
+function assertTelemetryMatches(events) {
+  let snapshot = Services.telemetry.snapshotEvents(
+    Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, true);
+
+  if (events.length == 0) {
+    ok(!snapshot.parent || snapshot.parent.length == 0, "There are no telemetry events");
+    return;
+  }
+
+  // Make sure we got some data.
+  ok(snapshot.parent && snapshot.parent.length > 0, "Got parent telemetry events in the snapshot");
+
+  // Only look at the related events after stripping the timestamp and category.
+  let relatedEvents = snapshot.parent
+    .filter(([timestamp, category, method]) =>
+      category == TELEMETRY_CATEGORY && TELEMETRY_METHODS.has(method))
+    .map(relatedEvent => relatedEvent.slice(2, 6));
+
+  // Events are now [method, object, value, extra] as expected.
+  Assert.deepEqual(relatedEvents, events, "The events are recorded correctly");
+}
+
 add_task(async function browseraction_popup_contextmenu() {
   let extension = ExtensionTestUtils.loadExtension(extData);
   await extension.startup();
 
   await clickBrowserAction(extension, window);
 
   let contentAreaContextMenu = await openContextMenuInPopup(extension);
   let item = contentAreaContextMenu.getElementsByAttribute("label", "Click me!");
@@ -240,19 +266,31 @@ add_task(async function browseraction_co
 
   await extension.startup();
 
   info("Add a dummy tab to prevent about:addons from being loaded in the initial about:blank tab");
   let dummyTab = await BrowserTestUtils.openNewForegroundTab(win.gBrowser, "http://example.com", true, true);
 
   info("Run tests in normal mode");
   await main(false);
+  assertTelemetryMatches([
+    ["action", "browserAction", null, {action: "manage", addonId: id}],
+    ["view", "aboutAddons", "detail", {addonId: id, type}],
+    ["action", "browserAction", null, {action: "manage", addonId: id}],
+    ["view", "aboutAddons", "detail", {addonId: id, type}],
+  ]);
 
   info("Run tests in customize mode");
   await main(true);
+  assertTelemetryMatches([
+    ["action", "browserAction", null, {action: "manage", addonId: id}],
+    ["view", "aboutAddons", "detail", {addonId: id, type}],
+    ["action", "browserAction", null, {action: "manage", addonId: id}],
+    ["view", "aboutAddons", "detail", {addonId: id, type}],
+  ]);
 
   info("Close the dummy tab and finish");
   win.gBrowser.removeTab(dummyTab);
   await extension.unload();
 
   await BrowserTestUtils.closeWindow(win);
 });
 
@@ -343,34 +381,48 @@ add_task(async function browseraction_co
     }
   }
 
   await extension.startup();
 
   info("Run tests in normal mode");
   await main(false);
 
+  assertTelemetryMatches([
+    ["action", "browserAction", "cancelled", {action: "uninstall", addonId: id}],
+    ["action", "browserAction", "cancelled", {action: "uninstall", addonId: id}],
+  ]);
+
   info("Run tests in customize mode");
   await main(true);
 
+  assertTelemetryMatches([
+    ["action", "browserAction", "cancelled", {action: "uninstall", addonId: id}],
+    ["action", "browserAction", "cancelled", {action: "uninstall", addonId: id}],
+  ]);
+
   let addon = await AddonManager.getAddonByID(id);
   ok(addon, "Addon is still installed");
 
   promptService._response = 0;
   let uninstalled = new Promise((resolve) => {
     AddonManager.addAddonListener({
       onUninstalled(addon) {
         is(addon.id, id, "The expected add-on has been uninstalled");
         AddonManager.removeAddonListener(this);
         resolve();
       },
     });
   });
   await testContextMenu("toolbar-context-menu", false);
   await uninstalled;
 
+  assertTelemetryMatches([
+    ["action", "browserAction", "accepted", {action: "uninstall", addonId: id}],
+  ]);
+
   addon = await AddonManager.getAddonByID(id);
   ok(!addon, "Addon has been uninstalled");
 
   await extension.unload();
 
   await BrowserTestUtils.closeWindow(win);
 });
--- a/browser/modules/test/browser/browser_PageActions.js
+++ b/browser/modules/test/browser/browser_PageActions.js
@@ -1257,16 +1257,18 @@ add_task(async function removeRetainStat
   // Done, clean up.
   testAction.remove();
 });
 
 
 // Opens the context menu on a non-built-in action.  (The context menu for
 // built-in actions is tested in browser_page_action_menu.js.)
 add_task(async function contextMenu() {
+  Services.telemetry.clearEvents();
+
   // Add a test action.
   let action = PageActions.addAction(new PageActions.Action({
     id: "test-contextMenu",
     title: "Test contextMenu",
     pinnedToUrlbar: true,
   }));
 
   // Open the panel and then open the context menu on the action's item.
@@ -1440,16 +1442,30 @@ add_task(async function contextMenu() {
   EventUtils.synthesizeMouseAtCenter(menuItems[2], {});
   values = await Promise.all([aboutAddonsPromise, contextMenuPromise]);
   aboutAddonsTab = values[0];
   BrowserTestUtils.removeTab(aboutAddonsTab);
 
   // Done, clean up.
   action.remove();
 
+  // Check the telemetry was collected properly.
+  let snapshot = Services.telemetry.snapshotEvents(
+    Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, true);
+  ok(snapshot.parent && snapshot.parent.length > 0,
+     "Got parent telemetry events in the snapshot");
+  let relatedEvents = snapshot.parent
+    .filter(([timestamp, category, method]) =>
+      category == "addonsManager" && method == "action")
+    .map(relatedEvent => relatedEvent.slice(3, 6));
+  Assert.deepEqual(relatedEvents, [
+    ["pageAction", null, {action: "manage"}],
+    ["pageAction", null, {action: "manage"}],
+  ]);
+
   // urlbar tests that run after this one can break if the mouse is left over
   // the area where the urlbar popup appears, which seems to happen due to the
   // above synthesized mouse events.  Move it over the urlbar.
   EventUtils.synthesizeMouseAtCenter(gURLBar, { type: "mousemove" });
   gURLBar.focus();
 });