Bug 1536454 - Part 5 - Add tests for permission prompt event telemetry. r=Ehsan a=pascalc
authorJohann Hofmann <jhofmann@mozilla.com>
Thu, 18 Apr 2019 13:43:38 +0000
changeset 526335 156dcfca7e317c51910a875ba8838e5d0ed5f5de
parent 526334 759d4a9a89701264867256be209a42a64e9f5f5f
child 526336 9cf0e5a84b2c175c4c866b31c3ee0c7fd564b53f
push id2032
push userffxbld-merge
push dateMon, 13 May 2019 09:36:57 +0000
treeherdermozilla-release@455c1065dcbe [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersEhsan, pascalc
bugs1536454
milestone67.0
Bug 1536454 - Part 5 - Add tests for permission prompt event telemetry. r=Ehsan a=pascalc Differential Revision: https://phabricator.services.mozilla.com/D27907
browser/base/content/test/permissions/browser.ini
browser/base/content/test/permissions/browser_permissions_event_telemetry.js
testing/mochitest/BrowserTestUtils/ContentTaskUtils.jsm
--- a/browser/base/content/test/permissions/browser.ini
+++ b/browser/base/content/test/permissions/browser.ini
@@ -1,16 +1,17 @@
 [DEFAULT]
 support-files=
   head.js
   permissions.html
 
 [browser_canvas_fingerprinting_resistance.js]
 skip-if = debug || os == "linux" && asan # Bug 1522069
 [browser_permissions.js]
+[browser_permissions_event_telemetry.js]
 [browser_permissions_postPrompt.js]
 support-files=
   dummy.js
 [browser_permissions_handling_user_input.js]
 support-files=
   dummy.js
 [browser_reservedkey.js]
 [browser_temporary_permissions.js]
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/permissions/browser_permissions_event_telemetry.js
@@ -0,0 +1,125 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const ORIGIN = "https://example.com";
+const PERMISSIONS_PAGE = getRootDirectory(gTestPath).replace("chrome://mochitests/content", ORIGIN) + "permissions.html";
+
+async function showPermissionPrompt(browser) {
+  let popupshown = BrowserTestUtils.waitForEvent(PopupNotifications.panel, "popupshown");
+
+  await ContentTask.spawn(browser, null, function() {
+    E10SUtils.wrapHandlingUserInput(content, true, () => {
+      // We need to synthesize the click instead of calling .click(),
+      // otherwise the document will not correctly register the user gesture.
+      let EventUtils = ContentTaskUtils.getEventUtils(content);
+      let notificationButton = content.document.getElementById("desktop-notification");
+      EventUtils.synthesizeMouseAtCenter(notificationButton, {isSynthesized: false}, content);
+    });
+  });
+
+  await popupshown;
+
+  ok(true, "Notification permission prompt was shown");
+}
+
+function checkEventTelemetry(method) {
+  let events = Services.telemetry.snapshotEvents(Ci.nsITelemetry.DATASET_ALL_CHANNELS, true).parent;
+  events = events.filter(
+    e => e[1] == "security.ui.permissionprompt" && e[2] == method && e[3] == "notifications");
+  is(events.length, 1, "recorded telemetry for showing the prompt");
+  ok(typeof events[0][4] == "string", "recorded a hashed and salted variant of the domain");
+  is(events[0][4].length * 4, 256, "hash is a 256 bit string");
+  ok(!events[0][4].includes("example.com"), "we're not including the domain by accident");
+
+  // We assume that even the slowest infra machines are able to show
+  // a permission prompt within five minutes.
+  const FIVE_MINUTES = 1000 * 60 * 5;
+
+  let timeOnPage = Number(events[0][5].timeOnPage);
+  let lastInteraction = Number(events[0][5].lastInteraction);
+  ok(timeOnPage > 0 && timeOnPage < FIVE_MINUTES, `Has recorded time on page (${timeOnPage})`);
+  is(events[0][5].hasUserInput, "true", "Has recorded user input");
+  is(events[0][5].allPermsDenied, "3", "Has recorded total denied permissions");
+  is(events[0][5].allPermsGranted, method == "accept" ? "3" : "2", "Has recorded total granted permissions");
+  is(events[0][5].thisPermDenied, "0", "Has recorded denied notification permissions");
+  is(events[0][5].thisPermGranted, method == "accept" ? "2" : "1",
+    "Has recorded granted notification permissions");
+  is(events[0][5].docHasUserInput, "true", "Has recorded user input on document");
+  ok(lastInteraction > (Date.now() - FIVE_MINUTES) &&
+     lastInteraction < Date.now(), `Has recorded last user input time (${lastInteraction})`);
+}
+
+add_task(async function setup() {
+  let oldCanRecord = Services.telemetry.canRecordExtended;
+  Services.telemetry.canRecordExtended = true;
+
+  Services.prefs.setBoolPref("permissions.eventTelemetry.enabled", true);
+
+  // Add some example permissions.
+  let uri = Services.io.newURI(PERMISSIONS_PAGE);
+  let uri2 = Services.io.newURI("https://example.org");
+  let uri3 = Services.io.newURI("http://sub.example.org");
+  Services.perms.add(uri, "geo", Services.perms.ALLOW_ACTION);
+  Services.perms.add(uri3, "desktop-notification", Services.perms.ALLOW_ACTION);
+  Services.perms.add(uri2, "microphone", Services.perms.DENY_ACTION);
+  Services.perms.add(uri, "camera", Services.perms.DENY_ACTION);
+  Services.perms.add(uri2, "geo", Services.perms.DENY_ACTION);
+
+  registerCleanupFunction(() => {
+    Services.perms.removeAll();
+    Services.prefs.clearUserPref("permissions.eventTelemetry.enabled");
+    Services.telemetry.canRecordExtended = oldCanRecord;
+  });
+
+  Services.telemetry.clearEvents();
+});
+
+add_task(async function testAccept() {
+  await BrowserTestUtils.withNewTab(PERMISSIONS_PAGE, async function(browser) {
+    await showPermissionPrompt(browser);
+
+    checkEventTelemetry("show");
+
+    let notification = PopupNotifications.panel.firstElementChild;
+    EventUtils.synthesizeMouseAtCenter(notification.button, {});
+
+    checkEventTelemetry("accept");
+
+    Services.telemetry.clearEvents();
+    Services.perms.remove(Services.io.newURI(PERMISSIONS_PAGE), "desktop-notification");
+  });
+});
+
+add_task(async function testDeny() {
+  await BrowserTestUtils.withNewTab(PERMISSIONS_PAGE, async function(browser) {
+    await showPermissionPrompt(browser);
+
+    checkEventTelemetry("show");
+
+    let notification = PopupNotifications.panel.firstElementChild;
+    EventUtils.synthesizeMouseAtCenter(notification.secondaryButton, {});
+
+    checkEventTelemetry("deny");
+
+    Services.telemetry.clearEvents();
+    Services.perms.remove(Services.io.newURI(PERMISSIONS_PAGE), "desktop-notification");
+  });
+});
+
+add_task(async function testLeave() {
+  let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, PERMISSIONS_PAGE);
+  await showPermissionPrompt(tab.linkedBrowser);
+
+  checkEventTelemetry("show");
+
+  let tabClosed = BrowserTestUtils.waitForTabClosing(tab);
+  await BrowserTestUtils.removeTab(tab);
+  await tabClosed;
+
+  checkEventTelemetry("leave");
+
+  Services.telemetry.clearEvents();
+  Services.perms.remove(Services.io.newURI(PERMISSIONS_PAGE), "desktop-notification");
+});
--- a/testing/mochitest/BrowserTestUtils/ContentTaskUtils.jsm
+++ b/testing/mochitest/BrowserTestUtils/ContentTaskUtils.jsm
@@ -11,16 +11,17 @@
  */
 
 "use strict";
 
 var EXPORTED_SYMBOLS = [
   "ContentTaskUtils",
 ];
 
