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:
@@ -55,7 +55,7 @@ class BrowserSearchTelemetryHandler {
|
|||||||
* Determines if we should record a search for this browser instance.
|
* Determines if we should record a search for this browser instance.
|
||||||
* Private Browsing mode is normally skipped.
|
* Private Browsing mode is normally skipped.
|
||||||
*
|
*
|
||||||
* @param {browser} browser
|
* @param {XULBrowserElement} browser
|
||||||
* The browser where the search was loaded.
|
* The browser where the search was loaded.
|
||||||
* @returns {boolean}
|
* @returns {boolean}
|
||||||
* True if the search should be recorded, false otherwise.
|
* 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
|
* Telemetry records only search counts per engine and action origin, but
|
||||||
* nothing pertaining to the search contents themselves.
|
* nothing pertaining to the search contents themselves.
|
||||||
*
|
*
|
||||||
* @param {browser} browser
|
* @param {XULBrowserElement} browser
|
||||||
* The browser where the search originated.
|
* The browser where the search originated.
|
||||||
* @param {nsISearchEngine} engine
|
* @param {nsISearchEngine} engine
|
||||||
* The engine handling the search.
|
* 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
|
* This function handles the "urlbar", "urlbar-oneoff", "searchbar" and
|
||||||
* "searchbar-oneoff" sources.
|
* "searchbar-oneoff" sources.
|
||||||
*
|
*
|
||||||
* @param {browser} browser
|
* @param {XULBrowserElement} browser
|
||||||
* The browser where the search originated.
|
* The browser where the search originated.
|
||||||
* @param {nsISearchEngine} engine
|
* @param {nsISearchEngine} engine
|
||||||
* The engine handling the search.
|
* The engine handling the search.
|
||||||
|
|||||||
@@ -519,6 +519,7 @@
|
|||||||
this._needBrowserFocusAtEnterKeyUp = true;
|
this._needBrowserFocusAtEnterKeyUp = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
lazy.BrowserSearchTelemetry.recordSearchForm(engine, "searchbar");
|
||||||
openTrustedLinkIn(searchForm, where, params);
|
openTrustedLinkIn(searchForm, where, params);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -217,6 +217,34 @@ sap:
|
|||||||
expires: never
|
expires: never
|
||||||
telemetry_mirror: h#SEARCH_COUNTS
|
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:
|
serp:
|
||||||
impression:
|
impression:
|
||||||
type: event
|
type: event
|
||||||
|
|||||||
@@ -184,6 +184,8 @@ support-files = [
|
|||||||
"telemetrySearchSuggestions.xml",
|
"telemetrySearchSuggestions.xml",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
["browser_search_telemetry_searchform.js"]
|
||||||
|
|
||||||
["browser_search_telemetry_shopping.js"]
|
["browser_search_telemetry_shopping.js"]
|
||||||
support-files = ["searchTelemetryAd_shopping.html"]
|
support-files = ["searchTelemetryAd_shopping.html"]
|
||||||
|
|
||||||
|
|||||||
@@ -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();
|
||||||
|
});
|
||||||
@@ -1867,8 +1867,10 @@ export class UrlbarInput {
|
|||||||
null,
|
null,
|
||||||
"search-mode-switcher"
|
"search-mode-switcher"
|
||||||
).uri.spec;
|
).uri.spec;
|
||||||
|
// TODO: record SAP telemetry, see Bug 1961789.
|
||||||
} else {
|
} else {
|
||||||
url = searchEngine.searchForm;
|
url = searchEngine.searchForm;
|
||||||
|
lazy.BrowserSearchTelemetry.recordSearchForm(searchEngine, "urlbar");
|
||||||
}
|
}
|
||||||
|
|
||||||
this._lastSearchString = "";
|
this._lastSearchString = "";
|
||||||
|
|||||||
8
tools/@types/lib.gecko.glean.d.ts
vendored
8
tools/@types/lib.gecko.glean.d.ts
vendored
@@ -908,6 +908,7 @@ interface GleanImpl {
|
|||||||
sap: {
|
sap: {
|
||||||
counts: GleanEvent;
|
counts: GleanEvent;
|
||||||
deprecatedCounts: Record<string, GleanCounter>;
|
deprecatedCounts: Record<string, GleanCounter>;
|
||||||
|
searchFormCounts: GleanEvent;
|
||||||
}
|
}
|
||||||
|
|
||||||
serp: {
|
serp: {
|
||||||
@@ -1176,6 +1177,7 @@ interface GleanImpl {
|
|||||||
tabgroup: {
|
tabgroup: {
|
||||||
createGroup: GleanEvent;
|
createGroup: GleanEvent;
|
||||||
reopen: GleanEvent;
|
reopen: GleanEvent;
|
||||||
|
addTab: GleanEvent;
|
||||||
activeGroups: Record<string, GleanQuantity>;
|
activeGroups: Record<string, GleanQuantity>;
|
||||||
tabsPerActiveGroup: Record<string, GleanQuantity>;
|
tabsPerActiveGroup: Record<string, GleanQuantity>;
|
||||||
tabCountInGroups: Record<string, GleanQuantity>;
|
tabCountInGroups: Record<string, GleanQuantity>;
|
||||||
@@ -5237,6 +5239,11 @@ interface GleanImpl {
|
|||||||
intervalHours: GleanTimingDistribution;
|
intervalHours: GleanTimingDistribution;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
hangs: {
|
||||||
|
reports: GleanObject;
|
||||||
|
modules: GleanObject;
|
||||||
|
}
|
||||||
|
|
||||||
backgroundTasksRmdirBase: {
|
backgroundTasksRmdirBase: {
|
||||||
metricBase: GleanEvent;
|
metricBase: GleanEvent;
|
||||||
elapsedMs: GleanQuantity;
|
elapsedMs: GleanQuantity;
|
||||||
@@ -6968,6 +6975,7 @@ interface GleanPingsImpl {
|
|||||||
useCounters: nsIGleanPingWithReason<"app_shutdown_confirmed"|"idle_startup">;
|
useCounters: nsIGleanPingWithReason<"app_shutdown_confirmed"|"idle_startup">;
|
||||||
fxAccounts: nsIGleanPingWithReason<"active"|"dirty_startup"|"inactive">;
|
fxAccounts: nsIGleanPingWithReason<"active"|"dirty_startup"|"inactive">;
|
||||||
bounceTrackingProtection: nsIGleanPingNoReason;
|
bounceTrackingProtection: nsIGleanPingNoReason;
|
||||||
|
hangReport: nsIGleanPingNoReason;
|
||||||
backgroundTasks: nsIGleanPingNoReason;
|
backgroundTasks: nsIGleanPingNoReason;
|
||||||
captchaDetection: nsIGleanPingNoReason;
|
captchaDetection: nsIGleanPingNoReason;
|
||||||
crash: nsIGleanPingWithReason<"crash"|"event_found">;
|
crash: nsIGleanPingWithReason<"crash"|"event_found">;
|
||||||
|
|||||||
Reference in New Issue
Block a user