fune/browser/components/urlbar/tests/unit/test_autofill_origins.js
Marco Bonardo e9ffac0820 Bug 1846781 - Use recalc_frecency for updating origins frecency instead of triggers. r=daisuke
Until now we updated origins frecency using direct SQL triggers.
While that guaranteed good performance, it also had some downsides:
 * replacing the algorithms is complicate, the current system only works
   with a straight sum of page frecencies. We are planning to experiment with
   different algorithms in the future.
 * it requires using multiple temp tables and DELETE triggers, that is error
   prone for consumers, that may forget to DELETE from the temp tables, and thus
   break data coherency.
 * there's not much atomicity, since the origins update must be triggered apart
   and a crash would lose some of the changes

This patch is changing the behavior to be closer to the recalc_frecency one that
is already used for pages.
When a page is added, visited, or removed, recalc_frecency of its origin is set
to 1. Later frecency of invalidated origins will be recalculated in chunks.
While this is surely less efficient than the existing system, it solves the
problems presented above.
A threshold is recalculated at each chunk, and stored in the moz_meta table.
This patch continues using the old STATS in the moz_meta table, to allow for
easier downgrades. Once a new threshold will be introduced we'll be able to
stop updating those.

The after delete temp table is maintained because there's no more efficient way
to remove orphan origins promptly. Thus, after a removal from moz_places,
consumers MUST still DELETE from the temp table to cleanup orphan origins.
This also introduces a delayed removal of orphan origins when their frecency
becomes 0.

Differential Revision: https://phabricator.services.mozilla.com/D186070
2023-09-13 13:58:30 +00:00