+const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
 const {clearInterval, setInterval, setTimeout} = ChromeUtils.import("resource://gre/modules/Timer.jsm");
 
 var ContentTaskUtils = {
   /**
    * Checks if a DOM element is hidden.
    *
    * @param {Element} element
    *        The element which is to be checked.
@@ -158,9 +159,41 @@ var ContentTaskUtils = {
           } catch (ex2) {
             // Maybe the provided object does not support removeEventListener.
           }
           setTimeout(() => reject(ex), 0);
         }
       }, capture, wantsUntrusted);
     });
   },
+
+  /**
+   * Gets an instance of the `EventUtils` helper module for usage in
+   * content tasks. See https://searchfox.org/mozilla-central/source/testing/mochitest/tests/SimpleTest/EventUtils.js
+   *
+   * @param content
+   *        The `content` global object from your content task.
+   *
+   * @returns an EventUtils instance.
+   */
+  getEventUtils(content) {
+    if (content._EventUtils) {
+      return content._EventUtils;
+    }
+
+    let EventUtils = content._EventUtils = {};
+
+    EventUtils.window = {};
+    EventUtils.parent = EventUtils.window;
+    /* eslint-disable camelcase */
+    EventUtils._EU_Ci = Ci;
+    EventUtils._EU_Cc = Cc;
+    /* eslint-enable camelcase */
+    // EventUtils' `sendChar` function relies on the navigator to synthetize events.
+    EventUtils.navigator = content.navigator;
+    EventUtils.KeyboardEvent = content.KeyboardEvent;
+
+    Services.scriptloader.loadSubScript(
+      "chrome://mochikit/content/tests/SimpleTest/EventUtils.js", EventUtils);
+
+    return EventUtils;
+  },
 };