Bug 1536454 - Part 5 - Add tests for permission prompt event telemetry. r=Ehsan
authorJohann Hofmann <jhofmann@mozilla.com>
Thu, 18 Apr 2019 13:43:38 +0000
changeset 470081 4b317b418c1410ce46cacd6f52bbdf2b049f3387
parent 470080 19b40bd2e67f28b2c8b11dc0671cd7337b408911
child 470082 cec2387cbef5532ab7c52fc1d49bd6276bd4eeaf
push id112843
push useraiakab@mozilla.com
push dateFri, 19 Apr 2019 09:50:22 +0000
treeherdermozilla-inbound@c06f27cbfe40 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersEhsan
bugs1536454
milestone68.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 1536454 - Part 5 - Add tests for permission prompt event telemetry. r=Ehsan 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;
+  },
 };