Source code
Revision control
Copy as Markdown
Other Tools
Test Info: Warnings
- This test runs only with pattern: os != 'android'
- Manifest: toolkit/components/places/tests/unit/xpcshell.toml
/* Any copyright is dedicated to the Public Domain.
/**
* Tests for integrating interaction table data with interactions based
* frecency. If the interaction data is considered "interesting", frecency
* should experience a boost.
*
* Since we don't know the precise values of the score, the tests typically
* have a pattern of caching a baseline without interactions and then checking
* what happens when either interesting or un-interesting interactions are
* inserted.
*/
"use strict";
// The Viewtime Threshold preference is stored in seconds. When recorded in
// places, the data is stored in milliseconds.
const VIEWTIME_THRESHOLD =
Services.prefs.getIntPref(
"places.frecency.pages.interactions.viewTimeSeconds"
) * 1000;
const MANY_KEYPRESSES_THRESHOLD = Services.prefs.getIntPref(
"places.frecency.pages.interactions.manyKeypresses"
);
// The Viewtime Threshold for Keypresses preference is stored in seconds.
// When recorded in places, the data is stored in milliseconds.
const VIEWTIME_IF_MANY_KEYPRESSES_THRESHOLD =
Services.prefs.getIntPref(
"places.frecency.pages.interactions.viewTimeIfManyKeypressesSeconds"
) * 1000;
const SAMPLED_VISITS_THRESHOLD = Services.prefs.getIntPref(
"places.frecency.pages.numSampledVisits"
);
const MAX_VISIT_GAP = Services.prefs.getIntPref(
"places.frecency.pages.interactions.maxVisitGapSeconds"
);
async function insertIntoMozPlacesMetadata(
place_id,
{
referrer_place_id = null,
created_at = Date.now(),
updated_at = Date.now(),
total_view_time = 0,
typing_time = 0,
key_presses = 0,
scrolling_time = 0,
scrolling_distance = 0,
document_type = 0,
search_query_id = null,
}
) {
info("Inserting interaction into moz_places_metadata.");
await PlacesUtils.withConnectionWrapper(
"test_frecency_interactions::insertIntoMozPlacesMetadata",
async db => {
await db.execute(
`
INSERT INTO moz_places_metadata (
place_id,
referrer_place_id,
created_at,
updated_at,
total_view_time,
typing_time,
key_presses,
scrolling_time,
scrolling_distance,
document_type,
search_query_id
) VALUES (
:place_id,
:referrer_place_id,
:created_at,
:updated_at,
:total_view_time,
:typing_time,
:key_presses,
:scrolling_time,
:scrolling_distance,
:document_type,
:search_query_id
)
`,
{
place_id,
referrer_place_id,
created_at,
updated_at,
total_view_time,
typing_time,
key_presses,
scrolling_time,
scrolling_distance,
document_type,
search_query_id,
}
);
}
);
}
async function insertIntoMozPlaces({
url,
guid,
url_hash,
origin_id,
frecency,
}) {
await PlacesUtils.withConnectionWrapper(
"test_frecency_interactions::insertIntoMozPlaces",
async db => {
await db.execute(
`
INSERT INTO moz_places (
url,
guid,
url_hash,
origin_id,
frecency
) VALUES (
:url,
:guid,
:url_hash,
:origin_id,
:frecency
)
`,
{
url,
guid,
url_hash,
origin_id,
frecency,
}
);
}
);
}
async function getPageWithUrl(url) {
info(`Find ${url} in moz_places.`);
let db = await PlacesUtils.promiseDBConnection();
let rows = await db.execute(`SELECT * FROM moz_places WHERE url = :url`, {
url,
});
Assert.equal(rows.length, 1, "Found one matching row in moz_places.");
return rows.map(r => ({
id: r.getResultByName("id"),
url: r.getResultByName("url"),
title: r.getResultByName("title"),
frecency: r.getResultByName("frecency"),
recalc_frecency: r.getResultByName("recalc_frecency"),
}))[0];
}
add_setup(async function () {
registerCleanupFunction(PlacesUtils.history.clear);
});
/**
* Each of the interactions occur at the same time as the visit so they
* are paired as one sample.
*/
add_task(async function one_visit_one_matching_interaction() {
const TESTS = [
{
title: "View time is under threshold",
interactionData: {
total_view_time: VIEWTIME_THRESHOLD - 1,
},
expectIncrease: false,
},
{
title: "View time exceeds threshold",
interactionData: {
total_view_time: VIEWTIME_THRESHOLD,
},
expectIncrease: true,
},
{
title: "View time and key presses under threshold",
interactionData: {
total_view_time: VIEWTIME_IF_MANY_KEYPRESSES_THRESHOLD - 1,
key_presses: MANY_KEYPRESSES_THRESHOLD,
},
expectIncrease: false,
},
{
title: "View time and key presses under threshold",
interactionData: {
total_view_time: VIEWTIME_IF_MANY_KEYPRESSES_THRESHOLD,
key_presses: MANY_KEYPRESSES_THRESHOLD - 1,
},
expectIncrease: false,
},
{
title: "View time and key presses exceed threshold",
interactionData: {
total_view_time: VIEWTIME_IF_MANY_KEYPRESSES_THRESHOLD,
key_presses: MANY_KEYPRESSES_THRESHOLD,
},
expectIncrease: true,
},
];
for (let test of TESTS) {
info(`Running test: ${test.title}`);
let url = "https://testdomain1.moz.org/";
await PlacesTestUtils.addVisits([url]);
let page = await getPageWithUrl(url);
let oldFrecency = page.frecency;
Assert.notEqual(
oldFrecency,
0,
"Frecency with a visit but no interaction should not be zero."
);
await PlacesFrecencyRecalculator.recalculateAnyOutdatedFrecencies();
Assert.equal(
page.frecency,
oldFrecency,
`Frecency of ${url} should not have changed.`
);
await insertIntoMozPlacesMetadata(page.id, test.interactionData);
await PlacesFrecencyRecalculator.recalculateAnyOutdatedFrecencies();
page = await getPageWithUrl(url);
if (test.expectIncrease) {
Assert.greater(
page.frecency,
oldFrecency,
`Frecency of ${url} should have increased.`
);
} else {
Assert.equal(
page.frecency,
oldFrecency,
`Frecency of ${url} should not have changed.`
);
}
await PlacesUtils.history.clear();
}
});
/**
* Each of the interactions are one day before the visit so the interaction
* and visit are separate samples.
*/
add_task(async function one_visit_one_non_matching_interaction() {
const today = new Date();
const yesterday = new Date();
yesterday.setDate(today.getDate() - 1);
const TESTS = [
{
title: "View time is under threshold",
interactionData: {
total_view_time: VIEWTIME_THRESHOLD - 1,
created_at: yesterday.getTime(),
},
expectIncrease: false,
},
{
title: "View time exceeds threshold",
interactionData: {
total_view_time: VIEWTIME_THRESHOLD,
created_at: yesterday.getTime(),
},
expectIncrease: true,
},
{
title: "View time and key presses under threshold",
interactionData: {
total_view_time: VIEWTIME_IF_MANY_KEYPRESSES_THRESHOLD - 1,
key_presses: MANY_KEYPRESSES_THRESHOLD,
created_at: yesterday.getTime(),
},
expectIncrease: false,
},
{
title: "View time and key presses under threshold",
interactionData: {
total_view_time: VIEWTIME_IF_MANY_KEYPRESSES_THRESHOLD,
key_presses: MANY_KEYPRESSES_THRESHOLD - 1,
created_at: yesterday.getTime(),
},
expectIncrease: false,
},
{
title: "View time and key presses exceed threshold",
interactionData: {
total_view_time: VIEWTIME_IF_MANY_KEYPRESSES_THRESHOLD,
key_presses: MANY_KEYPRESSES_THRESHOLD,
created_at: yesterday.getTime(),
},
expectIncrease: true,
},
];
for (let test of TESTS) {
info(test.title);
let url = "https://testdomain1.moz.org/";
await PlacesTestUtils.addVisits([url]);
let page = await getPageWithUrl(url);
let oldFrecency = page.frecency;
await PlacesFrecencyRecalculator.recalculateAnyOutdatedFrecencies();
Assert.equal(
page.frecency,
oldFrecency,
`Frecency of ${url} should not have changed.`
);
await insertIntoMozPlacesMetadata(page.id, test.interactionData);
await PlacesFrecencyRecalculator.recalculateAnyOutdatedFrecencies();
page = await getPageWithUrl(url);
if (test.expectIncrease) {
Assert.greater(
page.frecency,
oldFrecency,
`Frecency of ${url} should have increased.`
);
} else {
Assert.equal(
page.frecency,
oldFrecency,
`Frecency of ${url} should not have changed.`
);
}
await PlacesUtils.history.clear();
}
});
add_task(async function zero_visits_one_interaction() {
const TESTS = [
{
title: "View time is under threshold",
interactionData: {
total_view_time: VIEWTIME_THRESHOLD - 1,
},
},
{
title: "View time is at threshold",
interactionData: {
total_view_time: VIEWTIME_THRESHOLD,
},
},
];
for (let test of TESTS) {
info(test.title);
let url = "https://testdomain1.moz.org/";
await insertIntoMozPlaces({
url,
url_hash: "1234567890",
frecency: 0,
});
let page = await getPageWithUrl(url);
await PlacesFrecencyRecalculator.recalculateAnyOutdatedFrecencies();
Assert.equal(page.frecency, 0, "Frecency is zero");
await insertIntoMozPlacesMetadata(page.id, test.interactionData);
await PlacesFrecencyRecalculator.recalculateAnyOutdatedFrecencies();
page = await getPageWithUrl(url);
Assert.equal(
page.frecency,
0,
"Frecency remains 0 because frecency was at zero."
);
await PlacesUtils.history.clear();
}
});
// When the sample threshold is reached, check the correct samples are
// used. This is done by inserting the same number of visits as the sample
// threshold and adding an interaction that doesn't belong to any of the visits
// before or after the visits.
add_task(async function max_samples_threshold() {
const today = new Date();
const yesterday = new Date();
const twoDaysAgo = new Date();
yesterday.setDate(today.getDate() - 1);
twoDaysAgo.setDate(today.getDate() - 2);
const TESTS = [
{
title: "Interactions are beyond sample threshold",
interactionData: {
total_view_time: VIEWTIME_THRESHOLD,
created_at: twoDaysAgo.getTime(),
},
expectIncrease: false,
},
{
title: "Interactions are within sample threshold",
interactionData: {
total_view_time: VIEWTIME_THRESHOLD,
created_at: today.getTime(),
},
expectIncrease: true,
},
];
for (let test of TESTS) {
info(test.title);
let url = "https://testdomain1.moz.org/";
for (let i = 0; i < SAMPLED_VISITS_THRESHOLD; ++i) {
await PlacesTestUtils.addVisits([
{
url,
visitDate: yesterday.getTime() * 1000,
},
]);
}
let page = await getPageWithUrl(url);
await PlacesFrecencyRecalculator.recalculateAnyOutdatedFrecencies();
Assert.notEqual(page.frecency, 0, "Frecency is non-zero");
let oldFrecency = page.frecency;
await insertIntoMozPlacesMetadata(page.id, test.interactionData);
await PlacesFrecencyRecalculator.recalculateAnyOutdatedFrecencies();
page = await getPageWithUrl(url);
if (test.expectIncrease) {
Assert.notEqual(page.frecency, 0, "Frecency is non zero");
Assert.greater(
page.frecency,
oldFrecency,
"Frecency greater than the old value."
);
} else {
Assert.equal(page.frecency, oldFrecency, "Frecency didn't change");
}
await PlacesUtils.history.clear();
}
});
// Verify that the number of interactions contributing to frecency
// is as expected. Insert an old visit to ensure the URL has a positive
// frecency. Ensure all interactions are treated as virtual visits by making
// them interesting and more recent than the oldest visit.
// Each virtual visit should increase the frecency score until
// the threshold is exceeded. Interactions are deliberately inserted
// sequentially in the past, as the date can affect the score.
add_task(async function max_interesting_interactions() {
const today = new Date();
// We deliberately set a visit that can't be grouped with an interaction.
const yearAgo = new Date();
yearAgo.setMonth(yearAgo.getMonth() - 12);
let url = "https://testdomain1.moz.org/";
await PlacesTestUtils.addVisits({
url,
visitDate: yearAgo.getTime() * 1000,
transition: PlacesUtils.history.TRANSITIONS.LINK,
});
let page = await getPageWithUrl(url);
await PlacesFrecencyRecalculator.recalculateAnyOutdatedFrecencies();
Assert.greater(page.frecency, 0, "Frecency is above zero");
info("Insert interesting interactions.");
for (let i = 0; i < SAMPLED_VISITS_THRESHOLD; ++i) {
let pageBefore = await getPageWithUrl(url);
await insertIntoMozPlacesMetadata(page.id, {
total_view_time: VIEWTIME_THRESHOLD,
created_at: today.getTime() - i,
});
await PlacesFrecencyRecalculator.recalculateAnyOutdatedFrecencies();
let pageAfter = await getPageWithUrl(url);
Assert.greater(
pageAfter.frecency,
pageBefore.frecency,
"Frecency has increased."
);
}
info("Insert an interesting interaction beyond the threshold.");
let pageBefore = await getPageWithUrl(url);
await insertIntoMozPlacesMetadata(page.id, {
total_view_time: VIEWTIME_THRESHOLD,
// Choose a time that would make this below all other existing interactions.
created_at: today.getTime() - 100,
});
await PlacesFrecencyRecalculator.recalculateAnyOutdatedFrecencies();
let pageAfter = await getPageWithUrl(url);
Assert.equal(
pageBefore.frecency,
pageAfter.frecency,
"Frecency is the same."
);
await PlacesUtils.history.clear();
});
add_task(async function temp_redirect_frecency() {
const yesterday = new Date();
let url1 = "http://testdomain1.moz.org/";
let url2 = "https://testdomain2.moz.org/";
let url3 = "https://testdomain2.moz.org/dashboard";
const visitDate = yesterday * 1000;
await PlacesTestUtils.addVisits([
{
url: url1,
visitDate,
transition: PlacesUtils.history.TRANSITIONS.TYPED,
},
{
url: url2,
visitDate,
transition: PlacesUtils.history.TRANSITIONS.REDIRECT_PERMANENT,
referrer: Services.io.newURI(url1),
},
{
url: url3,
visitDate,
transition: PlacesUtils.history.TRANSITIONS.REDIRECT_TEMPORARY,
referrer: Services.io.newURI(url2),
},
]);
await PlacesFrecencyRecalculator.recalculateAnyOutdatedFrecencies();
let page1 = await getPageWithUrl(url1);
Assert.notEqual(page1.frecency, 0, "Frecency is non-zero");
let page2 = await getPageWithUrl(url2);
Assert.notEqual(page2.frecency, 0, "Frecency is non-zero");
let page3 = await getPageWithUrl(url3);
Assert.notEqual(page3.frecency, 0, "Frecency is non-zero");
info("For each visit, add an interesting interaction.");
const created_at = yesterday.getTime();
const updated_at = yesterday.getTime();
await insertIntoMozPlacesMetadata(page1.id, {
total_view_time: VIEWTIME_THRESHOLD,
created_at,
updated_at,
});
await insertIntoMozPlacesMetadata(page2.id, {
total_view_time: VIEWTIME_THRESHOLD,
created_at,
updated_at,
});
await insertIntoMozPlacesMetadata(page3.id, {
total_view_time: VIEWTIME_THRESHOLD,
created_at,
updated_at,
});
await PlacesFrecencyRecalculator.recalculateAnyOutdatedFrecencies();
let newPage1 = await getPageWithUrl(url1);
Assert.equal(newPage1.frecency, page1.frecency, "Frecency is the same");
let newPage2 = await getPageWithUrl(url2);
Assert.equal(newPage2.frecency, page2.frecency, "Frecency is the same");
let newPage3 = await getPageWithUrl(url3);
Assert.greater(newPage3.frecency, page3.frecency, "Frecency has increased");
await PlacesUtils.history.clear();
});
add_task(async function interaction_visit_gap() {
let url = "https://testdomain1.moz.org/";
let now = new Date();
let maxVisitGapMs = MAX_VISIT_GAP * 1000;
// Insert visits that match the number of sample threshold to avoid
// interesting interactions becoming virtual visits.
for (let i = 0; i < SAMPLED_VISITS_THRESHOLD; ++i) {
await PlacesTestUtils.addVisits([
{
url,
visitDate: now,
},
]);
}
await PlacesFrecencyRecalculator.recalculateAnyOutdatedFrecencies();
let page = await getPageWithUrl(url);
Assert.notEqual(page.frecency, 0, "Frecency is not zero");
info("Add an interaction just below the visit gap.");
await insertIntoMozPlacesMetadata(page.id, {
total_view_time: VIEWTIME_THRESHOLD,
created_at: now.getTime() - maxVisitGapMs - 1,
updated_at: now.getTime() - maxVisitGapMs - 1,
});
await PlacesFrecencyRecalculator.recalculateAnyOutdatedFrecencies();
let updatedPage = await getPageWithUrl(url);
Assert.equal(updatedPage.frecency, page.frecency, "Frecency didn't change.");
info("Add an interaction at the visit gap.");
await insertIntoMozPlacesMetadata(page.id, {
total_view_time: VIEWTIME_THRESHOLD,
created_at: now.getTime() - maxVisitGapMs,
updated_at: now.getTime() - maxVisitGapMs,
});
await PlacesFrecencyRecalculator.recalculateAnyOutdatedFrecencies();
updatedPage = await getPageWithUrl(url);
Assert.greater(updatedPage.frecency, page.frecency, "Frecency increased.");
await PlacesUtils.history.clear();
});
add_task(async function old_visits_not_zero() {
let testCases = [
{ daysOld: 365, description: "1 year old" },
{ daysOld: 1825, description: "5 years old" },
{ daysOld: 3650, description: "10 years old" },
];
for (let testCase of testCases) {
let oldDate = new Date();
oldDate.setDate(oldDate.getDate() - testCase.daysOld);
let url = `https://www.example-${testCase.daysOld}.com/`;
await PlacesTestUtils.addVisits([
{
url,
visitDate: oldDate.getTime() * 1000,
},
]);
await PlacesFrecencyRecalculator.recalculateAnyOutdatedFrecencies();
let page = await getPageWithUrl(url);
Assert.notEqual(
page.frecency,
0,
`Frecency should not be zero for ${testCase.description}`
);
Assert.greater(
page.frecency,
0,
`Frecency should be positive for ${testCase.description}`
);
}
await PlacesUtils.history.clear();
});
add_task(async function old_visits_new_interactions() {
let url = "https://testdomain1.moz.org/";
let oldDate = new Date();
oldDate.setDate(oldDate.getDate() - 5000);
await PlacesTestUtils.addVisits([
{ url, visitDate: oldDate.getTime() * 1000 },
]);
await PlacesFrecencyRecalculator.recalculateAnyOutdatedFrecencies();
let page = await getPageWithUrl(url);
let baselineFrecency = page.frecency;
Assert.notEqual(baselineFrecency, 0, "Baseline frecency should not be zero.");
Assert.greater(
baselineFrecency,
0,
"Baseline frecency should be greater than 0."
);
info("Add a recent interesting interaction.");
await insertIntoMozPlacesMetadata(page.id, {
total_view_time: VIEWTIME_THRESHOLD * 2,
});
await PlacesFrecencyRecalculator.recalculateAnyOutdatedFrecencies();
page = await getPageWithUrl(url);
Assert.greater(
page.frecency,
baselineFrecency,
"Recent interaction should boost frecency of page with really old visit."
);
await PlacesUtils.history.clear();
});