Bug 1334615 - Add a probe to determine whether the keyboard or the mouse was used to select an action. r=bsmedberg,mak
authorDrew Willcoxon <adw@mozilla.com>
Fri, 14 Apr 2017 17:27:51 -0700
changeset 563303 dd703291b11ae7f2d469c0dfd694900e8e03c20d
parent 563302 b29865f8d36bbe4526bc7674914a2b1a9dd20ead
child 563304 26a0e8b9f27895eb55e21f53e3f086bbf809472a
push id54258
push usercpeterson@mozilla.com
push dateSun, 16 Apr 2017 05:52:14 +0000
reviewersbsmedberg, mak
bugs1334615
milestone55.0a1
Bug 1334615 - Add a probe to determine whether the keyboard or the mouse was used to select an action. r=bsmedberg,mak MozReview-Commit-ID: G3K2l6jnCR8
browser/base/content/urlbarBindings.xml
browser/components/search/content/search.xml
browser/modules/BrowserUsageTelemetry.jsm
browser/modules/test/browser/browser_UsageTelemetry_searchbar.js
browser/modules/test/browser/browser_UsageTelemetry_urlbar.js
toolkit/components/telemetry/Histograms.json
--- a/browser/base/content/urlbarBindings.xml
+++ b/browser/base/content/urlbarBindings.xml
@@ -372,16 +372,18 @@ file, You can obtain one at http://mozil
         <parameter name="openUILinkParams"/>
         <body><![CDATA[
           let isMouseEvent = event instanceof MouseEvent;
           if (isMouseEvent && event.button == 2) {
             // Do nothing for right clicks.
             return;
           }
 
+          BrowserUsageTelemetry.recordUrlbarSelectedResultMethod(event);
+
           // Determine whether to use the selected one-off search button.  In
           // one-off search buttons parlance, "selected" means that the button
           // has been navigated to via the keyboard.  So we want to use it if
           // the triggering event is not a mouse click -- i.e., it's a Return
           // key -- or if the one-off was mouse-clicked.
           let selectedOneOff = this.popup.oneOffSearchButtons.selectedButton;
           if (selectedOneOff &&
               isMouseEvent &&
--- a/browser/components/search/content/search.xml
+++ b/browser/components/search/content/search.xml
@@ -373,16 +373,21 @@
         <parameter name="aParams"/>
         <body><![CDATA[
           var textBox = this._textbox;
           var textValue = textBox.value;
 
           let selection = this.telemetrySearchDetails;
           let oneOffRecorded = false;
 
+          BrowserUsageTelemetry.recordSearchbarSelectedResultMethod(
+            aEvent,
+            selection ? selection.index : -1
+          );
+
           if (!selection || (selection.index == -1)) {
             oneOffRecorded = this.textbox.popup.oneOffButtons
                                  .maybeRecordTelemetry(aEvent, aWhere, aParams);
             if (!oneOffRecorded) {
               let source = "unknown";
               let type = "unknown";
               let target = aEvent.originalTarget;
               if (aEvent instanceof KeyboardEvent) {
@@ -966,16 +971,21 @@
           if (aEvent.button == 0 && !aEvent.shiftKey && !aEvent.ctrlKey &&
               !aEvent.altKey && !aEvent.metaKey) {
             controller.handleEnter(true, aEvent);
             return;
           }
 
           // Check for middle-click or modified clicks on the search bar
           if (popupForSearchBar) {
+            BrowserUsageTelemetry.recordSearchbarSelectedResultMethod(
+              aEvent,
+              this.selectedIndex
+            );
+
             // Handle search bar popup clicks
             var search = controller.getValueAt(this.selectedIndex);
 
             // open the search results according to the clicking subtlety
             var where = whereToOpenLink(aEvent, false, true);
             let params = {};
 
             // But open ctrl/cmd clicks on autocomplete items in a new background tab.
--- a/browser/modules/BrowserUsageTelemetry.jsm
+++ b/browser/modules/BrowserUsageTelemetry.jsm
@@ -1,16 +1,20 @@
 /* -*- js-indent-level: 2; indent-tabs-mode: nil -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
-this.EXPORTED_SYMBOLS = ["BrowserUsageTelemetry", "URLBAR_SELECTED_RESULT_TYPES"];
+this.EXPORTED_SYMBOLS = [
+  "BrowserUsageTelemetry",
+  "URLBAR_SELECTED_RESULT_TYPES",
+  "URLBAR_SELECTED_RESULT_METHODS",
+ ];
 
 const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
 
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
                                   "resource://gre/modules/PrivateBrowsingUtils.jsm");
@@ -63,16 +67,29 @@ const URLBAR_SELECTED_RESULT_TYPES = {
   switchtab: 6,
   tag: 7,
   visiturl: 8,
   remotetab: 9,
   extension: 10,
   "preloaded-top-site": 11,
 };
 
+/**
+ * This maps the categories used by the FX_URLBAR_SELECTED_RESULT_METHOD and
+ * FX_SEARCHBAR_SELECTED_RESULT_METHOD histograms to their indexes in the
+ * `labels` array.  This only needs to be used by tests that need to map from
+ * category names to indexes in histogram snapshots.  Actual app code can use
+ * these category names directly when they add to a histogram.
+ */
+const URLBAR_SELECTED_RESULT_METHODS = {
+  enter: 0,
+  enterSelection: 1,
+  click: 2,
+};
+
 function getOpenTabsAndWinsCounts() {
   let tabCount = 0;
   let winCount = 0;
 
   let browserEnum = Services.wm.getEnumerator("navigator:browser");
   while (browserEnum.hasMoreElements()) {
     let win = browserEnum.getNext();
     winCount++;
@@ -203,16 +220,20 @@ let URICountListener = {
     this._domainSet.clear();
   },
 
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener,
                                          Ci.nsISupportsWeakReference]),
 };
 
 let urlbarListener = {
+
+  // This is needed for recordUrlbarSelectedResultMethod().
+  selectedIndex: -1,
+
   init() {
     Services.obs.addObserver(this, AUTOCOMPLETE_ENTER_TEXT_TOPIC, true);
   },
 
   uninit() {
     Services.obs.removeObserver(this, AUTOCOMPLETE_ENTER_TEXT_TOPIC);
   },
 
@@ -226,22 +247,32 @@ let urlbarListener = {
 
   /**
    * Used to log telemetry when the user enters text in the urlbar.
    *
    * @param {nsIAutoCompleteInput} input  The autocomplete element where the
    *                                      text was entered.
    */
   _handleURLBarTelemetry(input) {
-    if (!input ||
-        input.id != "urlbar" ||
-        input.inPrivateContext ||
-        input.popup.selectedIndex < 0) {
+    if (!input || input.id != "urlbar") {
+      return;
+    }
+    if (input.inPrivateContext || input.popup.selectedIndex < 0) {
+      this.selectedIndex = -1;
       return;
     }
+
+    // Except for the history popup, the urlbar always has a selection.  The
+    // first result at index 0 is the "heuristic" result that indicates what
+    // will happen when you press the Enter key.  Treat it as no selection.
+    this.selectedIndex =
+      input.popup.selectedIndex > 0 || !input.popup._isFirstResultHeuristic ?
+      input.popup.selectedIndex :
+      -1;
+
     let controller =
       input.popup.view.QueryInterface(Ci.nsIAutoCompleteController);
     let idx = input.popup.selectedIndex;
     let value = controller.getValueAt(idx);
     let action = input._parseActionUrl(value);
     let actionType;
     if (action) {
       actionType =
@@ -459,16 +490,67 @@ let BrowserUsageTelemetry = {
       return;
     }
 
     // The search signal was generated by typing something and pressing enter.
     this._recordSearch(engine, sourceName, "enter");
   },
 
   /**
+   * Records the method by which the user selected a urlbar result.
+   *
+   * @param {nsIDOMEvent} event
+   *        The event that triggered the selection.
+   */
+  recordUrlbarSelectedResultMethod(event) {
+    // The reason this method relies on urlbarListener instead of having the
+    // caller pass in an index is that by the time the urlbar handles a
+    // selection, the selection in its popup has been cleared, so it's not easy
+    // to tell which popup index was selected.  Fortunately this file already
+    // has urlbarListener, which gets notified of selections in the urlbar
+    // before the popup selection is cleared, so just use that.
+    this._recordUrlOrSearchbarSelectedResultMethod(
+      event, urlbarListener.selectedIndex,
+      "FX_URLBAR_SELECTED_RESULT_METHOD"
+    );
+  },
+
+  /**
+   * Records the method by which the user selected a searchbar result.
+   *
+   * @param {nsIDOMEvent} event
+   *        The event that triggered the selection.
+   * @param {number} highlightedIndex
+   *        The index that the user chose in the popup, or -1 if there wasn't a
+   *        selection.
+   */
+  recordSearchbarSelectedResultMethod(event, highlightedIndex) {
+    this._recordUrlOrSearchbarSelectedResultMethod(
+      event, highlightedIndex,
+      "FX_SEARCHBAR_SELECTED_RESULT_METHOD"
+    );
+  },
+
+  _recordUrlOrSearchbarSelectedResultMethod(event, highlightedIndex, histogramID) {
+    let histogram = Services.telemetry.getHistogramById(histogramID);
+    // command events are from the one-off context menu.  Treat them as clicks.
+    let isClick = event instanceof Ci.nsIDOMMouseEvent ||
+                  (event && event.type == "command");
+    let category;
+    if (isClick) {
+      category = "click";
+    } else if (highlightedIndex >= 0) {
+      category = "enterSelection";
+    } else {
+      category = "enter";
+    }
+    histogram.add(category);
+  },
+
+  /**
    * This gets called shortly after the SessionStore has finished restoring
    * windows and tabs. It counts the open tabs and adds listeners to all the
    * windows.
    */
   _setupAfterRestore() {
     // Make sure to catch new chrome windows and subsession splits.
     Services.obs.addObserver(this, DOMWINDOW_OPENED_TOPIC, false);
     Services.obs.addObserver(this, TELEMETRY_SUBSESSIONSPLIT_TOPIC, false);
--- a/browser/modules/test/browser/browser_UsageTelemetry_searchbar.js
+++ b/browser/modules/test/browser/browser_UsageTelemetry_searchbar.js
@@ -1,12 +1,27 @@
 "use strict";
 
 const SCALAR_SEARCHBAR = "browser.engagement.navigation.searchbar";
 
+XPCOMUtils.defineLazyModuleGetter(this, "URLBAR_SELECTED_RESULT_METHODS",
+                                  "resource:///modules/BrowserUsageTelemetry.jsm");
+
+function checkHistogramResults(resultIndexes, expected, histogram) {
+  for (let i = 0; i < resultIndexes.counts.length; i++) {
+    if (i == expected) {
+      Assert.equal(resultIndexes.counts[i], 1,
+        `expected counts should match for ${histogram} index ${i}`);
+    } else {
+      Assert.equal(resultIndexes.counts[i], 0,
+        `unexpected counts should be zero for ${histogram} index ${i}`);
+    }
+  }
+}
+
 let searchInSearchbar = Task.async(function* (inputText) {
   let win = window;
   yield new Promise(r => waitForFocus(r, win));
   let sb = BrowserSearch.searchBar;
   // Write the search query in the searchbar.
   sb.focus();
   sb.value = inputText;
   sb.textbox.controller.startSearch(inputText);
@@ -62,35 +77,42 @@ add_task(function* setup() {
   let engineDefault = Services.search.getEngineByName("MozSearch");
   let originalEngine = Services.search.currentEngine;
   Services.search.currentEngine = engineDefault;
 
   // Move the second engine at the beginning of the one-off list.
   let engineOneOff = Services.search.getEngineByName("MozSearch2");
   Services.search.moveEngine(engineOneOff, 0);
 
+  // Enable local telemetry recording for the duration of the tests.
+  let oldCanRecord = Services.telemetry.canRecordExtended;
+  Services.telemetry.canRecordExtended = true;
+
   // Enable Extended Telemetry.
   yield SpecialPowers.pushPrefEnv({"set": [["toolkit.telemetry.enabled", true]]});
 
   // Enable event recording for the events tested here.
   Services.telemetry.setEventRecordingEnabled("navigation", true);
 
   // Make sure to restore the engine once we're done.
   registerCleanupFunction(function* () {
+    Services.telemetry.canRecordExtended = oldCanRecord;
     Services.search.currentEngine = originalEngine;
     Services.search.removeEngine(engineDefault);
     Services.search.removeEngine(engineOneOff);
     Services.telemetry.setEventRecordingEnabled("navigation", false);
   });
 });
 
 add_task(function* test_plainQuery() {
   // Let's reset the counts.
   Services.telemetry.clearScalars();
   Services.telemetry.clearEvents();
+  let resultMethodHist = Services.telemetry.getHistogramById("FX_SEARCHBAR_SELECTED_RESULT_METHOD");
+  resultMethodHist.clear();
   let search_hist = getSearchCountsHistogram();
 
   let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:blank");
 
   info("Simulate entering a simple search.");
   let p = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
   yield searchInSearchbar("simple query");
   EventUtils.sendKey("return");
@@ -105,23 +127,33 @@ add_task(function* test_plainQuery() {
   // Make sure SEARCH_COUNTS contains identical values.
   checkKeyedHistogram(search_hist, "other-MozSearch.searchbar", 1);
 
   // Also check events.
   let events = Services.telemetry.snapshotBuiltinEvents(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, false);
   events = (events.default || []).filter(e => e[1] == "navigation" && e[2] == "search");
   checkEvents(events, [["navigation", "search", "searchbar", "enter", {engine: "other-MozSearch"}]]);
 
+  // Check the histograms as well.
+  let resultMethods = resultMethodHist.snapshot();
+  checkHistogramResults(resultMethods,
+    URLBAR_SELECTED_RESULT_METHODS.enter,
+    "FX_SEARCHBAR_SELECTED_RESULT_METHOD");
+
   yield BrowserTestUtils.removeTab(tab);
 });
 
-add_task(function* test_oneOff() {
+// Performs a search using the first result, a one-off button, and the Return
+// (Enter) key.
+add_task(function* test_oneOff_enter() {
   // Let's reset the counts.
   Services.telemetry.clearScalars();
   Services.telemetry.clearEvents();
+  let resultMethodHist = Services.telemetry.getHistogramById("FX_SEARCHBAR_SELECTED_RESULT_METHOD");
+  resultMethodHist.clear();
   let search_hist = getSearchCountsHistogram();
 
   let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:blank");
 
   info("Perform a one-off search using the first engine.");
   let p = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
   yield searchInSearchbar("query");
 
@@ -139,23 +171,102 @@ add_task(function* test_oneOff() {
   // Make sure SEARCH_COUNTS contains identical values.
   checkKeyedHistogram(search_hist, "other-MozSearch2.searchbar", 1);
 
   // Also check events.
   let events = Services.telemetry.snapshotBuiltinEvents(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, false);
   events = (events.default || []).filter(e => e[1] == "navigation" && e[2] == "search");
   checkEvents(events, [["navigation", "search", "searchbar", "oneoff", {engine: "other-MozSearch2"}]]);
 
+  // Check the histograms as well.
+  let resultMethods = resultMethodHist.snapshot();
+  checkHistogramResults(resultMethods,
+    URLBAR_SELECTED_RESULT_METHODS.enter,
+    "FX_SEARCHBAR_SELECTED_RESULT_METHOD");
+
   yield BrowserTestUtils.removeTab(tab);
 });
 
-add_task(function* test_suggestion() {
+// Performs a search using the second result, a one-off button, and the Return
+// (Enter) key.  This only tests the FX_SEARCHBAR_SELECTED_RESULT_METHOD
+// histogram since test_oneOff_enter covers everything else.
+add_task(function* test_oneOff_enterSelection() {
+  // Let's reset the counts.
+  Services.telemetry.clearScalars();
+  let resultMethodHist = Services.telemetry.getHistogramById("FX_SEARCHBAR_SELECTED_RESULT_METHOD");
+  resultMethodHist.clear();
+
+  // Create an engine to generate search suggestions and add it as default
+  // for this test.
+  const url = getRootDirectory(gTestPath) + "usageTelemetrySearchSuggestions.xml";
+  let suggestionEngine = yield new Promise((resolve, reject) => {
+    Services.search.addEngine(url, null, "", false, {
+      onSuccess(engine) { resolve(engine) },
+      onError() { reject() }
+    });
+  });
+
+  let previousEngine = Services.search.currentEngine;
+  Services.search.currentEngine = suggestionEngine;
+
+  let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:blank");
+
+  info("Type a query. Suggestions should be generated by the test engine.");
+  let p = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+  yield searchInSearchbar("query");
+
+  info("Select the second result, press Alt+Down to take us to the first one-off engine.");
+  EventUtils.synthesizeKey("VK_DOWN", {});
+  EventUtils.synthesizeKey("VK_DOWN", { altKey: true });
+  EventUtils.sendKey("return");
+  yield p;
+
+  let resultMethods = resultMethodHist.snapshot();
+  checkHistogramResults(resultMethods,
+    URLBAR_SELECTED_RESULT_METHODS.enterSelection,
+    "FX_SEARCHBAR_SELECTED_RESULT_METHOD");
+
+  Services.search.currentEngine = previousEngine;
+  Services.search.removeEngine(suggestionEngine);
+  yield BrowserTestUtils.removeTab(tab);
+});
+
+// Performs a search using a click on a one-off button.  This only tests the
+// FX_SEARCHBAR_SELECTED_RESULT_METHOD histogram since test_oneOff_enter covers
+// everything else.
+add_task(function* test_oneOff_click() {
+  // Let's reset the counts.
+  Services.telemetry.clearScalars();
+  let resultMethodHist = Services.telemetry.getHistogramById("FX_SEARCHBAR_SELECTED_RESULT_METHOD");
+  resultMethodHist.clear();
+
+  let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:blank");
+
+  info("Type a query.");
+  let p = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+  yield searchInSearchbar("query");
+  info("Click the first one-off button.");
+  BrowserSearch.searchBar.textbox.popup.oneOffButtons.getSelectableButtons(false)[0].click();
+  yield p;
+
+  let resultMethods = resultMethodHist.snapshot();
+  checkHistogramResults(resultMethods,
+    URLBAR_SELECTED_RESULT_METHODS.click,
+    "FX_SEARCHBAR_SELECTED_RESULT_METHOD");
+
+  yield BrowserTestUtils.removeTab(tab);
+});
+
+// Clicks the first suggestion offered by the test search engine.
+add_task(function* test_suggestion_click() {
   // Let's reset the counts.
   Services.telemetry.clearScalars();
   Services.telemetry.clearEvents();
+  let resultMethodHist = Services.telemetry.getHistogramById("FX_SEARCHBAR_SELECTED_RESULT_METHOD");
+  resultMethodHist.clear();
   let search_hist = getSearchCountsHistogram();
 
   // Create an engine to generate search suggestions and add it as default
   // for this test.
   const url = getRootDirectory(gTestPath) + "usageTelemetrySearchSuggestions.xml";
   let suggestionEngine = yield new Promise((resolve, reject) => {
     Services.search.addEngine(url, null, "", false, {
       onSuccess(engine) { resolve(engine) },
@@ -185,12 +296,61 @@ add_task(function* test_suggestion() {
   let searchEngineId = "other-" + suggestionEngine.name;
   checkKeyedHistogram(search_hist, searchEngineId + ".searchbar", 1);
 
   // Also check events.
   let events = Services.telemetry.snapshotBuiltinEvents(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, false);
   events = (events.default || []).filter(e => e[1] == "navigation" && e[2] == "search");
   checkEvents(events, [["navigation", "search", "searchbar", "suggestion", {engine: searchEngineId}]]);
 
+  // Check the histograms as well.
+  let resultMethods = resultMethodHist.snapshot();
+  checkHistogramResults(resultMethods,
+    URLBAR_SELECTED_RESULT_METHODS.click,
+    "FX_SEARCHBAR_SELECTED_RESULT_METHOD");
+
   Services.search.currentEngine = previousEngine;
   Services.search.removeEngine(suggestionEngine);
   yield BrowserTestUtils.removeTab(tab);
 });
+
+// Selects and presses the Return (Enter) key on the first suggestion offered by
+// the test search engine.  This only tests the
+// FX_SEARCHBAR_SELECTED_RESULT_METHOD histogram since test_suggestion_click
+// covers everything else.
+add_task(function* test_suggestion_enterSelection() {
+  // Let's reset the counts.
+  Services.telemetry.clearScalars();
+  let resultMethodHist = Services.telemetry.getHistogramById("FX_SEARCHBAR_SELECTED_RESULT_METHOD");
+  resultMethodHist.clear();
+
+  // Create an engine to generate search suggestions and add it as default
+  // for this test.
+  const url = getRootDirectory(gTestPath) + "usageTelemetrySearchSuggestions.xml";
+  let suggestionEngine = yield new Promise((resolve, reject) => {
+    Services.search.addEngine(url, null, "", false, {
+      onSuccess(engine) { resolve(engine) },
+      onError() { reject() }
+    });
+  });
+
+  let previousEngine = Services.search.currentEngine;
+  Services.search.currentEngine = suggestionEngine;
+
+  let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:blank");
+
+  info("Type a query. Suggestions should be generated by the test engine.");
+  let p = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+  yield searchInSearchbar("query");
+  info("Select the second result and press Return.");
+  EventUtils.synthesizeKey("VK_DOWN", {});
+  EventUtils.sendKey("return");
+  yield p;
+
+  let resultMethods = resultMethodHist.snapshot();
+  checkHistogramResults(resultMethods,
+    URLBAR_SELECTED_RESULT_METHODS.enterSelection,
+    "FX_SEARCHBAR_SELECTED_RESULT_METHOD");
+
+  Services.search.currentEngine = previousEngine;
+  Services.search.removeEngine(suggestionEngine);
+  yield BrowserTestUtils.removeTab(tab);
+});
--- a/browser/modules/test/browser/browser_UsageTelemetry_urlbar.js
+++ b/browser/modules/test/browser/browser_UsageTelemetry_urlbar.js
@@ -6,16 +6,19 @@ const SCALAR_URLBAR = "browser.engagemen
 const SUGGEST_URLBAR_PREF = "browser.urlbar.suggest.searches";
 // The name of the search engine used to generate suggestions.
 const SUGGESTION_ENGINE_NAME = "browser_UsageTelemetry usageTelemetrySearchSuggestions.xml";
 const ONEOFF_URLBAR_PREF = "browser.urlbar.oneOffSearches";
 
 XPCOMUtils.defineLazyModuleGetter(this, "URLBAR_SELECTED_RESULT_TYPES",
                                   "resource:///modules/BrowserUsageTelemetry.jsm");
 
+XPCOMUtils.defineLazyModuleGetter(this, "URLBAR_SELECTED_RESULT_METHODS",
+                                  "resource:///modules/BrowserUsageTelemetry.jsm");
+
 function checkHistogramResults(resultIndexes, expected, histogram) {
   for (let i = 0; i < resultIndexes.counts.length; i++) {
     if (i == expected) {
       Assert.equal(resultIndexes.counts[i], 1,
         `expected counts should match for ${histogram} index ${i}`);
     } else {
       Assert.equal(resultIndexes.counts[i], 0,
         `unexpected counts should be zero for ${histogram} index ${i}`);
@@ -79,16 +82,20 @@ add_task(function* setup() {
 
   // Enable local telemetry recording for the duration of the tests.
   let oldCanRecord = Services.telemetry.canRecordExtended;
   Services.telemetry.canRecordExtended = true;
 
   // Enable event recording for the events tested here.
   Services.telemetry.setEventRecordingEnabled("navigation", true);
 
+  // Clear history so that history added by previous tests doesn't mess up this
+  // test when it selects results in the urlbar.
+  yield PlacesTestUtils.clearHistory();
+
   // Make sure to restore the engine once we're done.
   registerCleanupFunction(function* () {
     Services.telemetry.canRecordExtended = oldCanRecord;
     Services.search.currentEngine = originalEngine;
     Services.search.removeEngine(engine);
     Services.prefs.clearUserPref(SUGGEST_URLBAR_PREF);
     Services.prefs.clearUserPref(ONEOFF_URLBAR_PREF);
     yield PlacesTestUtils.clearHistory();
@@ -97,20 +104,22 @@ add_task(function* setup() {
 });
 
 add_task(function* test_simpleQuery() {
   // Let's reset the counts.
   Services.telemetry.clearScalars();
   Services.telemetry.clearEvents();
   let resultIndexHist = Services.telemetry.getHistogramById("FX_URLBAR_SELECTED_RESULT_INDEX");
   let resultTypeHist = Services.telemetry.getHistogramById("FX_URLBAR_SELECTED_RESULT_TYPE");
+  let resultMethodHist = Services.telemetry.getHistogramById("FX_URLBAR_SELECTED_RESULT_METHOD");
   let resultIndexByTypeHist = Services.telemetry.getKeyedHistogramById("FX_URLBAR_SELECTED_RESULT_INDEX_BY_TYPE");
   resultIndexByTypeHist.clear();
   resultIndexHist.clear();
   resultTypeHist.clear();
+  resultMethodHist.clear();
 
   let search_hist = getSearchCountsHistogram();
 
   let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:blank");
 
   info("Simulate entering a simple search.");
   let p = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
   yield searchInAwesomebar("simple query");
@@ -140,29 +149,36 @@ add_task(function* test_simpleQuery() {
     URLBAR_SELECTED_RESULT_TYPES.searchengine,
     "FX_URLBAR_SELECTED_RESULT_TYPE");
 
   let resultIndexByType = resultIndexByTypeHist.snapshot("searchengine");
   checkHistogramResults(resultIndexByType,
     0,
     "FX_URLBAR_SELECTED_RESULT_INDEX_BY_TYPE");
 
+  let resultMethods = resultMethodHist.snapshot();
+  checkHistogramResults(resultMethods,
+    URLBAR_SELECTED_RESULT_METHODS.enter,
+    "FX_URLBAR_SELECTED_RESULT_METHOD");
+
   yield BrowserTestUtils.removeTab(tab);
 });
 
 add_task(function* test_searchAlias() {
   // Let's reset the counts.
   Services.telemetry.clearScalars();
   Services.telemetry.clearEvents();
   let resultIndexHist = Services.telemetry.getHistogramById("FX_URLBAR_SELECTED_RESULT_INDEX");
   let resultTypeHist = Services.telemetry.getHistogramById("FX_URLBAR_SELECTED_RESULT_TYPE");
   let resultIndexByTypeHist = Services.telemetry.getKeyedHistogramById("FX_URLBAR_SELECTED_RESULT_INDEX_BY_TYPE");
+  let resultMethodHist = Services.telemetry.getHistogramById("FX_URLBAR_SELECTED_RESULT_METHOD");
   resultIndexByTypeHist.clear();
   resultIndexHist.clear();
   resultTypeHist.clear();
+  resultMethodHist.clear();
 
   let search_hist = getSearchCountsHistogram();
 
   let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:blank");
 
   info("Search using a search alias.");
   let p = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
   yield searchInAwesomebar("mozalias query");
@@ -192,29 +208,38 @@ add_task(function* test_searchAlias() {
     URLBAR_SELECTED_RESULT_TYPES.searchengine,
     "FX_URLBAR_SELECTED_RESULT_TYPE");
 
   let resultIndexByType = resultIndexByTypeHist.snapshot("searchengine");
   checkHistogramResults(resultIndexByType,
     0,
     "FX_URLBAR_SELECTED_RESULT_INDEX_BY_TYPE");
 
+  let resultMethods = resultMethodHist.snapshot();
+  checkHistogramResults(resultMethods,
+    URLBAR_SELECTED_RESULT_METHODS.enter,
+    "FX_URLBAR_SELECTED_RESULT_METHOD");
+
   yield BrowserTestUtils.removeTab(tab);
 });
 
-add_task(function* test_oneOff() {
+// Performs a search using the first result, a one-off button, and the Return
+// (Enter) key.
+add_task(function* test_oneOff_enter() {
   // Let's reset the counts.
   Services.telemetry.clearScalars();
   Services.telemetry.clearEvents();
   let resultIndexHist = Services.telemetry.getHistogramById("FX_URLBAR_SELECTED_RESULT_INDEX");
   let resultTypeHist = Services.telemetry.getHistogramById("FX_URLBAR_SELECTED_RESULT_TYPE");
   let resultIndexByTypeHist = Services.telemetry.getKeyedHistogramById("FX_URLBAR_SELECTED_RESULT_INDEX_BY_TYPE");
+  let resultMethodHist = Services.telemetry.getHistogramById("FX_URLBAR_SELECTED_RESULT_METHOD");
   resultIndexByTypeHist.clear();
   resultIndexHist.clear();
   resultTypeHist.clear();
+  resultMethodHist.clear();
 
   let search_hist = getSearchCountsHistogram();
 
   let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:blank");
 
   info("Perform a one-off search using the first engine.");
   let p = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
   yield searchInAwesomebar("query");
@@ -247,29 +272,109 @@ add_task(function* test_oneOff() {
     URLBAR_SELECTED_RESULT_TYPES.searchengine,
     "FX_URLBAR_SELECTED_RESULT_TYPE");
 
   let resultIndexByType = resultIndexByTypeHist.snapshot("searchengine");
   checkHistogramResults(resultIndexByType,
     0,
     "FX_URLBAR_SELECTED_RESULT_INDEX_BY_TYPE");
 
+  let resultMethods = resultMethodHist.snapshot();
+  checkHistogramResults(resultMethods,
+    URLBAR_SELECTED_RESULT_METHODS.enter,
+    "FX_URLBAR_SELECTED_RESULT_METHOD");
+
   yield BrowserTestUtils.removeTab(tab);
 });
 
-add_task(function* test_suggestion() {
+// Performs a search using the second result, a one-off button, and the Return
+// (Enter) key.  This only tests the FX_URLBAR_SELECTED_RESULT_METHOD histogram
+// since test_oneOff_enter covers everything else.
+add_task(function* test_oneOff_enterSelection() {
+  // Let's reset the counts.
+  Services.telemetry.clearScalars();
+
+  let resultMethodHist = Services.telemetry.getHistogramById("FX_URLBAR_SELECTED_RESULT_METHOD");
+  resultMethodHist.clear();
+
+  // Create an engine to generate search suggestions and add it as default
+  // for this test.
+  const url = getRootDirectory(gTestPath) + "usageTelemetrySearchSuggestions.xml";
+  let suggestionEngine = yield new Promise((resolve, reject) => {
+    Services.search.addEngine(url, null, "", false, {
+      onSuccess(engine) { resolve(engine) },
+      onError() { reject() }
+    });
+  });
+
+  let previousEngine = Services.search.currentEngine;
+  Services.search.currentEngine = suggestionEngine;
+
+  let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:blank");
+
+  info("Type a query. Suggestions should be generated by the test engine.");
+  let p = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+  yield searchInAwesomebar("query");
+
+  info("Select the second result, press Alt+Down to take us to the first one-off engine.");
+  EventUtils.synthesizeKey("VK_DOWN", {});
+  EventUtils.synthesizeKey("VK_DOWN", { altKey: true });
+  EventUtils.sendKey("return");
+  yield p;
+
+  let resultMethods = resultMethodHist.snapshot();
+  checkHistogramResults(resultMethods,
+    URLBAR_SELECTED_RESULT_METHODS.enterSelection,
+    "FX_URLBAR_SELECTED_RESULT_METHOD");
+
+  Services.search.currentEngine = previousEngine;
+  Services.search.removeEngine(suggestionEngine);
+  yield BrowserTestUtils.removeTab(tab);
+});
+
+// Performs a search using a click on a one-off button.  This only tests the
+// FX_URLBAR_SELECTED_RESULT_METHOD histogram since test_oneOff_enter covers
+// everything else.
+add_task(function* test_oneOff_click() {
+  // Let's reset the counts.
+  Services.telemetry.clearScalars();
+
+  let resultMethodHist = Services.telemetry.getHistogramById("FX_URLBAR_SELECTED_RESULT_METHOD");
+  resultMethodHist.clear();
+
+  let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:blank");
+
+  info("Type a query.");
+  let p = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+  yield searchInAwesomebar("query");
+  info("Click the first one-off button.");
+  gURLBar.popup.oneOffSearchButtons.getSelectableButtons(false)[0].click();
+  yield p;
+
+  let resultMethods = resultMethodHist.snapshot();
+  checkHistogramResults(resultMethods,
+    URLBAR_SELECTED_RESULT_METHODS.click,
+    "FX_URLBAR_SELECTED_RESULT_METHOD");
+
+  yield BrowserTestUtils.removeTab(tab);
+});
+
+// Clicks the first suggestion offered by the test search engine.
+add_task(function* test_suggestion_click() {
   // Let's reset the counts.
   Services.telemetry.clearScalars();
   Services.telemetry.clearEvents();
   let resultIndexHist = Services.telemetry.getHistogramById("FX_URLBAR_SELECTED_RESULT_INDEX");
   let resultTypeHist = Services.telemetry.getHistogramById("FX_URLBAR_SELECTED_RESULT_TYPE");
   let resultIndexByTypeHist = Services.telemetry.getKeyedHistogramById("FX_URLBAR_SELECTED_RESULT_INDEX_BY_TYPE");
+  let resultMethodHist = Services.telemetry.getHistogramById("FX_URLBAR_SELECTED_RESULT_METHOD");
   resultIndexByTypeHist.clear();
   resultIndexHist.clear();
   resultTypeHist.clear();
+  resultMethodHist.clear();
 
   let search_hist = getSearchCountsHistogram();
 
   // Create an engine to generate search suggestions and add it as default
   // for this test.
   const url = getRootDirectory(gTestPath) + "usageTelemetrySearchSuggestions.xml";
   let suggestionEngine = yield new Promise((resolve, reject) => {
     Services.search.addEngine(url, null, "", false, {
@@ -278,17 +383,17 @@ add_task(function* test_suggestion() {
     });
   });
 
   let previousEngine = Services.search.currentEngine;
   Services.search.currentEngine = suggestionEngine;
 
   let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:blank");
 
-  info("Perform a one-off search using the first engine.");
+  info("Type a query. Suggestions should be generated by the test engine.");
   let p = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
   yield searchInAwesomebar("query");
   info("Clicking the urlbar suggestion.");
   clickURLBarSuggestion("queryfoo");
   yield p;
 
   // Check if the scalars contain the expected values.
   const scalars = getParentProcessScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, true, false);
@@ -314,12 +419,60 @@ add_task(function* test_suggestion() {
     URLBAR_SELECTED_RESULT_TYPES.searchsuggestion,
     "FX_URLBAR_SELECTED_RESULT_TYPE");
 
   let resultIndexByType = resultIndexByTypeHist.snapshot("searchsuggestion");
   checkHistogramResults(resultIndexByType,
     3,
     "FX_URLBAR_SELECTED_RESULT_INDEX_BY_TYPE");
 
+  let resultMethods = resultMethodHist.snapshot();
+  checkHistogramResults(resultMethods,
+    URLBAR_SELECTED_RESULT_METHODS.click,
+    "FX_URLBAR_SELECTED_RESULT_METHOD");
+
   Services.search.currentEngine = previousEngine;
   Services.search.removeEngine(suggestionEngine);
   yield BrowserTestUtils.removeTab(tab);
 });
+
+// Selects and presses the Return (Enter) key on the first suggestion offered by
+// the test search engine.  This only tests the FX_URLBAR_SELECTED_RESULT_METHOD
+// histogram since test_suggestion_click covers everything else.
+add_task(function* test_suggestion_enterSelection() {
+  // Let's reset the counts.
+  Services.telemetry.clearScalars();
+
+  let resultMethodHist = Services.telemetry.getHistogramById("FX_URLBAR_SELECTED_RESULT_METHOD");
+  resultMethodHist.clear();
+
+  // Create an engine to generate search suggestions and add it as default
+  // for this test.
+  const url = getRootDirectory(gTestPath) + "usageTelemetrySearchSuggestions.xml";
+  let suggestionEngine = yield new Promise((resolve, reject) => {
+    Services.search.addEngine(url, null, "", false, {
+      onSuccess(engine) { resolve(engine) },
+      onError() { reject() }
+    });
+  });
+
+  let previousEngine = Services.search.currentEngine;
+  Services.search.currentEngine = suggestionEngine;
+
+  let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:blank");
+
+  info("Type a query. Suggestions should be generated by the test engine.");
+  let p = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+  yield searchInAwesomebar("query");
+  info("Select the second result and press Return.");
+  EventUtils.synthesizeKey("VK_DOWN", {});
+  EventUtils.sendKey("return");
+  yield p;
+
+  let resultMethods = resultMethodHist.snapshot();
+  checkHistogramResults(resultMethods,
+    URLBAR_SELECTED_RESULT_METHODS.enterSelection,
+    "FX_URLBAR_SELECTED_RESULT_METHOD");
+
+  Services.search.currentEngine = previousEngine;
+  Services.search.removeEngine(suggestionEngine);
+  yield BrowserTestUtils.removeTab(tab);
+});
--- a/toolkit/components/telemetry/Histograms.json
+++ b/toolkit/components/telemetry/Histograms.json
@@ -5906,16 +5906,42 @@
     "expires_in_version": "never",
     "kind": "enumerated",
     "n_values": 14,
     "keyed": true,
     "releaseChannelCollection": "opt-out",
     "bug_numbers": [1345834],
     "description": "Firefox: The index of the selected result in the URL bar popup by the type of the selected result in the URL bar popup. See BrowserUsageTelemetry.jsm:URLBAR_SELECTED_RESULT_TYPES for the result types."
   },
+  "FX_URLBAR_SELECTED_RESULT_METHOD": {
+    "alert_emails": ["dzeber@mozilla.com"],
+    "expires_in_version": "63",
+    "releaseChannelCollection": "opt-out",
+    "kind": "categorical",
+    "labels": [
+      "enter",
+      "enterSelection",
+      "click"
+    ],
+    "bug_numbers": [1334615],
+    "description": "The input method the user used to select a result in the urlbar. 'enter' => The user hit the Enter key on the heuristic result at index 0. 'enterSelection' => The user chose a non-heuristic result and then hit the Enter key. 'click' => The user clicked a result with the mouse."
+  },
+  "FX_SEARCHBAR_SELECTED_RESULT_METHOD": {
+    "alert_emails": ["dzeber@mozilla.com"],
+    "expires_in_version": "63",
+    "releaseChannelCollection": "opt-out",
+    "kind": "categorical",
+    "labels": [
+      "enter",
+      "enterSelection",
+      "click"
+    ],
+    "bug_numbers": [1334615],
+    "description": "The input method the user used to select a result in the searchbar. 'enter' => The user hit the Enter key without choosing a result in the popup. 'enterSelection' => The user chose a result and then hit the Enter key. 'click' => The user clicked a result with the mouse."
+  },
   "INNERWINDOWS_WITH_MUTATION_LISTENERS": {
     "expires_in_version": "never",
     "kind": "boolean",
     "description": "Deleted or to-be-reused innerwindow which has had mutation event listeners."
   },
   "CHARSET_OVERRIDE_SITUATION": {
     "expires_in_version": "never",
     "kind": "enumerated",