Source code
Revision control
Copy as Markdown
Other Tools
/* Any copyright is dedicated to the Public Domain.
"use strict";
Services.scriptloader.loadSubScript(
this
);
ChromeUtils.defineESModuleGetters(this, {
CustomizableUITestUtils:
QuickSuggest: "moz-src:///browser/components/urlbar/QuickSuggest.sys.mjs",
});
const lazy = {};
ChromeUtils.defineLazyGetter(lazy, "QuickSuggestTestUtils", () => {
const { QuickSuggestTestUtils: module } = ChromeUtils.importESModule(
);
module.init(this);
return module;
});
ChromeUtils.defineLazyGetter(this, "MerinoTestUtils", () => {
const { MerinoTestUtils: module } = ChromeUtils.importESModule(
);
module.init(this);
return module;
});
ChromeUtils.defineLazyGetter(this, "GeolocationTestUtils", () => {
const { GeolocationTestUtils: module } = ChromeUtils.importESModule(
);
module.init(this);
return module;
});
ChromeUtils.defineESModuleGetters(lazy, {
});
ChromeUtils.defineLazyGetter(this, "PlacesFrecencyRecalculator", () => {
return Cc["@mozilla.org/places/frecency-recalculator;1"].getService(
Ci.nsIObserver
).wrappedJSObject;
});
async function addTopSites(url) {
for (let i = 0; i < 5; i++) {
await PlacesTestUtils.addVisits(url);
}
await updateTopSites(sites => {
return sites && sites[0] && sites[0].url == url;
});
}
function assertAbandonmentTelemetry(expectedExtraList) {
assertGleanTelemetry("abandonment", expectedExtraList);
}
function assertEngagementTelemetry(expectedExtraList) {
assertGleanTelemetry("engagement", expectedExtraList);
}
function assertExposureTelemetry(expectedExtraList) {
assertGleanTelemetry("exposure", expectedExtraList);
}
function assertDisableTelemetry(expectedExtraList) {
assertGleanTelemetry("disable", expectedExtraList);
}
function assertBounceTelemetry(expectedExtraList) {
assertGleanTelemetry("bounce", expectedExtraList);
}
function assertGleanTelemetry(telemetryName, expectedExtraList) {
const camelName = telemetryName.replaceAll(/_(.)/g, (match, p1) =>
p1.toUpperCase()
);
const telemetries = Glean.urlbar[camelName].testGetValue() ?? [];
info(
"Asserting Glean telemetry is correct, actual events are: " +
JSON.stringify(telemetries)
);
Assert.equal(
telemetries.length,
expectedExtraList.length,
"Telemetry event length matches expected event length."
);
for (let i = 0; i < telemetries.length; i++) {
const telemetry = telemetries[i];
Assert.equal(telemetry.category, "urlbar");
Assert.equal(telemetry.name, telemetryName);
const expectedExtra = expectedExtraList[i];
for (const key of Object.keys(expectedExtra)) {
Assert.equal(
telemetry.extra[key],
expectedExtra[key],
`${key} is correct`
);
}
}
}
async function ensureQuickSuggestInit({ ...args } = {}) {
return lazy.QuickSuggestTestUtils.ensureQuickSuggestInit({
remoteSettingsRecords: [
{
collection: lazy.QuickSuggestTestUtils.RS_COLLECTION.AMP,
type: lazy.QuickSuggestTestUtils.RS_TYPE.AMP,
attachment: [
lazy.QuickSuggestTestUtils.ampRemoteSettings({
keywords: ["amp", "amp and wikipedia"],
}),
],
},
{
collection: lazy.QuickSuggestTestUtils.RS_COLLECTION.OTHER,
type: lazy.QuickSuggestTestUtils.RS_TYPE.WIKIPEDIA,
attachment: [
lazy.QuickSuggestTestUtils.wikipediaRemoteSettings({
keywords: ["wikipedia", "amp and wikipedia"],
}),
],
},
lazy.QuickSuggestTestUtils.weatherRecord(),
{
type: "dynamic-suggestions",
suggestion_type: "test-exposure-aaa",
score: 1.0,
attachment: [
{
keywords: ["aaa keyword"],
data: {
result: {
isHiddenExposure: true,
},
},
},
],
},
{
type: "dynamic-suggestions",
suggestion_type: "important_dates",
score: 1.0,
attachment: [
{
keywords: ["important dates"],
data: {
result: {
payload: {
dates: [
// These dates are always in the future, so that we will
// always get a match in the test when we expect one.
Temporal.Now.plainDateISO().add({ months: 1 }).toString(),
Temporal.Now.plainDateISO().add({ years: 1 }).toString(),
],
name: "Event 1",
},
},
},
},
],
},
],
...args,
});
}
async function doBlur(testUtils = UrlbarTestUtils, win = window) {
await testUtils.promisePopupClose(win, () => {
testUtils.getUrlbar(win).blur();
});
}
async function doClick() {
const selected = UrlbarTestUtils.getSelectedRow(window);
const onLoad = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
EventUtils.synthesizeMouseAtCenter(selected, {});
await onLoad;
}
async function doClickSubButton(selector) {
const selected = UrlbarTestUtils.getSelectedElement(window);
const button = selected.closest(".urlbarView-row").querySelector(selector);
EventUtils.synthesizeMouseAtCenter(button, {});
}
async function doDropAndGo(data) {
const onLoad = BrowserTestUtils.browserLoaded(browser);
EventUtils.synthesizeDrop(
document.getElementById("back-button"),
gURLBar.inputField,
[[{ type: "text/plain", data }]],
"copy",
window
);
await onLoad;
}
async function doEnter(modifier = {}, win = window) {
const onLoad = BrowserTestUtils.browserLoaded(win.gBrowser.selectedBrowser);
EventUtils.synthesizeKey("KEY_Enter", modifier, win);
await onLoad;
}
async function doPaste(data) {
await SimpleTest.promiseClipboardChange(data, () => {
clipboardHelper.copyString(data);
});
gURLBar.focus();
gURLBar.select();
document.commandDispatcher
.getControllerForCommand("cmd_paste")
.doCommand("cmd_paste");
await UrlbarTestUtils.promiseSearchComplete(window);
}
async function doPasteAndGo(data) {
await SimpleTest.promiseClipboardChange(data, () => {
clipboardHelper.copyString(data);
});
const inputBox = gURLBar.querySelector("moz-input-box");
const contextMenu = inputBox.menupopup;
const onPopup = BrowserTestUtils.waitForEvent(contextMenu, "popupshown");
EventUtils.synthesizeMouseAtCenter(gURLBar.inputField, {
type: "contextmenu",
button: 2,
});
await onPopup;
const onLoad = BrowserTestUtils.browserLoaded(browser);
const menuitem = inputBox.getMenuItem("paste-and-go");
contextMenu.activateItem(menuitem);
await onLoad;
}
async function doTest(testFn) {
await Services.fog.testFlushAllChildren();
Services.fog.testResetFOG();
gURLBar.controller.engagementEvent.reset();
await PlacesUtils.history.clear();
await PlacesUtils.bookmarks.eraseEverything();
await PlacesTestUtils.clearHistoryVisits();
await PlacesTestUtils.clearInputHistory();
await UrlbarTestUtils.formHistory.clear(window);
await QuickSuggest.clearDismissedSuggestions();
await updateTopSites(() => true);
await BrowserTestUtils.withNewTab(gBrowser, testFn);
}
async function initGroupTest() {
/* import-globals-from head-groups.js */
Services.scriptloader.loadSubScript(
this
);
await setup();
}
async function initInteractionTest() {
/* import-globals-from head-interaction.js */
Services.scriptloader.loadSubScript(
this
);
await setup();
}
async function initNCharsAndNWordsTest() {
/* import-globals-from head-n_chars_n_words.js */
Services.scriptloader.loadSubScript(
this
);
await setup();
}
async function initSapTest() {
/* import-globals-from head-sap.js */
Services.scriptloader.loadSubScript(
this
);
await setup();
}
async function initSearchEngineDefaultIdTest() {
/* import-globals-from head-search_engine_default_id.js */
Services.scriptloader.loadSubScript(
this
);
await setup();
}
async function initSearchModeTest() {
/* import-globals-from head-search_mode.js */
Services.scriptloader.loadSubScript(
this
);
await setup();
}
async function initExposureTest() {
/* import-globals-from head-exposure.js */
Services.scriptloader.loadSubScript(
this
);
await setup();
}
function loadOmniboxAddon({ keyword }) {
return ExtensionTestUtils.loadExtension({
manifest: {
permissions: ["tabs"],
omnibox: {
keyword,
},
},
background() {
/* global browser */
browser.omnibox.setDefaultSuggestion({
description: "doit",
});
browser.omnibox.onInputEntered.addListener(() => {
});
browser.omnibox.onInputChanged.addListener((text, suggest) => {
suggest([]);
});
},
});
}
async function loadRemoteTab(url) {
await SpecialPowers.pushPrefEnv({
set: [
["browser.urlbar.suggest.searches", false],
["browser.urlbar.maxHistoricalSearchSuggestions", 0],
["browser.urlbar.autoFill", false],
["services.sync.username", "fake"],
],
});
const REMOTE_TAB = {
id: "test",
type: "client",
lastModified: 1492201200,
name: "test",
clientType: "desktop",
tabs: [
{
type: "tab",
title: "tesrt",
url,
icon: UrlbarUtils.ICON.DEFAULT,
client: "test",
lastUsed: Math.floor(Date.now() / 1000),
},
],
};
const sandbox = lazy.sinon.createSandbox();
// eslint-disable-next-line no-undef
const syncedTabs = SyncedTabs;
const originalSyncedTabsInternal = syncedTabs._internal;
syncedTabs._internal = {
isConfiguredToSyncTabs: true,
hasSyncedThisSession: true,
getTabClients() {
return Promise.resolve([]);
},
syncTabs() {
return Promise.resolve();
},
};
const weaveXPCService = Cc["@mozilla.org/weave/service;1"].getService(
Ci.nsISupports
).wrappedJSObject;
const oldWeaveServiceReady = weaveXPCService.ready;
weaveXPCService.ready = true;
sandbox
.stub(syncedTabs._internal, "getTabClients")
.callsFake(() => Promise.resolve(Cu.cloneInto([REMOTE_TAB], {})));
return {
async unload() {
sandbox.restore();
weaveXPCService.ready = oldWeaveServiceReady;
syncedTabs._internal = originalSyncedTabsInternal;
// Reset internal cache in UrlbarProviderRemoteTabs.
Services.obs.notifyObservers(null, "weave:engine:sync:finish", "tabs");
await SpecialPowers.popPrefEnv();
},
};
}
async function openPopup(input, testUtils = UrlbarTestUtils, win = window) {
await testUtils.promisePopupOpen(win, async () => {
await testUtils.inputIntoURLBar(win, input);
});
await testUtils.promiseSearchComplete(win);
}
async function selectRowByURL(url) {
for (let i = 0; i < UrlbarTestUtils.getResultCount(window); i++) {
const detail = await UrlbarTestUtils.getDetailsOfResultAt(window, i);
if (detail.url === url) {
UrlbarTestUtils.setSelectedRowIndex(window, i);
return;
}
}
}
async function selectRowByProvider(provider) {
for (let i = 0; i < UrlbarTestUtils.getResultCount(window); i++) {
const detail = await UrlbarTestUtils.getDetailsOfResultAt(window, i);
if (detail.result.providerName === provider) {
UrlbarTestUtils.setSelectedRowIndex(window, i);
break;
}
}
}
async function selectRowByType(type) {
for (let i = 0; i < UrlbarTestUtils.getResultCount(window); i++) {
const detail = await UrlbarTestUtils.getDetailsOfResultAt(window, i);
if (detail.result.payload.type === type) {
UrlbarTestUtils.setSelectedRowIndex(window, i);
return;
}
}
}
async function setup() {
await SpecialPowers.pushPrefEnv({
set: [
["browser.urlbar.searchEngagementTelemetry.enabled", true],
["browser.urlbar.quickactions.enabled", true],
["browser.urlbar.secondaryActions.featureGate", true],
["browser.urlbar.scotchBonnet.enableOverride", false],
],
});
const engine = await SearchTestUtils.installOpenSearchEngine({
});
const originalDefaultEngine = await SearchService.getDefault();
await SearchService.setDefault(engine, SearchService.CHANGE_REASON.UNKNOWN);
await SearchService.moveEngine(engine, 0);
registerCleanupFunction(async function () {
// Tests verify that no prefs have been changed so clear any
// so clear any prefs we may have touched while running tests.
let prefs = [
"services.sync.lastTabFetch",
"services.settings.clock_skew_seconds",
"services.settings.last_update_seconds",
"services.settings.last_etag",
"browser.urlbar.recentsearches.lastDefaultChanged",
"browser.search.totalSearches",
"browser.urlbar.events.bounce.maxSecondsFromLastSearch",
];
prefs.forEach(pref => Services.prefs.clearUserPref(pref));
await SpecialPowers.popPrefEnv();
await SearchService.setDefault(
originalDefaultEngine,
SearchService.CHANGE_REASON.UNKNOWN
);
});
}
async function setupNimbus(variables) {
return lazy.UrlbarTestUtils.initNimbusFeature(variables);
}
async function showResultByArrowDown() {
gURLBar.value = "";
gURLBar.select();
await UrlbarTestUtils.promisePopupOpen(window, () => {
EventUtils.synthesizeKey("KEY_ArrowDown");
});
await UrlbarTestUtils.promiseSearchComplete(window);
}
async function expectNoConsoleErrors(task) {
let endConsoleListening = TestUtils.listenForConsoleMessages();
let msgs;
let taskResult;
try {
taskResult = await task();
} finally {
msgs = await endConsoleListening();
}
for (let msg of msgs) {
if (msg.level === "error") {
throw new Error(`Console error detected: ${msg.arguments[0]}`);
}
}
return taskResult;
}
/**
* Helper to run a semantic history test.
*
* @param {Array<UrlbarResult>} results array of results to return
* @param {Function} task async function to wrap
* @param {number} [embeddingSize] size of embeddings
*/
async function doTestWithSemantic(results, task, embeddingSize = 16) {
/**
* A mock object that pretends to be an MLEngine.
*/
class MockMLEngine {
async run(request) {
const texts = request.args[0];
return texts.map(text => {
if (typeof text !== "string" || text.trim() === "") {
throw new Error("Invalid input: text must be a non-empty string");
}
// Return a mock embedding vector (e.g., an array of zeros)
return Array(embeddingSize).fill(0);
});
}
}
const { getPlacesSemanticHistoryManager } = ChromeUtils.importESModule(
"resource://gre/modules/PlacesSemanticHistoryManager.sys.mjs"
);
let semanticManager = getPlacesSemanticHistoryManager();
let canUseSemanticStub = sinon.stub(semanticManager, "canUseSemanticSearch");
canUseSemanticStub.get(() => true);
let hasSufficientEntriesStub = sinon
.stub(semanticManager, "hasSufficientEntriesForSearching")
.resolves(true);
semanticManager.embedder.setEngine(new MockMLEngine());
let inferStub = sinon.stub(semanticManager, "infer").resolves({ results });
await SpecialPowers.pushPrefEnv({
set: [["places.semanticHistory.featureGate", true]],
});
try {
await doTest(task);
} finally {
await SpecialPowers.popPrefEnv();
hasSufficientEntriesStub.restore();
canUseSemanticStub.restore();
inferStub.restore();
}
}