From 6bf0a5cb5034a7e684dcc3500e841785237ce2dd Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 19:32:43 +0200 Subject: Adding upstream version 1:115.7.0. Signed-off-by: Daniel Baumann --- .../urlbar/tests/unit/test_search_suggestions.js | 2078 ++++++++++++++++++++ 1 file changed, 2078 insertions(+) create mode 100644 browser/components/urlbar/tests/unit/test_search_suggestions.js (limited to 'browser/components/urlbar/tests/unit/test_search_suggestions.js') diff --git a/browser/components/urlbar/tests/unit/test_search_suggestions.js b/browser/components/urlbar/tests/unit/test_search_suggestions.js new file mode 100644 index 0000000000..1a49ff2e7d --- /dev/null +++ b/browser/components/urlbar/tests/unit/test_search_suggestions.js @@ -0,0 +1,2078 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Tests that search engine suggestions are returned by + * UrlbarProviderSearchSuggestions. + */ + +const lazy = {}; + +ChromeUtils.defineESModuleGetters(lazy, { + sinon: "resource://testing-common/Sinon.sys.mjs", +}); + +const SUGGEST_PREF = "browser.urlbar.suggest.searches"; +const SUGGEST_ENABLED_PREF = "browser.search.suggest.enabled"; +const PRIVATE_ENABLED_PREF = "browser.search.suggest.enabled.private"; +const PRIVATE_SEARCH_PREF = "browser.search.separatePrivateDefault.ui.enabled"; +const TAB_TO_SEARCH_PREF = "browser.urlbar.suggest.engines"; +const TRENDING_PREF = "browser.urlbar.trending.featureGate"; +const QUICKACTIONS_PREF = "browser.urlbar.suggest.quickactions"; +const MAX_RICH_RESULTS_PREF = "browser.urlbar.maxRichResults"; +const MAX_FORM_HISTORY_PREF = "browser.urlbar.maxHistoricalSearchSuggestions"; +const SHOW_SEARCH_SUGGESTIONS_FIRST_PREF = + "browser.urlbar.showSearchSuggestionsFirst"; +const SEARCH_STRING = "hello"; + +const MAX_RESULTS = Services.prefs.getIntPref(MAX_RICH_RESULTS_PREF, 10); + +var suggestionsFn; +var previousSuggestionsFn; +let port; +let sandbox; + +/** + * Set the current suggestion funciton. + * + * @param {Function} fn + * A function that that a search string and returns an array of strings that + * will be used as search suggestions. + * Note: `fn` should return > 0 suggestions in most cases. Otherwise, you may + * encounter unexpected behaviour with UrlbarProviderSuggestion's + * _lastLowResultsSearchSuggestion safeguard. + */ +function setSuggestionsFn(fn) { + previousSuggestionsFn = suggestionsFn; + suggestionsFn = fn; +} + +async function cleanup() { + Services.prefs.clearUserPref("browser.urlbar.autoFill"); + Services.prefs.clearUserPref("browser.urlbar.autoFill.searchEngines"); + Services.prefs.clearUserPref(SUGGEST_PREF); + Services.prefs.clearUserPref(SUGGEST_ENABLED_PREF); + await PlacesUtils.bookmarks.eraseEverything(); + await PlacesUtils.history.clear(); + sandbox.restore(); +} + +async function cleanUpSuggestions() { + await cleanup(); + if (previousSuggestionsFn) { + suggestionsFn = previousSuggestionsFn; + previousSuggestionsFn = null; + } +} + +function makeFormHistoryResults(context, count) { + let results = []; + for (let i = 0; i < count; i++) { + results.push( + makeFormHistoryResult(context, { + suggestion: `${SEARCH_STRING} world Form History ${i}`, + engineName: SUGGESTIONS_ENGINE_NAME, + }) + ); + } + return results; +} + +function makeRemoteSuggestionResults( + context, + { suggestionPrefix = SEARCH_STRING, query = undefined } = {} +) { + // The suggestions function in `setup` returns: + // [searchString, searchString + "foo", searchString + "bar"] + // But when the heuristic is a search result, the muxer discards suggestion + // results that match the search string, and therefore we expect only two + // remote suggestion results, the "foo" and "bar" ones. + return [ + makeSearchResult(context, { + query, + engineName: SUGGESTIONS_ENGINE_NAME, + suggestion: suggestionPrefix + " foo", + }), + makeSearchResult(context, { + query, + engineName: SUGGESTIONS_ENGINE_NAME, + suggestion: suggestionPrefix + " bar", + }), + ]; +} + +function setResultGroups(groups) { + sandbox.restore(); + sandbox.stub(UrlbarPrefs, "resultGroups").get(() => { + return { + children: [ + // heuristic + { + maxResultCount: 1, + children: [ + { group: UrlbarUtils.RESULT_GROUP.HEURISTIC_TEST }, + { group: UrlbarUtils.RESULT_GROUP.HEURISTIC_EXTENSION }, + { group: UrlbarUtils.RESULT_GROUP.HEURISTIC_SEARCH_TIP }, + { group: UrlbarUtils.RESULT_GROUP.HEURISTIC_OMNIBOX }, + { group: UrlbarUtils.RESULT_GROUP.HEURISTIC_AUTOFILL }, + { group: UrlbarUtils.RESULT_GROUP.HEURISTIC_TOKEN_ALIAS_ENGINE }, + { group: UrlbarUtils.RESULT_GROUP.HEURISTIC_FALLBACK }, + ], + }, + // extensions using the omnibox API + { + group: UrlbarUtils.RESULT_GROUP.OMNIBOX, + }, + ...groups, + ], + }; + }); +} + +add_task(async function setup() { + sandbox = lazy.sinon.createSandbox(); + + let engine = await addTestSuggestionsEngine(searchStr => { + return suggestionsFn(searchStr); + }); + port = engine.getSubmission("").uri.port; + + setSuggestionsFn(searchStr => { + let suffixes = ["foo", "bar"]; + return [searchStr].concat(suffixes.map(s => searchStr + " " + s)); + }); + + // Install the test engine. + let oldDefaultEngine = await Services.search.getDefault(); + registerCleanupFunction(async () => { + Services.search.setDefault( + oldDefaultEngine, + Ci.nsISearchService.CHANGE_REASON_UNKNOWN + ); + Services.prefs.clearUserPref(PRIVATE_SEARCH_PREF); + Services.prefs.clearUserPref(TRENDING_PREF); + Services.prefs.clearUserPref(QUICKACTIONS_PREF); + Services.prefs.clearUserPref(TAB_TO_SEARCH_PREF); + sandbox.restore(); + }); + Services.search.setDefault(engine, Ci.nsISearchService.CHANGE_REASON_UNKNOWN); + Services.prefs.setBoolPref(PRIVATE_SEARCH_PREF, false); + Services.prefs.setBoolPref(TRENDING_PREF, false); + Services.prefs.setBoolPref(QUICKACTIONS_PREF, false); + // Tab-to-search engines can introduce unexpected results, espescially because + // they depend on real en-US engines. + Services.prefs.setBoolPref(TAB_TO_SEARCH_PREF, false); + + // Add MAX_RESULTS form history. + let context = createContext(SEARCH_STRING, { isPrivate: false }); + let entries = makeFormHistoryResults(context, MAX_RESULTS).map(r => ({ + value: r.payload.suggestion, + source: SUGGESTIONS_ENGINE_NAME, + })); + await UrlbarTestUtils.formHistory.add(entries); +}); + +add_task(async function disabled_urlbarSuggestions() { + Services.prefs.setBoolPref(SUGGEST_PREF, false); + Services.prefs.setBoolPref(SUGGEST_ENABLED_PREF, true); + let context = createContext(SEARCH_STRING, { isPrivate: false }); + await check_results({ + context, + matches: [ + makeSearchResult(context, { + engineName: SUGGESTIONS_ENGINE_NAME, + heuristic: true, + }), + ], + }); + await cleanUpSuggestions(); +}); + +add_task(async function disabled_allSuggestions() { + Services.prefs.setBoolPref(SUGGEST_PREF, true); + Services.prefs.setBoolPref(SUGGEST_ENABLED_PREF, false); + let context = createContext(SEARCH_STRING, { isPrivate: false }); + await check_results({ + context, + matches: [ + makeSearchResult(context, { + engineName: SUGGESTIONS_ENGINE_NAME, + heuristic: true, + }), + ], + }); + await cleanUpSuggestions(); +}); + +add_task(async function disabled_privateWindow() { + Services.prefs.setBoolPref(SUGGEST_PREF, true); + Services.prefs.setBoolPref(SUGGEST_ENABLED_PREF, true); + Services.prefs.setBoolPref(PRIVATE_ENABLED_PREF, false); + let context = createContext(SEARCH_STRING, { isPrivate: true }); + await check_results({ + context, + matches: [ + makeSearchResult(context, { + engineName: SUGGESTIONS_ENGINE_NAME, + heuristic: true, + }), + ], + }); + await cleanUpSuggestions(); +}); + +add_task(async function disabled_urlbarSuggestions_withRestrictionToken() { + Services.prefs.setBoolPref(SUGGEST_PREF, false); + Services.prefs.setBoolPref(SUGGEST_ENABLED_PREF, true); + let context = createContext( + `${UrlbarTokenizer.RESTRICT.SEARCH} ${SEARCH_STRING}`, + { isPrivate: false } + ); + await check_results({ + context, + matches: [ + makeSearchResult(context, { + query: SEARCH_STRING, + alias: UrlbarTokenizer.RESTRICT.SEARCH, + engineName: SUGGESTIONS_ENGINE_NAME, + heuristic: true, + }), + ...makeFormHistoryResults(context, MAX_RESULTS - 3), + ...makeRemoteSuggestionResults(context, { + query: SEARCH_STRING, + }), + ], + }); + await cleanUpSuggestions(); +}); + +add_task( + async function disabled_urlbarSuggestions_withRestrictionToken_private() { + Services.prefs.setBoolPref(SUGGEST_PREF, false); + Services.prefs.setBoolPref(SUGGEST_ENABLED_PREF, true); + Services.prefs.setBoolPref(PRIVATE_ENABLED_PREF, false); + let context = createContext( + `${UrlbarTokenizer.RESTRICT.SEARCH} ${SEARCH_STRING}`, + { isPrivate: true } + ); + await check_results({ + context, + matches: [ + makeSearchResult(context, { + query: SEARCH_STRING, + alias: UrlbarTokenizer.RESTRICT.SEARCH, + engineName: SUGGESTIONS_ENGINE_NAME, + heuristic: true, + }), + ], + }); + await cleanUpSuggestions(); + } +); + +add_task( + async function disabled_urlbarSuggestions_withRestrictionToken_private_enabled() { + Services.prefs.setBoolPref(SUGGEST_PREF, false); + Services.prefs.setBoolPref(SUGGEST_ENABLED_PREF, true); + Services.prefs.setBoolPref(PRIVATE_ENABLED_PREF, true); + let context = createContext( + `${UrlbarTokenizer.RESTRICT.SEARCH} ${SEARCH_STRING}`, + { isPrivate: true } + ); + await check_results({ + context, + matches: [ + makeSearchResult(context, { + query: SEARCH_STRING, + alias: UrlbarTokenizer.RESTRICT.SEARCH, + engineName: SUGGESTIONS_ENGINE_NAME, + heuristic: true, + }), + ...makeFormHistoryResults(context, MAX_RESULTS - 3), + ...makeRemoteSuggestionResults(context, { + query: SEARCH_STRING, + }), + ], + }); + await cleanUpSuggestions(); + } +); + +add_task(async function enabled_by_pref_privateWindow() { + Services.prefs.setBoolPref(SUGGEST_PREF, true); + Services.prefs.setBoolPref(SUGGEST_ENABLED_PREF, true); + Services.prefs.setBoolPref(PRIVATE_ENABLED_PREF, true); + let context = createContext(SEARCH_STRING, { isPrivate: true }); + await check_results({ + context, + matches: [ + makeSearchResult(context, { + engineName: SUGGESTIONS_ENGINE_NAME, + heuristic: true, + }), + ...makeFormHistoryResults(context, MAX_RESULTS - 3), + ...makeRemoteSuggestionResults(context), + ], + }); + await cleanUpSuggestions(); + + Services.prefs.clearUserPref(PRIVATE_ENABLED_PREF); +}); + +add_task(async function singleWordQuery() { + Services.prefs.setBoolPref(SUGGEST_PREF, true); + Services.prefs.setBoolPref(SUGGEST_ENABLED_PREF, true); + let context = createContext(SEARCH_STRING, { isPrivate: false }); + + await check_results({ + context, + matches: [ + makeSearchResult(context, { + engineName: SUGGESTIONS_ENGINE_NAME, + heuristic: true, + }), + ...makeFormHistoryResults(context, MAX_RESULTS - 3), + ...makeRemoteSuggestionResults(context), + ], + }); + + await cleanUpSuggestions(); +}); + +add_task(async function multiWordQuery() { + Services.prefs.setBoolPref(SUGGEST_PREF, true); + Services.prefs.setBoolPref(SUGGEST_ENABLED_PREF, true); + const query = `${SEARCH_STRING} world`; + let context = createContext(query, { isPrivate: false }); + await check_results({ + context, + matches: [ + makeSearchResult(context, { + engineName: SUGGESTIONS_ENGINE_NAME, + heuristic: true, + }), + ...makeFormHistoryResults(context, MAX_RESULTS - 3), + ...makeRemoteSuggestionResults(context, { + suggestionPrefix: query, + }), + ], + }); + + await cleanUpSuggestions(); +}); + +add_task(async function suffixMatch() { + Services.prefs.setBoolPref(SUGGEST_PREF, true); + Services.prefs.setBoolPref(SUGGEST_ENABLED_PREF, true); + + setSuggestionsFn(searchStr => { + let prefixes = ["baz", "quux"]; + return prefixes.map(p => p + " " + searchStr); + }); + + let context = createContext(SEARCH_STRING, { isPrivate: false }); + + await check_results({ + context, + matches: [ + makeSearchResult(context, { + engineName: SUGGESTIONS_ENGINE_NAME, + heuristic: true, + }), + ...makeFormHistoryResults(context, MAX_RESULTS - 3), + makeSearchResult(context, { + engineName: SUGGESTIONS_ENGINE_NAME, + suggestion: "baz " + SEARCH_STRING, + }), + makeSearchResult(context, { + engineName: SUGGESTIONS_ENGINE_NAME, + suggestion: "quux " + SEARCH_STRING, + }), + ], + }); + + await cleanUpSuggestions(); +}); + +add_task(async function remoteSuggestionsDupeSearchString() { + Services.prefs.setIntPref(MAX_FORM_HISTORY_PREF, 0); + + // Return remote suggestions with the trimmed search string, the uppercased + // search string, and the search string with a trailing space, plus the usual + // "foo" and "bar" suggestions. + setSuggestionsFn(searchStr => { + let suffixes = ["foo", "bar"]; + return [searchStr.trim(), searchStr.toUpperCase(), searchStr + " "].concat( + suffixes.map(s => searchStr + " " + s) + ); + }); + + // Do a search with a trailing space. All the variations of the search string + // with regard to spaces and case should be discarded from the remote + // suggestions, leaving only the usual "foo" and "bar" suggestions. + let query = SEARCH_STRING + " "; + let context = createContext(query, { isPrivate: false }); + await check_results({ + context, + matches: [ + makeSearchResult(context, { + query, + engineName: SUGGESTIONS_ENGINE_NAME, + heuristic: true, + }), + ...makeRemoteSuggestionResults(context), + ], + }); + + await cleanUpSuggestions(); + Services.prefs.clearUserPref(MAX_FORM_HISTORY_PREF); +}); + +add_task(async function queryIsNotASubstring() { + Services.prefs.setBoolPref(SUGGEST_PREF, true); + + setSuggestionsFn(searchStr => { + return ["aaa", "bbb"]; + }); + + let context = createContext(SEARCH_STRING, { isPrivate: false }); + + await check_results({ + context, + matches: [ + makeSearchResult(context, { + engineName: SUGGESTIONS_ENGINE_NAME, + heuristic: true, + }), + ...makeFormHistoryResults(context, MAX_RESULTS - 3), + makeSearchResult(context, { + engineName: SUGGESTIONS_ENGINE_NAME, + suggestion: "aaa", + }), + makeSearchResult(context, { + engineName: SUGGESTIONS_ENGINE_NAME, + suggestion: "bbb", + }), + ], + }); + + await cleanUpSuggestions(); +}); + +add_task(async function restrictToken() { + Services.prefs.setBoolPref(SUGGEST_PREF, true); + Services.prefs.setBoolPref(SUGGEST_ENABLED_PREF, true); + + // Add a visit and a bookmark. Actually, make the bookmark visited too so + // that it's guaranteed, with its higher frecency, to appear above the search + // suggestions. + await PlacesTestUtils.addVisits([ + { + uri: Services.io.newURI(`http://example.com/${SEARCH_STRING}-visit`), + title: `${SEARCH_STRING} visit`, + }, + { + uri: Services.io.newURI(`http://example.com/${SEARCH_STRING}-bookmark`), + title: `${SEARCH_STRING} bookmark`, + }, + ]); + + await PlacesTestUtils.addBookmarkWithDetails({ + uri: Services.io.newURI(`http://example.com/${SEARCH_STRING}-bookmark`), + title: `${SEARCH_STRING} bookmark`, + }); + + let context = createContext(SEARCH_STRING, { isPrivate: false }); + + // Do an unrestricted search to make sure everything appears in it, including + // the visit and bookmark. + await check_results({ + context, + matches: [ + makeSearchResult(context, { + engineName: SUGGESTIONS_ENGINE_NAME, + heuristic: true, + }), + ...makeFormHistoryResults(context, MAX_RESULTS - 5), + ...makeRemoteSuggestionResults(context), + makeBookmarkResult(context, { + uri: `http://example.com/${SEARCH_STRING}-bookmark`, + title: `${SEARCH_STRING} bookmark`, + }), + makeVisitResult(context, { + uri: `http://example.com/${SEARCH_STRING}-visit`, + title: `${SEARCH_STRING} visit`, + }), + ], + }); + + // Now do a restricted search to make sure only suggestions appear. + context = createContext( + `${UrlbarTokenizer.RESTRICT.SEARCH} ${SEARCH_STRING}`, + { + isPrivate: false, + } + ); + await check_results({ + context, + matches: [ + makeSearchResult(context, { + engineName: SUGGESTIONS_ENGINE_NAME, + alias: UrlbarTokenizer.RESTRICT.SEARCH, + query: SEARCH_STRING, + heuristic: true, + }), + ...makeFormHistoryResults(context, MAX_RESULTS - 3), + ...makeRemoteSuggestionResults(context, { + suggestionPrefix: SEARCH_STRING, + query: SEARCH_STRING, + }), + ], + }); + + // Typing the search restriction char shows the Search Engine entry and local + // results. + context = createContext(UrlbarTokenizer.RESTRICT.SEARCH, { + isPrivate: false, + }); + await check_results({ + context, + matches: [ + makeSearchResult(context, { + engineName: SUGGESTIONS_ENGINE_NAME, + query: "", + heuristic: true, + }), + ...makeFormHistoryResults(context, MAX_RESULTS - 1), + ], + }); + + // Also if followed by multiple spaces. + context = createContext(`${UrlbarTokenizer.RESTRICT.SEARCH} `, { + isPrivate: false, + }); + await check_results({ + context, + matches: [ + makeSearchResult(context, { + engineName: SUGGESTIONS_ENGINE_NAME, + alias: UrlbarTokenizer.RESTRICT.SEARCH, + query: "", + heuristic: true, + }), + ...makeFormHistoryResults(context, MAX_RESULTS - 1), + ], + }); + + // If followed by any char we should fetch suggestions. + // Note this uses "h" to match form history. + context = createContext(`${UrlbarTokenizer.RESTRICT.SEARCH}h`, { + isPrivate: false, + }); + await check_results({ + context, + matches: [ + makeSearchResult(context, { + engineName: SUGGESTIONS_ENGINE_NAME, + query: "h", + heuristic: true, + }), + ...makeFormHistoryResults(context, MAX_RESULTS - 3), + ...makeRemoteSuggestionResults(context, { + suggestionPrefix: "h", + query: "h", + }), + ], + }); + + // Also if followed by a space and single char. + context = createContext(`${UrlbarTokenizer.RESTRICT.SEARCH} h`, { + isPrivate: false, + }); + await check_results({ + context, + matches: [ + makeSearchResult(context, { + engineName: SUGGESTIONS_ENGINE_NAME, + alias: UrlbarTokenizer.RESTRICT.SEARCH, + query: "h", + heuristic: true, + }), + ...makeFormHistoryResults(context, MAX_RESULTS - 3), + ...makeRemoteSuggestionResults(context, { + suggestionPrefix: "h", + query: "h", + }), + ], + }); + + // Leading search-mode restriction tokens are removed. + context = createContext( + `${UrlbarTokenizer.RESTRICT.BOOKMARK} ${SEARCH_STRING}`, + { isPrivate: false } + ); + await check_results({ + context, + matches: [ + makeSearchResult(context, { + source: UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL, + heuristic: true, + query: SEARCH_STRING, + alias: UrlbarTokenizer.RESTRICT.BOOKMARK, + }), + makeBookmarkResult(context, { + uri: `http://example.com/${SEARCH_STRING}-bookmark`, + title: `${SEARCH_STRING} bookmark`, + }), + ], + }); + + // Non-search-mode restriction tokens remain in the query and heuristic search + // result. + let token; + for (let t of Object.values(UrlbarTokenizer.RESTRICT)) { + if (!UrlbarTokenizer.SEARCH_MODE_RESTRICT.has(t)) { + token = t; + break; + } + } + Assert.ok( + token, + "Non-search-mode restrict token exists -- if not, you can probably remove me!" + ); + context = createContext(token, { + isPrivate: false, + }); + await check_results({ + context, + matches: [ + makeSearchResult(context, { + engineName: SUGGESTIONS_ENGINE_NAME, + heuristic: true, + }), + ], + }); + + await cleanUpSuggestions(); +}); + +add_task(async function mixup_frecency() { + Services.prefs.setBoolPref(SUGGEST_PREF, true); + + // At most, we should have 22 results in this subtest. We set this to 30 to + // make we're not cutting off any results and we are actually getting 22. + Services.prefs.setIntPref(MAX_RICH_RESULTS_PREF, 30); + + // Add a visit and a bookmark. Actually, make the bookmark visited too so + // that it's guaranteed, with its higher frecency, to appear above the search + // suggestions. + await PlacesTestUtils.addVisits([ + { + uri: Services.io.newURI("http://example.com/lo0"), + title: `${SEARCH_STRING} low frecency 0`, + }, + { + uri: Services.io.newURI("http://example.com/lo1"), + title: `${SEARCH_STRING} low frecency 1`, + }, + { + uri: Services.io.newURI("http://example.com/lo2"), + title: `${SEARCH_STRING} low frecency 2`, + }, + { + uri: Services.io.newURI("http://example.com/lo3"), + title: `${SEARCH_STRING} low frecency 3`, + }, + { + uri: Services.io.newURI("http://example.com/lo4"), + title: `${SEARCH_STRING} low frecency 4`, + }, + ]); + + for (let i = 0; i < 5; i++) { + await PlacesTestUtils.addVisits([ + { + uri: Services.io.newURI("http://example.com/hi0"), + title: `${SEARCH_STRING} high frecency 0`, + transition: Ci.nsINavHistoryService.TRANSITION_TYPED, + }, + { + uri: Services.io.newURI("http://example.com/hi1"), + title: `${SEARCH_STRING} high frecency 1`, + transition: Ci.nsINavHistoryService.TRANSITION_TYPED, + }, + { + uri: Services.io.newURI("http://example.com/hi2"), + title: `${SEARCH_STRING} high frecency 2`, + transition: Ci.nsINavHistoryService.TRANSITION_TYPED, + }, + { + uri: Services.io.newURI("http://example.com/hi3"), + title: `${SEARCH_STRING} high frecency 3`, + transition: Ci.nsINavHistoryService.TRANSITION_TYPED, + }, + ]); + } + + for (let i = 0; i < 4; i++) { + let href = `http://example.com/hi${i}`; + await PlacesTestUtils.addBookmarkWithDetails({ + uri: href, + title: `${SEARCH_STRING} high frecency ${i}`, + }); + } + + // Do an unrestricted search to make sure everything appears in it, including + // the visit and bookmark. + let context = createContext(SEARCH_STRING, { isPrivate: false }); + await check_results({ + context, + matches: [ + makeSearchResult(context, { + engineName: SUGGESTIONS_ENGINE_NAME, + heuristic: true, + }), + ...makeFormHistoryResults(context, MAX_RESULTS), + ...makeRemoteSuggestionResults(context), + makeBookmarkResult(context, { + uri: "http://example.com/hi3", + title: `${SEARCH_STRING} high frecency 3`, + }), + makeBookmarkResult(context, { + uri: "http://example.com/hi2", + title: `${SEARCH_STRING} high frecency 2`, + }), + makeBookmarkResult(context, { + uri: "http://example.com/hi1", + title: `${SEARCH_STRING} high frecency 1`, + }), + makeBookmarkResult(context, { + uri: "http://example.com/hi0", + title: `${SEARCH_STRING} high frecency 0`, + }), + makeVisitResult(context, { + uri: "http://example.com/lo4", + title: `${SEARCH_STRING} low frecency 4`, + }), + makeVisitResult(context, { + uri: "http://example.com/lo3", + title: `${SEARCH_STRING} low frecency 3`, + }), + makeVisitResult(context, { + uri: "http://example.com/lo2", + title: `${SEARCH_STRING} low frecency 2`, + }), + makeVisitResult(context, { + uri: "http://example.com/lo1", + title: `${SEARCH_STRING} low frecency 1`, + }), + makeVisitResult(context, { + uri: "http://example.com/lo0", + title: `${SEARCH_STRING} low frecency 0`, + }), + ], + }); + + // Change the mixup. + setResultGroups([ + // 1 suggestion + { + maxResultCount: 1, + children: [ + { group: UrlbarUtils.RESULT_GROUP.FORM_HISTORY }, + { group: UrlbarUtils.RESULT_GROUP.REMOTE_SUGGESTION }, + ], + }, + // 5 general + { + maxResultCount: 5, + group: UrlbarUtils.RESULT_GROUP.GENERAL, + }, + // 1 suggestion + { + maxResultCount: 1, + children: [ + { group: UrlbarUtils.RESULT_GROUP.FORM_HISTORY }, + { group: UrlbarUtils.RESULT_GROUP.REMOTE_SUGGESTION }, + ], + }, + // remaining general + { group: UrlbarUtils.RESULT_GROUP.GENERAL }, + // remaining suggestions + { group: UrlbarUtils.RESULT_GROUP.FORM_HISTORY }, + { group: UrlbarUtils.RESULT_GROUP.REMOTE_SUGGESTION }, + ]); + + // Do an unrestricted search to make sure everything appears in it, including + // the visits and bookmarks. + context = createContext(SEARCH_STRING, { isPrivate: false }); + await check_results({ + context, + matches: [ + makeSearchResult(context, { + engineName: SUGGESTIONS_ENGINE_NAME, + heuristic: true, + }), + ...makeFormHistoryResults(context, 1), + makeBookmarkResult(context, { + uri: "http://example.com/hi3", + title: `${SEARCH_STRING} high frecency 3`, + }), + makeBookmarkResult(context, { + uri: "http://example.com/hi2", + title: `${SEARCH_STRING} high frecency 2`, + }), + makeBookmarkResult(context, { + uri: "http://example.com/hi1", + title: `${SEARCH_STRING} high frecency 1`, + }), + makeBookmarkResult(context, { + uri: "http://example.com/hi0", + title: `${SEARCH_STRING} high frecency 0`, + }), + makeVisitResult(context, { + uri: "http://example.com/lo4", + title: `${SEARCH_STRING} low frecency 4`, + }), + ...makeFormHistoryResults(context, 2).slice(1), + makeVisitResult(context, { + uri: "http://example.com/lo3", + title: `${SEARCH_STRING} low frecency 3`, + }), + makeVisitResult(context, { + uri: "http://example.com/lo2", + title: `${SEARCH_STRING} low frecency 2`, + }), + makeVisitResult(context, { + uri: "http://example.com/lo1", + title: `${SEARCH_STRING} low frecency 1`, + }), + makeVisitResult(context, { + uri: "http://example.com/lo0", + title: `${SEARCH_STRING} low frecency 0`, + }), + ...makeFormHistoryResults(context, MAX_RESULTS).slice(2), + ...makeRemoteSuggestionResults(context), + ], + }); + + Services.prefs.clearUserPref(MAX_RICH_RESULTS_PREF); + await cleanUpSuggestions(); +}); + +add_task(async function prohibit_suggestions() { + Services.prefs.setBoolPref(SUGGEST_PREF, true); + Services.prefs.setBoolPref( + `browser.fixup.domainwhitelist.${SEARCH_STRING}`, + false + ); + + let context = createContext(SEARCH_STRING, { isPrivate: false }); + await check_results({ + context, + matches: [ + makeSearchResult(context, { + engineName: SUGGESTIONS_ENGINE_NAME, + heuristic: true, + }), + ...makeFormHistoryResults(context, MAX_RESULTS - 3), + ...makeRemoteSuggestionResults(context), + ], + }); + + Services.prefs.setBoolPref( + `browser.fixup.domainwhitelist.${SEARCH_STRING}`, + true + ); + registerCleanupFunction(() => { + Services.prefs.setBoolPref( + `browser.fixup.domainwhitelist.${SEARCH_STRING}`, + false + ); + }); + context = createContext(SEARCH_STRING, { isPrivate: false }); + await check_results({ + context, + matches: [ + makeVisitResult(context, { + source: UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL, + uri: `http://${SEARCH_STRING}/`, + fallbackTitle: `http://${SEARCH_STRING}/`, + iconUri: "", + heuristic: true, + }), + ...makeFormHistoryResults(context, MAX_RESULTS - 2), + makeSearchResult(context, { + engineName: SUGGESTIONS_ENGINE_NAME, + heuristic: false, + }), + ], + }); + + // When using multiple words, we should still get suggestions: + let query = `${SEARCH_STRING} world`; + context = createContext(query, { isPrivate: false }); + await check_results({ + context, + matches: [ + makeSearchResult(context, { + engineName: SUGGESTIONS_ENGINE_NAME, + heuristic: true, + }), + ...makeFormHistoryResults(context, MAX_RESULTS - 3), + ...makeRemoteSuggestionResults(context, { suggestionPrefix: query }), + ], + }); + + // Clear the whitelist for SEARCH_STRING and try preferring DNS for any single + // word instead: + Services.prefs.setBoolPref( + `browser.fixup.domainwhitelist.${SEARCH_STRING}`, + false + ); + Services.prefs.setBoolPref("browser.fixup.dns_first_for_single_words", true); + registerCleanupFunction(() => { + Services.prefs.clearUserPref("browser.fixup.dns_first_for_single_words"); + }); + + context = createContext(SEARCH_STRING, { isPrivate: false }); + await check_results({ + context, + matches: [ + makeVisitResult(context, { + source: UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL, + uri: `http://${SEARCH_STRING}/`, + fallbackTitle: `http://${SEARCH_STRING}/`, + iconUri: "", + heuristic: true, + }), + ...makeFormHistoryResults(context, MAX_RESULTS - 2), + makeSearchResult(context, { + engineName: SUGGESTIONS_ENGINE_NAME, + heuristic: false, + }), + ], + }); + + context = createContext("somethingelse", { isPrivate: false }); + await check_results({ + context, + matches: [ + makeVisitResult(context, { + source: UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL, + uri: "http://somethingelse/", + fallbackTitle: "http://somethingelse/", + iconUri: "", + heuristic: true, + }), + makeSearchResult(context, { + engineName: SUGGESTIONS_ENGINE_NAME, + heuristic: false, + }), + ], + }); + + // When using multiple words, we should still get suggestions: + query = `${SEARCH_STRING} world`; + context = createContext(query, { isPrivate: false }); + await check_results({ + context, + matches: [ + makeSearchResult(context, { + engineName: SUGGESTIONS_ENGINE_NAME, + heuristic: true, + }), + ...makeFormHistoryResults(context, MAX_RESULTS - 3), + ...makeRemoteSuggestionResults(context, { suggestionPrefix: query }), + ], + }); + + Services.prefs.clearUserPref("browser.fixup.dns_first_for_single_words"); + + context = createContext("http://1.2.3.4/", { isPrivate: false }); + await check_results({ + context, + matches: [ + makeVisitResult(context, { + source: UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL, + uri: "http://1.2.3.4/", + fallbackTitle: "http://1.2.3.4/", + iconUri: "page-icon:http://1.2.3.4/", + heuristic: true, + }), + ], + }); + + context = createContext("[2001::1]:30", { isPrivate: false }); + await check_results({ + context, + matches: [ + makeVisitResult(context, { + source: UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL, + uri: "http://[2001::1]:30/", + fallbackTitle: "http://[2001::1]:30/", + iconUri: "", + heuristic: true, + }), + ], + }); + + context = createContext("user:pass@test", { isPrivate: false }); + await check_results({ + context, + matches: [ + makeVisitResult(context, { + source: UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL, + uri: "http://user:pass@test/", + fallbackTitle: "http://user:pass@test/", + iconUri: "", + heuristic: true, + }), + ], + }); + + context = createContext("data:text/plain,Content", { isPrivate: false }); + await check_results({ + context, + matches: [ + makeVisitResult(context, { + source: UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL, + uri: "data:text/plain,Content", + fallbackTitle: "data:text/plain,Content", + iconUri: "", + heuristic: true, + }), + ], + }); + + context = createContext("a", { isPrivate: false }); + await check_results({ + context, + matches: [ + makeSearchResult(context, { + engineName: SUGGESTIONS_ENGINE_NAME, + heuristic: true, + }), + ], + }); + + await cleanUpSuggestions(); +}); + +add_task(async function uri_like_queries() { + Services.prefs.setBoolPref(SUGGEST_PREF, true); + Services.prefs.setBoolPref(SUGGEST_ENABLED_PREF, true); + + // We should not fetch any suggestions for an actual URL. + let query = "mozilla.org"; + let context = createContext(query, { isPrivate: false }); + await check_results({ + context, + matches: [ + makeVisitResult(context, { + source: UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL, + fallbackTitle: `http://${query}/`, + uri: `http://${query}/`, + iconUri: "", + heuristic: true, + }), + makeSearchResult(context, { query, engineName: SUGGESTIONS_ENGINE_NAME }), + ], + }); + + // We should also not fetch suggestions for a partially-typed URL. + query = "mozilla.o"; + context = createContext(query, { isPrivate: false }); + await check_results({ + context, + matches: [ + makeSearchResult(context, { + engineName: SUGGESTIONS_ENGINE_NAME, + heuristic: true, + }), + ], + }); + + // Now trying queries that could be confused for URLs. They should return + // results. + const uriLikeQueries = [ + "mozilla.org is a great website", + "I like mozilla.org", + "a/b testing", + "he/him", + "Google vs.", + "5.8 cm", + ]; + for (query of uriLikeQueries) { + context = createContext(query, { isPrivate: false }); + await check_results({ + context, + matches: [ + makeSearchResult(context, { + engineName: SUGGESTIONS_ENGINE_NAME, + heuristic: true, + }), + ...makeRemoteSuggestionResults(context, { + suggestionPrefix: query, + }), + ], + }); + } + + await cleanUpSuggestions(); +}); + +add_task(async function avoid_remote_url_suggestions_1() { + Services.prefs.setBoolPref(SUGGEST_PREF, true); + setSuggestionsFn(searchStr => { + let suffixes = [".com", "/test", ":1]", "@test", ". com"]; + return suffixes.map(s => searchStr + s); + }); + + const query = "test"; + + await UrlbarTestUtils.formHistory.add([`${query}.com`]); + + let context = createContext(query, { isPrivate: false }); + await check_results({ + context, + matches: [ + makeSearchResult(context, { + engineName: SUGGESTIONS_ENGINE_NAME, + heuristic: true, + }), + makeFormHistoryResult(context, { + engineName: SUGGESTIONS_ENGINE_NAME, + suggestion: `${query}.com`, + }), + makeSearchResult(context, { + engineName: SUGGESTIONS_ENGINE_NAME, + suggestion: `${query}. com`, + }), + ], + }); + + await cleanUpSuggestions(); + await UrlbarTestUtils.formHistory.remove([`${query}.com`]); +}); + +add_task(async function avoid_remote_url_suggestions_2() { + Services.prefs.setBoolPref(SUGGEST_PREF, true); + Services.prefs.setBoolPref("browser.urlbar.autoFill", false); + + setSuggestionsFn(searchStr => { + let suffixes = ["ed", "eds"]; + return suffixes.map(s => searchStr + s); + }); + + let context = createContext("htt", { isPrivate: false }); + await check_results({ + context, + matches: [ + makeSearchResult(context, { + engineName: SUGGESTIONS_ENGINE_NAME, + heuristic: true, + }), + makeSearchResult(context, { + engineName: SUGGESTIONS_ENGINE_NAME, + suggestion: "htted", + }), + makeSearchResult(context, { + engineName: SUGGESTIONS_ENGINE_NAME, + suggestion: "htteds", + }), + ], + }); + + context = createContext("ftp", { isPrivate: false }); + await check_results({ + context, + matches: [ + makeSearchResult(context, { + engineName: SUGGESTIONS_ENGINE_NAME, + heuristic: true, + }), + makeSearchResult(context, { + engineName: SUGGESTIONS_ENGINE_NAME, + suggestion: "ftped", + }), + makeSearchResult(context, { + engineName: SUGGESTIONS_ENGINE_NAME, + suggestion: "ftpeds", + }), + ], + }); + + context = createContext("http", { isPrivate: false }); + await check_results({ + context, + matches: [ + makeSearchResult(context, { + engineName: SUGGESTIONS_ENGINE_NAME, + heuristic: true, + }), + makeSearchResult(context, { + engineName: SUGGESTIONS_ENGINE_NAME, + suggestion: "httped", + }), + makeSearchResult(context, { + engineName: SUGGESTIONS_ENGINE_NAME, + suggestion: "httpeds", + }), + ], + }); + + context = createContext("http:", { isPrivate: false }); + await check_results({ + context, + matches: [ + makeSearchResult(context, { + engineName: SUGGESTIONS_ENGINE_NAME, + heuristic: true, + }), + ], + }); + + context = createContext("https", { isPrivate: false }); + await check_results({ + context, + matches: [ + makeSearchResult(context, { + engineName: SUGGESTIONS_ENGINE_NAME, + heuristic: true, + }), + makeSearchResult(context, { + engineName: SUGGESTIONS_ENGINE_NAME, + suggestion: "httpsed", + }), + makeSearchResult(context, { + engineName: SUGGESTIONS_ENGINE_NAME, + suggestion: "httpseds", + }), + ], + }); + + context = createContext("https:", { isPrivate: false }); + await check_results({ + context, + matches: [ + makeSearchResult(context, { + engineName: SUGGESTIONS_ENGINE_NAME, + heuristic: true, + }), + ], + }); + + context = createContext("httpd", { isPrivate: false }); + await check_results({ + context, + matches: [ + makeSearchResult(context, { + engineName: SUGGESTIONS_ENGINE_NAME, + heuristic: true, + }), + makeSearchResult(context, { + engineName: SUGGESTIONS_ENGINE_NAME, + suggestion: "httpded", + }), + makeSearchResult(context, { + engineName: SUGGESTIONS_ENGINE_NAME, + suggestion: "httpdeds", + }), + ], + }); + + // Check FTP disabled + context = createContext("ftp:", { isPrivate: false }); + await check_results({ + context, + matches: [ + makeSearchResult(context, { + engineName: SUGGESTIONS_ENGINE_NAME, + heuristic: true, + }), + ], + }); + + context = createContext("ftp:/", { isPrivate: false }); + await check_results({ + context, + matches: [ + makeSearchResult(context, { + engineName: SUGGESTIONS_ENGINE_NAME, + heuristic: true, + }), + ], + }); + + context = createContext("ftp://", { isPrivate: false }); + await check_results({ + context, + matches: [ + makeSearchResult(context, { + engineName: SUGGESTIONS_ENGINE_NAME, + heuristic: true, + }), + ], + }); + + context = createContext("ftp://test", { isPrivate: false }); + await check_results({ + context, + matches: [ + makeVisitResult(context, { + source: UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL, + uri: "ftp://test/", + fallbackTitle: "ftp://test/", + iconUri: "", + heuristic: true, + }), + ], + }); + + context = createContext("http:/", { isPrivate: false }); + await check_results({ + context, + matches: [ + makeSearchResult(context, { + engineName: SUGGESTIONS_ENGINE_NAME, + heuristic: true, + }), + ], + }); + + context = createContext("https:/", { isPrivate: false }); + await check_results({ + context, + matches: [ + makeSearchResult(context, { + engineName: SUGGESTIONS_ENGINE_NAME, + heuristic: true, + }), + ], + }); + + context = createContext("http://", { isPrivate: false }); + await check_results({ + context, + matches: [ + makeSearchResult(context, { + engineName: SUGGESTIONS_ENGINE_NAME, + heuristic: true, + }), + ], + }); + + context = createContext("https://", { isPrivate: false }); + await check_results({ + context, + matches: [ + makeSearchResult(context, { + engineName: SUGGESTIONS_ENGINE_NAME, + heuristic: true, + }), + ], + }); + + context = createContext("http://www", { isPrivate: false }); + await check_results({ + context, + matches: [ + makeVisitResult(context, { + source: UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL, + uri: "http://www/", + fallbackTitle: "http://www/", + iconUri: "", + heuristic: true, + }), + ], + }); + + context = createContext("https://www", { isPrivate: false }); + await check_results({ + context, + matches: [ + makeVisitResult(context, { + source: UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL, + uri: "https://www/", + fallbackTitle: "https://www/", + iconUri: "", + heuristic: true, + }), + ], + }); + + context = createContext("http://test", { isPrivate: false }); + await check_results({ + context, + matches: [ + makeVisitResult(context, { + source: UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL, + uri: "http://test/", + fallbackTitle: "http://test/", + iconUri: "", + heuristic: true, + }), + ], + }); + + context = createContext("https://test", { isPrivate: false }); + await check_results({ + context, + matches: [ + makeVisitResult(context, { + source: UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL, + uri: "https://test/", + fallbackTitle: "https://test/", + iconUri: "", + heuristic: true, + }), + ], + }); + + context = createContext("http://www.test", { isPrivate: false }); + await check_results({ + context, + matches: [ + makeVisitResult(context, { + source: UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL, + uri: "http://www.test/", + fallbackTitle: "http://www.test/", + iconUri: "", + heuristic: true, + }), + ], + }); + + context = createContext("http://www.test.com", { isPrivate: false }); + await check_results({ + context, + matches: [ + makeVisitResult(context, { + source: UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL, + uri: "http://www.test.com/", + fallbackTitle: "http://www.test.com/", + iconUri: "", + heuristic: true, + }), + ], + }); + + context = createContext("file", { isPrivate: false }); + await check_results({ + context, + matches: [ + makeSearchResult(context, { + engineName: SUGGESTIONS_ENGINE_NAME, + heuristic: true, + }), + makeSearchResult(context, { + engineName: SUGGESTIONS_ENGINE_NAME, + suggestion: "fileed", + }), + makeSearchResult(context, { + engineName: SUGGESTIONS_ENGINE_NAME, + suggestion: "fileeds", + }), + ], + }); + + context = createContext("file:", { isPrivate: false }); + await check_results({ + context, + matches: [ + makeSearchResult(context, { + engineName: SUGGESTIONS_ENGINE_NAME, + heuristic: true, + }), + ], + }); + + context = createContext("file:///Users", { isPrivate: false }); + await check_results({ + context, + matches: [ + makeVisitResult(context, { + source: UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL, + uri: "file:///Users", + fallbackTitle: "file:///Users", + iconUri: "", + heuristic: true, + }), + ], + }); + + context = createContext("moz-test://", { isPrivate: false }); + await check_results({ + context, + matches: [ + makeSearchResult(context, { + engineName: SUGGESTIONS_ENGINE_NAME, + heuristic: true, + }), + ], + }); + + context = createContext("moz+test://", { isPrivate: false }); + await check_results({ + context, + matches: [ + makeSearchResult(context, { + engineName: SUGGESTIONS_ENGINE_NAME, + heuristic: true, + }), + ], + }); + + context = createContext("about", { isPrivate: false }); + await check_results({ + context, + matches: [ + makeSearchResult(context, { + engineName: SUGGESTIONS_ENGINE_NAME, + heuristic: true, + }), + makeSearchResult(context, { + engineName: SUGGESTIONS_ENGINE_NAME, + suggestion: "abouted", + }), + makeSearchResult(context, { + engineName: SUGGESTIONS_ENGINE_NAME, + suggestion: "abouteds", + }), + ], + }); + + await cleanUpSuggestions(); +}); + +add_task(async function restrict_remote_suggestions_after_no_results() { + // We don't fetch remote suggestions if a query with a length over + // maxCharsForSearchSuggestions returns 0 results. We set it to 4 here to + // avoid constructing a 100+ character string. + Services.prefs.setIntPref("browser.urlbar.maxCharsForSearchSuggestions", 4); + setSuggestionsFn(searchStr => { + return []; + }); + + const query = SEARCH_STRING.substring(0, SEARCH_STRING.length - 1); + let context = createContext(query, { isPrivate: false }); + await check_results({ + context, + matches: [ + makeSearchResult(context, { + engineName: SUGGESTIONS_ENGINE_NAME, + heuristic: true, + }), + ...makeFormHistoryResults(context, MAX_RESULTS - 1), + ], + }); + + context = createContext(SEARCH_STRING, { isPrivate: false }); + await check_results({ + context, + matches: [ + makeSearchResult(context, { + engineName: SUGGESTIONS_ENGINE_NAME, + heuristic: true, + }), + ...makeFormHistoryResults(context, MAX_RESULTS - 1), + // Because the previous search returned no suggestions, we will not fetch + // remote suggestions for this query that is just a longer version of the + // previous query. + ], + }); + + // Do one more search before resetting maxCharsForSearchSuggestions to reset + // the search suggestion provider's _lastLowResultsSearchSuggestion property. + // Otherwise it will be stuck at SEARCH_STRING, which interferes with + // subsequent tests. + context = createContext("not the search string", { isPrivate: false }); + await check_results({ + context, + matches: [ + makeSearchResult(context, { + engineName: SUGGESTIONS_ENGINE_NAME, + heuristic: true, + }), + ], + }); + + Services.prefs.clearUserPref("browser.urlbar.maxCharsForSearchSuggestions"); + + await cleanUpSuggestions(); +}); + +add_task(async function formHistory() { + Services.prefs.setBoolPref(SUGGEST_PREF, true); + Services.prefs.setBoolPref(SUGGEST_ENABLED_PREF, true); + + // `maxHistoricalSearchSuggestions` is no longer treated as a max count but as + // a boolean: If it's zero, then the user has opted out of form history so we + // shouldn't include any at all; if it's non-zero, then we include form + // history according to the limits specified in the muxer's result groups. + + // zero => no form history + Services.prefs.setIntPref(MAX_FORM_HISTORY_PREF, 0); + let context = createContext(SEARCH_STRING, { isPrivate: false }); + await check_results({ + context, + matches: [ + makeSearchResult(context, { + engineName: SUGGESTIONS_ENGINE_NAME, + heuristic: true, + }), + ...makeRemoteSuggestionResults(context), + ], + }); + + // non-zero => allow form history + Services.prefs.setIntPref(MAX_FORM_HISTORY_PREF, 1); + context = createContext(SEARCH_STRING, { isPrivate: false }); + await check_results({ + context, + matches: [ + makeSearchResult(context, { + engineName: SUGGESTIONS_ENGINE_NAME, + heuristic: true, + }), + ...makeFormHistoryResults(context, MAX_RESULTS - 3), + ...makeRemoteSuggestionResults(context), + ], + }); + + // non-zero => allow form history + Services.prefs.setIntPref(MAX_FORM_HISTORY_PREF, 2); + context = createContext(SEARCH_STRING, { isPrivate: false }); + await check_results({ + context, + matches: [ + makeSearchResult(context, { + engineName: SUGGESTIONS_ENGINE_NAME, + heuristic: true, + }), + ...makeFormHistoryResults(context, MAX_RESULTS - 3), + ...makeRemoteSuggestionResults(context), + ], + }); + + Services.prefs.clearUserPref(MAX_FORM_HISTORY_PREF); + + // Do a search for exactly the suggestion of the first form history result. + // The heuristic's query should be the suggestion; the first form history + // result should not be included since it dupes the heuristic; the other form + // history results should not be included since they don't match; and both + // remote suggestions should be included. + let firstSuggestion = makeFormHistoryResults(context, 1)[0].payload + .suggestion; + context = createContext(firstSuggestion, { isPrivate: false }); + await check_results({ + context, + matches: [ + makeSearchResult(context, { + engineName: SUGGESTIONS_ENGINE_NAME, + heuristic: true, + }), + ...makeRemoteSuggestionResults(context, { + suggestionPrefix: firstSuggestion, + }), + ], + }); + + // Do the same search but in uppercase with a trailing space. We should get + // the same results, i.e., the form history result dupes the trimmed search + // string so it shouldn't be included. + let query = firstSuggestion.toUpperCase() + " "; + context = createContext(query, { isPrivate: false }); + await check_results({ + context, + matches: [ + makeSearchResult(context, { + query, + engineName: SUGGESTIONS_ENGINE_NAME, + heuristic: true, + }), + ...makeRemoteSuggestionResults(context, { + suggestionPrefix: firstSuggestion.toUpperCase(), + }), + ], + }); + + // Add a form history entry that dupes the first remote suggestion and do a + // search that triggers both. The form history should be included but the + // remote suggestion should not since it dupes the form history. + let suggestionPrefix = "dupe"; + let dupeSuggestion = makeRemoteSuggestionResults(context, { + suggestionPrefix, + })[0].payload.suggestion; + Assert.ok(dupeSuggestion, "Sanity check: dupeSuggestion is defined"); + await UrlbarTestUtils.formHistory.add([dupeSuggestion]); + + context = createContext(suggestionPrefix, { isPrivate: false }); + await check_results({ + context, + matches: [ + makeSearchResult(context, { + engineName: SUGGESTIONS_ENGINE_NAME, + heuristic: true, + }), + makeFormHistoryResult(context, { + suggestion: dupeSuggestion, + engineName: SUGGESTIONS_ENGINE_NAME, + }), + ...makeRemoteSuggestionResults(context, { suggestionPrefix }).slice(1), + ], + }); + + await UrlbarTestUtils.formHistory.remove([dupeSuggestion]); + + // Add these form history strings to use below. + let formHistoryStrings = ["foo", "FOO ", "foobar", "fooquux"]; + await UrlbarTestUtils.formHistory.add(formHistoryStrings); + + // Search for "foo". "foo" and "FOO " shouldn't be included since they dupe + // the heuristic. Both "foobar" and "fooquux" should be included even though + // the max form history count is only two and there are four matching form + // history results (including the discarded "foo" and "FOO "). + context = createContext("foo", { isPrivate: false }); + await check_results({ + context, + matches: [ + makeSearchResult(context, { + engineName: SUGGESTIONS_ENGINE_NAME, + heuristic: true, + }), + makeFormHistoryResult(context, { + suggestion: "foobar", + engineName: SUGGESTIONS_ENGINE_NAME, + }), + makeFormHistoryResult(context, { + suggestion: "fooquux", + engineName: SUGGESTIONS_ENGINE_NAME, + }), + ...makeRemoteSuggestionResults(context, { + suggestionPrefix: "foo", + }), + ], + }); + + // Add a visit that matches "foo" and will autofill so that the heuristic is + // not a search result. Now the "foo" and "foobar" form history should be + // included. The "foo" remote suggestion should not be included since it + // dupes the "foo" form history. + await PlacesTestUtils.addVisits("http://foo.example.com/"); + context = createContext("foo", { isPrivate: false }); + await check_results({ + context, + matches: [ + makeVisitResult(context, { + source: UrlbarUtils.RESULT_SOURCE.HISTORY, + uri: "http://foo.example.com/", + title: "test visit for http://foo.example.com/", + heuristic: true, + }), + makeFormHistoryResult(context, { + suggestion: "foo", + engineName: SUGGESTIONS_ENGINE_NAME, + }), + makeFormHistoryResult(context, { + suggestion: "foobar", + engineName: SUGGESTIONS_ENGINE_NAME, + }), + makeFormHistoryResult(context, { + suggestion: "fooquux", + engineName: SUGGESTIONS_ENGINE_NAME, + }), + ...makeRemoteSuggestionResults(context, { + suggestionPrefix: "foo", + }), + ], + }); + await PlacesUtils.history.clear(); + + // Add SERPs for "foobar", "fooBAR ", and "food", and search for "foo". The + // "foo" form history should be excluded since it dupes the heuristic; the + // "foobar" and "fooquux" form history should be included; the "food" SERP + // should be included since it doesn't dupe either form history result; and + // the "foobar" and "fooBAR " SERPs depend on the result groups, see below. + let engine = await Services.search.getDefault(); + let serpURLs = ["foobar", "fooBAR ", "food"].map( + term => UrlbarUtils.getSearchQueryUrl(engine, term)[0] + ); + await PlacesTestUtils.addVisits(serpURLs); + + // First set showSearchSuggestionsFirst = false so that general results appear + // before suggestions, which means that the muxer visits the "foobar" and + // "fooBAR " SERPs before visiting the "foobar" form history, and so it + // doesn't see that these two SERPs dupe the form history. They are therefore + // included. + Services.prefs.setBoolPref(SHOW_SEARCH_SUGGESTIONS_FIRST_PREF, false); + context = createContext("foo", { isPrivate: false }); + await check_results({ + context, + matches: [ + makeSearchResult(context, { + engineName: SUGGESTIONS_ENGINE_NAME, + heuristic: true, + }), + makeVisitResult(context, { + uri: `http://localhost:${port}/search?q=food`, + title: `test visit for http://localhost:${port}/search?q=food`, + }), + makeVisitResult(context, { + uri: `http://localhost:${port}/search?q=fooBAR+`, + title: `test visit for http://localhost:${port}/search?q=fooBAR+`, + }), + makeVisitResult(context, { + uri: `http://localhost:${port}/search?q=foobar`, + title: `test visit for http://localhost:${port}/search?q=foobar`, + }), + makeFormHistoryResult(context, { + suggestion: "foobar", + engineName: SUGGESTIONS_ENGINE_NAME, + }), + makeFormHistoryResult(context, { + suggestion: "fooquux", + engineName: SUGGESTIONS_ENGINE_NAME, + }), + ...makeRemoteSuggestionResults(context, { + suggestionPrefix: "foo", + }), + ], + }); + + // Now clear showSearchSuggestionsFirst so that suggestions appear before + // general results. Now the muxer will see that the "foobar" and "fooBAR " + // SERPs dupe the "foobar" form history, so it will exclude them. + Services.prefs.clearUserPref(SHOW_SEARCH_SUGGESTIONS_FIRST_PREF); + context = createContext("foo", { isPrivate: false }); + await check_results({ + context, + matches: [ + makeSearchResult(context, { + engineName: SUGGESTIONS_ENGINE_NAME, + heuristic: true, + }), + makeFormHistoryResult(context, { + suggestion: "foobar", + engineName: SUGGESTIONS_ENGINE_NAME, + }), + makeFormHistoryResult(context, { + suggestion: "fooquux", + engineName: SUGGESTIONS_ENGINE_NAME, + }), + ...makeRemoteSuggestionResults(context, { + suggestionPrefix: "foo", + }), + makeVisitResult(context, { + uri: `http://localhost:${port}/search?q=food`, + title: `test visit for http://localhost:${port}/search?q=food`, + }), + ], + }); + + await UrlbarTestUtils.formHistory.remove(formHistoryStrings); + + await cleanUpSuggestions(); + await PlacesUtils.history.clear(); +}); + +// When the heuristic is hidden, search results that match the heuristic should +// be included and not deduped. +add_task(async function hideHeuristic() { + UrlbarPrefs.set("experimental.hideHeuristic", true); + UrlbarPrefs.set("browser.search.suggest.enabled", true); + UrlbarPrefs.set("suggest.searches", true); + let context = createContext(SEARCH_STRING, { isPrivate: false }); + await check_results({ + context, + matches: [ + makeSearchResult(context, { + engineName: SUGGESTIONS_ENGINE_NAME, + heuristic: true, + }), + ...makeFormHistoryResults(context, MAX_RESULTS - 3), + makeSearchResult(context, { + query: SEARCH_STRING, + engineName: SUGGESTIONS_ENGINE_NAME, + suggestion: SEARCH_STRING, + }), + ...makeRemoteSuggestionResults(context), + ], + }); + await cleanUpSuggestions(); + UrlbarPrefs.clear("experimental.hideHeuristic"); +}); + +// When the heuristic is hidden, form history results that match the heuristic +// should be included and not deduped. +add_task(async function hideHeuristic_formHistory() { + UrlbarPrefs.set("experimental.hideHeuristic", true); + UrlbarPrefs.set("browser.search.suggest.enabled", true); + UrlbarPrefs.set("suggest.searches", true); + + // Search for exactly the suggestion of the first form history result. + // Expected results: + // + // * First form history should be included even though it dupes the heuristic + // * Other form history should not be included because they don't match the + // search string + // * The first remote suggestion that just echoes the search string should not + // be included because it dupes the first form history + // * The remaining remote suggestions should be included because they don't + // dupe anything + let context = createContext(SEARCH_STRING, { isPrivate: false }); + let firstFormHistory = makeFormHistoryResults(context, 1)[0]; + context = createContext(firstFormHistory.payload.suggestion, { + isPrivate: false, + }); + await check_results({ + context, + matches: [ + makeSearchResult(context, { + engineName: SUGGESTIONS_ENGINE_NAME, + heuristic: true, + }), + firstFormHistory, + ...makeRemoteSuggestionResults(context, { + suggestionPrefix: firstFormHistory.payload.suggestion, + }), + ], + }); + + // Add these form history strings to use below. + let formHistoryStrings = ["foo", "FOO ", "foobar", "fooquux"]; + await UrlbarTestUtils.formHistory.add(formHistoryStrings); + + // Search for "foo". Expected results: + // + // * "foo" form history should be included even though it dupes the heuristic + // * "FOO " form history should not be included because it dupes the "foo" + // form history + // * "foobar" and "fooqux" form history should be included because they don't + // dupe anything + // * "foo" remote suggestion should not be included because it dupes the "foo" + // form history + // * "foo foo" and "foo bar" remote suggestions should be included because + // they don't dupe anything + context = createContext("foo", { isPrivate: false }); + await check_results({ + context, + matches: [ + makeSearchResult(context, { + engineName: SUGGESTIONS_ENGINE_NAME, + heuristic: true, + }), + makeFormHistoryResult(context, { + suggestion: "foo", + engineName: SUGGESTIONS_ENGINE_NAME, + }), + makeFormHistoryResult(context, { + suggestion: "foobar", + engineName: SUGGESTIONS_ENGINE_NAME, + }), + makeFormHistoryResult(context, { + suggestion: "fooquux", + engineName: SUGGESTIONS_ENGINE_NAME, + }), + ...makeRemoteSuggestionResults(context, { + suggestionPrefix: "foo", + }), + ], + }); + + // Add SERPs for "foo" and "food", and search for "foo". Expected results: + // + // * "foo" form history should be included even though it dupes the heuristic + // * "foobar" and "fooqux" form history should be included because they don't + // dupe anything + // * "foo" SERP depends on `showSearchSuggestionsFirst`, see below + // * "food" SERP should be include because it doesn't dupe anything + // * "foo" remote suggestion should not be included because it dupes the "foo" + // form history + // * "foo foo" and "foo bar" remote suggestions should be included because + // they don't dupe anything + let engine = await Services.search.getDefault(); + let serpURLs = ["foo", "food"].map( + term => UrlbarUtils.getSearchQueryUrl(engine, term)[0] + ); + await PlacesTestUtils.addVisits(serpURLs); + + // With `showSearchSuggestionsFirst = false` so that general results appear + // before suggestions, the muxer visits the "foo" (and "food") SERPs before + // visiting the "foo" form history, and so it doesn't see that the "foo" SERP + // dupes the form history. The SERP is therefore included. + UrlbarPrefs.set("showSearchSuggestionsFirst", false); + context = createContext("foo", { isPrivate: false }); + await check_results({ + context, + matches: [ + makeSearchResult(context, { + engineName: SUGGESTIONS_ENGINE_NAME, + heuristic: true, + }), + makeVisitResult(context, { + uri: `http://localhost:${port}/search?q=food`, + title: `test visit for http://localhost:${port}/search?q=food`, + }), + makeVisitResult(context, { + uri: `http://localhost:${port}/search?q=foo`, + title: `test visit for http://localhost:${port}/search?q=foo`, + }), + makeFormHistoryResult(context, { + suggestion: "foo", + engineName: SUGGESTIONS_ENGINE_NAME, + }), + makeFormHistoryResult(context, { + suggestion: "foobar", + engineName: SUGGESTIONS_ENGINE_NAME, + }), + makeFormHistoryResult(context, { + suggestion: "fooquux", + engineName: SUGGESTIONS_ENGINE_NAME, + }), + ...makeRemoteSuggestionResults(context, { + suggestionPrefix: "foo", + }), + ], + }); + + // Now clear `showSearchSuggestionsFirst` so that suggestions appear before + // general results. Now the muxer will see that the "foo" SERP dupes the "foo" + // form history, so it will exclude it. + UrlbarPrefs.clear("showSearchSuggestionsFirst"); + context = createContext("foo", { isPrivate: false }); + await check_results({ + context, + matches: [ + makeSearchResult(context, { + engineName: SUGGESTIONS_ENGINE_NAME, + heuristic: true, + }), + makeFormHistoryResult(context, { + suggestion: "foo", + engineName: SUGGESTIONS_ENGINE_NAME, + }), + makeFormHistoryResult(context, { + suggestion: "foobar", + engineName: SUGGESTIONS_ENGINE_NAME, + }), + makeFormHistoryResult(context, { + suggestion: "fooquux", + engineName: SUGGESTIONS_ENGINE_NAME, + }), + ...makeRemoteSuggestionResults(context, { + suggestionPrefix: "foo", + }), + makeVisitResult(context, { + uri: `http://localhost:${port}/search?q=food`, + title: `test visit for http://localhost:${port}/search?q=food`, + }), + ], + }); + + await UrlbarTestUtils.formHistory.remove(formHistoryStrings); + + await cleanUpSuggestions(); + UrlbarPrefs.clear("experimental.hideHeuristic"); +}); -- cgit v1.2.3