From 6bf0a5cb5034a7e684dcc3500e841785237ce2dd Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 19:32:43 +0200 Subject: Adding upstream version 1:115.7.0. Signed-off-by: Daniel Baumann --- .../search/test/browser/browser_contentSearchUI.js | 1158 ++++++++++++++++++++ 1 file changed, 1158 insertions(+) create mode 100644 browser/components/search/test/browser/browser_contentSearchUI.js (limited to 'browser/components/search/test/browser/browser_contentSearchUI.js') diff --git a/browser/components/search/test/browser/browser_contentSearchUI.js b/browser/components/search/test/browser/browser_contentSearchUI.js new file mode 100644 index 0000000000..9196b1355c --- /dev/null +++ b/browser/components/search/test/browser/browser_contentSearchUI.js @@ -0,0 +1,1158 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +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, { + ContentSearch: "resource:///actors/ContentSearchParent.sys.mjs", + FormHistoryTestUtils: + "resource://testing-common/FormHistoryTestUtils.sys.mjs", + SearchSuggestionController: + "resource://gre/modules/SearchSuggestionController.sys.mjs", +}); + +const pageURL = getRootDirectory(gTestPath) + TEST_PAGE_BASENAME; +BrowserTestUtils.registerAboutPage( + registerCleanupFunction, + "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 +); + +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 () { + 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: + "https://example.com/browser/browser/components/search/test/browser/searchSuggestionEngine.sjs", + suggest_url_get_params: "query={searchTerms}", + }, + { setAsDefault: true } + ); + extension2 = await SearchTestUtils.installSearchExtension({ + name: TEST_ENGINE2.name, + suggest_url: + "https://example.com/browser/browser/components/search/test/browser/searchSuggestionEngine.sjs", + suggest_url_get_params: "query={searchTerms}", + }); + + for (let engine of currentEngines) { + await Services.search.removeEngine(engine); + } + + registerCleanupFunction(async () => { + ContentSearch._onMessageSearch = originalOnMessageSearch; + ContentSearch._onMessageManageEngines = originalOnMessageManageEngines; + }); + + 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"); +}); + +add_task(async function cleanup() { + Services.search.restoreDefaultEngines(); +}); + +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; + +async function promiseTab() { + let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser); + registerCleanupFunction(() => BrowserTestUtils.removeTab(tab)); + + let loadedPromise = BrowserTestUtils.firstBrowserLoaded(window); + openTrustedLinkIn("about:test-about-content-search-ui", "current"); + await loadedPromise; +} -- cgit v1.2.3