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:
Drew Willcoxon 2024-04-03 22:02:07 +00:00
Родитель 984109aa14
Коммит 7b341f3ffa
15 изменённых файлов: 590 добавлений и 32 удалений

Просмотреть файл

@ -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