Source code

Revision control

Copy as Markdown

Other Tools

Test Info: Errors

/* Any copyright is dedicated to the Public Domain.
/**
* This file tests urlbar autofill telemetry.
*/
"use strict";
const SCALAR_URLBAR = "browser.engagement.navigation.urlbar";
function assertSearchTelemetryEmpty(search_hist) {
const scalars = TelemetryTestUtils.getProcessScalars("parent", true, false);
Assert.ok(
!(SCALAR_URLBAR in scalars),
`Should not have recorded ${SCALAR_URLBAR}`
);
// SEARCH_COUNTS should not contain any engine counts at all. The keys in this
// histogram are search engine telemetry identifiers.
Assert.deepEqual(
Object.keys(search_hist.snapshot()),
[],
"SEARCH_COUNTS is empty"
);
// Also check events.
let events = Services.telemetry.snapshotEvents(
Ci.nsITelemetry.DATASET_PRERELEASE_CHANNELS,
false
);
events = (events.parent || []).filter(
e => e[1] == "navigation" && e[2] == "search"
);
Assert.deepEqual(
events,
[],
"Should not have recorded any navigation search events"
);
}
function snapshotHistograms() {
Services.telemetry.clearScalars();
Services.telemetry.clearEvents();
return {
resultMethodHist: TelemetryTestUtils.getAndClearHistogram(
"FX_URLBAR_SELECTED_RESULT_METHOD"
),
search_hist: TelemetryTestUtils.getAndClearKeyedHistogram("SEARCH_COUNTS"),
};
}
function assertTelemetryResults(histograms, type, index, method) {
TelemetryTestUtils.assertHistogram(histograms.resultMethodHist, method, 1);
TelemetryTestUtils.assertKeyedScalar(
TelemetryTestUtils.getProcessScalars("parent", true, true),
`urlbar.picked.${type}`,
index,
1
);
}
/**
* Performs a search and picks the first result.
*
* @param {string} searchString
* The search string. Assumed to trigger an autofill result
* @param {string} autofilledValue
* The input's expected value after autofill occurs.
* @param {string} unpickResult
* Optional: If true, do not pick any result. Default value is false.
* @param {string} urlToSelect
* Optional: If want to select result except autofill, pass the URL.
*/
async function triggerAutofillAndPickResult(
searchString,
autofilledValue,
unpickResult = false,
urlToSelect = null
) {
await BrowserTestUtils.withNewTab("about:blank", async () => {
await UrlbarTestUtils.promiseAutocompleteResultPopup({
window,
value: searchString,
fireInputEvent: true,
});
let details = await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
Assert.ok(details.autofill, "Result is autofill");
Assert.equal(gURLBar.value, autofilledValue, "gURLBar.value");
Assert.equal(gURLBar.selectionStart, searchString.length, "selectionStart");
Assert.equal(gURLBar.selectionEnd, autofilledValue.length, "selectionEnd");
if (urlToSelect) {
for (let row = 0; row < UrlbarTestUtils.getResultCount(window); row++) {
const result = await UrlbarTestUtils.getDetailsOfResultAt(window, row);
if (result.url === urlToSelect) {
UrlbarTestUtils.setSelectedRowIndex(window, row);
break;
}
}
}
if (unpickResult) {
// Close popup without any action.
await UrlbarTestUtils.promisePopupClose(window);
return;
}
let loadPromise = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
EventUtils.synthesizeKey("KEY_Enter");
await loadPromise;
let url;
if (urlToSelect) {
url = urlToSelect;
} else {
url = autofilledValue.includes(":")
? autofilledValue
: "http://" + autofilledValue;
}
Assert.equal(gBrowser.currentURI.spec, url, "Loaded URL is correct");
});
}
function createOtherAutofillProvider(searchString, autofilledValue) {
return new UrlbarTestUtils.TestProvider({
priority: Infinity,
type: UrlbarUtils.PROVIDER_TYPE.HEURISTIC,
results: [
Object.assign(
new UrlbarResult(
UrlbarUtils.RESULT_TYPE.URL,
UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
{
title: "Test",
}
),
{
heuristic: true,
autofill: {
value: autofilledValue,
selectionStart: searchString.length,
selectionEnd: autofilledValue.length,
// Leave out `type` to trigger "other"
},
}
),
],
});
}
// Allow more time for Mac machines so they don't time out in verify mode.
if (AppConstants.platform == "macosx") {
requestLongerTimeout(3);
}
add_setup(async function () {
await PlacesUtils.bookmarks.eraseEverything();
await PlacesUtils.history.clear();
await PlacesTestUtils.clearInputHistory();
// Enable local telemetry recording for the duration of the tests.
const originalCanRecord = Services.telemetry.canRecordExtended;
Services.telemetry.canRecordExtended = true;
// Make sure autofill is tested without upgrading pages to https
await SpecialPowers.pushPrefEnv({
set: [["dom.security.https_first_schemeless", false]],
});
registerCleanupFunction(async () => {
Services.telemetry.canRecordExtended = originalCanRecord;
await PlacesTestUtils.clearInputHistory();
await PlacesUtils.history.clear();
});
});
// Checks adaptive history, origin, and URL autofill.
add_task(async function history() {
const testData = [
{
useAdaptiveHistory: true,
visitHistory: ["http://example.com/test"],
inputHistory: [{ uri: "http://example.com/test", input: "exa" }],
userInput: "ex",
autofilled: "example.com/",
expected: "autofill_origin",
},
{
useAdaptiveHistory: true,
visitHistory: ["http://example.com/test"],
inputHistory: [{ uri: "http://example.com/test", input: "exa" }],
userInput: "exa",
autofilled: "example.com/test",
expected: "autofill_adaptive",
},
{
useAdaptiveHistory: true,
visitHistory: ["http://example.com/test"],
inputHistory: [{ uri: "http://example.com/test", input: "exa" }],
userInput: "exam",
autofilled: "example.com/test",
expected: "autofill_adaptive",
},
{
useAdaptiveHistory: true,
visitHistory: ["http://example.com/test"],
inputHistory: [{ uri: "http://example.com/test", input: "exa" }],
userInput: "example.com",
autofilled: "example.com/test",
expected: "autofill_adaptive",
},
{
useAdaptiveHistory: true,
visitHistory: ["http://example.com/test"],
inputHistory: [{ uri: "http://example.com/test", input: "exa" }],
userInput: "example.com/",
autofilled: "example.com/test",
expected: "autofill_adaptive",
},
{
useAdaptiveHistory: true,
visitHistory: ["http://example.com/test"],
inputHistory: [{ uri: "http://example.com/test", input: "exa" }],
userInput: "example.com/test",
autofilled: "example.com/test",
expected: "autofill_adaptive",
},
{
useAdaptiveHistory: true,
inputHistory: [{ uri: "http://example.com/test", input: "exa" }],
userInput: "example.org",
autofilled: "example.org/",
expected: "autofill_origin",
},
{
useAdaptiveHistory: true,
inputHistory: [{ uri: "http://example.com/test", input: "exa" }],
userInput: "example.com/test/",
autofilled: "example.com/test/",
expected: "autofill_url",
},
{
useAdaptiveHistory: true,
visitHistory: [{ uri: "http://example.com/test" }],
inputHistory: [
],
userInput: "http://example.com/test",
autofilled: "http://example.com/test",
expected: "autofill_adaptive",
},
{
useAdaptiveHistory: false,
visitHistory: [{ uri: "http://example.com/test" }],
inputHistory: [{ uri: "http://example.com/test", input: "exa" }],
userInput: "example",
autofilled: "example.com/",
expected: "autofill_origin",
},
{
useAdaptiveHistory: false,
visitHistory: [{ uri: "http://example.com/test" }],
inputHistory: [{ uri: "http://example.com/test", input: "exa" }],
userInput: "example.com/te",
autofilled: "example.com/test",
expected: "autofill_url",
},
];
for (const {
useAdaptiveHistory,
visitHistory,
inputHistory,
userInput,
autofilled,
expected,
} of testData) {
const histograms = snapshotHistograms();
await PlacesTestUtils.addVisits(visitHistory);
for (const { uri, input } of inputHistory) {
await UrlbarUtils.addToInputHistory(uri, input);
}
await PlacesFrecencyRecalculator.recalculateAnyOutdatedFrecencies();
UrlbarPrefs.set("autoFill.adaptiveHistory.enabled", useAdaptiveHistory);
await triggerAutofillAndPickResult(userInput, autofilled);
assertSearchTelemetryEmpty(histograms.search_hist);
assertTelemetryResults(
histograms,
expected,
0,
UrlbarTestUtils.SELECTED_RESULT_METHODS.enter
);
UrlbarPrefs.clear("autoFill.adaptiveHistory.enabled");
await PlacesTestUtils.clearInputHistory();
await PlacesUtils.history.clear();
}
});
// Checks about-page autofill (e.g., "about:about").
add_task(async function about() {
let histograms = snapshotHistograms();
await triggerAutofillAndPickResult("about:abou", "about:about");
assertSearchTelemetryEmpty(histograms.search_hist);
assertTelemetryResults(
histograms,
"autofill_about",
0,
UrlbarTestUtils.SELECTED_RESULT_METHODS.enter
);
await PlacesUtils.history.clear();
});
// Checks the "other" fallback, which shouldn't normally happen.
add_task(async function other() {
let searchString = "exam";
let autofilledValue = "example.com/";
let provider = createOtherAutofillProvider(searchString, autofilledValue);
UrlbarProvidersManager.registerProvider(provider);
let histograms = snapshotHistograms();
await triggerAutofillAndPickResult(searchString, autofilledValue);
assertSearchTelemetryEmpty(histograms.search_hist);
assertTelemetryResults(
histograms,
"autofill_other",
0,
UrlbarTestUtils.SELECTED_RESULT_METHODS.enter
);
await PlacesUtils.history.clear();
UrlbarProvidersManager.unregisterProvider(provider);
});
// Checks impression telemetry.
add_task(async function impression() {
const testData = [
{
description: "Adaptive history autofill and pick it",
useAdaptiveHistory: true,
inputHistory: [{ uri: "http://example.com/first", input: "exa" }],
userInput: "exa",
autofilled: "example.com/first",
expected: "autofill_adaptive",
},
{
description: "Adaptive history autofill but pick another result",
useAdaptiveHistory: true,
inputHistory: [{ uri: "http://example.com/first", input: "exa" }],
userInput: "exa",
urlToSelect: "http://example.com/second",
autofilled: "example.com/first",
expected: "autofill_adaptive",
},
{
description: "Adaptive history autofill but not pick any result",
unpickResult: true,
useAdaptiveHistory: true,
inputHistory: [{ uri: "http://example.com/first", input: "exa" }],
userInput: "exa",
autofilled: "example.com/first",
},
{
description: "Origin autofill and pick it",
userInput: "exa",
autofilled: "example.com/",
expected: "autofill_origin",
},
{
description: "Origin autofill but pick another result",
userInput: "exa",
urlToSelect: "http://example.com/second",
autofilled: "example.com/",
expected: "autofill_origin",
},
{
description: "Origin autofill but not pick any result",
unpickResult: true,
userInput: "exa",
autofilled: "example.com/",
},
{
description: "URL autofill and pick it",
userInput: "example.com/",
autofilled: "example.com/",
expected: "autofill_url",
},
{
description: "URL autofill but pick another result",
userInput: "example.com/",
urlToSelect: "http://example.com/second",
autofilled: "example.com/",
expected: "autofill_url",
},
{
description: "URL autofill but not pick any result",
unpickResult: true,
userInput: "example.com/",
autofilled: "example.com/",
},
{
description: "about page autofill and pick it",
userInput: "about:a",
autofilled: "about:about",
expected: "autofill_about",
},
{
description: "about page autofill but pick another result",
userInput: "about:a",
urlToSelect: "about:addons",
autofilled: "about:about",
expected: "autofill_about",
},
{
description: "about page autofill but not pick any result",
unpickResult: true,
userInput: "about:a",
autofilled: "about:about",
},
{
description: "Other provider's autofill and pick it",
useOtherProvider: true,
userInput: "example",
autofilled: "example.com/",
expected: "autofill_other",
},
{
description: "Other provider's autofill but not pick any result",
unpickResult: true,
useOtherProvider: true,
userInput: "example",
autofilled: "example.com/",
},
];
for (const {
description,
useAdaptiveHistory = false,
useOtherProvider = false,
unpickResult = false,
visitHistory,
inputHistory,
userInput,
select,
autofilled,
expected,
} of testData) {
info(description);
UrlbarPrefs.set("autoFill.adaptiveHistory.enabled", useAdaptiveHistory);
let otherProvider;
if (useOtherProvider) {
otherProvider = createOtherAutofillProvider(userInput, autofilled);
UrlbarProvidersManager.registerProvider(otherProvider);
}
if (visitHistory) {
await PlacesTestUtils.addVisits(visitHistory);
}
if (inputHistory) {
for (const { uri, input } of inputHistory) {
await UrlbarUtils.addToInputHistory(uri, input);
}
}
await PlacesFrecencyRecalculator.recalculateAnyOutdatedFrecencies();
await triggerAutofillAndPickResult(
userInput,
autofilled,
unpickResult,
select
);
const scalars = TelemetryTestUtils.getProcessScalars("parent", false, true);
if (unpickResult) {
TelemetryTestUtils.assertScalarUnset(
scalars,
"urlbar.impression.autofill_adaptive"
);
TelemetryTestUtils.assertScalarUnset(
scalars,
"urlbar.impression.autofill_origin"
);
TelemetryTestUtils.assertScalarUnset(
scalars,
"urlbar.impression.autofill_url"
);
TelemetryTestUtils.assertScalarUnset(
scalars,
"urlbar.impression.autofill_about"
);
} else {
TelemetryTestUtils.assertScalar(
scalars,
`urlbar.impression.${expected}`,
1
);
}
UrlbarPrefs.clear("autoFill.adaptiveHistory.enabled");
if (otherProvider) {
UrlbarProvidersManager.unregisterProvider(otherProvider);
}
await PlacesTestUtils.clearInputHistory();
await PlacesUtils.history.clear();
}
});
// Checks autofill deletion telemetry.
add_task(async function deletion() {
await PlacesTestUtils.addVisits(["http://example.com/"]);
await PlacesFrecencyRecalculator.recalculateAnyOutdatedFrecencies();
info("Delete autofilled value by DELETE key");
await doDeletionTest({
firstSearchString: "exa",
firstAutofilledValue: "example.com/",
trigger: () => {
EventUtils.synthesizeKey("KEY_Delete");
Assert.equal(gURLBar.value, "exa");
},
expectedScalar: 1,
});
info("Delete autofilled value by BACKSPACE key");
await doDeletionTest({
firstSearchString: "exa",
firstAutofilledValue: "example.com/",
trigger: () => {
EventUtils.synthesizeKey("KEY_Backspace");
Assert.equal(gURLBar.value, "exa");
},
expectedScalar: 1,
});
info("Delete autofilled value twice");
await doDeletionTest({
firstSearchString: "exa",
firstAutofilledValue: "example.com/",
trigger: () => {
// Delete autofilled string.
EventUtils.synthesizeKey("KEY_Delete");
Assert.equal(gURLBar.value, "exa");
// Re-autofilling.
EventUtils.synthesizeKey("m");
Assert.equal(gURLBar.value, "example.com/");
Assert.equal(gURLBar.selectionStart, "exam".length);
Assert.equal(gURLBar.selectionEnd, "example.com/".length);
// Delete autofilled string again.
EventUtils.synthesizeKey("KEY_Backspace");
Assert.equal(gURLBar.value, "exam");
},
expectedScalar: 2,
});
info("Delete one char after unselecting autofilled string");
await doDeletionTest({
firstSearchString: "exa",
firstAutofilledValue: "example.com/",
trigger: () => {
// Cancel selection.
EventUtils.synthesizeMouseAtCenter(gURLBar.inputField, {});
Assert.equal(gURLBar.selectionStart, "example.com/".length);
Assert.equal(gURLBar.selectionEnd, "example.com/".length);
EventUtils.synthesizeKey("KEY_Backspace");
Assert.equal(gURLBar.value, "example.com");
},
expectedScalar: 0,
});
info("Delete autofilled value after unselecting autofilled string");
await doDeletionTest({
firstSearchString: "exa",
firstAutofilledValue: "example.com/",
trigger: () => {
// Cancel selection.
EventUtils.synthesizeMouseAtCenter(gURLBar.inputField, {});
Assert.equal(gURLBar.selectionStart, "example.com/".length);
Assert.equal(gURLBar.selectionEnd, "example.com/".length);
// Delete autofilled string one by one.
for (let i = 0; i < "mple.com/".length; i++) {
EventUtils.synthesizeKey("KEY_Backspace");
}
Assert.equal(gURLBar.value, "exa");
},
expectedScalar: 0,
});
info(
"Delete autofilled value after unselecting autofilled string then selecting them manually again"
);
await doDeletionTest({
firstSearchString: "exa",
firstAutofilledValue: "example.com/",
trigger: () => {
// Cancel selection.
const previousSelectionStart = gURLBar.selectionStart;
const previousSelectionEnd = gURLBar.selectionEnd;
EventUtils.synthesizeMouseAtCenter(gURLBar.inputField, {});
Assert.equal(gURLBar.selectionStart, "example.com/".length);
Assert.equal(gURLBar.selectionEnd, "example.com/".length);
// Select same range again.
gURLBar.selectionStart = previousSelectionStart;
gURLBar.selectionEnd = previousSelectionEnd;
EventUtils.synthesizeKey("KEY_Backspace");
Assert.equal(gURLBar.value, "exa");
},
expectedScalar: 1,
});
await PlacesUtils.history.clear();
});
async function doDeletionTest({
firstSearchString,
firstAutofilledValue,
trigger,
expectedScalar,
}) {
await BrowserTestUtils.withNewTab("about:blank", async () => {
await UrlbarTestUtils.promiseAutocompleteResultPopup({
window,
value: firstSearchString,
fireInputEvent: true,
});
const details = await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
Assert.ok(details.autofill, "Result is autofill");
Assert.equal(gURLBar.value, firstAutofilledValue, "gURLBar.value");
Assert.equal(
gURLBar.selectionStart,
firstSearchString.length,
"selectionStart"
);
Assert.equal(
gURLBar.selectionEnd,
firstAutofilledValue.length,
"selectionEnd"
);
await trigger();
const scalars = TelemetryTestUtils.getProcessScalars("parent", false, true);
if (expectedScalar) {
TelemetryTestUtils.assertScalar(
scalars,
"urlbar.autofill_deletion",
expectedScalar
);
} else {
TelemetryTestUtils.assertScalarUnset(scalars, "urlbar.autofill_deletion");
}
await UrlbarTestUtils.promisePopupClose(window);
});
}