Skip to content
This repository was archived by the owner on Jul 9, 2025. It is now read-only.

Commit 7135a4d

Browse files
committed
Bug 1942435 - Fix dismissals for Merino suggestions that aren't managed by a Suggest feature. r=daisuke
I don't think we have any tests specifically for Merino suggestions that don't correspond to Suggest features, i.e., suggestions with a Merino provider that is technically unrecognized by Firefox. Dynamic Wikipedia suggestions and navigational suggestions (a.k.a. top sites) are two examples. So I added tasks for both sponsored and nonsponsored suggestions to make sure they're disabled when the relevant pref is disabled, in addition to testing this bug itself. Differential Revision: https://phabricator.services.mozilla.com/D234787
1 parent 3ee1c43 commit 7135a4d

File tree

8 files changed

+208
-12
lines changed

8 files changed

+208
-12
lines changed

browser/components/urlbar/UrlbarProviderQuickSuggest.sys.mjs

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -266,13 +266,27 @@ class ProviderQuickSuggest extends UrlbarProvider {
266266
}
267267

268268
onEngagement(queryContext, controller, details) {
269-
let feature = this.#getFeatureByResult(details.result);
270-
feature?.onEngagement(
271-
queryContext,
272-
controller,
273-
details,
274-
this._trimmedSearchString
275-
);
269+
let { result } = details;
270+
271+
// Delegate to the result's feature if there is one.
272+
let feature = this.#getFeatureByResult(result);
273+
if (feature) {
274+
feature.onEngagement(
275+
queryContext,
276+
controller,
277+
details,
278+
this._trimmedSearchString
279+
);
280+
return;
281+
}
282+
283+
// Otherwise, handle commands. The dismiss, manage, and help commands are
284+
// supported for results without features. Dismissal is the only one we need
285+
// to handle here since urlbar handles the others.
286+
if (details.selType == "dismiss" && result.payload.isBlockable) {
287+
lazy.QuickSuggest.blockedSuggestions.add(result.payload.url);
288+
controller.removeResult(result);
289+
}
276290
}
277291

