зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1881875 - Add telemetry for potential suggestion impressions in the urlbar. r=mak
This includes some unrelated changes that lint either made or complained about. Depends on D206110 Differential Revision: https://phabricator.services.mozilla.com/D206173
This commit is contained in:
Родитель
984109aa14
Коммит
7b341f3ffa
|
@ -133,6 +133,7 @@ export class UrlbarController {
|
|||
// notifications related to the previous query.
|
||||
this.notify(NOTIFICATIONS.QUERY_STARTED, queryContext);
|
||||
await this.manager.startQuery(queryContext, this);
|
||||
|
||||
// If the query has been cancelled, onQueryFinished was notified already.
|
||||
// Note this._lastQueryContextWrapper may have changed in the meanwhile.
|
||||
if (
|
||||
|
@ -144,6 +145,16 @@ export class UrlbarController {
|
|||
this.manager.cancelQuery(queryContext);
|
||||
this.notify(NOTIFICATIONS.QUERY_FINISHED, queryContext);
|
||||
}
|
||||
|
||||
// Record a potential exposure if the current search string matches one of
|
||||
// the registered keywords.
|
||||
if (!queryContext.isPrivate) {
|
||||
let searchStr = queryContext.trimmedLowerCaseSearchString;
|
||||
if (lazy.UrlbarPrefs.get("potentialExposureKeywords").has(searchStr)) {
|
||||
this.engagementEvent.addPotentialExposure(searchStr);
|
||||
}
|
||||
}
|
||||
|
||||
return queryContext;
|
||||
}
|
||||
|
||||
|
@ -335,7 +346,7 @@ export class UrlbarController {
|
|||
}
|
||||
event.preventDefault();
|
||||
break;
|
||||
case KeyEvent.DOM_VK_TAB:
|
||||
case KeyEvent.DOM_VK_TAB: {
|
||||
// It's always possible to tab through results when the urlbar was
|
||||
// focused with the mouse or has a search string, or when the view
|
||||
// already has a selection.
|
||||
|
@ -368,6 +379,7 @@ export class UrlbarController {
|
|||
event.preventDefault();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case KeyEvent.DOM_VK_PAGE_DOWN:
|
||||
case KeyEvent.DOM_VK_PAGE_UP:
|
||||
if (event.ctrlKey) {
|
||||
|
@ -952,6 +964,10 @@ class TelemetryEvent {
|
|||
}
|
||||
);
|
||||
|
||||
if (!details.isSessionOngoing) {
|
||||
this.#recordEndOfSessionTelemetry(details.searchString);
|
||||
}
|
||||
|
||||
if (skipLegacyTelemetry) {
|
||||
this._controller.manager.notifyEngagementChange(
|
||||
method,
|
||||
|
@ -1105,23 +1121,6 @@ class TelemetryEvent {
|
|||
return;
|
||||
}
|
||||
|
||||
// First check to see if we can record an exposure event
|
||||
if (method === "abandonment" || method === "engagement") {
|
||||
if (this.#exposureResultTypes.size) {
|
||||
let exposure = {
|
||||
results: [...this.#exposureResultTypes].sort().join(","),
|
||||
};
|
||||
this._controller.logger.debug(
|
||||
`exposure event: ${JSON.stringify(exposure)}`
|
||||
);
|
||||
Glean.urlbar.exposure.record(exposure);
|
||||
}
|
||||
|
||||
// reset the provider list on the controller
|
||||
this.#exposureResultTypes.clear();
|
||||
this.#tentativeExposureResultTypes.clear();
|
||||
}
|
||||
|
||||
this._controller.logger.info(
|
||||
`${method} event: ${JSON.stringify(eventInfo)}`
|
||||
);
|
||||
|
@ -1129,6 +1128,38 @@ class TelemetryEvent {
|
|||
Glean.urlbar[method].record(eventInfo);
|
||||
}
|
||||
|
||||
#recordEndOfSessionTelemetry(searchString) {
|
||||
// exposures
|
||||
if (this.#exposureResultTypes.size) {
|
||||
let exposure = {
|
||||
results: [...this.#exposureResultTypes].sort().join(","),
|
||||
};
|
||||
this._controller.logger.debug(
|
||||
`exposure event: ${JSON.stringify(exposure)}`
|
||||
);
|
||||
Glean.urlbar.exposure.record(exposure);
|
||||
this.#exposureResultTypes.clear();
|
||||
}
|
||||
this.#tentativeExposureResultTypes.clear();
|
||||
|
||||
// potential exposures
|
||||
if (this.#potentialExposureKeywords.size) {
|
||||
let normalizedSearchString = searchString.trim().toLowerCase();
|
||||
for (let keyword of this.#potentialExposureKeywords) {
|
||||
let data = {
|
||||
keyword,
|
||||
terminal: keyword == normalizedSearchString,
|
||||
};
|
||||
this._controller.logger.debug(
|
||||
`potential_exposure event: ${JSON.stringify(data)}`
|
||||
);
|
||||
Glean.urlbar.potentialExposure.record(data);
|
||||
}
|
||||
GleanPings.urlbarPotentialExposure.submit();
|
||||
this.#potentialExposureKeywords.clear();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers an exposure for a result in the current urlbar session. All
|
||||
* exposures that are added during a session are recorded in an exposure event
|
||||
|
@ -1178,6 +1209,16 @@ class TelemetryEvent {
|
|||
this.#tentativeExposureResultTypes.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a potential exposure in the current urlbar session.
|
||||
*
|
||||
* @param {string} keyword
|
||||
* The keyword that was matched.
|
||||
*/
|
||||
addPotentialExposure(keyword) {
|
||||
this.#potentialExposureKeywords.add(keyword);
|
||||
}
|
||||
|
||||
#getInteractionType(
|
||||
method,
|
||||
startEventInfo,
|
||||
|
@ -1371,4 +1412,5 @@ class TelemetryEvent {
|
|||
|
||||
#exposureResultTypes = new Set();
|
||||
#tentativeExposureResultTypes = new Set();
|
||||
#potentialExposureKeywords = new Set();
|
||||
}
|
||||
|
|
|
@ -1506,12 +1506,28 @@ class Preferences {
|
|||
return this.shouldHandOffToSearchModePrefs.some(
|
||||
prefName => !this.get(prefName)
|
||||
);
|
||||
case "autoFillAdaptiveHistoryUseCountThreshold":
|
||||
case "autoFillAdaptiveHistoryUseCountThreshold": {
|
||||
const nimbusValue =
|
||||
this._nimbus.autoFillAdaptiveHistoryUseCountThreshold;
|
||||
return nimbusValue === undefined
|
||||
? this.get("autoFill.adaptiveHistory.useCountThreshold")
|
||||
: parseFloat(nimbusValue);
|
||||
}
|
||||
case "potentialExposureKeywords": {
|
||||
// Get the keywords array from Nimbus or prefs and convert it to a Set.
|
||||
// If the value comes from Nimbus, it will already be an array. If it
|
||||
// comes from prefs, it should be a stringified array.
|
||||
let value = this._readPref(pref);
|
||||
if (typeof value == "string") {
|
||||
try {
|
||||
value = JSON.parse(value);
|
||||
} catch (e) {}
|
||||
}
|
||||
if (!Array.isArray(value)) {
|
||||
value = null;
|
||||
}
|
||||
return new Set(value);
|
||||
}
|
||||
}
|
||||
return this._readPref(pref);
|
||||
}
|
||||
|
|
|
@ -49,7 +49,7 @@ class ProviderAboutPages extends UrlbarProvider {
|
|||
* @returns {boolean} Whether this provider should be invoked for the search.
|
||||
*/
|
||||
isActive(queryContext) {
|
||||
return queryContext.trimmedSearchString.toLowerCase().startsWith("about:");
|
||||
return queryContext.trimmedLowerCaseSearchString.startsWith("about:");
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -61,7 +61,7 @@ class ProviderAboutPages extends UrlbarProvider {
|
|||
* result. A UrlbarResult should be passed to it.
|
||||
*/
|
||||
startQuery(queryContext, addCallback) {
|
||||
let searchString = queryContext.trimmedSearchString.toLowerCase();
|
||||
let searchString = queryContext.trimmedLowerCaseSearchString;
|
||||
for (const aboutUrl of lazy.AboutPagesUtils.visibleAboutUrls) {
|
||||
if (aboutUrl.startsWith(searchString)) {
|
||||
let result = new lazy.UrlbarResult(
|
||||
|
|
|
@ -653,7 +653,7 @@ class ProviderAutofill extends UrlbarProvider {
|
|||
queryType: QUERYTYPE.AUTOFILL_ADAPTIVE,
|
||||
// `fullSearchString` is the value the user typed including a prefix if
|
||||
// they typed one. `searchString` has been stripped of the prefix.
|
||||
fullSearchString: queryContext.searchString.toLowerCase(),
|
||||
fullSearchString: queryContext.lowerCaseSearchString,
|
||||
searchString: this._searchString,
|
||||
strippedPrefix: this._strippedPrefix,
|
||||
useCountThreshold: lazy.UrlbarPrefs.get(
|
||||
|
|
|
@ -236,7 +236,7 @@ class ProviderInputHistory extends UrlbarProvider {
|
|||
SQL_ADAPTIVE_QUERY,
|
||||
{
|
||||
parent: lazy.PlacesUtils.tagsFolderId,
|
||||
search_string: queryContext.searchString.toLowerCase(),
|
||||
search_string: queryContext.lowerCaseSearchString,
|
||||
matchBehavior: Ci.mozIPlacesAutoComplete.MATCH_ANYWHERE,
|
||||
searchBehavior: lazy.UrlbarPrefs.get("defaultBehavior"),
|
||||
userContextId: lazy.UrlbarPrefs.get("switchTabs.searchAllContainers")
|
||||
|
|
|
@ -95,7 +95,7 @@ class ProviderQuickActions extends UrlbarProvider {
|
|||
*/
|
||||
async startQuery(queryContext, addCallback) {
|
||||
await lazy.QuickActionsLoaderDefault.ensureLoaded();
|
||||
let input = queryContext.trimmedSearchString.toLowerCase();
|
||||
let input = queryContext.trimmedLowerCaseSearchString;
|
||||
|
||||
if (
|
||||
!queryContext.searchMode &&
|
||||
|
|
|
@ -173,7 +173,7 @@ class ProviderTokenAliasEngines extends UrlbarProvider {
|
|||
}
|
||||
|
||||
async _getAutofillResult(queryContext) {
|
||||
let lowerCaseSearchString = queryContext.searchString.toLowerCase();
|
||||
let { lowerCaseSearchString } = queryContext;
|
||||
|
||||
// The user is typing a specific engine. We should show a heuristic result.
|
||||
for (let { engine, tokenAliases } of this._engines) {
|
||||
|
|
|
@ -115,7 +115,7 @@ class ProviderWeather extends UrlbarProvider {
|
|||
return false;
|
||||
}
|
||||
|
||||
return keywords.has(queryContext.searchString.trim().toLocaleLowerCase());
|
||||
return keywords.has(queryContext.trimmedLowerCaseSearchString);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -854,7 +854,7 @@ export var UrlbarUtils = {
|
|||
* @returns {string} The modified paste data.
|
||||
*/
|
||||
stripUnsafeProtocolOnPaste(pasteData) {
|
||||
while (true) {
|
||||
for (;;) {
|
||||
let scheme = "";
|
||||
try {
|
||||
scheme = Services.io.extractScheme(pasteData);
|
||||
|
@ -2175,6 +2175,8 @@ export class UrlbarQueryContext {
|
|||
this.pendingHeuristicProviders = new Set();
|
||||
this.deferUserSelectionProviders = new Set();
|
||||
this.trimmedSearchString = this.searchString.trim();
|
||||
this.lowerCaseSearchString = this.searchString.toLowerCase();
|
||||
this.trimmedLowerCaseSearchString = this.trimmedSearchString.toLowerCase();
|
||||
this.userContextId =
|
||||
lazy.UrlbarProviderOpenTabs.getUserContextIdForOpenPagesTable(
|
||||
options.userContextId,
|
||||
|
|
|
@ -158,6 +158,7 @@ urlbar:
|
|||
notification_emails:
|
||||
- fx-search-telemetry@mozilla.com
|
||||
expires: never
|
||||
|
||||
engagement:
|
||||
type: event
|
||||
description: Recorded when the user executes an action on a result.
|
||||
|
@ -429,6 +430,40 @@ urlbar:
|
|||
- fx-search-telemetry@mozilla.com
|
||||
expires: never
|
||||
|
||||
potential_exposure:
|
||||
type: event
|
||||
description: >
|
||||
This event is recorded in the `urlbar-potential-exposure` ping, which is
|
||||
submitted at the end of urlbar sessions during which the user typed a
|
||||
keyword defined by the Nimbus variable `potentialExposureKeywords`. A
|
||||
"session" begins when the user focuses the urlbar and ends with an
|
||||
engagement or abandonment. The ping will contain one event per unique
|
||||
keyword that is typed during the session. This ping is not submitted for
|
||||
sessions in private windows.
|
||||
extra_keys:
|
||||
keyword:
|
||||
type: string
|
||||
description: >
|
||||
The matched keyword.
|
||||
terminal:
|
||||
type: boolean
|
||||
description: >
|
||||
Whether the matched keyword was present at the end of the urlbar
|
||||
session. If true, the session ended with the keyword. If false, the
|
||||
keyword was typed at some point during the session but the session
|
||||
did not end with it.
|
||||
bugs:
|
||||
- https://bugzilla.mozilla.org/show_bug.cgi?id=1881875
|
||||
data_reviews:
|
||||
- https://bugzilla.mozilla.org/show_bug.cgi?id=1881875
|
||||
data_sensitivity:
|
||||
- stored_content
|
||||
notification_emails:
|
||||
- fx-search-telemetry@mozilla.com
|
||||
expires: never
|
||||
send_in_pings:
|
||||
- urlbar-potential-exposure
|
||||
|
||||
quick_suggest_contextual_opt_in:
|
||||
type: event
|
||||
description: >
|
||||
|
|
|
@ -19,3 +19,19 @@ quick-suggest:
|
|||
- https://bugzilla.mozilla.org/show_bug.cgi?id=1854755
|
||||
notification_emails:
|
||||
- najiang@mozilla.com
|
||||
|
||||
urlbar-potential-exposure:
|
||||
description: |
|
||||
This ping is submitted at the end of urlbar sessions during which the user
|
||||
typed a keyword defined by the Nimbus variable `potentialExposureKeywords`.
|
||||
A "session" begins when the user focuses the urlbar and ends with an
|
||||
engagement or abandonment. The ping will contain one
|
||||
`urlbar.potential_exposure` event per unique keyword that is typed during
|
||||
the session. This ping is not submitted for sessions in private windows.
|
||||
include_client_id: false
|
||||
bugs:
|
||||
- https://bugzilla.mozilla.org/show_bug.cgi?id=1881875
|
||||
data_reviews:
|
||||
- https://bugzilla.mozilla.org/show_bug.cgi?id=1881875
|
||||
notification_emails:
|
||||
- fx-search-telemetry@mozilla.com
|
||||
|
|
|
@ -402,9 +402,6 @@ https_first_disabled = true
|
|||
|
||||
["browser_revert.js"]
|
||||
|
||||
["browser_search_continuation.js"]
|
||||
support-files = ["search-engines", "../../../search/test/browser/trendingSuggestionEngine.sjs"]
|
||||
|
||||
["browser_searchFunction.js"]
|
||||
|
||||
["browser_searchHistoryLimit.js"]
|
||||
|
@ -494,6 +491,9 @@ support-files = [
|
|||
|
||||
["browser_search_bookmarks_from_bookmarks_menu.js"]
|
||||
|
||||
["browser_search_continuation.js"]
|
||||
support-files = ["search-engines", "../../../search/test/browser/trendingSuggestionEngine.sjs"]
|
||||
|
||||
["browser_search_history_from_history_panel.js"]
|
||||
|
||||
["browser_selectStaleResults.js"]
|
||||
|
|
|
@ -26,8 +26,6 @@ prefs = ["browser.bookmarks.testing.skipDefaultBookmarksImport=true"]
|
|||
|
||||
["browser_glean_telemetry_abandonment_n_chars_n_words.js"]
|
||||
|
||||
["browser_glean_telemetry_abandonment_type.js"]
|
||||
|
||||
["browser_glean_telemetry_abandonment_sap.js"]
|
||||
|
||||
["browser_glean_telemetry_abandonment_search_engine_default_id.js"]
|
||||
|
@ -36,6 +34,8 @@ prefs = ["browser.bookmarks.testing.skipDefaultBookmarksImport=true"]
|
|||
|
||||
["browser_glean_telemetry_abandonment_tips.js"]
|
||||
|
||||
["browser_glean_telemetry_abandonment_type.js"]
|
||||
|
||||
["browser_glean_telemetry_engagement_edge_cases.js"]
|
||||
|
||||
["browser_glean_telemetry_engagement_groups.js"]
|
||||
|
@ -66,6 +66,8 @@ skip-if = ["verify"] # Bug 1852375 - MerinoTestUtils.initWeather() doesn't play
|
|||
|
||||
["browser_glean_telemetry_exposure_edge_cases.js"]
|
||||
|
||||
["browser_glean_telemetry_potential_exposure.js"]
|
||||
|
||||
["browser_glean_telemetry_record_preferences.js"]
|
||||
|
||||
["browser_glean_telemetry_reenter.js"]
|
||||
|
|
|
@ -0,0 +1,438 @@
|
|||
/* 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/. */
|
||||
|
||||
// Tests the `urlbar-potential-exposure` ping.
|
||||
|
||||
const WAIT_FOR_PING_TIMEOUT_MS = 1000;
|
||||
|
||||
// Avoid timeouts in verify mode, especially on Mac.
|
||||
requestLongerTimeout(3);
|
||||
|
||||
add_setup(async function test_setup() {
|
||||
Services.fog.testResetFOG();
|
||||
|
||||
// Add a mock engine so we don't hit the network.
|
||||
await SearchTestUtils.installSearchExtension({}, { setAsDefault: true });
|
||||
|
||||
registerCleanupFunction(() => {
|
||||
Services.fog.testResetFOG();
|
||||
});
|
||||
});
|
||||
|
||||
add_task(async function oneKeyword_noMatch_1() {
|
||||
await doTest({
|
||||
keywords: ["example"],
|
||||
searchStrings: ["exam"],
|
||||
expectedEvents: [],
|
||||
});
|
||||
});
|
||||
|
||||
add_task(async function oneKeyword_noMatch_2() {
|
||||
await doTest({
|
||||
keywords: ["exam"],
|
||||
searchStrings: ["example"],
|
||||
expectedEvents: [],
|
||||
});
|
||||
});
|
||||
|
||||
add_task(async function oneKeyword_oneMatch_terminal_1() {
|
||||
await doTest({
|
||||
keywords: ["example"],
|
||||
searchStrings: ["example"],
|
||||
expectedEvents: [{ extra: { keyword: "example", terminal: true } }],
|
||||
});
|
||||
});
|
||||
|
||||
add_task(async function oneKeyword_oneMatch_terminal_2() {
|
||||
await doTest({
|
||||
keywords: ["example"],
|
||||
searchStrings: ["exam", "example"],
|
||||
expectedEvents: [{ extra: { keyword: "example", terminal: true } }],
|
||||
});
|
||||
});
|
||||
|
||||
add_task(async function oneKeyword_oneMatch_nonterminal_1() {
|
||||
await doTest({
|
||||
keywords: ["example"],
|
||||
searchStrings: ["example", "exam"],
|
||||
expectedEvents: [{ extra: { keyword: "example", terminal: false } }],
|
||||
});
|
||||
});
|
||||
|
||||
add_task(async function oneKeyword_oneMatch_nonterminal_2() {
|
||||
await doTest({
|
||||
keywords: ["example"],
|
||||
searchStrings: ["ex", "example", "exam"],
|
||||
expectedEvents: [{ extra: { keyword: "example", terminal: false } }],
|
||||
});
|
||||
});
|
||||
|
||||
add_task(async function oneKeyword_dupeMatches_terminal_1() {
|
||||
await doTest({
|
||||
keywords: ["example"],
|
||||
searchStrings: ["example", "example"],
|
||||
expectedEvents: [{ extra: { keyword: "example", terminal: true } }],
|
||||
});
|
||||
});
|
||||
|
||||
add_task(async function oneKeyword_dupeMatches_terminal_2() {
|
||||
await doTest({
|
||||
keywords: ["example"],
|
||||
searchStrings: ["example", "exampl", "example"],
|
||||
expectedEvents: [{ extra: { keyword: "example", terminal: true } }],
|
||||
});
|
||||
});
|
||||
|
||||
add_task(async function oneKeyword_dupeMatches_terminal_3() {
|
||||
await doTest({
|
||||
keywords: ["example"],
|
||||
searchStrings: ["exam", "example", "example"],
|
||||
expectedEvents: [{ extra: { keyword: "example", terminal: true } }],
|
||||
});
|
||||
});
|
||||
|
||||
add_task(async function oneKeyword_dupeMatches_terminal_4() {
|
||||
await doTest({
|
||||
keywords: ["example"],
|
||||
searchStrings: ["exam", "example", "exampl", "example"],
|
||||
expectedEvents: [{ extra: { keyword: "example", terminal: true } }],
|
||||
});
|
||||
});
|
||||
|
||||
add_task(async function oneKeyword_dupeMatches_nonterminal_1() {
|
||||
await doTest({
|
||||
keywords: ["example"],
|
||||
searchStrings: ["example", "example", "exampl"],
|
||||
expectedEvents: [{ extra: { keyword: "example", terminal: false } }],
|
||||
});
|
||||
});
|
||||
|
||||
add_task(async function oneKeyword_dupeMatches_nonterminal_2() {
|
||||
await doTest({
|
||||
keywords: ["example"],
|
||||
searchStrings: ["exam", "example", "example", "exampl"],
|
||||
expectedEvents: [{ extra: { keyword: "example", terminal: false } }],
|
||||
});
|
||||
});
|
||||
|
||||
add_task(async function oneKeyword_dupeMatches_nonterminal_3() {
|
||||
await doTest({
|
||||
keywords: ["example"],
|
||||
searchStrings: ["example", "exam", "example", "exampl"],
|
||||
expectedEvents: [{ extra: { keyword: "example", terminal: false } }],
|
||||
});
|
||||
});
|
||||
|
||||
add_task(async function oneKeyword_dupeMatches_nonterminal_4() {
|
||||
await doTest({
|
||||
keywords: ["example"],
|
||||
searchStrings: ["exam", "example", "exampl", "example", "exampl"],
|
||||
expectedEvents: [{ extra: { keyword: "example", terminal: false } }],
|
||||
});
|
||||
});
|
||||
|
||||
add_task(async function manyKeywords_noMatch() {
|
||||
await doTest({
|
||||
keywords: ["foo", "bar", "baz"],
|
||||
searchStrings: ["example"],
|
||||
expectedEvents: [],
|
||||
});
|
||||
});
|
||||
|
||||
add_task(async function manyKeywords_oneMatch_terminal_1() {
|
||||
await doTest({
|
||||
keywords: ["foo", "bar", "baz"],
|
||||
searchStrings: ["bar"],
|
||||
expectedEvents: [{ extra: { keyword: "bar", terminal: true } }],
|
||||
});
|
||||
});
|
||||
|
||||
add_task(async function manyKeywords_oneMatch_terminal_2() {
|
||||
await doTest({
|
||||
keywords: ["foo", "bar", "baz"],
|
||||
searchStrings: ["example", "bar"],
|
||||
expectedEvents: [{ extra: { keyword: "bar", terminal: true } }],
|
||||
});
|
||||
});
|
||||
|
||||
add_task(async function manyKeywords_oneMatch_nonterminal_1() {
|
||||
await doTest({
|
||||
keywords: ["foo", "bar", "baz"],
|
||||
searchStrings: ["bar", "example"],
|
||||
expectedEvents: [{ extra: { keyword: "bar", terminal: false } }],
|
||||
});
|
||||
});
|
||||
|
||||
add_task(async function manyKeywords_oneMatch_nonterminal_2() {
|
||||
await doTest({
|
||||
keywords: ["foo", "bar", "baz"],
|
||||
searchStrings: ["exam", "bar", "example"],
|
||||
expectedEvents: [{ extra: { keyword: "bar", terminal: false } }],
|
||||
});
|
||||
});
|
||||
|
||||
add_task(async function manyKeywords_manyMatches_terminal_1() {
|
||||
let keywords = ["foo", "bar", "baz"];
|
||||
await doTest({
|
||||
keywords,
|
||||
searchStrings: keywords,
|
||||
expectedEvents: keywords.map((keyword, i) => ({
|
||||
extra: { keyword, terminal: i == keywords.length - 1 },
|
||||
})),
|
||||
});
|
||||
});
|
||||
|
||||
add_task(async function manyKeywords_manyMatches_terminal_2() {
|
||||
let keywords = ["foo", "bar", "baz"];
|
||||
await doTest({
|
||||
keywords,
|
||||
searchStrings: ["exam", "foo", "exampl", "bar", "example", "baz"],
|
||||
expectedEvents: keywords.map((keyword, i) => ({
|
||||
extra: { keyword, terminal: i == keywords.length - 1 },
|
||||
})),
|
||||
});
|
||||
});
|
||||
|
||||
add_task(async function manyKeywords_manyMatches_nonterminal_1() {
|
||||
let keywords = ["foo", "bar", "baz"];
|
||||
await doTest({
|
||||
keywords,
|
||||
searchStrings: ["foo", "bar", "baz", "example"],
|
||||
expectedEvents: keywords.map(keyword => ({
|
||||
extra: { keyword, terminal: false },
|
||||
})),
|
||||
});
|
||||
});
|
||||
|
||||
add_task(async function manyKeywords_manyMatches_nonterminal_2() {
|
||||
let keywords = ["foo", "bar", "baz"];
|
||||
await doTest({
|
||||
keywords,
|
||||
searchStrings: ["exam", "foo", "exampl", "bar", "example", "baz", "exam"],
|
||||
expectedEvents: keywords.map(keyword => ({
|
||||
extra: { keyword, terminal: false },
|
||||
})),
|
||||
});
|
||||
});
|
||||
|
||||
add_task(async function manyKeywords_dupeMatches_terminal() {
|
||||
let keywords = ["foo", "bar", "baz"];
|
||||
let searchStrings = [...keywords, ...keywords];
|
||||
await doTest({
|
||||
keywords,
|
||||
searchStrings,
|
||||
expectedEvents: keywords.map((keyword, i) => ({
|
||||
extra: { keyword, terminal: i == keywords.length - 1 },
|
||||
})),
|
||||
});
|
||||
});
|
||||
|
||||
add_task(async function manyKeywords_dupeMatches_nonterminal() {
|
||||
let keywords = ["foo", "bar", "baz"];
|
||||
let searchStrings = [...keywords, ...keywords, "example"];
|
||||
await doTest({
|
||||
keywords,
|
||||
searchStrings,
|
||||
expectedEvents: keywords.map(keyword => ({
|
||||
extra: { keyword, terminal: false },
|
||||
})),
|
||||
});
|
||||
});
|
||||
|
||||
add_task(async function searchStringNormalization_terminal() {
|
||||
await doTest({
|
||||
keywords: ["example"],
|
||||
searchStrings: [" ExaMPLe "],
|
||||
expectedEvents: [{ extra: { keyword: "example", terminal: true } }],
|
||||
});
|
||||
});
|
||||
|
||||
add_task(async function searchStringNormalization_nonterminal() {
|
||||
await doTest({
|
||||
keywords: ["example"],
|
||||
searchStrings: [" ExaMPLe ", "foo"],
|
||||
expectedEvents: [{ extra: { keyword: "example", terminal: false } }],
|
||||
});
|
||||
});
|
||||
|
||||
add_task(async function multiWordKeyword() {
|
||||
await doTest({
|
||||
keywords: ["this has multiple words"],
|
||||
searchStrings: ["this has multiple words"],
|
||||
expectedEvents: [
|
||||
{ extra: { keyword: "this has multiple words", terminal: true } },
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
// Smoke test that ends a session with an engagement instead of an abandonment
|
||||
// as other tasks in this file do.
|
||||
add_task(async function engagement() {
|
||||
await BrowserTestUtils.withNewTab("about:blank", async () => {
|
||||
await doTest({
|
||||
keywords: ["example"],
|
||||
searchStrings: ["example"],
|
||||
endSession: () =>
|
||||
// Hit the Enter key on the heuristic search result.
|
||||
UrlbarTestUtils.promisePopupClose(window, () =>
|
||||
EventUtils.synthesizeKey("KEY_Enter")
|
||||
),
|
||||
expectedEvents: [{ extra: { keyword: "example", terminal: true } }],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Smoke test that uses Nimbus to set keywords instead of a pref as other tasks
|
||||
// in this file do.
|
||||
add_task(async function nimbus() {
|
||||
let keywords = ["foo", "bar", "baz"];
|
||||
await doTest({
|
||||
useNimbus: true,
|
||||
keywords,
|
||||
searchStrings: keywords,
|
||||
expectedEvents: keywords.map((keyword, i) => ({
|
||||
extra: { keyword, terminal: i == keywords.length - 1 },
|
||||
})),
|
||||
});
|
||||
});
|
||||
|
||||
// The ping should not be submitted for sessions in private windows.
|
||||
add_task(async function privateWindow() {
|
||||
let privateWin = await BrowserTestUtils.openNewBrowserWindow({
|
||||
private: true,
|
||||
});
|
||||
await doTest({
|
||||
win: privateWin,
|
||||
keywords: ["example"],
|
||||
searchStrings: ["example"],
|
||||
expectedEvents: [],
|
||||
});
|
||||
await BrowserTestUtils.closeWindow(privateWin);
|
||||
});
|
||||
|
||||
add_task(async function invalidPotentialExposureKeywords_pref() {
|
||||
await doTest({
|
||||
keywords: "not an array of keywords",
|
||||
searchStrings: ["example", "not an array of keywords"],
|
||||
expectedEvents: [],
|
||||
});
|
||||
});
|
||||
|
||||
add_task(async function invalidPotentialExposureKeywords_nimbus() {
|
||||
await doTest({
|
||||
useNimbus: true,
|
||||
keywords: "not an array of keywords",
|
||||
searchStrings: ["example", "not an array of keywords"],
|
||||
expectedEvents: [],
|
||||
});
|
||||
});
|
||||
|
||||
async function doTest({
|
||||
keywords,
|
||||
searchStrings,
|
||||
expectedEvents,
|
||||
endSession = null,
|
||||
useNimbus = false,
|
||||
win = window,
|
||||
}) {
|
||||
endSession ||= () =>
|
||||
UrlbarTestUtils.promisePopupClose(win, () => win.gURLBar.blur());
|
||||
|
||||
let nimbusCleanup;
|
||||
let keywordsJson = JSON.stringify(keywords);
|
||||
if (useNimbus) {
|
||||
nimbusCleanup = await UrlbarTestUtils.initNimbusFeature({
|
||||
potentialExposureKeywords: keywordsJson,
|
||||
});
|
||||
} else {
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
set: [["browser.urlbar.potentialExposureKeywords", keywordsJson]],
|
||||
});
|
||||
}
|
||||
|
||||
let pingPromise = waitForPing();
|
||||
|
||||
for (let value of searchStrings) {
|
||||
await UrlbarTestUtils.promiseAutocompleteResultPopup({
|
||||
value,
|
||||
window: win,
|
||||
});
|
||||
}
|
||||
await endSession();
|
||||
|
||||
// Wait `WAIT_FOR_PING_TIMEOUT_MS` for the ping to be submitted before
|
||||
// reporting a timeout. Note that some tasks do not expect a ping to be
|
||||
// submitted, and they rely on this timeout behavior.
|
||||
info("Awaiting ping promise");
|
||||
let events = null;
|
||||
events = await Promise.race([
|
||||
pingPromise,
|
||||
new Promise(resolve =>
|
||||
// eslint-disable-next-line mozilla/no-arbitrary-setTimeout
|
||||
setTimeout(() => {
|
||||
if (!events) {
|
||||
info("Timed out waiting for ping");
|
||||
}
|
||||
resolve([]);
|
||||
}, WAIT_FOR_PING_TIMEOUT_MS)
|
||||
),
|
||||
]);
|
||||
|
||||
assertEvents(events, expectedEvents);
|
||||
|
||||
if (nimbusCleanup) {
|
||||
await nimbusCleanup();
|
||||
} else {
|
||||
await SpecialPowers.popPrefEnv();
|
||||
}
|
||||
Services.fog.testResetFOG();
|
||||
}
|
||||
|
||||
function waitForPing() {
|
||||
return new Promise(resolve => {
|
||||
GleanPings.urlbarPotentialExposure.testBeforeNextSubmit(() => {
|
||||
let events = Glean.urlbar.potentialExposure.testGetValue();
|
||||
info("testBeforeNextSubmit got events: " + JSON.stringify(events));
|
||||
resolve(events);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function assertEvents(actual, expected) {
|
||||
info("Comparing events: " + JSON.stringify({ actual, expected }));
|
||||
|
||||
// Add some expected boilerplate properties to the expected events so that
|
||||
// callers don't have to but so that we still check them.
|
||||
expected = expected.map(e => ({
|
||||
category: "urlbar",
|
||||
name: "potential_exposure",
|
||||
// `testGetValue()` stringifies booleans for some reason. Let callers
|
||||
// specify booleans since booleans are correct, and stringify them here.
|
||||
...stringifyBooleans(e),
|
||||
}));
|
||||
|
||||
// Filter out properties from the actual events that aren't defined in the
|
||||
// expected events. Ignore unimportant properties like timestamps.
|
||||
actual = actual.map((a, i) =>
|
||||
Object.fromEntries(
|
||||
Object.entries(a).filter(([key]) => expected[i]?.hasOwnProperty(key))
|
||||
)
|
||||
);
|
||||
|
||||
Assert.deepEqual(actual, expected, "Checking expected Glean events");
|
||||
}
|
||||
|
||||
function stringifyBooleans(obj) {
|
||||
let newObj = {};
|
||||
for (let [key, value] of Object.entries(obj)) {
|
||||
if (value && typeof value == "object") {
|
||||
newObj[key] = stringifyBooleans(value);
|
||||
} else if (typeof value == "boolean") {
|
||||
newObj[key] = String(value);
|
||||
} else {
|
||||
newObj[key] = value;
|
||||
}
|
||||
}
|
||||
return newObj;
|
||||
}
|
|
@ -264,6 +264,13 @@ urlbar:
|
|||
will be able to click the "Show less frequently" command for Pocket
|
||||
suggestions. If undefined or zero, the user will be able to click the
|
||||
command without any limit.
|
||||
potentialExposureKeywords:
|
||||
type: json
|
||||
fallbackPref: browser.urlbar.potentialExposureKeywords
|
||||
description: >-
|
||||
An array of keyword strings that will trigger the
|
||||
`urlbar-potential-exposure` ping when the user types one during a urlbar
|
||||
session.
|
||||
quickSuggestAllowPositionInSuggestions:
|
||||
type: boolean
|
||||
fallbackPref: browser.urlbar.quicksuggest.allowPositionInSuggestions
|
||||
|
|
Загрузка…
Ссылка в новой задаче