1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 // Basic tests for the quick suggest provider using the remote settings source.
6 // See also test_quicksuggest_merino.js.
10 const TELEMETRY_REMOTE_SETTINGS_LATENCY =
11 "FX_URLBAR_QUICK_SUGGEST_REMOTE_SETTINGS_LATENCY_MS";
13 const SPONSORED_SEARCH_STRING = "amp";
14 const NONSPONSORED_SEARCH_STRING = "wikipedia";
16 const HTTP_SEARCH_STRING = "http prefix";
17 const HTTPS_SEARCH_STRING = "https prefix";
18 const PREFIX_SUGGESTIONS_STRIPPED_URL = "example.com/prefix-test";
20 const { TIMESTAMP_TEMPLATE, TIMESTAMP_LENGTH } = QuickSuggest;
21 const TIMESTAMP_SEARCH_STRING = "timestamp";
22 const TIMESTAMP_SUGGESTION_URL = `http://example.com/timestamp-${TIMESTAMP_TEMPLATE}`;
23 const TIMESTAMP_SUGGESTION_CLICK_URL = `http://click.reporting.test.com/timestamp-${TIMESTAMP_TEMPLATE}-foo`;
25 const REMOTE_SETTINGS_RESULTS = [
28 url: "http://example.com/amp",
29 title: "AMP Suggestion",
30 keywords: [SPONSORED_SEARCH_STRING],
31 click_url: "http://example.com/amp-click",
32 impression_url: "http://example.com/amp-impression",
34 iab_category: "22 - Shopping",
38 url: "http://example.com/wikipedia",
39 title: "Wikipedia Suggestion",
40 keywords: [NONSPONSORED_SEARCH_STRING],
41 click_url: "http://example.com/wikipedia-click",
42 impression_url: "http://example.com/wikipedia-impression",
43 advertiser: "Wikipedia",
44 iab_category: "5 - Education",
48 url: "http://" + PREFIX_SUGGESTIONS_STRIPPED_URL,
49 title: "HTTP Suggestion",
50 keywords: [HTTP_SEARCH_STRING],
51 click_url: "http://example.com/http-click",
52 impression_url: "http://example.com/http-impression",
53 advertiser: "HttpAdvertiser",
54 iab_category: "22 - Shopping",
58 url: "https://" + PREFIX_SUGGESTIONS_STRIPPED_URL,
59 title: "https suggestion",
60 keywords: [HTTPS_SEARCH_STRING],
61 click_url: "http://click.reporting.test.com/prefix",
62 impression_url: "http://impression.reporting.test.com/prefix",
63 advertiser: "TestAdvertiserPrefix",
64 iab_category: "22 - Shopping",
68 url: TIMESTAMP_SUGGESTION_URL,
69 title: "Timestamp suggestion",
70 keywords: [TIMESTAMP_SEARCH_STRING],
71 click_url: TIMESTAMP_SUGGESTION_CLICK_URL,
72 impression_url: "http://impression.reporting.test.com/timestamp",
73 advertiser: "TestAdvertiserTimestamp",
74 iab_category: "22 - Shopping",
78 function expectedNonSponsoredResult() {
79 return makeWikipediaResult({
84 function expectedSponsoredResult() {
85 return makeAmpResult();
88 function expectedHttpResult() {
89 let suggestion = REMOTE_SETTINGS_RESULTS[2];
90 return makeAmpResult({
91 keyword: HTTP_SEARCH_STRING,
92 title: suggestion.title,
94 originalUrl: suggestion.url,
95 impressionUrl: suggestion.impression_url,
96 clickUrl: suggestion.click_url,
97 blockId: suggestion.id,
98 advertiser: suggestion.advertiser,
102 function expectedHttpsResult() {
103 let suggestion = REMOTE_SETTINGS_RESULTS[3];
104 return makeAmpResult({
105 keyword: HTTPS_SEARCH_STRING,
106 title: suggestion.title,
108 originalUrl: suggestion.url,
109 impressionUrl: suggestion.impression_url,
110 clickUrl: suggestion.click_url,
111 blockId: suggestion.id,
112 advertiser: suggestion.advertiser,
116 add_setup(async function init() {
117 UrlbarPrefs.set("quicksuggest.enabled", true);
118 UrlbarPrefs.set("quicksuggest.shouldShowOnboardingDialog", false);
119 UrlbarPrefs.set("quicksuggest.remoteSettings.enabled", true);
120 UrlbarPrefs.set("merino.enabled", false);
122 // Install a default test engine.
123 let engine = await addTestSuggestionsEngine();
124 await Services.search.setDefault(
126 Ci.nsISearchService.CHANGE_REASON_UNKNOWN
129 const testDataTypeResults = [
130 Object.assign({}, REMOTE_SETTINGS_RESULTS[0], { title: "test-data-type" }),
133 await QuickSuggestTestUtils.ensureQuickSuggestInit({
134 remoteSettingsResults: [
137 attachment: REMOTE_SETTINGS_RESULTS,
140 type: "test-data-type",
141 attachment: testDataTypeResults,
147 add_task(async function telemetryType_sponsored() {
149 QuickSuggest.getFeature("AdmWikipedia").getSuggestionTelemetryType({
153 "Telemetry type should be 'adm_sponsored'"
157 add_task(async function telemetryType_nonsponsored() {
159 QuickSuggest.getFeature("AdmWikipedia").getSuggestionTelemetryType({
163 "Telemetry type should be 'adm_nonsponsored'"
166 QuickSuggest.getFeature("AdmWikipedia").getSuggestionTelemetryType({}),
168 "Telemetry type should be 'adm_nonsponsored' if `is_sponsored` not defined"
172 // Tests with only non-sponsored suggestions enabled with a matching search
174 add_tasks_with_rust(async function nonsponsoredOnly_match() {
175 UrlbarPrefs.set("suggest.quicksuggest.nonsponsored", true);
176 UrlbarPrefs.set("suggest.quicksuggest.sponsored", false);
178 let context = createContext(NONSPONSORED_SEARCH_STRING, {
179 providers: [UrlbarProviderQuickSuggest.name],
182 await check_results({
184 matches: [expectedNonSponsoredResult()],
188 // Tests with only non-sponsored suggestions enabled with a non-matching search
190 add_tasks_with_rust(async function nonsponsoredOnly_noMatch() {
191 UrlbarPrefs.set("suggest.quicksuggest.nonsponsored", true);
192 UrlbarPrefs.set("suggest.quicksuggest.sponsored", false);
194 let context = createContext(SPONSORED_SEARCH_STRING, {
195 providers: [UrlbarProviderQuickSuggest.name],
198 await check_results({ context, matches: [] });
201 // Tests with only sponsored suggestions enabled with a matching search string.
202 add_tasks_with_rust(async function sponsoredOnly_sponsored() {
203 UrlbarPrefs.set("suggest.quicksuggest.nonsponsored", false);
204 UrlbarPrefs.set("suggest.quicksuggest.sponsored", true);
206 let context = createContext(SPONSORED_SEARCH_STRING, {
207 providers: [UrlbarProviderQuickSuggest.name],
210 await check_results({
212 matches: [expectedSponsoredResult()],
216 // Tests with only sponsored suggestions enabled with a non-matching search
218 add_tasks_with_rust(async function sponsoredOnly_nonsponsored() {
219 UrlbarPrefs.set("suggest.quicksuggest.nonsponsored", false);
220 UrlbarPrefs.set("suggest.quicksuggest.sponsored", true);
222 let context = createContext(NONSPONSORED_SEARCH_STRING, {
223 providers: [UrlbarProviderQuickSuggest.name],
226 await check_results({ context, matches: [] });
229 // Tests with both sponsored and non-sponsored suggestions enabled with a
230 // search string that matches the sponsored suggestion.
231 add_tasks_with_rust(async function both_sponsored() {
232 UrlbarPrefs.set("suggest.quicksuggest.nonsponsored", true);
233 UrlbarPrefs.set("suggest.quicksuggest.sponsored", true);
235 let context = createContext(SPONSORED_SEARCH_STRING, {
236 providers: [UrlbarProviderQuickSuggest.name],
239 await check_results({
241 matches: [expectedSponsoredResult()],
245 // Tests with both sponsored and non-sponsored suggestions enabled with a
246 // search string that matches the non-sponsored suggestion.
247 add_tasks_with_rust(async function both_nonsponsored() {
248 UrlbarPrefs.set("suggest.quicksuggest.nonsponsored", true);
249 UrlbarPrefs.set("suggest.quicksuggest.sponsored", true);
251 let context = createContext(NONSPONSORED_SEARCH_STRING, {
252 providers: [UrlbarProviderQuickSuggest.name],
255 await check_results({
257 matches: [expectedNonSponsoredResult()],
261 // Tests with both sponsored and non-sponsored suggestions enabled with a
262 // search string that doesn't match either suggestion.
263 add_tasks_with_rust(async function both_noMatch() {
264 UrlbarPrefs.set("suggest.quicksuggest.nonsponsored", true);
265 UrlbarPrefs.set("suggest.quicksuggest.sponsored", true);
267 let context = createContext("this doesn't match anything", {
268 providers: [UrlbarProviderQuickSuggest.name],
271 await check_results({ context, matches: [] });
274 // Tests with both the main and sponsored prefs disabled with a search string
275 // that matches the sponsored suggestion.
276 add_tasks_with_rust(async function neither_sponsored() {
277 UrlbarPrefs.set("suggest.quicksuggest.nonsponsored", false);
278 UrlbarPrefs.set("suggest.quicksuggest.sponsored", false);
280 let context = createContext(SPONSORED_SEARCH_STRING, {
281 providers: [UrlbarProviderQuickSuggest.name],
284 await check_results({ context, matches: [] });
287 // Tests with both the main and sponsored prefs disabled with a search string
288 // that matches the non-sponsored suggestion.
289 add_tasks_with_rust(async function neither_nonsponsored() {
290 UrlbarPrefs.set("suggest.quicksuggest.nonsponsored", false);
291 UrlbarPrefs.set("suggest.quicksuggest.sponsored", false);
293 let context = createContext(NONSPONSORED_SEARCH_STRING, {
294 providers: [UrlbarProviderQuickSuggest.name],
297 await check_results({ context, matches: [] });
300 // Search string matching should be case insensitive and ignore leading spaces.
301 add_tasks_with_rust(async function caseInsensitiveAndLeadingSpaces() {
302 UrlbarPrefs.set("suggest.quicksuggest.nonsponsored", true);
303 UrlbarPrefs.set("suggest.quicksuggest.sponsored", true);
305 let context = createContext(" " + SPONSORED_SEARCH_STRING.toUpperCase(), {
306 providers: [UrlbarProviderQuickSuggest.name],
309 await check_results({
311 matches: [expectedSponsoredResult()],
315 // The provider should not be active for search strings that are empty or
316 // contain only spaces.
317 add_tasks_with_rust(async function emptySearchStringsAndSpaces() {
318 UrlbarPrefs.set("suggest.quicksuggest.nonsponsored", true);
319 UrlbarPrefs.set("suggest.quicksuggest.sponsored", true);
321 let searchStrings = ["", " ", " ", " "];
322 for (let str of searchStrings) {
323 let msg = JSON.stringify(str) + ` (length = ${str.length})`;
324 info("Testing search string: " + msg);
326 let context = createContext(str, {
327 providers: [UrlbarProviderQuickSuggest.name],
330 await check_results({
335 !UrlbarProviderQuickSuggest.isActive(context),
336 "Provider should not be active for search string: " + msg
341 // Results should be returned even when `browser.search.suggest.enabled` is
343 add_tasks_with_rust(async function browser_search_suggest_enabled() {
344 UrlbarPrefs.set("suggest.quicksuggest.nonsponsored", true);
345 UrlbarPrefs.set("suggest.quicksuggest.sponsored", true);
346 UrlbarPrefs.set("browser.search.suggest.enabled", false);
348 let context = createContext(SPONSORED_SEARCH_STRING, {
349 providers: [UrlbarProviderQuickSuggest.name],
352 await check_results({
354 matches: [expectedSponsoredResult()],
357 UrlbarPrefs.clear("browser.search.suggest.enabled");
360 // Results should be returned even when `browser.urlbar.suggest.searches` is
362 add_tasks_with_rust(async function browser_search_suggest_enabled() {
363 UrlbarPrefs.set("suggest.quicksuggest.nonsponsored", true);
364 UrlbarPrefs.set("suggest.quicksuggest.sponsored", true);
365 UrlbarPrefs.set("suggest.searches", false);
367 let context = createContext(SPONSORED_SEARCH_STRING, {
368 providers: [UrlbarProviderQuickSuggest.name],
371 await check_results({
373 matches: [expectedSponsoredResult()],
376 UrlbarPrefs.clear("suggest.searches");
379 // Neither sponsored nor non-sponsored results should appear in private contexts
380 // even when suggestions in private windows are enabled.
381 add_tasks_with_rust(async function privateContext() {
382 UrlbarPrefs.set("suggest.quicksuggest.nonsponsored", true);
383 UrlbarPrefs.set("suggest.quicksuggest.sponsored", true);
385 for (let privateSuggestionsEnabled of [true, false]) {
387 "browser.search.suggest.enabled.private",
388 privateSuggestionsEnabled
390 let context = createContext(SPONSORED_SEARCH_STRING, {
391 providers: [UrlbarProviderQuickSuggest.name],
394 await check_results({
400 UrlbarPrefs.clear("browser.search.suggest.enabled.private");
403 // When search suggestions come before general results and the only general
404 // result is a quick suggest result, it should come last.
405 add_tasks_with_rust(async function suggestionsBeforeGeneral_only() {
406 UrlbarPrefs.set("suggest.quicksuggest.nonsponsored", true);
407 UrlbarPrefs.set("suggest.quicksuggest.sponsored", true);
408 UrlbarPrefs.set("browser.search.suggest.enabled", true);
409 UrlbarPrefs.set("suggest.searches", true);
410 UrlbarPrefs.set("showSearchSuggestionsFirst", true);
412 let context = createContext(SPONSORED_SEARCH_STRING, { isPrivate: false });
413 await check_results({
416 makeSearchResult(context, {
418 query: SPONSORED_SEARCH_STRING,
419 engineName: Services.search.defaultEngine.name,
421 makeSearchResult(context, {
422 query: SPONSORED_SEARCH_STRING,
423 suggestion: SPONSORED_SEARCH_STRING + " foo",
424 engineName: Services.search.defaultEngine.name,
426 makeSearchResult(context, {
427 query: SPONSORED_SEARCH_STRING,
428 suggestion: SPONSORED_SEARCH_STRING + " bar",
429 engineName: Services.search.defaultEngine.name,
431 expectedSponsoredResult(),
435 UrlbarPrefs.clear("browser.search.suggest.enabled");
436 UrlbarPrefs.clear("suggest.searches");
437 UrlbarPrefs.clear("showSearchSuggestionsFirst");
440 // When search suggestions come before general results and there are other
441 // general results besides quick suggest, the quick suggest result should come
443 add_tasks_with_rust(async function suggestionsBeforeGeneral_others() {
444 UrlbarPrefs.set("suggest.quicksuggest.nonsponsored", true);
445 UrlbarPrefs.set("suggest.quicksuggest.sponsored", true);
446 UrlbarPrefs.set("browser.search.suggest.enabled", true);
447 UrlbarPrefs.set("suggest.searches", true);
448 UrlbarPrefs.set("showSearchSuggestionsFirst", true);
450 let context = createContext(SPONSORED_SEARCH_STRING, { isPrivate: false });
452 // Add some history that will match our query below.
453 let maxResults = UrlbarPrefs.get("maxRichResults");
454 let historyResults = [];
455 for (let i = 0; i < maxResults; i++) {
456 let url = "http://example.com/" + SPONSORED_SEARCH_STRING + i;
458 makeVisitResult(context, {
460 title: "test visit for " + url,
463 await PlacesTestUtils.addVisits(url);
465 historyResults = historyResults.reverse().slice(0, historyResults.length - 4);
467 await check_results({
470 makeSearchResult(context, {
472 query: SPONSORED_SEARCH_STRING,
473 engineName: Services.search.defaultEngine.name,
475 makeSearchResult(context, {
476 query: SPONSORED_SEARCH_STRING,
477 suggestion: SPONSORED_SEARCH_STRING + " foo",
478 engineName: Services.search.defaultEngine.name,
480 makeSearchResult(context, {
481 query: SPONSORED_SEARCH_STRING,
482 suggestion: SPONSORED_SEARCH_STRING + " bar",
483 engineName: Services.search.defaultEngine.name,
486 expectedSponsoredResult(),
490 UrlbarPrefs.clear("browser.search.suggest.enabled");
491 UrlbarPrefs.clear("suggest.searches");
492 UrlbarPrefs.clear("showSearchSuggestionsFirst");
493 await PlacesUtils.history.clear();
496 // When general results come before search suggestions and the only general
497 // result is a quick suggest result, it should come before suggestions.
498 add_tasks_with_rust(async function generalBeforeSuggestions_only() {
499 UrlbarPrefs.set("suggest.quicksuggest.nonsponsored", true);
500 UrlbarPrefs.set("suggest.quicksuggest.sponsored", true);
501 UrlbarPrefs.set("browser.search.suggest.enabled", true);
502 UrlbarPrefs.set("suggest.searches", true);
503 UrlbarPrefs.set("showSearchSuggestionsFirst", false);
505 let context = createContext(SPONSORED_SEARCH_STRING, { isPrivate: false });
506 await check_results({
509 makeSearchResult(context, {
511 query: SPONSORED_SEARCH_STRING,
512 engineName: Services.search.defaultEngine.name,
514 expectedSponsoredResult(),
515 makeSearchResult(context, {
516 query: SPONSORED_SEARCH_STRING,
517 suggestion: SPONSORED_SEARCH_STRING + " foo",
518 engineName: Services.search.defaultEngine.name,
520 makeSearchResult(context, {
521 query: SPONSORED_SEARCH_STRING,
522 suggestion: SPONSORED_SEARCH_STRING + " bar",
523 engineName: Services.search.defaultEngine.name,
528 UrlbarPrefs.clear("browser.search.suggest.enabled");
529 UrlbarPrefs.clear("suggest.searches");
530 UrlbarPrefs.clear("showSearchSuggestionsFirst");
533 // When general results come before search suggestions and there are other
534 // general results besides quick suggest, the quick suggest result should be the
535 // last general result.
536 add_tasks_with_rust(async function generalBeforeSuggestions_others() {
537 UrlbarPrefs.set("suggest.quicksuggest.nonsponsored", true);
538 UrlbarPrefs.set("suggest.quicksuggest.sponsored", true);
539 UrlbarPrefs.set("browser.search.suggest.enabled", true);
540 UrlbarPrefs.set("suggest.searches", true);
541 UrlbarPrefs.set("showSearchSuggestionsFirst", false);
543 let context = createContext(SPONSORED_SEARCH_STRING, { isPrivate: false });
545 // Add some history that will match our query below.
546 let maxResults = UrlbarPrefs.get("maxRichResults");
547 let historyResults = [];
548 for (let i = 0; i < maxResults; i++) {
549 let url = "http://example.com/" + SPONSORED_SEARCH_STRING + i;
551 makeVisitResult(context, {
553 title: "test visit for " + url,
556 await PlacesTestUtils.addVisits(url);
558 historyResults = historyResults.reverse().slice(0, historyResults.length - 4);
560 await check_results({
563 makeSearchResult(context, {
565 query: SPONSORED_SEARCH_STRING,
566 engineName: Services.search.defaultEngine.name,
569 expectedSponsoredResult(),
570 makeSearchResult(context, {
571 query: SPONSORED_SEARCH_STRING,
572 suggestion: SPONSORED_SEARCH_STRING + " foo",
573 engineName: Services.search.defaultEngine.name,
575 makeSearchResult(context, {
576 query: SPONSORED_SEARCH_STRING,
577 suggestion: SPONSORED_SEARCH_STRING + " bar",
578 engineName: Services.search.defaultEngine.name,
583 UrlbarPrefs.clear("browser.search.suggest.enabled");
584 UrlbarPrefs.clear("suggest.searches");
585 UrlbarPrefs.clear("showSearchSuggestionsFirst");
586 await PlacesUtils.history.clear();
589 add_tasks_with_rust(async function dedupeAgainstURL_samePrefix() {
590 await doDedupeAgainstURLTest({
591 searchString: HTTP_SEARCH_STRING,
592 expectedQuickSuggestResult: expectedHttpResult(),
593 otherPrefix: "http://",
598 add_tasks_with_rust(async function dedupeAgainstURL_higherPrefix() {
599 await doDedupeAgainstURLTest({
600 searchString: HTTPS_SEARCH_STRING,
601 expectedQuickSuggestResult: expectedHttpsResult(),
602 otherPrefix: "http://",
607 add_tasks_with_rust(async function dedupeAgainstURL_lowerPrefix() {
608 await doDedupeAgainstURLTest({
609 searchString: HTTP_SEARCH_STRING,
610 expectedQuickSuggestResult: expectedHttpResult(),
611 otherPrefix: "https://",
617 * Tests how the muxer dedupes URL results against quick suggest results.
618 * Depending on prefix rank, quick suggest results should be preferred over
619 * other URL results with the same stripped URL: Other results should be
620 * discarded when their prefix rank is lower than the prefix rank of the quick
621 * suggest. They should not be discarded when their prefix rank is higher, and
622 * in that case both results should be included.
624 * This function adds a visit to the URL formed by the given `otherPrefix` and
625 * `PREFIX_SUGGESTIONS_STRIPPED_URL`. The visit's title will be set to the given
626 * `searchString` so that both the visit and the quick suggest will match it.
628 * @param {object} options
630 * @param {string} options.searchString
631 * The search string that should trigger one of the mock prefix-test quick
633 * @param {object} options.expectedQuickSuggestResult
634 * The expected quick suggest result.
635 * @param {string} options.otherPrefix
636 * The visit will be created with a URL with this prefix, e.g., "http://".
637 * @param {boolean} options.expectOther
638 * Whether the visit result should appear in the final results.
640 async function doDedupeAgainstURLTest({
642 expectedQuickSuggestResult,
646 // Disable search suggestions.
647 UrlbarPrefs.set("suggest.searches", false);
649 // Add a visit that will match our query below.
650 let otherURL = otherPrefix + PREFIX_SUGGESTIONS_STRIPPED_URL;
651 await PlacesTestUtils.addVisits({ uri: otherURL, title: searchString });
653 // First, do a search with quick suggest disabled to make sure the search
654 // string matches the visit.
655 info("Doing first query");
656 UrlbarPrefs.set("suggest.quicksuggest.nonsponsored", false);
657 UrlbarPrefs.set("suggest.quicksuggest.sponsored", false);
658 let context = createContext(searchString, { isPrivate: false });
659 await check_results({
662 makeSearchResult(context, {
665 engineName: Services.search.defaultEngine.name,
667 makeVisitResult(context, {
674 // Now do another search with quick suggest enabled.
675 UrlbarPrefs.set("suggest.quicksuggest.nonsponsored", true);
676 UrlbarPrefs.set("suggest.quicksuggest.sponsored", true);
678 context = createContext(searchString, { isPrivate: false });
680 let expectedResults = [
681 makeSearchResult(context, {
684 engineName: Services.search.defaultEngine.name,
688 expectedResults.push(
689 makeVisitResult(context, {
695 expectedResults.push(expectedQuickSuggestResult);
697 info("Doing second query");
698 await check_results({ context, matches: expectedResults });
700 UrlbarPrefs.clear("suggest.quicksuggest.nonsponsored");
701 UrlbarPrefs.clear("suggest.quicksuggest.sponsored");
702 UrlbarPrefs.clear("suggest.searches");
703 await PlacesUtils.history.clear();
706 // Tests the remote settings latency histogram.
707 add_task(async function latencyTelemetry() {
708 UrlbarPrefs.set("suggest.quicksuggest.nonsponsored", true);
709 UrlbarPrefs.set("suggest.quicksuggest.sponsored", true);
711 let histogram = Services.telemetry.getHistogramById(
712 TELEMETRY_REMOTE_SETTINGS_LATENCY
716 let context = createContext(SPONSORED_SEARCH_STRING, {
717 providers: [UrlbarProviderQuickSuggest.name],
720 await check_results({
722 matches: [expectedSponsoredResult()],
725 // In the latency histogram, there should be a single value across all
728 Object.values(histogram.snapshot().values).filter(v => v > 0),
730 "Latency histogram updated after search"
733 !TelemetryStopwatch.running(TELEMETRY_REMOTE_SETTINGS_LATENCY, context),
734 "Stopwatch not running after search"
738 // Tests setup and teardown of the remote settings client depending on whether
739 // quick suggest is enabled.
740 add_task(async function setupAndTeardown() {
742 QuickSuggest.jsBackend.isEnabled,
743 "Remote settings backend is enabled initially"
746 // Disable the suggest prefs so the settings client starts out torn down.
747 UrlbarPrefs.set("suggest.quicksuggest.nonsponsored", false);
748 UrlbarPrefs.set("suggest.quicksuggest.sponsored", false);
750 !QuickSuggest.jsBackend.rs,
751 "Settings client is null after disabling suggest prefs"
754 QuickSuggest.jsBackend.isEnabled,
755 "Remote settings backend remains enabled"
758 // Setting one of the suggest prefs should cause the client to be set up. We
759 // assume all previous tasks left `quicksuggest.enabled` true (from the init
761 UrlbarPrefs.set("suggest.quicksuggest.nonsponsored", true);
763 QuickSuggest.jsBackend.rs,
764 "Settings client is non-null after enabling suggest.quicksuggest.nonsponsored"
767 UrlbarPrefs.set("suggest.quicksuggest.nonsponsored", false);
769 !QuickSuggest.jsBackend.rs,
770 "Settings client is null after disabling suggest.quicksuggest.nonsponsored"
773 UrlbarPrefs.set("suggest.quicksuggest.sponsored", true);
775 QuickSuggest.jsBackend.rs,
776 "Settings client is non-null after enabling suggest.quicksuggest.sponsored"
779 UrlbarPrefs.set("suggest.quicksuggest.nonsponsored", true);
781 QuickSuggest.jsBackend.rs,
782 "Settings client remains non-null after enabling suggest.quicksuggest.nonsponsored"
785 UrlbarPrefs.set("suggest.quicksuggest.nonsponsored", false);
787 QuickSuggest.jsBackend.rs,
788 "Settings client remains non-null after disabling suggest.quicksuggest.nonsponsored"
791 UrlbarPrefs.set("suggest.quicksuggest.sponsored", false);
793 !QuickSuggest.jsBackend.rs,
794 "Settings client is null after disabling suggest.quicksuggest.sponsored"
797 UrlbarPrefs.set("suggest.quicksuggest.nonsponsored", true);
799 QuickSuggest.jsBackend.rs,
800 "Settings client is non-null after enabling suggest.quicksuggest.nonsponsored"
803 UrlbarPrefs.set("quicksuggest.enabled", false);
805 !QuickSuggest.jsBackend.rs,
806 "Settings client is null after disabling quicksuggest.enabled"
809 UrlbarPrefs.set("quicksuggest.enabled", true);
811 QuickSuggest.jsBackend.rs,
812 "Settings client is non-null after re-enabling quicksuggest.enabled"
815 QuickSuggest.jsBackend.isEnabled,
816 "Remote settings backend is enabled after re-enabling quicksuggest.enabled"
819 UrlbarPrefs.set("quicksuggest.rustEnabled", true);
821 !QuickSuggest.jsBackend.rs,
822 "Settings client is null after enabling the Rust backend"
825 !QuickSuggest.jsBackend.isEnabled,
826 "Remote settings backend is disabled after enabling the Rust backend"
829 UrlbarPrefs.clear("quicksuggest.rustEnabled");
831 QuickSuggest.jsBackend.rs,
832 "Settings client is non-null after disabling the Rust backend"
835 QuickSuggest.jsBackend.isEnabled,
836 "Remote settings backend is enabled after disabling the Rust backend"
839 // Leave the prefs in the same state as when the task started.
840 UrlbarPrefs.clear("suggest.quicksuggest.nonsponsored");
841 UrlbarPrefs.clear("suggest.quicksuggest.sponsored");
842 UrlbarPrefs.set("quicksuggest.enabled", true);
844 !QuickSuggest.jsBackend.rs,
845 "Settings client remains null at end of task"
849 // Timestamp templates in URLs should be replaced with real timestamps.
850 add_tasks_with_rust(async function timestamps() {
851 UrlbarPrefs.set("suggest.quicksuggest.nonsponsored", true);
852 UrlbarPrefs.set("suggest.quicksuggest.sponsored", true);
855 let context = createContext(TIMESTAMP_SEARCH_STRING, {
856 providers: [UrlbarProviderQuickSuggest.name],
859 let controller = UrlbarTestUtils.newMockController({
861 isPrivate: context.isPrivate,
866 return "dummy-search-source";
870 href: AppConstants.BROWSER_CHROME_URL,
875 await controller.startQuery(context);
877 // Should be one quick suggest result.
878 Assert.equal(context.results.length, 1, "One result returned");
879 let result = context.results[0];
881 QuickSuggestTestUtils.assertTimestampsReplaced(result, {
882 url: TIMESTAMP_SUGGESTION_URL,
883 sponsoredClickUrl: TIMESTAMP_SUGGESTION_CLICK_URL,
887 // Real quick suggest URLs include a timestamp template that
888 // UrlbarProviderQuickSuggest fills in when it fetches suggestions. When the
889 // user picks a quick suggest, its URL with its particular timestamp is added to
890 // history. If the user triggers the quick suggest again later, its new
891 // timestamp may be different from the one in the user's history. In that case,
892 // the two URLs should be treated as dupes and only the quick suggest should be
893 // shown, not the URL from history.
894 add_tasks_with_rust(async function dedupeAgainstURL_timestamps() {
895 // Disable search suggestions.
896 UrlbarPrefs.set("suggest.searches", false);
898 // Add a visit that will match the query below and dupe the quick suggest.
899 let dupeURL = TIMESTAMP_SUGGESTION_URL.replace(
904 // Add other visits that will match the query and almost dupe the quick
905 // suggest but not quite because they have invalid timestamps.
906 let badTimestamps = [
907 // not numeric digits
908 "x".repeat(TIMESTAMP_LENGTH),
910 "5".repeat(TIMESTAMP_LENGTH - 1),
911 // empty string, too few digits
914 let badTimestampURLs = badTimestamps.map(str =>
915 TIMESTAMP_SUGGESTION_URL.replace(TIMESTAMP_TEMPLATE, str)
918 await PlacesTestUtils.addVisits(
919 [dupeURL, ...badTimestampURLs].map(uri => ({
921 title: TIMESTAMP_SEARCH_STRING,
925 // First, do a search with quick suggest disabled to make sure the search
926 // string matches all the other URLs.
927 info("Doing first query");
928 UrlbarPrefs.set("suggest.quicksuggest.nonsponsored", false);
929 UrlbarPrefs.set("suggest.quicksuggest.sponsored", false);
930 let context = createContext(TIMESTAMP_SEARCH_STRING, { isPrivate: false });
932 let expectedHeuristic = makeSearchResult(context, {
934 query: TIMESTAMP_SEARCH_STRING,
935 engineName: Services.search.defaultEngine.name,
937 let expectedDupeResult = makeVisitResult(context, {
939 title: TIMESTAMP_SEARCH_STRING,
941 let expectedBadTimestampResults = [...badTimestampURLs].reverse().map(uri =>
942 makeVisitResult(context, {
944 title: TIMESTAMP_SEARCH_STRING,
948 await check_results({
952 ...expectedBadTimestampResults,
957 // Now do another search with quick suggest enabled.
958 info("Doing second query");
959 UrlbarPrefs.set("suggest.quicksuggest.nonsponsored", true);
960 UrlbarPrefs.set("suggest.quicksuggest.sponsored", true);
961 context = createContext(TIMESTAMP_SEARCH_STRING, { isPrivate: false });
963 let expectedQuickSuggest = makeAmpResult({
964 originalUrl: TIMESTAMP_SUGGESTION_URL,
965 keyword: TIMESTAMP_SEARCH_STRING,
966 title: "Timestamp suggestion",
967 impressionUrl: "http://impression.reporting.test.com/timestamp",
969 advertiser: "TestAdvertiserTimestamp",
970 iabCategory: "22 - Shopping",
973 let expectedResults = [
975 ...expectedBadTimestampResults,
976 expectedQuickSuggest,
979 let controller = UrlbarTestUtils.newMockController({
986 return "dummy-search-source";
990 href: AppConstants.BROWSER_CHROME_URL,
995 await controller.startQuery(context);
996 info("Actual results: " + JSON.stringify(context.results));
999 context.results.length,
1000 expectedResults.length,
1001 "Found the expected number of results"
1004 function getPayload(result, keysToIgnore = []) {
1006 for (let [key, value] of Object.entries(result.payload)) {
1007 if (value !== undefined && !keysToIgnore.includes(key)) {
1008 payload[key] = value;
1014 // Check actual vs. expected result properties.
1015 for (let i = 0; i < expectedResults.length; i++) {
1016 let actual = context.results[i];
1017 let expected = expectedResults[i];
1019 `Comparing results at index ${i}:` +
1021 JSON.stringify(actual) +
1023 JSON.stringify(expected)
1028 `result.type at result index ${i}`
1033 `result.source at result index ${i}`
1038 `result.heuristic at result index ${i}`
1041 // Check payloads except for the last result, which should be the quick
1043 if (i != expectedResults.length - 1) {
1045 getPayload(context.results[i]),
1046 getPayload(expectedResults[i]),
1047 "Payload at index " + i
1052 // Check the quick suggest's payload excluding the timestamp-related
1054 let actualQuickSuggest = context.results[context.results.length - 1];
1055 let timestampKeys = [
1057 "sponsoredClickUrl",
1059 "urlTimestampIndex",
1062 getPayload(actualQuickSuggest, timestampKeys),
1063 getPayload(expectedQuickSuggest, timestampKeys),
1064 "Quick suggest payload excluding timestamp-related keys"
1067 // Now check the timestamps in the payload.
1068 QuickSuggestTestUtils.assertTimestampsReplaced(actualQuickSuggest, {
1069 url: TIMESTAMP_SUGGESTION_URL,
1070 sponsoredClickUrl: TIMESTAMP_SUGGESTION_CLICK_URL,
1074 UrlbarPrefs.clear("suggest.quicksuggest.nonsponsored");
1075 UrlbarPrefs.clear("suggest.quicksuggest.sponsored");
1076 UrlbarPrefs.clear("suggest.searches");
1077 await PlacesUtils.history.clear();
1080 // Tests the API for blocking suggestions and the backing pref.
1081 add_task(async function blockedSuggestionsAPI() {
1082 // Start with no blocked suggestions.
1083 await QuickSuggest.blockedSuggestions.clear();
1085 QuickSuggest.blockedSuggestions._test_digests.size,
1087 "blockedSuggestions._test_digests is empty"
1090 UrlbarPrefs.get("quicksuggest.blockedDigests"),
1092 "quicksuggest.blockedDigests is an empty string"
1097 for (let i = 0; i < 3; i++) {
1098 urls.push("http://example.com/" + i);
1101 // Block each URL in turn and make sure previously blocked URLs are still
1102 // blocked and the remaining URLs are not blocked.
1103 for (let i = 0; i < urls.length; i++) {
1104 await QuickSuggest.blockedSuggestions.add(urls[i]);
1105 for (let j = 0; j < urls.length; j++) {
1107 await QuickSuggest.blockedSuggestions.has(urls[j]),
1109 `Suggestion at index ${j} is blocked or not as expected`
1114 // Make sure all URLs are blocked for good measure.
1115 for (let url of urls) {
1117 await QuickSuggest.blockedSuggestions.has(url),
1118 `Suggestion is blocked: ${url}`
1122 // Check `blockedSuggestions._test_digests` and `quicksuggest.blockedDigests`.
1124 QuickSuggest.blockedSuggestions._test_digests.size,
1126 "blockedSuggestions._test_digests has correct size"
1128 let array = JSON.parse(UrlbarPrefs.get("quicksuggest.blockedDigests"));
1129 Assert.ok(Array.isArray(array), "Parsed value of pref is an array");
1130 Assert.equal(array.length, urls.length, "Array has correct length");
1132 // Write some junk to `quicksuggest.blockedDigests`.
1133 // `blockedSuggestions._test_digests` should not be changed and all previously
1134 // blocked URLs should remain blocked.
1135 UrlbarPrefs.set("quicksuggest.blockedDigests", "not a json array");
1136 await QuickSuggest.blockedSuggestions._test_readyPromise;
1137 for (let url of urls) {
1139 await QuickSuggest.blockedSuggestions.has(url),
1140 `Suggestion remains blocked: ${url}`
1144 QuickSuggest.blockedSuggestions._test_digests.size,
1146 "blockedSuggestions._test_digests still has correct size"
1149 // Block a new URL. All URLs should remain blocked and the pref should be
1151 let newURL = "http://example.com/new-block";
1152 await QuickSuggest.blockedSuggestions.add(newURL);
1154 for (let url of urls) {
1156 await QuickSuggest.blockedSuggestions.has(url),
1157 `Suggestion is blocked: ${url}`
1161 QuickSuggest.blockedSuggestions._test_digests.size,
1163 "blockedSuggestions._test_digests has correct size"
1165 array = JSON.parse(UrlbarPrefs.get("quicksuggest.blockedDigests"));
1166 Assert.ok(Array.isArray(array), "Parsed value of pref is an array");
1167 Assert.equal(array.length, urls.length, "Array has correct length");
1169 // Add a new URL digest directly to the JSON'ed array in the pref.
1170 newURL = "http://example.com/direct-to-pref";
1172 array = JSON.parse(UrlbarPrefs.get("quicksuggest.blockedDigests"));
1173 array.push(await QuickSuggest.blockedSuggestions._test_getDigest(newURL));
1174 UrlbarPrefs.set("quicksuggest.blockedDigests", JSON.stringify(array));
1175 await QuickSuggest.blockedSuggestions._test_readyPromise;
1177 // All URLs should remain blocked and the new URL should be blocked.
1178 for (let url of urls) {
1180 await QuickSuggest.blockedSuggestions.has(url),
1181 `Suggestion is blocked: ${url}`
1185 QuickSuggest.blockedSuggestions._test_digests.size,
1187 "blockedSuggestions._test_digests has correct size"
1190 // Clear the pref. All URLs should be unblocked.
1191 UrlbarPrefs.clear("quicksuggest.blockedDigests");
1192 await QuickSuggest.blockedSuggestions._test_readyPromise;
1193 for (let url of urls) {
1195 !(await QuickSuggest.blockedSuggestions.has(url)),
1196 `Suggestion is no longer blocked: ${url}`
1200 QuickSuggest.blockedSuggestions._test_digests.size,
1202 "blockedSuggestions._test_digests is now empty"
1205 // Block all the URLs again and test `blockedSuggestions.clear()`.
1206 for (let url of urls) {
1207 await QuickSuggest.blockedSuggestions.add(url);
1209 for (let url of urls) {
1211 await QuickSuggest.blockedSuggestions.has(url),
1212 `Suggestion is blocked: ${url}`
1215 await QuickSuggest.blockedSuggestions.clear();
1216 for (let url of urls) {
1218 !(await QuickSuggest.blockedSuggestions.has(url)),
1219 `Suggestion is no longer blocked: ${url}`
1223 QuickSuggest.blockedSuggestions._test_digests.size,
1225 "blockedSuggestions._test_digests is now empty"
1229 // Test whether the blocking for remote settings results works.
1230 add_tasks_with_rust(async function block() {
1231 for (const result of REMOTE_SETTINGS_RESULTS) {
1232 await QuickSuggest.blockedSuggestions.add(result.url);
1235 for (const result of REMOTE_SETTINGS_RESULTS) {
1236 const context = createContext(result.keywords[0], {
1237 providers: [UrlbarProviderQuickSuggest.name],
1240 await check_results({
1246 await QuickSuggest.blockedSuggestions.clear();
1249 // Makes sure remote settings data is fetched using the correct `type` based on
1250 // the value of the `quickSuggestRemoteSettingsDataType` Nimbus variable.
1251 add_task(async function remoteSettingsDataType() {
1252 UrlbarPrefs.set("suggest.quicksuggest.sponsored", true);
1254 for (let dataType of [undefined, "test-data-type"]) {
1255 // Set up a mock Nimbus rollout with the data type.
1258 value.quickSuggestRemoteSettingsDataType = dataType;
1260 let cleanUpNimbus = await UrlbarTestUtils.initNimbusFeature(value);
1262 // Make the result for test data type.
1263 let expected = expectedSponsoredResult();
1265 expected = JSON.parse(JSON.stringify(expected));
1266 expected.payload.title = dataType;
1269 // Re-enable to trigger sync from remote settings.
1270 UrlbarPrefs.set("quicksuggest.remoteSettings.enabled", false);
1271 UrlbarPrefs.set("quicksuggest.remoteSettings.enabled", true);
1273 let context = createContext(SPONSORED_SEARCH_STRING, {
1274 providers: [UrlbarProviderQuickSuggest.name],
1277 await check_results({
1279 matches: [expected],
1282 await cleanUpNimbus();
1286 // For priority sponsored suggestion,
1287 // always isBestMatch will be true and suggestIndex will be 1.
1288 const EXPECTED_SPONSORED_PRIORITY_RESULT = {
1289 ...expectedSponsoredResult(),
1293 add_task(async function sponsoredPriority_normal() {
1294 await doSponsoredPriorityTest({
1295 searchWord: SPONSORED_SEARCH_STRING,
1296 remoteSettingsData: [REMOTE_SETTINGS_RESULTS[0]],
1297 expectedMatches: [EXPECTED_SPONSORED_PRIORITY_RESULT],
1301 add_task(async function sponsoredPriority_nonsponsoredSuggestion() {
1302 // Not affect to except sponsored suggestion.
1303 await doSponsoredPriorityTest({
1304 searchWord: NONSPONSORED_SEARCH_STRING,
1305 remoteSettingsData: [REMOTE_SETTINGS_RESULTS[1]],
1306 expectedMatches: [expectedNonSponsoredResult()],
1310 add_task(async function sponsoredPriority_sponsoredIndex() {
1311 await doSponsoredPriorityTest({
1312 nimbusSettings: { quickSuggestSponsoredIndex: 2 },
1313 searchWord: SPONSORED_SEARCH_STRING,
1314 remoteSettingsData: [REMOTE_SETTINGS_RESULTS[0]],
1315 expectedMatches: [EXPECTED_SPONSORED_PRIORITY_RESULT],
1319 add_task(async function sponsoredPriority_position() {
1320 await doSponsoredPriorityTest({
1321 nimbusSettings: { quickSuggestAllowPositionInSuggestions: true },
1322 searchWord: SPONSORED_SEARCH_STRING,
1323 remoteSettingsData: [
1324 Object.assign({}, REMOTE_SETTINGS_RESULTS[0], { position: 2 }),
1326 expectedMatches: [EXPECTED_SPONSORED_PRIORITY_RESULT],
1330 async function doSponsoredPriorityTest({
1331 remoteSettingsConfig = {},
1332 nimbusSettings = {},
1337 UrlbarPrefs.set("suggest.quicksuggest.nonsponsored", true);
1338 UrlbarPrefs.set("suggest.quicksuggest.sponsored", true);
1340 const cleanUpNimbusEnable = await UrlbarTestUtils.initNimbusFeature({
1342 quickSuggestSponsoredPriority: true,
1345 await QuickSuggestTestUtils.setRemoteSettingsResults([
1348 attachment: remoteSettingsData,
1351 await QuickSuggestTestUtils.setConfig(remoteSettingsConfig);
1353 await check_results({
1354 context: createContext(searchWord, {
1355 providers: [UrlbarProviderQuickSuggest.name],
1358 matches: expectedMatches,
1361 await cleanUpNimbusEnable();