Bug 1915252 - Add telemetry for use of shift-click/enter in the search bar & address bar. r=urlbar-reviewers,scunnane,daisuke

Differential Revision: https://phabricator.services.mozilla.com/D245704
This commit is contained in:
Moritz Beier
2025-04-22 16:57:19 +00:00
parent 0817f89625
commit 5d41f55f7f
7 changed files with 224 additions and 3 deletions

View File

@@ -55,7 +55,7 @@ class BrowserSearchTelemetryHandler {
* Determines if we should record a search for this browser instance.
* Private Browsing mode is normally skipped.
*
* @param {browser} browser
* @param {XULBrowserElement} browser
* The browser where the search was loaded.
* @returns {boolean}
* True if the search should be recorded, false otherwise.
@@ -127,7 +127,7 @@ class BrowserSearchTelemetryHandler {
* Telemetry records only search counts per engine and action origin, but
* nothing pertaining to the search contents themselves.
*
* @param {browser} browser
* @param {XULBrowserElement} browser
* The browser where the search originated.
* @param {nsISearchEngine} engine
* The engine handling the search.
@@ -232,11 +232,27 @@ class BrowserSearchTelemetryHandler {
}
}
/**
* Records visits to a search engine's search form.
*
* @param {nsISearchEngine} engine
* The engine whose search form is being visited.
* @param {string} source
* Where the search form was opened from.
* This can be "urlbar" or "searchbar".
*/
recordSearchForm(engine, source) {
Glean.sap.searchFormCounts.record({
source,
provider_id: engine.isAppProvided ? engine.id : "other",
});
}
/**
* This function handles the "urlbar", "urlbar-oneoff", "searchbar" and
* "searchbar-oneoff" sources.
*
* @param {browser} browser
* @param {XULBrowserElement} browser
* The browser where the search originated.
* @param {nsISearchEngine} engine
* The engine handling the search.

View File

@@ -519,6 +519,7 @@
this._needBrowserFocusAtEnterKeyUp = true;
}
lazy.BrowserSearchTelemetry.recordSearchForm(engine, "searchbar");
openTrustedLinkIn(searchForm, where, params);
}

View File

@@ -217,6 +217,34 @@ sap:
expires: never
telemetry_mirror: h#SEARCH_COUNTS
search_form_counts:
type: event
description: >
Records how often users visit the homepages of search engines (also
known as SearchForm) using a SAP. It only counts how many visits were
initiated, not how many homepages were loaded successfully.
bugs:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1915252
data_reviews:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1915252
data_sensitivity:
- interaction
notification_emails:
- fx-search-telemetry@mozilla.com
expires: never
extra_keys:
source:
description: >
The search access point from which the user initiated the search.
Possible values are `searchbar` and `urlbar`.
type: string
provider_id:
description: >
The identifier of the search engine provider that is being used for the
search. Note this may be different to the identifier of the provider in
SERP reports. For user installed engines, this will be the string `other`.
type: string
serp:
impression:
type: event

View File

@@ -184,6 +184,8 @@ support-files = [
"telemetrySearchSuggestions.xml",
]
["browser_search_telemetry_searchform.js"]
["browser_search_telemetry_shopping.js"]
support-files = ["searchTelemetryAd_shopping.html"]

View File

@@ -0,0 +1,164 @@
/* Any copyright is dedicated to the Public Domain.
https://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
const CONFIG = [
{
identifier: "defaultEngine",
},
{
identifier: "second_engine",
base: {
name: "Second Engine",
urls: {
search_form: {
base: "https://www.example.com/searchform",
},
},
},
},
];
const TEST_ENGINE_BASENAME = "testEngine.xml";
const TEST_ENGINE_NAME = "Foo";
function findOneOff(engineName) {
let oneOffButtons = document.getElementById("PopupSearchAutoComplete")
.oneOffButtons.buttons;
let oneOffChildren = [...oneOffButtons.children];
let oneOffButton = oneOffChildren.find(
node => node.engine?.name == engineName
);
Assert.notEqual(
oneOffButton,
undefined,
`One-off for ${engineName} should exist`
);
return oneOffButton;
}
async function openSearchbarPopup(searchBarValue) {
let searchBar = document.getElementById("searchbar");
searchBar.focus();
searchBar.value = searchBarValue;
if (searchBar.textbox.popupOpen) {
info("searchPanel is already open");
return;
}
let searchPopup = document.getElementById("PopupSearchAutoComplete");
let shownPromise = BrowserTestUtils.waitForEvent(searchPopup, "popupshown");
let searchIcon = searchBar.querySelector(".searchbar-search-button");
let oneOffInstance = searchPopup.oneOffButtons;
let builtPromise = BrowserTestUtils.waitForEvent(oneOffInstance, "rebuild");
info("Opening search panel");
EventUtils.synthesizeMouseAtCenter(searchIcon, {});
await Promise.all([shownPromise, builtPromise]);
}
add_setup(async function () {
await SearchTestUtils.updateRemoteSettingsConfig(CONFIG);
await SearchTestUtils.installOpenSearchEngine({
url: getRootDirectory(gTestPath) + "../" + TEST_ENGINE_BASENAME,
});
await gCUITestUtils.addSearchBar();
registerCleanupFunction(async () => {
gCUITestUtils.removeSearchBar();
resetTelemetry();
});
});
add_task(async function test_appProvidedSearchbar() {
await openSearchbarPopup("");
let oneOff = findOneOff("Second Engine");
EventUtils.synthesizeMouseAtCenter(oneOff, { shiftKey: true });
await BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
let events = Glean.sap.searchFormCounts.testGetValue();
Assert.equal(events.length, 1, "Event was recorded.");
Assert.equal(events[0].extra.source, "searchbar", "Source is correct");
Assert.equal(events[0].extra.provider_id, "second_engine", "Id is correct");
resetTelemetry();
});
add_task(async function test_extensionSearchbar() {
await openSearchbarPopup("");
let oneOff = findOneOff(TEST_ENGINE_NAME);
EventUtils.synthesizeMouseAtCenter(oneOff, { shiftKey: true });
await BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
let events = Glean.sap.searchFormCounts.testGetValue();
Assert.equal(events.length, 1, "Event was recorded");
Assert.equal(events[0].extra.source, "searchbar", "Source is correct");
Assert.equal(events[0].extra.provider_id, "other", "Id is correct");
resetTelemetry();
});
add_task(async function test_actualSearchSearchbar() {
// Enter something in the searchbar to start an actual search.
await openSearchbarPopup("foo");
let oneOff = findOneOff("Second Engine");
EventUtils.synthesizeMouseAtCenter(oneOff, { shiftKey: true });
await BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
let events = Glean.sap.searchFormCounts.testGetValue();
// Since we used the one off button to search (not to open the search form),
// no event should be recorded in `sap.searchFormCounts`.
Assert.equal(events, null, "No search form event is recorded for searches");
resetTelemetry();
});
add_task(async function test_appProvidedUrlbar() {
let popup = await UrlbarTestUtils.openSearchModeSwitcher(window);
info("Choose Second Engine in the unified search button popup.");
let item = popup.querySelector('menuitem[label="Second Engine"]');
let popupHidden = UrlbarTestUtils.searchModeSwitcherPopupClosed(window);
EventUtils.synthesizeMouseAtCenter(item, { shiftKey: true });
await popupHidden;
await BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
let events = Glean.sap.searchFormCounts.testGetValue();
Assert.equal(events.length, 1, "Event was recorded");
Assert.equal(events[0].extra.source, "urlbar", "Source is correct");
Assert.equal(events[0].extra.provider_id, "second_engine", "Id is correct");
resetTelemetry();
});
add_task(async function test_extensionUrlbar() {
let popup = await UrlbarTestUtils.openSearchModeSwitcher(window);
info("Choose extension engine in the unified search button popup.");
let item = popup.querySelector(`menuitem[label="${TEST_ENGINE_NAME}"]`);
let popupHidden = UrlbarTestUtils.searchModeSwitcherPopupClosed(window);
EventUtils.synthesizeMouseAtCenter(item, { shiftKey: true });
await popupHidden;
await BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
let events = Glean.sap.searchFormCounts.testGetValue();
Assert.equal(events.length, 1, "Event was recorded");
Assert.equal(events[0].extra.source, "urlbar", "Source is correct");
Assert.equal(events[0].extra.provider_id, "other");
resetTelemetry();
});
add_task(async function test_actualSearchUrlbar() {
await UrlbarTestUtils.promiseAutocompleteResultPopup({
window,
value: "foo",
});
let popup = await UrlbarTestUtils.openSearchModeSwitcher(window);
info("Shift-click Second Engine in the unified search button popup.");
let item = popup.querySelector('menuitem[label="Second Engine"]');
let popupHidden = UrlbarTestUtils.searchModeSwitcherPopupClosed(window);
EventUtils.synthesizeMouseAtCenter(item, { shiftKey: true });
await popupHidden;
await BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
let events = Glean.sap.searchFormCounts.testGetValue();
// Since we used the unified search button to search (not to open the search
// form), no event should be recorded in `sap.searchFormCounts`.
Assert.equal(events, null, "No search form event was recorded");
resetTelemetry();
});

View File

@@ -1867,8 +1867,10 @@ export class UrlbarInput {
null,
"search-mode-switcher"
).uri.spec;
// TODO: record SAP telemetry, see Bug 1961789.
} else {
url = searchEngine.searchForm;
lazy.BrowserSearchTelemetry.recordSearchForm(searchEngine, "urlbar");
}
this._lastSearchString = "";

View File

@@ -908,6 +908,7 @@ interface GleanImpl {
sap: {
counts: GleanEvent;
deprecatedCounts: Record<string, GleanCounter>;
searchFormCounts: GleanEvent;
}
serp: {
@@ -1176,6 +1177,7 @@ interface GleanImpl {
tabgroup: {
createGroup: GleanEvent;
reopen: GleanEvent;
addTab: GleanEvent;
activeGroups: Record<string, GleanQuantity>;
tabsPerActiveGroup: Record<string, GleanQuantity>;
tabCountInGroups: Record<string, GleanQuantity>;
@@ -5237,6 +5239,11 @@ interface GleanImpl {
intervalHours: GleanTimingDistribution;
}
hangs: {
reports: GleanObject;
modules: GleanObject;
}
backgroundTasksRmdirBase: {
metricBase: GleanEvent;
elapsedMs: GleanQuantity;
@@ -6968,6 +6975,7 @@ interface GleanPingsImpl {
useCounters: nsIGleanPingWithReason<"app_shutdown_confirmed"|"idle_startup">;
fxAccounts: nsIGleanPingWithReason<"active"|"dirty_startup"|"inactive">;
bounceTrackingProtection: nsIGleanPingNoReason;
hangReport: nsIGleanPingNoReason;
backgroundTasks: nsIGleanPingNoReason;
captchaDetection: nsIGleanPingNoReason;
crash: nsIGleanPingWithReason<"crash"|"event_found">;