Bug 1316281 - Part 2 - Record search event in Telemetry. r=dexter,mak data-review=rweiss
☠☠ backed out by dd01d903f823 ☠ ☠
authorGeorg Fritzsche <georg.fritzsche@googlemail.com>
Thu, 01 Dec 2016 14:57:32 +0100
changeset 325012 b3ea1334c33a48a50c21daf6150cf51651b1d16f
parent 325011 823e118b3fad7539b0fc64202535ed3cbde2c634
child 325013 2c711bb8e3738cc21e509df3c9aaf66071a4712c
push id24
push usermaklebus@msu.edu
push dateTue, 20 Dec 2016 03:11:33 +0000
reviewersdexter, mak
bugs1316281
milestone53.0a1
Bug 1316281 - Part 2 - Record search event in Telemetry. r=dexter,mak data-review=rweiss
browser/modules/BrowserUsageTelemetry.jsm
browser/modules/test/browser_UsageTelemetry_content.js
browser/modules/test/browser_UsageTelemetry_content_aboutHome.js
browser/modules/test/browser_UsageTelemetry_searchbar.js
browser/modules/test/browser_UsageTelemetry_urlbar.js
browser/modules/test/head.js
toolkit/components/telemetry/Events.yaml
--- a/browser/modules/BrowserUsageTelemetry.jsm
+++ b/browser/modules/BrowserUsageTelemetry.jsm
@@ -288,88 +288,91 @@ let BrowserUsageTelemetry = {
     } else {
       if (!KNOWN_SEARCH_SOURCES.includes(source)) {
         throw new Error("Unknown source for search: " + source);
       }
       Services.telemetry.getKeyedHistogramById("SEARCH_COUNTS").add(countId);
     }
 
     // Dispatch the search signal to other handlers.
-    this._handleSearchAction(source, details);
+    this._handleSearchAction(engine, source, details);
   },
 
