forked from mirrors/gecko-dev
293 lines
9.5 KiB
JavaScript
293 lines
9.5 KiB
JavaScript
/* 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/. */
|
|
|
|
const lazy = {};
|
|
|
|
ChromeUtils.defineESModuleGetters(lazy, {
|
|
PartnerLinkAttribution: "resource:///modules/PartnerLinkAttribution.sys.mjs",
|
|
PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.sys.mjs",
|
|
SearchSERPTelemetry: "resource:///modules/SearchSERPTelemetry.sys.mjs",
|
|
UrlbarSearchUtils: "resource:///modules/UrlbarSearchUtils.sys.mjs",
|
|
});
|
|
|
|
// A map of known search origins.
|
|
// The keys of this map are used in the calling code to recordSearch, and in
|
|
// the SEARCH_COUNTS histogram.
|
|
// The values of this map are used in the names of scalars for the following
|
|
// scalar groups:
|
|
// browser.engagement.navigation.*
|
|
// browser.search.content.*
|
|
// browser.search.withads.*
|
|
// browser.search.adclicks.*
|
|
const KNOWN_SEARCH_SOURCES = new Map([
|
|
["abouthome", "about_home"],
|
|
["contextmenu", "contextmenu"],
|
|
["newtab", "about_newtab"],
|
|
["searchbar", "searchbar"],
|
|
["system", "system"],
|
|
["urlbar", "urlbar"],
|
|
["urlbar-handoff", "urlbar_handoff"],
|
|
["urlbar-persisted", "urlbar_persisted"],
|
|
["urlbar-searchmode", "urlbar_searchmode"],
|
|
["webextension", "webextension"],
|
|
]);
|
|
|
|
/**
|
|
* This class handles saving search telemetry related to the url bar,
|
|
* search bar and other areas as per the sources above.
|
|
*/
|
|
class BrowserSearchTelemetryHandler {
|
|
KNOWN_SEARCH_SOURCES = KNOWN_SEARCH_SOURCES;
|
|
|
|
/**
|
|
* Determines if we should record a search for this browser instance.
|
|
* Private Browsing mode is normally skipped.
|
|
*
|
|
* @param {browser} browser
|
|
* The browser where the search was loaded.
|
|
* @returns {boolean}
|
|
* True if the search should be recorded, false otherwise.
|
|
*/
|
|
shouldRecordSearchCount(browser) {
|
|
return (
|
|
!lazy.PrivateBrowsingUtils.isWindowPrivate(browser.ownerGlobal) ||
|
|
!Services.prefs.getBoolPref("browser.engagement.search_counts.pbm", false)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Records the method by which the user selected a result from the urlbar or
|
|
* searchbar.
|
|
*
|
|
* @param {Event} event
|
|
* The event that triggered the selection.
|
|
* @param {string} source
|
|
* Either "urlbar" or "searchbar" depending on the source.
|
|
* @param {number} index
|
|
* The index that the user chose in the popup, or -1 if there wasn't a
|
|
* selection.
|
|
* @param {string} userSelectionBehavior
|
|
* How the user cycled through results before picking the current match.
|
|
* Could be one of "tab", "arrow" or "none".
|
|
*/
|
|
recordSearchSuggestionSelectionMethod(
|
|
event,
|
|
source,
|
|
index,
|
|
userSelectionBehavior = "none"
|
|
) {
|
|
// If the contents of the histogram are changed then
|
|
// `UrlbarTestUtils.SELECTED_RESULT_METHODS` should also be updated.
|
|
if (source == "searchbar" && userSelectionBehavior != "none") {
|
|
throw new Error("Did not expect a selection behavior for the searchbar.");
|
|
}
|
|
|
|
let histogram = Services.telemetry.getHistogramById(
|
|
source == "urlbar"
|
|
? "FX_URLBAR_SELECTED_RESULT_METHOD"
|
|
: "FX_SEARCHBAR_SELECTED_RESULT_METHOD"
|
|
);
|
|
// command events are from the one-off context menu. Treat them as clicks.
|
|
// Note that we don't care about MouseEvent subclasses here, since
|
|
// those are not clicks.
|
|
let isClick =
|
|
event &&
|
|
(ChromeUtils.getClassName(event) == "MouseEvent" ||
|
|
event.type == "command");
|
|
let category;
|
|
if (isClick) {
|
|
category = "click";
|
|
} else if (index >= 0) {
|
|
switch (userSelectionBehavior) {
|
|
case "tab":
|
|
category = "tabEnterSelection";
|
|
break;
|
|
case "arrow":
|
|
category = "arrowEnterSelection";
|
|
break;
|
|
case "rightClick":
|
|
// Selected by right mouse button.
|
|
category = "rightClickEnter";
|
|
break;
|
|
default:
|
|
category = "enterSelection";
|
|
}
|
|
} else {
|
|
category = "enter";
|
|
}
|
|
histogram.add(category);
|
|
}
|
|
|
|
/**
|
|
* Records entry into the Urlbar's search mode.
|
|
*
|
|
* Telemetry records only which search mode is entered and how it was entered.
|
|
* It does not record anything pertaining to searches made within search mode.
|
|
*
|
|
* @param {object} searchMode
|
|
* A search mode object. See UrlbarInput.setSearchMode documentation for
|
|
* details.
|
|
*/
|
|
recordSearchMode(searchMode) {
|
|
// Search mode preview is not search mode. Recording it would just create
|
|
// noise.
|
|
if (searchMode.isPreview) {
|
|
return;
|
|
}
|
|
|
|
let scalarKey = lazy.UrlbarSearchUtils.getSearchModeScalarKey(searchMode);
|
|
Services.telemetry.keyedScalarAdd(
|
|
"urlbar.searchmode." + searchMode.entry,
|
|
scalarKey,
|
|
1
|
|
);
|
|
}
|
|
|
|
/**
|
|
* The main entry point for recording search related Telemetry. This includes
|
|
* search counts and engagement measurements.
|
|
*
|
|
* Telemetry records only search counts per engine and action origin, but
|
|
* nothing pertaining to the search contents themselves.
|
|
*
|
|
* @param {browser} browser
|
|
* The browser where the search originated.
|
|
* @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 {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.isFormHistory=false]
|
|
* true if this event was generated by a form history result.
|
|
* @param {string} [details.alias=null]
|
|
* The search engine alias used in the search, if any.
|
|
* @param {string} [details.newtabSessionId=undefined]
|
|
* The newtab session that prompted this search, if any.
|
|
* @throws if source is not in the known sources list.
|
|
*/
|
|
recordSearch(browser, engine, source, details = {}) {
|
|
try {
|
|
if (!this.shouldRecordSearchCount(browser)) {
|
|
return;
|
|
}
|
|
if (!KNOWN_SEARCH_SOURCES.has(source)) {
|
|
console.error("Unknown source for search: ", source);
|
|
return;
|
|
}
|
|
|
|
const countIdPrefix = `${engine.telemetryId}.`;
|
|
const countIdSource = countIdPrefix + source;
|
|
let histogram = Services.telemetry.getKeyedHistogramById("SEARCH_COUNTS");
|
|
|
|
if (
|
|
details.alias &&
|
|
engine.isAppProvided &&
|
|
engine.aliases.includes(details.alias)
|
|
) {
|
|
// This is a keyword search using an AppProvided engine.
|
|
// Record the source as "alias", not "urlbar".
|
|
histogram.add(countIdPrefix + "alias");
|
|
} else {
|
|
histogram.add(countIdSource);
|
|
}
|
|
|
|
// Dispatch the search signal to other handlers.
|
|
switch (source) {
|
|
case "urlbar":
|
|
case "searchbar":
|
|
case "urlbar-searchmode":
|
|
case "urlbar-persisted":
|
|
case "urlbar-handoff":
|
|
this._handleSearchAndUrlbar(browser, engine, source, details);
|
|
break;
|
|
case "abouthome":
|
|
case "newtab":
|
|
this._recordSearch(browser, engine, details.url, source, "enter");
|
|
break;
|
|
default:
|
|
this._recordSearch(browser, engine, details.url, source);
|
|
break;
|
|
}
|
|
if (["urlbar-handoff", "abouthome", "newtab"].includes(source)) {
|
|
Glean.newtabSearch.issued.record({
|
|
newtab_visit_id: details.newtabSessionId,
|
|
search_access_point: KNOWN_SEARCH_SOURCES.get(source),
|
|
telemetry_id: engine.telemetryId,
|
|
});
|
|
lazy.SearchSERPTelemetry.recordBrowserNewtabSession(
|
|
browser,
|
|
details.newtabSessionId
|
|
);
|
|
}
|
|
} catch (ex) {
|
|
// Catch any errors here, so that search actions are not broken if
|
|
// telemetry is broken for some reason.
|
|
console.error(ex);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* This function handles the "urlbar", "urlbar-oneoff", "searchbar" and
|
|
* "searchbar-oneoff" sources.
|
|
*
|
|
* @param {browser} browser
|
|
* The browser where the search originated.
|
|
* @param {nsISearchEngine} engine
|
|
* The engine handling the search.
|
|
* @param {string} source
|
|
* Where the search originated from.
|
|
* @param {object} details
|
|
* See {@link BrowserSearchTelemetryHandler.recordSearch}
|
|
*/
|
|
_handleSearchAndUrlbar(browser, engine, source, details) {
|
|
const isOneOff = !!details.isOneOff;
|
|
let action = "enter";
|
|
if (isOneOff) {
|
|
action = "oneoff";
|
|
} else if (details.isFormHistory) {
|
|
action = "formhistory";
|
|
} else if (details.isSuggestion) {
|
|
action = "suggestion";
|
|
} else if (details.alias) {
|
|
action = "alias";
|
|
}
|
|
|
|
this._recordSearch(browser, engine, details.url, source, action);
|
|
}
|
|
|
|
_recordSearch(browser, engine, url, source, action = null) {
|
|
if (url) {
|
|
lazy.PartnerLinkAttribution.makeSearchEngineRequest(engine, url).catch(
|
|
console.error
|
|
);
|
|
}
|
|
|
|
let scalarSource = KNOWN_SEARCH_SOURCES.get(source);
|
|
|
|
lazy.SearchSERPTelemetry.recordBrowserSource(browser, scalarSource);
|
|
|
|
let scalarKey = action ? "search_" + action : "search";
|
|
Services.telemetry.keyedScalarAdd(
|
|
"browser.engagement.navigation." + scalarSource,
|
|
scalarKey,
|
|
1
|
|
);
|
|
Services.telemetry.recordEvent(
|
|
"navigation",
|
|
"search",
|
|
scalarSource,
|
|
action,
|
|
{
|
|
engine: engine.telemetryId,
|
|
}
|
|
);
|
|
}
|
|
}
|
|
|
|
export var BrowserSearchTelemetry = new BrowserSearchTelemetryHandler();
|