1041 lines
29 KiB
JavaScript

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const HEURISTIC_FALLBACK_PROVIDERNAME = "HeuristicFallback";
const origin = "example.com";
async function cleanup() {
let suggestPrefs = ["history", "bookmark", "openpage"];
for (let type of suggestPrefs) {
Services.prefs.clearUserPref("browser.urlbar.suggest." + type);
}
await cleanupPlaces();
}
testEngine_setup();
registerCleanupFunction(async () => {
Services.prefs.clearUserPref("browser.urlbar.suggest.quickactions");
});
Services.prefs.setBoolPref("browser.urlbar.suggest.quickactions", false);
// "example.com/" should match http://example.com/. i.e., the search string
// should be treated as if it didn't have the trailing slash.
add_task(async function trailingSlash() {
await PlacesTestUtils.addVisits([
{
uri: "http://example.com/",
},
]);
let context = createContext(`${origin}/`, { isPrivate: false });
await check_results({
context,
autofilled: `${origin}/`,
completed: `http://${origin}/`,
matches: [
makeVisitResult(context, {
uri: `http://${origin}/`,
title: `test visit for http://${origin}/`,
heuristic: true,
}),
],
});
await cleanup();
});
// "example.com/" should match http://www.example.com/. i.e., the search string
// should be treated as if it didn't have the trailing slash.
add_task(async function trailingSlashWWW() {
await PlacesTestUtils.addVisits([
{
uri: "http://www.example.com/",
},
]);
let context = createContext(`${origin}/`, { isPrivate: false });
await check_results({
context,
autofilled: "example.com/",
completed: "http://www.example.com/",
matches: [
makeVisitResult(context, {
uri: `http://www.${origin}/`,
title: `test visit for http://www.${origin}/`,
heuristic: true,
}),
],
});
await cleanup();
});
// "ex" should match http://example.com:8888/, and the port should be completed.
add_task(async function port() {
await PlacesTestUtils.addVisits([
{
uri: "http://example.com:8888/",
},
]);
let context = createContext("ex", { isPrivate: false });
await check_results({
context,
autofilled: "example.com:8888/",
completed: "http://example.com:8888/",
matches: [
makeVisitResult(context, {
uri: `http://${origin}:8888/`,
title: `test visit for http://${origin}:8888/`,
heuristic: true,
}),
],
});
await cleanup();
});
// "example.com:8" should match http://example.com:8888/, and the port should
// be completed.
add_task(async function portPartial() {
await PlacesTestUtils.addVisits([
{
uri: "http://example.com:8888/",
},
]);
let context = createContext(`${origin}:8`, { isPrivate: false });
await check_results({
context,
autofilled: "example.com:8888/",
completed: "http://example.com:8888/",
matches: [
makeVisitResult(context, {
uri: `http://${origin}:8888/`,
title: `test visit for http://${origin}:8888/`,
heuristic: true,
}),
],
});
await cleanup();
});
// "EXaM" should match http://example.com/ and the case of the search string
// should be preserved in the autofilled value.
add_task(async function preserveCase() {
await PlacesTestUtils.addVisits([
{
uri: "http://example.com/",
},
]);
let context = createContext("EXaM", { isPrivate: false });
await check_results({
context,
autofilled: "EXaMple.com/",
completed: "http://example.com/",
matches: [
makeVisitResult(context, {
uri: `http://${origin}/`,
title: `test visit for http://${origin}/`,
heuristic: true,
}),
],
});
await cleanup();
});
// "EXaM" should match http://example.com:8888/, the port should be completed,
// and the case of the search string should be preserved in the autofilled
// value.
add_task(async function preserveCasePort() {
await PlacesTestUtils.addVisits([
{
uri: "http://example.com:8888/",
},
]);
let context = createContext("EXaM", { isPrivate: false });
await check_results({
context,
autofilled: "EXaMple.com:8888/",
completed: "http://example.com:8888/",
matches: [
makeVisitResult(context, {
uri: `http://${origin}:8888/`,
title: `test visit for http://${origin}:8888/`,
heuristic: true,
}),
],
});
await cleanup();
});
// "example.com:89" should *not* match http://example.com:8888/.
add_task(async function portNoMatch1() {
await PlacesTestUtils.addVisits([
{
uri: "http://example.com:8888/",
},
]);
let context = createContext(`${origin}:89`, { isPrivate: false });
await check_results({
context,
matches: [
makeVisitResult(context, {
source: UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
uri: `http://${origin}:89/`,
fallbackTitle: `http://${origin}:89/`,
iconUri: "",
heuristic: true,
providerName: HEURISTIC_FALLBACK_PROVIDERNAME,
}),
],
});
await cleanup();
});
// "example.com:9" should *not* match http://example.com:8888/.
add_task(async function portNoMatch2() {
await PlacesTestUtils.addVisits([
{
uri: "http://example.com:8888/",
},
]);
let context = createContext(`${origin}:9`, { isPrivate: false });
await check_results({
context,
matches: [
makeVisitResult(context, {
source: UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
uri: `http://${origin}:9/`,
fallbackTitle: `http://${origin}:9/`,
iconUri: "",
heuristic: true,
providerName: HEURISTIC_FALLBACK_PROVIDERNAME,
}),
],
});
await cleanup();
});
// "example/" should *not* match http://example.com/.
add_task(async function trailingSlash_2() {
await PlacesTestUtils.addVisits([
{
uri: "http://example.com/",
},
]);
let context = createContext("example/", { isPrivate: false });
await check_results({
context,
matches: [
makeVisitResult(context, {
source: UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
uri: "http://example/",
fallbackTitle: "http://example/",
iconUri: "page-icon:http://example/",
heuristic: true,
providerName: HEURISTIC_FALLBACK_PROVIDERNAME,
}),
],
});
await cleanup();
});
// multi.dotted.domain, search up to dot.
add_task(async function multidotted() {
await PlacesTestUtils.addVisits([
{
uri: "http://www.example.co.jp:8888/",
},
]);
let context = createContext("www.example.co.", { isPrivate: false });
await check_results({
context,
autofilled: "www.example.co.jp:8888/",
completed: "http://www.example.co.jp:8888/",
matches: [
makeVisitResult(context, {
uri: "http://www.example.co.jp:8888/",
title: "test visit for http://www.example.co.jp:8888/",
heuristic: true,
}),
],
});
await cleanup();
});
add_task(async function test_ip() {
// IP addresses have complicated rules around whether they show
// HeuristicFallback's backup search result. Flip this pref to disable that
// backup search and simplify ths subtest.
Services.prefs.setBoolPref("keyword.enabled", false);
for (let str of [
"192.168.1.1/",
"255.255.255.255:8080/",
"[2001:db8::1428:57ab]/",
"[::c0a8:5909]/",
"[::1]/",
]) {
info("testing " + str);
await PlacesTestUtils.addVisits("http://" + str);
for (let i = 1; i < str.length; ++i) {
let context = createContext(str.substring(0, i), { isPrivate: false });
await check_results({
context,
autofilled: str,
completed: "http://" + str,
matches: [
makeVisitResult(context, {
uri: "http://" + str,
title: `test visit for http://${str}`,
heuristic: true,
}),
],
});
}
await cleanup();
}
Services.prefs.clearUserPref("keyword.enabled");
});
// host starting with large number.
add_task(async function large_number_host() {
await PlacesTestUtils.addVisits([
{
uri: "http://12345example.it:8888/",
},
]);
let context = createContext("1234", { isPrivate: false });
await check_results({
context,
autofilled: "12345example.it:8888/",
completed: "http://12345example.it:8888/",
matches: [
makeVisitResult(context, {
uri: "http://12345example.it:8888/",
title: "test visit for http://12345example.it:8888/",
heuristic: true,
}),
],
});
await cleanup();
});
// When determining which origins should be autofilled, all the origins sharing
// a host should be added together to get their combined frecency -- i.e.,
// prefixes should be collapsed. And then from that list, the origin with the
// highest frecency should be chosen.
add_task(async function groupByHost() {
// Add some visits to the same host, example.com. Add one http and two https
// so that https has a higher frecency and is therefore the origin that should
// be autofilled. Also add another origin that has a higher frecency than
// both so that alone, neither http nor https would be autofilled, but added
// together they should be.
await PlacesTestUtils.addVisits([
{ uri: "http://example.com/" },
{ uri: "https://example.com/" },
{ uri: "https://example.com/" },
{ uri: "https://mozilla.org/" },
{ uri: "https://mozilla.org/" },
{ uri: "https://mozilla.org/" },
{ uri: "https://mozilla.org/" },
]);
let httpFrec = await PlacesTestUtils.getDatabaseValue(
"moz_places",
"frecency",
{ url: "http://example.com/" }
);
let httpsFrec = await PlacesTestUtils.getDatabaseValue(
"moz_places",
"frecency",
{ url: "https://example.com/" }
);
let otherFrec = await PlacesTestUtils.getDatabaseValue(
"moz_places",
"frecency",
{ url: "https://mozilla.org/" }
);
Assert.less(httpFrec, httpsFrec, "Sanity check");
Assert.less(httpsFrec, otherFrec, "Sanity check");
// Make sure the frecencies of the three origins are as expected in relation
// to the threshold.
let threshold = await getOriginAutofillThreshold();
Assert.less(httpFrec, threshold, "http origin should be < threshold");
Assert.less(httpsFrec, threshold, "https origin should be < threshold");
Assert.ok(threshold <= otherFrec, "Other origin should cross threshold");
Assert.ok(
threshold <= httpFrec + httpsFrec,
"http and https origin added together should cross threshold"
);
// The https origin should be autofilled.
let context = createContext("ex", { isPrivate: false });
await check_results({
context,
autofilled: "example.com/",
completed: "https://example.com/",
matches: [
makeVisitResult(context, {
uri: "https://example.com/",
title: "test visit for https://example.com/",
heuristic: true,
}),
],
});
await cleanup();
});
// This is the same as the previous (groupByHost), but it changes the standard
// deviation multiplier by setting the corresponding pref. This makes sure that
// the pref is respected.
add_task(async function groupByHostNonDefaultStddevMultiplier() {
let stddevMultiplier = 1.5;
Services.prefs.setCharPref(
"browser.urlbar.autoFill.stddevMultiplier",
Number(stddevMultiplier).toFixed(1)
);
await PlacesTestUtils.addVisits([
{ uri: "http://example.com/" },
{ uri: "http://example.com/" },
{ uri: "https://example.com/" },
{ uri: "https://example.com/" },
{ uri: "https://example.com/" },
{ uri: "https://foo.com/" },
{ uri: "https://foo.com/" },
{ uri: "https://foo.com/" },
{ uri: "https://mozilla.org/" },
{ uri: "https://mozilla.org/" },
{ uri: "https://mozilla.org/" },
{ uri: "https://mozilla.org/" },
{ uri: "https://mozilla.org/" },
]);
let httpFrec = await PlacesTestUtils.getDatabaseValue(
"moz_places",
"frecency",
{
url: "http://example.com/",
}
);
let httpsFrec = await PlacesTestUtils.getDatabaseValue(
"moz_places",
"frecency",
{
url: "https://example.com/",
}
);
let otherFrec = await PlacesTestUtils.getDatabaseValue(
"moz_places",
"frecency",
{
url: "https://mozilla.org/",
}
);
Assert.less(httpFrec, httpsFrec, "Sanity check");
Assert.less(httpsFrec, otherFrec, "Sanity check");
// Make sure the frecencies of the three origins are as expected in relation
// to the threshold.
let threshold = await getOriginAutofillThreshold();
Assert.less(httpFrec, threshold, "http origin should be < threshold");
Assert.less(httpsFrec, threshold, "https origin should be < threshold");
Assert.ok(threshold <= otherFrec, "Other origin should cross threshold");
Assert.ok(
threshold <= httpFrec + httpsFrec,
"http and https origin added together should cross threshold"
);
// The https origin should be autofilled.
let context = createContext("ex", { isPrivate: false });
await check_results({
context,
autofilled: "example.com/",
completed: "https://example.com/",
matches: [
makeVisitResult(context, {
uri: "https://example.com/",
title: "test visit for https://example.com/",
heuristic: true,
}),
],
});
Services.prefs.clearUserPref("browser.urlbar.autoFill.stddevMultiplier");
await cleanup();
});
// This is similar to suggestHistoryFalse_bookmark_0 in test_autofill_tasks.js,
// but it adds unbookmarked visits for multiple URLs with the same origin.
add_task(async function suggestHistoryFalse_bookmark_multiple() {
// Force only bookmarked pages to be suggested and therefore only bookmarked
// pages to be completed.
Services.prefs.setBoolPref("browser.urlbar.suggest.history", false);
let search = "ex";
let baseURL = "http://example.com/";
let bookmarkedURL = baseURL + "bookmarked";
// Add visits for three different URLs all sharing the same origin, and then
// bookmark the second one. After that, the origin should be autofilled. The
// reason for adding unbookmarked visits before and after adding the
// bookmarked visit is to make sure our aggregate SQL query for determining
// whether an origin is bookmarked is correct.
await PlacesTestUtils.addVisits([
{
uri: baseURL + "other1",
},
]);
let context = createContext(search, { isPrivate: false });
await check_results({
context,
matches: [
makeSearchResult(context, {
engineName: SUGGESTIONS_ENGINE_NAME,
providerName: HEURISTIC_FALLBACK_PROVIDERNAME,
heuristic: true,
}),
],
});
await PlacesTestUtils.addVisits([
{
uri: bookmarkedURL,
},
]);
context = createContext(search, { isPrivate: false });
await check_results({
context,
matches: [
makeSearchResult(context, {
engineName: SUGGESTIONS_ENGINE_NAME,
providerName: HEURISTIC_FALLBACK_PROVIDERNAME,
heuristic: true,
}),
],
});
await PlacesTestUtils.addVisits([
{
uri: baseURL + "other2",
},
]);
context = createContext(search, { isPrivate: false });
await check_results({
context,
matches: [
makeSearchResult(context, {
engineName: SUGGESTIONS_ENGINE_NAME,
providerName: HEURISTIC_FALLBACK_PROVIDERNAME,
heuristic: true,
}),
],
});
// Now bookmark the second URL. It should be suggested and completed.
await PlacesTestUtils.addBookmarkWithDetails({
uri: bookmarkedURL,
});
context = createContext(search, { isPrivate: false });
await check_results({
context,
autofilled: "example.com/",
completed: baseURL,
matches: [
makeVisitResult(context, {
uri: baseURL,
fallbackTitle: "example.com",
heuristic: true,
}),
makeBookmarkResult(context, {
uri: bookmarkedURL,
title: "A bookmark",
}),
],
});
await cleanup();
});
// This is similar to suggestHistoryFalse_bookmark_prefix_0 in
// autofill_test_autofill_originsAndQueries.js, but it adds unbookmarked visits
// for multiple URLs with the same origin.
add_task(async function suggestHistoryFalse_bookmark_prefix_multiple() {
// Force only bookmarked pages to be suggested and therefore only bookmarked
// pages to be completed.
Services.prefs.setBoolPref("browser.urlbar.suggest.history", false);
let search = "http://ex";
let baseURL = "http://example.com/";
let bookmarkedURL = baseURL + "bookmarked";
// Add visits for three different URLs all sharing the same origin, and then
// bookmark the second one. After that, the origin should be autofilled. The
// reason for adding unbookmarked visits before and after adding the
// bookmarked visit is to make sure our aggregate SQL query for determining
// whether an origin is bookmarked is correct.
await PlacesTestUtils.addVisits([
{
uri: baseURL + "other1",
},
]);
let context = createContext(search, { isPrivate: false });
await check_results({
context,
matches: [
makeVisitResult(context, {
source: UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
uri: `${search}/`,
fallbackTitle: `${search}/`,
iconUri: "",
heuristic: true,
providerName: HEURISTIC_FALLBACK_PROVIDERNAME,
}),
],
});
await PlacesTestUtils.addVisits([
{
uri: bookmarkedURL,
},
]);
context = createContext(search, { isPrivate: false });
await check_results({
context,
matches: [
makeVisitResult(context, {
source: UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
uri: `${search}/`,
fallbackTitle: `${search}/`,
iconUri: "",
heuristic: true,
providerName: HEURISTIC_FALLBACK_PROVIDERNAME,
}),
],
});
await PlacesTestUtils.addVisits([
{
uri: baseURL + "other2",
},
]);
context = createContext(search, { isPrivate: false });
await check_results({
context,
matches: [
makeVisitResult(context, {
source: UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
uri: `${search}/`,
fallbackTitle: `${search}/`,
iconUri: "",
heuristic: true,
providerName: HEURISTIC_FALLBACK_PROVIDERNAME,
}),
],
});
// Now bookmark the second URL. It should be suggested and completed.
await PlacesTestUtils.addBookmarkWithDetails({
uri: bookmarkedURL,
});
context = createContext(search, { isPrivate: false });
await check_results({
context,
autofilled: "http://example.com/",
completed: baseURL,
matches: [
makeVisitResult(context, {
uri: baseURL,
fallbackTitle: "example.com",
heuristic: true,
}),
makeBookmarkResult(context, {
uri: bookmarkedURL,
title: "A bookmark",
}),
],
});
await cleanup();
});
// When the autofilled URL is `example.com/`, a visit for `example.com/?` should
// not be included in the results since it dupes the autofill result.
add_task(async function searchParams() {
await PlacesTestUtils.addVisits([
"http://example.com/",
"http://example.com/?",
"http://example.com/?foo",
]);
// First, do a search with autofill disabled to make sure the visits were
// properly added. `example.com/?foo` has the highest frecency because it was
// added last; `example.com/?` has the next highest. `example.com/` dupes
// `example.com/?`, so it should not appear.
UrlbarPrefs.set("autoFill", false);
let context = createContext("ex", { isPrivate: false });
await check_results({
context,
matches: [
makeSearchResult(context, {
engineName: SUGGESTIONS_ENGINE_NAME,
providerName: HEURISTIC_FALLBACK_PROVIDERNAME,
heuristic: true,
}),
makeVisitResult(context, {
uri: "http://example.com/?foo",
title: "test visit for http://example.com/?foo",
}),
makeVisitResult(context, {
uri: "http://example.com/?",
title: "test visit for http://example.com/?",
}),
],
});
// Now do a search with autofill enabled. This time `example.com/` will be
// autofilled, and since `example.com/?` dupes it, `example.com/?` should not
// appear.
UrlbarPrefs.clear("autoFill");
context = createContext("ex", { isPrivate: false });
await check_results({
context,
autofilled: "example.com/",
completed: "http://example.com/",
matches: [
makeVisitResult(context, {
uri: "http://example.com/",
title: "test visit for http://example.com/",
heuristic: true,
}),
makeVisitResult(context, {
uri: "http://example.com/?foo",
title: "test visit for http://example.com/?foo",
}),
],
});
await cleanup();
});
// When the autofilled URL is `example.com/`, a visit for `example.com/?` should
// not be included in the results since it dupes the autofill result. (Same as
// the previous task but with https URLs instead of http. There shouldn't be any
// substantive difference.)
add_task(async function searchParams_https() {
await PlacesTestUtils.addVisits([
"https://example.com/",
"https://example.com/?",
"https://example.com/?foo",
]);
// First, do a search with autofill disabled to make sure the visits were
// properly added. `example.com/?foo` has the highest frecency because it was
// added last; `example.com/?` has the next highest. `example.com/` dupes
// `example.com/?`, so it should not appear.
UrlbarPrefs.set("autoFill", false);
let context = createContext("ex", { isPrivate: false });
await check_results({
context,
matches: [
makeSearchResult(context, {
engineName: SUGGESTIONS_ENGINE_NAME,
providerName: HEURISTIC_FALLBACK_PROVIDERNAME,
heuristic: true,
}),
makeVisitResult(context, {
uri: "https://example.com/?foo",
title: "test visit for https://example.com/?foo",
}),
makeVisitResult(context, {
uri: "https://example.com/?",
title: "test visit for https://example.com/?",
}),
],
});
// Now do a search with autofill enabled. This time `example.com/` will be
// autofilled, and since `example.com/?` dupes it, `example.com/?` should not
// appear.
UrlbarPrefs.clear("autoFill");
context = createContext("ex", { isPrivate: false });
await check_results({
context,
autofilled: "example.com/",
completed: "https://example.com/",
matches: [
makeVisitResult(context, {
uri: "https://example.com/",
title: "test visit for https://example.com/",
heuristic: true,
}),
makeVisitResult(context, {
uri: "https://example.com/?foo",
title: "test visit for https://example.com/?foo",
}),
],
});
await cleanup();
});
// Checks an origin that looks like a prefix: a scheme with no dots + a port.
add_task(async function originLooksLikePrefix() {
let hostAndPort = "localhost:8888";
let address = `http://${hostAndPort}/`;
await PlacesTestUtils.addVisits([{ uri: address }]);
// addTestSuggestionsEngine adds a search engine
// with localhost as a server, so we have to disable the
// TTS result or else it will show up as a second result
// when searching l to localhost
UrlbarPrefs.set("suggest.engines", false);
for (let search of ["lo", "localhost", "localhost:", "localhost:8888"]) {
let context = createContext(search, { isPrivate: false });
await check_results({
context,
autofilled: hostAndPort + "/",
completed: address,
matches: [
makeVisitResult(context, {
uri: address,
title: `test visit for http://${hostAndPort}/`,
heuristic: true,
}),
],
});
}
await cleanup();
});
// Checks an origin whose prefix is "about:".
add_task(async function about() {
const testData = [
{
uri: "about:config",
input: "conf",
results: [
context =>
makeSearchResult(context, {
engineName: "Suggestions",
heuristic: true,
}),
context =>
makeBookmarkResult(context, {
uri: "about:config",
title: "A bookmark",
}),
],
},
{
uri: "about:blank",
input: "about:blan",
results: [
context =>
makeVisitResult(context, {
uri: "about:blan",
fallbackTitle: "about:blan",
source: UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
heuristic: true,
}),
context =>
makeBookmarkResult(context, {
uri: "about:blank",
title: "A bookmark",
}),
],
},
];
for (const { uri, input, results } of testData) {
await PlacesTestUtils.addBookmarkWithDetails({ uri });
const context = createContext(input, { isPrivate: false });
await check_results({
context,
matches: results.map(f => f(context)),
});
await cleanup();
}
});
// Checks an origin whose prefix is "place:".
add_task(async function place() {
const testData = [
{
uri: "place:transition=7&sort=4",
input: "tran",
},
{
uri: "place:transition=7&sort=4",
input: "place:tran",
},
];
for (const { uri, input } of testData) {
await PlacesTestUtils.addBookmarkWithDetails({ uri });
const context = createContext(input, { isPrivate: false });
await check_results({
context,
matches: [
makeSearchResult(context, {
engineName: "Suggestions",
heuristic: true,
}),
],
});
await cleanup();
}
});
add_task(async function nullTitle() {
await doTitleTest({
visits: [
{
uri: "http://example.com/",
// Set title of visits data to an empty string causes
// the title to be null in the database.
title: "",
frecency: 100,
},
{
uri: "https://www.example.com/",
title: "high frecency",
frecency: 50,
},
{
uri: "http://www.example.com/",
title: "low frecency",
frecency: 1,
},
],
input: "example.com",
expected: {
autofilled: "example.com/",
completed: "http://example.com/",
matches: context => [
makeVisitResult(context, {
uri: "http://example.com/",
title: "high frecency",
heuristic: true,
}),
makeVisitResult(context, {
uri: "https://www.example.com/",
title: "high frecency",
}),
],
},
});
});
add_task(async function domainTitle() {
await doTitleTest({
visits: [
{
uri: "http://example.com/",
title: "example.com",
frecency: 100,
},
{
uri: "https://www.example.com/",
title: "",
frecency: 50,
},
{
uri: "http://www.example.com/",
title: "lowest frecency but has title",
frecency: 1,
},
],
input: "example.com",
expected: {
autofilled: "example.com/",
completed: "http://example.com/",
matches: context => [
makeVisitResult(context, {
uri: "http://example.com/",
title: "lowest frecency but has title",
heuristic: true,
}),
makeVisitResult(context, {
uri: "https://www.example.com/",
title: "www.example.com",
}),
],
},
});
});
add_task(async function exactMatchedTitle() {
await doTitleTest({
visits: [
{
uri: "http://example.com/",
title: "exact match",
frecency: 50,
},
{
uri: "https://www.example.com/",
title: "high frecency uri",
frecency: 100,
},
],
input: "http://example.com/",
expected: {
autofilled: "http://example.com/",
completed: "http://example.com/",
matches: context => [
makeVisitResult(context, {
uri: "http://example.com/",
title: "exact match",
heuristic: true,
}),
makeVisitResult(context, {
uri: "https://www.example.com/",
title: "high frecency uri",
}),
],
},
});
});
async function doTitleTest({ visits, input, expected }) {
await PlacesTestUtils.addVisits(visits);
for (const { uri, frecency } of visits) {
// Prepare data.
await PlacesUtils.withConnectionWrapper("test::doTitleTest", async db => {
await db.execute(
`UPDATE moz_places SET frecency = :frecency, recalc_frecency=0 WHERE url = :url`,
{
frecency,
url: uri,
}
);
await PlacesFrecencyRecalculator.recalculateAnyOutdatedFrecencies();
});
}
const context = createContext(input, { isPrivate: false });
await check_results({
context,
autofilled: expected.autofilled,
completed: expected.completed,
matches: expected.matches(context),
});
await cleanup();
}