278292
onSearchSessionEnd(queryContext, controller, details) {

browser/components/urlbar/tests/quicksuggest/unit/head.js

Lines changed: 43 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -544,22 +544,61 @@ async function doRustProvidersTests({ searchString, tests }) {
544544
* The result that the command will act on.
545545
* @param {string} options.searchString
546546
* The search string to pass to `onEngagement()`.
547+
* @param {object} options.expectedCountsByCall
548+
* If non-null, this should map controller and view method names to the number
549+
* of times they should be called in response to the command.
550+
* @returns {Map}
551+
* A map from names of methods on the controller and view to the number of
552+
* times they were called.
547553
*/
548-
function triggerCommand({ feature, command, result, searchString = "" }) {
554+
function triggerCommand({
555+
feature,
556+
command,
557+
result,
558+
searchString = "",
559+
expectedCountsByCall = null,
560+
}) {
549561
info(`Calling ${feature.name}.onEngagement() to trigger command: ${command}`);
562+
563+
let countsByCall = new Map();
564+
let addCall = name => {
565+
if (!countsByCall.has(name)) {
566+
countsByCall.set(name, 0);
567+
}
568+
countsByCall.set(name, countsByCall.get(name) + 1);
569+
};
570+
550571
feature.onEngagement(
551572
// query context
552573
{},
553574
// controller
554575
{
555-
removeResult() {},
576+
removeResult() {
577+
addCall("removeResult");
578+
},
556579
view: {
557-
acknowledgeFeedback() {},
558-
invalidateResultMenuCommands() {},
580+
acknowledgeFeedback() {
581+
addCall("acknowledgeFeedback");
582+
},
583+
invalidateResultMenuCommands() {
584+
addCall("invalidateResultMenuCommands");
585+
},
559586
},
560587
},
561588
// details
562589
{ result, selType: command },
563590
searchString
564591
);
592+
593+
if (expectedCountsByCall) {
594+
for (let [name, expectedCount] of Object.entries(expectedCountsByCall)) {
595+
Assert.equal(
596+
countsByCall.get(name) ?? 0,
597+
expectedCount,
598+
"Function should have been called the expected number of times: " + name
599+
);
600+
}
601+
}
602+
603+
return countsByCall;
565604
}

browser/components/urlbar/tests/quicksuggest/unit/test_quicksuggest_fakespot.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,9 @@ add_task(async function notRelevant() {
308308
result,
309309
command: "not_relevant",
310310
feature: QuickSuggest.getFeature("FakespotSuggestions"),
311+
expectedCountsByCall: {
312+
removeResult: 1,
313+
},
311314
});
312315
await QuickSuggest.blockedSuggestions._test_readyPromise;
313316

@@ -359,6 +362,9 @@ add_task(async function notInterested() {
359362
result,
360363
command: "not_interested",
361364
feature: QuickSuggest.getFeature("FakespotSuggestions"),
365+
expectedCountsByCall: {
366+
removeResult: 1,
367+
},
362368
});
363369

364370
Assert.ok(

browser/components/urlbar/tests/quicksuggest/unit/test_quicksuggest_merino.js

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -563,6 +563,121 @@ add_task(async function bestMatch() {
563563
merinoClient().resetSession();
564564
});
565565

566+
// Tests a sponsored suggestion that isn't managed by a feature.
567+
add_task(async function unmanaged_sponsored() {
568+
await doUnmanagedTest({
569+
pref: "suggest.quicksuggest.sponsored",
570+
suggestion: {
571+
title: "Sponsored without feature",
572+
url: "https://example.com/sponsored-without-feature",
573+
provider: "sponsored-unrecognized-provider",
574+
is_sponsored: true,
575+
},
576+
});
577+
});
578+
579+
// Tests a nonsponsored suggestion that isn't managed by a feature.
580+
add_task(async function unmanaged_nonsponsored() {
581+
await doUnmanagedTest({
582+
pref: "suggest.quicksuggest.nonsponsored",
583+
suggestion: {
584+
title: "Nonsponsored without feature",
585+
url: "https://example.com/nonsponsored-without-feature",
586+
provider: "nonsponsored-unrecognized-provider",
587+
// no is_sponsored
588+
},
589+
});
590+
});
591+
592+
async function doUnmanagedTest({ pref, suggestion }) {
593+
UrlbarPrefs.set("suggest.quicksuggest.nonsponsored", true);
594+
UrlbarPrefs.set("suggest.quicksuggest.sponsored", true);
595+
await QuickSuggestTestUtils.forceSync();
596+
597+
UrlbarPrefs.set(PREF_DATA_COLLECTION_ENABLED, true);
598+
MerinoTestUtils.server.response.body.suggestions = [suggestion];
599+
600+
let expectedResult = {
601+
type: UrlbarUtils.RESULT_TYPE.URL,
602+
source: UrlbarUtils.RESULT_SOURCE.SEARCH,
603+
heuristic: false,
604+
payload: {
605+
title: suggestion.title,
606+
url: suggestion.url,
607+
displayUrl: suggestion.url.substring("https://".length),
608+
provider: suggestion.provider,
609+
telemetryType: suggestion.provider,
610+
isSponsored: !!suggestion.is_sponsored,
611+
source: "merino",
612+
isBlockable: true,
613+
blockL10n: {
614+
id: "urlbar-result-menu-dismiss-firefox-suggest",
615+
},
616+
isManageable: true,
617+
shouldShowUrl: true,
618+
},
619+
};
620+
621+
// Do an initial search. Sponsored and nonsponsored suggestions are both
622+
// enabled, so the suggestion should be matched.
623+
info("Doing search 1");
624+
await check_results({
625+
context: createContext("test", {
626+
providers: [UrlbarProviderQuickSuggest.name],
627+
isPrivate: false,
628+
}),
629+
matches: [expectedResult],
630+
});
631+
632+
// Set the pref to false and do another search. The suggestion shouldn't be
633+
// matched.
634+
UrlbarPrefs.set(pref, false);
635+
await QuickSuggestTestUtils.forceSync();
636+
637+
info("Doing search 2");
638+
await check_results({
639+
context: createContext("test", {
640+
providers: [UrlbarProviderQuickSuggest.name],
641+
isPrivate: false,
642+
}),
643+
matches: [],
644+
});
645+
646+
// Flip the pref back to true and do a third search.
647+
UrlbarPrefs.set(pref, true);
648+
await QuickSuggestTestUtils.forceSync();
649+
650+
info("Doing search 3");
651+
let context = createContext("test", {
652+
providers: [UrlbarProviderQuickSuggest.name],
653+
isPrivate: false,
654+
});
655+
await check_results({
656+
context,
657+
matches: [expectedResult],
658+
});
659+
660+
// Trigger the dismiss command on the result.
661+
triggerCommand({
662+
feature: UrlbarProviderQuickSuggest,
663+
command: "dismiss",
664+
result: context.results[0],
665+
expectedCountsByCall: {
666+
removeResult: 1,
667+
},
668+
});
669+
await QuickSuggest.blockedSuggestions._test_readyPromise;
670+
671+
Assert.ok(
672+
await QuickSuggest.blockedSuggestions.has(suggestion.url),
673+
"The suggestion URL should be blocked"
674+
);
675+
676+
await QuickSuggest.blockedSuggestions.clear();
677+
MerinoTestUtils.server.reset();
678+
merinoClient().resetSession();
679+
}
680+
566681
function merinoClient() {
567682
return QuickSuggest.getFeature("SuggestBackendMerino")?.client;
568683
}

browser/components/urlbar/tests/quicksuggest/unit/test_quicksuggest_pocket.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,9 @@ add_task(async function notRelevant() {
334334
result,
335335
command: "not_relevant",
336336
feature: QuickSuggest.getFeature("PocketSuggestions"),
337+
expectedCountsByCall: {
338+
removeResult: 1,
339+
},
337340
});
338341
await QuickSuggest.blockedSuggestions._test_readyPromise;
339342

@@ -396,6 +399,9 @@ add_task(async function notInterested() {
396399
result,
397400
command: "not_interested",
398401
feature: QuickSuggest.getFeature("PocketSuggestions"),
402+
expectedCountsByCall: {
403+
removeResult: 1,
404+
},
399405
});
400406

401407
Assert.ok(

browser/components/urlbar/tests/quicksuggest/unit/test_quicksuggest_yelp.js

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -542,8 +542,10 @@ add_task(async function notRelevant() {
542542
result,
543543
command: "not_relevant",
544544
feature: QuickSuggest.getFeature("YelpSuggestions"),
545+
expectedCountsByCall: {
546+
removeResult: 1,
547+
},
545548
});
546-
547549
await QuickSuggest.blockedSuggestions._test_readyPromise;
548550

549551
Assert.ok(
@@ -618,6 +620,9 @@ add_task(async function notInterested() {
618620
result,
619621
command: "not_interested",
620622
feature: QuickSuggest.getFeature("YelpSuggestions"),
623+
expectedCountsByCall: {
624+
removeResult: 1,
625+
},
621626
});
622627

623628
Assert.ok(
@@ -747,6 +752,10 @@ add_task(async function showLessFrequently() {
747752
feature,
748753
command: "show_less_frequently",
749754
searchString: input,
755+
expectedCountsByCall: {
756+
acknowledgeFeedback: 1,
757+
invalidateResultMenuCommands: after.canShowLessFrequently ? 0 : 1,
758+
},
750759
});
751760

752761
Assert.equal(

browser/components/urlbar/tests/quicksuggest/unit/test_quicksuggest_yelp_ml.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -482,6 +482,9 @@ add_task(async function notRelevant() {
482482
result,
483483
command: "not_relevant",
484484
feature: QuickSuggest.getFeature("YelpSuggestions"),
485+
expectedCountsByCall: {
486+
removeResult: 1,
487+
},
485488
});
486489
await QuickSuggest.blockedSuggestions._test_readyPromise;
487490

browser/components/urlbar/tests/quicksuggest/unit/test_weather_keywords.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -610,6 +610,10 @@ async function doShowLessFrequentlyTest({
610610
result: expectedResult,
611611
command: "show_less_frequently",
612612
searchString: input,
613+
expectedCountsByCall: {
614+
acknowledgeFeedback: 1,
615+
invalidateResultMenuCommands: after.canShowLessFrequently ? 0 : 1,
616+
},
613617
});
614618

615619
Assert.equal(

0 commit comments

Comments
 (0)