-  _handleSearchAction(source, details) {
+  _recordSearch(engine, source, action = null) {
+    let scalarKey = action ? "search_" + action : "search";
+    Services.telemetry.keyedScalarAdd("browser.engagement.navigation." + source,
+                                      scalarKey, 1);
+    Services.telemetry.recordEvent("navigation", "search", source, action,
+                                   { engine: getSearchEngineId(engine) });
+  },
+
+  _handleSearchAction(engine, source, details) {
     switch (source) {
       case "urlbar":
       case "oneoff-urlbar":
       case "searchbar":
       case "oneoff-searchbar":
       case "unknown": // Edge case: this is the searchbar (see bug 1195733 comment 7).
-        this._handleSearchAndUrlbar(source, details);
+        this._handleSearchAndUrlbar(engine, source, details);
         break;
       case "abouthome":
-        Services.telemetry.keyedScalarAdd("browser.engagement.navigation.about_home",
-                                          "search_enter", 1);
+        this._recordSearch(engine, "about_home", "enter");
         break;
       case "newtab":
-        Services.telemetry.keyedScalarAdd("browser.engagement.navigation.about_newtab",
-                                          "search_enter", 1);
+        this._recordSearch(engine, "about_newtab", "enter");
         break;
       case "contextmenu":
-        Services.telemetry.keyedScalarAdd("browser.engagement.navigation.contextmenu",
-                                          "search", 1);
+        this._recordSearch(engine, "contextmenu");
         break;
     }
   },
 
   /**
    * This function handles the "urlbar", "urlbar-oneoff", "searchbar" and
    * "searchbar-oneoff" sources.
    */
-  _handleSearchAndUrlbar(source, details) {
+  _handleSearchAndUrlbar(engine, source, details) {
     // We want "urlbar" and "urlbar-oneoff" (and similar cases) to go in the same
     // scalar, but in a different key.
 
     // When using one-offs in the searchbar we get an "unknown" source. See bug
     // 1195733 comment 7 for the context. Fix-up the label here.
-    const plainSourceName =
+    const sourceName =
       (source === "unknown") ? "searchbar" : source.replace("oneoff-", "");
-    const scalarName = "browser.engagement.navigation." + plainSourceName;
 
     const isOneOff = !!details.isOneOff;
     if (isOneOff) {
       // We will receive a signal from the "urlbar"/"searchbar" even when the
       // search came from "oneoff-urlbar". That's because both signals
       // are propagated from search.xml. Skip it if that's the case.
       // Moreover, we skip the "unknown" source that comes from the searchbar
       // when performing searches from the default search engine. See bug 1195733
       // comment 7 for context.
       if (["urlbar", "searchbar", "unknown"].includes(source)) {
         return;
       }
 
-      // If that's a legit one-off search signal, increment the scalar using the
-      // relative key.
-      Services.telemetry.keyedScalarAdd(scalarName, "search_oneoff", 1);
+      // If that's a legit one-off search signal, record it using the relative key.
+      this._recordSearch(engine, sourceName, "oneoff");
       return;
     }
 
     // The search was not a one-off. It was a search with the default search engine.
     if (details.isSuggestion) {
       // It came from a suggested search, so count it as such.
-      Services.telemetry.keyedScalarAdd(scalarName, "search_suggestion", 1);
+      this._recordSearch(engine, sourceName, "suggestion");
       return;
     } else if (details.isAlias) {
       // This one came from a search that used an alias.
-      Services.telemetry.keyedScalarAdd(scalarName, "search_alias", 1);
+      this._recordSearch(engine, sourceName, "alias");
       return;
     }
 
     // The search signal was generated by typing something and pressing enter.
-    Services.telemetry.keyedScalarAdd(scalarName, "search_enter", 1);
+    this._recordSearch(engine, sourceName, "enter");
   },
 
   /**
    * 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() {
--- a/browser/modules/test/browser_UsageTelemetry_content.js
+++ b/browser/modules/test/browser_UsageTelemetry_content.js
@@ -32,18 +32,19 @@ add_task(function* setup() {
   registerCleanupFunction(function* () {
     Services.search.currentEngine = originalEngine;
     Services.search.removeEngine(engineDefault);
     Services.search.removeEngine(engineOneOff);
   });
 });
 
 add_task(function* test_context_menu() {
-  // Let's reset the counts.
+  // Let's reset the Telemetry data.
   Services.telemetry.clearScalars();
+  Services.telemetry.clearEvents();
   let search_hist = getSearchCountsHistogram();
 
   // Open a new tab with a page containing some text.
   let tab =
     yield BrowserTestUtils.openNewForegroundTab(gBrowser, "data:text/plain;charset=utf8,test%20search");
 
   info("Select all the text in the page.");
   yield ContentTask.spawn(tab.linkedBrowser, "", function*() {
@@ -59,34 +60,40 @@ add_task(function* test_context_menu() {
   BrowserTestUtils.synthesizeMouseAtCenter("body", { type: "contextmenu", button: 2 },
                                            gBrowser.selectedBrowser);
   yield popupPromise;
 
   info("Click on search.");
   let searchItem = contextMenu.getElementsByAttribute("id", "context-searchselect")[0];
   searchItem.click();
 
-  info("Validate the search counts.");
+  info("Validate the search metrics.");
   const scalars =
     Services.telemetry.snapshotKeyedScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, false);
   checkKeyedScalar(scalars, SCALAR_CONTEXT_MENU, "search", 1);
   Assert.equal(Object.keys(scalars[SCALAR_CONTEXT_MENU]).length, 1,
                "This search must only increment one entry in the scalar.");
 
   // Make sure SEARCH_COUNTS contains identical values.
   checkKeyedHistogram(search_hist, 'other-MozSearch.contextmenu', 1);
 
+  // Also check events.
+  let events = Services.telemetry.snapshotBuiltinEvents(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, false);
+  events = events.filter(e => e[1] == "navigation" && e[2] == "search");
+  checkEvents(events, [["navigation", "search", "contextmenu", null, {engine: "other-MozSearch"}]]);
+
   contextMenu.hidePopup();
   yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
   yield BrowserTestUtils.removeTab(tab);
 });
 
 add_task(function* test_about_newtab() {
   // Let's reset the counts.
   Services.telemetry.clearScalars();
+  Services.telemetry.clearEvents();
   let search_hist = getSearchCountsHistogram();
 
   let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:newtab", false);
   yield ContentTask.spawn(tab.linkedBrowser, null, function* () {
     yield ContentTaskUtils.waitForCondition(() => !content.document.hidden);
   });
 
   info("Trigger a simple serch, just text + enter.");
@@ -100,10 +107,15 @@ add_task(function* test_about_newtab() {
     Services.telemetry.snapshotKeyedScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, false);
   checkKeyedScalar(scalars, SCALAR_ABOUT_NEWTAB, "search_enter", 1);
   Assert.equal(Object.keys(scalars[SCALAR_ABOUT_NEWTAB]).length, 1,
                "This search must only increment one entry in the scalar.");
 
   // Make sure SEARCH_COUNTS contains identical values.
   checkKeyedHistogram(search_hist, 'other-MozSearch.newtab', 1);
 
+  // Also check events.
+  let events = Services.telemetry.snapshotBuiltinEvents(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, false);
+  events = events.filter(e => e[1] == "navigation" && e[2] == "search");
+  checkEvents(events, [["navigation", "search", "about_newtab", "enter", {engine: "other-MozSearch"}]]);
+
   yield BrowserTestUtils.removeTab(tab);
 });
--- a/browser/modules/test/browser_UsageTelemetry_content_aboutHome.js
+++ b/browser/modules/test/browser_UsageTelemetry_content_aboutHome.js
@@ -36,16 +36,17 @@ add_task(function* setup() {
     Services.search.removeEngine(engineDefault);
     Services.search.removeEngine(engineOneOff);
   });
 });
 
 add_task(function* test_abouthome_simpleQuery() {
   // Let's reset the counts.
   Services.telemetry.clearScalars();
+  Services.telemetry.clearEvents();
   let search_hist = getSearchCountsHistogram();
 
   let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser);
 
   info("Setup waiting for AboutHomeLoadSnippetsCompleted.");
   let promiseAboutHomeLoaded = new Promise(resolve => {
     tab.linkedBrowser.addEventListener("AboutHomeLoadSnippetsCompleted", function loadListener(event) {
       tab.linkedBrowser.removeEventListener("AboutHomeLoadSnippetsCompleted", loadListener, true);
@@ -69,10 +70,15 @@ add_task(function* test_abouthome_simple
     Services.telemetry.snapshotKeyedScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, false);
   checkKeyedScalar(scalars, SCALAR_ABOUT_HOME, "search_enter", 1);
   Assert.equal(Object.keys(scalars[SCALAR_ABOUT_HOME]).length, 1,
                "This search must only increment one entry in the scalar.");
 
   // Make sure SEARCH_COUNTS contains identical values.
   checkKeyedHistogram(search_hist, 'other-MozSearch.abouthome', 1);
 
+  // Also check events.
+  let events = Services.telemetry.snapshotBuiltinEvents(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, false);
+  events = events.filter(e => e[1] == "navigation" && e[2] == "search");
+  checkEvents(events, [["navigation", "search", "about_home", "enter", {engine: "other-MozSearch"}]]);
+
   yield BrowserTestUtils.removeTab(tab);
 });
--- a/browser/modules/test/browser_UsageTelemetry_searchbar.js
+++ b/browser/modules/test/browser_UsageTelemetry_searchbar.js
@@ -76,16 +76,17 @@ add_task(function* setup() {
     Services.search.removeEngine(engineDefault);
     Services.search.removeEngine(engineOneOff);
   });
 });
 
 add_task(function* test_plainQuery() {
   // Let's reset the counts.
   Services.telemetry.clearScalars();
+  Services.telemetry.clearEvents();
   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");
@@ -96,22 +97,28 @@ add_task(function* test_plainQuery() {
     Services.telemetry.snapshotKeyedScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, false);
   checkKeyedScalar(scalars, SCALAR_SEARCHBAR, "search_enter", 1);
   Assert.equal(Object.keys(scalars[SCALAR_SEARCHBAR]).length, 1,
                "This search must only increment one entry in the scalar.");
 
   // 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.filter(e => e[1] == "navigation" && e[2] == "search");
+  checkEvents(events, [["navigation", "search", "searchbar", "enter", {engine: "other-MozSearch"}]]);
+
   yield BrowserTestUtils.removeTab(tab);
 });
 
 add_task(function* test_oneOff() {
   // Let's reset the counts.
   Services.telemetry.clearScalars();
+  Services.telemetry.clearEvents();
   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");
 
@@ -125,22 +132,28 @@ add_task(function* test_oneOff() {
     Services.telemetry.snapshotKeyedScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, false);
   checkKeyedScalar(scalars, SCALAR_SEARCHBAR, "search_oneoff", 1);
   Assert.equal(Object.keys(scalars[SCALAR_SEARCHBAR]).length, 1,
                "This search must only increment one entry in the scalar.");
 
   // 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.filter(e => e[1] == "navigation" && e[2] == "search");
+  checkEvents(events, [["navigation", "search", "searchbar", "oneoff", {engine: "other-MozSearch2"}]]);
+
   yield BrowserTestUtils.removeTab(tab);
 });
 
 add_task(function* test_suggestion() {
   // Let's reset the counts.
   Services.telemetry.clearScalars();
+  Services.telemetry.clearEvents();
   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) },
@@ -163,14 +176,20 @@ add_task(function* test_suggestion() {
   // Check if the scalars contain the expected values.
   const scalars =
     Services.telemetry.snapshotKeyedScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, false);
   checkKeyedScalar(scalars, SCALAR_SEARCHBAR, "search_suggestion", 1);
   Assert.equal(Object.keys(scalars[SCALAR_SEARCHBAR]).length, 1,
                "This search must only increment one entry in the scalar.");
 
   // Make sure SEARCH_COUNTS contains identical values.
-  checkKeyedHistogram(search_hist, 'other-' + suggestionEngine.name + '.searchbar', 1);
+  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.filter(e => e[1] == "navigation" && e[2] == "search");
+  checkEvents(events, [["navigation", "search", "searchbar", "suggestion", {engine: searchEngineId}]]);
 
   Services.search.currentEngine = previousEngine;
   Services.search.removeEngine(suggestionEngine);
   yield BrowserTestUtils.removeTab(tab);
 });
--- a/browser/modules/test/browser_UsageTelemetry_urlbar.js
+++ b/browser/modules/test/browser_UsageTelemetry_urlbar.js
@@ -69,16 +69,17 @@ add_task(function* setup() {
     Services.prefs.clearUserPref(SUGGEST_URLBAR_PREF, true);
     Services.prefs.clearUserPref(ONEOFF_URLBAR_PREF);
   });
 });
 
 add_task(function* test_simpleQuery() {
   // Let's reset the counts.
   Services.telemetry.clearScalars();
+  Services.telemetry.clearEvents();
   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");
   EventUtils.sendKey("return");
@@ -89,22 +90,28 @@ add_task(function* test_simpleQuery() {
     Services.telemetry.snapshotKeyedScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, false);
   checkKeyedScalar(scalars, SCALAR_URLBAR, "search_enter", 1);
   Assert.equal(Object.keys(scalars[SCALAR_URLBAR]).length, 1,
                "This search must only increment one entry in the scalar.");
 
   // Make sure SEARCH_COUNTS contains identical values.
   checkKeyedHistogram(search_hist, 'other-MozSearch.urlbar', 1);
 
+  // Also check events.
+  let events = Services.telemetry.snapshotBuiltinEvents(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, false);
+  events = events.filter(e => e[1] == "navigation" && e[2] == "search");
+  checkEvents(events, [["navigation", "search", "urlbar", "enter", {engine: "other-MozSearch"}]]);
+
   yield BrowserTestUtils.removeTab(tab);
 });
 
 add_task(function* test_searchAlias() {
   // Let's reset the counts.
   Services.telemetry.clearScalars();
+  Services.telemetry.clearEvents();
   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");
   EventUtils.sendKey("return");
@@ -115,22 +122,28 @@ add_task(function* test_searchAlias() {
     Services.telemetry.snapshotKeyedScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, false);
   checkKeyedScalar(scalars, SCALAR_URLBAR, "search_alias", 1);
   Assert.equal(Object.keys(scalars[SCALAR_URLBAR]).length, 1,
                "This search must only increment one entry in the scalar.");
 
   // Make sure SEARCH_COUNTS contains identical values.
   checkKeyedHistogram(search_hist, 'other-MozSearch.urlbar', 1);
 
+  // Also check events.
+  let events = Services.telemetry.snapshotBuiltinEvents(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, false);
+  events = events.filter(e => e[1] == "navigation" && e[2] == "search");
+  checkEvents(events, [["navigation", "search", "urlbar", "alias", {engine: "other-MozSearch"}]]);
+
   yield BrowserTestUtils.removeTab(tab);
 });
 
 add_task(function* test_oneOff() {
   // Let's reset the counts.
   Services.telemetry.clearScalars();
+  Services.telemetry.clearEvents();
   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");
 
@@ -144,22 +157,28 @@ add_task(function* test_oneOff() {
     Services.telemetry.snapshotKeyedScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, false);
   checkKeyedScalar(scalars, SCALAR_URLBAR, "search_oneoff", 1);
   Assert.equal(Object.keys(scalars[SCALAR_URLBAR]).length, 1,
                "This search must only increment one entry in the scalar.");
 
   // Make sure SEARCH_COUNTS contains identical values.
   checkKeyedHistogram(search_hist, 'other-MozSearch.urlbar', 1);
 
+  // Also check events.
+  let events = Services.telemetry.snapshotBuiltinEvents(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, false);
+  events = events.filter(e => e[1] == "navigation" && e[2] == "search");
+  checkEvents(events, [["navigation", "search", "urlbar", "oneoff", {engine: "other-MozSearch"}]]);
+
   yield BrowserTestUtils.removeTab(tab);
 });
 
 add_task(function* test_suggestion() {
   // Let's reset the counts.
   Services.telemetry.clearScalars();
+  Services.telemetry.clearEvents();
   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) },
@@ -182,14 +201,20 @@ add_task(function* test_suggestion() {
   // Check if the scalars contain the expected values.
   const scalars =
     Services.telemetry.snapshotKeyedScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, false);
   checkKeyedScalar(scalars, SCALAR_URLBAR, "search_suggestion", 1);
   Assert.equal(Object.keys(scalars[SCALAR_URLBAR]).length, 1,
                "This search must only increment one entry in the scalar.");
 
   // Make sure SEARCH_COUNTS contains identical values.
-  checkKeyedHistogram(search_hist, 'other-' + suggestionEngine.name + '.urlbar', 1);
+  let searchEngineId = 'other-' + suggestionEngine.name;
+  checkKeyedHistogram(search_hist, searchEngineId + '.urlbar', 1);
+
+  // Also check events.
+  let events = Services.telemetry.snapshotBuiltinEvents(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, false);
+  events = events.filter(e => e[1] == "navigation" && e[2] == "search");
+  checkEvents(events, [["navigation", "search", "urlbar", "suggestion", {engine: searchEngineId}]]);
 
   Services.search.currentEngine = previousEngine;
   Services.search.removeEngine(suggestionEngine);
   yield BrowserTestUtils.removeTab(tab);
 });
--- a/browser/modules/test/head.js
+++ b/browser/modules/test/head.js
@@ -90,8 +90,19 @@ function getSearchCountsHistogram() {
 /**
  * Check that the keyed histogram contains the right value.
  */
 function checkKeyedHistogram(h, key, expectedValue) {
   const snapshot = h.snapshot();
   Assert.ok(key in snapshot, `The histogram must contain ${key}.`);
   Assert.equal(snapshot[key].sum, expectedValue, `The key ${key} must contain ${expectedValue}.`);
 }
+
+function checkEvents(events, expectedEvents) {
+  Assert.equal(events.length, expectedEvents.length, "Should have matching amount of events.");
+
+  // Strip timestamps from the events for easier comparison.
+  events = events.map(e => e.slice(1));
+
+  for (let i = 0; i < events.length; ++i) {
+    Assert.deepEqual(events[i], expectedEvents[i], "Events should match.");
+  }
+}
--- a/toolkit/components/telemetry/Events.yaml
+++ b/toolkit/components/telemetry/Events.yaml
@@ -1,8 +1,23 @@
+navigation:
+- methods: ["search"]
+  objects: ["about_home", "about_newtab", "contextmenu", "oneoff",
+            "suggestion", "alias", "enter", "searchbar", "urlbar"]
+  release_channel_collection: opt-in
+  description: >
+    This is recorded on each search navigation.
+    The value field records the action used to trigger the search:
+      "enter", "oneoff", "suggestion", "alias", null (for contextmenu)
+  bug_numbers: [1316281]
+  notification_emails: ["past@mozilla.com"]
+  expiry_version: "58.0"
+  extra_keys:
+    engine: The id of the search engine used.
+
 # This category contains event entries used for Telemetry tests.
 # They will not be sent out with any pings.
 telemetry.test:
 - methods: ["test1", "test2"]
   objects: ["object1", "object2"]
   bug_numbers: [1286606]
   notification_emails: ["telemetry-client-dev@mozilla.com"]
   description: This is a test entry for Telemetry.