Source code

Revision control

Copy as Markdown

Other Tools

Test Info: Errors

/* Any copyright is dedicated to the Public Domain.
const TEST_PAGE_BASENAME = "contentSearchUI.html";
const TEST_ENGINE1 = {
name: "searchSuggestionEngine1",
id: "other-searchSuggestionEngine1",
loadPath: "[addon]searchsuggestionengine1@tests.mozilla.org",
};
const TEST_ENGINE2 = {
name: "searchSuggestionEngine2",
id: "other-searchSuggestionEngine2",
loadPath: "[addon]searchsuggestionengine2@tests.mozilla.org",
};
const TEST_MSG = "ContentSearchUIControllerTest";
ChromeUtils.defineESModuleGetters(this, {
FormHistoryTestUtils:
SearchSuggestionController:
});
requestLongerTimeout(2);
function waitForSuggestions() {
return SpecialPowers.spawn(gBrowser.selectedBrowser, [], () =>
ContentTaskUtils.waitForCondition(
() =>
Cu.waiveXrays(content).gController.input.getAttribute(
"aria-expanded"
) == "true",
"Waiting for suggestions",
200 // Increased interval to support long textruns.
)
);
}
async function waitForSearch() {
await BrowserTestUtils.waitForContentEvent(
gBrowser.selectedBrowser,
"ContentSearchClient",
true,
event => {
if (event.detail.type == "Search") {
event.target._eventDetail = event.detail.data;
return true;
}
return false;
},
true
);
return SpecialPowers.spawn(gBrowser.selectedBrowser, [], () => {
let eventDetail = content._eventDetail;
delete content._eventDetail;
return eventDetail;
});
}
async function waitForSearchSettings() {
await BrowserTestUtils.waitForContentEvent(
gBrowser.selectedBrowser,
"ContentSearchClient",
true,
event => {
if (event.detail.type == "ManageEngines") {
event.target._eventDetail = event.detail.data;
return true;
}
return false;
},
true
);
return SpecialPowers.spawn(gBrowser.selectedBrowser, [], () => {
let eventDetail = content._eventDetail;
delete content._eventDetail;
return eventDetail;
});
}
function getCurrentState() {
return SpecialPowers.spawn(gBrowser.selectedBrowser, [], () => {
let controller = Cu.waiveXrays(content).gController;
let state = {
selectedIndex: controller.selectedIndex,
selectedButtonIndex: controller.selectedButtonIndex,
numSuggestions: controller._table.hidden ? 0 : controller.numSuggestions,
suggestionAtIndex: [],
isFormHistorySuggestionAtIndex: [],
tableHidden: controller._table.hidden,
inputValue: controller.input.value,
ariaExpanded: controller.input.getAttribute("aria-expanded"),
};
if (state.numSuggestions) {
for (let i = 0; i < controller.numSuggestions; i++) {
state.suggestionAtIndex.push(controller.suggestionAtIndex(i));
state.isFormHistorySuggestionAtIndex.push(
controller.isFormHistorySuggestionAtIndex(i)
);
}
}
return state;
});
}
async function msg(type, data = null) {
switch (type) {
case "reset":
// Reset both the input and suggestions by select all + delete. If there was
// no text entered, this won't have any effect, so also escape to ensure the
// suggestions table is closed.
await SpecialPowers.spawn(gBrowser.selectedBrowser, [], () => {
Cu.waiveXrays(content).gController.input.focus();
EventUtils.synthesizeKey("a", { accelKey: true }, content);
EventUtils.synthesizeKey("KEY_Delete", {}, content);
EventUtils.synthesizeKey("KEY_Escape", {}, content);
});
break;
case "key": {
let keyName = typeof data == "string" ? data : data.key;
await BrowserTestUtils.synthesizeKey(
keyName,
data.modifiers || {},
gBrowser.selectedBrowser
);
if (data?.waitForSuggestions) {
await waitForSuggestions();
}
break;
}
case "text": {
await SpecialPowers.spawn(
gBrowser.selectedBrowser,
[data.value],
text => {
Cu.waiveXrays(content).gController.input.value = text.substring(
0,
text.length - 1
);
EventUtils.synthesizeKey(
text.substring(text.length - 1),
{},
content
);
}
);
if (data?.waitForSuggestions) {
await waitForSuggestions();
}
break;
}
case "startComposition":
await BrowserTestUtils.synthesizeComposition(
"compositionstart",
gBrowser.selectedBrowser
);
break;
case "changeComposition": {
await BrowserTestUtils.synthesizeCompositionChange(
{
composition: {
string: data.data,
clauses: [
{
length: data.length,
attr: Ci.nsITextInputProcessor.ATTR_RAW_CLAUSE,
},
],
},
caret: { start: data.length, length: 0 },
},
gBrowser.selectedBrowser
);
if (data?.waitForSuggestions) {
await waitForSuggestions();
}
break;
}
case "commitComposition":
await BrowserTestUtils.synthesizeComposition(
"compositioncommitasis",
gBrowser.selectedBrowser
);
break;
case "mousemove":
case "click": {
let event;
let index;
if (type == "mousemove") {
event = {
type: "mousemove",
clickcount: 0,
};
index = data;
} else {
event = data.modifiers || null;
index = data.eltIdx;
}
await SpecialPowers.spawn(
gBrowser.selectedBrowser,
[type, event, index],
(eventType, eventArgs, itemIndex) => {
let controller = Cu.waiveXrays(content).gController;
return new Promise(resolve => {
let row;
if (itemIndex == -1) {
row = controller._table.firstChild;
} else {
let allElts = [
...controller._suggestionsList.children,
...controller._oneOffButtons,
content.document.getElementById("contentSearchSettingsButton"),
];
row = allElts[itemIndex];
}
row.addEventListener(eventType, () => resolve(), { once: true });
EventUtils.synthesizeMouseAtCenter(row, eventArgs, content);
});
}
);
break;
}
}
return getCurrentState();
}
/**
* Focusses the in-content search bar.
*
* @returns {Promise}
* A promise that is resolved once the focus is complete.
*/
function focusContentSearchBar() {
return SpecialPowers.spawn(gBrowser.selectedBrowser, [], () => {
Cu.waiveXrays(content).input.focus();
});
}
let extension1;
let extension2;
add_setup(async function () {
const pageURL = getRootDirectory(gTestPath) + TEST_PAGE_BASENAME;
let cleanupAboutPage;
await BrowserTestUtils.registerAboutPage(
callback => (cleanupAboutPage = callback),
"test-about-content-search-ui",
pageURL,
Ci.nsIAboutModule.URI_SAFE_FOR_UNTRUSTED_CONTENT |
Ci.nsIAboutModule.URI_MUST_LOAD_IN_CHILD |
Ci.nsIAboutModule.ALLOW_SCRIPT |
Ci.nsIAboutModule.URI_CAN_LOAD_IN_PRIVILEGEDABOUT_PROCESS
);
let originalOnMessageSearch = ContentSearch._onMessageSearch;
let originalOnMessageManageEngines = ContentSearch._onMessageManageEngines;
ContentSearch._onMessageSearch = () => {};
ContentSearch._onMessageManageEngines = () => {};
let currentEngines = await Services.search.getVisibleEngines();
extension1 = await SearchTestUtils.installSearchExtension(
{
name: TEST_ENGINE1.name,
suggest_url:
suggest_url_get_params: "query={searchTerms}",
},
{ setAsDefault: true }
);
extension2 = await SearchTestUtils.installSearchExtension({
name: TEST_ENGINE2.name,
suggest_url:
suggest_url_get_params: "query={searchTerms}",
});
for (let engine of currentEngines) {
await Services.search.removeEngine(engine);
}
registerCleanupFunction(async () => {
// Ensure tabs are closed before we continue on with the cleanup.
for (let tab of tabs) {
BrowserTestUtils.removeTab(tab);
}
Services.search.restoreDefaultEngines();
await TestUtils.waitForTick();
ContentSearch._onMessageSearch = originalOnMessageSearch;
ContentSearch._onMessageManageEngines = originalOnMessageManageEngines;
if (cleanupAboutPage) {
await cleanupAboutPage();
}
});
await promiseTab();
});
add_task(async function emptyInput() {
await focusContentSearchBar();
let state = await msg("key", { key: "x", waitForSuggestions: true });
checkState(state, "x", ["xfoo", "xbar"], -1);
state = await msg("key", "VK_BACK_SPACE");
checkState(state, "", [], -1);
await msg("reset");
});
add_task(async function blur() {
await focusContentSearchBar();
let state = await msg("key", { key: "x", waitForSuggestions: true });
checkState(state, "x", ["xfoo", "xbar"], -1);
await SpecialPowers.spawn(gBrowser.selectedBrowser, [], () => {
Cu.waiveXrays(content).gController.input.blur();
});
state = await getCurrentState();
checkState(state, "x", [], -1);
await msg("reset");
});
add_task(async function upDownKeys() {
await focusContentSearchBar();
let state = await msg("key", { key: "x", waitForSuggestions: true });
checkState(state, "x", ["xfoo", "xbar"], -1);
// Cycle down the suggestions starting from no selection.
state = await msg("key", "VK_DOWN");
checkState(state, "xfoo", ["xfoo", "xbar"], 0);
state = await msg("key", "VK_DOWN");
checkState(state, "xbar", ["xfoo", "xbar"], 1);
state = await msg("key", "VK_DOWN");
checkState(state, "x", ["xfoo", "xbar"], 2);
state = await msg("key", "VK_DOWN");
checkState(state, "x", ["xfoo", "xbar"], 3);
state = await msg("key", "VK_DOWN");
checkState(state, "x", ["xfoo", "xbar"], -1);
// Cycle up starting from no selection.
state = await msg("key", "VK_UP");
checkState(state, "x", ["xfoo", "xbar"], 3);
state = await msg("key", "VK_UP");
checkState(state, "x", ["xfoo", "xbar"], 2);
state = await msg("key", "VK_UP");
checkState(state, "xbar", ["xfoo", "xbar"], 1);
state = await msg("key", "VK_UP");
checkState(state, "xfoo", ["xfoo", "xbar"], 0);
state = await msg("key", "VK_UP");
checkState(state, "x", ["xfoo", "xbar"], -1);
await msg("reset");
});
add_task(async function rightLeftKeys() {
await focusContentSearchBar();
let state = await msg("key", { key: "x", waitForSuggestions: true });
checkState(state, "x", ["xfoo", "xbar"], -1);
state = await msg("key", "VK_LEFT");
checkState(state, "x", ["xfoo", "xbar"], -1);
state = await msg("key", "VK_LEFT");
checkState(state, "x", ["xfoo", "xbar"], -1);
state = await msg("key", "VK_RIGHT");
checkState(state, "x", ["xfoo", "xbar"], -1);
state = await msg("key", "VK_RIGHT");
checkState(state, "x", [], -1);
state = await msg("key", { key: "VK_DOWN", waitForSuggestions: true });
checkState(state, "x", ["xfoo", "xbar"], -1);
state = await msg("key", "VK_DOWN");
checkState(state, "xfoo", ["xfoo", "xbar"], 0);
// This should make the xfoo suggestion sticky. To make sure it sticks,
// trigger suggestions again and cycle through them by pressing Down until
// nothing is selected again.
state = await msg("key", "VK_RIGHT");
checkState(state, "xfoo", [], -1);
state = await msg("key", { key: "VK_DOWN", waitForSuggestions: true });
checkState(state, "xfoo", ["xfoofoo", "xfoobar"], -1);
state = await msg("key", "VK_DOWN");
checkState(state, "xfoofoo", ["xfoofoo", "xfoobar"], 0);
state = await msg("key", "VK_DOWN");
checkState(state, "xfoobar", ["xfoofoo", "xfoobar"], 1);
state = await msg("key", "VK_DOWN");
checkState(state, "xfoo", ["xfoofoo", "xfoobar"], 2);
state = await msg("key", "VK_DOWN");
checkState(state, "xfoo", ["xfoofoo", "xfoobar"], 3);
state = await msg("key", "VK_DOWN");
checkState(state, "xfoo", ["xfoofoo", "xfoobar"], -1);
await msg("reset");
});
add_task(async function tabKey() {
await focusContentSearchBar();
await msg("key", { key: "x", waitForSuggestions: true });
let state = await msg("key", "VK_TAB");
checkState(state, "x", ["xfoo", "xbar"], 2);
state = await msg("key", "VK_TAB");
checkState(state, "x", ["xfoo", "xbar"], 3);
state = await msg("key", { key: "VK_TAB", modifiers: { shiftKey: true } });
checkState(state, "x", ["xfoo", "xbar"], 2);
state = await msg("key", { key: "VK_TAB", modifiers: { shiftKey: true } });
checkState(state, "x", [], -1);
await focusContentSearchBar();
await msg("key", { key: "VK_DOWN", waitForSuggestions: true });
for (let i = 0; i < 3; ++i) {
state = await msg("key", "VK_TAB");
}
checkState(state, "x", [], -1);
await focusContentSearchBar();
await msg("key", { key: "VK_DOWN", waitForSuggestions: true });
state = await msg("key", "VK_DOWN");
checkState(state, "xfoo", ["xfoo", "xbar"], 0);
state = await msg("key", "VK_TAB");
checkState(state, "xfoo", ["xfoo", "xbar"], 0, 0);
state = await msg("key", "VK_TAB");
checkState(state, "xfoo", ["xfoo", "xbar"], 0, 1);
state = await msg("key", "VK_DOWN");
checkState(state, "xbar", ["xfoo", "xbar"], 1, 1);
state = await msg("key", "VK_DOWN");
checkState(state, "x", ["xfoo", "xbar"], 2);
state = await msg("key", "VK_UP");
checkState(state, "xbar", ["xfoo", "xbar"], 1);
state = await msg("key", "VK_TAB");
checkState(state, "xbar", ["xfoo", "xbar"], 1, 0);
state = await msg("key", "VK_TAB");
checkState(state, "xbar", ["xfoo", "xbar"], 1, 1);
state = await msg("key", "VK_TAB");
checkState(state, "xbar", [], -1);
await msg("reset");
});
add_task(async function cycleSuggestions() {
await focusContentSearchBar();
await msg("key", { key: "x", waitForSuggestions: true });
let cycle = async function (aSelectedButtonIndex) {
let modifiers = {
shiftKey: true,
accelKey: true,
};
let state = await msg("key", { key: "VK_DOWN", modifiers });
checkState(state, "xfoo", ["xfoo", "xbar"], 0, aSelectedButtonIndex);
state = await msg("key", { key: "VK_DOWN", modifiers });
checkState(state, "xbar", ["xfoo", "xbar"], 1, aSelectedButtonIndex);
state = await msg("key", { key: "VK_DOWN", modifiers });
checkState(state, "x", ["xfoo", "xbar"], -1, aSelectedButtonIndex);
state = await msg("key", { key: "VK_DOWN", modifiers });
checkState(state, "xfoo", ["xfoo", "xbar"], 0, aSelectedButtonIndex);
state = await msg("key", { key: "VK_UP", modifiers });
checkState(state, "x", ["xfoo", "xbar"], -1, aSelectedButtonIndex);
state = await msg("key", { key: "VK_UP", modifiers });
checkState(state, "xbar", ["xfoo", "xbar"], 1, aSelectedButtonIndex);
state = await msg("key", { key: "VK_UP", modifiers });
checkState(state, "xfoo", ["xfoo", "xbar"], 0, aSelectedButtonIndex);
state = await msg("key", { key: "VK_UP", modifiers });
checkState(state, "x", ["xfoo", "xbar"], -1, aSelectedButtonIndex);
};
await cycle();
// Repeat with a one-off selected.
let state = await msg("key", "VK_TAB");
checkState(state, "x", ["xfoo", "xbar"], 2);
await cycle(0);
// Repeat with the settings button selected.
state = await msg("key", "VK_TAB");
checkState(state, "x", ["xfoo", "xbar"], 3);
await cycle(1);
await msg("reset");
});
add_task(async function cycleOneOffs() {
await focusContentSearchBar();
await msg("key", { key: "x", waitForSuggestions: true });
await SpecialPowers.spawn(gBrowser.selectedBrowser, [], () => {
let btn =
Cu.waiveXrays(content).gController._oneOffButtons[
Cu.waiveXrays(content).gController._oneOffButtons.length - 1
];
let newBtn = btn.cloneNode(true);
btn.parentNode.appendChild(newBtn);
Cu.waiveXrays(content).gController._oneOffButtons.push(newBtn);
});
let state = await msg("key", "VK_DOWN");
state = await msg("key", "VK_DOWN");
checkState(state, "xbar", ["xfoo", "xbar"], 1);
let modifiers = {
altKey: true,
};
state = await msg("key", { key: "VK_DOWN", modifiers });
checkState(state, "xbar", ["xfoo", "xbar"], 1, 0);
state = await msg("key", { key: "VK_DOWN", modifiers });
checkState(state, "xbar", ["xfoo", "xbar"], 1, 1);
state = await msg("key", { key: "VK_DOWN", modifiers });
checkState(state, "xbar", ["xfoo", "xbar"], 1);
state = await msg("key", { key: "VK_UP", modifiers });
checkState(state, "xbar", ["xfoo", "xbar"], 1, 1);
state = await msg("key", { key: "VK_UP", modifiers });
checkState(state, "xbar", ["xfoo", "xbar"], 1, 0);
state = await msg("key", { key: "VK_UP", modifiers });
checkState(state, "xbar", ["xfoo", "xbar"], 1);
// If the settings button is selected, pressing alt+up/down should select the
// last/first one-off respectively (and deselect the settings button).
await msg("key", "VK_TAB");
await msg("key", "VK_TAB");
state = await msg("key", "VK_TAB"); // Settings button selected.
checkState(state, "xbar", ["xfoo", "xbar"], 1, 2);
state = await msg("key", { key: "VK_UP", modifiers });
checkState(state, "xbar", ["xfoo", "xbar"], 1, 1);
state = await msg("key", "VK_TAB");
checkState(state, "xbar", ["xfoo", "xbar"], 1, 2);
state = await msg("key", { key: "VK_DOWN", modifiers });
checkState(state, "xbar", ["xfoo", "xbar"], 1, 0);
await SpecialPowers.spawn(gBrowser.selectedBrowser, [], () => {
Cu.waiveXrays(content).gController._oneOffButtons.pop().remove();
});
await msg("reset");
});
add_task(async function mouse() {
await focusContentSearchBar();
let state = await msg("key", { key: "x", waitForSuggestions: true });
checkState(state, "x", ["xfoo", "xbar"], -1);
state = await msg("mousemove", 0);
checkState(state, "x", ["xfoo", "xbar"], 0);
state = await msg("mousemove", 1);
checkState(state, "x", ["xfoo", "xbar"], 1);
state = await msg("mousemove", 2);
checkState(state, "x", ["xfoo", "xbar"], 2, 0);
state = await msg("mousemove", 3);
checkState(state, "x", ["xfoo", "xbar"], 3, 1);
state = await msg("mousemove", -1);
checkState(state, "x", ["xfoo", "xbar"], -1);
await msg("reset");
await focusContentSearchBar();
state = await msg("key", { key: "x", waitForSuggestions: true });
checkState(state, "x", ["xfoo", "xbar"], -1);
state = await msg("mousemove", 0);
checkState(state, "x", ["xfoo", "xbar"], 0);
state = await msg("mousemove", 2);
checkState(state, "x", ["xfoo", "xbar"], 2, 0);
state = await msg("mousemove", -1);
checkState(state, "x", ["xfoo", "xbar"], -1);
await msg("reset");
});
add_task(async function formHistory() {
await focusContentSearchBar();
// Type an X and add it to form history.
let state = await msg("key", { key: "x", waitForSuggestions: true });
checkState(state, "x", ["xfoo", "xbar"], -1);
// Wait for Satchel to say it's been added to form history.
let observePromise = new Promise(resolve => {
Services.obs.addObserver(function onAdd(subj, topic, data) {
if (data == "formhistory-add") {
Services.obs.removeObserver(onAdd, "satchel-storage-changed");
executeSoon(resolve);
}
}, "satchel-storage-changed");
});
await FormHistoryTestUtils.clear("searchbar-history");
let entry = await SpecialPowers.spawn(gBrowser.selectedBrowser, [], () => {
return Cu.waiveXrays(content).gController.addInputValueToFormHistory();
});
await observePromise;
Assert.greater(
await FormHistoryTestUtils.count("searchbar-history", {
source: entry.source,
}),
0
);
// Reset the input.
state = await msg("reset");
checkState(state, "", [], -1);
// Type an X again. The form history entry should appear.
state = await msg("key", { key: "x", waitForSuggestions: true });
checkState(
state,
"x",
[{ str: "x", type: "formHistory" }, "xfoo", "xbar"],
-1
);
// Select the form history entry and delete it.
state = await msg("key", "VK_DOWN");
checkState(
state,
"x",
[{ str: "x", type: "formHistory" }, "xfoo", "xbar"],
0
);
// Wait for Satchel.
observePromise = new Promise(resolve => {
Services.obs.addObserver(function onRemove(subj, topic, data) {
if (data == "formhistory-remove") {
Services.obs.removeObserver(onRemove, "satchel-storage-changed");
executeSoon(resolve);
}
}, "satchel-storage-changed");
});
state = await msg("key", "VK_DELETE");
checkState(state, "x", ["xfoo", "xbar"], -1);
await observePromise;
// Reset the input.
state = await msg("reset");
checkState(state, "", [], -1);
// Type an X again. The form history entry should still be gone.
state = await msg("key", { key: "x", waitForSuggestions: true });
checkState(state, "x", ["xfoo", "xbar"], -1);
await msg("reset");
});
add_task(async function formHistory_limit() {
info("Check long strings are not added to form history");
await focusContentSearchBar();
const gLongString = new Array(
SearchSuggestionController.SEARCH_HISTORY_MAX_VALUE_LENGTH + 1
)
.fill("x")
.join("");
// Type and confirm a very long string.
let state = await msg("text", {
value: gLongString,
waitForSuggestions: true,
});
checkState(
state,
gLongString,
[`${gLongString}foo`, `${gLongString}bar`],
-1
);
await FormHistoryTestUtils.clear("searchbar-history");
let entry = await SpecialPowers.spawn(gBrowser.selectedBrowser, [], () => {
return Cu.waiveXrays(content).gController.addInputValueToFormHistory();
});
// There's nothing we can wait for, since addition should not be happening.
/* eslint-disable mozilla/no-arbitrary-setTimeout */
await new Promise(resolve => setTimeout(resolve, 500));
Assert.equal(
await FormHistoryTestUtils.count("searchbar-history", {
source: entry.source,
}),
0
);
await msg("reset");
});
add_task(async function cycleEngines() {
await focusContentSearchBar();
await msg("key", { key: "VK_DOWN", waitForSuggestions: true });
Services.telemetry.clearEvents();
Services.fog.testResetFOG();
let p = SearchTestUtils.promiseSearchNotification(
"engine-default",
"browser-search-engine-modified"
);
await msg("key", { key: "VK_DOWN", modifiers: { accelKey: true } });
let newEngine = await p;
Assert.equal(
newEngine.name,
TEST_ENGINE2.name,
"Should have correctly cycled the engine"
);
TelemetryTestUtils.assertEvents(
[
{
object: "change_default",
value: "user_searchbar",
extra: {
prev_id: TEST_ENGINE1.id,
new_id: TEST_ENGINE2.id,
new_name: TEST_ENGINE2.name,
new_load_path: TEST_ENGINE2.loadPath,
new_sub_url: "",
},
},
],
{ category: "search", method: "engine" }
);
let snapshot = await Glean.searchEngineDefault.changed.testGetValue();
delete snapshot[0].timestamp;
Assert.deepEqual(
snapshot[0],
{
category: "search.engine.default",
name: "changed",
extra: {
new_load_path: TEST_ENGINE2.loadPath,
previous_engine_id: TEST_ENGINE1.id,
change_source: "user_searchbar",
new_engine_id: TEST_ENGINE2.id,
new_display_name: TEST_ENGINE2.name,
new_submission_url: "",
},
},
"Should have received the correct event details"
);
p = SearchTestUtils.promiseSearchNotification(
"engine-default",
"browser-search-engine-modified"
);
await msg("key", { key: "VK_UP", modifiers: { accelKey: true } });
newEngine = await p;
Assert.equal(
newEngine.name,
TEST_ENGINE1.name,
"Should have correctly cycled the engine"
);
TelemetryTestUtils.assertEvents(
[
{
object: "change_default",
value: "user_searchbar",
extra: {
prev_id: TEST_ENGINE2.id,
new_id: TEST_ENGINE1.id,
new_name: TEST_ENGINE1.name,
new_load_path: TEST_ENGINE1.loadPath,
new_sub_url: "",
},
},
],
{ category: "search", method: "engine" }
);
snapshot = await Glean.searchEngineDefault.changed.testGetValue();
delete snapshot[1].timestamp;
Assert.deepEqual(
snapshot[1],
{
category: "search.engine.default",
name: "changed",
extra: {
new_load_path: TEST_ENGINE1.loadPath,
previous_engine_id: TEST_ENGINE2.id,
change_source: "user_searchbar",
new_engine_id: TEST_ENGINE1.id,
new_display_name: TEST_ENGINE1.name,
new_submission_url: "",
},
},
"Should have received the correct event details"
);
await msg("reset");
});
add_task(async function search() {
await focusContentSearchBar();
let modifiers = {};
["altKey", "ctrlKey", "metaKey", "shiftKey"].forEach(
k => (modifiers[k] = true)
);
// Test typing a query and pressing enter.
let p = waitForSearch();
await msg("key", { key: "x", waitForSuggestions: true });
await msg("key", { key: "VK_RETURN", modifiers });
let mesg = await p;
let eventData = {
engineName: TEST_ENGINE1.name,
searchString: "x",
healthReportKey: "test",
searchPurpose: "test",
originalEvent: modifiers,
};
SimpleTest.isDeeply(eventData, mesg, "Search event data");
await promiseTab();
await focusContentSearchBar();
// Test typing a query, then selecting a suggestion and pressing enter.
p = waitForSearch();
await msg("key", { key: "x", waitForSuggestions: true });
await msg("key", "VK_DOWN");
await msg("key", "VK_DOWN");
await msg("key", { key: "VK_RETURN", modifiers });
mesg = await p;
eventData.searchString = "xfoo";
eventData.engineName = TEST_ENGINE1.name;
eventData.selection = {
index: 1,
kind: "key",
};
SimpleTest.isDeeply(eventData, mesg, "Search event data");
await promiseTab();
await focusContentSearchBar();
// Test typing a query, then selecting a one-off button and pressing enter.
p = waitForSearch();
await msg("key", { key: "x", waitForSuggestions: true });
await msg("key", "VK_UP");
await msg("key", "VK_UP");
await msg("key", { key: "VK_RETURN", modifiers });
mesg = await p;
delete eventData.selection;
eventData.searchString = "x";
eventData.engineName = TEST_ENGINE2.name;
SimpleTest.isDeeply(eventData, mesg, "Search event data");
await promiseTab();
await focusContentSearchBar();
// Test typing a query and clicking the search engine header.
p = waitForSearch();
modifiers.button = 0;
await msg("key", { key: "x", waitForSuggestions: true });
await msg("mousemove", -1);
await msg("click", { eltIdx: -1, modifiers });
mesg = await p;
eventData.originalEvent = modifiers;
eventData.engineName = TEST_ENGINE1.name;
SimpleTest.isDeeply(eventData, mesg, "Search event data");
await promiseTab();
await focusContentSearchBar();
// Test typing a query and then clicking a suggestion.
await msg("key", { key: "x", waitForSuggestions: true });
p = waitForSearch();
await msg("mousemove", 1);
await msg("click", { eltIdx: 1, modifiers });
mesg = await p;
eventData.searchString = "xfoo";
eventData.selection = {
index: 1,
kind: "mouse",
};
SimpleTest.isDeeply(eventData, mesg, "Search event data");
await promiseTab();
await focusContentSearchBar();
// Test typing a query and then clicking a one-off button.
await msg("key", { key: "x", waitForSuggestions: true });
p = waitForSearch();
await msg("mousemove", 3);
await msg("click", { eltIdx: 3, modifiers });
mesg = await p;
eventData.searchString = "x";
eventData.engineName = TEST_ENGINE2.name;
delete eventData.selection;
SimpleTest.isDeeply(eventData, mesg, "Search event data");
await promiseTab();
await focusContentSearchBar();
// Test selecting a suggestion, then clicking a one-off without deselecting the
// suggestion, using the keyboard.
delete modifiers.button;
await msg("key", { key: "x", waitForSuggestions: true });
p = waitForSearch();
await msg("key", "VK_DOWN");
await msg("key", "VK_DOWN");
await msg("key", "VK_TAB");
await msg("key", { key: "VK_RETURN", modifiers });
mesg = await p;
eventData.searchString = "xfoo";
eventData.selection = {
index: 1,
kind: "key",
};
SimpleTest.isDeeply(eventData, mesg, "Search event data");
await promiseTab();
await focusContentSearchBar();
// Test searching when using IME composition.
let state = await msg("startComposition", { data: "" });
checkState(state, "", [], -1);
state = await msg("changeComposition", {
data: "x",
waitForSuggestions: true,
});
checkState(
state,
"x",
[
{ str: "x", type: "formHistory" },
{ str: "xfoo", type: "formHistory" },
"xbar",
],
-1
);
await msg("commitComposition");
delete modifiers.button;
p = waitForSearch();
await msg("key", { key: "VK_RETURN", modifiers });
mesg = await p;
eventData.searchString = "x";
eventData.originalEvent = modifiers;
eventData.engineName = TEST_ENGINE1.name;
delete eventData.selection;
SimpleTest.isDeeply(eventData, mesg, "Search event data");
await promiseTab();
await focusContentSearchBar();
state = await msg("startComposition", { data: "" });
checkState(state, "", [], -1);
state = await msg("changeComposition", {
data: "x",
waitForSuggestions: true,
});
checkState(
state,
"x",
[
{ str: "x", type: "formHistory" },
{ str: "xfoo", type: "formHistory" },
"xbar",
],
-1
);
// Mouse over the first suggestion.
state = await msg("mousemove", 0);
checkState(
state,
"x",
[
{ str: "x", type: "formHistory" },
{ str: "xfoo", type: "formHistory" },
"xbar",
],
0
);
// Mouse over the second suggestion.
state = await msg("mousemove", 1);
checkState(
state,
"x",
[
{ str: "x", type: "formHistory" },
{ str: "xfoo", type: "formHistory" },
"xbar",
],
1
);
modifiers.button = 0;
p = waitForSearch();
await msg("click", { eltIdx: 1, modifiers });
mesg = await p;
eventData.searchString = "xfoo";
eventData.originalEvent = modifiers;
eventData.selection = {
index: 1,
kind: "mouse",
};
SimpleTest.isDeeply(eventData, mesg, "Search event data");
await promiseTab();
await focusContentSearchBar();
// Remove form history entries.
// Wait for Satchel.
let observePromise = new Promise(resolve => {
let historyCount = 2;
Services.obs.addObserver(function onRemove(subj, topic, data) {
if (data == "formhistory-remove") {
if (--historyCount) {
return;
}
Services.obs.removeObserver(onRemove, "satchel-storage-changed");
executeSoon(resolve);
}
}, "satchel-storage-changed");
});
await msg("key", { key: "x", waitForSuggestions: true });
await msg("key", "VK_DOWN");
await msg("key", "VK_DOWN");
await msg("key", "VK_DELETE");
await msg("key", "VK_DOWN");
await msg("key", "VK_DELETE");
await observePromise;
await msg("reset");
state = await msg("key", { key: "x", waitForSuggestions: true });
checkState(state, "x", ["xfoo", "xbar"], -1);
await promiseTab();
await focusContentSearchBar();
await msg("reset");
});
add_task(async function settings() {
await focusContentSearchBar();
await msg("key", { key: "VK_DOWN", waitForSuggestions: true });
await msg("key", "VK_UP");
let p = waitForSearchSettings();
await msg("key", "VK_RETURN");
await p;
await msg("reset");
});
function checkState(
actualState,
expectedInputVal,
expectedSuggestions,
expectedSelectedIdx,
expectedSelectedButtonIdx
) {
expectedSuggestions = expectedSuggestions.map(sugg => {
return typeof sugg == "object"
? sugg
: {
str: sugg,
type: "remote",
};
});
if (expectedSelectedIdx == -1 && expectedSelectedButtonIdx != undefined) {
expectedSelectedIdx =
expectedSuggestions.length + expectedSelectedButtonIdx;
}
let expectedState = {
selectedIndex: expectedSelectedIdx,
numSuggestions: expectedSuggestions.length,
suggestionAtIndex: expectedSuggestions.map(s => s.str),
isFormHistorySuggestionAtIndex: expectedSuggestions.map(
s => s.type == "formHistory"
),
tableHidden: !expectedSuggestions.length,
inputValue: expectedInputVal,
ariaExpanded: !expectedSuggestions.length ? "false" : "true",
};
if (expectedSelectedButtonIdx != undefined) {
expectedState.selectedButtonIndex = expectedSelectedButtonIdx;
} else if (expectedSelectedIdx < expectedSuggestions.length) {
expectedState.selectedButtonIndex = -1;
} else {
expectedState.selectedButtonIndex =
expectedSelectedIdx - expectedSuggestions.length;
}
SimpleTest.isDeeply(actualState, expectedState, "State");
}
var gMsgMan;
var tabs = [];
async function promiseTab() {
let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
tabs.push(tab);
let loadedPromise = BrowserTestUtils.firstBrowserLoaded(window);
openTrustedLinkIn("about:test-about-content-search-ui", "current");
await loadedPromise;
}