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 --- .../modules/test/browser/browser_ContentSearch.js | 519 +++++++++++++++++++++ 1 file changed, 519 insertions(+) create mode 100644 browser/modules/test/browser/browser_ContentSearch.js (limited to 'browser/modules/test/browser/browser_ContentSearch.js') diff --git a/browser/modules/test/browser/browser_ContentSearch.js b/browser/modules/test/browser/browser_ContentSearch.js new file mode 100644 index 0000000000..0e489a54a1 --- /dev/null +++ b/browser/modules/test/browser/browser_ContentSearch.js @@ -0,0 +1,519 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +ChromeUtils.defineESModuleGetters(this, { + SearchTestUtils: "resource://testing-common/SearchTestUtils.sys.mjs", +}); + +SearchTestUtils.init(this); + +const SERVICE_EVENT_TYPE = "ContentSearchService"; +const CLIENT_EVENT_TYPE = "ContentSearchClient"; + +var arrayBufferIconTested = false; +var plainURIIconTested = false; + +function sendEventToContent(browser, data) { + return SpecialPowers.spawn( + browser, + [CLIENT_EVENT_TYPE, data], + (eventName, eventData) => { + content.dispatchEvent( + new content.CustomEvent(eventName, { + detail: Cu.cloneInto(eventData, content), + }) + ); + } + ); +} + +add_setup(async function () { + await SpecialPowers.pushPrefEnv({ + set: [ + ["browser.newtab.preload", false], + ["browser.search.separatePrivateDefault.ui.enabled", true], + ["browser.search.separatePrivateDefault", true], + ], + }); + + await SearchTestUtils.promiseNewSearchEngine({ + url: "chrome://mochitests/content/browser/browser/components/search/test/browser/testEngine.xml", + setAsDefault: true, + }); + + await SearchTestUtils.promiseNewSearchEngine({ + url: "chrome://mochitests/content/browser/browser/components/search/test/browser/testEngine_diacritics.xml", + setAsDefaultPrivate: true, + }); + + await SearchTestUtils.promiseNewSearchEngine({ + url: getRootDirectory(gTestPath) + "testEngine_chromeicon.xml", + }); +}); + +add_task(async function GetState() { + let { browser } = await addTab(); + let statePromise = await waitForTestMsg(browser, "State"); + sendEventToContent(browser, { + type: "GetState", + }); + let msg = await statePromise.donePromise; + checkMsg(msg, { + type: "State", + data: await currentStateObj(false), + }); + + ok(arrayBufferIconTested, "ArrayBuffer path for the iconData was tested"); + ok(plainURIIconTested, "Plain URI path for the iconData was tested"); +}); + +add_task(async function SetDefaultEngine() { + let { browser } = await addTab(); + let newDefaultEngine = await Services.search.getEngineByName("FooChromeIcon"); + let oldDefaultEngine = await Services.search.getDefault(); + let searchPromise = await waitForTestMsg(browser, "CurrentEngine"); + sendEventToContent(browser, { + type: "SetCurrentEngine", + data: newDefaultEngine.name, + }); + let deferredPromise = new Promise(resolve => { + Services.obs.addObserver(function obs(subj, topic, data) { + info("Test observed " + data); + if (data == "engine-default") { + ok(true, "Test observed engine-default"); + Services.obs.removeObserver(obs, "browser-search-engine-modified"); + resolve(); + } + }, "browser-search-engine-modified"); + }); + info("Waiting for test to observe engine-default..."); + await deferredPromise; + let msg = await searchPromise.donePromise; + checkMsg(msg, { + type: "CurrentEngine", + data: await constructEngineObj(newDefaultEngine), + }); + + let enginePromise = await waitForTestMsg(browser, "CurrentEngine"); + await Services.search.setDefault( + oldDefaultEngine, + Ci.nsISearchService.CHANGE_REASON_UNKNOWN + ); + msg = await enginePromise.donePromise; + checkMsg(msg, { + type: "CurrentEngine", + data: await constructEngineObj(oldDefaultEngine), + }); +}); + +// ContentSearchChild doesn't support setting the private engine at this time +// as it doesn't need to, so we just test updating the default here. +add_task(async function setDefaultEnginePrivate() { + const engine = await Services.search.getEngineByName("FooChromeIcon"); + const { browser } = await addTab(); + let enginePromise = await waitForTestMsg(browser, "CurrentPrivateEngine"); + await Services.search.setDefaultPrivate( + engine, + Ci.nsISearchService.CHANGE_REASON_UNKNOWN + ); + let msg = await enginePromise.donePromise; + checkMsg(msg, { + type: "CurrentPrivateEngine", + data: await constructEngineObj(engine), + }); +}); + +add_task(async function modifyEngine() { + let { browser } = await addTab(); + let engine = await Services.search.getDefault(); + let oldAlias = engine.alias; + let statePromise = await waitForTestMsg(browser, "CurrentState"); + engine.alias = "ContentSearchTest"; + let msg = await statePromise.donePromise; + checkMsg(msg, { + type: "CurrentState", + data: await currentStateObj(), + }); + statePromise = await waitForTestMsg(browser, "CurrentState"); + engine.alias = oldAlias; + msg = await statePromise.donePromise; + checkMsg(msg, { + type: "CurrentState", + data: await currentStateObj(), + }); +}); + +add_task(async function test_hideEngine() { + let { browser } = await addTab(); + let engine = await Services.search.getEngineByName("Foo \u2661"); + let statePromise = await waitForTestMsg(browser, "CurrentState"); + Services.prefs.setStringPref("browser.search.hiddenOneOffs", engine.name); + let msg = await statePromise.donePromise; + checkMsg(msg, { + type: "CurrentState", + data: await currentStateObj(undefined, "Foo \u2661"), + }); + statePromise = await waitForTestMsg(browser, "CurrentState"); + Services.prefs.clearUserPref("browser.search.hiddenOneOffs"); + msg = await statePromise.donePromise; + checkMsg(msg, { + type: "CurrentState", + data: await currentStateObj(), + }); +}); + +add_task(async function search() { + let { browser } = await addTab(); + let engine = await Services.search.getDefault(); + let data = { + engineName: engine.name, + searchString: "ContentSearchTest", + healthReportKey: "ContentSearchTest", + searchPurpose: "ContentSearchTest", + }; + let submissionURL = engine.getSubmission(data.searchString, "", data.whence) + .uri.spec; + + await performSearch(browser, data, submissionURL); +}); + +add_task(async function searchInBackgroundTab() { + // This test is like search(), but it opens a new tab after starting a search + // in another. In other words, it performs a search in a background tab. The + // search page should be loaded in the same tab that performed the search, in + // the background tab. + let { browser } = await addTab(); + let engine = await Services.search.getDefault(); + let data = { + engineName: engine.name, + searchString: "ContentSearchTest", + healthReportKey: "ContentSearchTest", + searchPurpose: "ContentSearchTest", + }; + let submissionURL = engine.getSubmission(data.searchString, "", data.whence) + .uri.spec; + + let searchPromise = performSearch(browser, data, submissionURL); + let newTab = BrowserTestUtils.addTab(gBrowser); + gBrowser.selectedTab = newTab; + registerCleanupFunction(() => gBrowser.removeTab(newTab)); + + await searchPromise; +}); + +add_task(async function badImage() { + let { browser } = await addTab(); + // If the bad image URI caused an exception to be thrown within ContentSearch, + // then we'll hang waiting for the CurrentState responses triggered by the new + // engine. That's what we're testing, and obviously it shouldn't happen. + let vals = await waitForNewEngine(browser, "contentSearchBadImage.xml"); + let engine = vals[0]; + let finalCurrentStateMsg = vals[vals.length - 1]; + let expectedCurrentState = await currentStateObj(); + let expectedEngine = expectedCurrentState.engines.find( + e => e.name == engine.name + ); + ok(!!expectedEngine, "Sanity check: engine should be in expected state"); + ok( + expectedEngine.iconData === + "chrome://browser/skin/search-engine-placeholder.png", + "Sanity check: icon of engine in expected state should be the placeholder: " + + expectedEngine.iconData + ); + checkMsg(finalCurrentStateMsg, { + type: "CurrentState", + data: expectedCurrentState, + }); + // Removing the engine triggers a final CurrentState message. Wait for it so + // it doesn't trip up subsequent tests. + let statePromise = await waitForTestMsg(browser, "CurrentState"); + await Services.search.removeEngine(engine); + await statePromise.donePromise; +}); + +add_task( + async function GetSuggestions_AddFormHistoryEntry_RemoveFormHistoryEntry() { + let { browser } = await addTab(); + + // Add the test engine that provides suggestions. + let vals = await waitForNewEngine(browser, "contentSearchSuggestions.xml"); + let engine = vals[0]; + + let searchStr = "browser_ContentSearch.js-suggestions-"; + + // Add a form history suggestion and wait for Satchel to notify about it. + sendEventToContent(browser, { + type: "AddFormHistoryEntry", + data: { + value: searchStr + "form", + engineName: engine.name, + }, + }); + await 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"); + }); + + // Send GetSuggestions using the test engine. Its suggestions should appear + // in the remote suggestions in the Suggestions response below. + let suggestionsPromise = await waitForTestMsg(browser, "Suggestions"); + sendEventToContent(browser, { + type: "GetSuggestions", + data: { + engineName: engine.name, + searchString: searchStr, + }, + }); + + // Check the Suggestions response. + let msg = await suggestionsPromise.donePromise; + checkMsg(msg, { + type: "Suggestions", + data: { + engineName: engine.name, + searchString: searchStr, + formHistory: [searchStr + "form"], + remote: [searchStr + "foo", searchStr + "bar"], + }, + }); + + // Delete the form history suggestion and wait for Satchel to notify about it. + sendEventToContent(browser, { + type: "RemoveFormHistoryEntry", + data: searchStr + "form", + }); + + await 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"); + }); + + // Send GetSuggestions again. + suggestionsPromise = await waitForTestMsg(browser, "Suggestions"); + sendEventToContent(browser, { + type: "GetSuggestions", + data: { + engineName: engine.name, + searchString: searchStr, + }, + }); + + // The formHistory suggestions in the Suggestions response should be empty. + msg = await suggestionsPromise.donePromise; + checkMsg(msg, { + type: "Suggestions", + data: { + engineName: engine.name, + searchString: searchStr, + formHistory: [], + remote: [searchStr + "foo", searchStr + "bar"], + }, + }); + + // Finally, clean up by removing the test engine. + let statePromise = await waitForTestMsg(browser, "CurrentState"); + await Services.search.removeEngine(engine); + await statePromise.donePromise; + } +); + +async function performSearch(browser, data, expectedURL) { + let stoppedPromise = BrowserTestUtils.browserStopped(browser, expectedURL); + sendEventToContent(browser, { + type: "Search", + data, + expectedURL, + }); + + await stoppedPromise; + // BrowserTestUtils.browserStopped should ensure this, but let's + // be absolutely sure. + Assert.equal( + browser.currentURI.spec, + expectedURL, + "Correct search page loaded" + ); +} + +function buffersEqual(actualArrayBuffer, expectedArrayBuffer) { + let expectedView = new Int8Array(expectedArrayBuffer); + let actualView = new Int8Array(actualArrayBuffer); + for (let i = 0; i < expectedView.length; i++) { + if (actualView[i] != expectedView[i]) { + return false; + } + } + return true; +} + +function arrayBufferEqual(actualArrayBuffer, expectedArrayBuffer) { + ok(actualArrayBuffer instanceof ArrayBuffer, "Actual value is ArrayBuffer."); + ok( + expectedArrayBuffer instanceof ArrayBuffer, + "Expected value is ArrayBuffer." + ); + Assert.equal( + actualArrayBuffer.byteLength, + expectedArrayBuffer.byteLength, + "Array buffers have the same length." + ); + ok( + buffersEqual(actualArrayBuffer, expectedArrayBuffer), + "Buffers are equal." + ); +} + +function checkArrayBuffers(actual, expected) { + if (actual instanceof ArrayBuffer) { + arrayBufferEqual(actual, expected); + } + if (typeof actual == "object") { + for (let i in actual) { + checkArrayBuffers(actual[i], expected[i]); + } + } +} + +function checkMsg(actualMsg, expectedMsgData) { + SimpleTest.isDeeply(actualMsg, expectedMsgData, "Checking message"); + + // Engines contain ArrayBuffers which we have to compare byte by byte and + // not as Objects (like SimpleTest.isDeeply does). + checkArrayBuffers(actualMsg, expectedMsgData); +} + +async function waitForTestMsg(browser, type, count = 1) { + await SpecialPowers.spawn( + browser, + [SERVICE_EVENT_TYPE, type, count], + (childEvent, childType, childCount) => { + content.eventDetails = []; + function listener(event) { + if (event.detail.type != childType) { + return; + } + + content.eventDetails.push(event.detail); + + if (--childCount > 0) { + return; + } + + content.removeEventListener(childEvent, listener, true); + } + content.addEventListener(childEvent, listener, true); + } + ); + + let donePromise = SpecialPowers.spawn( + browser, + [type, count], + async (childType, childCount) => { + await ContentTaskUtils.waitForCondition(() => { + return content.eventDetails.length == childCount; + }, "Expected " + childType + " event"); + + return childCount > 1 ? content.eventDetails : content.eventDetails[0]; + } + ); + + return { donePromise }; +} + +async function waitForNewEngine(browser, basename) { + info("Waiting for engine to be added: " + basename); + + // Wait for the search events triggered by adding the new engine. + // There are two events triggerd by engine-added and engine-loaded + let statePromise = await waitForTestMsg(browser, "CurrentState", 2); + + // Wait for addOpenSearchEngine(). + let engine = await SearchTestUtils.promiseNewSearchEngine({ + url: getRootDirectory(gTestPath) + basename, + }); + let results = await statePromise.donePromise; + return [engine, ...results]; +} + +async function addTab() { + let tab = await BrowserTestUtils.openNewForegroundTab( + gBrowser, + "about:newtab" + ); + registerCleanupFunction(() => gBrowser.removeTab(tab)); + + return { browser: tab.linkedBrowser }; +} + +var currentStateObj = async function (isPrivateWindowValue, hiddenEngine = "") { + let state = { + engines: [], + currentEngine: await constructEngineObj(await Services.search.getDefault()), + currentPrivateEngine: await constructEngineObj( + await Services.search.getDefaultPrivate() + ), + }; + for (let engine of await Services.search.getVisibleEngines()) { + let uri = engine.getIconURLBySize(16, 16); + state.engines.push({ + name: engine.name, + iconData: await iconDataFromURI(uri), + hidden: engine.name == hiddenEngine, + isAppProvided: engine.isAppProvided, + }); + } + if (typeof isPrivateWindowValue == "boolean") { + state.isInPrivateBrowsingMode = isPrivateWindowValue; + state.isAboutPrivateBrowsing = isPrivateWindowValue; + } + return state; +}; + +async function constructEngineObj(engine) { + let uriFavicon = engine.getIconURLBySize(16, 16); + return { + name: engine.name, + iconData: await iconDataFromURI(uriFavicon), + isAppProvided: engine.isAppProvided, + }; +} + +function iconDataFromURI(uri) { + if (!uri) { + return Promise.resolve( + "chrome://browser/skin/search-engine-placeholder.png" + ); + } + + if (!uri.startsWith("data:")) { + plainURIIconTested = true; + return Promise.resolve(uri); + } + + return new Promise(resolve => { + let xhr = new XMLHttpRequest(); + xhr.open("GET", uri, true); + xhr.responseType = "arraybuffer"; + xhr.onerror = () => { + resolve("chrome://browser/skin/search-engine-placeholder.png"); + }; + xhr.onload = () => { + arrayBufferIconTested = true; + resolve(xhr.response); + }; + try { + xhr.send(); + } catch (err) { + resolve("chrome://browser/skin/search-engine-placeholder.png"); + } + }); +} -- cgit v1.2.3