fune/browser/components/urlbar/tests/browser-tips/browser_picks.js
Drew Willcoxon a3b0a1fd4c Bug 1827762 - Replace UrlbarProvider.pickResult() and blockResult() with onEngagement() r=mak
This removes `UrlbarProvider.pickResult()` and `blockResult()` in favor of
handling picks and dismissals through `onEngagement()`. A number of providers
use those two methods, so this revision touches a lot of files.

Handling dismissals through `onEngagement()` means `UrlbarInput.pickResult()`
can no longer tell whether a result is successfully dismissed, so it can't
remove the result anymore. (Maybe `onEngagement()` could return some value
indicating it dismissed the result, but I don't want to go down that road.)
Instead, I split `UrlbarController.handleDeleteEntry()` into two methods: a
public one that removes the result and notifies listeners, and a private one
that handles dismissing the selected result internally in
UrlbarController. Providers that have dismissable results should now implement
`onEngagement()` and call `controller.removeResult()`.

I made some other improvements to engagement handling. There's still room for
more but this patch is big enough already.

Other notable changes:

Include the engaged result in engagement notifications so providers have easy
access to it and can respond to clicks and dismissals more easily. That also
lets us stop passing `selIndex` and `provider` to `engagementEvent.record()`
since now it can compute those from the passed-in result.

Add the concept of `isSessionOngoing` to engagement notifications so providers
can tell whether an engagement ended the search session. Right now, providers
like quick suggest that record a bunch of provider-specific legacy telemetry
assume that `onEngagement()` ends the session, but that's no longer true.

Unify result buttons and result menu commands by setting
`element.dataset.command` on buttons (hopefully we can remove buttons soon, at
least the ones that aren't tip buttons)

Make sure we always notify providers on engagement even on dismissals or
when skipping legacy telemetry

Move dismissal of restyled search suggestions and history results from
`UrlbarController.handleDeleteEntry()` to the Places provider

Move dismissal of form history results from
`UrlbarController.handleDeleteEntry()` to the search suggestions provider

In the Places provider, remove the unused `_addSearchEngineMatch()` method. Also
remove the code in the "searchengine" case that creates a non-search-history
result. This code is unreached because the only time the provider creates a
"searchengine" match it also sets `isSearchHistory` to true.

In `UrlbarTestUtils.promiseAutocompleteResultPopup()`, change the default value
of the `fireInputEvent` param from false to true. This is necessary because
without a starting input event, the start event info in `engagementEvent` will
be null, so when `engagementEvent.record()` is called at the end of the
engagement, it will bail, and providers will not be notified of the engagement.
IMO true is a better default value anyway because input events will typically be
fired when the user performs a search.

Differential Revision: https://phabricator.services.mozilla.com/D174941
2023-04-13 06:03:33 +00:00

223 lines
6.5 KiB
JavaScript

/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
// Tests clicks and enter key presses on UrlbarUtils.RESULT_TYPE.TIP results.
"use strict";
const TIP_URL = "http://example.com/tip";
const HELP_URL = "http://example.com/help";
add_setup(async function() {
window.windowUtils.disableNonTestMouseEvents(true);
registerCleanupFunction(() => {
window.windowUtils.disableNonTestMouseEvents(false);
});
Services.telemetry.clearScalars();
Services.telemetry.clearEvents();
await SpecialPowers.pushPrefEnv({
set: [["browser.urlbar.eventTelemetry.enabled", true]],
});
});
add_task(async function enter_mainButton_url() {
await doTest({ click: false, buttonUrl: TIP_URL });
});
add_task(async function enter_mainButton_noURL() {
await doTest({ click: false });
});
add_task(async function enter_help() {
await doTest({ click: false, helpUrl: HELP_URL });
});
add_task(async function mouse_mainButton_url() {
await doTest({ click: true, buttonUrl: TIP_URL });
});
add_task(async function mouse_mainButton_noURL() {
await doTest({ click: true });
});
add_task(async function mouse_help() {
await doTest({ click: true, helpUrl: HELP_URL });
});
// Clicks inside a tip but not on any button.
add_task(async function mouse_insideTipButNotOnButtons() {
let results = [makeTipResult({ buttonUrl: TIP_URL, helpUrl: HELP_URL })];
let provider = new UrlbarTestUtils.TestProvider({ results, priority: 1 });
UrlbarProvidersManager.registerProvider(provider);
// Click inside the tip but outside the buttons. Nothing should happen. Make
// the result the heuristic to check that the selection on the main button
// isn't lost.
results[0].heuristic = true;
await UrlbarTestUtils.promiseAutocompleteResultPopup({
value: "test",
window,
fireInputEvent: true,
});
let row = await UrlbarTestUtils.waitForAutocompleteResultAt(window, 0);
Assert.equal(
UrlbarTestUtils.getSelectedElementIndex(window),
0,
"The main button's index should be selected initially"
);
Assert.equal(
UrlbarTestUtils.getSelectedElement(window),
row._buttons.get("0"),
"The main button element should be selected initially"
);
EventUtils.synthesizeMouseAtCenter(row, {});
// eslint-disable-next-line mozilla/no-arbitrary-setTimeout
await new Promise(r => setTimeout(r, 500));
Assert.ok(gURLBar.view.isOpen, "The view should remain open");
Assert.equal(
UrlbarTestUtils.getSelectedElementIndex(window),
0,
"The main button's index should remain selected"
);
Assert.equal(
UrlbarTestUtils.getSelectedElement(window),
row._buttons.get("0"),
"The main button element should remain selected"
);
await UrlbarTestUtils.promisePopupClose(window);
UrlbarProvidersManager.unregisterProvider(provider);
});
/**
* Runs this test's main checks.
*
* @param {object} options
* Options for the test.
* @param {boolean} options.click
* Pass true to trigger a click, false to trigger an enter key.
* @param {string} [options.buttonUrl]
* Pass a URL if picking the main button should open a URL. Pass nothing if
* a URL shouldn't be opened or if you want to pick the help button instead of
* the main button.
* @param {string} [options.helpUrl]
* Pass a URL if you want to pick the help button. Pass nothing if you want
* to pick the main button instead.
*/
async function doTest({ click, buttonUrl = undefined, helpUrl = undefined }) {
// Open a new tab for the test if we expect to load a URL.
let tab;
if (buttonUrl || helpUrl) {
tab = await BrowserTestUtils.openNewForegroundTab({
gBrowser,
url: "about:blank",
});
}
// Add our test provider.
let provider = new UrlbarTestUtils.TestProvider({
results: [makeTipResult({ buttonUrl, helpUrl })],
priority: 1,
});
UrlbarProvidersManager.registerProvider(provider);
let onEngagementPromise = new Promise(
resolve => (provider.onEngagement = resolve)
);
// Do a search to show our tip result.
await UrlbarTestUtils.promiseAutocompleteResultPopup({
value: "test",
window,
fireInputEvent: true,
});
let row = await UrlbarTestUtils.waitForAutocompleteResultAt(window, 0);
let mainButton = row._buttons.get("0");
let target = helpUrl
? row._buttons.get(UrlbarPrefs.get("resultMenu") ? "menu" : "help")
: mainButton;
// If we're picking the tip with the keyboard, TAB to select the proper
// target.
if (!click) {
EventUtils.synthesizeKey("KEY_Tab", { repeat: helpUrl ? 2 : 1 });
Assert.equal(
UrlbarTestUtils.getSelectedElement(window),
target,
`${target.className} should be selected.`
);
}
// Now pick the target and wait for provider.onEngagement to be called and
// the URL to load if necessary.
let loadPromise;
if (buttonUrl || helpUrl) {
loadPromise = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
}
await UrlbarTestUtils.promisePopupClose(window, () => {
if (helpUrl && UrlbarPrefs.get("resultMenu")) {
UrlbarTestUtils.openResultMenuAndPressAccesskey(window, "h", {
openByMouse: click,
resultIndex: 0,
});
} else if (click) {
EventUtils.synthesizeMouseAtCenter(target, {});
} else {
EventUtils.synthesizeKey("KEY_Enter");
}
});
await onEngagementPromise;
await loadPromise;
// Check telemetry.
let scalars = TelemetryTestUtils.getProcessScalars("parent", true, true);
TelemetryTestUtils.assertKeyedScalar(
scalars,
"urlbar.tips",
helpUrl ? "test-help" : "test-picked",
1
);
TelemetryTestUtils.assertEvents(
[
{
category: "urlbar",
method: "engagement",
object:
click && !(helpUrl && UrlbarPrefs.get("resultMenu"))
? "click"
: "enter",
value: "typed",
},
],
{ category: "urlbar" }
);
// Done.
UrlbarProvidersManager.unregisterProvider(provider);
if (tab) {
BrowserTestUtils.removeTab(tab);
}
}
function makeTipResult({ buttonUrl, helpUrl }) {
return new UrlbarResult(
UrlbarUtils.RESULT_TYPE.TIP,
UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
{
type: "test",
titleL10n: { id: "urlbar-search-tips-confirm" },
buttons: [
{
url: buttonUrl,
l10n: { id: "urlbar-search-tips-confirm" },
},
],
helpUrl,
helpL10n: {
id: UrlbarPrefs.get("resultMenu")
? "urlbar-result-menu-tip-get-help"
: "urlbar-tip-help-icon",
},
}
);
}