Bug 1334630 - Measure the edit distance when selecting a search suggestion.
MozReview-Commit-ID: DGyQUmA0Yuv
--- a/browser/base/content/urlbarBindings.xml
+++ b/browser/base/content/urlbarBindings.xml
@@ -470,17 +470,18 @@ file, You can obtain one at http://mozil
}
break;
case "searchengine":
if (selectedOneOff && selectedOneOff.engine) {
// Replace the engine with the selected one-off engine.
action.params.engineName = selectedOneOff.engine.name;
}
const actionDetails = {
- isSuggestion: !!action.params.searchSuggestion,
+ query: action.params.searchQuery,
+ suggestion: action.params.searchSuggestion,
isAlias: !!action.params.alias
};
[url, postData] = this._parseAndRecordSearchEngineLoad(
action.params.engineName,
action.params.searchSuggestion || action.params.searchQuery,
event,
where,
openUILinkParams,
--- a/browser/components/search/content/search.xml
+++ b/browser/components/search/content/search.xml
@@ -437,19 +437,25 @@
let engine = aEngine || this.currentEngine;
var submission = engine.getSubmission(aData, null, "searchbar");
let telemetrySearchDetails = this.telemetrySearchDetails;
this.telemetrySearchDetails = null;
if (telemetrySearchDetails && telemetrySearchDetails.index == -1) {
telemetrySearchDetails = null;
}
// If we hit here, we come either from a one-off, a plain search or a suggestion.
+ let query =
+ telemetrySearchDetails ? telemetrySearchDetails.query : null;
+ let suggestion =
+ aOneOff || !telemetrySearchDetails ? null :
+ textBox.mController.getValueAt(telemetrySearchDetails.index);
const details = {
+ query: query,
isOneOff: aOneOff,
- isSuggestion: (!aOneOff && telemetrySearchDetails),
+ suggestion: suggestion,
selection: telemetrySearchDetails
};
BrowserSearch.recordSearchInTelemetry(engine, "searchbar", details);
// null parameter below specifies HTML response for search
let params = {
postData: submission.postData,
};
if (aParams) {
@@ -956,16 +962,17 @@
return;
var controller = this.view.QueryInterface(Components.interfaces.nsIAutoCompleteController);
var searchBar = BrowserSearch.searchBar;
var popupForSearchBar = searchBar && searchBar.textbox == this.mInput;
if (popupForSearchBar) {
searchBar.telemetrySearchDetails = {
+ query: this.mInput.value,
index: controller.selection.currentIndex,
kind: "mouse"
};
}
// Check for unmodified left-click, and use default behavior
if (aEvent.button == 0 && !aEvent.shiftKey && !aEvent.ctrlKey &&
!aEvent.altKey && !aEvent.metaKey) {
--- a/browser/modules/BrowserUsageTelemetry.jsm
+++ b/browser/modules/BrowserUsageTelemetry.jsm
@@ -7,16 +7,19 @@
this.EXPORTED_SYMBOLS = ["BrowserUsageTelemetry", "URLBAR_SELECTED_RESULT_TYPES"];
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, "NLP",
+ "resource://gre/modules/NLP.jsm");
+
XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
"resource://gre/modules/PrivateBrowsingUtils.jsm");
// The upper bound for the count of the visited unique domain names.
const MAX_UNIQUE_VISITED_DOMAINS = 100;
// Observed topic names.
const WINDOWS_RESTORED_TOPIC = "sessionstore-windows-restored";
@@ -346,20 +349,23 @@ let BrowserUsageTelemetry = {
* nothing pertaining to the search contents themselves.
*
* @param {nsISearchEngine} engine
* The engine handling the search.
* @param {String} source
* Where the search originated from. See KNOWN_SEARCH_SOURCES for allowed
* values.
* @param {Object} [details] Options object.
+ * @param {String} [details.query=null]
+ * The search query string as entered by the user.
* @param {Boolean} [details.isOneOff=false]
* true if this event was generated by a one-off search.
- * @param {Boolean} [details.isSuggestion=false]
- * true if this event was generated by a suggested search.
+ * @param {Boolean} [details.suggestion=null]
+ * The search suggestion if this event was generated by a suggested
+ * search.
* @param {Boolean} [details.isAlias=false]
* true if this event was generated by a search using an alias.
* @param {Object} [details.type=null]
* The object describing the event that triggered the search.
* @throws if source is not in the known sources list.
*/
recordSearch(engine, source, details = {}) {
const isOneOff = !!details.isOneOff;
@@ -444,30 +450,45 @@ let BrowserUsageTelemetry = {
}
// 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) {
+ if (details.suggestion) {
// It came from a suggested search, so count it as such.
- this._recordSearch(engine, sourceName, "suggestion");
+ this._recordSearchSuggestion(engine, sourceName, details);
return;
} else if (details.isAlias) {
// This one came from a search that used an alias.
this._recordSearch(engine, sourceName, "alias");
return;
}
// The search signal was generated by typing something and pressing enter.
this._recordSearch(engine, sourceName, "enter");
},
+ _recordSearchSuggestion(engine, source, details) {
+ this._recordSearch(engine, source, "suggestion");
+ if (!details.query || !details.suggestion) {
+ return;
+ }
+ let histogram = Services.telemetry.getKeyedHistogramById(
+ "FX_SEARCH_SUGGESTION_EDIT_DISTANCE"
+ );
+ let queryLengths = [6, 12, 24, 48];
+ let key = queryLengths.find(l => details.query.length <= l) ||
+ ">" + queryLengths[queryLengths.length - 1];
+ let distance = NLP.levenshtein(details.query, details.suggestion);
+ histogram.add(String(key), distance);
+ },
+
/**
* 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);
--- a/toolkit/components/telemetry/Histograms.json
+++ b/toolkit/components/telemetry/Histograms.json
@@ -5481,16 +5481,26 @@
"bug_numbers": [1324167],
"alert_emails": ["jaws@mozilla.com"],
"expires_in_version": "56",
"kind": "categorical",
"labels": ["unknown", "general", "search", "content", "applications", "privacy", "security", "sync", "advancedGeneral", "advancedDataChoices", "advancedNetwork", "advancedUpdates", "advancedCerts"],
"releaseChannelCollection": "opt-out",
"description": "Count how often each preference category is opened."
},
+ "FX_SEARCH_SUGGESTION_EDIT_DISTANCE": {
+ "bug_numbers": [1334630],
+ "alert_emails": ["adw@mozilla.com"],
+ "expires_in_version": "58",
+ "kind": "exponential",
+ "keyed": true,
+ "high": 200,
+ "n_buckets": 20,
+ "description": "The edit (Levenshtein) distance between the user's query string and the search suggestion they clicked, keyed on approximate query string length. Keys: '6' => query strings of lengths in the range (0, 6]; '12' => (6, 12]; '24' => (12, 24]; '48' => (24, 48]; '>48' for lengths > 48."
+ },
"INPUT_EVENT_RESPONSE_MS": {
"alert_emails": ["perf-telemetry-alerts@mozilla.com"],
"bug_numbers": [1235908],
"expires_in_version": "never",
"kind": "exponential",
"high": 10000,
"n_buckets": 50,
"description": "Time (ms) from the Input event being created to the end of it being handled"
--- a/toolkit/content/widgets/autocomplete.xml
+++ b/toolkit/content/widgets/autocomplete.xml
@@ -525,16 +525,17 @@
case KeyEvent.DOM_VK_RETURN:
if (/Mac/.test(navigator.platform)) {
// Prevent the default action, since it will beep on Mac
if (aEvent.metaKey)
aEvent.preventDefault();
}
if (this.mController.selection) {
this._selectionDetails = {
+ query: this.value,
index: this.mController.selection.currentIndex,
kind: "key"
};
}
cancel = this.handleEnter(aEvent);
break;
case KeyEvent.DOM_VK_DELETE:
if (/Mac/.test(navigator.platform) && !aEvent.shiftKey) {